Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/BushelCloud/.gitrepo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
remote = git@github.com:brightdigit/BushelCloud.git
branch = mistkit
commit = f13f8695a0ca4b041db1e775c239d6cc8b258fb2
parent = 1ec3d15919adf827cd144f948fc31e822c60ab99
parent = 95d49423328d8db2778fbf440deea16e651305c8
method = merge
cmdver = 0.4.9
6 changes: 6 additions & 0 deletions Examples/BushelCloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ export CLOUDKIT_PRIVATE_KEY_PATH="./path/to/private-key.pem"
# Optional: Enable VirtualBuddy TSS signing status
export VIRTUALBUDDY_API_KEY="YOUR_VIRTUALBUDDY_API_KEY"

# Optional: Enable VirtualBuddy TSS signing status
export VIRTUALBUDDY_API_KEY="YOUR_VIRTUALBUDDY_API_KEY"

# Sync with verbose logging to learn how MistKit works
.build/debug/bushel-cloud sync --verbose

Expand Down Expand Up @@ -490,6 +493,9 @@ export CLOUDKIT_PRIVATE_KEY_PATH="$HOME/.cloudkit/bushel-private-key.pem"
# Optional: VirtualBuddy TSS signing status (get from https://tss.virtualbuddy.app/)
export VIRTUALBUDDY_API_KEY="YOUR_VIRTUALBUDDY_API_KEY"

# Optional: VirtualBuddy TSS signing status (get from https://tss.virtualbuddy.app/)
export VIRTUALBUDDY_API_KEY="YOUR_VIRTUALBUDDY_API_KEY"

