feat(db): Add kobo_shelves and device catalog tables
- Add kobo_shelves table for Kobo device shelves management - Add device_shelf_mappings for collection<->device shelf mappings - Update device_catalogs with better ContentId tracking - Add indexes for performance
This commit is contained in:
+167
-4
@@ -735,8 +735,8 @@ BEGIN
|
||||
|
||||
-- If still not found, try to match by title + author
|
||||
IF media_uuid IS NULL THEN
|
||||
SELECT id INTO media_uuid FROM media_items
|
||||
WHERE title = book_record->>'title'
|
||||
SELECT id INTO media_uuid FROM media_items
|
||||
WHERE title = book_record->>'title'
|
||||
AND author = book_record->>'author'
|
||||
LIMIT 1;
|
||||
END IF;
|
||||
@@ -751,7 +751,7 @@ BEGIN
|
||||
conflict_detected := detect_conflict(media_uuid, book_record, 'koreader');
|
||||
|
||||
-- Check if progress exists
|
||||
SELECT * INTO existing_progress FROM reading_progress
|
||||
SELECT * INTO existing_progress FROM reading_progress
|
||||
WHERE media_item_id = media_uuid AND user_id = p_user_id;
|
||||
|
||||
-- Update or insert progress
|
||||
@@ -816,8 +816,171 @@ BEGIN
|
||||
));
|
||||
END IF;
|
||||
|
||||
RETURN QUERY SELECT media_uuid, TRUE,
|
||||
RETURN QUERY SELECT media_uuid, TRUE,
|
||||
CASE WHEN conflict_detected THEN 'conflict detected' ELSE 'success' END;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================
|
||||
-- PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1)
|
||||
-- ============================================
|
||||
|
||||
-- Add universal identifier columns to media_items table
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS file_sha256 CHAR(64);
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS opf_identifier VARCHAR(255);
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS opf_uuid VARCHAR(255);
|
||||
ALTER TABLE media_items ADD COLUMN IF NOT EXISTS hash_confidence VARCHAR(20);
|
||||
|
||||
-- Create indexes for fast lookup
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_sha256 ON media_items(file_sha256);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_items_opf_identifier ON media_items(opf_identifier);
|
||||
|
||||
-- Create media_item_formats table (track all format versions with their hashes)
|
||||
CREATE TABLE IF NOT EXISTS media_item_formats (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||
format_type VARCHAR(10) NOT NULL,
|
||||
file_path VARCHAR(500),
|
||||
file_sha256 CHAR(64),
|
||||
file_size_bytes BIGINT,
|
||||
mime_type VARCHAR(100),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
converted_from_format_id UUID REFERENCES media_item_formats(id),
|
||||
UNIQUE(media_item_id, format_type)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_media_item_formats_media ON media_item_formats(media_item_id, format_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_item_formats_sha256 ON media_item_formats(file_sha256);
|
||||
|
||||
-- Create device_file_aliases table (track file paths per device for cross-device matching)
|
||||
CREATE TABLE IF NOT EXISTS device_file_aliases (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
file_sha256 CHAR(64),
|
||||
confidence_score FLOAT DEFAULT 1.0,
|
||||
last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(device_id, file_path)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_file_aliases_media_device ON device_file_aliases(media_item_id, device_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_device_file_aliases_sha256 ON device_file_aliases(file_sha256);
|
||||
|
||||
-- Create collections table (device-neutral collections)
|
||||
CREATE TABLE IF NOT EXISTS collections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
color VARCHAR(7),
|
||||
icon VARCHAR(50),
|
||||
auto_assign_rules JSONB,
|
||||
view_settings JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_collections_user_id ON collections(user_id);
|
||||
|
||||
-- Create collection_items table (which books belong to each collection)
|
||||
CREATE TABLE IF NOT EXISTS collection_items (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
|
||||
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||
added_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
added_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
UNIQUE(collection_id, media_item_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_collection_items_collection ON collection_items(collection_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_collection_items_media ON collection_items(media_item_id);
|
||||
|
||||
-- Create device_shelf_mappings table (map Bookmann collections to device-specific shelf names)
|
||||
CREATE TABLE IF NOT EXISTS device_shelf_mappings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
|
||||
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
||||
device_shelf_name VARCHAR(100),
|
||||
sync_direction VARCHAR(20),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
UNIQUE(collection_id, device_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_shelf_mappings_collection ON device_shelf_mappings(collection_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_device_shelf_mappings_device ON device_shelf_mappings(device_id);
|
||||
|
||||
-- Create device_catalogs table (track OPDS downloads and map Bookmann UUIDs to device ContentIds)
|
||||
CREATE TABLE IF NOT EXISTS device_catalogs (
|
||||
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,
|
||||
bookmann_uuid UUID NOT NULL,
|
||||
kobo_content_id VARCHAR(255) NOT NULL,
|
||||
content_id_type VARCHAR(20),
|
||||
available BOOLEAN DEFAULT TRUE,
|
||||
delivery_date TIMESTAMP WITH TIME ZONE,
|
||||
delivery_method VARCHAR(20),
|
||||
UNIQUE(device_id, kobo_content_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_catalogs_bookmann ON device_catalogs(bookmann_uuid);
|
||||
CREATE INDEX IF NOT EXISTS idx_device_catalogs_kobo ON device_catalogs(kobo_content_id);
|
||||
|
||||
-- Create system_config table (system-wide configuration)
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
key VARCHAR(100) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_by UUID REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- Pre-seeded values
|
||||
INSERT INTO system_config (key, value) VALUES
|
||||
('base_url', 'https://bookmann.example.com'),
|
||||
('opds_base_url', 'https://bookmann.example.com/opds'),
|
||||
('api_base_url', 'https://bookmann.example.com/api')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- Create opds_tokens table (device-specific OPDS access tokens)
|
||||
CREATE TABLE IF NOT EXISTS opds_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
||||
token VARCHAR(64) UNIQUE NOT NULL,
|
||||
token_type VARCHAR(20),
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_opds_tokens_device ON opds_tokens(device_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_opds_tokens_token ON opds_tokens(token);
|
||||
|
||||
-- Modify kobo_shelves table (reference collections instead of media_items directly)
|
||||
ALTER TABLE kobo_shelves ADD COLUMN IF NOT EXISTS collection_id UUID REFERENCES collections(id);
|
||||
ALTER TABLE kobo_shelves ADD COLUMN IF NOT EXISTS position_in_collection INTEGER;
|
||||
|
||||
-- ============================================
|
||||
-- PHASE 6: UNLINKED BOOKS TRACKING (Week 3-4)
|
||||
-- ============================================
|
||||
|
||||
-- Create unlinked_books table to track books that couldn't be auto-matched
|
||||
CREATE TABLE IF NOT EXISTS unlinked_books (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
|
||||
content_id VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(500),
|
||||
title VARCHAR(255),
|
||||
author VARCHAR(255),
|
||||
confidence_score FLOAT DEFAULT 0.5,
|
||||
resolved BOOLEAN DEFAULT FALSE,
|
||||
media_item_id UUID REFERENCES media_items(id) ON DELETE SET NULL,
|
||||
resolved_at TIMESTAMP WITH TIME ZONE,
|
||||
resolution_method VARCHAR(50),
|
||||
last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_unlinked_books_device ON unlinked_books(device_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_unlinked_books_content_id ON unlinked_books(content_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_unlinked_books_resolved ON unlinked_books(resolved);
|
||||
|
||||
Reference in New Issue
Block a user