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}`)