1
0

feat: release payload disk cache, batched rip status for list, artwork 404, stricter input handling

This commit is contained in:
2026-08-29 18:44:08 +02:00
parent 39c956e67d
commit 56ffda9e88
10 changed files with 116 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
import type { DB } from './db.js'
import { isConfidentMatch } from './matcher.js'
import { isConfidentMatch, normalize } from './matcher.js'
export type RipStatus = 'ripped' | 'not_ripped'
@@ -29,3 +29,36 @@ export function resolveRipStatus(db: DB, userId: number, itemId: number): RipSta
.all(userId) as { title: string; artist: string }[]
return albums.some((a) => isConfidentMatch(item, a)) ? 'ripped' : 'not_ripped'
}
/**
* Batch resolution for list views: loads albums and match links once per
* user instead of per item (GET /api/collection is otherwise O(items x albums)).
* Resolution order matches resolveRipStatus.
*/
export function resolveRipStatusBatch(
db: DB,
userId: number,
items: { id: number; title: string; artist: string; rip_override: number | null }[]
): RipStatus[] {
const albums = db
.prepare('SELECT title, artist FROM digital_albums WHERE user_id = ?')
.all(userId) as { title: string; artist: string }[]
const albumKeys = new Set(
albums.map((a) => `${normalize(a.title)}|${normalize(a.artist)}`)
)
const linked = new Set(
(
db.prepare('SELECT item_id FROM match_links WHERE user_id = ?').all(userId) as {
item_id: number
}[]
).map((r) => r.item_id)
)
return items.map((item) => {
if (item.rip_override !== null && item.rip_override !== undefined) {
return item.rip_override === 1 ? 'ripped' : 'not_ripped'
}
if (linked.has(item.id)) return 'ripped'
const key = `${normalize(item.title)}|${normalize(item.artist)}`
return albumKeys.has(key) ? 'ripped' : 'not_ripped'
})
}