DB-first library reads, mixed library indexing, and manual NFO refresh

- API reads now serve from media_items cache instead of scanning the filesystem
  on every request; scans (manual or scheduled) remain the write path
- NFO metadata is no longer parsed automatically during scans; title falls back
  to folder/filename — metadata can be refreshed per-item via the kabob menu
- Mixed libraries are now indexed in media_items (new mixed_file item type)
  with file_path stored; scanMixed walks recursively and upserts all files
- Added file_path column to media_items and migrated item_type CHECK constraint
  to include mixed_file via safe table-recreation migration
- New POST /api/nfo-refresh endpoint reads the .nfo for a single item and
  patches its DB row (supports movie, tv_series, tv_episode)
- Added "Refresh metadata" button to movie and TV series kabob menus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garret Patti
2026-04-06 18:20:21 -04:00
parent 01a4a1c0b7
commit 819748d1ff
12 changed files with 597 additions and 94 deletions

View File

@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import { NextRequest, NextResponse } from 'next/server'
import { getLibrary, resolveLibraryRoot, resolveAndJail } from '@/lib/libraries'
import { scanTvLibrary, scanTvSeasons, scanTvEpisodes } from '@/lib/tv'
import { tvSeriesFromDb, tvSeasonsFromDb, tvEpisodesFromDb } from '@/lib/tv'
import { removeAllAssignmentsForItem } from '@/lib/tags'
import { requireLibraryAccess, requireAdmin } from '@/lib/auth'
@@ -27,20 +27,15 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Library is not a TV library' }, { status: 400 })
}
const root = resolveLibraryRoot(library)
if (seriesId && seasonId) {
const episodes = scanTvEpisodes(root, libraryId, seriesId, seasonId)
return NextResponse.json(episodes)
return NextResponse.json(tvEpisodesFromDb(libraryId, seriesId, seasonId))
}
if (seriesId) {
const seasons = scanTvSeasons(root, libraryId, seriesId)
return NextResponse.json(seasons)
return NextResponse.json(tvSeasonsFromDb(libraryId, seriesId))
}
const series = scanTvLibrary(root, libraryId)
return NextResponse.json(series)
return NextResponse.json(tvSeriesFromDb(libraryId))
}
export async function DELETE(request: NextRequest) {