Skip to main content

## MIGRATION_001

# Constant MIGRATION_001

Source

    const MIGRATION_001: &str = r#"
    -- Jellyfin servers the user has connected to
    CREATE TABLE IF NOT EXISTS servers (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        url TEXT NOT NULL UNIQUE,
        version TEXT,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        last_connected_at TEXT
    );

    -- User accounts on Jellyfin servers
    CREATE TABLE IF NOT EXISTS users (
        id TEXT PRIMARY KEY,
        server_id TEXT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
        username TEXT NOT NULL,
        access_token TEXT,
        is_active INTEGER DEFAULT 0,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        last_login_at TEXT,
        UNIQUE(server_id, username)
    );

    -- Libraries/views from Jellyfin
    CREATE TABLE IF NOT EXISTS libraries (
        id TEXT PRIMARY KEY,
        server_id TEXT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
        name TEXT NOT NULL,
        collection_type TEXT,
        image_tag TEXT,
        sort_order INTEGER DEFAULT 0,
        synced_at TEXT,
        UNIQUE(server_id, id)
    );

    -- Media items (movies, shows, episodes, albums, songs, artists)
    CREATE TABLE IF NOT EXISTS items (
        id TEXT PRIMARY KEY,
        server_id TEXT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
        library_id TEXT REFERENCES libraries(id) ON DELETE SET NULL,
        parent_id TEXT REFERENCES items(id) ON DELETE CASCADE,

        -- Core metadata
        name TEXT NOT NULL,
        sort_name TEXT,
        original_title TEXT,
        item_type TEXT NOT NULL,  -- Movie, Series, Episode, MusicAlbum, Audio, MusicArtist, etc.

        -- Media info
        overview TEXT,
        tagline TEXT,
        genres TEXT,  -- JSON array
        tags TEXT,    -- JSON array
        studios TEXT, -- JSON array

        -- For episodes
        series_id TEXT,
        series_name TEXT,
        season_id TEXT,
        season_name TEXT,
        index_number INTEGER,        -- Episode number
        parent_index_number INTEGER, -- Season number

        -- For music
        album_id TEXT,
        album_name TEXT,
        album_artist TEXT,
        artists TEXT,  -- JSON array

        -- Dates
        premiere_date TEXT,
        production_year INTEGER,
        date_created TEXT,

        -- Runtime (ticks)
        runtime_ticks INTEGER,

        -- Images
        primary_image_tag TEXT,
        backdrop_image_tags TEXT,  -- JSON array

        -- Ratings
        community_rating REAL,
        official_rating TEXT,

        -- Sync metadata
        synced_at TEXT,
        etag TEXT,

        UNIQUE(server_id, id)
    );

    -- Full-text search index for items
    CREATE VIRTUAL TABLE IF NOT EXISTS items_fts USING fts5(
        name,
        overview,
        album_name,
        album_artist,
        artists,
        series_name,
        content='items',
        content_rowid='rowid'
    );

    -- Triggers to keep FTS index in sync
    CREATE TRIGGER IF NOT EXISTS items_ai AFTER INSERT ON items BEGIN
        INSERT INTO items_fts(rowid, name, overview, album_name, album_artist, artists, series_name)
        VALUES (new.rowid, new.name, new.overview, new.album_name, new.album_artist, new.artists, new.series_name);
    END;

    CREATE TRIGGER IF NOT EXISTS items_ad AFTER DELETE ON items BEGIN
        INSERT INTO items_fts(items_fts, rowid, name, overview, album_name, album_artist, artists, series_name)
        VALUES('delete', old.rowid, old.name, old.overview, old.album_name, old.album_artist, old.artists, old.series_name);
    END;

    CREATE TRIGGER IF NOT EXISTS items_au AFTER UPDATE ON items BEGIN
        INSERT INTO items_fts(items_fts, rowid, name, overview, album_name, album_artist, artists, series_name)
        VALUES('delete', old.rowid, old.name, old.overview, old.album_name, old.album_artist, old.artists, old.series_name);
        INSERT INTO items_fts(rowid, name, overview, album_name, album_artist, artists, series_name)
        VALUES (new.rowid, new.name, new.overview, new.album_name, new.album_artist, new.artists, new.series_name);
    END;

    -- Media streams (audio/subtitle tracks)
    CREATE TABLE IF NOT EXISTS media_streams (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        item_id TEXT NOT NULL REFERENCES items(id) ON DELETE CASCADE,
        stream_index INTEGER NOT NULL,
        stream_type TEXT NOT NULL,  -- Audio, Subtitle, Video
        codec TEXT,
        language TEXT,
        display_title TEXT,
        is_default INTEGER DEFAULT 0,
        is_forced INTEGER DEFAULT 0,
        is_external INTEGER DEFAULT 0,
        path TEXT,  -- For external subtitles
        UNIQUE(item_id, stream_index)
    );

    -- User-specific data (watch progress, favorites)
    CREATE TABLE IF NOT EXISTS user_data (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
        item_id TEXT NOT NULL REFERENCES items(id) ON DELETE CASCADE,

        -- Playback state
        playback_position_ticks INTEGER DEFAULT 0,
        play_count INTEGER DEFAULT 0,
        is_played INTEGER DEFAULT 0,
        is_favorite INTEGER DEFAULT 0,

        -- Timestamps
        last_played_at TEXT,

        -- Sync status
        synced_at TEXT,
        pending_sync INTEGER DEFAULT 0,  -- 1 if local changes need sync

        UNIQUE(user_id, item_id)
    );

    -- Downloaded media files
    CREATE TABLE IF NOT EXISTS downloads (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        item_id TEXT NOT NULL REFERENCES items(id) ON DELETE CASCADE,
        user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,

        -- File info
        file_path TEXT NOT NULL,
        file_size INTEGER,
        mime_type TEXT,

        -- Download state
        status TEXT DEFAULT 'pending',  -- pending, downloading, completed, failed, paused
        progress REAL DEFAULT 0,        -- 0.0 to 1.0

        -- Transcoding options used
        bitrate INTEGER,
        container TEXT,

        -- Timestamps
        queued_at TEXT DEFAULT CURRENT_TIMESTAMP,
        started_at TEXT,
        completed_at TEXT,

        -- Error tracking
        error_message TEXT,
        retry_count INTEGER DEFAULT 0,

        UNIQUE(item_id, user_id)
    );

    -- Offline mutation queue (changes to sync back to server)
    CREATE TABLE IF NOT EXISTS sync_queue (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,

        -- Operation details
        operation TEXT NOT NULL,  -- mark_played, mark_favorite, update_progress, etc.
        item_id TEXT,
        payload TEXT,  -- JSON data for the operation

        -- Queue state
        status TEXT DEFAULT 'pending',  -- pending, processing, completed, failed
        retry_count INTEGER DEFAULT 0,

        -- Timestamps
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        processed_at TEXT,

        -- Error tracking
        error_message TEXT
    );

    -- Cached thumbnails
    CREATE TABLE IF NOT EXISTS thumbnails (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        item_id TEXT NOT NULL REFERENCES items(id) ON DELETE CASCADE,
        image_type TEXT NOT NULL,  -- Primary, Backdrop, Thumb, Logo, etc.
        image_tag TEXT NOT NULL,
        file_path TEXT NOT NULL,
        width INTEGER,
        height INTEGER,
        cached_at TEXT DEFAULT CURRENT_TIMESTAMP,
        UNIQUE(item_id, image_type, image_tag)
    );

    -- User playlists (local + synced)
    CREATE TABLE IF NOT EXISTS playlists (
        id TEXT PRIMARY KEY,
        user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
        name TEXT NOT NULL,
        is_local INTEGER DEFAULT 0,  -- 1 for local-only playlists
        jellyfin_id TEXT,            -- NULL for local-only
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        updated_at TEXT
    );

    -- Playlist items
    CREATE TABLE IF NOT EXISTS playlist_items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        playlist_id TEXT NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
        item_id TEXT NOT NULL REFERENCES items(id) ON DELETE CASCADE,
        sort_order INTEGER NOT NULL,
        added_at TEXT DEFAULT CURRENT_TIMESTAMP,
        UNIQUE(playlist_id, item_id)
    );

    -- Indexes for common queries
    CREATE INDEX IF NOT EXISTS idx_items_server ON items(server_id);
    CREATE INDEX IF NOT EXISTS idx_items_library ON items(library_id);
    CREATE INDEX IF NOT EXISTS idx_items_parent ON items(parent_id);
    CREATE INDEX IF NOT EXISTS idx_items_type ON items(item_type);
    CREATE INDEX IF NOT EXISTS idx_items_album ON items(album_id);
    CREATE INDEX IF NOT EXISTS idx_items_series ON items(series_id);
    CREATE INDEX IF NOT EXISTS idx_items_season ON items(season_id);
    CREATE INDEX IF NOT EXISTS idx_user_data_user ON user_data(user_id);
    CREATE INDEX IF NOT EXISTS idx_user_data_item ON user_data(item_id);
    CREATE INDEX IF NOT EXISTS idx_downloads_status ON downloads(status);
    CREATE INDEX IF NOT EXISTS idx_sync_queue_status ON sync_queue(status);
    CREATE INDEX IF NOT EXISTS idx_thumbnails_item ON thumbnails(item_id);
    "#;

Expand description

Initial schema migration