diff --git a/database/schema/schema.sql b/database/schema/schema.sql index da93ed9..332b363 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -596,4 +596,123 @@ BEGIN 'merge_timestamp', NOW() ); END; -$$ LANGUAGE plpgsql; \ No newline at end of file +$$ LANGUAGE plpgsql; + +-- ============================================ +-- KOREADER SYNC FUNCTIONS (Phase 3) +-- ============================================ + +-- Bulk update progress from KOReader sync data +CREATE OR REPLACE FUNCTION bulk_update_progress_from_koreader( + p_user_id UUID, + p_sync_data JSONB +) RETURNS TABLE ( + media_item_id UUID, + success BOOLEAN, + message TEXT +) AS $$ +DECLARE + book_record JSONB; + media_uuid UUID; + existing_progress reading_progress%ROWTYPE; + conflict_detected BOOLEAN; +BEGIN + FOR book_record IN SELECT * FROM jsonb_array_elements(p_sync_data->'books') + LOOP + -- Try to find media item by UUID + media_uuid := NULL; + SELECT id INTO media_uuid FROM media_items WHERE id = (book_record->>'uuid')::uuid; + + -- If not found by UUID, try to match by file path + IF media_uuid IS NULL AND book_record ? 'file_path' THEN + SELECT id INTO media_uuid FROM media_items WHERE file_path = book_record->>'file_path'; + END IF; + + -- 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' + AND author = book_record->>'author' + LIMIT 1; + END IF; + + -- If no match found, return failure + IF media_uuid IS NULL THEN + RETURN QUERY SELECT NULL::uuid, FALSE, 'media item not found'; + CONTINUE; + END IF; + + -- Check for conflicts + conflict_detected := detect_conflict(media_uuid, book_record, 'koreader'); + + -- Check if progress exists + SELECT * INTO existing_progress FROM reading_progress + WHERE media_item_id = media_uuid AND user_id = p_user_id; + + -- Update or insert progress + IF existing_progress.media_item_id IS NOT NULL THEN + UPDATE reading_progress SET + percentage = (book_record->>'percentage')::FLOAT, + character_offset = CASE WHEN book_record ? 'character' THEN (book_record->>'character')::BIGINT ELSE existing_progress.character_offset END, + epubcfi = CASE WHEN book_record ? 'epubcfi' THEN (book_record->>'epubcfi')::TEXT ELSE existing_progress.epubcfi END, + chapter = CASE WHEN book_record ? 'chapter' THEN (book_record->>'chapter')::INTEGER ELSE existing_progress.chapter END, + chapter_progress = (book_record->>'percentage')::FLOAT, + last_sync_device = 'koreader', + last_sync_source = 'koreader', + last_sync_timestamp = NOW(), + conflict_detected = conflict_detected, + conflict_resolved = NOT conflict_detected, + last_read_at = CASE WHEN book_record ? 'last_read' THEN (book_record->>'last_read')::TIMESTAMP WITH TIME ZONE ELSE NOW() END, + current_page = CASE WHEN book_record ? 'page' THEN (book_record->>'page')::INTEGER ELSE existing_progress.current_page END, + total_pages = CASE WHEN book_record ? 'total_pages' THEN (book_record->>'total_pages')::INTEGER ELSE existing_progress.total_pages END + WHERE media_item_id = media_uuid AND user_id = p_user_id; + ELSE + INSERT INTO reading_progress ( + media_item_id, + user_id, + percentage, + character_offset, + epubcfi, + chapter, + chapter_progress, + last_sync_device, + last_sync_source, + last_sync_timestamp, + conflict_detected, + conflict_resolved, + last_read_at, + current_page, + total_pages + ) VALUES ( + media_uuid, + p_user_id, + (book_record->>'percentage')::FLOAT, + CASE WHEN book_record ? 'character' THEN (book_record->>'character')::BIGINT ELSE NULL END, + CASE WHEN book_record ? 'epubcfi' THEN (book_record->>'epubcfi')::TEXT ELSE NULL END, + CASE WHEN book_record ? 'chapter' THEN (book_record->>'chapter')::INTEGER ELSE NULL END, + (book_record->>'percentage')::FLOAT, + 'koreader', + 'koreader', + NOW(), + conflict_detected, + NOT conflict_detected, + CASE WHEN book_record ? 'last_read' THEN (book_record->>'last_read')::TIMESTAMP WITH TIME ZONE ELSE NOW() END, + CASE WHEN book_record ? 'page' THEN (book_record->>'page')::INTEGER ELSE NULL END, + CASE WHEN book_record ? 'total_pages' THEN (book_record->>'total_pages')::INTEGER ELSE NULL END + ); + END IF; + + -- Create conflict record if detected + IF conflict_detected THEN + INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data) + VALUES (media_uuid, p_user_id, 'progress', jsonb_build_object( + 'koreader', book_record, + 'timestamp', NOW() + )); + END IF; + + RETURN QUERY SELECT media_uuid, TRUE, + CASE WHEN conflict_detected THEN 'conflict detected' ELSE 'success' END; + END LOOP; +END; +$$ LANGUAGE plpgsql;