fix: use BigInt for PlexLibrary.duration to prevent INT4 overflow

The duration column (Int/int4, max ~2.15B) overflows when storing
millisecond values for items with large durations from Audiobookshelf
or Plex backends. Change to BigInt (int8) and wrap duration calculations
in BigInt() at the Prisma write boundary.

Changes:
- prisma/schema.prisma: PlexLibrary.duration Int? → BigInt?
- plex-recently-added.processor.ts: BigInt(Math.round(...)) wrapping
- scan-plex.processor.ts: same BigInt wrapping
- documentation/backend/database.md: updated duration type notation

Fixes #193

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
H0tChicken
2026-05-03 14:29:09 +00:00
parent 5f0855b2f8
commit 37f063229c
4 changed files with 6 additions and 6 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ PostgreSQL database storing users, audiobooks, requests, downloads, configuratio
### Plex_Library (Library Cache)
- `id` (UUID PK), `plex_guid` (unique, external ID from Plex or Audiobookshelf), `plex_rating_key`
- `title`, `author`, `narrator`, `summary`, `duration` (milliseconds), `year`, `user_rating` (0-10 scale)
- `title`, `author`, `narrator`, `summary`, `duration` (BigInt, milliseconds), `year`, `user_rating` (0-10 scale)
- **Universal identifiers:** `asin` (Audible ASIN), `isbn` (ISBN-10 or ISBN-13)
- `file_path`, `thumb_url`, `cached_library_cover_path` (local cached cover path), `plex_library_id`, `added_at`
- `last_scanned_at`, `created_at`, `updated_at`
+1 -1
View File
@@ -132,7 +132,7 @@ model PlexLibrary {
author String
narrator String?
summary String? @db.Text
duration Int? // Duration in milliseconds (Plex format)
duration BigInt? // Duration in milliseconds (Plex format)
year Int?
userRating Decimal? @map("user_rating") @db.Decimal(3, 1) // User's rating (0-10 scale from Plex)
@@ -106,7 +106,7 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
author: item.author || 'Unknown Author',
narrator: item.narrator,
summary: item.description,
duration: item.duration ? item.duration * 1000 : null, // Convert seconds to milliseconds
duration: item.duration ? BigInt(Math.round(item.duration * 1000)) : null, // Convert seconds to milliseconds
year: item.year,
asin: item.asin, // Store ASIN from library backend
isbn: item.isbn, // Store ISBN from library backend
@@ -146,7 +146,7 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
author: item.author || existing.author,
narrator: item.narrator || existing.narrator,
summary: item.description || existing.summary,
duration: item.duration ? item.duration * 1000 : existing.duration,
duration: item.duration ? BigInt(Math.round(item.duration * 1000)) : existing.duration,
year: item.year || existing.year,
asin: item.asin || existing.asin, // Update ASIN if available
isbn: item.isbn || existing.isbn, // Update ISBN if available
+2 -2
View File
@@ -90,7 +90,7 @@ export async function processScanPlex(payload: ScanPlexPayload): Promise<any> {
author: item.author || existing.author,
narrator: item.narrator || existing.narrator,
summary: item.description || existing.summary,
duration: item.duration ? item.duration * 1000 : existing.duration, // Convert seconds to milliseconds
duration: item.duration ? BigInt(Math.round(item.duration * 1000)) : existing.duration, // Convert seconds to milliseconds
year: item.year || existing.year,
asin: item.asin || existing.asin, // Store ASIN from library backend
isbn: item.isbn || existing.isbn, // Store ISBN from library backend
@@ -132,7 +132,7 @@ export async function processScanPlex(payload: ScanPlexPayload): Promise<any> {
author: item.author || 'Unknown Author',
narrator: item.narrator,
summary: item.description,
duration: item.duration ? item.duration * 1000 : null, // Convert seconds to milliseconds
duration: item.duration ? BigInt(Math.round(item.duration * 1000)) : null, // Convert seconds to milliseconds
year: item.year,
asin: item.asin, // Store ASIN from library backend (Plex or Audiobookshelf)
isbn: item.isbn, // Store ISBN from library backend