Refactor data sources and feed model

This commit is contained in:
2026-01-10 00:25:36 +00:00
parent 1e65a3f57d
commit 324b35a464
15 changed files with 631 additions and 609 deletions

View File

@@ -10,7 +10,7 @@ import Foundation
final class FeedStore {
struct CardKey: Hashable, Codable {
let id: String
let type: WinnerType
let type: FeedItemType
}
struct CardState: Codable, Equatable {
@@ -58,17 +58,17 @@ final class FeedStore {
}
}
func lastShownAt(candidateId: String) -> Int? {
func lastShownAt(feedItemId: String) -> Int? {
queue.sync {
let matches = states.compactMap { (key, value) -> Int? in
guard key.hasSuffix("|" + candidateId) else { return nil }
guard key.hasSuffix("|" + feedItemId) else { return nil }
return value.lastShownAt
}
return matches.max()
}
}
func isSuppressed(id: String, type: WinnerType, now: Int) -> Bool {
func isSuppressed(id: String, type: FeedItemType, now: Int) -> Bool {
queue.sync {
let key = Self.keyString(id: id, type: type)
guard let state = states[key] else { return false }
@@ -78,7 +78,7 @@ final class FeedStore {
}
}
func dismiss(id: String, type: WinnerType, until: Int? = nil) {
func dismiss(id: String, type: FeedItemType, until: Int? = nil) {
queue.sync {
let key = Self.keyString(id: id, type: type)
var state = states[key] ?? CardState()
@@ -88,7 +88,7 @@ final class FeedStore {
}
}
func snooze(id: String, type: WinnerType, until: Int) {
func snooze(id: String, type: FeedItemType, until: Int) {
queue.sync {
let key = Self.keyString(id: id, type: type)
var state = states[key] ?? CardState()
@@ -98,7 +98,7 @@ final class FeedStore {
}
}
func clearSuppression(id: String, type: WinnerType) {
func clearSuppression(id: String, type: FeedItemType) {
queue.sync {
let key = Self.keyString(id: id, type: type)
var state = states[key] ?? CardState()
@@ -119,11 +119,11 @@ final class FeedStore {
}
private func normalizedFeed(_ feed: FeedEnvelope, now: Int, applyingSuppression: Bool = true) -> FeedEnvelope {
let normalizedCards = feed.feed.compactMap { card -> FeedCard? in
let normalizedCards = feed.feed.compactMap { card -> FeedItem? in
let ttl = max(1, card.ttlSec)
if feed.generatedAt + ttl <= now { return nil }
if applyingSuppression, isSuppressedUnlocked(id: card.id, type: card.type, now: now) { return nil }
return FeedCard(
return FeedItem(
id: card.id,
type: card.type,
title: card.title.truncated(maxLength: TextConstraints.titleMax),
@@ -131,6 +131,7 @@ final class FeedStore {
priority: min(max(card.priority, 0.0), 1.0),
ttlSec: ttl,
condition: card.condition,
startsAt: card.startsAt,
bucket: card.bucket,
actions: card.actions
)
@@ -146,7 +147,7 @@ final class FeedStore {
return normalized
}
private func isSuppressedUnlocked(id: String, type: WinnerType, now: Int) -> Bool {
private func isSuppressedUnlocked(id: String, type: FeedItemType, now: Int) -> Bool {
let key = Self.keyString(id: id, type: type)
guard let state = states[key] else { return false }
if let until = state.dismissedUntil, until > now { return true }
@@ -159,7 +160,7 @@ final class FeedStore {
Self.save(persisted, to: fileURL)
}
private static func keyString(id: String, type: WinnerType) -> String {
private static func keyString(id: String, type: FeedItemType) -> String {
"\(type.rawValue)|\(id)"
}