-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Overview
Enable iOS app users to add new feeds to the shared public catalog with immediate article display. Requires implementing feed/article creation with proper deduplication and race condition handling.
Background
Schema updated to allow _creator (authenticated users) to CREATE Feed and Article records:
- Users can add new feeds from iOS app
- Users cannot modify feeds after creation (only server can WRITE)
- iOS app must parse RSS and create articles on first add for immediate display
- Server continues to handle all subsequent updates via scheduled jobs
Implementation Requirements
1. Feed Deduplication
Problem: Prevent duplicate feeds when multiple users add the same feed URL simultaneously.
Solution: Use deterministic recordName based on feedURL
// Generate recordName from feedURL
func recordNameForFeed(_ feedURL: String) -> String {
let hash = SHA256.hash(data: Data(feedURL.utf8))
let hashString = hash.compactMap { String(format: "%02x", $0) }.joined()
return "feed-\(hashString.prefix(16))"
}Benefits:
- CloudKit enforces recordName uniqueness automatically
- Duplicate feed attempts fail gracefully with
CKError.serverRecordChanged - No race conditions - first write wins
Implementation:
func addFeed(url: String) async throws {
// 1. Check if feed already exists
let recordName = recordNameForFeed(url)
do {
let existing = try await cloudKit.fetchRecord(recordName: recordName, recordType: "Feed")
// Feed already exists - show user existing feed/articles
return existing
} catch {
// Feed doesn't exist - continue to create
}
// 2. Parse RSS feed
let parsedFeed = try await RSSParser.parse(url: url)
// 3. Create Feed record with deterministic recordName
let feedRecord = CKRecord(recordType: "Feed", recordID: CKRecord.ID(recordName: recordName))
feedRecord["feedURL"] = url
feedRecord["title"] = parsedFeed.title
// ... other fields
try await cloudKit.save(feedRecord)
// 4. Create articles
try await createArticles(parsedFeed.articles, feedRecordName: recordName)
}2. Article Deduplication
Problem: Prevent duplicate articles when multiple users add the same feed or server updates overlap with user-created articles.
Current Server Behavior (UpdateCommand.swift:192-236):
- Server queries existing articles by GUID before creating
- Only creates articles with new GUIDs
- Updates articles if contentHash changed
iOS App Requirements:
- Must use same GUID-based deduplication logic
- Query existing articles before creating
- Handle partial success (some articles may already exist)
Implementation:
func createArticles(_ articles: [ParsedArticle], feedRecordName: String) async throws {
// 1. Extract GUIDs from parsed articles
let guids = articles.map { $0.guid }
// 2. Query existing articles with those GUIDs
let existingArticles = try await queryArticlesByGUIDs(guids, feedRecordName: feedRecordName)
let existingGUIDs = Set(existingArticles.map { $0.guid })
// 3. Filter to only new articles
let newArticles = articles.filter { !existingGUIDs.contains($0.guid) }
// 4. Create only new articles
let records = newArticles.map { article -> CKRecord in
let record = CKRecord(recordType: "Article")
record["feedRecordName"] = feedRecordName
record["guid"] = article.guid
record["title"] = article.title
record["url"] = article.url
record["content"] = article.content
record["contentHash"] = article.contentHash
record["publishedTimestamp"] = article.publishedDate
record["fetchedTimestamp"] = Date()
// ... other fields
return record
}
// 5. Batch save (handle partial failures gracefully)
try await cloudKit.saveRecords(records)
}3. Error Handling
Feed Already Exists:
catch let error as CKError where error.code == .serverRecordChanged {
// Feed was created by another user between check and create
// Fetch the existing feed and show to user
return try await cloudKit.fetchRecord(recordName: recordName, recordType: "Feed")
}RSS Parse Failures:
- Invalid feed URL → Show user-friendly error
- Network timeout → Retry with exponential backoff
- Malformed RSS → Show validation error with details
Partial Article Creation Failures:
- Some articles fail validation → Skip invalid articles, create valid ones
- Network errors → Retry failed articles
- Log failures for debugging
4. User Experience
Add Feed Flow:
- User enters feed URL
- Show loading indicator "Parsing feed..."
- Display feed preview (title, description, article count)
- User confirms → Create feed + articles
- Navigate to feed view with articles immediately visible
Feedback:
- ✅ "Feed added successfully! 25 articles loaded."
⚠️ "Feed already in catalog. Showing existing articles."- ❌ "Failed to parse feed: Invalid RSS format"
Testing Checklist
- Add new feed → Articles appear immediately
- Add duplicate feed → Graceful error, show existing feed
- Race condition: Two users add same feed → First wins, second sees existing
- Invalid feed URL → User-friendly error message
- Network timeout during RSS parse → Retry logic works
- Malformed RSS → Validation error displayed
- Partial article creation failure → Valid articles created, invalid skipped
- Server update after user-created articles → No duplicates, existing articles updated if changed
Dependencies
- CelestraKit package (Feed, Article models)
- RSS parsing library (SyndiKit or equivalent)
- CloudKit framework
- SHA256 hashing for recordName generation
Related Issues
- Server-side duplicate handling improvements (see separate issue)
References
- Schema:
schema.ckdb(lines 43-50, 85-94) - Server deduplication logic:
UpdateCommand.swift:192-236 - CloudKit permissions model:
CLAUDE.md