schema: make all schema statements idempotent (Phase 1)

- Convert 18 CREATE TABLE → CREATE TABLE IF NOT EXISTS (27 total)
- Convert 62 CREATE INDEX → CREATE INDEX IF NOT EXISTS (82 total)
- Add ON CONFLICT to 2 INSERT statements (3 total)
- Verify 8 ALTER TABLE already have IF NOT EXISTS
- Verify 6 CREATE FUNCTION use OR REPLACE

Schema is now fully idempotent and safe for automatic initialization on every startup.
This commit is contained in:
2026-02-10 16:47:18 -05:00
parent 5b85f61125
commit 6ffb2ef6bc
+84 -82
View File
@@ -2,7 +2,7 @@
-- This file contains the complete current schema for the Bookhoard media library management system
-- Create library types table
CREATE TABLE library_types (
CREATE TABLE IF NOT EXISTS library_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
@@ -14,10 +14,11 @@ CREATE TABLE library_types (
INSERT INTO library_types (name, description, allowed_extensions) VALUES
('ebooks', 'Ebook files including EPUB, PDF, MOBI, etc.', ARRAY['.epub', '.pdf', '.mobi', '.azw', '.azw3', '.txt', '.rtf', '.doc', '.docx', '.lit', '.fb2', '.pdb']),
('comics', 'Comic book archives and image formats', ARRAY['.cbz', '.cbr', '.cb7', '.cbt', '.pdf']),
('manga', 'Manga files including archives and image folders', ARRAY['.cbz', '.cbr', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']);
('manga', 'Manga files including archives and image folders', ARRAY['.cbz', '.cbr', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'])
ON CONFLICT (name) DO NOTHING;
-- Create users table
CREATE TABLE users (
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(255) UNIQUE NOT NULL,
@@ -32,7 +33,7 @@ CREATE TABLE users (
);
-- Create system_settings table
CREATE TABLE system_settings (
CREATE TABLE IF NOT EXISTS system_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
setting_key VARCHAR(100) UNIQUE NOT NULL,
setting_value TEXT NOT NULL,
@@ -43,10 +44,11 @@ CREATE TABLE system_settings (
-- Insert default system settings
INSERT INTO system_settings (setting_key, setting_value, description) VALUES
('scan_frequency_minutes', '60', 'How often to scan all libraries in minutes'),
('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide');
('auto_scan_enabled', 'true', 'Whether auto-scanning is enabled system-wide')
ON CONFLICT (setting_key) DO NOTHING;
-- Create refresh_tokens table
CREATE TABLE refresh_tokens (
CREATE TABLE IF NOT EXISTS refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
@@ -56,7 +58,7 @@ CREATE TABLE refresh_tokens (
);
-- Create libraries table
CREATE TABLE libraries (
CREATE TABLE IF NOT EXISTS libraries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
@@ -67,7 +69,7 @@ CREATE TABLE libraries (
);
-- Create library_folders table for multiple folders per library
CREATE TABLE library_folders (
CREATE TABLE IF NOT EXISTS library_folders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
folder_path VARCHAR(500) NOT NULL,
@@ -76,7 +78,7 @@ CREATE TABLE library_folders (
);
-- Create library_visibility table for user-specific library visibility
CREATE TABLE library_visibility (
CREATE TABLE IF NOT EXISTS library_visibility (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
@@ -87,7 +89,7 @@ CREATE TABLE library_visibility (
);
-- Create media_items table
CREATE TABLE media_items (
CREATE TABLE IF NOT EXISTS media_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
@@ -132,8 +134,8 @@ CREATE TABLE media_items (
);
-- Add GIN indexes for fast array searches
CREATE INDEX idx_media_items_tags_gin ON media_items USING GIN (tags);
CREATE INDEX idx_media_items_contributors_gin ON media_items USING GIN (contributors);
CREATE INDEX IF NOT EXISTS idx_media_items_tags_gin ON media_items USING GIN (tags);
CREATE INDEX IF NOT EXISTS idx_media_items_contributors_gin ON media_items USING GIN (contributors);
-- Add search fields for case-insensitive, punctuation-free searching
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS tags_search TEXT[];
@@ -144,7 +146,7 @@ CREATE INDEX IF NOT EXISTS idx_media_items_tags_search ON media_items USING GIN
CREATE INDEX IF NOT EXISTS idx_media_items_contributors_search ON media_items USING GIN (contributors_search);
-- Create reading_progress table
CREATE TABLE reading_progress (
CREATE TABLE IF NOT EXISTS reading_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -174,7 +176,7 @@ CREATE TABLE reading_progress (
);
-- Create media_ratings table
CREATE TABLE media_ratings (
CREATE TABLE IF NOT EXISTS media_ratings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -185,7 +187,7 @@ CREATE TABLE media_ratings (
);
-- Create media_notes table for user notes on media items
CREATE TABLE media_notes (
CREATE TABLE IF NOT EXISTS media_notes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -204,7 +206,7 @@ CREATE TABLE media_notes (
);
-- Create media_highlights table for user highlights on media items
CREATE TABLE media_highlights (
CREATE TABLE IF NOT EXISTS media_highlights (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -232,7 +234,7 @@ CREATE TABLE media_highlights (
-- ============================================
-- DEVICE REGISTRY (Phase 1)
-- ============================================
CREATE TABLE devices (
CREATE TABLE IF NOT EXISTS devices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
device_name VARCHAR(100) NOT NULL,
@@ -252,7 +254,7 @@ CREATE TABLE devices (
-- ============================================
-- SYNC QUEUE FOR OFFLINE SUPPORT (Phase 1)
-- ============================================
CREATE TABLE sync_queue (
CREATE TABLE IF NOT EXISTS sync_queue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE,
@@ -270,7 +272,7 @@ CREATE TABLE sync_queue (
-- ============================================
-- CONFLICT RESOLUTION (Phase 1)
-- ============================================
CREATE TABLE sync_conflicts (
CREATE TABLE IF NOT EXISTS sync_conflicts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -286,7 +288,7 @@ CREATE TABLE sync_conflicts (
-- ============================================
-- KOBO SHELF MANAGEMENT (Phase 4)
-- ============================================
CREATE TABLE kobo_shelves (
CREATE TABLE IF NOT EXISTS kobo_shelves (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
@@ -297,14 +299,14 @@ CREATE TABLE kobo_shelves (
UNIQUE(device_id, media_item_id)
);
CREATE INDEX idx_kobo_shelves_device_id ON kobo_shelves(device_id);
CREATE INDEX idx_kobo_shelves_media_item_id ON kobo_shelves(media_item_id);
CREATE INDEX idx_kobo_shelves_shelf_name ON kobo_shelves(shelf_name);
CREATE INDEX IF NOT EXISTS idx_kobo_shelves_device_id ON kobo_shelves(device_id);
CREATE INDEX IF NOT EXISTS idx_kobo_shelves_media_item_id ON kobo_shelves(media_item_id);
CREATE INDEX IF NOT EXISTS idx_kobo_shelves_shelf_name ON kobo_shelves(shelf_name);
-- ============================================
-- KOBO ENTITLEMENTS TRACKING (Phase 4)
-- ============================================
CREATE TABLE kobo_entitlements (
CREATE TABLE IF NOT EXISTS kobo_entitlements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
@@ -321,15 +323,15 @@ CREATE TABLE kobo_entitlements (
UNIQUE(device_id, entitlement_id)
);
CREATE INDEX idx_kobo_entitlements_device_id ON kobo_entitlements(device_id);
CREATE INDEX idx_kobo_entitlements_media_item_id ON kobo_entitlements(media_item_id);
CREATE INDEX idx_kobo_entitlements_entitlement_id ON kobo_entitlements(entitlement_id);
CREATE INDEX idx_kobo_entitlements_content_id ON kobo_entitlements(content_id);
CREATE INDEX IF NOT EXISTS idx_kobo_entitlements_device_id ON kobo_entitlements(device_id);
CREATE INDEX IF NOT EXISTS idx_kobo_entitlements_media_item_id ON kobo_entitlements(media_item_id);
CREATE INDEX IF NOT EXISTS idx_kobo_entitlements_entitlement_id ON kobo_entitlements(entitlement_id);
CREATE INDEX IF NOT EXISTS idx_kobo_entitlements_content_id ON kobo_entitlements(content_id);
-- ============================================
-- READING HISTORY (Phase 1)
-- ============================================
CREATE TABLE reading_history (
CREATE TABLE IF NOT EXISTS reading_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
@@ -345,87 +347,87 @@ CREATE TABLE reading_history (
-- Create indexes for better query performance
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_library_types_name ON library_types(name);
CREATE INDEX idx_refresh_tokens_token ON refresh_tokens(token);
CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_library_types_name ON library_types(name);
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token);
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id);
-- Library indexes
CREATE INDEX idx_libraries_name ON libraries(name);
CREATE INDEX idx_libraries_library_type_id ON libraries(library_type_id);
CREATE INDEX idx_libraries_created_by_admin_id ON libraries(created_by_admin_id);
CREATE INDEX idx_library_folders_library_id ON library_folders(library_id);
CREATE INDEX idx_library_visibility_user_id ON library_visibility(user_id);
CREATE INDEX idx_library_visibility_library_id ON library_visibility(library_id);
CREATE INDEX IF NOT EXISTS idx_libraries_name ON libraries(name);
CREATE INDEX IF NOT EXISTS idx_libraries_library_type_id ON libraries(library_type_id);
CREATE INDEX IF NOT EXISTS idx_libraries_created_by_admin_id ON libraries(created_by_admin_id);
CREATE INDEX IF NOT EXISTS idx_library_folders_library_id ON library_folders(library_id);
CREATE INDEX IF NOT EXISTS idx_library_visibility_user_id ON library_visibility(user_id);
CREATE INDEX IF NOT EXISTS idx_library_visibility_library_id ON library_visibility(library_id);
-- Media items indexes
CREATE INDEX idx_media_items_title ON media_items(title);
CREATE INDEX idx_media_items_author ON media_items(author);
CREATE INDEX idx_media_items_library_id ON media_items(library_id);
CREATE INDEX idx_media_items_added_by_admin_id ON media_items(added_by_admin_id);
CREATE INDEX idx_media_items_genre ON media_items(genre);
CREATE INDEX idx_media_items_language ON media_items(language);
CREATE INDEX idx_media_items_copyright_year ON media_items(copyright_year);
CREATE INDEX idx_media_items_page_count ON media_items(page_count);
CREATE INDEX idx_media_items_series ON media_items(series);
CREATE INDEX IF NOT EXISTS idx_media_items_title ON media_items(title);
CREATE INDEX IF NOT EXISTS idx_media_items_author ON media_items(author);
CREATE INDEX IF NOT EXISTS idx_media_items_library_id ON media_items(library_id);
CREATE INDEX IF NOT EXISTS idx_media_items_added_by_admin_id ON media_items(added_by_admin_id);
CREATE INDEX IF NOT EXISTS idx_media_items_genre ON media_items(genre);
CREATE INDEX IF NOT EXISTS idx_media_items_language ON media_items(language);
CREATE INDEX IF NOT EXISTS idx_media_items_copyright_year ON media_items(copyright_year);
CREATE INDEX IF NOT EXISTS idx_media_items_page_count ON media_items(page_count);
CREATE INDEX IF NOT EXISTS idx_media_items_series ON media_items(series);
-- Progress and ratings indexes
CREATE INDEX idx_reading_progress_media_item_id ON reading_progress(media_item_id);
CREATE INDEX idx_reading_progress_user_id ON reading_progress(user_id);
CREATE INDEX idx_media_ratings_media_item_id ON media_ratings(media_item_id);
CREATE INDEX idx_media_ratings_user_id ON media_ratings(user_id);
CREATE INDEX IF NOT EXISTS idx_reading_progress_media_item_id ON reading_progress(media_item_id);
CREATE INDEX IF NOT EXISTS idx_reading_progress_user_id ON reading_progress(user_id);
CREATE INDEX IF NOT EXISTS idx_media_ratings_media_item_id ON media_ratings(media_item_id);
CREATE INDEX IF NOT EXISTS idx_media_ratings_user_id ON media_ratings(user_id);
-- Notes and highlights indexes
CREATE INDEX idx_media_notes_media_item_id ON media_notes(media_item_id);
CREATE INDEX idx_media_notes_user_id ON media_notes(user_id);
CREATE INDEX idx_media_highlights_media_item_id ON media_highlights(media_item_id);
CREATE INDEX idx_media_highlights_user_id ON media_highlights(user_id);
CREATE INDEX idx_media_highlights_note_id ON media_highlights(note_id);
CREATE INDEX IF NOT EXISTS idx_media_notes_media_item_id ON media_notes(media_item_id);
CREATE INDEX IF NOT EXISTS idx_media_notes_user_id ON media_notes(user_id);
CREATE INDEX IF NOT EXISTS idx_media_highlights_media_item_id ON media_highlights(media_item_id);
CREATE INDEX IF NOT EXISTS idx_media_highlights_user_id ON media_highlights(user_id);
CREATE INDEX IF NOT EXISTS idx_media_highlights_note_id ON media_highlights(note_id);
-- ============================================
-- NEW TABLE INDEXES (Phase 1)
-- ============================================
-- Device registry indexes
CREATE INDEX idx_devices_user_id ON devices(user_id);
CREATE INDEX idx_devices_device_type ON devices(device_type);
CREATE INDEX idx_devices_device_identifier ON devices(device_identifier);
CREATE INDEX IF NOT EXISTS idx_devices_user_id ON devices(user_id);
CREATE INDEX IF NOT EXISTS idx_devices_device_type ON devices(device_type);
CREATE INDEX IF NOT EXISTS idx_devices_device_identifier ON devices(device_identifier);
-- Sync queue indexes
CREATE INDEX idx_sync_queue_device_id ON sync_queue(device_id);
CREATE INDEX idx_sync_queue_status ON sync_queue(status);
CREATE INDEX idx_sync_queue_priority ON sync_queue(priority);
CREATE INDEX IF NOT EXISTS idx_sync_queue_device_id ON sync_queue(device_id);
CREATE INDEX IF NOT EXISTS idx_sync_queue_status ON sync_queue(status);
CREATE INDEX IF NOT EXISTS idx_sync_queue_priority ON sync_queue(priority);
-- Sync conflicts indexes
CREATE INDEX idx_sync_conflicts_media_item_id ON sync_conflicts(media_item_id);
CREATE INDEX idx_sync_conflicts_user_id ON sync_conflicts(user_id);
CREATE INDEX idx_sync_conflicts_status ON sync_conflicts(resolution_status);
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_media_item_id ON sync_conflicts(media_item_id);
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_user_id ON sync_conflicts(user_id);
CREATE INDEX IF NOT EXISTS idx_sync_conflicts_status ON sync_conflicts(resolution_status);
-- Composite indexes for sync queue performance (Phase 7)
CREATE INDEX idx_sync_queue_device_status_priority ON sync_queue(device_id, status, priority ASC, created_at ASC);
CREATE INDEX idx_sync_queue_status_priority_created ON sync_queue(status, priority ASC, created_at ASC);
CREATE INDEX idx_sync_queue_device_created_at ON sync_queue(device_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sync_queue_device_status_priority ON sync_queue(device_id, status, priority ASC, created_at ASC);
CREATE INDEX IF NOT EXISTS idx_sync_queue_status_priority_created ON sync_queue(status, priority ASC, created_at ASC);
CREATE INDEX IF NOT EXISTS idx_sync_queue_device_created_at ON sync_queue(device_id, created_at DESC);
-- Composite indexes for reading progress performance (Phase 7)
CREATE INDEX idx_reading_progress_user_last_sync ON reading_progress(user_id, last_sync_timestamp DESC);
CREATE INDEX idx_reading_progress_media_last_sync ON reading_progress(media_item_id, last_sync_timestamp DESC);
CREATE INDEX idx_reading_progress_user_sync_source ON reading_progress(user_id, last_sync_source, last_sync_timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_reading_progress_user_last_sync ON reading_progress(user_id, last_sync_timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_reading_progress_media_last_sync ON reading_progress(media_item_id, last_sync_timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_reading_progress_user_sync_source ON reading_progress(user_id, last_sync_source, last_sync_timestamp DESC);
-- Composite indexes for devices (Phase 7)
CREATE INDEX idx_devices_user_sync_enabled ON devices(user_id, sync_enabled, auto_sync);
CREATE INDEX idx_devices_user_type ON devices(user_id, device_type);
CREATE INDEX idx_devices_last_seen ON devices(last_seen DESC) WHERE sync_enabled = true;
CREATE INDEX IF NOT EXISTS idx_devices_user_sync_enabled ON devices(user_id, sync_enabled, auto_sync);
CREATE INDEX IF NOT EXISTS idx_devices_user_type ON devices(user_id, device_type);
CREATE INDEX IF NOT EXISTS idx_devices_last_seen ON devices(last_seen DESC) WHERE sync_enabled = true;
-- Composite indexes for annotations (Phase 7)
CREATE INDEX idx_media_notes_user_media ON media_notes(user_id, media_item_id);
CREATE INDEX idx_media_highlights_user_media ON media_highlights(user_id, media_item_id);
CREATE INDEX IF NOT EXISTS idx_media_notes_user_media ON media_notes(user_id, media_item_id);
CREATE INDEX IF NOT EXISTS idx_media_highlights_user_media ON media_highlights(user_id, media_item_id);
-- Reading history indexes
CREATE INDEX idx_reading_history_user_id ON reading_history(user_id);
CREATE INDEX idx_reading_history_media_item_id ON reading_history(media_item_id);
CREATE INDEX idx_reading_history_created_at ON reading_history(created_at DESC);
CREATE INDEX idx_reading_history_user_device_created ON reading_history(user_id, device_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_reading_history_user_id ON reading_history(user_id);
CREATE INDEX IF NOT EXISTS idx_reading_history_media_item_id ON reading_history(media_item_id);
CREATE INDEX IF NOT EXISTS idx_reading_history_created_at ON reading_history(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_reading_history_user_device_created ON reading_history(user_id, device_id, created_at DESC);
-- ============================================
-- TRIGGER FUNCTIONS (Phase 1)