nix: set up project-wide formatter

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4806c58aa0a17f504c9312723ad770166a6a6964
This commit is contained in:
raf 2026-03-22 23:42:02 +03:00
commit 9e5eb41d39
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
78 changed files with 7406 additions and 2504 deletions

View file

@ -1,19 +1,19 @@
-- Add file_mtime column to media_items table for incremental scanning
-- Stores Unix timestamp in seconds of the file's modification time
ALTER TABLE media_items ADD COLUMN file_mtime INTEGER;
ALTER TABLE media_items
ADD COLUMN file_mtime INTEGER;
-- Create index for quick mtime lookups
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items(file_mtime);
CREATE INDEX IF NOT EXISTS idx_media_items_file_mtime ON media_items (file_mtime);
-- Create a scan_history table to track when each directory was last scanned
CREATE TABLE IF NOT EXISTS scan_history (
id TEXT PRIMARY KEY,
directory TEXT NOT NULL UNIQUE,
last_scan_at TEXT NOT NULL,
files_scanned INTEGER NOT NULL DEFAULT 0,
files_changed INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER
id TEXT PRIMARY KEY,
directory TEXT NOT NULL UNIQUE,
last_scan_at TEXT NOT NULL,
files_scanned INTEGER NOT NULL DEFAULT 0,
files_changed INTEGER NOT NULL DEFAULT 0,
scan_duration_ms INTEGER
);
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history(directory);
CREATE INDEX IF NOT EXISTS idx_scan_history_directory ON scan_history (directory);

View file

@ -1,18 +1,17 @@
-- Session persistence for database-backed sessions
-- Replaces in-memory session storage
CREATE TABLE IF NOT EXISTS sessions (
session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT,
username TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_accessed TEXT NOT NULL
session_token TEXT PRIMARY KEY NOT NULL,
user_id TEXT,
username TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_accessed TEXT NOT NULL
);
-- Index for efficient cleanup of expired sessions
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions (expires_at);
-- Index for listing sessions by username
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions(username);
CREATE INDEX IF NOT EXISTS idx_sessions_username ON sessions (username);

View file

@ -1,54 +1,62 @@
-- V12: Book Management Schema
-- Adds comprehensive book metadata tracking, authors, and identifiers
-- Book metadata (supplements media_items for EPUB/PDF/MOBI)
CREATE TABLE book_metadata (
media_id TEXT PRIMARY KEY REFERENCES media_items(id) ON DELETE CASCADE,
isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT,
language TEXT, -- ISO 639-1 code
page_count INTEGER,
publication_date TEXT, -- ISO 8601 date string
series_name TEXT,
series_index REAL, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
media_id TEXT PRIMARY KEY REFERENCES media_items (id) ON DELETE CASCADE,
isbn TEXT,
isbn13 TEXT, -- Normalized ISBN-13 for lookups
publisher TEXT,
language TEXT, -- ISO 639-1 code
page_count INTEGER,
publication_date TEXT, -- ISO 8601 date string
series_name TEXT,
series_index REAL, -- Supports 1.5, etc.
format TEXT, -- 'epub', 'pdf', 'mobi', 'azw3'
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
updated_at TEXT NOT NULL DEFAULT (datetime ('now'))
) STRICT;
CREATE INDEX idx_book_isbn13 ON book_metadata(isbn13);
CREATE INDEX idx_book_series ON book_metadata(series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata(publisher);
CREATE INDEX idx_book_language ON book_metadata(language);
CREATE INDEX idx_book_isbn13 ON book_metadata (isbn13);
CREATE INDEX idx_book_series ON book_metadata (series_name, series_index);
CREATE INDEX idx_book_publisher ON book_metadata (publisher);
CREATE INDEX idx_book_language ON book_metadata (language);
-- Multiple authors per book (many-to-many)
CREATE TABLE book_authors (
media_id TEXT NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
position INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (media_id, author_name, role)
media_id TEXT NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
author_sort TEXT, -- "Last, First" for sorting
role TEXT NOT NULL DEFAULT 'author', -- author, translator, editor, illustrator
position INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (media_id, author_name, role)
) STRICT;
CREATE INDEX idx_book_authors_name ON book_authors(author_name);
CREATE INDEX idx_book_authors_sort ON book_authors(author_sort);
CREATE INDEX idx_book_authors_name ON book_authors (author_name);
CREATE INDEX idx_book_authors_sort ON book_authors (author_sort);
-- Multiple identifiers (ISBN variants, ASIN, DOI, etc.)
CREATE TABLE book_identifiers (
media_id TEXT NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value)
media_id TEXT NOT NULL REFERENCES media_items (id) ON DELETE CASCADE,
identifier_type TEXT NOT NULL, -- isbn, isbn13, asin, doi, lccn, oclc
identifier_value TEXT NOT NULL,
PRIMARY KEY (media_id, identifier_type, identifier_value)
) STRICT;
CREATE INDEX idx_book_identifiers ON book_identifiers(identifier_type, identifier_value);
CREATE INDEX idx_book_identifiers ON book_identifiers (identifier_type, identifier_value);
-- Trigger to update updated_at on book_metadata changes
CREATE TRIGGER update_book_metadata_timestamp
AFTER UPDATE ON book_metadata
FOR EACH ROW
AFTER
UPDATE ON book_metadata FOR EACH ROW
BEGIN
UPDATE book_metadata SET updated_at = datetime('now') WHERE media_id = NEW.media_id;
UPDATE book_metadata
SET
updated_at = datetime ('now')
WHERE
media_id = NEW.media_id;
END;

