Fix matching: add missing subtitle field, proper error logging, match-all endpoint

- MatchResult was missing subtitle field, causing AttributeError in
  _apply_match that silently killed every background match task
- Wrap _apply_match in try/except with exc_info logging so failures
  are visible in docker compose logs backend
- New POST /api/libraries/:id/match-all endpoint to trigger matching
  for all unlocked items (useful for items scanned before the fix)
- Admin UI: Match button per library next to the Scan button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Audiolib
2026-05-26 15:15:11 +02:00
parent 6bb07ff873
commit 3871da4bcc
5 changed files with 58 additions and 6 deletions

View File

@@ -14,6 +14,9 @@ export const searchLibrary = (libraryId: string, q: string) =>
export const scanLibrary = (libraryId: string) =>
api.post(`/api/libraries/${libraryId}/scan`).then((r) => r.data)
export const matchAllLibrary = (libraryId: string) =>
api.post(`/api/libraries/${libraryId}/match-all`).then((r) => r.data)
export const createLibrary = (data: object) =>
api.post('/api/libraries', data).then((r) => r.data)

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { Users, Library, Settings, Trash2, Plus, RefreshCw, Loader2, Check, X, FolderOpen, Pencil } from 'lucide-react'
import { Users, Library, Settings, Trash2, Plus, RefreshCw, Loader2, Check, X, FolderOpen, Pencil, Sparkles } from 'lucide-react'
import { getUsers, createUser, deleteUser, getSettings, updateSettings } from '../api/me'
import { getLibraries, scanLibrary, createLibrary, updateLibrary, deleteLibrary } from '../api/libraries'
import { getLibraries, scanLibrary, matchAllLibrary, createLibrary, updateLibrary, deleteLibrary } from '../api/libraries'
import FileBrowser from '../components/common/FileBrowser'
type Tab = 'users' | 'libraries' | 'settings'
@@ -214,6 +214,7 @@ function LibrariesPanel() {
const [libraries, setLibraries] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [scanning, setScanning] = useState<string | null>(null)
const [matching, setMatching] = useState<string | null>(null)
const [showCreate, setShowCreate] = useState(false)
const [editingId, setEditingId] = useState<string | null>(null)
@@ -226,6 +227,12 @@ function LibrariesPanel() {
setTimeout(() => setScanning(null), 5000)
}
const handleMatchAll = async (id: string) => {
setMatching(id)
await matchAllLibrary(id).catch(() => {})
setTimeout(() => setMatching(null), 3000)
}
const handleCreate = async (form: { name: string; path: string; mediaType: string; matchSources: string[] }) => {
await createLibrary({
name: form.name, folders: [{ fullPath: form.path }], media_type: form.mediaType,
@@ -293,6 +300,14 @@ function LibrariesPanel() {
<RefreshCw size={12} className={scanning === lib.id ? 'animate-spin' : ''} />
Scan
</button>
{lib.mediaType !== 'podcast' && (
<button onClick={() => handleMatchAll(lib.id)} disabled={matching === lib.id}
title="Matching für alle Items starten"
className="flex items-center gap-1 text-xs text-gray-400 hover:text-white bg-white/5 px-3 py-1.5 rounded-lg disabled:opacity-50 flex-shrink-0">
<Sparkles size={12} className={matching === lib.id ? 'animate-pulse' : ''} />
Match
</button>
)}
<button onClick={() => { setEditingId(lib.id); setShowCreate(false) }}
className="text-gray-500 hover:text-white p-1 flex-shrink-0">
<Pencil size={14} />