mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Add release blocklist feature
Introduce a per-request release blocklist to auto-block permanently failing releases and provide admin management. Changes include: - Database: add BlockedRelease model (blocked_releases) to Prisma schema with unique (requestId, releaseKey) and indexes; documented in backend database docs. - Service & utils: new blocklist.service, release-key and filter helpers for normalization and matching; processors updated to emit auto-blocks (monitor-download, organize-files, search processors, RSS). - HTTP API: add admin endpoints GET/DELETE /api/admin/blocklist, DELETE /api/admin/blocklist/[id], and GET /api/admin/blocklist/by-request/[requestId]. - Admin UI: new /admin/blocklist page and numerous React components (toolbar, filters, table, rows, pagination, skeleton, chips, date picker) with URL-driven state hook and per-row unblock UX. - Tests: add unit/integration tests for service, routes, utils, and updated processor tests. The blocklist is idempotent (upsert), filters search results before ranking (interactive search shows badges only), and admin-only APIs require auth. This commit wires docs, API, DB, frontend and tests for the new feature.
This commit is contained in:
@@ -259,6 +259,7 @@ model Request {
|
||||
jobs Job[]
|
||||
parentRequest Request? @relation("EbookParent", fields: [parentRequestId], references: [id], onDelete: SetNull)
|
||||
childRequests Request[] @relation("EbookParent")
|
||||
blockedReleases BlockedRelease[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([audiobookId])
|
||||
@@ -270,6 +271,42 @@ model Request {
|
||||
@@map("requests")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BLOCKED RELEASES TABLE
|
||||
// Per-request blocklist of failed releases (organize-fail or download-fail).
|
||||
// Search processors filter their candidate set against this table so future
|
||||
// searches skip releases that have already failed for the same request.
|
||||
// Documentation: documentation/backend/database.md
|
||||
// ============================================================================
|
||||
|
||||
model BlockedRelease {
|
||||
id String @id @default(uuid())
|
||||
requestId String @map("request_id")
|
||||
releaseName String @map("release_name") @db.Text
|
||||
releaseKey String @map("release_key") @db.Text // normalized: trim + lowercase
|
||||
releaseHash String? @map("release_hash") // torrentHash OR nzbId (mutually exclusive in source)
|
||||
indexerName String? @map("indexer_name")
|
||||
indexerId Int? @map("indexer_id")
|
||||
source String // 'organize_fail' | 'download_fail' | 'manual' (manual reserved for v2)
|
||||
reason String @db.Text // short reason, e.g. "No audiobook files found"
|
||||
reasonDetail String? @map("reason_detail") @db.Text // raw client error (SAB failMessage, NZBGet Par/Unpack)
|
||||
downloadHistoryId String? @map("download_history_id") // traceability to the DownloadHistory row that failed
|
||||
jobId String? @map("job_id") // origin job (also drives JobEvent emission via logger)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Relations
|
||||
// Cascade: hard-delete of Request wipes its blocklist rows.
|
||||
// Soft-delete (Request.deletedAt) does NOT cascade — entries survive.
|
||||
request Request @relation(fields: [requestId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([requestId, releaseKey]) // idempotency: one row per (request, normalized name)
|
||||
@@index([requestId])
|
||||
@@index([releaseKey])
|
||||
@@index([releaseHash])
|
||||
@@index([createdAt(sort: Desc)])
|
||||
@@map("blocked_releases")
|
||||
}
|
||||
|
||||
model DownloadHistory {
|
||||
id String @id @default(uuid())
|
||||
requestId String @map("request_id")
|
||||
|
||||
Reference in New Issue
Block a user