From 2706ae52c1040d1ec586bf8248cf7b4d460208f3 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Fri, 13 Feb 2026 21:50:29 -0500 Subject: [PATCH] refactor: remove Phase X terminology from source code comments Remove planning document phase references from code comments: app_test.go: - Remove Phase 5 references from 8 test function comments querier.go & queries.sql.go: - Remove Phase 1, 2, 3, 4, 6 references from section headers - Clean up week numbers (Weeks 5-6, Week 3-4, etc.) queries.sql: - Remove Phase 4 references from Kobo queries kobo.go: - Remove Phase 6 references from ContentId mapping comments progress.go: - Remove Phase 1 reference from route comment media_scanner.go & media_scanner_library_type_test.go: - Remove Phase 2 references from library type scanning comments schema.sql: - Remove Phase 1, 2, 3, 4, 5, 7 references from table/section comments - Clean up: Format Detection, Progress Tracking, Device Registry, Sync Queue, Conflict Resolution, Reading History, Indexes, etc. test_helpers.go: - Remove Phase 6 reference from handler setup comment These phase numbers were from internal planning documents and have no meaning in the codebase. Removing them makes the code self-documenting. --- database/schema/schema.sql | 46 +++++++++---------- internal/app/app_test.go | 24 +++++----- internal/database/querier.go | 14 +++--- internal/database/queries.sql.go | 26 +++++------ internal/database/queries/queries.sql | 28 +++++------ internal/handlers/kobo.go | 16 +++---- internal/router/progress.go | 2 +- internal/services/media_scanner.go | 28 +++++------ .../media_scanner_library_type_test.go | 8 ++-- 9 files changed, 96 insertions(+), 96 deletions(-) diff --git a/database/schema/schema.sql b/database/schema/schema.sql index 8226491..0be1978 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -119,14 +119,14 @@ CREATE TABLE IF NOT EXISTS media_items ( added_by_admin_id UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - -- Universal Sync Format Detection (Phase 1) + -- Universal Sync Format Detection format_group VARCHAR(20) NOT NULL DEFAULT 'reflowable', format_mimetype VARCHAR(100), is_reflowable BOOLEAN DEFAULT TRUE, has_fixed_layout BOOLEAN DEFAULT FALSE, total_characters BIGINT, chapter_count INTEGER, - -- Kobo-Specific Metadata (Phase 4) + -- Kobo-Specific Metadata entitlement_id VARCHAR(255) UNIQUE, revision_number INTEGER DEFAULT 1, kobo_content_id VARCHAR(255), @@ -154,7 +154,7 @@ CREATE TABLE IF NOT EXISTS reading_progress ( total_pages INTEGER, last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(media_item_id, user_id), - -- Universal Progress Tracking (Phase 1) + -- Universal Progress Tracking percentage FLOAT CHECK (percentage >= 0 AND percentage <= 1), character_offset BIGINT, epubcfi TEXT, @@ -167,7 +167,7 @@ CREATE TABLE IF NOT EXISTS reading_progress ( scroll_position_y FLOAT DEFAULT 0, panel_number INTEGER, reading_mode VARCHAR(20), - -- Device Sync Metadata (Phase 1) + -- Device Sync Metadata last_sync_device VARCHAR(50), last_sync_source VARCHAR(20), last_sync_timestamp TIMESTAMP WITH TIME ZONE, @@ -195,7 +195,7 @@ CREATE TABLE IF NOT EXISTS media_notes ( position VARCHAR(100), -- optional position (page:offset or CFI) for standalone notes created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - -- Location Enhancements (Phase 1) + -- Location Enhancements percentage_location FLOAT, character_start INTEGER, character_end INTEGER, @@ -217,7 +217,7 @@ CREATE TABLE IF NOT EXISTS media_highlights ( note_id UUID REFERENCES media_notes(id) ON DELETE SET NULL, -- optional associated note created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - -- Location Enhancements (Phase 1) + -- Location Enhancements percentage_start FLOAT, percentage_end FLOAT, character_start INTEGER, @@ -232,7 +232,7 @@ CREATE TABLE IF NOT EXISTS media_highlights ( ); -- ============================================ --- DEVICE REGISTRY (Phase 1) +-- DEVICE REGISTRY -- ============================================ CREATE TABLE IF NOT EXISTS devices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -252,7 +252,7 @@ CREATE TABLE IF NOT EXISTS devices ( ); -- ============================================ --- SYNC QUEUE FOR OFFLINE SUPPORT (Phase 1) +-- SYNC QUEUE FOR OFFLINE SUPPORT -- ============================================ CREATE TABLE IF NOT EXISTS sync_queue ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -270,7 +270,7 @@ CREATE TABLE IF NOT EXISTS sync_queue ( ); -- ============================================ --- CONFLICT RESOLUTION (Phase 1) +-- CONFLICT RESOLUTION -- ============================================ CREATE TABLE IF NOT EXISTS sync_conflicts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -286,7 +286,7 @@ CREATE TABLE IF NOT EXISTS sync_conflicts ( ); -- ============================================ --- KOBO SHELF MANAGEMENT (Phase 4) +-- KOBO SHELF MANAGEMENT -- ============================================ CREATE TABLE IF NOT EXISTS kobo_shelves ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -304,7 +304,7 @@ CREATE INDEX IF NOT EXISTS idx_kobo_shelves_media_item_id ON kobo_shelves(media_ CREATE INDEX IF NOT EXISTS idx_kobo_shelves_shelf_name ON kobo_shelves(shelf_name); -- ============================================ --- KOBO ENTITLEMENTS TRACKING (Phase 4) +-- KOBO ENTITLEMENTS TRACKING -- ============================================ CREATE TABLE IF NOT EXISTS kobo_entitlements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -329,7 +329,7 @@ CREATE INDEX IF NOT EXISTS idx_kobo_entitlements_entitlement_id ON kobo_entitlem CREATE INDEX IF NOT EXISTS idx_kobo_entitlements_content_id ON kobo_entitlements(content_id); -- ============================================ --- READING HISTORY (Phase 1) +-- READING HISTORY -- ============================================ CREATE TABLE IF NOT EXISTS reading_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), @@ -386,7 +386,7 @@ CREATE INDEX IF NOT EXISTS idx_media_highlights_user_id ON media_highlights(user CREATE INDEX IF NOT EXISTS idx_media_highlights_note_id ON media_highlights(note_id); -- ============================================ --- NEW TABLE INDEXES (Phase 1) +-- NEW TABLE INDEXES -- ============================================ -- Device registry indexes @@ -404,22 +404,22 @@ CREATE INDEX IF NOT EXISTS idx_sync_conflicts_media_item_id ON sync_conflicts(me 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) +-- Composite indexes for sync queue performance 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) +-- Composite indexes for reading progress performance 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) +-- Composite indexes for devices 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) +-- Composite indexes for annotations 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); @@ -430,7 +430,7 @@ CREATE INDEX IF NOT EXISTS idx_reading_history_created_at ON reading_history(cre 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) +-- TRIGGER FUNCTIONS -- ============================================ -- Update updated_at timestamp for devices @@ -476,7 +476,7 @@ COMMENT ON COLUMN media_items.google_books_id IS 'Google Books identifier for in -- - Highlights can have associated notes for detailed annotations -- ============================================ --- PERFORMANCE OPTIMIZATION (Phase 7) +-- PERFORMANCE OPTIMIZATION -- ============================================ -- NOTE: ALTER SYSTEM commands are commented out for sqlc compatibility. -- These should be run manually by DBA or via init script: @@ -513,7 +513,7 @@ COMMENT ON COLUMN media_items.google_books_id IS 'Google Books identifier for in -- REINDEX TABLE CONCURRENTLY reading_progress; -- ============================================ --- SYNC HELPER FUNCTIONS (Phase 1) +-- SYNC HELPER FUNCTIONS -- ============================================ -- Detect format group based on mimetype and file path @@ -680,7 +680,7 @@ END; $$ LANGUAGE plpgsql; -- ============================================ --- KOREADER SYNC FUNCTIONS (Phase 3) +-- KOREADER SYNC FUNCTIONS -- ============================================ -- Bulk update progress from KOReader sync data @@ -799,7 +799,7 @@ END; $$ LANGUAGE plpgsql; -- ============================================ --- PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1) +--: UNIVERSAL BOOK IDENTIFIERS -- ============================================ -- Add universal identifier columns to media_items table @@ -937,7 +937,7 @@ ALTER TABLE kobo_shelves ADD COLUMN IF NOT EXISTS collection_id UUID REFERENCES ALTER TABLE kobo_shelves ADD COLUMN IF NOT EXISTS position_in_collection INTEGER; -- ============================================ --- PHASE 6: UNLINKED BOOKS TRACKING (Week 3-4) +--: UNLINKED BOOKS TRACKING -- ============================================ -- Create unlinked_books table to track books that couldn't be auto-matched diff --git a/internal/app/app_test.go b/internal/app/app_test.go index c7e5565..c508bc9 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -48,7 +48,7 @@ func (m *mockHandler) reset() { m.stopSchedulerCalled = false } -// TestApp_New tests Phase 5: App constructor +// TestApp_New tests App constructor func TestApp_New(t *testing.T) { e := echo.New() handler := &mockHandler{} @@ -62,7 +62,7 @@ func TestApp_New(t *testing.T) { assert.NotNil(t, app.shutdownDone, "Shutdown done channel should be initialized") } -// TestApp_SetShutdownTimeout tests Phase 5: configurable shutdown timeout +// TestApp_SetShutdownTimeout tests configurable shutdown timeout func TestApp_SetShutdownTimeout(t *testing.T) { e := echo.New() handler := &mockHandler{} @@ -74,7 +74,7 @@ func TestApp_SetShutdownTimeout(t *testing.T) { assert.Equal(t, customTimeout, app.shutdownTimeout, "Shutdown timeout should be updated") } -// TestApp_ShutdownDone tests Phase 5: shutdown done channel +// TestApp_ShutdownDone tests shutdown done channel func TestApp_ShutdownDone(t *testing.T) { e := echo.New() handler := &mockHandler{} @@ -92,7 +92,7 @@ func TestApp_ShutdownDone(t *testing.T) { } } -// TestApp_Start_BackgroundServices tests Phase 1 & 5: background service startup +// TestApp_Start_BackgroundServices tests background service startup func TestApp_Start_BackgroundServices(t *testing.T) { e := echo.New() handler := &mockHandler{ @@ -135,7 +135,7 @@ func TestApp_Start_BackgroundServices(t *testing.T) { <-done2 } -// TestApp_Shutdown_GracefulShutdown tests Phase 5: graceful shutdown sequence +// TestApp_Shutdown_GracefulShutdown tests graceful shutdown sequence func TestApp_Shutdown_GracefulShutdown(t *testing.T) { e := echo.New() handler := &mockHandler{ @@ -150,7 +150,7 @@ func TestApp_Shutdown_GracefulShutdown(t *testing.T) { assert.True(t, handler.stopSchedulerCalled, "StopScheduler should be called") } -// TestApp_Shutdown_ThreadSafety tests Phase 5: thread-safe shutdown +// TestApp_Shutdown_ThreadSafety tests thread-safe shutdown func TestApp_Shutdown_ThreadSafety(t *testing.T) { e := echo.New() handler := &mockHandler{} @@ -175,7 +175,7 @@ func TestApp_Shutdown_ThreadSafety(t *testing.T) { assert.True(t, handler.stopSchedulerCalled, "StopScheduler should be called at least once") } -// TestApp_Shutdown_Timeout tests Phase 5: shutdown timeout handling +// TestApp_Shutdown_Timeout tests shutdown timeout handling func TestApp_Shutdown_Timeout(t *testing.T) { e := echo.New() handler := &mockHandler{ @@ -191,7 +191,7 @@ func TestApp_Shutdown_Timeout(t *testing.T) { assert.True(t, handler.stopSchedulerCalled, "StopScheduler should still be called") } -// TestApp_Shutdown_ClosesEchoServer tests Phase 5: HTTP server shutdown +// TestApp_Shutdown_ClosesEchoServer tests HTTP server shutdown func TestApp_Shutdown_ClosesEchoServer(t *testing.T) { e := echo.New() handler := &mockHandler{} @@ -213,7 +213,7 @@ func TestApp_Shutdown_ClosesEchoServer(t *testing.T) { assert.True(t, handler.stopSchedulerCalled, "StopScheduler should be called") } -// TestApp_SignalHandling tests Phase 5: signal handling (SIGINT, SIGTERM, SIGQUIT) +// TestApp_SignalHandling tests signal handling (SIGINT, SIGTERM, SIGQUIT) func TestApp_SignalHandling(t *testing.T) { tests := []struct { name string @@ -259,7 +259,7 @@ func TestApp_SignalHandling(t *testing.T) { } } -// TestApp_Integration_StartupSequence tests Phase 1 & 5: complete startup sequence +// TestApp_Integration_StartupSequence tests complete startup sequence func TestApp_Integration_StartupSequence(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") @@ -320,8 +320,8 @@ func TestApp_HandlerInterface(t *testing.T) { var _ Handler = &mockHandler{} } -// TestPhase1_AutoStartVerification tests Phase 1: auto-start functionality -func TestPhase1_AutoStartVerification(t *testing.T) { +// TestApp_AutoStartVerification tests auto-start functionality +func TestApp_AutoStartVerification(t *testing.T) { t.Run("Auto-start runs asynchronously", func(t *testing.T) { e := echo.New() handler := &mockHandler{ diff --git a/internal/database/querier.go b/internal/database/querier.go index dd39b7c..3fb6773 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -15,7 +15,7 @@ type Querier interface { // Add book to collection AddBookToCollection(ctx context.Context, arg AddBookToCollectionParams) (CollectionItems, error) // ============================================ - // KOBO SHELF MANAGEMENT QUERIES (Phase 4) + // KOBO SHELF MANAGEMENT QUERIES // ============================================ AddBookToKoboShelf(ctx context.Context, arg AddBookToKoboShelfParams) (KoboShelves, error) // Library Folders queries @@ -36,7 +36,7 @@ type Querier interface { // Create collection CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collections, error) // ============================================ - // PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6) + // DEVICE MANAGEMENT & AUTH // ============================================ // Device Registration & Management CreateDevice(ctx context.Context, arg CreateDeviceParams) (Devices, error) @@ -65,7 +65,7 @@ type Querier interface { // Create OPDS token CreateOpdsToken(ctx context.Context, arg CreateOpdsTokenParams) (OpdsTokens, error) // ============================================ - // KOBO ENTITLEMENT QUERIES (Phase 4) + // KOBO ENTITLEMENT QUERIES // ============================================ CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) // Create reading history entry @@ -78,7 +78,7 @@ type Querier interface { // Sync Queue Management CreateSyncQueueItem(ctx context.Context, arg CreateSyncQueueItemParams) (SyncQueue, error) // ============================================ - // PHASE 6: ENHANCED KOBO SYNC (Week 3-4) + // ENHANCED KOBO SYNC // ============================================ // Create unlinked book entry CreateUnlinkedBook(ctx context.Context, arg CreateUnlinkedBookParams) (UnlinkedBooks, error) @@ -166,7 +166,7 @@ type Querier interface { GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) GetMediaItemByFilePath(ctx context.Context, filePath string) (MediaItems, error) // ============================================ - // PHASE 3: KOREADER SYNC PROTOCOL (Weeks 7-9) + // KOREADER SYNC PROTOCOL // ============================================ GetMediaItemByFilePathForSync(ctx context.Context, filePath string) (MediaItems, error) GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) @@ -293,13 +293,13 @@ type Querier interface { // Update media item format UpdateMediaItemFormat(ctx context.Context, arg UpdateMediaItemFormatParams) (MediaItemFormats, error) // ============================================ - // PHASE 1: FORMAT DETECTION & PROGRESS (Week 2) + // FORMAT DETECTION & PROGRESS // ============================================ // Update media item format group information UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMediaItemFormatGroupParams) error // Media Items Admin Operations // ============================================ - // PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1) + // UNIVERSAL BOOK IDENTIFIERS // ============================================ // Update media item with universal identifiers UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMediaItemIdentifiersParams) (MediaItems, error) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 3dc63e3..50bc983 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -60,7 +60,7 @@ type AddBookToKoboShelfParams struct { } // ============================================ -// KOBO SHELF MANAGEMENT QUERIES (Phase 4) +// KOBO SHELF MANAGEMENT QUERIES // ============================================ func (q *Queries) AddBookToKoboShelf(ctx context.Context, arg AddBookToKoboShelfParams) (KoboShelves, error) { row := q.db.QueryRow(ctx, AddBookToKoboShelf, @@ -292,7 +292,7 @@ type CreateDeviceParams struct { } // ============================================ -// PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6) +// DEVICE MANAGEMENT & AUTH // ============================================ // Device Registration & Management func (q *Queries) CreateDevice(ctx context.Context, arg CreateDeviceParams) (Devices, error) { @@ -837,7 +837,7 @@ type CreateOrUpdateKoboEntitlementParams struct { } // ============================================ -// KOBO ENTITLEMENT QUERIES (Phase 4) +// KOBO ENTITLEMENT QUERIES // ============================================ func (q *Queries) CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) { row := q.db.QueryRow(ctx, CreateOrUpdateKoboEntitlement, @@ -1084,7 +1084,7 @@ type CreateUnlinkedBookParams struct { } // ============================================ -// PHASE 6: ENHANCED KOBO SYNC (Week 3-4) +// ENHANCED KOBO SYNC // ============================================ // Create unlinked book entry func (q *Queries) CreateUnlinkedBook(ctx context.Context, arg CreateUnlinkedBookParams) (UnlinkedBooks, error) { @@ -2902,7 +2902,7 @@ SELECT id, library_id, title, author, isbn, description, file_path, file_size, m ` // ============================================ -// PHASE 3: KOREADER SYNC PROTOCOL (Weeks 7-9) +// KOREADER SYNC PROTOCOL // ============================================ func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath string) (MediaItems, error) { row := q.db.QueryRow(ctx, GetMediaItemByFilePathForSync, filePath) @@ -3407,8 +3407,8 @@ func (q *Queries) GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) } const GetNextRetryTime = `-- name: GetNextRetryTime :one -SELECT - CASE +SELECT + CASE WHEN attempts = 0 THEN NOW() WHEN attempts = 1 THEN NOW() + INTERVAL '1 minute' WHEN attempts = 2 THEN NOW() + INTERVAL '5 minutes' @@ -4053,7 +4053,7 @@ func (q *Queries) GetUnlinkedBooksByDevice(ctx context.Context, deviceID pgtype. } const GetUser = `-- name: GetUser :one -SELECT +SELECT u.id, u.email, u.username, @@ -4602,7 +4602,7 @@ const IncrementSyncQueueAttempts = `-- name: IncrementSyncQueueAttempts :one UPDATE sync_queue SET attempts = attempts + 1, - status = CASE + status = CASE WHEN attempts + 1 >= max_attempts THEN 'failed' ELSE 'pending' END, @@ -4716,7 +4716,7 @@ func (q *Queries) LinkUnlinkedBook(ctx context.Context, arg LinkUnlinkedBookPara } const ListAllSyncQueueItems = `-- name: ListAllSyncQueueItems :many -SELECT +SELECT sq.id, sq.device_id, sq.media_item_id, sq.sync_type, sq.sync_data, sq.priority, sq.attempts, sq.max_attempts, sq.status, sq.error_message, sq.created_at, sq.processed_at, d.device_name, d.device_type, @@ -5776,7 +5776,7 @@ func (q *Queries) ListUnresolvedUnlinkedBooks(ctx context.Context, arg ListUnres } const ListUsers = `-- name: ListUsers :many -SELECT +SELECT u.id, u.email, u.username, @@ -7175,7 +7175,7 @@ type UpdateMediaItemFormatGroupParams struct { } // ============================================ -// PHASE 1: FORMAT DETECTION & PROGRESS (Week 2) +// FORMAT DETECTION & PROGRESS // ============================================ // Update media item format group information func (q *Queries) UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMediaItemFormatGroupParams) error { @@ -7215,7 +7215,7 @@ type UpdateMediaItemIdentifiersParams struct { // Media Items Admin Operations // ============================================ -// PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1) +// UNIVERSAL BOOK IDENTIFIERS // ============================================ // Update media item with universal identifiers func (q *Queries) UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMediaItemIdentifiersParams) (MediaItems, error) { diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index d3a295b..f1cdb76 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -16,7 +16,7 @@ SELECT id, email, username, theme, first_name, last_name, role, created_at, upda SELECT id, email, username, password_hash, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1 OR username = $1; -- name: GetUser :one -SELECT +SELECT u.id, u.email, u.username, @@ -35,7 +35,7 @@ WHERE u.id = $1; SELECT password_hash FROM users WHERE id = $1; -- name: ListUsers :many -SELECT +SELECT u.id, u.email, u.username, @@ -520,7 +520,7 @@ UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at I DELETE FROM refresh_tokens WHERE expires_at < NOW() OR (revoked_at IS NOT NULL AND revoked_at < NOW() - INTERVAL '7 days'); -- ============================================ --- PHASE 1: FORMAT DETECTION & PROGRESS (Week 2) +-- FORMAT DETECTION & PROGRESS -- ============================================ -- Update media item format group information @@ -716,7 +716,7 @@ SELECT LIMIT $2; -- ============================================ --- PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6) +-- DEVICE MANAGEMENT & AUTH -- ============================================ -- Device Registration & Management @@ -837,7 +837,7 @@ ORDER BY priority ASC, created_at ASC; UPDATE sync_queue SET attempts = attempts + 1, - status = CASE + status = CASE WHEN attempts + 1 >= max_attempts THEN 'failed' ELSE 'pending' END, @@ -856,7 +856,7 @@ FROM sync_queue WHERE device_id = $1; -- name: ListAllSyncQueueItems :many -SELECT +SELECT sq.*, d.device_name, d.device_type, @@ -870,8 +870,8 @@ ORDER BY sq.priority ASC, sq.created_at DESC LIMIT $1 OFFSET $2; -- name: GetNextRetryTime :one -SELECT - CASE +SELECT + CASE WHEN attempts = 0 THEN NOW() WHEN attempts = 1 THEN NOW() + INTERVAL '1 minute' WHEN attempts = 2 THEN NOW() + INTERVAL '5 minutes' @@ -929,7 +929,7 @@ DELETE FROM sync_conflicts WHERE id = $1; ORDER BY sc.created_at DESC; -- ============================================ --- PHASE 3: KOREADER SYNC PROTOCOL (Weeks 7-9) +-- KOREADER SYNC PROTOCOL -- ============================================ -- name: GetMediaItemByFilePathForSync :one @@ -1040,7 +1040,7 @@ WHERE media_item_id = $1 AND last_sync_source != $3; -- ============================================ --- KOBO SHELF MANAGEMENT QUERIES (Phase 4) +-- KOBO SHELF MANAGEMENT QUERIES -- ============================================ -- name: AddBookToKoboShelf :one @@ -1093,7 +1093,7 @@ FROM kobo_shelves WHERE device_id = $1; -- ============================================ --- KOBO ENTITLEMENT QUERIES (Phase 4) +-- KOBO ENTITLEMENT QUERIES -- ============================================ -- name: CreateOrUpdateKoboEntitlement :one @@ -1161,7 +1161,7 @@ SELECT * FROM media_items WHERE kobo_content_id = $1; -- Media Items Admin Operations -- ============================================ --- PHASE 1: UNIVERSAL BOOK IDENTIFIERS (Week 1) +-- UNIVERSAL BOOK IDENTIFIERS -- ============================================ -- Update media item with universal identifiers @@ -1522,7 +1522,7 @@ 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) +-- ENHANCED KOBO SYNC -- ============================================ -- Create unlinked book entry @@ -1587,4 +1587,4 @@ FROM unlinked_books ub JOIN devices d ON ub.device_id = d.id WHERE ub.resolved = false ORDER BY ub.last_seen_at DESC -LIMIT $1 OFFSET $2; \ No newline at end of file +LIMIT $1 OFFSET $2; diff --git a/internal/handlers/kobo.go b/internal/handlers/kobo.go index 3872e0a..a617a1e 100644 --- a/internal/handlers/kobo.go +++ b/internal/handlers/kobo.go @@ -27,7 +27,7 @@ func NewKoboHandler(db *database.Queries, connManager *wsync.ConnectionManager) } // mapContentIdToBookhoardUUID maps Kobo ContentId to Bookhoard UUID with multiple fallback strategies -// Phase 6: Enhanced Kobo Sync - ContentId Mapping Logic +// Enhanced Kobo Sync - ContentId Mapping Logic func (h *KoboHandler) mapContentIdToBookhoardUUID(ctx echo.Context, contentId string, deviceID uuid.UUID) (uuid.UUID, error, string) { // Step 1: Try direct ContentId lookup in device_catalogs table catalog, err := h.db.GetDeviceCatalogByKoboContentId(ctx.Request().Context(), contentId) @@ -320,7 +320,7 @@ func (h *KoboHandler) Initialization(c echo.Context) error { author = item.Author.String } - // Phase 6: Use ContentId mapping instead of direct UUID + // Use ContentId mapping instead of direct UUID koboContentId, err := h.mapBookhoardUUIDToKoboContentId(c, bookhoardUUID, deviceUUID) if err != nil { // Fallback to entitlement_id or generate new one @@ -351,7 +351,7 @@ func (h *KoboHandler) Initialization(c echo.Context) error { contentType = "5" } - // Phase 6: Get collection metadata for this book + // Get collection metadata for this book collections, _ := h.getCollectionMetadataForBook(c, bookhoardUUID, deviceUUID) librarySync = append(librarySync, KoboLibraryBook{ @@ -403,7 +403,7 @@ func (h *KoboHandler) Markup(c echo.Context) error { unlinkedBooks := 0 for _, readingSync := range req.ReadingSync { - // Phase 6: Use ContentId mapping with fallback logic + // Use ContentId mapping with fallback logic bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, readingSync.ContentId, deviceUUID) if err != nil || bookhoardUUID == uuid.Nil { // Unlinked book detected @@ -440,7 +440,7 @@ func (h *KoboHandler) Markup(c echo.Context) error { } for _, bookmarkSync := range req.BookmarkSync { - // Phase 6: Use ContentId mapping with fallback logic + // Use ContentId mapping with fallback logic bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, bookmarkSync.ContentId, deviceUUID) if err != nil || bookhoardUUID == uuid.Nil { // Unlinked book - skip @@ -541,7 +541,7 @@ func (h *KoboHandler) Bookmark(c echo.Context) error { bookmarksSynced := 0 for _, bookmarkSync := range req.BookmarkSync { - // Phase 6: Use ContentId mapping with fallback logic + // Use ContentId mapping with fallback logic bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, bookmarkSync.ContentId, deviceUUID) if err != nil || bookhoardUUID == uuid.Nil { // Unlinked book - skip @@ -606,7 +606,7 @@ func (h *KoboHandler) AnalyticsGettests(c echo.Context) error { } for _, test := range req { - // Phase 6: Use ContentId mapping with fallback logic + // Use ContentId mapping with fallback logic bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, test.ContentId, deviceUUID) if err != nil || bookhoardUUID == uuid.Nil { // Unlinked book - skip @@ -683,7 +683,7 @@ func (h *KoboHandler) SyncFromServer(c echo.Context) error { highlightsSent := 0 for _, syncData := range req { - // Phase 6: Use ContentId mapping with fallback logic + // Use ContentId mapping with fallback logic bookhoardUUID, err, _ := h.mapContentIdToBookhoardUUID(c, syncData.ContentId, deviceUUID) if err != nil || bookhoardUUID == uuid.Nil { // Unlinked book - skip diff --git a/internal/router/progress.go b/internal/router/progress.go index c3d4f4d..f36f4e6 100644 --- a/internal/router/progress.go +++ b/internal/router/progress.go @@ -11,7 +11,7 @@ func registerProgressRoutes(cfg *Config, scannerHandler *handlers.Handler) { jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware) - // Universal Progress routes (Phase 1) + // Universal Progress routes protected.GET("/progress/:id", scannerHandler.GetUniversalProgress) protected.POST("/progress/:id", scannerHandler.UpdateUniversalProgress) protected.GET("/progress/:id/history", scannerHandler.GetProgressHistory) diff --git a/internal/services/media_scanner.go b/internal/services/media_scanner.go index 61cdd6a..b96e4b4 100644 --- a/internal/services/media_scanner.go +++ b/internal/services/media_scanner.go @@ -48,8 +48,8 @@ type MediaMetadata struct { ASIN string Tags []string - Phase1HashInfo *HashInfo - Phase1FormatFormats []*FormatInfo + FileHashInfo *HashInfo + FileFormats []*FormatInfo } type HashInfo struct { @@ -338,15 +338,15 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) error metadata = &MediaMetadata{} } - // Extract hash information (Phase 2) + // Extract hash information during metadata extraction hashInfo, formatInfo, err := s.extractHashInfo(path) if err != nil { fmt.Printf("Warning: failed to extract hash info from %s: %v\n", path, err) hashInfo = &HashInfo{} formatInfo = &FormatInfo{} } else { - metadata.Phase1HashInfo = hashInfo - metadata.Phase1FormatFormats = []*FormatInfo{formatInfo} + metadata.FileHashInfo = hashInfo + metadata.FileFormats = []*FormatInfo{formatInfo} fmt.Printf("Hash info for %s: SHA256=%s, OPF_ID=%s, OPF_UUID=%s, Confidence=%s\n", path, hashInfo.FileSHA256, hashInfo.OPFIdentifier, hashInfo.OPFUUID, hashInfo.HashConfidence) } @@ -479,22 +479,22 @@ func (s *MediaScanner) processMediaFile(ctx context.Context, path string) error return fmt.Errorf("failed to create media item: %v", err) } - // Update hash information (Phase 2) - if metadata.Phase1HashInfo != nil && metadata.Phase1HashInfo.FileSHA256 != "" { + // Update hash information before database storage + if metadata.FileHashInfo != nil && metadata.FileHashInfo.FileSHA256 != "" { _, err = s.db.UpdateMediaItemIdentifiers(ctx, database.UpdateMediaItemIdentifiersParams{ ID: createdItem.ID, - FileSha256: pgtype.Text{String: metadata.Phase1HashInfo.FileSHA256, Valid: true}, - OpfIdentifier: pgtype.Text{String: metadata.Phase1HashInfo.OPFIdentifier, Valid: metadata.Phase1HashInfo.OPFIdentifier != ""}, - OpfUuid: pgtype.Text{String: metadata.Phase1HashInfo.OPFUUID, Valid: metadata.Phase1HashInfo.OPFUUID != ""}, - HashConfidence: pgtype.Text{String: metadata.Phase1HashInfo.HashConfidence, Valid: true}, + FileSha256: pgtype.Text{String: metadata.FileHashInfo.FileSHA256, Valid: true}, + OpfIdentifier: pgtype.Text{String: metadata.FileHashInfo.OPFIdentifier, Valid: metadata.FileHashInfo.OPFIdentifier != ""}, + OpfUuid: pgtype.Text{String: metadata.FileHashInfo.OPFUUID, Valid: metadata.FileHashInfo.OPFUUID != ""}, + HashConfidence: pgtype.Text{String: metadata.FileHashInfo.HashConfidence, Valid: true}, }) if err != nil { fmt.Printf("Warning: failed to update hash identifiers for %s: %v\n", path, err) } } - // Store format information (Phase 2) - for _, format := range metadata.Phase1FormatFormats { + // Store format information in the database + for _, format := range metadata.FileFormats { _, err = s.db.CreateMediaItemFormat(ctx, database.CreateMediaItemFormatParams{ MediaItemID: createdItem.ID, FormatType: format.FormatType, @@ -1021,7 +1021,7 @@ func (s *MediaScanner) Close() error { } // ============================================ -// PHASE 2: SCANNER ENHANCEMENTS (Week 1-2) +// SCANNER ENHANCEMENTS // ============================================ // calculateFileSHA256 calculates SHA-256 hash using streaming to avoid loading entire file into memory diff --git a/internal/services/media_scanner_library_type_test.go b/internal/services/media_scanner_library_type_test.go index d586ee1..23f918e 100644 --- a/internal/services/media_scanner_library_type_test.go +++ b/internal/services/media_scanner_library_type_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestMediaScanner_LibraryTypeAwareScanning tests Phase 2: library-type-aware scanning +// TestMediaScanner_LibraryTypeAwareScanning tests library-type-aware scanning func TestMediaScanner_LibraryTypeAwareScanning(t *testing.T) { t.Run("isScannableFile checks library type restrictions", func(t *testing.T) { scanner := &MediaScanner{ @@ -121,7 +121,7 @@ func TestMediaScanner_LibraryTypeAwareScanning(t *testing.T) { }) } -// TestMediaScanner_SetFolders_BuildsLibraryTypeCache tests Phase 2: SetFolders builds cache +// TestMediaScanner_SetFolders_BuildsLibraryTypeCache tests SetFolders builds cache func TestMediaScanner_SetFolders_BuildsLibraryTypeCache(t *testing.T) { // This is a unit test that verifies SetFolders properly initializes the libraryTypes cache // Full integration testing would require a mock database @@ -150,7 +150,7 @@ func TestMediaScanner_SetFolders_BuildsLibraryTypeCache(t *testing.T) { }) } -// TestMediaScanner_LibraryTypeCrossContamination tests Phase 2: prevents cross-contamination +// TestMediaScanner_LibraryTypeCrossContamination tests prevents cross-contamination func TestMediaScanner_LibraryTypeCrossContamination(t *testing.T) { t.Run("Ebook library rejects comic formats", func(t *testing.T) { scanner := &MediaScanner{ @@ -185,7 +185,7 @@ func TestMediaScanner_LibraryTypeCrossContamination(t *testing.T) { }) } -// TestMediaScanner_MultipleLibraryTypes tests Phase 2: multiple libraries with different types +// TestMediaScanner_MultipleLibraryTypes tests multiple libraries with different types func TestMediaScanner_MultipleLibraryTypes(t *testing.T) { scanner := &MediaScanner{ folders: []string{