# 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<String>`/`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