mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
fix(tmdb): resolve confirmed review findings — matching correctness, race guards, cache schema
Fixes the confirmed findings from the PR #1123 code review: - Stalker search: setSelectedContentType now runs BEFORE setSelectedItem, so the TMDB enrichment gate in the selection hook no longer sees the content type of the previously open tab (wrong/no enrichment after ITV -> search -> movie). - Title normalization is now two-tier (normalizeTitleKeys): the exact normalized form keeps a trailing year, the base form strips it and remembers the tag. Year stripping is anchored to the end of the title ("2001: A Space Odyssey" keeps its year) and language-prefix stripping is UPPERCASE-only ("It: Chapter Two" is no longer amputated). - All catalog matching (similar rail, actor pages, DB worker DB_MATCH_TITLES) compares exact forms first and only accepts year-stripped matches when the stripped tag is year-compatible (+-1) with the TMDB year — "Blade Runner" (1982) can no longer claim a catalog "Blade Runner 2049". CatalogTitleMatch carries the stripped trailingYear so the renderer can apply the guard to worker matches. - mergedBackdrops tolerates a plain-string backdrop_path; enrichment merge+patch blocks are wrapped in try/catch so a malformed provider payload can no longer become an unhandled rejection. - loadGlobalMatches (both actor routes) guards against actor->actor navigation races — a slow match for the previous person no longer overwrites the current one's results. - tmdb_metadata media_type CHECK widened to ('movie','tv','person') and person rows now use the honest 'person' type (TmdbCacheMediaType). Pre-release dev DBs with the narrow CHECK are rebuilt in place — the table is a pure cache, so the migration is a self-healing drop-and-recreate keyed off sqlite_master. Docs updated (tmdb-metadata-enrichment.md, CLAUDE.md). New regression coverage: title-normalization.util.spec.ts, two-tier cases in tmdb-similar.util.spec.ts and title-match.operations.spec.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa92a3b826
commit
ac3a4e91f3
25 files changed
+545
-124
No files matched your search
@@ -73,6 +73,17 @@ export function getDatabasePath(): string {
|
||||
/**
|
||||
* SQL statements for creating all tables
|
||||
*/
|
||||
const TMDB_METADATA_TABLE_SQL = `CREATE TABLE IF NOT EXISTS tmdb_metadata (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_type TEXT NOT NULL CHECK (media_type IN ('movie', 'tv', 'person')),
|
||||
lookup_key TEXT NOT NULL,
|
||||
language TEXT NOT NULL,
|
||||
tmdb_id INTEGER,
|
||||
payload TEXT,
|
||||
fetched_at TEXT DEFAULT (datetime('now'))
|
||||
)`;
|
||||
const TMDB_METADATA_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS tmdb_metadata_lookup_unique ON tmdb_metadata(media_type, lookup_key, language)`;
|
||||
|
||||
const CREATE_TABLE_STATEMENTS = [
|
||||
`CREATE TABLE IF NOT EXISTS playlists (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -295,16 +306,8 @@ const CREATE_TABLE_STATEMENTS = [
|
||||
`CREATE INDEX IF NOT EXISTS downloads_playlist_idx ON downloads(playlist_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS downloads_status_idx ON downloads(status)`,
|
||||
// TMDB metadata cache (details payloads + search match resolutions)
|
||||
`CREATE TABLE IF NOT EXISTS tmdb_metadata (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_type TEXT NOT NULL CHECK (media_type IN ('movie', 'tv')),
|
||||
lookup_key TEXT NOT NULL,
|
||||
language TEXT NOT NULL,
|
||||
tmdb_id INTEGER,
|
||||
payload TEXT,
|
||||
fetched_at TEXT DEFAULT (datetime('now'))
|
||||
)`,
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS tmdb_metadata_lookup_unique ON tmdb_metadata(media_type, lookup_key, language)`,
|
||||
TMDB_METADATA_TABLE_SQL,
|
||||
TMDB_METADATA_INDEX_SQL,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -724,10 +727,41 @@ function runMigrationStatements(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-release installs created tmdb_metadata with a CHECK that only
|
||||
* allowed 'movie'/'tv'; person rows need 'person'. The table is a pure
|
||||
* cache, so the cheapest "migration" is a drop-and-recreate with the
|
||||
* widened constraint. Self-healing via sqlite_master — no app_state key.
|
||||
*/
|
||||
function widenTmdbMetadataMediaTypeCheck(sqliteDb: Database.Database): void {
|
||||
try {
|
||||
const row = sqliteDb
|
||||
.prepare(
|
||||
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'tmdb_metadata'`
|
||||
)
|
||||
.get() as { sql?: string } | undefined;
|
||||
if (!row?.sql || row.sql.includes(`'person'`)) {
|
||||
return;
|
||||
}
|
||||
const rebuild = sqliteDb.transaction(() => {
|
||||
sqliteDb.prepare(`DROP TABLE IF EXISTS tmdb_metadata`).run();
|
||||
sqliteDb.prepare(TMDB_METADATA_TABLE_SQL).run();
|
||||
sqliteDb.prepare(TMDB_METADATA_INDEX_SQL).run();
|
||||
});
|
||||
rebuild();
|
||||
console.log(
|
||||
'[DB] Rebuilt tmdb_metadata cache with widened media_type CHECK'
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('[DB] tmdb_metadata CHECK widening failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run migrations that may fail if already applied
|
||||
*/
|
||||
function runMigrations(sqliteDb: Database.Database): void {
|
||||
widenTmdbMetadataMediaTypeCheck(sqliteDb);
|
||||
runMigrationStatements(sqliteDb, COLUMN_MIGRATION_STATEMENTS);
|
||||
ensureContentTitleFts(sqliteDb);
|
||||
deduplicateXtreamCache(sqliteDb);
|
||||
|
||||
@@ -359,7 +359,7 @@ export const tmdbMetadata = sqliteTable(
|
||||
'tmdb_metadata',
|
||||
{
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
mediaType: text('media_type', { enum: ['movie', 'tv'] }).notNull(),
|
||||
mediaType: text('media_type', { enum: ['movie', 'tv', 'person'] }).notNull(),
|
||||
lookupKey: text('lookup_key').notNull(),
|
||||
language: text('language').notNull(),
|
||||
tmdbId: integer('tmdb_id'),
|
||||
|
||||
Reference in new issue
Block a user