mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
4e0d669dafa4c795a722b8798d20c0bd030054f6
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b88a1c81e5 |
feat(self-hosted): server storage API in web-backend (#1898)
* feat(interfaces): add the server storage HTTP contract Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web-backend): read server storage configuration Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web-backend): open the server database and track revisions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web-backend): playlist and user-state repositories Playlist documents use the shared desktop row codec, so the server writes the same rows. keepPayload upserts take the stored playlist/items/header fields when the client leaves them out. Xtream user data lives in the server-only server_user_state table, one JSON value per namespace and playlist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web-backend): storage error mapping and access token guard Storage errors map to JSON status bodies and are logged by name and code only. An optional bearer token guards every route except /, /health, /config.js and /v1/instance, provider proxy routes included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web-backend): serve the /v1 server storage API /v1/instance is always public and reports whether server storage is on. With storage, /v1/playlists serves whole documents with ETags and 304s, accepts keepPayload upserts, and deletes playlists with their user state; /v1/user-state and /v1/app-state store the rest. The proxy routes are unchanged unless an access token is configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(web-backend): open server storage on start and close on shutdown With IPTVNATOR_STORAGE=server, main.ts opens the database before it listens and exits with a clear message when the data directory is not writable. SIGTERM and SIGINT close the server and the database. The native better-sqlite3 module stays outside the esbuild bundle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-backend): match weakened ETags on playlist reads nginx weakens the ETag of a gzipped response, and clients send the weak form back in If-None-Match. The route now compares tags weakly, as RFC 9110 requires for GET, so it answers 304 before reading the document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(docker): persist server storage on a volume The image builds better-sqlite3 in a native stage per target platform, enables server storage on the /data volume, and raises the nginx body limit to 64 MB for /api with gzip for JSON. The compose file mounts a named volume and documents the optional access token. The PWA does not use the storage yet; docs say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(web-backend): type the storage route responses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-backend): harden server storage reads, deletes and app state - Metadata reads return the stored document without its payload fields, so fields that live only in the JSON survive a metadata round-trip. - A conditional GET checks that the playlist exists before answering 304. - Deleting a playlist and its user state commits in one transaction; the ETag moves only after the commit. - Browser app state is stored under a client: prefix and the instance id is repaired when empty, so a client write cannot stop the backend. - User-state buckets have no prototype, so ids like __proto__ round-trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(self-hosted): keep the access token unset until the PWA sends it Add the release note for the Docker server storage volume. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-backend): bound shutdown and reject padded playlist ids - SIGTERM closes idle connections at once and cuts the rest after five seconds, so a long provider download cannot keep the backend running. - Playlist ids with surrounding whitespace are rejected: the row codec trims ids, so storage and ETags would track different keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-backend): clear optional columns on whole-document upserts The row codec maps empty values to undefined, which ON CONFLICT DO UPDATE skips, so a cleared User-Agent, Referrer or credential kept its old column value and came back on the next read. Server upserts now write NULL to every replaceable column the document no longer sets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(self-hosted): linear token parsing, no-store app state, longer start - The access guard reads the Bearer token by string scanning instead of a regular expression that CodeQL flagged as polynomial on long whitespace. - GET /v1/app-state/:key sends Cache-Control: no-store like the other state routes, so devices never reuse a stale value. - The entrypoint waits up to IPTVNATOR_BACKEND_START_TIMEOUT (300 s) for /health, because the backend migrates its database before it listens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(self-hosted): metadata reads skip payloads, redact JSON parse warnings List playlist metadata from a server-only payload-free copy of each document instead of running json_remove over every payload blob, and log only the error name when a stored JSON value fails to parse, since the parser's message quotes the stored text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-backend): copy carried-over panel clock into playlist metadata When an upsert keeps the stored serverTimezone, build the metadata copy from the saved payload so metadata and full reads agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
94570bc387 |
refactor(storage): seams for server-backed playlist storage (#1896)
* docs(storage): server storage design and implementation plans Design for keeping playlists and user data on the self-hosted server (#305), revised against master, plus three implementation plans: storage seams, the web-backend storage API, and the PWA server mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(database): share the playlist row codec Move buildPlaylistRow, parseAppPlaylist and the upsert conflict helpers from the Electron database operations into libs/shared/database (@iptvnator/shared/database/app-playlist) so the self-hosted web backend can write the same rows. The Electron modules re-export them; behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(database): read the data root from IPTVNATOR_DATA_DIR Deployments that keep the database on a mounted volume, such as the self-hosted web backend in Docker, can now set the data root. The E2E override still wins; without either variable the root stays ~/.iptvnator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(services): add the playlist storage backend resolver PlaylistStorageBackend becomes the single place that decides whether playlists persist through the Electron bridge or IndexedDB, and defines the PlaylistDocumentStore contract a server store will implement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(services): resolve playlist storage through one backend PlaylistsService asks PlaylistStorageBackend for its document store instead of reading window.electron and supportsSqlite directly. Paths that only make sense on the desktop (legacy IndexedDB import, worker deletion with progress, the favorites-channel query) check for the Electron kind explicitly. Behavior is unchanged: the backend still resolves to the Electron bridge or IndexedDB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(xtream): store PWA user data through USER_DATA_STORAGE PwaXtreamDataSource and PwaCollectionStorage read and write favorites, recently viewed items, collection snapshots and playback positions through the new USER_DATA_STORAGE token (default: localStorage) instead of calling localStorage directly, so a deployment can substitute a server-synced storage. Behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(pwa): document the storage seams Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(database): bracket access in the shared playlist codec libs/shared/database compiles with noPropertyAccessFromIndexSignature, which the Electron backend did not enforce on the moved code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(electron): alias the shared playlist codec in the worker bundle The database worker is bundled by esbuild with an explicit alias table; without the new entry, @iptvnator/shared/database/app-playlist resolved under the library's index file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(services): read localStorage lazily in USER_DATA_STORAGE When the browser denies storage access, reading localStorage itself throws. The default factory returned the global, so injecting the token failed and PwaXtreamDataSource could not be constructed. The adapter now reads the global inside each call, where the data source already guards its reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(database): describe /data as an example data root The Docker image does not set IPTVNATOR_DATA_DIR yet; that arrives with the server storage backend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |