From af87f9f571c67db3b01bad1ef2b4d84d0ae25aee Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Sun, 29 Mar 2026 14:02:34 -0400 Subject: [PATCH] Design shared data models for native platforms - Define core entities: FeedItem, FeedSubscription, SearchHistoryItem - Document entity relationships with diagram - Create database schema with SQLite and FTS5 - Provide type mapping guide for Swift/Kotlin/C - Define value types vs reference types - Document enumeration types for all platforms - Specify serialization/deserialization requirements - Outline indexing strategy for performance - Include memory considerations and future extensibility --- .../02-design-shared-data-models.md | 506 ++++++++++++++++++ 1 file changed, 506 insertions(+) create mode 100644 tasks/native-business-logic-migration/02-design-shared-data-models.md diff --git a/tasks/native-business-logic-migration/02-design-shared-data-models.md b/tasks/native-business-logic-migration/02-design-shared-data-models.md new file mode 100644 index 0000000..bb85287 --- /dev/null +++ b/tasks/native-business-logic-migration/02-design-shared-data-models.md @@ -0,0 +1,506 @@ +# 02. Design shared data models for all platforms + +meta: + id: native-business-logic-migration-02 + feature: native-business-logic-migration + priority: P0 + depends_on: [native-business-logic-migration-01] + tags: [design, data-models] + +objective: +- Design platform-agnostic data models that can be implemented natively on each platform + +deliverables: +- Data model specification document +- Entity relationship diagram +- Type mapping guide (TypeScript → Swift/Kotlin/C) +- Database schema design + +steps: +- Review TypeScript types from src/types/feed.ts +- Identify core entities: Feed, FeedItem, FeedSubscription, SearchResult +- Design normalized database schema +- Create type mapping documentation +- Define value types vs reference types +- Design enumeration types for each platform +- Document serialization/deserialization requirements + +acceptance_criteria: +- All data models documented with properties and types +- Entity relationships clearly defined +- Type mapping guide complete for all three platforms +- Database schema designed with proper indexing +- Value types identified (Date, UUID, Enums) + +notes: +- Use UUID for identifiers across all platforms +- Date handling needs platform-specific implementation +- Consider memory footprint for large feed items +- Plan for future extensibility + +--- + +# Shared Data Models Design + +## Overview + +This document defines platform-agnostic data models for RSSuper, enabling native implementations on iOS (Swift), Android (Kotlin), and Linux (C). The models are designed for SQLite persistence with full-text search support. + +--- + +## 1. Core Entities + +### 1.1 FeedItem + +Represents a single article or episode from a feed. + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| id | UUID | Yes | Unique identifier | +| title | String | Yes | Item title | +| link | String | No | URL to full article | +| description | String | No | Short summary | +| content | String | No | Full content (may be large) | +| author | String | No | Author name | +| published | DateTime | No | Publication date | +| updated | DateTime | No | Last update date | +| categories | String[] | No | Category tags | +| enclosure | Enclosure | No | Media attachment | +| guid | String | No | Global unique identifier from feed | +| subscriptionId | UUID | Yes | Parent subscription | +| subscriptionTitle | String | No | Denormalized for display | + +#### Enclosure (nested) + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| url | String | Yes | Media URL | +| type | String | Yes | MIME type (audio/mpeg, image/jpeg, etc.) | +| length | Int64 | No | File size in bytes | + +### 1.2 FeedSubscription + +Represents a feed subscription with configuration. + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| id | UUID | Yes | Unique identifier | +| url | String | Yes | Feed URL (unique) | +| title | String | Yes | Display title | +| category | String | No | User-defined category | +| enabled | Boolean | Yes | Active/inactive | +| fetchInterval | Int32 | Yes | Fetch interval in minutes | +| createdAt | DateTime | Yes | Creation timestamp | +| updatedAt | DateTime | Yes | Last modification | +| lastFetchedAt | DateTime | No | Last successful fetch | +| nextFetchAt | DateTime | No | Next scheduled fetch | +| error | String | No | Last error message | +| httpAuth | HttpAuth | No | HTTP authentication | + +#### HttpAuth (nested) + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| username | String | Yes | Username | +| password | String | Yes | Password (should be encrypted) | + +### 1.3 SearchHistoryItem + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| id | UUID | Yes | Unique identifier | +| query | String | Yes | Search query text | +| timestamp | DateTime | Yes | When query was executed | + +--- + +## 2. Entity Relationship Diagram + +``` +┌─────────────────────┐ ┌─────────────────────┐ +│ FeedSubscription │ │ SearchHistory │ +├─────────────────────┤ ├─────────────────────┤ +│ id (PK) │ │ id (PK) │ +│ url (UNIQUE) │ │ query │ +│ title │ │ timestamp │ +│ category │ └─────────────────────┘ +│ enabled │ +│ fetch_interval │ ┌─────────────────────┐ +│ created_at │ │ FeedItem │ +│ updated_at │ ├─────────────────────┤ +│ last_fetched_at │ │ id (PK) │ +│ next_fetch_at │──────┐ │ subscription_id(FK)│◄──┐ +│ error │ │ │ title │ │ +│ http_auth_username │ │ │ link │ │ +│ http_auth_password │ │ │ description │ │ +└─────────────────────┘ │ │ content │ │ + │ │ author │ │ + │ │ published │ │ + │ │ updated │ │ + │ │ guid (UNIQUE) │ │ + │ │ enclosure_url │ │ + │ │ enclosure_type │ │ + │ │ enclosure_length │ │ + │ │ categories (JSON) │ │ + │ │ created_at │ │ + │ └─────────────────────┘ │ + │ │ + └────────────────────────────────┘ +``` + +--- + +## 3. Database Schema Design + +### 3.1 subscriptions + +```sql +CREATE TABLE subscriptions ( + id TEXT PRIMARY KEY, + url TEXT UNIQUE NOT NULL, + title TEXT NOT NULL, + category TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + fetch_interval INTEGER NOT NULL DEFAULT 60, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + last_fetched_at INTEGER, + next_fetch_at INTEGER, + error TEXT, + http_auth_username TEXT, + http_auth_password TEXT +); + +CREATE INDEX idx_subscriptions_url ON subscriptions(url); +CREATE INDEX idx_subscriptions_category ON subscriptions(category); +CREATE INDEX idx_subscriptions_next_fetch ON subscriptions(next_fetch_at) WHERE enabled = 1; +CREATE INDEX idx_subscriptions_enabled ON subscriptions(enabled); +``` + +### 3.2 feed_items + +```sql +CREATE TABLE feed_items ( + id TEXT PRIMARY KEY, + subscription_id TEXT NOT NULL, + title TEXT NOT NULL, + link TEXT, + description TEXT, + content TEXT, + author TEXT, + published INTEGER, + updated INTEGER, + guid TEXT, + enclosure_url TEXT, + enclosure_type TEXT, + enclosure_length INTEGER, + categories TEXT, + created_at INTEGER NOT NULL, + FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE +); + +CREATE UNIQUE INDEX idx_feed_items_guid ON feed_items(guid) WHERE guid IS NOT NULL; +CREATE INDEX idx_feed_items_subscription ON feed_items(subscription_id); +CREATE INDEX idx_feed_items_published ON feed_items(published DESC); +CREATE INDEX idx_feed_items_created ON feed_items(created_at DESC); +``` + +### 3.3 search_history + +```sql +CREATE TABLE search_history ( + id TEXT PRIMARY KEY, + query TEXT NOT NULL, + timestamp INTEGER NOT NULL +); + +CREATE INDEX idx_search_history_timestamp ON search_history(timestamp DESC); +``` + +### 3.4 Full-Text Search (FTS5) + +```sql +CREATE VIRTUAL TABLE feed_items_fts USING fts5( + title, + description, + content, + author, + categories, + content='feed_items', + content_rowid='rowid' +); + +CREATE VIRTUAL TABLE subscriptions_fts USING fts5( + title, + url, + category, + content='subscriptions', + content_rowid='rowid' +); +``` + +--- + +## 4. Type Mapping Guide + +### 4.1 Primitive Types + +| TypeScript | Swift (Kotlin/C) | SQLite | Notes | +|------------|------------------|--------|-------| +| `string` | `String` (`String`/`char*`) | TEXT | UTF-8 encoded | +| `number` | `Int`/`Double` (`Int`/`Double`/`int64_t`) | INTEGER/REAL | Use REAL for decimals | +| `boolean` | `Bool` (`Boolean`/`int`) | INTEGER | 0 or 1 | +| `Date` | `Date` (`LocalDateTime`/`time_t`) | INTEGER | Unix timestamp (milliseconds) | +| `UUID` | `UUID` (`UUID`/`uuid_t`) | TEXT | String format: "550e8400-e29b-41d4-a716-446655440000" | +| `string[]` | `[String]` (`List`/`char**`) | TEXT | JSON array | + +### 4.2 Core Model Mapping + +#### FeedItem + +| Property | TypeScript | Swift | Kotlin | C (struct) | +|----------|------------|-------|--------|------------| +| id | `string` | `String` | `String` | `char id[37]` | +| title | `string` | `String` | `String` | `char title[512]` | +| link | `string?` | `String?` | `String?` | `char* link` | +| description | `string?` | `String?` | `String?` | `char* description` | +| content | `string?` | `String?` | `String?` | `char* content` | +| author | `string?` | `String?` | `String?` | `char* author` | +| published | `Date?` | `Date?` | `Long?` | `int64_t published` | +| updated | `Date?` | `Date?` | `Long?` | `int64_t updated` | +| categories | `string[]?` | `[String]?` | `String?` | `char* categories` | +| enclosure | `Enclosure?` | `Enclosure?` | `Enclosure?` | `Enclosure* enclosure` | +| guid | `string?` | `String?` | `String?` | `char* guid` | +| subscriptionId | `string` | `String` | `String` | `char subscription_id[37]` | + +#### FeedSubscription + +| Property | TypeScript | Swift | Kotlin | C (struct) | +|----------|------------|-------|--------|------------| +| id | `string` | `String` | `String` | `char id[37]` | +| url | `string` | `String` | `String` | `char url[2048]` | +| title | `string` | `String` | `String` | `char title[512]` | +| category | `string?` | `String?` | `String?` | `char* category` | +| enabled | `boolean` | `Bool` | `Boolean` | `int enabled` | +| fetchInterval | `number` | `Int` | `Int` | `int fetch_interval` | +| createdAt | `Date` | `Date` | `Long` | `int64_t created_at` | +| updatedAt | `Date` | `Date` | `Long` | `int64_t updated_at` | +| lastFetchedAt | `Date?` | `Date?` | `Long?` | `int64_t last_fetched_at` | +| nextFetchAt | `Date?` | `Date?` | `Long?` | `int64_t next_fetch_at` | +| error | `string?` | `String?` | `String?` | `char* error` | +| httpAuth | `HttpAuth?` | `HttpAuth?` | `HttpAuth?` | `HttpAuth* http_auth` | + +--- + +## 5. Value Types vs Reference Types + +### 5.1 Value Types (Copy on assign) + +All primitive types should be treated as **value types**: +- UUID (string representation) +- Date (Unix timestamp) +- Boolean +- Integer +- Float + +### 5.2 Reference Types (Pointer/Heap) + +Complex types should be **reference types**: +- FeedItem (stored by ID reference) +- FeedSubscription (stored by ID reference) +- Enclosure (inline in FeedItem) +- HttpAuth (inline in FeedSubscription) + +### 5.3 Implementation Notes + +**Swift**: Use `struct` for value types, `class` for reference types +**Kotlin**: Use `data class` for all models, pass by value +**C**: Use struct with explicit memory management + +--- + +## 6. Enumeration Types + +### 6.1 ContentType + +```typescript +type ContentType = 'article' | 'audio' | 'video'; +``` + +| TypeScript | Swift | Kotlin | C | +|------------|-------|--------|---| +| `'article'` | `ContentType.article` | `ContentType.ARTICLE` | `CONTENT_TYPE_ARTICLE` | +| `'audio'` | `ContentType.audio` | `ContentType.AUDIO` | `CONTENT_TYPE_AUDIO` | +| `'video'` | `ContentType.video` | `ContentType.VIDEO` | `CONTENT_TYPE_VIDEO` | + +### 6.2 Theme + +```typescript +type Theme = 'system' | 'light' | 'dark'; +``` + +| TypeScript | Swift | Kotlin | C | +|------------|-------|--------|---| +| `'system'` | `Theme.system` | `Theme.SYSTEM` | `THEME_SYSTEM` | +| `'light'` | `Theme.light` | `Theme.LIGHT` | `THEME_LIGHT` | +| `'dark'` | `Theme.dark` | `Theme.DARK` | `THEME_DARK` | + +### 6.3 Privacy + +```typescript +type Privacy = 'public' | 'private' | 'anonymous'; +``` + +| TypeScript | Swift | Kotlin | C | +|------------|-------|--------|---| +| `'public'` | `Privacy.public` | `Privacy.PUBLIC` | `PRIVACY_PUBLIC` | +| `'private'` | `Privacy.private` | `Privacy.PRIVATE` | `PRIVACY_PRIVATE` | +| `'anonymous'` | `Privacy.anonymous` | `Privacy.ANONYMOUS` | `PRIVACY_ANONYMOUS` | + +### 6.4 NotificationType + +```typescript +enum NotificationType { + NEW_ARTICLE = 'NEW_ARTICLE', + EPISODE_RELEASE = 'EPISODE_RELEASE', + CUSTOM_ALERT = 'CUSTOM_ALERT', + UPGRADE_PROMO = 'UPGRADE_PROMO' +} +``` + +| TypeScript | Swift | Kotlin | C | +|------------|-------|--------|---| +| `NEW_ARTICLE` | `NotificationType.newArticle` | `NotificationType.NEW_ARTICLE` | `NOTIFICATION_TYPE_NEW_ARTICLE` | +| `EPISODE_RELEASE` | `NotificationType.episodeRelease` | `NotificationType.EPISODE_RELEASE` | `NOTIFICATION_TYPE_EPISODE_RELEASE` | +| `CUSTOM_ALERT` | `NotificationType.customAlert` | `NotificationType.CUSTOM_ALERT` | `NOTIFICATION_TYPE_CUSTOM_ALERT` | +| `UPGRADE_PROMO` | `NotificationType.upgradePromo` | `NotificationType.UPGRADE_PROMO` | `NOTIFICATION_TYPE_UPGRADE_PROMO` | + +--- + +## 7. Serialization/Deserialization + +### 7.1 Date Handling + +All dates stored as **Unix timestamp (milliseconds)** in SQLite. Convert to/from platform-specific types: + +| Platform | Conversion | +|----------|------------| +| Swift | `Date(timeIntervalSince1970: timestamp / 1000)` | +| Kotlin | `Date(timestamp)` | +| C | `time_t` with `timestamp / 1000` | + +### 7.2 JSON Fields + +Arrays and nested objects stored as JSON strings: + +```sql +-- Categories stored as JSON array +categories = '["tech", "programming", "rust"]' + +-- Enclosure stored as JSON object +enclosure = '{"url": "...", "type": "audio/mpeg", "length": 1234567}' +``` + +### 7.3 UUID Handling + +Store UUIDs as strings in canonical format: +``` +550e8400-e29b-41d4-a716-446655440000 +``` + +Generate using platform UUID libraries: +- Swift: `UUID().uuidString` +- Kotlin: `UUID.randomUUID().toString()` +- C: Use `uuid_generate()` from libuuid + +--- + +## 8. Indexing Strategy + +### 8.1 Primary Indexes +- `subscriptions.id` (auto-created for PRIMARY KEY) +- `feed_items.id` (auto-created for PRIMARY KEY) + +### 8.2 Secondary Indexes +| Table | Index | Purpose | +|-------|-------|---------| +| subscriptions | `idx_subscriptions_url` | URL lookups | +| subscriptions | `idx_subscriptions_category` | Category filtering | +| subscriptions | `idx_subscriptions_next_fetch` | Sync scheduling | +| feed_items | `idx_feed_items_guid` | Deduplication | +| feed_items | `idx_feed_items_subscription` | Feed-specific items | +| feed_items | `idx_feed_items_published` | Sorting by date | +| search_history | `idx_search_history_timestamp` | History sorting | + +### 8.3 FTS Indexes +| Virtual Table | Purpose | +|---------------|---------| +| `feed_items_fts` | Full-text search on articles | +| `subscriptions_fts` | Full-text search on subscriptions | + +--- + +## 9. Memory Considerations + +### 9.1 Large Content Handling + +- `content` field can be very large (full article HTML) +- Use lazy loading for content display +- Consider compressing content with zlib for storage +- Limit in-memory cache to N most recent items + +### 9.2 Streaming for Large Result Sets + +Use cursor-based pagination: +```sql +-- Instead of OFFSET +SELECT * FROM feed_items +WHERE subscription_id = ? +ORDER BY published DESC +LIMIT 20; +-- Use last_published as cursor for next page +``` + +--- + +## 10. Future Extensibility + +### 10.1 Schema Versioning + +Add version table for migrations: + +```sql +CREATE TABLE schema_version ( + version INTEGER PRIMARY KEY, + applied_at INTEGER NOT NULL +); +``` + +### 10.2 Adding New Fields + +- New optional fields: Add column with NULL default +- New required fields: Use migration with DEFAULT values +- Breaking changes: New table with shared ID + +### 10.3 Feed Extensions + +Support for future feed types: +- Podcast: Already supported via enclosure +- Video feeds: Use content type detection +- Media RSS: Extend enclosure model + +--- + +## 11. Implementation Checklist + +- [ ] Define UUID type alias for each platform +- [ ] Create Date conversion utilities +- [ ] Implement FeedItem model (struct/class) +- [ ] Implement FeedSubscription model +- [ ] Implement SearchHistoryItem model +- [ ] Create SQLite table definitions +- [ ] Implement FTS5 triggers for feed_items +- [ ] Implement FTS5 triggers for subscriptions +- [ ] Add database indexes +- [ ] Write serialization/deserialization code +- [ ] Implement pagination helpers \ No newline at end of file