# Then simply run
bushel-cloud sync
```
Expand Down
2 changes: 1 addition & 1 deletion Examples/CelestraCloud/.gitrepo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
remote = git@github.com:brightdigit/CelestraCloud.git
branch = mistkit
commit = 319bc6208763c31706b9e10c3608d9f7cc5c2ef5
parent = 10cf4510ab393ad1b4277832fcd8441e32fe0b65
parent = 95d49423328d8db2778fbf440deea16e651305c8
method = merge
cmdver = 0.4.9
28 changes: 14 additions & 14 deletions Examples/CelestraCloud/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@ let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("WarnUnsafeReflection"),

// Enhanced compiler checking
.unsafeFlags([
// Enable concurrency warnings
"-warn-concurrency",
// Enable actor data race checks
"-enable-actor-data-race-checks",
// Complete strict concurrency checking
"-strict-concurrency=complete",
// Enable testing support
"-enable-testing",
// Warn about functions with >100 lines
"-Xfrontend", "-warn-long-function-bodies=100",
// Warn about slow type checking expressions
"-Xfrontend", "-warn-long-expression-type-checking=100"
])
// .unsafeFlags([
// // Enable concurrency warnings
// "-warn-concurrency",
// // Enable actor data race checks
// "-enable-actor-data-race-checks",
// // Complete strict concurrency checking
// "-strict-concurrency=complete",
// // Enable testing support
// "-enable-testing",
// // Warn about functions with >100 lines
// "-Xfrontend", "-warn-long-function-bodies=100",
// // Warn about slow type checking expressions
// "-Xfrontend", "-warn-long-expression-type-checking=100"
// ])
]

let package = Package(
Expand Down
42 changes: 42 additions & 0 deletions Examples/CelestraCloud/Scripts/setup-cloudkit-schema.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@

set -eo pipefail

# Parse command line arguments
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run)
DRY_RUN=true
shift
;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --dry-run Validate schema without importing"
echo " --help, -h Show this help message"
echo ""
echo "Environment variables:"
echo " CLOUDKIT_CONTAINER_ID CloudKit container ID (default: iCloud.com.brightdigit.Bushel)"
echo " CLOUDKIT_TEAM_ID Apple Developer Team ID (10-character)"
echo " CLOUDKIT_ENVIRONMENT Environment (development or production, default: development)"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
Expand All @@ -14,6 +43,9 @@ NC='\033[0m' # No Color
echo "========================================"
echo "CloudKit Schema Setup for Celestra"
echo "========================================"
if [ "$DRY_RUN" = true ]; then
echo "(DRY RUN MODE - No changes will be made)"
fi
echo ""

# Check if cktool is available
Expand Down Expand Up @@ -108,6 +140,15 @@ fi

echo ""

# Skip import if dry-run
if [ "$DRY_RUN" = true ]; then
echo ""
echo -e "${GREEN}✓✓✓ Dry run complete! ✓✓✓${NC}"
echo ""
echo "Schema validation passed. Run without --dry-run to import."
exit 0
fi

# Confirm before import
echo -e "${YELLOW}Warning: This will import the schema into your CloudKit container.${NC}"
echo "This operation will create/modify record types in the $ENVIRONMENT environment."
Expand Down Expand Up @@ -140,6 +181,7 @@ if xcrun cktool import-schema \
echo " a. Go to: https://icloud.developer.apple.com/dashboard/"
echo " b. Navigate to: API Access → Server-to-Server Keys"
echo " c. Create a new key and download the private key .pem file"
echo " d. Store it securely (e.g., ~/.cloudkit/bushel-private-key.pem)"
echo ""
echo " 2. Configure your .env file with CloudKit credentials"
echo " 3. Run 'swift run celestra add-feed <url>' to add an RSS feed"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,22 @@ public struct ArticleCloudKitService: Sendable {
_ guids: [String],
feedRecordName: String?
) async throws(CloudKitError) -> [Article] {
// CloudKit Web Services has issues with combining .in() with other filters.
// Current approach: Use .in() ONLY for GUID filtering (single filter, no combinations).
// Feed filtering is done in-memory (line 135-136) to avoid the .in() + filter issue.
//
// Known limitation: Cannot efficiently query by both GUID and feedRecordName in one query.
// This is acceptable because GUID queries are typically small batches (<150 items).
//
// Alternative considered: Multiple single-GUID queries would be significantly slower
// and hit rate limits faster. The in-memory filter is the pragmatic solution.
let filters: [QueryFilter] = [.in("guid", guids.map { FieldValue.string($0) })]
// Query articles by GUID using the IN filter.
// Now that issue #192 is fixed, we can combine .in() with other filters.
// If feedRecordName is specified, we filter at query time for efficiency.
var filters: [QueryFilter] = [.in("guid", guids.map { FieldValue.string($0) })]
if let feedName = feedRecordName {
filters.append(.equals("feedRecordName", FieldValue.string(feedName)))
}

let records = try await recordOperator.queryRecords(
recordType: "Article",
filters: filters,
sortBy: nil,
limit: 200,
desiredKeys: nil
)
let articles = records.compactMap { record in
return records.compactMap { record in
do {
return try Article(from: record)
} catch {
Expand All @@ -137,12 +135,6 @@ public struct ArticleCloudKitService: Sendable {
return nil
}
}

// Filter by feedRecordName in-memory if specified
if let feedName = feedRecordName {
return articles.filter { $0.feedRecordName == feedName }
}
return articles
}

// MARK: - Create Operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,11 @@ public struct ArticleSyncService: Sendable {
feedRecordName: String
) async throws(CloudKitError) -> ArticleSyncResult {
// 1. Query existing articles by GUID
// TEMPORARY: Skip GUID query due to CloudKit Web Services .in() operator issue
// TODO: Fix query or implement alternative deduplication strategy
let existingArticles: [Article] = []
// let guids = items.map(\.guid)
// let existingArticles = try await articleService.queryArticlesByGUIDs(
// guids,
// feedRecordName: feedRecordName
// )
let guids = items.map(\.guid)
let existingArticles = try await articleService.queryArticlesByGUIDs(
guids,
feedRecordName: feedRecordName
)

// 2. Categorize into new vs modified (pure function)
let categorization = categorizer.categorize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,27 +114,25 @@ extension ArticleCloudKitService {
let mock = MockCloudKitRecordOperator()
let service = CelestraCloudKit.ArticleCloudKitService(recordOperator: mock)

// Mock returns 2 articles: one matching feed, one not
// Now that issue #192 is fixed, feedRecordName filter is applied at query time.
// Mock returns only the matching article (CloudKit would filter server-side).
let matchingFields = createArticleRecordFields(guid: "guid-1")
let nonMatchingFields = createArticleRecordFields(guid: "guid-2")
.merging(["feedRecordName": .string("other-feed")]) { _, new in new }

mock.queryRecordsResult = .success([
createMockRecordInfo(recordName: "article-1", fields: matchingFields),
createMockRecordInfo(recordName: "article-2", fields: nonMatchingFields)
createMockRecordInfo(recordName: "article-1", fields: matchingFields)
])

let result = try await service.queryArticlesByGUIDs(
["guid-1", "guid-2"],
feedRecordName: "feed-123"
)

// Verify CloudKit query behavior
// Verify CloudKit query combines filters
#expect(mock.queryCalls.count == 1)
// Should have 1 filter (GUID only), feedRecordName filtered in-memory
#expect(mock.queryCalls[0].filters?.count == 1)
// Should have 2 filters: IN for GUID + EQUALS for feedRecordName
#expect(mock.queryCalls[0].filters?.count == 2)

// Verify in-memory filtering works
// Verify filtering at query time works correctly
#expect(result.count == 1) // Only matching article returned
#expect(result[0].guid == "guid-1")
#expect(result[0].feedRecordName == "feed-123")
Expand Down
12 changes: 6 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("WarnUnsafeReflection"),

// Enhanced compiler checking
// .unsafeFlags([
// // Warn about functions with >100 lines
// "-Xfrontend", "-warn-long-function-bodies=100",
// // Warn about slow type checking expressions
// "-Xfrontend", "-warn-long-expression-type-checking=100"
// ])
.unsafeFlags([
// Warn about functions with >100 lines
"-Xfrontend", "-warn-long-function-bodies=100",
// Warn about slow type checking expressions
"-Xfrontend", "-warn-long-expression-type-checking=100"
])
]

let package = Package(
Expand Down
Loading