Compare commits

...

2 Commits

Author SHA1 Message Date
kikootwo 3e2221ad5b Bump package version to 1.1.1
Update package.json version from 1.1.0 to 1.1.1 to reflect a patch release.
2026-03-05 15:03:29 -05:00
kikootwo 859a331012 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.
2026-03-05 15:02:59 -05:00
5 changed files with 23 additions and 6 deletions
+9
View File
@@ -403,6 +403,15 @@ echo "🔄 Running Prisma migrations..."
cd /app 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..." 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) # Stop internal PostgreSQL (supervisord will restart it via wrapper)
if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then if [ "$USE_EXTERNAL_POSTGRES" = "false" ]; then
echo "🔧 Stopping temporary PostgreSQL instance..." echo "🔧 Stopping temporary PostgreSQL instance..."
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "readmeabook", "name": "readmeabook",
"version": "1.1.0", "version": "1.1.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -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);
@@ -196,10 +196,10 @@ export async function POST(
const langConfig = getLanguageForRegion(region); const langConfig = getLanguageForRegion(region);
// Rank torrents using the ranking algorithm with indexer priorities and flag configs // 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 // requireAuthor: false - interactive mode, show all results for user decision
const rankedResults = rankTorrents(results, { const rankedResults = rankTorrents(results, {
title: requestRecord.audiobook.title, title: searchTitle,
author: requestRecord.audiobook.author, author: requestRecord.audiobook.author,
durationMinutes, durationMinutes,
}, { }, {
@@ -218,7 +218,7 @@ export async function POST(
const top3 = rankedResults.slice(0, 3); const top3 = rankedResults.slice(0, 3);
if (top3.length > 0) { if (top3.length > 0) {
logger.debug('==================== RANKING DEBUG ===================='); 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(`Top ${top3.length} results (out of ${rankedResults.length} total)`);
logger.debug('--------------------------------------------------------'); logger.debug('--------------------------------------------------------');
top3.forEach((result, index) => { top3.forEach((result, index) => {
@@ -166,9 +166,10 @@ export async function processSearchIndexers(payload: SearchIndexersPayload): Pro
// Rank results with indexer priorities and flag configs // Rank results with indexer priorities and flag configs
// Note: rankTorrents now filters out results < 20 MB internally // 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 // requireAuthor: true (default) - strict filtering for automatic selection
const rankedResults = ranker.rankTorrents(searchResults, { const rankedResults = ranker.rankTorrents(searchResults, {
title: audiobook.title, title: effectiveSearchTitle,
author: audiobook.author, author: audiobook.author,
durationMinutes, durationMinutes,
}, { }, {
@@ -228,7 +229,7 @@ export async function processSearchIndexers(payload: SearchIndexersPayload): Pro
// Log top 3 results with detailed breakdown // Log top 3 results with detailed breakdown
const top3 = filteredResults.slice(0, 3); const top3 = filteredResults.slice(0, 3);
logger.info(`==================== RANKING DEBUG ====================`); 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(`Requested Author: "${audiobook.author}"`);
logger.info(`Top ${top3.length} results (out of ${filteredResults.length} above threshold):`); logger.info(`Top ${top3.length} results (out of ${filteredResults.length} above threshold):`);
logger.info(`--------------------------------------------------------`); logger.info(`--------------------------------------------------------`);