Files
SEEN/project.md
T
2026-04-10 12:06:24 +02:00

22 KiB

/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

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.