mirror of
https://github.com/Dvorinka/SEEN.git
synced 2026-06-03 20:13:02 +00:00
1100 lines
22 KiB
Markdown
1100 lines
22 KiB
Markdown
# /seen/
|
|
|
|
/seen/ is a modern self-hosted platform for managing personal movies, TV shows, and games.
|
|
|
|
It combines the functionality of a media tracker, streaming dashboard, download manager, and recommendation engine into a single application.
|
|
|
|
The system allows users to discover content, track watched media, manage downloads, watch their personal media library, and manage a game backlog from a single modern dashboard-style interface.
|
|
|
|
/seen/ is designed to feel like a **personal streaming control center** that runs entirely on your own infrastructure.
|
|
|
|
---
|
|
|
|
# Core Philosophy
|
|
|
|
The platform should be:
|
|
|
|
- self-hosted
|
|
- fast
|
|
- modular
|
|
- visually premium
|
|
- simple to operate
|
|
- scalable
|
|
|
|
This project should **not resemble a generic admin dashboard**.
|
|
|
|
Instead it should feel like a combination of:
|
|
|
|
- Plex
|
|
- Letterboxd
|
|
- Netflix-style UI
|
|
- personal media server
|
|
- game backlog manager
|
|
|
|
---
|
|
|
|
# Core Features
|
|
|
|
## Personal Media Tracking
|
|
|
|
Users can:
|
|
|
|
- mark movies as watched
|
|
- mark movies or shows as watch later
|
|
- track TV episode progress
|
|
- track game completion states
|
|
- organize active, paused, completed, and wishlist games
|
|
- maintain personal collections
|
|
- assign personal ratings
|
|
- mark favorites
|
|
- view watch history
|
|
|
|
---
|
|
|
|
## Continue Watching
|
|
|
|
Playback progress is tracked per user.
|
|
|
|
Features include:
|
|
|
|
- resume playback
|
|
- per-episode progress tracking
|
|
- automatic watched detection
|
|
- dashboard continue watching section
|
|
|
|
Fast reads for progress are cached using DragonflyDB.
|
|
|
|
---
|
|
|
|
## Discover Content
|
|
|
|
Content discovery is powered by TMDB for movies and TV, and IGDB for games.
|
|
|
|
Sections include:
|
|
|
|
- trending
|
|
- popular
|
|
- top rated
|
|
- upcoming
|
|
- now playing
|
|
- airing today
|
|
- recently released games
|
|
- most anticipated games
|
|
- genre browsing
|
|
|
|
Users can also perform global search.
|
|
|
|
Game metadata should be fetched through the backend using IGDB and Twitch OAuth credentials, never directly from the browser.
|
|
|
|
---
|
|
|
|
## Recommendations
|
|
|
|
Recommendations are based on:
|
|
|
|
- user watch history
|
|
- favorite genres
|
|
- TMDB similarity
|
|
- IGDB genre, platform, and franchise similarity
|
|
- internal recommendation scoring
|
|
|
|
Recommendation results may be cached for performance.
|
|
|
|
---
|
|
|
|
## Download Manager
|
|
|
|
Users can add downloads through:
|
|
|
|
- magnet links
|
|
- torrent files
|
|
- direct download links
|
|
- HTTP URLs
|
|
|
|
The download manager handles:
|
|
|
|
- job queue
|
|
- progress
|
|
- download speed
|
|
- estimated completion
|
|
- status reporting
|
|
|
|
Download states include:
|
|
|
|
- queued
|
|
- downloading
|
|
- stalled
|
|
- completed
|
|
- failed
|
|
|
|
Downloaded files are stored on mounted storage volumes.
|
|
|
|
---
|
|
|
|
## Media Library
|
|
|
|
Downloaded or imported files are scanned and linked to TMDB metadata.
|
|
|
|
Users can:
|
|
|
|
- browse their media library
|
|
- stream media inside the platform
|
|
- access files via mounted storage
|
|
- view metadata and artwork
|
|
|
|
A background **media scanner service** automatically matches files to movies or shows.
|
|
|
|
---
|
|
|
|
## Built-in Player
|
|
|
|
The application includes a browser-based player.
|
|
|
|
Capabilities:
|
|
|
|
- playback resume
|
|
- subtitle support
|
|
- episode autoplay
|
|
- theater mode
|
|
|
|
Playback progress updates during viewing.
|
|
|
|
---
|
|
|
|
## Release Calendar
|
|
|
|
Calendar showing upcoming releases.
|
|
|
|
Includes:
|
|
|
|
- movie premieres
|
|
- new TV episodes
|
|
- next season releases
|
|
- game launches
|
|
- expansion and DLC release windows
|
|
|
|
Users can filter by:
|
|
|
|
- watchlist
|
|
- movie vs show
|
|
- game platform
|
|
- genre
|
|
|
|
---
|
|
|
|
## Dashboard
|
|
|
|
The dashboard is the main hub.
|
|
|
|
Widgets include:
|
|
|
|
- continue watching
|
|
- watch later
|
|
- game backlog
|
|
- recently played
|
|
- active downloads
|
|
- recommendations
|
|
- upcoming releases
|
|
- trending media
|
|
- recently watched
|
|
|
|
---
|
|
|
|
# Technology Stack
|
|
|
|
## Backend
|
|
|
|
- Golang
|
|
- Gin web framework
|
|
- PostgreSQL
|
|
- sqlc
|
|
- goose migrations
|
|
- zap structured logging
|
|
- DragonflyDB caching
|
|
|
|
Backend responsibilities:
|
|
|
|
- API
|
|
- authentication
|
|
- metadata management
|
|
- downloads orchestration
|
|
- background jobs
|
|
- TMDB integration
|
|
- IGDB integration
|
|
|
|
---
|
|
|
|
## Frontend
|
|
|
|
- SolidJS
|
|
- TypeScript
|
|
- Vite
|
|
- TailwindCSS
|
|
- shadcn-style component system
|
|
- Ark UI primitives
|
|
|
|
The frontend should behave like a **modern SaaS dashboard**.
|
|
|
|
---
|
|
|
|
## Infrastructure
|
|
|
|
- Docker
|
|
- Docker Compose
|
|
- Nginx reverse proxy
|
|
|
|
The system must be easy to deploy on a home server or VPS.
|
|
|
|
---
|
|
|
|
# UI Design
|
|
|
|
Design inspiration:
|
|
|
|
https://haptics.lochie.me/
|
|
|
|
The UI should feel:
|
|
|
|
- minimal
|
|
- spacious
|
|
- elegant
|
|
- modern
|
|
- smooth
|
|
|
|
Subtle motion and polished transitions should be used.
|
|
|
|
---
|
|
|
|
# Color Palette
|
|
|
|
Primary accent
|
|
|
|
|
|
#00b7ff
|
|
|
|
|
|
Secondary accent
|
|
|
|
|
|
#5800a9
|
|
|
|
|
|
Base background
|
|
|
|
|
|
#111111
|
|
|
|
|
|
The color scheme should be used subtly to maintain a premium appearance.
|
|
|
|
---
|
|
|
|
# Themes
|
|
|
|
The platform must support:
|
|
|
|
- dark mode
|
|
- light mode
|
|
- system preference detection
|
|
- persistent theme storage
|
|
|
|
Dark mode should be the primary visual style.
|
|
|
|
---
|
|
|
|
# Application Layout
|
|
|
|
Main application routes:
|
|
|
|
|
|
/app/dashboard
|
|
/app/discover
|
|
/app/movies
|
|
/app/shows
|
|
/app/watch-later
|
|
/app/watched
|
|
/app/downloads
|
|
/app/calendar
|
|
/app/recommendations
|
|
/app/library
|
|
/app/collections
|
|
/app/settings
|
|
/app/admin
|
|
|
|
|
|
The layout consists of:
|
|
|
|
- sidebar navigation
|
|
- top bar with search and profile
|
|
- dashboard content area
|
|
|
|
---
|
|
|
|
# Backend Architecture
|
|
|
|
Directory layout example:
|
|
|
|
|
|
cmd/
|
|
internal/
|
|
api/
|
|
services/
|
|
repositories/
|
|
domain/
|
|
middleware/
|
|
workers/
|
|
integrations/
|
|
downloader/
|
|
scanner/
|
|
pkg/
|
|
migrations/
|
|
sql/
|
|
|
|
|
|
The architecture separates:
|
|
|
|
- transport layer
|
|
- business logic
|
|
- data access
|
|
- integrations
|
|
|
|
---
|
|
|
|
# Background Workers
|
|
|
|
Background services handle tasks such as:
|
|
|
|
- TMDB metadata synchronization
|
|
- recommendation generation
|
|
- download monitoring
|
|
- media scanning
|
|
- scheduled cleanup
|
|
|
|
---
|
|
|
|
# Downloader Service
|
|
|
|
Downloader responsibilities include:
|
|
|
|
- torrent downloads
|
|
- HTTP downloads
|
|
- queue management
|
|
- status reporting
|
|
- file path tracking
|
|
|
|
The downloader should be modular so engines can be replaced later.
|
|
|
|
---
|
|
|
|
# Media Scanner
|
|
|
|
The scanner links downloaded files to metadata.
|
|
|
|
Process:
|
|
|
|
1. scan library folders
|
|
2. parse file names
|
|
3. query TMDB
|
|
4. match metadata
|
|
5. create library entries
|
|
|
|
---
|
|
|
|
# Database
|
|
|
|
PostgreSQL acts as the primary data store.
|
|
|
|
Main tables include:
|
|
|
|
|
|
users
|
|
sessions
|
|
movies
|
|
tv_shows
|
|
seasons
|
|
episodes
|
|
genres
|
|
user_watch_status
|
|
user_watchlist
|
|
user_progress
|
|
user_ratings
|
|
collections
|
|
collection_items
|
|
downloads
|
|
download_files
|
|
library_items
|
|
|
|
|
|
Indexes and foreign keys must be properly defined.
|
|
|
|
---
|
|
|
|
# Caching Strategy
|
|
|
|
DragonflyDB is used for:
|
|
|
|
- session storage
|
|
- API caching
|
|
- TMDB response cache
|
|
- continue watching cache
|
|
- search suggestions
|
|
- recommendation snapshots
|
|
|
|
PostgreSQL remains the source of truth.
|
|
|
|
---
|
|
|
|
# API
|
|
|
|
All endpoints follow versioned REST design.
|
|
|
|
Example routes:
|
|
|
|
|
|
/api/v1/auth
|
|
/api/v1/movies
|
|
/api/v1/shows
|
|
/api/v1/search
|
|
/api/v1/watchlist
|
|
/api/v1/progress
|
|
/api/v1/downloads
|
|
/api/v1/library
|
|
/api/v1/recommendations
|
|
|
|
|
|
Endpoints must support pagination and filtering.
|
|
|
|
---
|
|
|
|
# Authentication
|
|
|
|
Authentication system features:
|
|
|
|
- email and password login
|
|
- secure password hashing
|
|
- refresh token or session strategy
|
|
- role-based access
|
|
|
|
Roles include:
|
|
|
|
|
|
user
|
|
admin
|
|
|
|
|
|
---
|
|
|
|
# Security
|
|
|
|
Security measures include:
|
|
|
|
- input validation
|
|
- rate limiting
|
|
- secure cookie handling
|
|
- CSRF protection
|
|
- environment-based configuration
|
|
- safe file handling
|
|
|
|
---
|
|
|
|
# Deployment
|
|
|
|
The application runs in Docker containers.
|
|
|
|
Services include:
|
|
|
|
|
|
seen-backend
|
|
seen-frontend
|
|
postgres
|
|
dragonfly
|
|
nginx
|
|
downloader
|
|
|
|
|
|
The system should run with a single docker compose setup.
|
|
|
|
---
|
|
|
|
# Long-Term Vision
|
|
|
|
/seen/ aims to become a complete self-hosted media platform combining the strengths of:
|
|
|
|
- Plex
|
|
- Letterboxd
|
|
- Netflix UI
|
|
- personal media tracking
|
|
|
|
All while remaining open, customizable, and fully controlled by the user.
|
|
|
|
|
|
# UI Component Reference
|
|
|
|
- https://pro.alignui.com/blocks
|
|
|
|
Use this as visual reference for selected layout and component patterns.
|
|
The final result should stay clean, minimal, and modern SaaS style while keeping
|
|
the /seen/ brand direction.
|
|
|
|
---
|
|
|
|
# Implementation Snapshot (March 10, 2026)
|
|
|
|
This section reflects the repository state on **March 10, 2026**.
|
|
|
|
## Frontend status
|
|
|
|
Implemented:
|
|
|
|
- app shell (sidebar + top bar)
|
|
- route guards and auth session bootstrap
|
|
- dashboard page with live widgets contract
|
|
- discover page with search, filters, pagination behavior
|
|
- watch later page with authenticated add/remove flow
|
|
- continue watching widget actions wired to persisted progress updates
|
|
- dark/light/system theme persistence
|
|
- typed service layer with mock/live API switch
|
|
|
|
Scaffolded but still placeholder pages:
|
|
|
|
- /app/movies
|
|
- /app/shows
|
|
- /app/watched
|
|
- /app/downloads
|
|
- /app/calendar
|
|
- /app/recommendations
|
|
- /app/library
|
|
- /app/collections
|
|
- /app/settings
|
|
- /app/admin
|
|
|
|
## Backend status
|
|
|
|
Implemented:
|
|
|
|
- Gin server with request id, access logging, and recovery middleware
|
|
- health endpoints and readiness checks
|
|
- auth endpoints: register, login, refresh, me
|
|
- JWT access token + persisted refresh sessions
|
|
- live catalog endpoints: dashboard, continue watching, discover, search
|
|
- authenticated watch later endpoints: list, add, remove
|
|
- authenticated progress update endpoint with continue watching refresh
|
|
- persistent catalog schema for media, genres, and discover sections
|
|
- seeded catalog dataset for discover/search/dashboard rails
|
|
- PostgreSQL catalog repository wired into catalog service
|
|
- user_watch_later persistence with per-user ordering
|
|
- user_progress persistence with ordered continue watching reads
|
|
- worker manager with downloader and scanner worker scaffolding
|
|
|
|
Not implemented yet:
|
|
|
|
- write endpoints for watched history, ratings, favorites, and downloads
|
|
- real TMDB synchronization jobs
|
|
- production downloader engine integration
|
|
- scanner-to-library persistence flow
|
|
|
|
## Database status
|
|
|
|
Current migration provides:
|
|
|
|
- users
|
|
- sessions
|
|
- media_items
|
|
- genres
|
|
- media_genres
|
|
- discover_sections
|
|
- discover_section_items
|
|
- user_watch_later
|
|
- user_progress
|
|
|
|
Remaining domain tables are planned below in the Data Model Expansion Plan.
|
|
|
|
---
|
|
|
|
# Current API Surface (v1)
|
|
|
|
## Live endpoints
|
|
|
|
- GET /api/v1/health/live
|
|
- GET /api/v1/health/ready
|
|
- POST /api/v1/auth/register
|
|
- POST /api/v1/auth/login
|
|
- POST /api/v1/auth/refresh
|
|
- GET /api/v1/auth/me
|
|
- GET /api/v1/dashboard
|
|
- GET /api/v1/progress/continue-watching
|
|
- GET /api/v1/discover
|
|
- GET /api/v1/search
|
|
- GET /api/v1/watch-later
|
|
- POST /api/v1/watch-later
|
|
- DELETE /api/v1/watch-later/:mediaId
|
|
- POST /api/v1/progress
|
|
|
|
## Placeholder endpoints
|
|
|
|
- GET /api/v1/movies
|
|
- GET /api/v1/shows
|
|
- GET /api/v1/watched
|
|
- GET /api/v1/watchlist
|
|
- GET /api/v1/progress
|
|
- GET /api/v1/downloads
|
|
- GET /api/v1/calendar
|
|
- GET /api/v1/library
|
|
- GET /api/v1/collections
|
|
- GET /api/v1/settings
|
|
- GET /api/v1/admin
|
|
- GET /api/v1/recommendations
|
|
|
|
---
|
|
|
|
# Phase Roadmap
|
|
|
|
## Phase 2A - Persistent catalog and metadata foundation
|
|
|
|
Goals:
|
|
|
|
- move catalog read models from in-memory seed data to PostgreSQL
|
|
- keep the current frontend API contracts stable
|
|
|
|
Deliverables:
|
|
|
|
- tables for media, genres, and linking tables
|
|
- repository + sqlc queries for catalog reads
|
|
- TMDB sync job for trending/popular/upcoming datasets
|
|
- cache keys and TTL policy for discovery and dashboard aggregates
|
|
|
|
Exit criteria:
|
|
|
|
- /dashboard, /discover, and /search responses come from persisted data
|
|
- API response shape remains backward compatible with current frontend types
|
|
|
|
## Phase 2B - User tracking and watch flows
|
|
|
|
Goals:
|
|
|
|
- enable user-specific state changes from UI interactions
|
|
|
|
Deliverables:
|
|
|
|
- watch later CRUD endpoints
|
|
- watched history writes
|
|
- per-episode progress updates
|
|
- rating and favorites persistence
|
|
|
|
Exit criteria:
|
|
|
|
- users can add/remove watch later items
|
|
- users can mark watched and resume playback positions
|
|
- dashboard widgets reflect real per-user data
|
|
|
|
## Phase 2C - Download manager (real jobs)
|
|
|
|
Goals:
|
|
|
|
- replace placeholder download data with executable jobs
|
|
|
|
Deliverables:
|
|
|
|
- create/list/cancel download endpoints
|
|
- queue persistence + job state transitions
|
|
- worker integration with selected download engine
|
|
- progress events polling contract for UI
|
|
|
|
Exit criteria:
|
|
|
|
- a submitted download moves through queued/downloading/completed or failed
|
|
- dashboard active downloads widget is fully live
|
|
|
|
## Phase 3A - Library and scanner
|
|
|
|
Goals:
|
|
|
|
- connect downloaded/imported files into a browsable library
|
|
|
|
Deliverables:
|
|
|
|
- scanner pipeline: file discovery, parsing, metadata matching, persistence
|
|
- library browse endpoints with filters
|
|
- artwork and metadata hydration strategy
|
|
|
|
Exit criteria:
|
|
|
|
- new media files appear in /library with matched metadata
|
|
- unmatched files are visible in a review queue
|
|
|
|
## Phase 3B - Recommendations and calendar
|
|
|
|
Goals:
|
|
|
|
- move recommendation and calendar from static placeholders to generated data
|
|
|
|
Deliverables:
|
|
|
|
- recommendation scoring job
|
|
- release calendar endpoint with watchlist filters
|
|
- cache snapshot pipeline for fast dashboard reads
|
|
|
|
Exit criteria:
|
|
|
|
- recommendation list and calendar are generated per user profile
|
|
|
|
---
|
|
|
|
# Data Model Expansion Plan
|
|
|
|
Already present (migrations 000001 to 000004):
|
|
|
|
- users
|
|
- sessions
|
|
- media_items
|
|
- genres
|
|
- media_genres
|
|
- discover_sections
|
|
- discover_section_items
|
|
- user_watch_later
|
|
- user_progress
|
|
|
|
Planned next:
|
|
|
|
- media_external_ids
|
|
- show_seasons
|
|
- show_episodes
|
|
- user_watched
|
|
- user_ratings
|
|
- user_favorites
|
|
- collections
|
|
- collection_items
|
|
- download_jobs
|
|
- download_files
|
|
- download_events
|
|
- library_items
|
|
- library_streams
|
|
|
|
Index priorities:
|
|
|
|
- (user_id, updated_at) on user activity tables
|
|
- (media_type, release_date) on media browsing tables
|
|
- (status, created_at) on download_jobs
|
|
- GIN/trigram search index for title/overview search
|
|
|
|
---
|
|
|
|
# Quality Gates
|
|
|
|
Before merging major slices:
|
|
|
|
1. backend unit tests pass (`go test ./...`)
|
|
2. frontend unit tests pass (`npm run test:unit`)
|
|
3. frontend smoke e2e passes (`npm run test:e2e`)
|
|
4. docker compose stack boots and health endpoints are green
|
|
5. no placeholder endpoint is silently reused for production behavior
|
|
|
|
---
|
|
|
|
# Immediate Next Build Order
|
|
|
|
1. Start TMDB sync worker to refresh discover sections in PostgreSQL.
|
|
2. Add `download_jobs` schema + worker state machine for Phase 2C.
|
|
3. Add cached dashboard aggregate reads in DragonflyDB.
|
|
4. Replace static dashboard recommendations with persisted recommendation snapshots.
|
|
5. Add watched history write endpoints and UI hooks.
|
|
|
|
---
|
|
|
|
# Phase 2C Delivery Blueprint (Downloads)
|
|
|
|
## Scope
|
|
|
|
Turn `/api/v1/downloads` from placeholder into a real per-user job system with queue state transitions, progress updates, cancellation, and dashboard integration.
|
|
|
|
## Schema draft
|
|
|
|
`download_jobs`
|
|
|
|
- id (uuid, primary key)
|
|
- user_id (uuid, not null, fk users.id)
|
|
- source_type (text, check in `magnet|torrent|direct|http`)
|
|
- source (text, not null)
|
|
- title (text, not null default '')
|
|
- status (text, check in `queued|preparing|downloading|stalled|retrying|completed|failed|cancelled`)
|
|
- queue_position (integer, nullable)
|
|
- progress_percent (integer, not null default 0, check 0-100)
|
|
- bytes_total (bigint, not null default 0)
|
|
- bytes_downloaded (bigint, not null default 0)
|
|
- download_speed_mbps (double precision, not null default 0)
|
|
- eta_seconds (integer, nullable)
|
|
- engine_job_id (text, nullable, unique)
|
|
- error_message (text, not null default '')
|
|
- retry_count (smallint, not null default 0)
|
|
- created_at (timestamptz, not null default now())
|
|
- updated_at (timestamptz, not null default now())
|
|
- started_at (timestamptz, nullable)
|
|
- completed_at (timestamptz, nullable)
|
|
- cancelled_at (timestamptz, nullable)
|
|
|
|
`download_events`
|
|
|
|
- id (bigserial, primary key)
|
|
- job_id (uuid, not null, fk download_jobs.id on delete cascade)
|
|
- status (text, not null)
|
|
- message (text, not null default '')
|
|
- progress_percent (integer, not null default 0)
|
|
- payload (jsonb, not null default '{}'::jsonb)
|
|
- created_at (timestamptz, not null default now())
|
|
|
|
Indexes:
|
|
|
|
- `(user_id, created_at desc)` on `download_jobs`
|
|
- `(user_id, status, updated_at desc)` on `download_jobs`
|
|
- `(status, created_at asc)` partial where status in (`queued`, `retrying`)
|
|
- `(job_id, created_at desc)` on `download_events`
|
|
|
|
## State machine
|
|
|
|
Transitions:
|
|
|
|
- queued -> preparing
|
|
- preparing -> downloading
|
|
- downloading -> stalled
|
|
- stalled -> retrying
|
|
- retrying -> preparing
|
|
- downloading -> completed
|
|
- downloading -> failed
|
|
- queued|preparing|downloading|stalled|retrying -> cancelled
|
|
|
|
Rules:
|
|
|
|
- progress is monotonic for active states
|
|
- completed implies `progress_percent = 100`
|
|
- cancelled jobs keep historical events and are excluded from active dashboard count
|
|
- retries capped (`retry_count <= 3`) before terminal `failed`
|
|
|
|
## API contract draft
|
|
|
|
`POST /api/v1/downloads`
|
|
|
|
- request body:
|
|
- `sourceType`: `magnet|torrent|direct|http`
|
|
- `source`: non-empty URL/magnet
|
|
- `title`: optional UI label
|
|
- response:
|
|
- `201 Created`
|
|
- `{ "id": "...", "status": "queued", ... }`
|
|
|
|
`GET /api/v1/downloads`
|
|
|
|
- query params:
|
|
- `status` optional
|
|
- `limit` default 20
|
|
- `cursor` optional for pagination
|
|
- response:
|
|
- ordered by newest activity
|
|
- includes progress and timing fields
|
|
|
|
`DELETE /api/v1/downloads/:id`
|
|
|
|
- semantics:
|
|
- soft cancel if job is active
|
|
- no-op if already terminal
|
|
- response:
|
|
- `200 OK` with updated job payload
|
|
|
|
`GET /api/v1/downloads/:id/events`
|
|
|
|
- query params:
|
|
- `after` optional RFC3339 timestamp
|
|
- `limit` default 100
|
|
- response:
|
|
- ordered events for timeline and debugging
|
|
|
|
## Worker split
|
|
|
|
`downloader-dispatcher`:
|
|
|
|
- pulls `queued/retrying` jobs
|
|
- reserves available concurrency slots
|
|
- sends Add call to selected engine
|
|
|
|
`downloader-monitor`:
|
|
|
|
- polls engine status
|
|
- writes progress updates + events
|
|
- finalizes terminal states
|
|
|
|
Runtime defaults:
|
|
|
|
- `DOWNLOAD_MAX_ACTIVE=3`
|
|
- `DOWNLOAD_POLL_INTERVAL=3s`
|
|
- `DOWNLOAD_RETRY_BACKOFF=15s`
|
|
|
|
## Exit criteria for Phase 2C
|
|
|
|
- dashboard `activeDownloads` comes from persisted jobs
|
|
- creating a job from API moves it through real states
|
|
- cancellation works for both queued and active jobs
|
|
- smoke e2e covers submit -> progress -> complete/cancel path
|
|
|
|
---
|
|
|
|
# Phase 3A Delivery Blueprint (Library + Scanner)
|
|
|
|
## Scope
|
|
|
|
Build a durable library index for local media files and expose browse + matching workflows.
|
|
|
|
## Schema draft
|
|
|
|
`library_items`
|
|
|
|
- id (uuid, primary key)
|
|
- media_id (bigint, nullable, fk media_items.id)
|
|
- source_job_id (uuid, nullable, fk download_jobs.id)
|
|
- absolute_path (text, not null, unique)
|
|
- file_name (text, not null)
|
|
- file_size_bytes (bigint, not null default 0)
|
|
- container_format (text, not null default '')
|
|
- video_codec (text, not null default '')
|
|
- audio_codec (text, not null default '')
|
|
- duration_seconds (integer, not null default 0)
|
|
- resolution_label (text, not null default '')
|
|
- match_state (text, check in `matched|unmatched|ignored`)
|
|
- match_confidence (double precision, not null default 0)
|
|
- discovered_at (timestamptz, not null default now())
|
|
- scanned_at (timestamptz, nullable)
|
|
- created_at (timestamptz, not null default now())
|
|
- updated_at (timestamptz, not null default now())
|
|
|
|
`library_streams`
|
|
|
|
- id (uuid, primary key)
|
|
- library_item_id (uuid, not null, fk library_items.id on delete cascade)
|
|
- stream_type (text, check in `video|audio|subtitle`)
|
|
- codec (text, not null default '')
|
|
- language (text, not null default '')
|
|
- channels (smallint, not null default 0)
|
|
- bitrate_kbps (integer, not null default 0)
|
|
- is_default (boolean, not null default false)
|
|
|
|
Indexes:
|
|
|
|
- `(match_state, updated_at desc)` on `library_items`
|
|
- `(media_id, updated_at desc)` on `library_items`
|
|
- trigram index on `file_name` for unmatched review search
|
|
|
|
## Scanner workflow
|
|
|
|
1. discover files under configured library roots
|
|
2. normalize path and deduplicate by `absolute_path`
|
|
3. parse title/season/episode hints from filename
|
|
4. call TMDB search/match pipeline
|
|
5. upsert `library_items` + `library_streams`
|
|
6. mark low-confidence matches as `unmatched`
|
|
|
|
## API contract draft
|
|
|
|
`GET /api/v1/library`
|
|
|
|
- filters:
|
|
- `matchState`
|
|
- `mediaType`
|
|
- `query`
|
|
- `page`, `pageSize`
|
|
- returns enriched cards for UI browse grid
|
|
|
|
`POST /api/v1/library/rescan`
|
|
|
|
- optional body:
|
|
- `paths`: array of roots
|
|
- returns:
|
|
- scan job id + accepted count
|
|
|
|
`GET /api/v1/library/unmatched`
|
|
|
|
- paginated unmatched queue with confidence metadata
|
|
|
|
`POST /api/v1/library/:id/match`
|
|
|
|
- request body:
|
|
- `mediaId`
|
|
- `force` optional bool
|
|
- manually links file to a media entry
|
|
|
|
## Exit criteria for Phase 3A
|
|
|
|
- new files are indexed within one scan cycle
|
|
- unmatched queue is visible and manually resolvable
|
|
- `/api/v1/library` is no longer placeholder
|
|
- scanner worker persists state changes, not heartbeats only
|
|
|
|
---
|
|
|
|
# Cache Key Contract (DragonflyDB)
|
|
|
|
Use explicit versioned keys so invalidation is predictable:
|
|
|
|
- `seen:v1:discover:{query_hash}` (TTL: 10m)
|
|
- `seen:v1:dashboard:{user_id}` (TTL: 2m)
|
|
- `seen:v1:continue-watching:{user_id}` (TTL: 2m)
|
|
- `seen:v1:recommendations:{user_id}` (TTL: 30m)
|
|
- `seen:v1:calendar:{user_id}:{month}` (TTL: 6h)
|
|
|
|
Invalidation triggers:
|
|
|
|
- watch-later change -> invalidate `dashboard` and `recommendations`
|
|
- progress update -> invalidate `continue-watching` and `dashboard`
|
|
- TMDB sync refresh -> invalidate discover keys
|
|
- recommendation snapshot refresh -> invalidate recommendation key
|
|
|
|
---
|
|
|
|
# File-Level Build Plan
|
|
|
|
Backend additions:
|
|
|
|
- `backend/migrations/000005_init_downloads.up.sql`
|
|
- `backend/migrations/000005_init_downloads.down.sql`
|
|
- `backend/migrations/000006_init_library.up.sql`
|
|
- `backend/migrations/000006_init_library.down.sql`
|
|
- `backend/sql/queries/downloads.sql`
|
|
- `backend/sql/queries/library.sql`
|
|
- `backend/internal/repositories/postgres/download_repository.go`
|
|
- `backend/internal/repositories/postgres/library_repository.go`
|
|
- `backend/internal/services/download/service.go`
|
|
- `backend/internal/services/library/service.go`
|
|
- `backend/internal/api/handlers/download_handler.go`
|
|
- `backend/internal/api/handlers/library_handler.go`
|
|
|
|
Frontend additions:
|
|
|
|
- `frontend/src/services/download-service.ts`
|
|
- `frontend/src/services/library-service.ts`
|
|
- `frontend/src/pages/downloads-page.tsx`
|
|
- `frontend/src/pages/library-page.tsx`
|
|
- `frontend/src/pages/calendar-page.tsx`
|
|
- `frontend/src/pages/recommendations-page.tsx`
|
|
|
|
Tests to add:
|
|
|
|
- backend unit tests for download state transitions
|
|
- backend repository tests for scanner/library filtering
|
|
- frontend unit tests for downloads and library stores/services
|
|
- e2e flow covering download create/cancel and library browse
|
|
|
|
---
|
|
|
|
# Updated Build Priority (Next 2 Sprints)
|
|
|
|
Sprint A:
|
|
|
|
1. Implement download schema + SQL queries + repository.
|
|
2. Implement download service state machine + worker loops.
|
|
3. Ship `/api/v1/downloads` endpoints and replace dashboard download mock.
|
|
4. Add tests and smoke e2e for download lifecycle.
|
|
|
|
Sprint B:
|
|
|
|
1. Implement library schema + scanner persistence pipeline.
|
|
2. Ship `/api/v1/library` list/rescan/unmatched/match endpoints.
|
|
3. Replace placeholder library page with real browse + unmatched queue.
|
|
4. Add TMDB sync job and cache invalidation wiring for discover/dashboard.
|