diff --git a/README.md b/README.md index 8b032c0..1e695a6 100644 --- a/README.md +++ b/README.md @@ -70,4 +70,5 @@ docker compose -f compose.yml -f compose.traefik.yml up -d | `NEXT_PUBLIC_API_URL` | frontend build arg | Baked in at build time — must point to the public backend URL | | `API_URL` | frontend runtime env | Server-side only (Next.js API routes). Set in compose. | | `DATABASE_URL` | backend | `sqlite:///app/data/k-tv.db` or postgres DSN | -| `SESSION_SECRET` | backend | Change in production | +| `JWT_SECRET` | backend | JWT signing key — change in production (min 32 chars) | +| `COOKIE_SECRET` | backend | OIDC state cookie encryption key — change in production (min 64 chars) | diff --git a/k-tv-backend/README.md b/k-tv-backend/README.md index 8a99bc0..a4f5cfc 100644 --- a/k-tv-backend/README.md +++ b/k-tv-backend/README.md @@ -11,7 +11,7 @@ The backend is a Cargo workspace with three crates following Hexagonal (Ports & ``` k-tv-backend/ ├── domain/ # Pure business logic — no I/O, no frameworks -├── infra/ # Adapters: SQLite/Postgres repositories, Jellyfin HTTP client +├── infra/ # Adapters: SQLite/Postgres repositories, Jellyfin HTTP client, local files └── api/ # Axum HTTP server — routes, DTOs, startup wiring ``` @@ -79,11 +79,20 @@ OIDC state (CSRF token, PKCE verifier, nonce) is stored in a short-lived encrypt If Jellyfin variables are not set, the server starts normally but schedule generation endpoints return an error. Channel CRUD and auth still work. +### Local Files (optional — requires `local-files` feature) + +| Variable | Default | Description | +|----------|---------|-------------| +| `LOCAL_FILES_DIR` | — | Absolute path to local video library root. Enables the local-files provider when set. | +| `TRANSCODE_DIR` | — | Directory for FFmpeg HLS transcode cache. Enables transcoding when set. | +| `TRANSCODE_CLEANUP_TTL_HOURS` | `24` | Hours after last access before a transcode cache entry is deleted. | + ### CORS & Production | Variable | Default | Description | |----------|---------|-------------| | `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins | +| `BASE_URL` | `http://localhost:3000` | Public base URL used to build stream URLs for local files | | `PRODUCTION` | `false` | Enforces minimum secret lengths when `true` | ## Feature Flags @@ -100,6 +109,7 @@ default = ["sqlite", "auth-jwt", "jellyfin"] | `auth-jwt` | JWT Bearer token authentication | | `auth-oidc` | OpenID Connect integration | | `jellyfin` | Jellyfin media provider adapter | +| `local-files` | Local filesystem media provider with optional FFmpeg transcoding | ## API Reference @@ -137,11 +147,49 @@ All endpoints are under `/api/v1/`. Endpoints marked **Bearer** require an `Auth | `GET` | `/channels/:id/epg?from=&until=` | Bearer | EPG slots overlapping a time window (RFC3339 datetimes) | | `GET` | `/channels/:id/stream` | Bearer | `307` redirect to the current item's stream URL — `204` if no-signal | -### Other +### Library + +All endpoints require Bearer auth and return `501 Not Implemented` if the active provider lacks the relevant capability. | Method | Path | Auth | Description | |--------|------|------|-------------| -| `GET` | `/config` | — | Server configuration flags | +| `GET` | `/library/collections` | Bearer | List media collections/libraries | +| `GET` | `/library/series` | Bearer | List TV series (supports `?collection=`, `?provider=`) | +| `GET` | `/library/genres` | Bearer | List genres (supports `?type=`, `?provider=`) | +| `GET` | `/library/items` | Bearer | Search/filter media items (supports `?q=`, `?type=`, `?series[]=`, `?collection=`, `?limit=`, `?strategy=`, `?provider=`) | + +### Files (local-files feature only) + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| `GET` | `/files/stream/:id` | — | Range-header video streaming for local files | +| `POST` | `/files/rescan` | Bearer | Trigger library rescan, returns `{ items_found }` | +| `GET` | `/files/transcode/:id/playlist.m3u8` | — | HLS transcode playlist | +| `GET` | `/files/transcode/:id/:segment` | — | HLS transcode segment | +| `GET` | `/files/transcode-settings` | Bearer | Get transcode settings (`cleanup_ttl_hours`) | +| `PUT` | `/files/transcode-settings` | Bearer | Update transcode settings | +| `GET` | `/files/transcode-stats` | Bearer | Cache stats `{ cache_size_bytes, item_count }` | +| `DELETE` | `/files/transcode-cache` | Bearer | Clear the transcode cache | + +### IPTV + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| `GET` | `/iptv/playlist.m3u` | `?token=` | M3U playlist of all channels | +| `GET` | `/iptv/epg.xml` | `?token=` | XMLTV EPG for all channels | + +### Admin + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| `GET` | `/admin/logs` | `?token=` | SSE stream of live server log lines (`{ level, target, message, timestamp }`) | +| `GET` | `/admin/activity` | Bearer | Recent 50 in-app activity events | + +### Config + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| `GET` | `/config` | — | Server configuration flags and provider capabilities | ## Examples @@ -267,6 +315,21 @@ curl -s -I http://localhost:3000/api/v1/channels//stream \ ### Channel A named broadcast channel owned by a user. Holds a `schedule_config` (the programming template) and a `recycle_policy`. +Channel fields: + +| Field | Description | +|-------|-------------| +| `access_mode` | `public` / `password_protected` / `account_required` / `owner_only` | +| `access_password` | Hashed password when `access_mode` is `password_protected` | +| `logo` | URL or inline SVG for the watermark overlay | +| `logo_position` | `top_right` (default) / `top_left` / `bottom_left` / `bottom_right` | +| `logo_opacity` | 0.0–1.0, default 1.0 | +| `auto_schedule` | When `true`, the server auto-regenerates the schedule when it expires | +| `webhook_url` | HTTP endpoint called on domain events | +| `webhook_poll_interval_secs` | Polling interval for webhook delivery | +| `webhook_body_template` | Handlebars template for the webhook POST body | +| `webhook_headers` | JSON object of extra HTTP headers sent with webhooks | + ### ScheduleConfig The shareable programming template: an ordered list of `ProgrammingBlock`s. Channels do not need to cover all 24 hours — gaps are valid and produce a no-signal state. @@ -286,6 +349,8 @@ Provider-agnostic filter used by algorithmic blocks: | `tags` | Provider tag strings | | `min_duration_secs` / `max_duration_secs` | Duration bounds for item selection | | `collections` | Abstract groupings (Jellyfin library IDs, Plex sections, folder paths, etc.) | +| `series_names` | List of TV series names (OR-combined) | +| `search_term` | Free-text search term for library browsing | ### FillStrategy How an algorithmic block fills its time budget: @@ -305,6 +370,22 @@ Controls when previously aired items become eligible again: | `cooldown_generations` | Don't replay within this many schedule generations | | `min_available_ratio` | Always keep at least this fraction (0.0–1.0) of the matching pool selectable, even if their cooldown hasn't expired. Prevents small libraries from running dry. | +### ProviderCapabilities + +`GET /config` returns `providers[]` with per-provider capabilities. Library endpoints return `501` if the active provider lacks the relevant capability. + +| Capability | Description | +|------------|-------------| +| `collections` | Provider can list/filter by collections | +| `series` | Provider exposes TV series groupings | +| `genres` | Provider exposes genre metadata | +| `tags` | Provider supports tag filtering | +| `decade` | Provider supports decade filtering | +| `search` | Provider supports free-text search | +| `streaming_protocol` | `hls` or `direct_file` | +| `rescan` | Provider supports triggering a library rescan | +| `transcode` | FFmpeg transcoding is available (local-files only) | + ### No-signal state `GET /channels/:id/now` and `GET /channels/:id/stream` return `204 No Content` when the current time falls in a gap between blocks. The frontend should display static / noise in this case — matching the broadcast TV experience. @@ -338,6 +419,9 @@ cargo build -F sqlite,auth-jwt,auth-oidc,jellyfin # PostgreSQL variant cargo build --no-default-features -F postgres,auth-jwt,jellyfin + +# With local files + transcoding +cargo build -F sqlite,auth-jwt,jellyfin,local-files ``` ### Docker @@ -357,7 +441,8 @@ k-tv-backend/ │ │ # ScheduledSlot, MediaItem, PlaybackRecord, User, ... │ ├── value_objects.rs # MediaFilter, FillStrategy, RecyclePolicy, │ │ # MediaItemId, ContentType, Email, ... -│ ├── ports.rs # IMediaProvider trait +│ ├── ports.rs # IMediaProvider trait, ProviderCapabilities +│ ├── events.rs # Domain event types │ ├── repositories.rs # ChannelRepository, ScheduleRepository, UserRepository │ ├── services.rs # ChannelService, ScheduleEngineService, UserService │ └── errors.rs # DomainError @@ -366,7 +451,9 @@ k-tv-backend/ │ ├── channel_repository.rs # SQLite + Postgres ChannelRepository adapters │ ├── schedule_repository.rs # SQLite + Postgres ScheduleRepository adapters │ ├── user_repository.rs # SQLite + Postgres UserRepository adapters +│ ├── activity_log_repository/ # Activity log persistence │ ├── jellyfin.rs # Jellyfin IMediaProvider adapter +│ ├── local_files/ # Local filesystem provider + FFmpeg transcoder │ ├── auth/ │ │ ├── jwt.rs # JWT create + validate │ │ └── oidc.rs # OIDC flow (stateless cookie state) @@ -376,13 +463,22 @@ k-tv-backend/ ├── api/src/ │ ├── routes/ │ │ ├── auth.rs # /auth/* endpoints -│ │ ├── channels.rs # /channels/* endpoints (CRUD, EPG, broadcast) -│ │ └── config.rs # /config endpoint +│ │ ├── channels/ # /channels/* endpoints (CRUD, EPG, broadcast) +│ │ ├── admin.rs # /admin/logs (SSE), /admin/activity +│ │ ├── config.rs # /config endpoint +│ │ ├── files.rs # /files/* endpoints (local-files feature) +│ │ ├── iptv.rs # /iptv/playlist.m3u, /iptv/epg.xml +│ │ └── library.rs # /library/* endpoints │ ├── config.rs # Config::from_env() │ ├── state.rs # AppState │ ├── extractors.rs # CurrentUser (JWT Bearer extractor) │ ├── error.rs # ApiError → HTTP status mapping │ ├── dto.rs # All request + response types +│ ├── events.rs # SSE event broadcasting +│ ├── log_layer.rs # Tracing layer → SSE log stream +│ ├── poller.rs # Webhook polling task +│ ├── scheduler.rs # Auto-schedule renewal task +│ ├── webhook.rs # Webhook delivery │ └── main.rs # Startup wiring │ ├── migrations_sqlite/ diff --git a/k-tv-frontend/README.md b/k-tv-frontend/README.md index e215bc4..cfbd030 100644 --- a/k-tv-frontend/README.md +++ b/k-tv-frontend/README.md @@ -1,36 +1,41 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# K-TV Frontend -## Getting Started +Next.js 16 / React 19 EPG viewer and channel manager for [k-tv](../README.md) — the self-hosted linear TV orchestration backend. -First, run the development server: +## Quick Start ```bash -npm run dev -# or -yarn dev -# or +cp .env.local.example .env.local +# Edit .env.local — set NEXT_PUBLIC_API_URL to your backend URL pnpm dev -# or -bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +Open `http://localhost:3001` in your browser. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## Environment Variables -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +| Variable | Where | Description | +|----------|-------|-------------| +| `NEXT_PUBLIC_API_URL` | build arg + runtime | Browser-side API base URL. **Baked in at build time** — must point to the public backend URL. Default: `http://localhost:4000/api/v1` | +| `API_URL` | runtime only | Server-side API URL for Next.js API routes (e.g. stream redirect resolver). Falls back to `NEXT_PUBLIC_API_URL` if not set. Use this to set a private backend address in Docker. | -## Learn More +## Routes -To learn more about Next.js, take a look at the following resources: +| Path | Auth | Description | +|------|------|-------------| +| `/tv` | public | TV player — EPG grid, channel switching, HLS/direct video stream | +| `/dashboard` | required | Channel management — create, edit, configure schedule blocks | +| `/admin` | required | Live server log stream + recent activity log | +| `/login` | — | Email/password login | +| `/register` | — | New account registration | -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## Architecture -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +- **`lib/api.ts`** — typed fetch client wrapping all backend endpoints (`api.auth.*`, `api.channels.*`, `api.schedule.*`, `api.library.*`) +- **`hooks/`** — TanStack Query v5 hooks for all data fetching and mutations; components never fetch directly +- **`context/auth-context.tsx`** — JWT stored in localStorage, `isLoaded` flag prevents flash redirects +- Components are props-only; all business logic lives in hooks -## Deploy on Vercel +## Docker -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +See the [root README](../README.md) for build and deploy instructions.