|
| 1 | +// |
| 2 | +// AppSettingsSync.swift |
| 3 | +// InterlinedList |
| 4 | +// |
| 5 | + |
| 6 | +import Foundation |
| 7 | + |
| 8 | +/// The app's key in the settings-sync service. Contract: |
| 9 | +/// https://interlinedlist.com/help/api/app-settings |
| 10 | +enum AppSettingsKey { |
| 11 | + static let appKey = "il-ios" |
| 12 | + static let platform = "ios" |
| 13 | + /// Bump when the meaning of a stored field changes so an older client can tell. |
| 14 | + static let schemaVersion = 1 |
| 15 | + /// Server cap (`MAX_SETTINGS_BYTES`). Enforced client-side too — a write above |
| 16 | + /// it is a 413, which is a bug in what we chose to store, not a transient error. |
| 17 | + static let maxSettingsBytes = 64 * 1024 |
| 18 | +} |
| 19 | + |
| 20 | +/// The account-level (shared) settings iOS syncs. Deliberately small: preferences a |
| 21 | +/// second device should inherit, never cached content. |
| 22 | +/// |
| 23 | +/// Anything phone-specific (per-device UI state, local-only toggles) belongs in the |
| 24 | +/// per-device document instead, which is why this type carries none of it. |
| 25 | +struct AppSharedSettings: Codable, Equatable { |
| 26 | + var theme: String? |
| 27 | + var defaultPubliclyVisible: Bool? |
| 28 | + var showAdvancedPostSettings: Bool? |
| 29 | + var viewingPreference: String? |
| 30 | + var messagesPerPage: Int? |
| 31 | + var showPreviews: Bool? |
| 32 | + var notificationTrayLimit: Int? |
| 33 | + |
| 34 | + static let empty = AppSharedSettings() |
| 35 | +} |
| 36 | + |
| 37 | +/// One settings document as the service stores it. `settings` is an opaque blob the |
| 38 | +/// server round-trips byte-for-byte, so it is modelled generically. |
| 39 | +struct SettingsDoc<Settings: Codable>: Codable { |
| 40 | + let appKey: String |
| 41 | + let scope: String? |
| 42 | + let deviceId: String? |
| 43 | + let version: Int |
| 44 | + let updatedAt: String? |
| 45 | + let schemaVersion: Int? |
| 46 | + let settings: Settings |
| 47 | +} |
| 48 | + |
| 49 | +/// `GET …/bootstrap` — where a fresh install should start from. |
| 50 | +struct SettingsBootstrap<Settings: Codable>: Codable { |
| 51 | + let source: String |
| 52 | + let version: Int? |
| 53 | + let schemaVersion: Int? |
| 54 | + let settings: Settings? |
| 55 | + let defaultDeviceId: String? |
| 56 | + let defaultDeviceName: String? |
| 57 | +} |
| 58 | + |
| 59 | +struct AppDevice: Codable, Identifiable { |
| 60 | + let deviceId: String |
| 61 | + let deviceName: String? |
| 62 | + let platform: String? |
| 63 | + let isDefault: Bool? |
| 64 | + let lastSeenAt: String? |
| 65 | + let appVersion: String? |
| 66 | + let osVersion: String? |
| 67 | + |
| 68 | + var id: String { deviceId } |
| 69 | +} |
| 70 | + |
| 71 | +struct AppDevicesResponse: Codable { |
| 72 | + let devices: [AppDevice] |
| 73 | +} |
| 74 | + |
| 75 | +struct AppDeviceResponse: Codable { |
| 76 | + let device: AppDevice |
| 77 | +} |
| 78 | + |
| 79 | +/// Raised when the server rejects a write because someone else wrote first. The |
| 80 | +/// caller must re-read and re-apply — last-write-wins is wrong here, and the server |
| 81 | +/// hands back the winning document so no extra round trip is needed. |
| 82 | +struct SettingsVersionConflict<Settings: Codable>: Error { |
| 83 | + let current: SettingsDoc<Settings>? |
| 84 | +} |
0 commit comments