View file

@ -1,15 +1,40 @@
-- V13: Enhanced photo metadata support
-- Add photo-specific fields to media_items table
ALTER TABLE media_items
ADD COLUMN date_taken TIMESTAMP;
ALTER TABLE media_items ADD COLUMN date_taken TIMESTAMP;
ALTER TABLE media_items ADD COLUMN latitude REAL;
ALTER TABLE media_items ADD COLUMN longitude REAL;
ALTER TABLE media_items ADD COLUMN camera_make TEXT;
ALTER TABLE media_items ADD COLUMN camera_model TEXT;
ALTER TABLE media_items ADD COLUMN rating INTEGER CHECK (rating >= 0 AND rating <= 5);
ALTER TABLE media_items
ADD COLUMN latitude REAL;
ALTER TABLE media_items
ADD COLUMN longitude REAL;
ALTER TABLE media_items
ADD COLUMN camera_make TEXT;
ALTER TABLE media_items
ADD COLUMN camera_model TEXT;
ALTER TABLE media_items
ADD COLUMN rating INTEGER CHECK (
rating >= 0
AND rating <= 5
);
-- Indexes for photo queries
CREATE INDEX idx_media_date_taken ON media_items(date_taken) WHERE date_taken IS NOT NULL;
CREATE INDEX idx_media_location ON media_items(latitude, longitude) WHERE latitude IS NOT NULL AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items(camera_make) WHERE camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items(rating) WHERE rating IS NOT NULL;
CREATE INDEX idx_media_date_taken ON media_items (date_taken)
WHERE
date_taken IS NOT NULL;
CREATE INDEX idx_media_location ON media_items (latitude, longitude)
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL;
CREATE INDEX idx_media_camera ON media_items (camera_make)
WHERE
camera_make IS NOT NULL;
CREATE INDEX idx_media_rating ON media_items (rating)
WHERE
rating IS NOT NULL;

View file

@ -1,7 +1,9 @@
-- V14: Perceptual hash for duplicate detection
-- Add perceptual hash column for image similarity detection
ALTER TABLE media_items ADD COLUMN perceptual_hash TEXT;
ALTER TABLE media_items
ADD COLUMN perceptual_hash TEXT;
-- Index for perceptual hash lookups
CREATE INDEX idx_media_phash ON media_items(perceptual_hash) WHERE perceptual_hash IS NOT NULL;
CREATE INDEX idx_media_phash ON media_items (perceptual_hash)
WHERE
perceptual_hash IS NOT NULL;

View file

@ -1,30 +1,33 @@
-- V15: Managed File Storage
-- Adds server-side content-addressable storage for uploaded files
-- Add storage mode to media_items (external = file on disk, managed = in content-addressable storage)
ALTER TABLE media_items ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
ALTER TABLE media_items
ADD COLUMN storage_mode TEXT NOT NULL DEFAULT 'external';
-- Original filename for managed uploads (preserved separately from file_name which may be normalized)
ALTER TABLE media_items ADD COLUMN original_filename TEXT;
ALTER TABLE media_items
ADD COLUMN original_filename TEXT;
-- When the file was uploaded to managed storage
ALTER TABLE media_items ADD COLUMN uploaded_at TEXT;
ALTER TABLE media_items
ADD COLUMN uploaded_at TEXT;
-- Storage key for looking up the blob (usually same as content_hash for deduplication)
ALTER TABLE media_items ADD COLUMN storage_key TEXT;
ALTER TABLE media_items
ADD COLUMN storage_key TEXT;
-- Managed blobs table - tracks deduplicated file storage
CREATE TABLE managed_blobs (
content_hash TEXT PRIMARY KEY NOT NULL,
file_size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
reference_count INTEGER NOT NULL DEFAULT 1,
stored_at TEXT NOT NULL,
last_verified TEXT
content_hash TEXT PRIMARY KEY NOT NULL,
file_size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
reference_count INTEGER NOT NULL DEFAULT 1,
stored_at TEXT NOT NULL,
last_verified TEXT
);
-- Index for finding managed media items
CREATE INDEX idx_media_storage_mode ON media_items(storage_mode);
CREATE INDEX idx_media_storage_mode ON media_items (storage_mode);
-- Index for finding orphaned blobs (reference_count = 0)
CREATE INDEX idx_blobs_reference_count ON managed_blobs(reference_count);
CREATE INDEX idx_blobs_reference_count ON managed_blobs (reference_count);

