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);
|
||||
|
||||
+108
-7
@@ -8,6 +8,57 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type CollectionItems struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
||||
AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"`
|
||||
}
|
||||
|
||||
type Collections struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description pgtype.Text `db:"description" json:"description"`
|
||||
Color pgtype.Text `db:"color" json:"color"`
|
||||
Icon pgtype.Text `db:"icon" json:"icon"`
|
||||
AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"`
|
||||
ViewSettings []byte `db:"view_settings" json:"view_settings"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type DeviceCatalogs struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
BookmannUuid pgtype.UUID `db:"bookmann_uuid" json:"bookmann_uuid"`
|
||||
KoboContentID string `db:"kobo_content_id" json:"kobo_content_id"`
|
||||
ContentIDType pgtype.Text `db:"content_id_type" json:"content_id_type"`
|
||||
Available pgtype.Bool `db:"available" json:"available"`
|
||||
DeliveryDate pgtype.Timestamptz `db:"delivery_date" json:"delivery_date"`
|
||||
DeliveryMethod pgtype.Text `db:"delivery_method" json:"delivery_method"`
|
||||
}
|
||||
|
||||
type DeviceFileAliases struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
FilePath string `db:"file_path" json:"file_path"`
|
||||
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
||||
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
||||
LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"`
|
||||
}
|
||||
|
||||
type DeviceShelfMappings struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"`
|
||||
SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type Devices struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
@@ -163,13 +214,15 @@ type KoboEntitlements struct {
|
||||
}
|
||||
|
||||
type KoboShelves struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
||||
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
||||
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
||||
LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"`
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"`
|
||||
ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"`
|
||||
AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"`
|
||||
LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"`
|
||||
CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"`
|
||||
PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"`
|
||||
}
|
||||
|
||||
type Libraries struct {
|
||||
@@ -230,6 +283,18 @@ type MediaHighlights struct {
|
||||
DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"`
|
||||
}
|
||||
|
||||
type MediaItemFormats struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
FormatType string `db:"format_type" json:"format_type"`
|
||||
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
||||
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
||||
FileSizeBytes pgtype.Int8 `db:"file_size_bytes" json:"file_size_bytes"`
|
||||
MimeType pgtype.Text `db:"mime_type" json:"mime_type"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
ConvertedFromFormatID pgtype.UUID `db:"converted_from_format_id" json:"converted_from_format_id"`
|
||||
}
|
||||
|
||||
type MediaItems struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
LibraryID pgtype.UUID `db:"library_id" json:"library_id"`
|
||||
@@ -277,6 +342,10 @@ type MediaItems struct {
|
||||
RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"`
|
||||
KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"`
|
||||
KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"`
|
||||
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
|
||||
OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"`
|
||||
OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"`
|
||||
HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"`
|
||||
}
|
||||
|
||||
type MediaNotes struct {
|
||||
@@ -306,6 +375,15 @@ type MediaRatings struct {
|
||||
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
type OpdsTokens struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
Token string `db:"token" json:"token"`
|
||||
TokenType pgtype.Text `db:"token_type" json:"token_type"`
|
||||
ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type ReadingHistory struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
UserID pgtype.UUID `db:"user_id" json:"user_id"`
|
||||
@@ -383,6 +461,29 @@ type SyncQueue struct {
|
||||
ProcessedAt pgtype.Timestamptz `db:"processed_at" json:"processed_at"`
|
||||
}
|
||||
|
||||
type SystemConfig struct {
|
||||
Key string `db:"key" json:"key"`
|
||||
Value string `db:"value" json:"value"`
|
||||
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
|
||||
UpdatedBy pgtype.UUID `db:"updated_by" json:"updated_by"`
|
||||
}
|
||||
|
||||
type UnlinkedBooks struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
|
||||
ContentID string `db:"content_id" json:"content_id"`
|
||||
FilePath pgtype.Text `db:"file_path" json:"file_path"`
|
||||
Title pgtype.Text `db:"title" json:"title"`
|
||||
Author pgtype.Text `db:"author" json:"author"`
|
||||
ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"`
|
||||
Resolved pgtype.Bool `db:"resolved" json:"resolved"`
|
||||
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
|
||||
ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"`
|
||||
ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"`
|
||||
LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"`
|
||||
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type Users struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
Email string `db:"email" json:"email"`
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
// COLLECTION ITEMS QUERIES
|
||||
// Add book to collection
|
||||
AddBookToCollection(ctx context.Context, arg AddBookToCollectionParams) (CollectionItems, error)
|
||||
// ============================================
|
||||
// KOBO SHELF MANAGEMENT QUERIES (Phase 4)
|
||||
// ============================================
|
||||
@@ -20,16 +23,32 @@ type Querier interface {
|
||||
// Bulk update format group for all media items
|
||||
BulkUpdateFormatGroups(ctx context.Context) error
|
||||
BulkUpdateProgressFromSync(ctx context.Context, arg BulkUpdateProgressFromSyncParams) ([]interface{}, error)
|
||||
// Cleanup expired OPDS tokens
|
||||
CleanupExpiredOpdsTokens(ctx context.Context) error
|
||||
CleanupExpiredRefreshTokens(ctx context.Context) error
|
||||
ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID) error
|
||||
ClearKoboShelf(ctx context.Context, deviceID pgtype.UUID) error
|
||||
ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfByNameParams) error
|
||||
// Count unlinked books for a device
|
||||
CountUnlinkedBooks(ctx context.Context, deviceID pgtype.UUID) (int64, error)
|
||||
CountUserDevices(ctx context.Context, userID pgtype.UUID) (int64, error)
|
||||
// COLLECTIONS QUERIES
|
||||
// Create collection
|
||||
CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collections, error)
|
||||
// ============================================
|
||||
// PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6)
|
||||
// ============================================
|
||||
// Device Registration & Management
|
||||
CreateDevice(ctx context.Context, arg CreateDeviceParams) (Devices, error)
|
||||
// DEVICE CATALOGS QUERIES
|
||||
// Create device catalog entry
|
||||
CreateDeviceCatalog(ctx context.Context, arg CreateDeviceCatalogParams) (DeviceCatalogs, error)
|
||||
// DEVICE FILE ALIASES QUERIES
|
||||
// Create device file alias
|
||||
CreateDeviceFileAlias(ctx context.Context, arg CreateDeviceFileAliasParams) (DeviceFileAliases, error)
|
||||
// DEVICE SHELF MAPPINGS QUERIES
|
||||
// Create device shelf mapping
|
||||
CreateDeviceShelfMapping(ctx context.Context, arg CreateDeviceShelfMappingParams) (DeviceShelfMappings, error)
|
||||
// Backward compatibility - Ebook Notes queries (using views)
|
||||
CreateEbookNote(ctx context.Context, arg CreateEbookNoteParams) (MediaNotes, error)
|
||||
// Libraries queries
|
||||
@@ -38,9 +57,15 @@ type Querier interface {
|
||||
CreateMediaHighlight(ctx context.Context, arg CreateMediaHighlightParams) (MediaHighlights, error)
|
||||
// Media Items queries
|
||||
CreateMediaItem(ctx context.Context, arg CreateMediaItemParams) (MediaItems, error)
|
||||
// MEDIA ITEM FORMATS QUERIES
|
||||
// Create media item format
|
||||
CreateMediaItemFormat(ctx context.Context, arg CreateMediaItemFormatParams) (MediaItemFormats, error)
|
||||
// Media Notes queries
|
||||
CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams) (MediaNotes, error)
|
||||
CreateMediaRating(ctx context.Context, arg CreateMediaRatingParams) (MediaRatings, error)
|
||||
// OPDS TOKENS QUERIES
|
||||
// Create OPDS token
|
||||
CreateOpdsToken(ctx context.Context, arg CreateOpdsTokenParams) (OpdsTokens, error)
|
||||
// ============================================
|
||||
// KOBO ENTITLEMENT QUERIES (Phase 4)
|
||||
// ============================================
|
||||
@@ -54,26 +79,71 @@ type Querier interface {
|
||||
CreateSyncHistoryEntry(ctx context.Context, arg CreateSyncHistoryEntryParams) (SyncQueue, error)
|
||||
// Sync Queue Management
|
||||
CreateSyncQueueItem(ctx context.Context, arg CreateSyncQueueItemParams) (SyncQueue, error)
|
||||
// ============================================
|
||||
// PHASE 6: ENHANCED KOBO SYNC (Week 3-4)
|
||||
// ============================================
|
||||
// Create unlinked book entry
|
||||
CreateUnlinkedBook(ctx context.Context, arg CreateUnlinkedBookParams) (UnlinkedBooks, error)
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error)
|
||||
// Delete collection
|
||||
DeleteCollection(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteDevice(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteDeviceByToken(ctx context.Context, authToken string) error
|
||||
// Delete device catalog entry
|
||||
DeleteDeviceCatalog(ctx context.Context, id pgtype.UUID) error
|
||||
// Delete device file alias
|
||||
DeleteDeviceFileAlias(ctx context.Context, id pgtype.UUID) error
|
||||
// Delete device shelf mapping
|
||||
DeleteDeviceShelfMapping(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteEbookNote(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteKoboEntitlement(ctx context.Context, arg DeleteKoboEntitlementParams) error
|
||||
DeleteLibrary(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFolderParams) (LibraryFolders, error)
|
||||
DeleteMediaHighlight(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteMediaItem(ctx context.Context, id pgtype.UUID) error
|
||||
// Delete media item format
|
||||
DeleteMediaItemFormat(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteMediaNote(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteMediaRating(ctx context.Context, arg DeleteMediaRatingParams) error
|
||||
DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error
|
||||
DeleteSyncConflict(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteSyncQueueItem(ctx context.Context, id pgtype.UUID) error
|
||||
// Delete system config
|
||||
DeleteSystemConfig(ctx context.Context, key string) error
|
||||
DeleteUser(ctx context.Context, id pgtype.UUID) error
|
||||
GenerateKoboEntitlementId(ctx context.Context) (interface{}, error)
|
||||
// Get all system config
|
||||
GetAllSystemConfig(ctx context.Context) ([]SystemConfig, error)
|
||||
GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsForBookParams) ([]GetAnnotationsForBookRow, error)
|
||||
// Get collection
|
||||
GetCollection(ctx context.Context, id pgtype.UUID) (Collections, error)
|
||||
// Get collection items
|
||||
GetCollectionItems(ctx context.Context, collectionID pgtype.UUID) ([]GetCollectionItemsRow, error)
|
||||
// Get collection with book count
|
||||
GetCollectionWithBookCount(ctx context.Context, id pgtype.UUID) (GetCollectionWithBookCountRow, error)
|
||||
// Get collections by user
|
||||
GetCollectionsByUser(ctx context.Context, userID pgtype.UUID) ([]Collections, error)
|
||||
// Get collections for book
|
||||
GetCollectionsForBook(ctx context.Context, mediaItemID pgtype.UUID) ([]Collections, error)
|
||||
GetDevice(ctx context.Context, id pgtype.UUID) (Devices, error)
|
||||
GetDeviceByAuthToken(ctx context.Context, authToken string) (Devices, error)
|
||||
GetDeviceByIdentifier(ctx context.Context, deviceIdentifier string) (Devices, error)
|
||||
// Get device catalog by Bookmann UUID
|
||||
GetDeviceCatalogByBookmannUUID(ctx context.Context, arg GetDeviceCatalogByBookmannUUIDParams) (DeviceCatalogs, error)
|
||||
// Get device catalog by Kobo ContentId
|
||||
GetDeviceCatalogByKoboContentId(ctx context.Context, koboContentID string) (DeviceCatalogs, error)
|
||||
// Get device catalog entries
|
||||
GetDeviceCatalogEntries(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceCatalogEntriesRow, error)
|
||||
// Get device file alias
|
||||
GetDeviceFileAlias(ctx context.Context, arg GetDeviceFileAliasParams) (DeviceFileAliases, error)
|
||||
// Get device file alias by SHA-256
|
||||
GetDeviceFileAliasBySHA256(ctx context.Context, fileSha256 pgtype.Text) (GetDeviceFileAliasBySHA256Row, error)
|
||||
// Get device file aliases by device
|
||||
GetDeviceFileAliasesByDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceFileAliasesByDeviceRow, error)
|
||||
// Get device shelf mapping
|
||||
GetDeviceShelfMapping(ctx context.Context, arg GetDeviceShelfMappingParams) (DeviceShelfMappings, error)
|
||||
// Get device shelf mappings
|
||||
GetDeviceShelfMappings(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceShelfMappingsRow, error)
|
||||
GetFailedSyncQueueItems(ctx context.Context, limit int32) ([]SyncQueue, error)
|
||||
GetKoboEntitlementByContentId(ctx context.Context, arg GetKoboEntitlementByContentIdParams) (GetKoboEntitlementByContentIdRow, error)
|
||||
GetKoboEntitlementByEntitlementId(ctx context.Context, arg GetKoboEntitlementByEntitlementIdParams) (GetKoboEntitlementByEntitlementIdRow, error)
|
||||
@@ -81,6 +151,8 @@ type Querier interface {
|
||||
GetKoboShelfBookCount(ctx context.Context, deviceID pgtype.UUID) (int64, error)
|
||||
GetKoboShelfBooks(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboShelfBooksRow, error)
|
||||
GetKoboShelfBooksByShelfName(ctx context.Context, arg GetKoboShelfBooksByShelfNameParams) ([]GetKoboShelfBooksByShelfNameRow, error)
|
||||
// Get Kobo shelves by collection
|
||||
GetKoboShelvesByCollection(ctx context.Context, arg GetKoboShelvesByCollectionParams) ([]GetKoboShelvesByCollectionRow, error)
|
||||
GetLibrary(ctx context.Context, id pgtype.UUID) (GetLibraryRow, error)
|
||||
GetLibraryByFolder(ctx context.Context, folderPath string) (GetLibraryByFolderRow, error)
|
||||
GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) ([]LibraryFolders, error)
|
||||
@@ -98,11 +170,27 @@ type Querier interface {
|
||||
// ============================================
|
||||
GetMediaItemByFilePathForSync(ctx context.Context, filePath string) (MediaItems, error)
|
||||
GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error)
|
||||
// Get media item by OPF identifier
|
||||
GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier pgtype.Text) (MediaItems, error)
|
||||
// Get media item by OPF UUID
|
||||
GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text) (MediaItems, error)
|
||||
// Get media item by SHA-256 hash
|
||||
GetMediaItemBySHA256(ctx context.Context, fileSha256 pgtype.Text) (MediaItems, error)
|
||||
// Get media item format by SHA-256
|
||||
GetMediaItemFormatBySHA256(ctx context.Context, fileSha256 pgtype.Text) (MediaItemFormats, error)
|
||||
// Get media item format by type
|
||||
GetMediaItemFormatByType(ctx context.Context, arg GetMediaItemFormatByTypeParams) (MediaItemFormats, error)
|
||||
// Get media item formats
|
||||
GetMediaItemFormats(ctx context.Context, mediaItemID pgtype.UUID) ([]MediaItemFormats, error)
|
||||
GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error)
|
||||
GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([]MediaNotes, error)
|
||||
GetMediaRating(ctx context.Context, arg GetMediaRatingParams) (MediaRatings, error)
|
||||
GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) ([]GetMediaRatingsRow, error)
|
||||
GetNextRetryTime(ctx context.Context) (interface{}, error)
|
||||
// Get OPDS token
|
||||
GetOpdsToken(ctx context.Context, token string) (GetOpdsTokenRow, error)
|
||||
// Get OPDS tokens by device
|
||||
GetOpdsTokensByDevice(ctx context.Context, deviceID pgtype.UUID) ([]OpdsTokens, error)
|
||||
// Get reading history for a user and book
|
||||
GetReadingHistory(ctx context.Context, arg GetReadingHistoryParams) ([]ReadingHistory, error)
|
||||
GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error)
|
||||
@@ -112,8 +200,15 @@ type Querier interface {
|
||||
GetSyncConflict(ctx context.Context, id pgtype.UUID) (SyncConflicts, error)
|
||||
GetSyncQueueItem(ctx context.Context, id pgtype.UUID) (SyncQueue, error)
|
||||
GetSyncQueueStats(ctx context.Context, deviceID pgtype.UUID) (GetSyncQueueStatsRow, error)
|
||||
// SYSTEM CONFIG QUERIES
|
||||
// Get system config
|
||||
GetSystemConfig(ctx context.Context, key string) (SystemConfig, error)
|
||||
// Get universal progress for a book
|
||||
GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error)
|
||||
// Get unlinked book by ContentId
|
||||
GetUnlinkedBookByContentId(ctx context.Context, arg GetUnlinkedBookByContentIdParams) (UnlinkedBooks, error)
|
||||
// Get unlinked books for a device
|
||||
GetUnlinkedBooksByDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetUnlinkedBooksByDeviceRow, error)
|
||||
GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error)
|
||||
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
|
||||
GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error)
|
||||
@@ -124,7 +219,11 @@ type Querier interface {
|
||||
GetUserProgressForBooks(ctx context.Context, arg GetUserProgressForBooksParams) ([]GetUserProgressForBooksRow, error)
|
||||
GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, error)
|
||||
IncrementSyncQueueAttempts(ctx context.Context, arg IncrementSyncQueueAttemptsParams) (SyncQueue, error)
|
||||
// Check if book is in collection
|
||||
IsBookInCollection(ctx context.Context, arg IsBookInCollectionParams) (bool, error)
|
||||
IsBookOnKoboShelf(ctx context.Context, arg IsBookOnKoboShelfParams) (bool, error)
|
||||
// Link unlinked book to media item
|
||||
LinkUnlinkedBook(ctx context.Context, arg LinkUnlinkedBookParams) (UnlinkedBooks, error)
|
||||
ListAllConflictsByUserAndStatus(ctx context.Context, arg ListAllConflictsByUserAndStatusParams) ([]ListAllConflictsByUserAndStatusRow, error)
|
||||
ListAllSyncQueueItems(ctx context.Context, arg ListAllSyncQueueItemsParams) ([]ListAllSyncQueueItemsRow, error)
|
||||
ListDevicesByType(ctx context.Context, deviceType string) ([]Devices, error)
|
||||
@@ -138,10 +237,18 @@ type Querier interface {
|
||||
ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error)
|
||||
ListSyncConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListSyncConflictsByUserRow, error)
|
||||
ListUsers(ctx context.Context) ([]ListUsersRow, error)
|
||||
// Query media items by multiple identifiers with confidence scoring
|
||||
QueryMediaItemsByIdentifiers(ctx context.Context, arg QueryMediaItemsByIdentifiersParams) ([]QueryMediaItemsByIdentifiersRow, error)
|
||||
// Remove book from collection
|
||||
RemoveBookFromCollection(ctx context.Context, arg RemoveBookFromCollectionParams) error
|
||||
RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFromKoboShelfParams) error
|
||||
ResolveSyncConflict(ctx context.Context, arg ResolveSyncConflictParams) (SyncConflicts, error)
|
||||
// Resolve unlinked book
|
||||
ResolveUnlinkedBook(ctx context.Context, arg ResolveUnlinkedBookParams) (UnlinkedBooks, error)
|
||||
RevokeAllUserRefreshTokens(ctx context.Context, userID pgtype.UUID) error
|
||||
RevokeDevice(ctx context.Context, id pgtype.UUID) error
|
||||
// Revoke OPDS token
|
||||
RevokeOpdsToken(ctx context.Context, token string) error
|
||||
RevokeRefreshToken(ctx context.Context, token pgtype.UUID) error
|
||||
// Note: User ebook folders replaced by library folders system
|
||||
// Legacy folder management is now handled through libraries
|
||||
@@ -150,23 +257,43 @@ type Querier interface {
|
||||
SearchMediaItemsFuzzy(ctx context.Context, arg SearchMediaItemsFuzzyParams) ([]SearchMediaItemsFuzzyRow, error)
|
||||
// Library Visibility queries
|
||||
SetLibraryVisibility(ctx context.Context, arg SetLibraryVisibilityParams) (LibraryVisibility, error)
|
||||
// Set system config
|
||||
SetSystemConfig(ctx context.Context, arg SetSystemConfigParams) (SystemConfig, error)
|
||||
// Update collection
|
||||
UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collections, error)
|
||||
UpdateDevice(ctx context.Context, arg UpdateDeviceParams) (Devices, error)
|
||||
// Update device catalog availability
|
||||
UpdateDeviceCatalogAvailability(ctx context.Context, arg UpdateDeviceCatalogAvailabilityParams) error
|
||||
// Update device file alias
|
||||
UpdateDeviceFileAlias(ctx context.Context, arg UpdateDeviceFileAliasParams) (DeviceFileAliases, error)
|
||||
UpdateDeviceLastSeen(ctx context.Context, id pgtype.UUID) (Devices, error)
|
||||
UpdateDeviceLastSync(ctx context.Context, id pgtype.UUID) (Devices, error)
|
||||
// Update device shelf mapping
|
||||
UpdateDeviceShelfMapping(ctx context.Context, arg UpdateDeviceShelfMappingParams) (DeviceShelfMappings, error)
|
||||
UpdateDeviceSyncTimestamp(ctx context.Context, id pgtype.UUID) (Devices, error)
|
||||
UpdateEbookNote(ctx context.Context, arg UpdateEbookNoteParams) (MediaNotes, error)
|
||||
UpdateEmail(ctx context.Context, arg UpdateEmailParams) error
|
||||
UpdateKoboEntitlementRevision(ctx context.Context, arg UpdateKoboEntitlementRevisionParams) error
|
||||
UpdateKoboEntitlementStatus(ctx context.Context, arg UpdateKoboEntitlementStatusParams) error
|
||||
UpdateKoboShelfBookPosition(ctx context.Context, arg UpdateKoboShelfBookPositionParams) error
|
||||
// Update Kobo shelves to support collections
|
||||
UpdateKoboShelfCollection(ctx context.Context, arg UpdateKoboShelfCollectionParams) (KoboShelves, error)
|
||||
UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error)
|
||||
UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighlightParams) (MediaHighlights, error)
|
||||
UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error)
|
||||
// Update media item format
|
||||
UpdateMediaItemFormat(ctx context.Context, arg UpdateMediaItemFormatParams) (MediaItemFormats, error)
|
||||
// ============================================
|
||||
// PHASE 1: FORMAT DETECTION & PROGRESS (Week 2)
|
||||
// ============================================
|
||||
// Update media item format group information
|
||||
UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMediaItemFormatGroupParams) error
|
||||
// Media Items Admin Operations
|
||||
// ============================================
|
||||
// PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1)
|
||||
// ============================================
|
||||
// Update media item with universal identifiers
|
||||
UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMediaItemIdentifiersParams) (MediaItems, error)
|
||||
UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMediaItemKoboMetadataParams) (MediaItems, error)
|
||||
UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams) (MediaNotes, error)
|
||||
UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error)
|
||||
|
||||
+2090
-48
File diff suppressed because it is too large
Load Diff
@@ -919,13 +919,14 @@ SELECT
|
||||
mi.chapter_count,
|
||||
mi.entitlement_id,
|
||||
mi.revision_number,
|
||||
mi.file_size
|
||||
mi.file_size,
|
||||
mi.file_sha256
|
||||
FROM media_items mi
|
||||
JOIN library_visibility lv ON mi.library_id = lv.library_id
|
||||
WHERE lv.user_id = $1
|
||||
WHERE lv.user_id = $1
|
||||
AND lv.is_visible = true
|
||||
ORDER BY mi.title ASC
|
||||
LIMIT 1000;
|
||||
ORDER BY mi.title ASC
|
||||
LIMIT 1000;
|
||||
|
||||
-- name: CheckForProgressConflicts :one
|
||||
SELECT COUNT(*) as conflict_count
|
||||
@@ -1054,4 +1055,416 @@ RETURNING *;
|
||||
-- name: GetMediaItemByKoboContentId :one
|
||||
SELECT * FROM media_items WHERE kobo_content_id = $1;
|
||||
|
||||
-- Media Items Admin Operations
|
||||
-- Media Items Admin Operations
|
||||
|
||||
-- ============================================
|
||||
-- PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1)
|
||||
-- ============================================
|
||||
|
||||
-- Update media item with universal identifiers
|
||||
-- name: UpdateMediaItemIdentifiers :one
|
||||
UPDATE media_items
|
||||
SET
|
||||
file_sha256 = $2,
|
||||
opf_identifier = $3,
|
||||
opf_uuid = $4,
|
||||
hash_confidence = $5,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Get media item by SHA-256 hash
|
||||
-- name: GetMediaItemBySHA256 :one
|
||||
SELECT * FROM media_items WHERE file_sha256 = $1;
|
||||
|
||||
-- Get media item by OPF identifier
|
||||
-- name: GetMediaItemByOPFIdentifier :one
|
||||
SELECT * FROM media_items WHERE opf_identifier = $1;
|
||||
|
||||
-- Get media item by OPF UUID
|
||||
-- name: GetMediaItemByOPFUUID :one
|
||||
SELECT * FROM media_items WHERE opf_uuid = $1;
|
||||
|
||||
-- Query media items by multiple identifiers with confidence scoring
|
||||
-- name: QueryMediaItemsByIdentifiers :many
|
||||
SELECT
|
||||
mi.id,
|
||||
mi.file_sha256,
|
||||
mi.opf_identifier,
|
||||
mi.opf_uuid,
|
||||
mi.isbn,
|
||||
mi.asin,
|
||||
mi.title,
|
||||
mi.author,
|
||||
mi.file_size,
|
||||
mi.hash_confidence,
|
||||
CASE
|
||||
WHEN $1::uuid IS NOT NULL AND mi.id = $1::uuid THEN 1.0
|
||||
WHEN mi.opf_uuid IS NOT NULL AND mi.opf_uuid = $2::text THEN 0.95
|
||||
WHEN mi.file_sha256 IS NOT NULL AND mi.file_sha256 = $3::text THEN 0.9
|
||||
WHEN mi.opf_identifier IS NOT NULL AND mi.opf_identifier = $4::text THEN 0.85
|
||||
WHEN mi.isbn IS NOT NULL AND mi.isbn = $5::text THEN 0.8
|
||||
WHEN mi.asin IS NOT NULL AND mi.asin = $6::text THEN 0.8
|
||||
ELSE 0.5
|
||||
END as confidence_score
|
||||
FROM media_items mi
|
||||
WHERE
|
||||
($1::uuid IS NULL OR mi.id = $1::uuid)
|
||||
OR ($2::text IS NULL OR mi.opf_uuid = $2::text)
|
||||
OR ($3::text IS NULL OR mi.file_sha256 = $3::text)
|
||||
OR ($4::text IS NULL OR mi.opf_identifier = $4::text)
|
||||
OR ($5::text IS NULL OR mi.isbn = $5::text)
|
||||
OR ($6::text IS NULL OR mi.asin = $6::text)
|
||||
OR ($7::text IS NULL OR mi.title ILIKE '%' || $7::text || '%')
|
||||
ORDER BY confidence_score DESC;
|
||||
|
||||
-- MEDIA ITEM FORMATS QUERIES
|
||||
|
||||
-- Create media item format
|
||||
-- name: CreateMediaItemFormat :one
|
||||
INSERT INTO media_item_formats (media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, converted_from_format_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *;
|
||||
|
||||
-- Get media item formats
|
||||
-- name: GetMediaItemFormats :many
|
||||
SELECT * FROM media_item_formats WHERE media_item_id = $1;
|
||||
|
||||
-- Get media item format by type
|
||||
-- name: GetMediaItemFormatByType :one
|
||||
SELECT * FROM media_item_formats WHERE media_item_id = $1 AND format_type = $2;
|
||||
|
||||
-- Get media item format by SHA-256
|
||||
-- name: GetMediaItemFormatBySHA256 :one
|
||||
SELECT * FROM media_item_formats WHERE file_sha256 = $1;
|
||||
|
||||
-- Update media item format
|
||||
-- name: UpdateMediaItemFormat :one
|
||||
UPDATE media_item_formats
|
||||
SET
|
||||
file_path = $2,
|
||||
file_sha256 = $3,
|
||||
file_size_bytes = $4,
|
||||
mime_type = $5
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Delete media item format
|
||||
-- name: DeleteMediaItemFormat :exec
|
||||
DELETE FROM media_item_formats WHERE id = $1;
|
||||
|
||||
-- DEVICE FILE ALIASES QUERIES
|
||||
|
||||
-- Create device file alias
|
||||
-- name: CreateDeviceFileAlias :one
|
||||
INSERT INTO device_file_aliases (media_item_id, device_id, file_path, file_sha256, confidence_score)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *;
|
||||
|
||||
-- Get device file alias
|
||||
-- name: GetDeviceFileAlias :one
|
||||
SELECT * FROM device_file_aliases WHERE device_id = $1 AND file_path = $2;
|
||||
|
||||
-- Get device file aliases by device
|
||||
-- name: GetDeviceFileAliasesByDevice :many
|
||||
SELECT dfa.*, mi.title, mi.author
|
||||
FROM device_file_aliases dfa
|
||||
JOIN media_items mi ON dfa.media_item_id = mi.id
|
||||
WHERE dfa.device_id = $1
|
||||
ORDER BY dfa.last_seen_at DESC;
|
||||
|
||||
-- Get device file alias by SHA-256
|
||||
-- name: GetDeviceFileAliasBySHA256 :one
|
||||
SELECT dfa.*, mi.title, mi.author
|
||||
FROM device_file_aliases dfa
|
||||
JOIN media_items mi ON dfa.media_item_id = mi.id
|
||||
WHERE dfa.file_sha256 = $1;
|
||||
|
||||
-- Update device file alias
|
||||
-- name: UpdateDeviceFileAlias :one
|
||||
UPDATE device_file_aliases
|
||||
SET
|
||||
media_item_id = $2,
|
||||
file_sha256 = $3,
|
||||
confidence_score = $4,
|
||||
last_seen_at = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Delete device file alias
|
||||
-- name: DeleteDeviceFileAlias :exec
|
||||
DELETE FROM device_file_aliases WHERE id = $1;
|
||||
|
||||
-- COLLECTIONS QUERIES
|
||||
|
||||
-- Create collection
|
||||
-- name: CreateCollection :one
|
||||
INSERT INTO collections (user_id, name, description, color, icon, auto_assign_rules, view_settings)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *;
|
||||
|
||||
-- Get collection
|
||||
-- name: GetCollection :one
|
||||
SELECT * FROM collections WHERE id = $1;
|
||||
|
||||
-- Get collections by user
|
||||
-- name: GetCollectionsByUser :many
|
||||
SELECT * FROM collections WHERE user_id = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- Get collection with book count
|
||||
-- name: GetCollectionWithBookCount :one
|
||||
SELECT
|
||||
c.*,
|
||||
COUNT(ci.id) as book_count
|
||||
FROM collections c
|
||||
LEFT JOIN collection_items ci ON c.id = ci.collection_id
|
||||
WHERE c.id = $1
|
||||
GROUP BY c.id;
|
||||
|
||||
-- Update collection
|
||||
-- name: UpdateCollection :one
|
||||
UPDATE collections
|
||||
SET
|
||||
name = $2,
|
||||
description = $3,
|
||||
color = $4,
|
||||
icon = $5,
|
||||
auto_assign_rules = $6,
|
||||
view_settings = $7
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Delete collection
|
||||
-- name: DeleteCollection :exec
|
||||
DELETE FROM collections WHERE id = $1;
|
||||
|
||||
-- COLLECTION ITEMS QUERIES
|
||||
|
||||
-- Add book to collection
|
||||
-- name: AddBookToCollection :one
|
||||
INSERT INTO collection_items (collection_id, media_item_id, added_by_user_id)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (collection_id, media_item_id) DO NOTHING
|
||||
RETURNING *;
|
||||
|
||||
-- Remove book from collection
|
||||
-- name: RemoveBookFromCollection :exec
|
||||
DELETE FROM collection_items WHERE collection_id = $1 AND media_item_id = $2;
|
||||
|
||||
-- Get collection items
|
||||
-- name: GetCollectionItems :many
|
||||
SELECT ci.*, mi.title, mi.author, mi.cover_image_path
|
||||
FROM collection_items ci
|
||||
JOIN media_items mi ON ci.media_item_id = mi.id
|
||||
WHERE ci.collection_id = $1
|
||||
ORDER BY ci.added_at DESC;
|
||||
|
||||
-- Get collections for book
|
||||
-- name: GetCollectionsForBook :many
|
||||
SELECT c.*
|
||||
FROM collections c
|
||||
JOIN collection_items ci ON c.id = ci.collection_id
|
||||
WHERE ci.media_item_id = $1;
|
||||
|
||||
-- Check if book is in collection
|
||||
-- name: IsBookInCollection :one
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM collection_items
|
||||
WHERE collection_id = $1 AND media_item_id = $2
|
||||
) as in_collection;
|
||||
|
||||
-- DEVICE SHELF MAPPINGS QUERIES
|
||||
|
||||
-- Create device shelf mapping
|
||||
-- name: CreateDeviceShelfMapping :one
|
||||
INSERT INTO device_shelf_mappings (collection_id, device_id, device_shelf_name, sync_direction)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (collection_id, device_id)
|
||||
DO UPDATE SET
|
||||
device_shelf_name = EXCLUDED.device_shelf_name,
|
||||
sync_direction = EXCLUDED.sync_direction
|
||||
RETURNING *;
|
||||
|
||||
-- Get device shelf mappings
|
||||
-- name: GetDeviceShelfMappings :many
|
||||
SELECT dsm.*, c.name as collection_name, c.icon as collection_icon
|
||||
FROM device_shelf_mappings dsm
|
||||
JOIN collections c ON dsm.collection_id = c.id
|
||||
WHERE dsm.device_id = $1;
|
||||
|
||||
-- Get device shelf mapping
|
||||
-- name: GetDeviceShelfMapping :one
|
||||
SELECT * FROM device_shelf_mappings WHERE device_id = $1 AND collection_id = $2;
|
||||
|
||||
-- Update device shelf mapping
|
||||
-- name: UpdateDeviceShelfMapping :one
|
||||
UPDATE device_shelf_mappings
|
||||
SET
|
||||
device_shelf_name = $2,
|
||||
sync_direction = $3
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Delete device shelf mapping
|
||||
-- name: DeleteDeviceShelfMapping :exec
|
||||
DELETE FROM device_shelf_mappings WHERE id = $1;
|
||||
|
||||
-- DEVICE CATALOGS QUERIES
|
||||
|
||||
-- Create device catalog entry
|
||||
-- name: CreateDeviceCatalog :one
|
||||
INSERT INTO device_catalogs (device_id, media_item_id, bookmann_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (device_id, kobo_content_id)
|
||||
DO UPDATE SET
|
||||
available = EXCLUDED.available,
|
||||
delivery_date = COALESCE(EXCLUDED.delivery_date, device_catalogs.delivery_date),
|
||||
delivery_method = EXCLUDED.delivery_method
|
||||
RETURNING *;
|
||||
|
||||
-- Get device catalog by Bookmann UUID
|
||||
-- name: GetDeviceCatalogByBookmannUUID :one
|
||||
SELECT * FROM device_catalogs WHERE device_id = $1 AND bookmann_uuid = $2;
|
||||
|
||||
-- Get device catalog by Kobo ContentId
|
||||
-- name: GetDeviceCatalogByKoboContentId :one
|
||||
SELECT * FROM device_catalogs WHERE kobo_content_id = $1;
|
||||
|
||||
-- Get device catalog entries
|
||||
-- name: GetDeviceCatalogEntries :many
|
||||
SELECT dc.*, mi.title, mi.author
|
||||
FROM device_catalogs dc
|
||||
JOIN media_items mi ON dc.media_item_id = mi.id
|
||||
WHERE dc.device_id = $1 AND dc.available = true
|
||||
ORDER BY dc.delivery_date DESC;
|
||||
|
||||
-- Update device catalog availability
|
||||
-- name: UpdateDeviceCatalogAvailability :exec
|
||||
UPDATE device_catalogs
|
||||
SET available = $2
|
||||
WHERE id = $1;
|
||||
|
||||
-- Delete device catalog entry
|
||||
-- name: DeleteDeviceCatalog :exec
|
||||
DELETE FROM device_catalogs WHERE id = $1;
|
||||
|
||||
-- SYSTEM CONFIG QUERIES
|
||||
|
||||
-- Get system config
|
||||
-- name: GetSystemConfig :one
|
||||
SELECT * FROM system_config WHERE key = $1;
|
||||
|
||||
-- Get all system config
|
||||
-- name: GetAllSystemConfig :many
|
||||
SELECT * FROM system_config ORDER BY key;
|
||||
|
||||
-- Set system config
|
||||
-- name: SetSystemConfig :one
|
||||
INSERT INTO system_config (key, value, updated_by)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET
|
||||
value = EXCLUDED.value,
|
||||
updated_by = EXCLUDED.updated_by,
|
||||
updated_at = NOW()
|
||||
RETURNING *;
|
||||
|
||||
-- Delete system config
|
||||
-- name: DeleteSystemConfig :exec
|
||||
DELETE FROM system_config WHERE key = $1;
|
||||
|
||||
-- OPDS TOKENS QUERIES
|
||||
|
||||
-- Create OPDS token
|
||||
-- name: CreateOpdsToken :one
|
||||
INSERT INTO opds_tokens (device_id, token, token_type, expires_at)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *;
|
||||
|
||||
-- Get OPDS token
|
||||
-- name: GetOpdsToken :one
|
||||
SELECT ot.*, d.device_name, d.device_type
|
||||
FROM opds_tokens ot
|
||||
JOIN devices d ON ot.device_id = d.id
|
||||
WHERE ot.token = $1 AND ot.expires_at > NOW();
|
||||
|
||||
-- Get OPDS tokens by device
|
||||
-- name: GetOpdsTokensByDevice :many
|
||||
SELECT * FROM opds_tokens WHERE device_id = $1 AND expires_at > NOW();
|
||||
|
||||
-- Revoke OPDS token
|
||||
-- name: RevokeOpdsToken :exec
|
||||
DELETE FROM opds_tokens WHERE token = $1;
|
||||
|
||||
-- Cleanup expired OPDS tokens
|
||||
-- name: CleanupExpiredOpdsTokens :exec
|
||||
DELETE FROM opds_tokens WHERE expires_at < NOW();
|
||||
|
||||
-- Update Kobo shelves to support collections
|
||||
-- name: UpdateKoboShelfCollection :one
|
||||
UPDATE kobo_shelves
|
||||
SET
|
||||
collection_id = $2,
|
||||
position_in_collection = $3,
|
||||
last_synced_at = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Get Kobo shelves by collection
|
||||
-- name: GetKoboShelvesByCollection :many
|
||||
SELECT ks.*, mi.title, mi.author
|
||||
FROM kobo_shelves ks
|
||||
JOIN media_items mi ON ks.media_item_id = mi.id
|
||||
WHERE ks.device_id = $1 AND ks.collection_id = $2
|
||||
ORDER BY ks.position_in_collection ASC, ks.shelf_position ASC;
|
||||
|
||||
-- ============================================
|
||||
-- PHASE 6: ENHANCED KOBO SYNC (Week 3-4)
|
||||
-- ============================================
|
||||
|
||||
-- Create unlinked book entry
|
||||
-- name: CreateUnlinkedBook :one
|
||||
INSERT INTO unlinked_books (device_id, content_id, file_path, title, author, confidence_score)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *;
|
||||
|
||||
-- Get unlinked books for a device
|
||||
-- name: GetUnlinkedBooksByDevice :many
|
||||
SELECT ub.*, d.device_name, d.device_type
|
||||
FROM unlinked_books ub
|
||||
JOIN devices d ON ub.device_id = d.id
|
||||
WHERE ub.device_id = $1 AND ub.resolved = false
|
||||
ORDER BY ub.last_seen_at DESC;
|
||||
|
||||
-- Get unlinked book by ContentId
|
||||
-- name: GetUnlinkedBookByContentId :one
|
||||
SELECT * FROM unlinked_books WHERE device_id = $1 AND content_id = $2;
|
||||
|
||||
-- Resolve unlinked book
|
||||
-- name: ResolveUnlinkedBook :one
|
||||
UPDATE unlinked_books
|
||||
SET
|
||||
resolved = true,
|
||||
media_item_id = $2,
|
||||
resolved_at = NOW(),
|
||||
resolution_method = $3
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Count unlinked books for a device
|
||||
-- name: CountUnlinkedBooks :one
|
||||
SELECT COUNT(*) as count
|
||||
FROM unlinked_books
|
||||
WHERE device_id = $1 AND resolved = false;
|
||||
|
||||
-- Link unlinked book to media item
|
||||
-- name: LinkUnlinkedBook :one
|
||||
UPDATE unlinked_books
|
||||
SET
|
||||
media_item_id = $2,
|
||||
confidence_score = $3,
|
||||
resolved = true,
|
||||
resolved_at = NOW(),
|
||||
resolution_method = 'manual_link'
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
Reference in New Issue
Block a user