From 859a331012b7ad3bac61d4ed929d524155b12229 Mon Sep 17 00:00:00 2001 From: kikootwo Date: Thu, 5 Mar 2026 15:02:59 -0500 Subject: [PATCH] Run data migrations; use search title for ranking Add an entrypoint step to execute idempotent SQL data migrations (prisma db execute) from prisma/data-migrations/*.sql so fixes that prisma db push doesn't handle are applied on startup. Add normalize-local-usernames.sql to normalize local users' plex_username and plex_id to lowercase. Update interactive search and search-indexers processor to prefer the user-provided/custom search title (searchTitle / effectiveSearchTitle) when ranking torrents and adjust debug logs to show the ranking title alongside the audiobook title/author for clearer diagnostics. --- docker/unified/entrypoint.sh | 9 +++++++++ prisma/data-migrations/normalize-local-usernames.sql | 7 +++++++ src/app/api/requests/[id]/interactive-search/route.ts | 6 +++--- src/lib/processors/search-indexers.processor.ts | 5 +++-- 4 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 prisma/data-migrations/normalize-local-usernames.sql diff --git a/docker/unified/entrypoint.sh b/docker/unified/entrypoint.sh index be6a4a5..e0a65d7 100644 --- a/docker/unified/entrypoint.sh +++ b/docker/unified/entrypoint.sh @@ -403,6 +403,15 @@ echo "🔄 Running Prisma migrations..." cd /app su - node -c "cd /app && DATABASE_URL='$DATABASE_URL' npx prisma db push --skip-generate --accept-data-loss" || echo "⚠️ Migrations may have failed, continuing..." +# Run data migrations (idempotent SQL scripts that prisma db push doesn't handle) +echo "🔄 Running data migrations..." +for sql_file in /app/prisma/data-migrations/*.sql; do + if [ -f "$sql_file" ]; then + echo " Running $(basename "$sql_file")..." + su - node -c "cd /app && DATABASE_URL='$DATABASE_URL' npx prisma db execute --schema prisma/schema.prisma --file '$sql_file'" || echo "⚠️ Data migration $(basename "$sql_file") may have failed, continuing..." + fi +done + # Stop internal PostgreSQL (supervisord will restart it via wrapper) if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then echo "🔧 Stopping temporary PostgreSQL instance..." diff --git a/prisma/data-migrations/normalize-local-usernames.sql b/prisma/data-migrations/normalize-local-usernames.sql new file mode 100644 index 0000000..e94fb55 --- /dev/null +++ b/prisma/data-migrations/normalize-local-usernames.sql @@ -0,0 +1,7 @@ +-- Normalize existing local usernames to lowercase (idempotent - safe to run multiple times) +-- Only affects local auth users, not Plex/OIDC users +UPDATE users SET plex_username = LOWER(plex_username) + WHERE auth_provider = 'local' AND deleted_at IS NULL AND plex_username != LOWER(plex_username); + +UPDATE users SET plex_id = 'local-' || LOWER(SUBSTRING(plex_id FROM 7)) + WHERE plex_id LIKE 'local-%' AND plex_id NOT LIKE 'local-%-deleted-%' AND plex_id != LOWER(plex_id); diff --git a/src/app/api/requests/[id]/interactive-search/route.ts b/src/app/api/requests/[id]/interactive-search/route.ts index db39906..5bfd7ae 100644 --- a/src/app/api/requests/[id]/interactive-search/route.ts +++ b/src/app/api/requests/[id]/interactive-search/route.ts @@ -196,10 +196,10 @@ export async function POST( const langConfig = getLanguageForRegion(region); // Rank torrents using the ranking algorithm with indexer priorities and flag configs - // Always use the audiobook's title/author for ranking (not custom search query) + // Use searchTitle for ranking so custom search terms and search bar overrides are respected // requireAuthor: false - interactive mode, show all results for user decision const rankedResults = rankTorrents(results, { - title: requestRecord.audiobook.title, + title: searchTitle, author: requestRecord.audiobook.author, durationMinutes, }, { @@ -218,7 +218,7 @@ export async function POST( const top3 = rankedResults.slice(0, 3); if (top3.length > 0) { logger.debug('==================== RANKING DEBUG ===================='); - logger.debug('Search parameters', { searchTitle, requestedTitle: requestRecord.audiobook.title, requestedAuthor: requestRecord.audiobook.author }); + logger.debug('Search parameters', { searchTitle, rankingTitle: searchTitle, audiobookTitle: requestRecord.audiobook.title, requestedAuthor: requestRecord.audiobook.author }); logger.debug(`Top ${top3.length} results (out of ${rankedResults.length} total)`); logger.debug('--------------------------------------------------------'); top3.forEach((result, index) => { diff --git a/src/lib/processors/search-indexers.processor.ts b/src/lib/processors/search-indexers.processor.ts index e114023..3e13776 100644 --- a/src/lib/processors/search-indexers.processor.ts +++ b/src/lib/processors/search-indexers.processor.ts @@ -166,9 +166,10 @@ export async function processSearchIndexers(payload: SearchIndexersPayload): Pro // Rank results with indexer priorities and flag configs // Note: rankTorrents now filters out results < 20 MB internally + // Use effectiveSearchTitle so custom search terms are respected for ranking // requireAuthor: true (default) - strict filtering for automatic selection const rankedResults = ranker.rankTorrents(searchResults, { - title: audiobook.title, + title: effectiveSearchTitle, author: audiobook.author, durationMinutes, }, { @@ -228,7 +229,7 @@ export async function processSearchIndexers(payload: SearchIndexersPayload): Pro // Log top 3 results with detailed breakdown const top3 = filteredResults.slice(0, 3); logger.info(`==================== RANKING DEBUG ====================`); - logger.info(`Requested Title: "${audiobook.title}"`); + logger.info(`Ranking Title: "${effectiveSearchTitle}"${effectiveSearchTitle !== audiobook.title ? ` (audiobook: "${audiobook.title}")` : ''}`); logger.info(`Requested Author: "${audiobook.author}"`); logger.info(`Top ${top3.length} results (out of ${filteredResults.length} above threshold):`); logger.info(`--------------------------------------------------------`);