From aae41e9803dd990e716da7b4a9413c046cbf78df Mon Sep 17 00:00:00 2001 From: Garret Patti <42485635+garretpatti@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:51:51 -0400 Subject: [PATCH] add individual library scanning --- src/app/api/scan/[id]/route.ts | 28 +++++++++++++++ src/app/library/[id]/page.tsx | 6 ++++ src/app/manage/page.tsx | 5 ++- src/components/ScanLibraryButton.tsx | 53 ++++++++++++++++++++++++++++ src/lib/scanner.ts | 14 ++++++++ 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/app/api/scan/[id]/route.ts create mode 100644 src/components/ScanLibraryButton.tsx diff --git a/src/app/api/scan/[id]/route.ts b/src/app/api/scan/[id]/route.ts new file mode 100644 index 0000000..ab871f2 --- /dev/null +++ b/src/app/api/scan/[id]/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getLibrary } from '@/lib/libraries' +import { isScanRunning, runSingleLibraryScan } from '@/lib/scanner' +import { requireAdmin } from '@/lib/auth' + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = await requireAdmin(request) + if (auth instanceof NextResponse) return auth + + const { id } = await params + + const library = getLibrary(id) + if (!library) { + return NextResponse.json({ error: 'Library not found' }, { status: 404 }) + } + + if (isScanRunning()) { + return NextResponse.json({ error: 'Scan already in progress' }, { status: 409 }) + } + + // Fire-and-forget + void runSingleLibraryScan(library) + + return new NextResponse(null, { status: 202 }) +} diff --git a/src/app/library/[id]/page.tsx b/src/app/library/[id]/page.tsx index 45179f5..3ae6ffa 100644 --- a/src/app/library/[id]/page.tsx +++ b/src/app/library/[id]/page.tsx @@ -6,6 +6,7 @@ import GamesView from '@/components/games/GamesView' import MixedView from '@/components/mixed/MixedView' import MoviesView from '@/components/movies/MoviesView' import TvView from '@/components/tv/TvView' +import ScanLibraryButton from '@/components/ScanLibraryButton' interface Props { params: Promise<{ id: string }> @@ -37,6 +38,11 @@ export default async function LibraryPage({ params, searchParams }: Props) { {library.name} + {session.role === 'admin' && ( +
+ +
+ )} {library.type === 'games' && } diff --git a/src/app/manage/page.tsx b/src/app/manage/page.tsx index 70bf022..50c16fe 100644 --- a/src/app/manage/page.tsx +++ b/src/app/manage/page.tsx @@ -286,7 +286,10 @@ function AddLibraryForm({ onAdded }: { onAdded: () => void }) { return } - // Success — reset form + // Success — fire scan for the new library (fire-and-forget) + void fetch(`/api/scan/${encodeURIComponent((data as { id: string }).id)}`, { method: 'POST' }) + + // Reset form setName('') setLibPath('') setType('games') diff --git a/src/components/ScanLibraryButton.tsx b/src/components/ScanLibraryButton.tsx new file mode 100644 index 0000000..144b7d3 --- /dev/null +++ b/src/components/ScanLibraryButton.tsx @@ -0,0 +1,53 @@ +'use client' + +import { useState } from 'react' + +interface Props { + libraryId: string +} + +export default function ScanLibraryButton({ libraryId }: Props) { + const [scanning, setScanning] = useState(false) + const [message, setMessage] = useState(null) + + const handleScan = async () => { + setScanning(true) + setMessage(null) + + try { + const res = await fetch(`/api/scan/${encodeURIComponent(libraryId)}`, { method: 'POST' }) + + if (res.status === 409) { + setMessage('A scan is already in progress.') + } + } catch { + setMessage('Failed to start scan.') + } finally { + setScanning(false) + } + } + + return ( +
+ + {message && ( + + {message} + + )} +
+ ) +} diff --git a/src/lib/scanner.ts b/src/lib/scanner.ts index 230d5d7..65d1710 100644 --- a/src/lib/scanner.ts +++ b/src/lib/scanner.ts @@ -38,6 +38,20 @@ export async function runFullScan(): Promise { } } +export async function runSingleLibraryScan(library: Library): Promise { + if (scanRunning) return + scanRunning = true + console.log(`[scanner] Starting single library scan for "${library.name}"`) + try { + await runLibraryScan(library) + const now = Date.now() + setScanLastRan(now) + console.log(`[scanner] Single library scan complete for "${library.name}"`) + } finally { + scanRunning = false + } +} + export async function runLibraryScan(library: Library): Promise { const libraryRoot = resolveLibraryRoot(library) console.log(`[scanner] Scanning library "${library.name}" (${library.type}) at ${libraryRoot}`)