View file

@ -1,117 +1,129 @@
-- V16: Cross-Device Sync System
-- Adds device registration, change tracking, and chunked upload support
-- Sync devices table
CREATE TABLE sync_devices (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
device_type TEXT NOT NULL,
client_version TEXT NOT NULL,
os_info TEXT,
device_token_hash TEXT NOT NULL UNIQUE,
last_sync_at TEXT,
last_seen_at TEXT NOT NULL,
sync_cursor INTEGER DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
device_type TEXT NOT NULL,
client_version TEXT NOT NULL,
os_info TEXT,
device_token_hash TEXT NOT NULL UNIQUE,
last_sync_at TEXT,
last_seen_at TEXT NOT NULL,
sync_cursor INTEGER DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
CREATE INDEX idx_sync_devices_user ON sync_devices(user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices(device_token_hash);
CREATE INDEX idx_sync_devices_user ON sync_devices (user_id);
CREATE INDEX idx_sync_devices_token ON sync_devices (device_token_hash);
-- Sync log table - tracks all changes for sync
CREATE TABLE sync_log (
id TEXT PRIMARY KEY NOT NULL,
sequence INTEGER NOT NULL UNIQUE,
change_type TEXT NOT NULL,
media_id TEXT,
path TEXT NOT NULL,
content_hash TEXT,
file_size INTEGER,
metadata_json TEXT,
changed_by_device TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL,
FOREIGN KEY (changed_by_device) REFERENCES sync_devices(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
sequence INTEGER NOT NULL UNIQUE,
change_type TEXT NOT NULL,
media_id TEXT,
path TEXT NOT NULL,
content_hash TEXT,
file_size INTEGER,
metadata_json TEXT,
changed_by_device TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL,
FOREIGN KEY (changed_by_device) REFERENCES sync_devices (id) ON DELETE SET NULL
);
CREATE INDEX idx_sync_log_sequence ON sync_log(sequence);
CREATE INDEX idx_sync_log_path ON sync_log(path);
CREATE INDEX idx_sync_log_timestamp ON sync_log(timestamp);
CREATE INDEX idx_sync_log_sequence ON sync_log (sequence);
CREATE INDEX idx_sync_log_path ON sync_log (path);
CREATE INDEX idx_sync_log_timestamp ON sync_log (timestamp);
-- Sequence counter for sync log
CREATE TABLE sync_sequence (
id INTEGER PRIMARY KEY CHECK (id = 1),
current_value INTEGER NOT NULL DEFAULT 0
id INTEGER PRIMARY KEY CHECK (id = 1),
current_value INTEGER NOT NULL DEFAULT 0
);
INSERT INTO sync_sequence (id, current_value) VALUES (1, 0);
INSERT INTO
sync_sequence (id, current_value)
VALUES
(1, 0);
-- Device sync state - tracks sync status per device per file
CREATE TABLE device_sync_state (
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT,
server_hash TEXT,
local_mtime INTEGER,
server_mtime INTEGER,
sync_status TEXT NOT NULL,
last_synced_at TEXT,
conflict_info_json TEXT,
PRIMARY KEY (device_id, path),
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT,
server_hash TEXT,
local_mtime INTEGER,
server_mtime INTEGER,
sync_status TEXT NOT NULL,
last_synced_at TEXT,
conflict_info_json TEXT,
PRIMARY KEY (device_id, path),
FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
);
CREATE INDEX idx_device_sync_status ON device_sync_state(device_id, sync_status);
CREATE INDEX idx_device_sync_status ON device_sync_state (device_id, sync_status);
-- Upload sessions for chunked uploads
CREATE TABLE upload_sessions (
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL,
expected_size INTEGER NOT NULL,
chunk_size INTEGER NOT NULL,
chunk_count INTEGER NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_activity TEXT NOT NULL,
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
target_path TEXT NOT NULL,
expected_hash TEXT NOT NULL,
expected_size INTEGER NOT NULL,
chunk_size INTEGER NOT NULL,
chunk_count INTEGER NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_activity TEXT NOT NULL,
FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
);
CREATE INDEX idx_upload_sessions_device ON upload_sessions(device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions(status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions(expires_at);
CREATE INDEX idx_upload_sessions_device ON upload_sessions (device_id);
CREATE INDEX idx_upload_sessions_status ON upload_sessions (status);
CREATE INDEX idx_upload_sessions_expires ON upload_sessions (expires_at);
-- Upload chunks - tracks received chunks
CREATE TABLE upload_chunks (
upload_id TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
offset INTEGER NOT NULL,
upload_id TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
offset
INTEGER NOT NULL,
size INTEGER NOT NULL,
hash TEXT NOT NULL,
received_at TEXT NOT NULL,
PRIMARY KEY (upload_id, chunk_index),
FOREIGN KEY (upload_id) REFERENCES upload_sessions(id) ON DELETE CASCADE
FOREIGN KEY (upload_id) REFERENCES upload_sessions (id) ON DELETE CASCADE
);
-- Sync conflicts
CREATE TABLE sync_conflicts (
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT NOT NULL,
local_mtime INTEGER NOT NULL,
server_hash TEXT NOT NULL,
server_mtime INTEGER NOT NULL,
detected_at TEXT NOT NULL,
resolved_at TEXT,
resolution TEXT,
FOREIGN KEY (device_id) REFERENCES sync_devices(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
device_id TEXT NOT NULL,
path TEXT NOT NULL,
local_hash TEXT NOT NULL,
local_mtime INTEGER NOT NULL,
server_hash TEXT NOT NULL,
server_mtime INTEGER NOT NULL,
detected_at TEXT NOT NULL,
resolved_at TEXT,
resolution TEXT,
FOREIGN KEY (device_id) REFERENCES sync_devices (id) ON DELETE CASCADE
);
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts(device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts(device_id, resolved_at) WHERE resolved_at IS NULL;
CREATE INDEX idx_sync_conflicts_device ON sync_conflicts (device_id);
CREATE INDEX idx_sync_conflicts_unresolved ON sync_conflicts (device_id, resolved_at)
WHERE
resolved_at IS NULL;

View file

@ -1,85 +1,133 @@
-- V17: Enhanced Sharing System
-- Replaces simple share_links with comprehensive sharing capabilities
-- Enhanced shares table
CREATE TABLE shares (
id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (target_type IN ('media', 'collection', 'tag', 'saved_search')),
target_id TEXT NOT NULL,
owner_id TEXT NOT NULL,
recipient_type TEXT NOT NULL CHECK (recipient_type IN ('public_link', 'user', 'group', 'federated')),
recipient_user_id TEXT,
recipient_group_id TEXT,
recipient_federated_handle TEXT,
recipient_federated_server TEXT,
public_token TEXT UNIQUE,
public_password_hash TEXT,
perm_view INTEGER NOT NULL DEFAULT 1,
perm_download INTEGER NOT NULL DEFAULT 0,
perm_edit INTEGER NOT NULL DEFAULT 0,
perm_delete INTEGER NOT NULL DEFAULT 0,
perm_reshare INTEGER NOT NULL DEFAULT 0,
perm_add INTEGER NOT NULL DEFAULT 0,
note TEXT,
expires_at TEXT,
access_count INTEGER NOT NULL DEFAULT 0,
last_accessed TEXT,
inherit_to_children INTEGER NOT NULL DEFAULT 1,
parent_share_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (recipient_user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (parent_share_id) REFERENCES shares(id) ON DELETE CASCADE,
UNIQUE(owner_id, target_type, target_id, recipient_type, recipient_user_id)
id TEXT PRIMARY KEY NOT NULL,
target_type TEXT NOT NULL CHECK (
target_type IN ('media', 'collection', 'tag', 'saved_search')
),
target_id TEXT NOT NULL,
owner_id TEXT NOT NULL,
recipient_type TEXT NOT NULL CHECK (
recipient_type IN ('public_link', 'user', 'group', 'federated')
),
recipient_user_id TEXT,
recipient_group_id TEXT,
recipient_federated_handle TEXT,
recipient_federated_server TEXT,
public_token TEXT UNIQUE,
public_password_hash TEXT,
perm_view INTEGER NOT NULL DEFAULT 1,
perm_download INTEGER NOT NULL DEFAULT 0,
perm_edit INTEGER NOT NULL DEFAULT 0,
perm_delete INTEGER NOT NULL DEFAULT 0,
perm_reshare INTEGER NOT NULL DEFAULT 0,
perm_add INTEGER NOT NULL DEFAULT 0,
note TEXT,
expires_at TEXT,
access_count INTEGER NOT NULL DEFAULT 0,
last_accessed TEXT,
inherit_to_children INTEGER NOT NULL DEFAULT 1,
parent_share_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (owner_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (recipient_user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (parent_share_id) REFERENCES shares (id) ON DELETE CASCADE,
UNIQUE (
owner_id,
target_type,
target_id,
recipient_type,
recipient_user_id
)
);
CREATE INDEX idx_shares_owner ON shares(owner_id);
CREATE INDEX idx_shares_recipient_user ON shares(recipient_user_id);
CREATE INDEX idx_shares_target ON shares(target_type, target_id);
CREATE INDEX idx_shares_token ON shares(public_token);
CREATE INDEX idx_shares_expires ON shares(expires_at);
CREATE INDEX idx_shares_owner ON shares (owner_id);
CREATE INDEX idx_shares_recipient_user ON shares (recipient_user_id);
CREATE INDEX idx_shares_target ON shares (target_type, target_id);
CREATE INDEX idx_shares_token ON shares (public_token);
CREATE INDEX idx_shares_expires ON shares (expires_at);
-- Share activity log
CREATE TABLE share_activity (
id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL,
actor_id TEXT,
actor_ip TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (share_id) REFERENCES shares(id) ON DELETE CASCADE,
FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
share_id TEXT NOT NULL,
actor_id TEXT,
actor_ip TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (share_id) REFERENCES shares (id) ON DELETE CASCADE,
FOREIGN KEY (actor_id) REFERENCES users (id) ON DELETE SET NULL
);
CREATE INDEX idx_share_activity_share ON share_activity(share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity(timestamp);
CREATE INDEX idx_share_activity_share ON share_activity (share_id);
CREATE INDEX idx_share_activity_timestamp ON share_activity (timestamp);
-- Share notifications
CREATE TABLE share_notifications (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
share_id TEXT NOT NULL,
notification_type TEXT NOT NULL,
is_read INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (share_id) REFERENCES shares(id) ON DELETE CASCADE
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
share_id TEXT NOT NULL,
notification_type TEXT NOT NULL,
is_read INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (share_id) REFERENCES shares (id) ON DELETE CASCADE
);
CREATE INDEX idx_share_notifications_user ON share_notifications(user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications(user_id, is_read) WHERE is_read = 0;
CREATE INDEX idx_share_notifications_user ON share_notifications (user_id);
CREATE INDEX idx_share_notifications_unread ON share_notifications (user_id, is_read)
WHERE
is_read = 0;
-- Migrate existing share_links to new shares table (if share_links exists)
INSERT OR IGNORE INTO shares (
id, target_type, target_id, owner_id, recipient_type,
public_token, public_password_hash, perm_view, perm_download,
access_count, expires_at, created_at, updated_at
INSERT
OR IGNORE INTO shares (
id,
target_type,
target_id,
owner_id,
recipient_type,
public_token,
public_password_hash,
perm_view,
perm_download,
access_count,
expires_at,
created_at,
updated_at
)
SELECT
id, 'media', media_id, created_by, 'public_link',
token, password_hash, 1, 1,
view_count, expires_at, created_at, created_at
FROM share_links
WHERE EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name='share_links');
id,
'media',
media_id,
created_by,
'public_link',
token,
password_hash,
1,
1,
view_count,
expires_at,
created_at,
created_at
FROM
share_links
WHERE
EXISTS (
SELECT
1
FROM
sqlite_master
WHERE
type = 'table'
AND name = 'share_links'
);

View file

@ -1,11 +1,13 @@
-- V18: File Management (Rename, Move, Trash)
-- Adds soft delete support for trash/recycle bin functionality
-- Add deleted_at column for soft delete (trash)
ALTER TABLE media_items ADD COLUMN deleted_at TEXT;
ALTER TABLE media_items
ADD COLUMN deleted_at TEXT;
-- Index for efficient trash queries
CREATE INDEX idx_media_deleted_at ON media_items(deleted_at);
CREATE INDEX idx_media_deleted_at ON media_items (deleted_at);
-- Index for listing non-deleted items (most common query pattern)
CREATE INDEX idx_media_not_deleted ON media_items(id) WHERE deleted_at IS NULL;
CREATE INDEX idx_media_not_deleted ON media_items (id)
WHERE
deleted_at IS NULL;

View file

@ -1,35 +1,35 @@
-- V19: Markdown Links (Obsidian-style bidirectional links)
-- Adds support for wikilinks, markdown links, embeds, and backlink tracking
-- Table for storing extracted markdown links
CREATE TABLE IF NOT EXISTS markdown_links (
id TEXT PRIMARY KEY NOT NULL,
source_media_id TEXT NOT NULL,
target_path TEXT NOT NULL, -- raw link target (wikilink or path)
target_media_id TEXT, -- resolved media_id (nullable if unresolved)
link_type TEXT NOT NULL, -- 'wikilink', 'markdown_link', 'embed'
link_text TEXT, -- display text for the link
line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview
created_at TEXT NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
source_media_id TEXT NOT NULL,
target_path TEXT NOT NULL, -- raw link target (wikilink or path)
target_media_id TEXT, -- resolved media_id (nullable if unresolved)
link_type TEXT NOT NULL, -- 'wikilink', 'markdown_link', 'embed'
link_text TEXT, -- display text for the link
line_number INTEGER, -- line number in source file
context TEXT, -- surrounding text for preview
created_at TEXT NOT NULL,
FOREIGN KEY (source_media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (target_media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
-- Index for efficient outgoing link queries (what does this note link to?)
CREATE INDEX idx_links_source ON markdown_links(source_media_id);
CREATE INDEX idx_links_source ON markdown_links (source_media_id);
-- Index for efficient backlink queries (what links to this note?)
CREATE INDEX idx_links_target ON markdown_links(target_media_id);
CREATE INDEX idx_links_target ON markdown_links (target_media_id);
-- Index for path-based resolution (finding unresolved links)
CREATE INDEX idx_links_target_path ON markdown_links(target_path);
CREATE INDEX idx_links_target_path ON markdown_links (target_path);
-- Index for link type filtering
CREATE INDEX idx_links_type ON markdown_links(link_type);
CREATE INDEX idx_links_type ON markdown_links (link_type);
-- Track when links were last extracted from a media item
ALTER TABLE media_items ADD COLUMN links_extracted_at TEXT;
ALTER TABLE media_items
ADD COLUMN links_extracted_at TEXT;
-- Index for finding media items that need link extraction
CREATE INDEX idx_media_links_extracted ON media_items(links_extracted_at);
CREATE INDEX idx_media_links_extracted ON media_items (links_extracted_at);

View file

@ -1,77 +1,75 @@
CREATE TABLE IF NOT EXISTS root_dirs (
path TEXT PRIMARY KEY NOT NULL
);
CREATE TABLE IF NOT EXISTS root_dirs (path TEXT PRIMARY KEY NOT NULL);
CREATE TABLE IF NOT EXISTS media_items (
id TEXT PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
media_type TEXT NOT NULL,
content_hash TEXT NOT NULL UNIQUE,
file_size INTEGER NOT NULL,
title TEXT,
artist TEXT,
album TEXT,
genre TEXT,
year INTEGER,
duration_secs REAL,
description TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY NOT NULL,
path TEXT NOT NULL UNIQUE,
file_name TEXT NOT NULL,
media_type TEXT NOT NULL,
content_hash TEXT NOT NULL UNIQUE,
file_size INTEGER NOT NULL,
title TEXT,
artist TEXT,
album TEXT,
genre TEXT,
year INTEGER,
duration_secs REAL,
description TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tags (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES tags(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
parent_id TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES tags (id) ON DELETE SET NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags(name, parent_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_name_parent ON tags (name, parent_id);
CREATE TABLE IF NOT EXISTS media_tags (
media_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (media_id, tag_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
media_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (media_id, tag_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS collections (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
kind TEXT NOT NULL,
filter_query TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
kind TEXT NOT NULL,
filter_query TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS collection_members (
collection_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL,
PRIMARY KEY (collection_id, media_id),
FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
collection_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL,
PRIMARY KEY (collection_id, media_id),
FOREIGN KEY (collection_id) REFERENCES collections (id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS audit_log (
id TEXT PRIMARY KEY NOT NULL,
media_id TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY NOT NULL,
media_id TEXT,
action TEXT NOT NULL,
details TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS custom_fields (
media_id TEXT NOT NULL,
field_name TEXT NOT NULL,
field_type TEXT NOT NULL,
field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
media_id TEXT NOT NULL,
field_name TEXT NOT NULL,
field_type TEXT NOT NULL,
field_value TEXT NOT NULL,
PRIMARY KEY (media_id, field_name),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);

View file

@ -1,27 +1,114 @@
CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5(
CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5 (
title,
artist,
album,
genre,
description,
file_name,
content = 'media_items',
content_rowid = 'rowid'
);
CREATE TRIGGER IF NOT EXISTS media_fts_insert
AFTER INSERT ON media_items
BEGIN
INSERT INTO
media_fts (
rowid,
title,
artist,
album,
genre,
description,
file_name,
content='media_items',
content_rowid='rowid'
);
file_name
)
VALUES
(
new.rowid,
new.title,
new.artist,
new.album,
new.genre,
new.description,
new.file_name
);
CREATE TRIGGER IF NOT EXISTS media_fts_insert AFTER INSERT ON media_items BEGIN
INSERT INTO media_fts(rowid, title, artist, album, genre, description, file_name)
VALUES (new.rowid, new.title, new.artist, new.album, new.genre, new.description, new.file_name);
END;
CREATE TRIGGER IF NOT EXISTS media_fts_update AFTER UPDATE ON media_items BEGIN
INSERT INTO media_fts(media_fts, rowid, title, artist, album, genre, description, file_name)
VALUES ('delete', old.rowid, old.title, old.artist, old.album, old.genre, old.description, old.file_name);
INSERT INTO media_fts(rowid, title, artist, album, genre, description, file_name)
VALUES (new.rowid, new.title, new.artist, new.album, new.genre, new.description, new.file_name);
CREATE TRIGGER IF NOT EXISTS media_fts_update
AFTER
UPDATE ON media_items
BEGIN
INSERT INTO
media_fts (
media_fts,
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
'delete',
old.rowid,
old.title,
old.artist,
old.album,
old.genre,
old.description,
old.file_name
);
INSERT INTO
media_fts (
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
new.rowid,
new.title,
new.artist,
new.album,
new.genre,
new.description,
new.file_name
);
END;
CREATE TRIGGER IF NOT EXISTS media_fts_delete AFTER DELETE ON media_items BEGIN
INSERT INTO media_fts(media_fts, rowid, title, artist, album, genre, description, file_name)
VALUES ('delete', old.rowid, old.title, old.artist, old.album, old.genre, old.description, old.file_name);
CREATE TRIGGER IF NOT EXISTS media_fts_delete
AFTER DELETE ON media_items
BEGIN
INSERT INTO
media_fts (
media_fts,
rowid,
title,
artist,
album,
genre,
description,
file_name
)
VALUES
(
'delete',
old.rowid,
old.title,
old.artist,
old.album,
old.genre,
old.description,
old.file_name
);
END;

View file

@ -1,6 +1,11 @@
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log(media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items(content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items(media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items(created_at);
CREATE INDEX IF NOT EXISTS idx_audit_media_id ON audit_log (media_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log (timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log (action);
CREATE INDEX IF NOT EXISTS idx_media_content_hash ON media_items (content_hash);
CREATE INDEX IF NOT EXISTS idx_media_media_type ON media_items (media_type);
CREATE INDEX IF NOT EXISTS idx_media_created_at ON media_items (created_at);

View file

@ -1 +1,2 @@
ALTER TABLE media_items ADD COLUMN thumbnail_path TEXT;
ALTER TABLE media_items
ADD COLUMN thumbnail_path TEXT;

View file

@ -1,12 +1,15 @@
-- Integrity tracking columns
ALTER TABLE media_items ADD COLUMN last_verified_at TEXT;
ALTER TABLE media_items ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
ALTER TABLE media_items
ADD COLUMN last_verified_at TEXT;
ALTER TABLE media_items
ADD COLUMN integrity_status TEXT DEFAULT 'unverified';
-- Saved searches
CREATE TABLE IF NOT EXISTS saved_searches (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
query TEXT NOT NULL,
sort_order TEXT,
created_at TEXT NOT NULL
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
query TEXT NOT NULL,
sort_order TEXT,
created_at TEXT NOT NULL
);

View file

@ -1,15 +1,16 @@
-- Plugin registry table
CREATE TABLE plugin_registry (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json TEXT,
manifest_json TEXT,
installed_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
config_json TEXT,
manifest_json TEXT,
installed_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- Index for quick lookups
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry(enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry(name);
CREATE INDEX idx_plugin_registry_enabled ON plugin_registry (enabled);
CREATE INDEX idx_plugin_registry_name ON plugin_registry (name);

View file

@ -1,35 +1,37 @@
-- Users table
CREATE TABLE users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- User profiles table
CREATE TABLE user_profiles (
user_id TEXT PRIMARY KEY,
avatar_path TEXT,
bio TEXT,
preferences_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
user_id TEXT PRIMARY KEY,
avatar_path TEXT,
bio TEXT,
preferences_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id)
);
-- User library access table
CREATE TABLE user_libraries (
user_id TEXT NOT NULL,
root_path TEXT NOT NULL,
permission TEXT NOT NULL,
granted_at TEXT NOT NULL,
PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users(id)
user_id TEXT NOT NULL,
root_path TEXT NOT NULL,
permission TEXT NOT NULL,
granted_at TEXT NOT NULL,
PRIMARY KEY (user_id, root_path),
FOREIGN KEY (user_id) REFERENCES users (id)
);
-- Indexes for efficient lookups
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries(user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries(root_path);
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_user_libraries_user_id ON user_libraries (user_id);
CREATE INDEX idx_user_libraries_root_path ON user_libraries (root_path);

View file

@ -1,143 +1,148 @@
-- Ratings
CREATE TABLE IF NOT EXISTS ratings (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
stars INTEGER NOT NULL CHECK (stars >= 1 AND stars <= 5),
review_text TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
stars INTEGER NOT NULL CHECK (
stars >= 1
AND stars <= 5
),
review_text TEXT,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
UNIQUE (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Comments
CREATE TABLE IF NOT EXISTS comments (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
parent_comment_id TEXT,
text TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE,
FOREIGN KEY (parent_comment_id) REFERENCES comments(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
parent_comment_id TEXT,
text TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE,
FOREIGN KEY (parent_comment_id) REFERENCES comments (id) ON DELETE CASCADE
);
-- Favorites
CREATE TABLE IF NOT EXISTS favorites (
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
PRIMARY KEY (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Share links
CREATE TABLE IF NOT EXISTS share_links (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
created_by TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
password_hash TEXT,
expires_at TEXT,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
created_by TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
password_hash TEXT,
expires_at TEXT,
view_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Playlists
CREATE TABLE IF NOT EXISTS playlists (
id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
is_public INTEGER NOT NULL DEFAULT 0,
is_smart INTEGER NOT NULL DEFAULT 0,
filter_query TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
is_public INTEGER NOT NULL DEFAULT 0,
is_smart INTEGER NOT NULL DEFAULT 0,
filter_query TEXT,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
updated_at TEXT NOT NULL DEFAULT (datetime ('now'))
);
-- Playlist items
CREATE TABLE IF NOT EXISTS playlist_items (
playlist_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (playlist_id, media_id),
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
playlist_id TEXT NOT NULL,
media_id TEXT NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
added_at TEXT NOT NULL DEFAULT (datetime ('now')),
PRIMARY KEY (playlist_id, media_id),
FOREIGN KEY (playlist_id) REFERENCES playlists (id) ON DELETE CASCADE,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Usage events
CREATE TABLE IF NOT EXISTS usage_events (
id TEXT PRIMARY KEY,
media_id TEXT,
user_id TEXT,
event_type TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
duration_secs REAL,
context_json TEXT,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE SET NULL
id TEXT PRIMARY KEY,
media_id TEXT,
user_id TEXT,
event_type TEXT NOT NULL,
timestamp TEXT NOT NULL DEFAULT (datetime ('now')),
duration_secs REAL,
context_json TEXT,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events(media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events(user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events(timestamp);
CREATE INDEX IF NOT EXISTS idx_usage_events_media ON usage_events (media_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_user ON usage_events (user_id);
CREATE INDEX IF NOT EXISTS idx_usage_events_timestamp ON usage_events (timestamp);
-- Watch history / progress
CREATE TABLE IF NOT EXISTS watch_history (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
progress_secs REAL NOT NULL DEFAULT 0,
last_watched TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
media_id TEXT NOT NULL,
progress_secs REAL NOT NULL DEFAULT 0,
last_watched TEXT NOT NULL DEFAULT (datetime ('now')),
UNIQUE (user_id, media_id),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
-- Subtitles
CREATE TABLE IF NOT EXISTS subtitles (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
language TEXT,
format TEXT NOT NULL,
file_path TEXT,
is_embedded INTEGER NOT NULL DEFAULT 0,
track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
language TEXT,
format TEXT NOT NULL,
file_path TEXT,
is_embedded INTEGER NOT NULL DEFAULT 0,
track_index INTEGER,
offset_ms INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles(media_id);
CREATE INDEX IF NOT EXISTS idx_subtitles_media ON subtitles (media_id);
-- External metadata (enrichment)
CREATE TABLE IF NOT EXISTS external_metadata (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
source TEXT NOT NULL,
external_id TEXT,
metadata_json TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 0.0,
last_updated TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
source TEXT NOT NULL,
external_id TEXT,
metadata_json TEXT NOT NULL DEFAULT '{}',
confidence REAL NOT NULL DEFAULT 0.0,
last_updated TEXT NOT NULL DEFAULT (datetime ('now')),
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata(media_id);
CREATE INDEX IF NOT EXISTS idx_external_metadata_media ON external_metadata (media_id);
-- Transcode sessions
CREATE TABLE IF NOT EXISTS transcode_sessions (
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
user_id TEXT,
profile TEXT NOT NULL,
cache_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress REAL NOT NULL DEFAULT 0.0,
error_message TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT,
FOREIGN KEY (media_id) REFERENCES media_items(id) ON DELETE CASCADE
id TEXT PRIMARY KEY,
media_id TEXT NOT NULL,
user_id TEXT,
profile TEXT NOT NULL,
cache_path TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
progress REAL NOT NULL DEFAULT 0.0,
error_message TEXT,
created_at TEXT NOT NULL DEFAULT (datetime ('now')),
expires_at TEXT,
FOREIGN KEY (media_id) REFERENCES media_items (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions(media_id);
CREATE INDEX IF NOT EXISTS idx_transcode_sessions_media ON transcode_sessions (media_id);

View file

@ -1,18 +1,25 @@
-- Drop redundant indexes (already covered by UNIQUE constraints)
DROP INDEX IF EXISTS idx_users_username;
DROP INDEX IF EXISTS idx_user_libraries_user_id;
-- Add missing indexes for comments table
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments(media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments(parent_comment_id);
CREATE INDEX IF NOT EXISTS idx_comments_media ON comments (media_id);
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments (parent_comment_id);
-- Remove duplicates before adding unique index (keep the first row)
DELETE FROM external_metadata
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM external_metadata
GROUP BY media_id, source
);
WHERE
rowid NOT IN (
SELECT
MIN(rowid)
FROM
external_metadata
GROUP BY
media_id,
source
);
-- Add unique index for external_metadata to prevent duplicates
CREATE UNIQUE INDEX IF NOT EXISTS uq_external_metadata_source ON external_metadata(media_id, source);
CREATE UNIQUE INDEX IF NOT EXISTS uq_external_metadata_source ON external_metadata (media_id, source);