nix: set up project-wide formatter
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I4806c58aa0a17f504c9312723ad770166a6a6964
This commit is contained in:
parent
aa9c55277c
commit
9e5eb41d39
78 changed files with 7406 additions and 2504 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
ALTER TABLE media_items ADD COLUMN thumbnail_path TEXT;
|
||||
ALTER TABLE media_items
|
||||
ADD COLUMN thumbnail_path TEXT;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue