* fix(web): scope realtime user state events * feat(events): add canonical catalog event publishers * feat(events): publish canonical catalog events * refactor(web): centralize realtime events provider * feat(events): normalize user state event name * feat(web): patch item user state from realtime events * fix(web): refetch active catalog on realtime changes * fix(events): publish item changes during metadata enrichment * fix(web): improve dashboard and mutation reactivity * feat(admin): improve realtime session activity * feat(admin): refine playback admin surfaces * feat(admin): improve library task controls * fix(collections): position defaults progress below header * feat(library): surface matcher backlog * fix(admin): hide matcher backlog from server activity * chore(migrations): renumber branch migrations * feat(admin): show registered devices without overrides * feat(admin): improve scheduled task visibility * fix(realtime): tighten admin update handling * docs(admin): document library job id parsing * docs(library): explain mount check feedback timing * fix(library): guard metadata match queue handlers * fix(admin): avoid stale queued job cancellation * fix(settings): harden device registration and task timing * fix(jellycompat): fill large browse pages * perf(jellycompat): compress and batch list image work * feat(autoscan): pluggable scan-source autoscan category (Sonarr/Radarr) (#44) * docs: design spec for autoscan arr polling Periodic poller over autoscan-enabled Radarr/Sonarr instances (reusing request_integrations) that maps import paths to Silo media folders and enqueues targeted scans via the existing scantrigger + scanqueue. Lean single-service model: no cross-node fan-out guard or retry queue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for autoscan arr polling Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): settings and sources schema Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): core types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): path rewrite helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): dedupe imported paths to parent folders Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): arr import-history client Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): settings + sources repository Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): redis scan-suppression seam Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): PollOnce poll cycle * feat(autoscan): poll task and wiring * feat(autoscan): admin API endpoints * feat(autoscan): admin API endpoints Adds ErrIntegrationNotFound sentinel (errors.Is) instead of string matching. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): autoscan types and hooks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(web): autoscan admin tab * fix(autoscan): release suppression claim on enqueue failure; reconfigure trigger on interval change; skip source on key-resolution error Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): update handler test for 3-arg NewAutoscanHandler * fix(autoscan): per-path suppression key, bounded poll window + overlap, boundary-safe rewrites, GREATEST cursor guard, async trigger, quiet unresolved-path skip, FK->404 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): normalize Windows path separators, surface status errors, re-seed source editor on save Addresses minor code-review findings: Windows backslash paths now normalized before rewrite/dedupe; HandleStatus returns repository errors instead of 200; the per-source editor re-seeds from server data after a save. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: design spec for autoscan rewrite-sync from arr root folders Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for autoscan rewrite-sync Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): suffix-match rewrite suggester Add suggestRewrites / commonSuffixLen for Task 1 of the autoscan arr-polling feature. Pure function: matches arr root-folder paths to Silo media folder paths by longest common trailing segment count, adjusted for depth-delta so coincidental same-named segments at different structural levels don't inflate confidence. Categorises each arr root as Proposed, Ambiguous, Unmatched, or Covered by an existing PathRewrite rule. TDD: test file written first, verified failing, then implementation added. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): GetSource single-source lookup * feat(autoscan): arr root-folder client + Silo folder lister * feat(autoscan): Service.SuggestRewrites Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): rewrite-suggestions endpoint Add GET /autoscan/sources/{id}/rewrite-suggestions admin endpoint: extend the autoscanTriggerer interface with SuggestRewrites, wire SetRewriteResolvers in the router, and add handler + test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(web): autoscan rewrite-suggestions types and hook * feat(web): autoscan sync-rewrites preview * fix(autoscan): normalize covered-rule paths, dedup roots/folders, skip no-op suggestions Addresses final-review edge cases: coveredBy normalizes the existing rewrite's From (so a stored Windows/dup-slash rule still covers a root); duplicate arr roots and duplicate Silo folder paths are de-duplicated; an arr path that already equals its Silo path is not proposed as a no-op rewrite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): vitest 4 compatible fetch spy in recipes.test (unblocks build after vitest 4.1.0 bump) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): non-null suggestion slices + move Sync into rewrites card - suggestRewrites initializes Proposed/Unmatched/Ambiguous/Covered to empty slices so the JSON response is [] not null — fixes the 'Something went wrong' crash when every root is already covered (frontend mapped over null). - Move the sync button into the Path rewrites card beside 'Add rewrite' and rename it 'Sync rewrites'; guard the proposed map with ?? []. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): long root-folder timeout + sync spinner + collapse rewrites on load - Root-folder fetch for sync uses a 2-min timeout: Radarr/Sonarr compute unmappedFolders by scanning all roots, so a large library's /rootfolder takes 20-30s+ and tripped arrclient's 30s default (Sonarr 502'd at exactly 30s). - Spin the sync icon + show 'Syncing…' while the request is in flight. - Path rewrites card starts collapsed on page load. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): rescan on Sonarr/Radarr file renames History polling previously only tracked downloadFolderImported events. A rename in Sonarr/Radarr (episodeFileRenamed / movieFileRenamed) moves a file without an import event, leaving the library folder stale until the next full scan. Extend the history client to also surface renamed paths: both the new path and the old sourcePath, since a rename can move a file between folders and both parents may need rescanning. Delete events are still skipped — upgrade-deletes are covered by the paired import, and standalone deletes carry no file path in arr history. Renames the interface method ImportedPaths -> ChangedPaths to reflect the broader scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(autoscan): synchronize trigger test with detached PollOnce goroutine HandleTrigger dispatches PollOnce on a detached goroutine and responds 202 immediately. The test read trig.called straight after the handler returned, racing the goroutine (usually 'PollOnce was not invoked') and reading the field without synchronization (a data race under -race). Signal completion through a channel the fake sends on when PollOnce runs; the test waits on it (bounded) before asserting. The channel send happens-before the receive, so the subsequent read of called is race-free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: design spec for autoscan as a pluggable scan-source category Reframes autoscan from a Requests-coupled, arr-only feature into a standalone Autoscan category. Change-detection providers become out-of-process plugins via a new additive scan_source.v1 capability (client-pull, opaque marker); Sonarr/Radarr is the first provider. Host keeps a provider-agnostic resolve/suppress/enqueue engine; all arr-specific logic (and path rewrites) move into the plugin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for scan_source.v1 SDK capability First of the per-repo plans from the autoscan-plugin-architecture spec. Adds the additive scan_source.v1 capability to silo-plugin-sdk (proto + codegen + capability allowlist + runtime wiring), TDD per task, tagged as v0.5.0 so the host and arr-plugin plans can build against it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for autoscan host backend (part 1 of 2) Backend for the standalone Autoscan category: scan_source.v1 plugin plumbing (pluginhost client + plugins.Service resolver), generalized engine driven by a provider seam, autoscan_connections + autoscan_sources schema (decoupled from Requests), connection resolution (own or Requests-linked), admin API. Depends on silo-plugin-sdk v0.5.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plans for autoscan arr plugin + host UI arr plugin: new installable scan_source.v1 plugin (history imports+renames, rewrites, Silo-native paths), structured like silo-plugin-tmdb; ports the arr-specific logic from the closed PR #43. host UI (part 2 of 2): standalone Autoscan admin category (connections, sources, settings) extracted out of Requests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(autoscan): replace silo-plugin-sdk with local scan_source.v1 checkout Temporary dev replace so the host backend can build against the unreleased scan_source.v1 capability (silo-plugin-sdk PR #2). Finalize to v0.5.0 once the SDK is tagged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(pluginhost): scan_source.v1 capability client wrapper Adds ScanSourceClient struct, the Client.ScanSource() accessor (mirrors ScheduledTask pattern), and a PollChanges method. Also introduces client_test.go with capability-gate tests for both scheduled_task.v1 and scan_source.v1 using a lazy gRPC ClientConn. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test+fix(pluginhost): cover capability-id gate, dedicated scan_source timeout Adds a "wrong id returns error" subtest to both capability-gate tests so the capability-ID component is exercised independently of the type. Introduces DefaultScanSourceTimeout (2m) for PollChanges, which polls an external arr API that can be slow, instead of the generic 10s control timeout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(plugins): expose scan_source.v1 client resolver Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(migrations): autoscan v2 schema (connections + sources, no requests FK) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): v2 types and repository Replace the request_integrations-coupled model with the decoupled v2 schema (autoscan_settings + autoscan_connections + autoscan_sources). Connection CRUD, source upsert/list/get, and AdvanceMarker/RecordError for opaque marker bookkeeping. ErrIntegrationNotFound becomes ErrNotFound. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): resolve connections (own credentials or Requests-linked) ConnectionResolver turns a stored Connection into concrete credentials, reading a soft-linked Requests integration's live base URL/key when RequestIntegrationID is set, then resolving the api-key ref to plaintext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): scan-source provider seam over the plugin resolver ScanSourceProvider lets the engine poll changed paths without a live plugin; pluginProvider adapts plugins.Service.ScanSourceClient in production. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): generic engine drives sources via scan_source provider Rewrite PollOnce to iterate enabled sources, resolve each connection, poll the provider for changed paths, and run the salvaged resolve→suppress→enqueue loop (uniqueParentDirs, (folder,path) suppression key, RequestError quiet-skip, release-claims-on-enqueue-fail) verbatim. Store the opaque next marker via AdvanceMarker only after a successful enqueue; RecordError + keep marker on provider failure. Tests reworked onto a fakeProvider/fakeStore with an added opaque-marker-verbatim assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): drop conflicting connection CHECK, add connection resolver tests - migration 172: remove the autoscan_connections_source_present CHECK. It conflicted with request_integration_id ON DELETE SET NULL: deleting a Requests integration that a linked-only connection (base_url NULL) points at would null the FK and trip the CHECK, blocking the delete. The intended behavior is for the connection to survive as an orphaned 'needs attention' row. Creation-time validity is now enforced at the application layer. Verified on a throwaway DB: full chain applies and the delete-cascade leaves an orphaned (both-null) connection. - connection.go: TrimSpace the api key ref + resolved secret before the empty-string checks, matching requests.resolveAPIKey parity. - connection_test.go: fake-based tests for ConnectionResolver.Resolve (own creds, linked, linked-missing error, lookup error, trim/fallback). - repository.go: bound RecordError's stored last_error to 2048 chars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(api): autoscan v2 admin endpoints Rewrite the autoscan admin HTTP handler against the v2 model: settings, connection CRUD, source update, manual trigger (detached PollOnce), and status. Connection/source responses omit api_key_ref and resolved keys (has_api_key flag only); unknown connection/source ids map to 404 via autoscan.ErrNotFound. Retire the host-side rewrite-suggestions endpoint (now lives in the arr plugin). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): wire v2 service, routes, retire rewrite-suggestions Export PollChangesClient/ScanSourceResolver from the autoscan provider so the api package can declare a structurally-conformant plugin adapter (Go has no return-type covariance, so the adapter must name the interface as its return type). Add api.BuildAutoscanService with the requests-integration lookup and plugin scan-source adapters, shared by the router (manual trigger) and the background poll task. Re-wire router routes to the v2 connections/sources/settings/trigger/status surface and drop the rewrite-suggestions route. Update cmd/silo to build the v2 poll task, seeding its interval from Settings.DefaultPollIntervalSeconds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): enforce connection requires own URL or a Requests link Migration 172 dropped the DB CHECK that required an autoscan connection to carry either its own base_url or a request_integration_id, delegating that invariant to the application layer — but the enforcement was never added, so HandleCreateConnection/HandleUpdateConnection accepted both-NULL orphans that ConnectionResolver.Resolve would hand a plugin as an empty base URL. Add a shared validateConnectionInput helper (whitespace-only request_integration_id counts as absent) and reject both-empty payloads with HTTP 400 on both the create and update paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): deliver resolved connection to plugin PollChanges now populates PollChangesRequest.Connection with the resolved {base_url, api_key} instead of dropping the conn param on the floor. Drops the stale doc comment claiming the connection was delivered out-of-band at upsert time -- that mechanism never existed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): auto-discover sources from installed scan_source plugins Auto-discovery seeds a disabled, connection-less source row per installed scan_source.v1 capability before an operator binds a connection, so connection_id is now nullable end to end: - migration 172: connection_id drops NOT NULL (still ON DELETE RESTRICT) - Source.ConnectionID becomes *string; repository scans/writes it as nullable and adds idempotent EnsureSource (INSERT ... ON CONFLICT DO NOTHING) - new ScanSourceLister seam + Service.DiscoverSources, called at the start of PollOnce (errors logged, non-fatal); production adapter enumerates ListEnabled -> ListCapabilities filtered to scan_source.v1 - PollOnce skips an enabled source with no connection bound, recording 'no connection bound' so the UI can surface it - HandleUpdateSource rejects enabling a source with no effective connection (400); source DTOs expose connection_id as nullable - BuildAutoscanService / NewService thread the installation store at both wiring sites (router + poll task) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): honor per-source poll interval PollOnce now skips an enabled source that ran too recently: the floor is source.PollIntervalSeconds when set, else settings.DefaultPollIntervalSeconds. The global poll task fires at the default cadence, so this makes the per-source interval a 'poll at most every N seconds' floor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(autoscan): reconcile spec + arr-plugin plan with credential-in-request + auto-discovery The credential-delivery mechanism changed during execution: the host now passes resolved {base_url, api_key} in PollChangesRequest.connection each poll (not plugin runtime config). Also records source auto-discovery, nullable connection_id, and the per-source interval floor decided at the final integration review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): autoscan v2 types and query hooks Replace v1 autoscan types and hooks with v2 DTOs matching the backend handler (autoscan.go): settings, connection (with has_api_key, no raw key), source (installation_id/capability_id/connection_id), status. Add connections CRUD hooks, useAutoscanStatus, update sources hook to v2 input shape. Retain deprecated shims for AutoscanPathRewrite, AutoscanRewriteSuggestions, and useAutoscanRewriteSuggestions so AdminRequests.tsx continues to compile until Task 6 removes that tab. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): autoscan connections panel (reuse or own) Card+Table listing connections with "Reused from Requests" / "Own" badges. Add/edit dialog with two modes: reuse a Sonarr/Radarr Requests integration or enter own name/URL/API-key credentials. Delete with alert-dialog confirm. Never renders key material — only has_api_key is sent by the backend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): autoscan sources panel Table of auto-discovered scan sources (one row per installed scan_source plugin capability). Operator can bind a connection via inline Select (auto-saved on change), set a per-source poll interval (saved on blur), and toggle enabled. Shows a "Needs connection" badge for unbound sources; attempting to enable without a connection lets the backend 400 surface via the existing toast in useUpdateAutoscanSource.onError. Status column shows last_run_at relative time or last_error with icon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): standalone Autoscan admin page Tabs page (Sources | Connections | Settings) mirroring AdminRequests header/layout. Settings tab exposes global enable switch, default poll interval, and debounce — all auto-saved on blur or toggle. "Run now" button calls useTriggerAutoscan and toasts "Autoscan triggered" on 202. Route and sidebar nav are intentionally deferred to Task 5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): route and sidebar nav for Autoscan category Add /admin/autoscan route pointing to AdminAutoscan and a matching "Autoscan" item in the Content group of the admin sidebar (with RefreshCw icon), so the new standalone page is reachable from the nav. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): move Autoscan out of Requests into its own category Remove the Autoscan tab, AutoscanTab/AutoscanSourceEditor component definitions, and AutoscanSettingsFormState from AdminRequests.tsx. Delete the Task-1 compatibility stubs: AutoscanPathRewrite and AutoscanRewriteSuggestions types from api/types.ts, and the useAutoscanRewriteSuggestions no-op shim from useAutoscan.ts. The build confirms zero dangling references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): allow unbinding a source connection (full-state source update) Change the source-update input struct's connection_id from string to *string so the UI can send null to unbind, a UUID to bind, or omit (null) to clear. Remove the fall-back-to-existing logic; the handler now sets the source's ConnectionID directly from the input. The enable-guard fires when the resulting connection is nil regardless of cause. Frontend sends the complete triple (connection_id, enabled, poll_interval_seconds) on every mutation site; selecting "— No connection —" sends null for a real unbind. Adds aria-label to connection Select and interval Input for accessibility. Backend tests cover bind, unbind, unbind while enabled → 400, and enable without connection → 400. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(migrations): backfill autoscan v1 settings+connections instead of dropping Migration 172 unconditionally DROPped the shipped v1 autoscan_settings/ autoscan_sources (migration 171), losing an upgraded operator's enable flag, poll cadence, debounce, and arr server list — autoscan came back OFF. Rewrite 172 up to be non-destructive of what can be carried: rename the v1 tables aside, create the v2 schema, backfill settings (poll minutes -> seconds) and seed a reusable LINKED connection per distinct v1 source integration, then drop the renamed v1 tables. v2 sources are keyed on a plugin (installation_id, capability_id) that did not exist in v1, so they are left to runtime discovery; path rewrites move to plugin config and are intentionally not carried. Verified against a throwaway DB: after 171 + v1 seed data, applying 172 yields enabled=true, default_poll_interval_seconds=300, debounce_seconds=30, and one autoscan_connections row linked to the v1 integration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): preserve api key on metadata-only connection edit UpdateConnection unconditionally wrote api_key_ref = nullable(c.APIKeyRef), so a metadata-only edit (the UI omits the key when left blank — "leave blank to keep existing") NULLed the stored key and broke the next poll. Mirror requests' UpdateIntegration: api_key_ref = CASE WHEN $5 = '' THEN api_key_ref ELSE $5 END, passing the raw trimmed string so a blank incoming ref keeps the existing value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): skip orphaned sources + add source delete endpoint An enabled source whose scan_source plugin was uninstalled/disabled kept its autoscan_sources row, which errored every poll cycle, and there was no way to remove it. DiscoverSources now returns the set of currently-discovered (installation_id, capability_id) pairs; PollOnce skips any enabled source not in that set quietly (no RecordError), stopping the per-cycle error spam for orphans. A nil set (no lister / discovery failed) disables pruning so a transient discovery failure does not silence live sources. Adds DELETE /admin/autoscan/sources/{id} -> HandleDeleteSource -> repo.DeleteSource so an operator can clear orphans (unknown id -> 404). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): reject reused connection when Requests integration is disabled RequestIntegrationLookup.Get returned a linked integration's base_url/api_key even when the integration was disabled or had a blank base_url (the v1 poll gate `WHERE ri.enabled = true` was dropped in v2). Now Get surfaces a disabled or unconfigured linked integration as an error, which the engine turns into a logged skip / RecordError instead of polling an unusable target. The gating is extracted into a pure checkRequestIntegrationUsable helper so it is unit-testable without a DB-backed repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): reschedule poll task on settings change HandleUpdateSettings no longer rescheduled the poll task (the v1 triggerUpdater / UpdateTriggers wiring was dropped in v2), so a default_poll_interval_seconds change only applied after a restart. Re-add an optional triggerUpdater (taskmanager.UpdateTriggers) on AutoscanHandler, wired via SetTriggerUpdater from the router when a task manager is available. On a successful settings update the handler recomputes the interval trigger from default_poll_interval_seconds and calls UpdateTriggers("autoscan_poll", ...). The dependency is optional: a nil updater skips rescheduling so tests need no task manager, and a reschedule failure is non-fatal (the interval is persisted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): disable enable toggle for unbound sources, add source delete + interval hint - Disable the Enable switch when a source has no effective bound connection (connection_id null and no pending edit selection), re-enabling once bound. - Add useDeleteAutoscanSource hook mirroring useDeleteAutoscanConnection pattern. - Add per-row delete button (Trash2 icon → AlertDialog confirm) to let operators remove orphaned/unwanted source rows. - Add interval floor helper text showing the global default poll interval so operators know values below it have no effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): consume source_paths from merged scan_source contract The merged plugin SDK renamed PollChangesResponse.changed_paths to source_paths and the plugin now returns RAW source-namespace paths. pluginProvider.PollChanges reads GetSourcePaths(); the host applies per-source path rewrites before resolving/enqueueing (separate commit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(migrations): add path_rewrites to autoscan_sources Add path_rewrites jsonb NOT NULL DEFAULT '[]' to the autoscan_sources CREATE in migration 172 (unreleased/branch-only, so amended in place). The host now owns per-source prefix rewrites. v1 path_rewrites cannot be backfilled (v2 sources key on a plugin installation/capability with no v1 mapping); documented that operators must re-enter rewrites post-upgrade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): host-owned per-source path rewrites Rewrite ownership moved from the scan_source plugin to the host. The plugin returns raw source-namespace paths; the host now normalizes separators and applies the source's per-source prefix rewrites before dedupe/resolve/enqueue. - types: add PathRewrite{From,To} and Source.PathRewrites - rewrite: re-add applyRewrites/normalizeSeparators; apply the MOST-SPECIFIC (longest From) match, not first-match, so a broad rule can't shadow a nested one regardless of ordering - service.PollOnce: rewrite raw provider paths before resolveAndClaim - repository: marshal/unmarshal path_rewrites jsonb in UpsertSource and all source scans (EnsureSource discovery rows take the DB default []) - handlers: autoscanSourceInput/response + status DTO carry path_rewrites (full-state like connection_id); reject blank from/to with 400 - tests: rewrite unit tests, engine applies rewrites before enqueue, handler round-trips path_rewrites and 400s on a blank rewrite Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): discover installed scan_source plugins on sources-list view A scan_source plugin installed via the normal /admin/plugins flow must show up in the Autoscan component immediately, not only after a poll cycle (which runs only when autoscan is enabled). HandleListSources now runs discovery (seeding a disabled, connection-less source row per installed scan_source capability) before listing. Best-effort: discovery failure does not block listing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): per-source path rewrites editor + plugins-page install hint Add AutoscanPathRewrite type and path_rewrites fields to AutoscanSource/ AutoscanSourceInput. SourcesPanel gains an expandable rewrite editor per source row (from→to pairs, Add/Remove/Save) threaded into the full-state body so connection, interval, and rewrite changes always carry all fields. Adds a Plugins-page install hint in both the empty state and above the table for discoverability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(autoscan): host-owned path rewrites + install/discovery flow Reconcile the spec with the merged SDK decision (rewrites moved host-side; PollChangesResponse.source_paths carries raw provider paths). Document that scan-source plugins install via the normal /admin/plugins page and surface in Autoscan via discovery (run on poll cycles and on sources-list view). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build: depend on merged silo-plugin-sdk via pseudo-version (drop local replace) PR #2 (scan_source.v1 + source_paths) is merged to silo-plugin-sdk main, so the host can resolve the canonical module at the merged commit (v0.4.1-0.20260603030807-807b07e785b2) instead of a local-path replace. The branch now builds off-machine (CI/Docker). Bump to a clean v0.5.0 once tagged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(migrations): single clean autoscan v2 migration (v1 never shipped) The v1 in-process autoscan (migration 171) was never released to origin/main, so no live system has v1 autoscan data to preserve. Collapse the v1-create + v2-rename/backfill/drop dance into one clean 171 that creates the v2 connections-based schema directly. Removes 172 entirely. The runner applies by version set-difference with no checksum validation, so the already-migrated test instance (171+172 recorded) skips both and is unaffected; fresh installs get the clean v2 schema in one step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): allow many sources per plugin + add-source enumeration Drop the one-source-per-(installation, capability) model. A single installed scan_source plugin capability can now back many sources, each bound to a different connection (e.g. one Sonarr plugin fronting four arr servers). - migration 171: remove the autoscan_sources UNIQUE(installation_id, capability_id) constraint; sources are operator-created, not auto-seeded. - repository: replace UpsertSource (relied on the unique conflict) with a plain CreateSource (fresh uuid) + a by-id UpdateSource; remove EnsureSource. - discovery: replace auto-seeding (DiscoverSources/RefreshDiscovered) with ListAvailableScanSources (the Add-source picker list, enriched with plugin id + display name) and an installedScanSources set used only for orphan-skip. - service: PollOnce stops seeding and instead fetches the installed-capability set for orphan detection; Store gains GetSource and drops EnsureSource. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): connection test endpoint (engine) Add Service.TestConnection / TestConnectionByID: resolve a connection (ad-hoc input or an existing stored connection) to concrete credentials and probe the arr GET /api/v3/system/status with a short timeout. A reachable/authorized target yields OK=true plus the reported version; an unreachable / 401 / non-200 target yields OK=false with a human-readable error (the probe failure is part of the result payload, never an error from the method itself). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): host-side rewrite suggester + admin API for new endpoints Port the path-rewrite suggester back host-side (it had moved into the plugin): suggestRewrites suffix-matches arr root folders against Silo media folders to propose path rewrites, reporting proposed / unmatched / ambiguous / covered. Service.SuggestRewrites resolves the source's bound connection, lists arr roots (GET /api/v3/rootfolder) and Silo folder paths, and runs the matcher; a source with no bound connection returns ErrNoConnection (400). Admin API (all admin-gated): - POST /admin/autoscan/sources create a source - GET /admin/autoscan/scan-source-plugins Add-source picker list - POST /admin/autoscan/connections/test probe a connection - GET /admin/autoscan/sources/{id}/rewrite-suggestions sync rewrites HandleListSources no longer auto-seeds; create validates the capability is currently installed and that enabling requires a connection. Wiring threads the arr root-folder/status client and the catalog folder lister through BuildAutoscanService; the lister now surfaces plugin id + display name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): autoscan hooks + types for sources, connection test, rewrites Add types and React Query hooks backing the autoscan admin UI batch: - AutoscanAvailableSource / useAvailableScanSources (scan-source plugins) - AutoscanSourceCreateInput / useCreateAutoscanSource (POST sources) - AutoscanConnectionTestResult / useTestAutoscanConnection (advisory test) - AutoscanRewriteSuggestions / useAutoscanRewriteSuggestions (on-demand) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): add-source dialog + sync-from-arr rewrites in SourcesPanel Add a "+ Add source" header action opening a dialog that creates a scan source from any installed scan-source plugin bound to an arr connection, so operators can add one source per connection (e.g. four arr instances). Empty state links to /admin/plugins when no plugins are installed. Add a "Sync from arr" button to each source's rewrite editor that fetches root-folder rewrite suggestions and renders a preview: checkbox-selectable Proposed rewrites plus collapsed Unmatched / Ambiguous / Already-mapped sections. "Apply selected" merges the checked rewrites (dedupe by `from`) and persists via the normal full-state source PUT. Sync is disabled until the source has a bound connection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): test-connection button in autoscan ConnectionsPanel dialog Add an advisory "Test connection" button to the add/edit connection dialog. It probes the current dialog input — connection_id when editing, request_integration_id in reuse mode, or base_url/api_key_ref for own credentials — and renders the result inline: green "Connected (vX.Y)" on success, red error on failure. Never blocks save; stale results clear when credential fields change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): autoscan page polish + global enable toggle in header Surface a global Autoscan enable toggle and an enabled/disabled status badge next to the page title, alongside the existing "Run now" header action so primary controls are reachable without opening a tab. Remove the now-redundant enable switch from the Settings tab (it points at the header toggle instead). Tighten header layout for wrap on narrow widths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): right-align autoscan enable toggle + Run now in the page header Drop the redundant nested justify-between wrapper so the header actions sit directly under .page-header (space-between + bottom-align), matching the /admin/libraries header layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): hold poll marker when paths return but none resolve A freshly-enabled source whose path_rewrites aren't configured yet returns provider paths that resolve to zero library folders. PollOnce previously advanced the marker unconditionally on any successful poll, permanently skipping those imports. Now the marker advances only when there is nothing to do (zero paths) or at least one path resolved+enqueued; when paths come back but none resolve, the marker is held and an explaining error recorded so the operator can fix the rewrites and a later poll re-reads the same window. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): don't prune sources of disabled-but-installed plugins PluginScanSourceLister used the installation store's ListEnabled, so a temporarily-disabled plugin dropped out of the discovered set and PollOnce treated its sources as orphaned, skipping them with no last_error (silent vanish). Switch to List so only a fully-uninstalled plugin counts as orphaned; a disabled-but-installed plugin's sources are still attempted and surface a visible RecordError when the client fails to load. The Add-source picker shares the same all-installed set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): treat empty request_integration_id as no link ConnectionResolver.Resolve gated the linked-integration path on a non-nil RequestIntegrationID pointer, so a pointer-to-empty-string (from a both-NULL orphan or a stripped link) called requests.Get(""). Guard on a non-empty trimmed value so it falls back to the connection's own fields instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): align startup poll interval with reschedule computation Startup seeded the poll task by integer-dividing default_poll_interval_seconds by 60 (minutes), while HandleUpdateSettings reschedules with seconds*1000 ms; the two diverged for sub-minute and non-60-multiple intervals. NewAutoscanPollTask now takes the interval in milliseconds and main.go seeds it as seconds*1000, matching the reschedule path so both agree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): normalize stored rewrite From at poll time applyRewrites matched the stored From after only TrimSpace/TrimSuffix, while suggest.go coveredBy normalizes via normalizePath (backslash->slash, collapse '//'). A Windows-style or dup-slash stored rewrite was thus reported 'covered' at suggest time yet never matched at poll time. applyRewrites now normalizes From through normalizePath so poll-time and suggest-time agree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): don't corrupt source poll interval on enable/connection change Add a `parseInterval` helper that maps empty input to null (use global default), valid positive integers to the integer, and any other mid-edit-invalid value to the source's currently-persisted `poll_interval_seconds` — so toggling the enable switch or changing the connection cannot silently overwrite the interval with 0 or NaN. Wire the helper through `fullBody()` (the single source of truth for PUT payloads) and remove the two inline duplications in `handleConnectionChange` and `handleRewriteSave` that both previously used raw `Number()`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): make the source connection optional (provider-agnostic) A host connection is the credential/endpoint for server-based providers (Sonarr/Radarr); other scan_source providers (e.g. a CephFS/filesystem watcher that reads ceph.dir.r* xattrs) need none. PollOnce now polls connection-less sources, passing an empty ResolvedConnection the plugin may ignore; a plugin that requires credentials surfaces the error at poll time. Drops the enable-requires-connection 400s. Provider-specific config lives in the plugin's own global_config_schema, not a host connection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): provider-agnostic autoscan copy + optional source connection Replace arr-hardcoded framing in AdminAutoscan, SourcesPanel, and ConnectionsPanel with neutral scan-source language. Remove the connection-required gate on the source enable toggle so connectionless providers (e.g. filesystem watchers) can be enabled; soften the badge from "Needs connection" to "No connection". Sync-from-server button remains gated on a bound connection (it needs a server to query). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: repo-relative paths in autoscan plans Replace local absolute filesystem paths (/opt/silo, sibling checkouts, /tmp/go/bin) in docs/superpowers/plans with repository-relative wording per CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): rune-safe last_error truncation Truncate RecordError messages on a UTF-8 rune boundary so a byte-bounded cut can't split a multi-byte rune and store invalid UTF-8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): advance marker when resolved-but-suppressed (not unresolved) resolveAndClaim now reports resolvedAny (whether any path mapped to a Silo library folder, independent of suppression). PollOnce gates the "none matched a Silo library folder" hold+RecordError on !resolvedAny instead of len(targets)==0, so a poll whose paths resolved but were all debounced/suppressed advances the marker instead of being treated as a misconfiguration. Adds a regression test for the suppressed case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): normalize request_integration_id Trim whitespace and collapse empty-after-trim request_integration_id to nil on connection create and update, so a pointer-to-"" or " " is never persisted as a bogus Requests link. Also corrects a stale migration-172 comment to 171 (the collapsed migration number). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(autoscan): provider-agnostic poll-task copy Rename the poll task to "Autoscan poll" with a provider-agnostic description and progress message; drop Sonarr/Radarr/arr wording. Key() (autoscan_poll) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(autoscan): fix typo in connectionless-source test name Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): add scan source management * chore(deps): bump silo-plugin-sdk for structured scan source changes Pins silo-plugin-sdk to 0d78651, which adds source_config on PollChangesRequest plus the structured changes / ScanSourceChangeScope fields on PollChangesResponse that internal/autoscan/provider.go already consumes. Without this the branch fails to compile against the prior pin (807b07e). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): label scan sources by connection name in admin UI arr-plugin sources fan out one-per-connection under a single generic "arr" capability, so every row in the Sources and Activity panels rendered an identical "arr (plugin #N)" label. Lead with the bound connection name (Radarr/Sonarr/...) instead, demoting capability + plugin to a subtitle. Sources without a connection (e.g. cephfs) keep the capability fallback. Activity threads a source_id -> connection name lookup (built from the existing sources + connections queries) through the scan/poll tables the same way librariesByID is threaded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(autoscan): spec for generic + operator-editable source labels Design for a shared label-resolution helper (operator label -> connection name -> manifest display_name -> capability_id) consumed by the Sources and Activity panels, plus an operator-editable per-source label backed by a new autoscan_sources.label column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(autoscan): implementation plan for source labels Task-by-task TDD plan: migration 174 (label column), Go domain/repo/handler wiring with server-side normalization, shared frontend label helper, and Sources/Activity panel integration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): migration for source label column * feat(autoscan): source label domain field + normalizer * feat(autoscan): persist source label in repository Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): accept, normalize, and return source label Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): add label to source API types * feat(autoscan): shared source-label resolution helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(autoscan): polish source-label helper per review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(autoscan): label sources via shared helper + operator label input Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(autoscan): clarify source label naming per review * feat(autoscan): resolve activity source labels via shared helper Replace the sourceNames Map plumbing in ActivityPanel with SourceLabelLookups and delegate both name functions to resolveEventSourceName from @/lib/autoscanLabels, enabling the full label chain (operator label → connection name → manifest display_name → capability_id) for all Scan History and Poll log rows. * fix(autoscan): carry label on status source + guard poll label Final-review follow-ups: add the label field to the autoscanStatusSource response (and AutoscanStatusSource type) so the status view matches the source response per spec, and give pollSourceName a non-empty fallback for symmetry with scanSourceName. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(autoscan): resolve source aria-labels through the label chain Replace the legacy capability-only sourceLabel() helper with resolveSourceName() (operator label -> connection -> display_name -> capability). Row controls now announce the row's resolvedLabel (reflecting in-progress edits) and the delete dialog announces the resolved name, so screen readers hear "4K Movies" instead of "arr (plugin #4)". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(autoscan): paginate queue + history with a shared table pager Replace the card/table hybrid and 200-row "Load more" cap on the autoscan Activity panel with proper tables and real pagination. Backend: add offset + total-count to the scans/events list endpoints so history pages through the full set instead of a capped window. Extract shared event/scan WHERE-clause builders so list and count filter identically, and add CountEvents / CountAutoscanScans. Frontend: add a reusable TablePagination component (rows-per-page, "showing X-Y of Z", numbered window with ellipses, responsive) and reuse it for the server-paginated history (scans + polls) and the client-paginated live queue. Unify all three tables behind one DataTable shell so they read as one family. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com> * fix(migrations): renumber PR 48 migrations * fix(migrations): tolerate stale device profile ids --------- Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com> Co-authored-by: fluxis <warmasterx555@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2471 lines
69 KiB
SQL
2471 lines
69 KiB
SQL
--
|
|
-- PostgreSQL database dump
|
|
--
|
|
|
|
|
|
-- Dumped from database version 18.3 (Debian 18.3-1.pgdg12+1)
|
|
-- Dumped by pg_dump version 18.3 (Debian 18.3-1.pgdg12+1)
|
|
|
|
SET statement_timeout = 0;
|
|
SET lock_timeout = 0;
|
|
SET idle_in_transaction_session_timeout = 0;
|
|
SET transaction_timeout = 0;
|
|
SET client_encoding = 'UTF8';
|
|
SET standard_conforming_strings = on;
|
|
SELECT pg_catalog.set_config('search_path', '', false);
|
|
SET check_function_bodies = false;
|
|
SET xmloption = content;
|
|
SET client_min_messages = warning;
|
|
SET row_security = off;
|
|
|
|
--
|
|
-- Name: vector; Type: EXTENSION; Schema: -; Owner: -
|
|
--
|
|
|
|
CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public;
|
|
|
|
|
|
SET default_tablespace = '';
|
|
|
|
SET default_table_access_method = heap;
|
|
|
|
--
|
|
-- Name: activity_log; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.activity_log (
|
|
id bigint NOT NULL,
|
|
"timestamp" timestamp with time zone DEFAULT now() NOT NULL,
|
|
client_ip inet NOT NULL,
|
|
user_id integer,
|
|
session_id text,
|
|
method text NOT NULL,
|
|
path text NOT NULL,
|
|
status_code integer,
|
|
user_agent text,
|
|
duration_ms integer
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: activity_log_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.activity_log_id_seq
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: activity_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.activity_log_id_seq OWNED BY public.activity_log.id;
|
|
|
|
|
|
--
|
|
-- Name: admin_jobs; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.admin_jobs (
|
|
id text NOT NULL,
|
|
job_type text NOT NULL,
|
|
status text NOT NULL,
|
|
created_by_user_id integer NOT NULL,
|
|
request_payload jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
result_payload jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
message text DEFAULT ''::text NOT NULL,
|
|
error_message text DEFAULT ''::text NOT NULL,
|
|
progress_current integer DEFAULT 0 NOT NULL,
|
|
progress_total integer DEFAULT 0 NOT NULL,
|
|
artifact_bucket text DEFAULT ''::text NOT NULL,
|
|
artifact_key text DEFAULT ''::text NOT NULL,
|
|
artifact_size_bytes bigint DEFAULT 0 NOT NULL,
|
|
requested_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
started_at timestamp with time zone,
|
|
completed_at timestamp with time zone,
|
|
heartbeat_at timestamp with time zone,
|
|
expires_at timestamp with time zone,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT admin_jobs_status_check CHECK ((status = ANY (ARRAY['queued'::text, 'running'::text, 'completed'::text, 'failed'::text, 'cancelled'::text])))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: api_keys; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.api_keys (
|
|
id bigint NOT NULL,
|
|
user_id integer NOT NULL,
|
|
label text NOT NULL,
|
|
api_key text NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
last_used_at timestamp with time zone,
|
|
rate_tier text DEFAULT 'standard'::text NOT NULL,
|
|
CONSTRAINT chk_api_keys_rate_tier CHECK ((rate_tier = ANY (ARRAY['standard'::text, 'elevated'::text])))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: api_keys_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.api_keys_id_seq
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: api_keys_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.api_keys_id_seq OWNED BY public.api_keys.id;
|
|
|
|
|
|
--
|
|
-- Name: auth_sessions; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.auth_sessions (
|
|
id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
device_name text,
|
|
ip_address inet,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
expires_at timestamp with time zone NOT NULL,
|
|
revoked_at timestamp with time zone
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.downloaded_subtitles (
|
|
id integer NOT NULL,
|
|
media_file_id integer NOT NULL,
|
|
provider text NOT NULL,
|
|
language text NOT NULL,
|
|
format text NOT NULL,
|
|
release_name text DEFAULT ''::text NOT NULL,
|
|
s3_key text NOT NULL,
|
|
score double precision DEFAULT 0 NOT NULL,
|
|
hearing_impaired boolean DEFAULT false NOT NULL,
|
|
downloaded_by integer,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.downloaded_subtitles_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.downloaded_subtitles_id_seq OWNED BY public.downloaded_subtitles.id;
|
|
|
|
|
|
--
|
|
-- Name: episodes; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.episodes (
|
|
content_id text NOT NULL,
|
|
series_id text NOT NULL,
|
|
season_number integer NOT NULL,
|
|
episode_number integer NOT NULL,
|
|
title text,
|
|
overview text,
|
|
air_date date,
|
|
runtime integer,
|
|
rating_imdb double precision,
|
|
rating_tmdb double precision,
|
|
still_path text,
|
|
still_thumbhash text,
|
|
metadata_s3_path text,
|
|
metadata_etag text,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
season_id text
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: invite_codes; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.invite_codes (
|
|
id bigint NOT NULL,
|
|
code text NOT NULL,
|
|
label text DEFAULT ''::text NOT NULL,
|
|
max_uses integer NOT NULL,
|
|
use_count integer DEFAULT 0 NOT NULL,
|
|
created_by bigint NOT NULL,
|
|
enabled boolean DEFAULT true NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: invite_codes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.invite_codes_id_seq
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: invite_codes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.invite_codes_id_seq OWNED BY public.invite_codes.id;
|
|
|
|
|
|
--
|
|
-- Name: item_cowatch; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.item_cowatch (
|
|
item_id text NOT NULL,
|
|
similar_item_id text NOT NULL,
|
|
jaccard_score double precision NOT NULL,
|
|
cowatch_count integer NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now()
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: item_people; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.item_people (
|
|
id bigint NOT NULL,
|
|
content_id text NOT NULL,
|
|
person_id bigint NOT NULL,
|
|
kind smallint NOT NULL,
|
|
"character" text DEFAULT ''::text NOT NULL,
|
|
sort_order integer DEFAULT 0 NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: jellycompat_sessions; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.jellycompat_sessions (
|
|
token text NOT NULL,
|
|
username text NOT NULL,
|
|
account_username text NOT NULL,
|
|
profile_id text NOT NULL,
|
|
profile_name text NOT NULL,
|
|
pseudo_user_id uuid NOT NULL,
|
|
streamapp_user_id integer NOT NULL,
|
|
streamapp_access_token text NOT NULL,
|
|
streamapp_refresh_token text NOT NULL,
|
|
streamapp_token_expiry timestamp with time zone NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
expires_at timestamp with time zone NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: library_collection_items; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.library_collection_items (
|
|
collection_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
"position" integer DEFAULT 0 NOT NULL,
|
|
source_rank integer DEFAULT 0 NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: library_collection_sync_runs; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.library_collection_sync_runs (
|
|
id text NOT NULL,
|
|
collection_id text NOT NULL,
|
|
status text NOT NULL,
|
|
message text DEFAULT ''::text NOT NULL,
|
|
items_added integer DEFAULT 0 NOT NULL,
|
|
items_removed integer DEFAULT 0 NOT NULL,
|
|
items_matched integer DEFAULT 0 NOT NULL,
|
|
items_unmatched integer DEFAULT 0 NOT NULL,
|
|
warnings jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
started_at timestamp with time zone,
|
|
completed_at timestamp with time zone,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT library_collection_sync_runs_status_check CHECK ((status = ANY (ARRAY['running'::text, 'success'::text, 'failed'::text, 'warning'::text])))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: library_collections; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.library_collections (
|
|
id text NOT NULL,
|
|
library_id integer NOT NULL,
|
|
slug text NOT NULL,
|
|
title text NOT NULL,
|
|
description text DEFAULT ''::text NOT NULL,
|
|
collection_type text NOT NULL,
|
|
visibility text DEFAULT 'visible'::text NOT NULL,
|
|
sort_order integer DEFAULT 0 NOT NULL,
|
|
featured boolean DEFAULT false NOT NULL,
|
|
poster_url text DEFAULT ''::text NOT NULL,
|
|
backdrop_url text DEFAULT ''::text NOT NULL,
|
|
source_url text DEFAULT ''::text NOT NULL,
|
|
source_config jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
last_sync_status text DEFAULT 'idle'::text NOT NULL,
|
|
last_sync_message text DEFAULT ''::text NOT NULL,
|
|
last_sync_at timestamp with time zone,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT library_collections_collection_type_check CHECK ((collection_type = ANY (ARRAY['manual'::text, 'mdblist'::text, 'kometa'::text]))),
|
|
CONSTRAINT library_collections_last_sync_status_check CHECK ((last_sync_status = ANY (ARRAY['idle'::text, 'running'::text, 'success'::text, 'failed'::text, 'warning'::text]))),
|
|
CONSTRAINT library_collections_visibility_check CHECK ((visibility = ANY (ARRAY['visible'::text, 'hidden'::text])))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: library_provider_chains; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.library_provider_chains (
|
|
media_folder_id integer NOT NULL,
|
|
provider_id integer NOT NULL,
|
|
priority integer NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: media_files; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.media_files (
|
|
id integer NOT NULL,
|
|
content_id text,
|
|
media_folder_id integer NOT NULL,
|
|
file_path text NOT NULL,
|
|
file_size bigint,
|
|
file_hash text,
|
|
codec_video text,
|
|
codec_audio text,
|
|
resolution text,
|
|
audio_channels integer,
|
|
hdr boolean,
|
|
container text,
|
|
duration bigint,
|
|
bitrate integer,
|
|
subtitle_tracks jsonb,
|
|
external_subtitles jsonb,
|
|
intro_start double precision,
|
|
intro_end double precision,
|
|
credits_start double precision,
|
|
credits_end double precision,
|
|
probe_source text,
|
|
probe_updated_at timestamp with time zone,
|
|
missing_since timestamp with time zone,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
episode_id text,
|
|
season_number integer,
|
|
episode_number integer,
|
|
video_tracks jsonb,
|
|
audio_tracks jsonb
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: media_files_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.media_files_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: media_files_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.media_files_id_seq OWNED BY public.media_files.id;
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.media_folder_paths (
|
|
id integer NOT NULL,
|
|
media_folder_id integer NOT NULL,
|
|
path text NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.media_folder_paths_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.media_folder_paths_id_seq OWNED BY public.media_folder_paths.id;
|
|
|
|
|
|
--
|
|
-- Name: media_folders; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.media_folders (
|
|
id integer NOT NULL,
|
|
type text NOT NULL,
|
|
name text NOT NULL,
|
|
enabled boolean DEFAULT true,
|
|
last_scanned_at timestamp with time zone,
|
|
scan_warning_code text,
|
|
scan_warning_message text,
|
|
scan_warning_at timestamp with time zone,
|
|
allow_empty_cleanup_once boolean DEFAULT false NOT NULL,
|
|
poster_path text DEFAULT ''::text NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: media_folders_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.media_folders_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: media_folders_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.media_folders_id_seq OWNED BY public.media_folders.id;
|
|
|
|
|
|
--
|
|
-- Name: media_item_embeddings; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.media_item_embeddings (
|
|
media_item_id text NOT NULL,
|
|
embedding public.vector(1536) NOT NULL,
|
|
model text NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
canonical_text text DEFAULT ''::text NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: media_item_libraries; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.media_item_libraries (
|
|
content_id text NOT NULL,
|
|
media_folder_id integer NOT NULL,
|
|
first_seen_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: media_items; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.media_items (
|
|
content_id text NOT NULL,
|
|
type text NOT NULL,
|
|
title text NOT NULL,
|
|
sort_title text,
|
|
original_title text,
|
|
year integer,
|
|
genres text[],
|
|
content_rating text,
|
|
runtime integer,
|
|
overview text,
|
|
tagline text,
|
|
rating_imdb double precision,
|
|
rating_tmdb double precision,
|
|
rating_rt_critic integer,
|
|
rating_rt_audience integer,
|
|
imdb_id text,
|
|
tmdb_id text,
|
|
tvdb_id text,
|
|
poster_path text,
|
|
poster_thumbhash text,
|
|
backdrop_path text,
|
|
backdrop_thumbhash text,
|
|
logo_path text,
|
|
metadata_s3_path text,
|
|
metadata_etag text,
|
|
season_count integer,
|
|
matched_at timestamp with time zone,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
studios text[] DEFAULT '{}'::text[] NOT NULL,
|
|
networks text[] DEFAULT '{}'::text[] NOT NULL,
|
|
countries text[] DEFAULT '{}'::text[] NOT NULL,
|
|
first_air_date text,
|
|
last_air_date text,
|
|
last_refreshed timestamp with time zone,
|
|
refresh_failures integer DEFAULT 0 NOT NULL,
|
|
locked_fields integer[] DEFAULT '{}'::integer[] NOT NULL,
|
|
status text DEFAULT 'matched'::text NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: metadata_providers; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.metadata_providers (
|
|
id integer NOT NULL,
|
|
slug text NOT NULL,
|
|
provider_type text NOT NULL,
|
|
enabled boolean DEFAULT true,
|
|
settings jsonb,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: metadata_providers_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.metadata_providers_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: metadata_providers_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.metadata_providers_id_seq OWNED BY public.metadata_providers.id;
|
|
|
|
|
|
--
|
|
-- Name: page_sections; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.page_sections (
|
|
id text NOT NULL,
|
|
scope text NOT NULL,
|
|
library_id bigint,
|
|
"position" integer DEFAULT 0 NOT NULL,
|
|
section_type text NOT NULL,
|
|
title text NOT NULL,
|
|
featured boolean DEFAULT false NOT NULL,
|
|
item_limit integer DEFAULT 20 NOT NULL,
|
|
config jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
enabled boolean DEFAULT true NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT page_sections_library_scope CHECK ((((scope = 'home'::text) AND (library_id IS NULL)) OR ((scope = 'library'::text) AND (library_id IS NOT NULL)))),
|
|
CONSTRAINT page_sections_scope_check CHECK ((scope = ANY (ARRAY['home'::text, 'library'::text])))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: people; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.people (
|
|
id bigint NOT NULL,
|
|
name text NOT NULL,
|
|
sort_name text DEFAULT ''::text NOT NULL,
|
|
bio text DEFAULT ''::text NOT NULL,
|
|
birth_date date,
|
|
death_date date,
|
|
birthplace text DEFAULT ''::text NOT NULL,
|
|
homepage text DEFAULT ''::text NOT NULL,
|
|
photo_path text DEFAULT ''::text NOT NULL,
|
|
photo_thumbhash text DEFAULT ''::text NOT NULL,
|
|
tmdb_id text DEFAULT ''::text NOT NULL,
|
|
imdb_id text DEFAULT ''::text NOT NULL,
|
|
tvdb_id text DEFAULT ''::text NOT NULL,
|
|
plex_guid text DEFAULT ''::text NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: playback_history_admin; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.playback_history_admin (
|
|
session_id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
profile_name text DEFAULT ''::text NOT NULL,
|
|
media_item_id text DEFAULT ''::text NOT NULL,
|
|
media_file_id integer NOT NULL,
|
|
play_method text NOT NULL,
|
|
started_at timestamp with time zone NOT NULL,
|
|
ended_at timestamp with time zone NOT NULL,
|
|
watched_seconds double precision DEFAULT 0 NOT NULL,
|
|
duration_seconds double precision,
|
|
completed boolean DEFAULT false NOT NULL,
|
|
client_ip inet
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: playback_sessions_sync; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.playback_sessions_sync (
|
|
session_id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
profile_id text,
|
|
media_file_id integer,
|
|
play_method text,
|
|
reporting_node text,
|
|
started_at timestamp with time zone,
|
|
updated_at timestamp with time zone,
|
|
last_sync_at timestamp with time zone
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: recommendation_cache; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.recommendation_cache (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
rec_type text NOT NULL,
|
|
source_item_id text DEFAULT ''::text NOT NULL,
|
|
items jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
expires_at timestamp with time zone NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: seasons; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.seasons (
|
|
content_id text NOT NULL,
|
|
series_id text NOT NULL,
|
|
season_number integer NOT NULL,
|
|
title text,
|
|
overview text,
|
|
air_date date,
|
|
poster_path text,
|
|
poster_thumbhash text,
|
|
metadata_s3_path text,
|
|
metadata_etag text,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: server_settings; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.server_settings (
|
|
key text NOT NULL,
|
|
value text NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: stream_nodes; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.stream_nodes (
|
|
id integer NOT NULL,
|
|
name text NOT NULL,
|
|
type text NOT NULL,
|
|
url text NOT NULL,
|
|
enabled boolean DEFAULT true NOT NULL,
|
|
healthy boolean DEFAULT false NOT NULL,
|
|
active_jobs integer DEFAULT 0 NOT NULL,
|
|
last_health_check timestamp with time zone,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT stream_nodes_type_check CHECK ((type = ANY (ARRAY['proxy'::text, 'transcode'::text])))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: stream_nodes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.stream_nodes_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: stream_nodes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.stream_nodes_id_seq OWNED BY public.stream_nodes.id;
|
|
|
|
|
|
--
|
|
-- Name: subtitle_provider_config; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.subtitle_provider_config (
|
|
provider_name text NOT NULL,
|
|
enabled boolean DEFAULT false NOT NULL,
|
|
api_key text DEFAULT ''::text NOT NULL,
|
|
username text DEFAULT ''::text NOT NULL,
|
|
password text DEFAULT ''::text NOT NULL,
|
|
extra_config jsonb,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_aggregates; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_aggregates (
|
|
user_id integer NOT NULL,
|
|
total_watched integer DEFAULT 0,
|
|
favorites_count integer DEFAULT 0,
|
|
watchlist_count integer DEFAULT 0,
|
|
active_node text,
|
|
last_sync_at timestamp with time zone
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_audio_preferences; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_audio_preferences (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
series_id text NOT NULL,
|
|
audio_track_index integer,
|
|
audio_language text,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_downloads; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_downloads (
|
|
id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
media_file_id integer NOT NULL,
|
|
quality text DEFAULT ''::text NOT NULL,
|
|
transcoded boolean DEFAULT false NOT NULL,
|
|
file_size bigint,
|
|
expires_at timestamp with time zone,
|
|
downloaded_at timestamp with time zone
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_favorites; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_favorites (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
added_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_personal_collection_items; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_personal_collection_items (
|
|
user_id integer NOT NULL,
|
|
collection_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
"position" integer DEFAULT 0 NOT NULL,
|
|
added_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_personal_collections; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_personal_collections (
|
|
id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
name text NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_playback_sessions; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_playback_sessions (
|
|
session_id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_file_id integer NOT NULL,
|
|
play_method text NOT NULL,
|
|
position_seconds double precision DEFAULT 0 NOT NULL,
|
|
is_paused boolean DEFAULT false NOT NULL,
|
|
started_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_profile_allowed_libraries; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_profile_allowed_libraries (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
library_id integer NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_profiles; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_profiles (
|
|
id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
name text NOT NULL,
|
|
avatar text DEFAULT ''::text NOT NULL,
|
|
pin_hash text DEFAULT ''::text NOT NULL,
|
|
is_child boolean DEFAULT false NOT NULL,
|
|
max_content_rating text DEFAULT ''::text NOT NULL,
|
|
quality_preference text DEFAULT '1080p'::text NOT NULL,
|
|
language text DEFAULT 'en'::text NOT NULL,
|
|
subtitle_language text DEFAULT ''::text NOT NULL,
|
|
subtitle_mode text DEFAULT 'auto'::text NOT NULL,
|
|
auto_skip_intro boolean DEFAULT false NOT NULL,
|
|
auto_skip_credits boolean DEFAULT false NOT NULL,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
library_restrictions_enabled boolean DEFAULT false NOT NULL,
|
|
max_playback_quality text DEFAULT ''::text NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_ratings; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_ratings (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
rating smallint NOT NULL,
|
|
rated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT user_ratings_rating_check CHECK (((rating >= 1) AND (rating <= 5)))
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_settings; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_settings (
|
|
user_id integer NOT NULL,
|
|
key text NOT NULL,
|
|
value text NOT NULL
|
|
);
|
|
|
|
CREATE TABLE public.user_device_settings (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
device_id text NOT NULL,
|
|
key text NOT NULL,
|
|
value text NOT NULL,
|
|
device_name text DEFAULT ''::text NOT NULL,
|
|
device_platform text DEFAULT ''::text NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
CREATE TABLE public.user_devices (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
device_id text NOT NULL,
|
|
device_name text DEFAULT ''::text NOT NULL,
|
|
device_platform text DEFAULT ''::text NOT NULL,
|
|
last_seen_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_subtitle_preferences; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_subtitle_preferences (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
series_id text NOT NULL,
|
|
subtitle_language text DEFAULT ''::text NOT NULL,
|
|
subtitle_track_index integer DEFAULT 0 NOT NULL,
|
|
external_subtitle_path text DEFAULT ''::text NOT NULL,
|
|
subtitle_mode text DEFAULT ''::text NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_taste_clusters; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_taste_clusters (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
cluster_idx integer NOT NULL,
|
|
embedding public.vector(768),
|
|
dominant_genres jsonb,
|
|
label text,
|
|
member_count integer,
|
|
total_weight double precision,
|
|
updated_at timestamp with time zone DEFAULT now()
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_taste_profiles; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_taste_profiles (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
embedding public.vector(1536) NOT NULL,
|
|
signal_counts jsonb DEFAULT '{}'::jsonb NOT NULL,
|
|
max_content_rating text DEFAULT ''::text NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
stale_at timestamp with time zone
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_watch_history; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_watch_history (
|
|
id text NOT NULL,
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
watched_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
duration_seconds double precision,
|
|
completed boolean DEFAULT false NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_watch_progress; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_watch_progress (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
position_seconds double precision DEFAULT 0 NOT NULL,
|
|
duration_seconds double precision DEFAULT 0 NOT NULL,
|
|
completed boolean DEFAULT false NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
last_file_id integer,
|
|
last_resolution text,
|
|
last_hdr boolean,
|
|
last_codec_video text
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: user_watchlist; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.user_watchlist (
|
|
user_id integer NOT NULL,
|
|
profile_id text NOT NULL,
|
|
media_item_id text NOT NULL,
|
|
added_at timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: users; Type: TABLE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE TABLE public.users (
|
|
id integer NOT NULL,
|
|
email text,
|
|
username text,
|
|
password_hash text,
|
|
role text,
|
|
permissions text[] DEFAULT '{}'::text[] NOT NULL,
|
|
enabled boolean DEFAULT true,
|
|
library_ids integer[],
|
|
max_streams integer DEFAULT 6,
|
|
max_transcodes integer DEFAULT 2,
|
|
download_allowed boolean DEFAULT true,
|
|
download_transcode_allowed boolean DEFAULT false,
|
|
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
max_playback_quality text DEFAULT ''::text NOT NULL,
|
|
access_policy_revision bigint DEFAULT 1 NOT NULL
|
|
);
|
|
|
|
|
|
--
|
|
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE SEQUENCE public.users_id_seq
|
|
AS integer
|
|
START WITH 1
|
|
INCREMENT BY 1
|
|
NO MINVALUE
|
|
NO MAXVALUE
|
|
CACHE 1;
|
|
|
|
|
|
--
|
|
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
|
|
|
|
|
|
--
|
|
-- Name: activity_log id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.activity_log ALTER COLUMN id SET DEFAULT nextval('public.activity_log_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: api_keys id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.api_keys ALTER COLUMN id SET DEFAULT nextval('public.api_keys_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.downloaded_subtitles ALTER COLUMN id SET DEFAULT nextval('public.downloaded_subtitles_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: invite_codes id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.invite_codes ALTER COLUMN id SET DEFAULT nextval('public.invite_codes_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: media_files id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_files ALTER COLUMN id SET DEFAULT nextval('public.media_files_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_folder_paths ALTER COLUMN id SET DEFAULT nextval('public.media_folder_paths_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: media_folders id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_folders ALTER COLUMN id SET DEFAULT nextval('public.media_folders_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: metadata_providers id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.metadata_providers ALTER COLUMN id SET DEFAULT nextval('public.metadata_providers_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: stream_nodes id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.stream_nodes ALTER COLUMN id SET DEFAULT nextval('public.stream_nodes_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: users id; Type: DEFAULT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
|
|
|
|
|
|
--
|
|
-- Name: activity_log activity_log_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.activity_log
|
|
ADD CONSTRAINT activity_log_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: admin_jobs admin_jobs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.admin_jobs
|
|
ADD CONSTRAINT admin_jobs_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: api_keys api_keys_api_key_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.api_keys
|
|
ADD CONSTRAINT api_keys_api_key_key UNIQUE (api_key);
|
|
|
|
|
|
--
|
|
-- Name: api_keys api_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.api_keys
|
|
ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: auth_sessions auth_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.auth_sessions
|
|
ADD CONSTRAINT auth_sessions_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles downloaded_subtitles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.downloaded_subtitles
|
|
ADD CONSTRAINT downloaded_subtitles_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles downloaded_subtitles_s3_key_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.downloaded_subtitles
|
|
ADD CONSTRAINT downloaded_subtitles_s3_key_key UNIQUE (s3_key);
|
|
|
|
|
|
--
|
|
-- Name: episodes episodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.episodes
|
|
ADD CONSTRAINT episodes_pkey PRIMARY KEY (content_id);
|
|
|
|
|
|
--
|
|
-- Name: episodes episodes_series_season_episode_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.episodes
|
|
ADD CONSTRAINT episodes_series_season_episode_key UNIQUE (series_id, season_number, episode_number);
|
|
|
|
|
|
--
|
|
-- Name: invite_codes invite_codes_code_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.invite_codes
|
|
ADD CONSTRAINT invite_codes_code_key UNIQUE (code);
|
|
|
|
|
|
--
|
|
-- Name: invite_codes invite_codes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.invite_codes
|
|
ADD CONSTRAINT invite_codes_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: item_cowatch item_cowatch_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.item_cowatch
|
|
ADD CONSTRAINT item_cowatch_pkey PRIMARY KEY (item_id, similar_item_id);
|
|
|
|
|
|
--
|
|
-- Name: item_people item_people_content_id_person_id_kind_character_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.item_people
|
|
ADD CONSTRAINT item_people_content_id_person_id_kind_character_key UNIQUE (content_id, person_id, kind, "character");
|
|
|
|
|
|
--
|
|
-- Name: item_people item_people_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.item_people
|
|
ADD CONSTRAINT item_people_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: jellycompat_sessions jellycompat_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.jellycompat_sessions
|
|
ADD CONSTRAINT jellycompat_sessions_pkey PRIMARY KEY (token);
|
|
|
|
|
|
--
|
|
-- Name: library_collection_items library_collection_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collection_items
|
|
ADD CONSTRAINT library_collection_items_pkey PRIMARY KEY (collection_id, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: library_collection_sync_runs library_collection_sync_runs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collection_sync_runs
|
|
ADD CONSTRAINT library_collection_sync_runs_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: library_collections library_collections_library_slug_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collections
|
|
ADD CONSTRAINT library_collections_library_slug_unique UNIQUE (library_id, slug);
|
|
|
|
|
|
--
|
|
-- Name: library_collections library_collections_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collections
|
|
ADD CONSTRAINT library_collections_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: library_provider_chains library_provider_chains_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_provider_chains
|
|
ADD CONSTRAINT library_provider_chains_pkey PRIMARY KEY (media_folder_id, provider_id);
|
|
|
|
|
|
--
|
|
-- Name: media_files media_files_file_path_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_files
|
|
ADD CONSTRAINT media_files_file_path_key UNIQUE (file_path);
|
|
|
|
|
|
--
|
|
-- Name: media_files media_files_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_files
|
|
ADD CONSTRAINT media_files_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths media_folder_paths_path_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_folder_paths
|
|
ADD CONSTRAINT media_folder_paths_path_key UNIQUE (path);
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths media_folder_paths_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_folder_paths
|
|
ADD CONSTRAINT media_folder_paths_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: media_folders media_folders_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_folders
|
|
ADD CONSTRAINT media_folders_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: media_item_embeddings media_item_embeddings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_item_embeddings
|
|
ADD CONSTRAINT media_item_embeddings_pkey PRIMARY KEY (media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: media_item_libraries media_item_libraries_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_item_libraries
|
|
ADD CONSTRAINT media_item_libraries_pkey PRIMARY KEY (content_id, media_folder_id);
|
|
|
|
|
|
--
|
|
-- Name: media_items media_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_items
|
|
ADD CONSTRAINT media_items_pkey PRIMARY KEY (content_id);
|
|
|
|
|
|
--
|
|
-- Name: metadata_providers metadata_providers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.metadata_providers
|
|
ADD CONSTRAINT metadata_providers_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: metadata_providers metadata_providers_slug_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.metadata_providers
|
|
ADD CONSTRAINT metadata_providers_slug_key UNIQUE (slug);
|
|
|
|
|
|
--
|
|
-- Name: page_sections page_sections_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.page_sections
|
|
ADD CONSTRAINT page_sections_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: people people_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.people
|
|
ADD CONSTRAINT people_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: playback_history_admin playback_history_admin_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.playback_history_admin
|
|
ADD CONSTRAINT playback_history_admin_pkey PRIMARY KEY (session_id);
|
|
|
|
|
|
--
|
|
-- Name: playback_sessions_sync playback_sessions_sync_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.playback_sessions_sync
|
|
ADD CONSTRAINT playback_sessions_sync_pkey PRIMARY KEY (session_id);
|
|
|
|
|
|
--
|
|
-- Name: recommendation_cache recommendation_cache_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.recommendation_cache
|
|
ADD CONSTRAINT recommendation_cache_pkey PRIMARY KEY (user_id, profile_id, rec_type, source_item_id);
|
|
|
|
|
|
--
|
|
-- Name: seasons seasons_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.seasons
|
|
ADD CONSTRAINT seasons_pkey PRIMARY KEY (content_id);
|
|
|
|
|
|
--
|
|
-- Name: seasons seasons_series_id_season_number_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.seasons
|
|
ADD CONSTRAINT seasons_series_id_season_number_key UNIQUE (series_id, season_number);
|
|
|
|
|
|
--
|
|
-- Name: server_settings server_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.server_settings
|
|
ADD CONSTRAINT server_settings_pkey PRIMARY KEY (key);
|
|
|
|
|
|
--
|
|
-- Name: stream_nodes stream_nodes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.stream_nodes
|
|
ADD CONSTRAINT stream_nodes_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: stream_nodes stream_nodes_url_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.stream_nodes
|
|
ADD CONSTRAINT stream_nodes_url_key UNIQUE (url);
|
|
|
|
|
|
--
|
|
-- Name: subtitle_provider_config subtitle_provider_config_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.subtitle_provider_config
|
|
ADD CONSTRAINT subtitle_provider_config_pkey PRIMARY KEY (provider_name);
|
|
|
|
|
|
--
|
|
-- Name: user_aggregates user_aggregates_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_aggregates
|
|
ADD CONSTRAINT user_aggregates_pkey PRIMARY KEY (user_id);
|
|
|
|
|
|
--
|
|
-- Name: user_audio_preferences user_audio_preferences_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_audio_preferences
|
|
ADD CONSTRAINT user_audio_preferences_pkey PRIMARY KEY (user_id, profile_id, series_id);
|
|
|
|
|
|
--
|
|
-- Name: user_downloads user_downloads_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_downloads
|
|
ADD CONSTRAINT user_downloads_pkey PRIMARY KEY (user_id, id);
|
|
|
|
|
|
--
|
|
-- Name: user_favorites user_favorites_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_favorites
|
|
ADD CONSTRAINT user_favorites_pkey PRIMARY KEY (user_id, profile_id, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: user_personal_collection_items user_personal_collection_items_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_personal_collection_items
|
|
ADD CONSTRAINT user_personal_collection_items_pkey PRIMARY KEY (user_id, collection_id, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: user_personal_collections user_personal_collections_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_personal_collections
|
|
ADD CONSTRAINT user_personal_collections_pkey PRIMARY KEY (user_id, id);
|
|
|
|
|
|
--
|
|
-- Name: user_playback_sessions user_playback_sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_playback_sessions
|
|
ADD CONSTRAINT user_playback_sessions_pkey PRIMARY KEY (user_id, session_id);
|
|
|
|
|
|
--
|
|
-- Name: user_profile_allowed_libraries user_profile_allowed_libraries_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_profile_allowed_libraries
|
|
ADD CONSTRAINT user_profile_allowed_libraries_pkey PRIMARY KEY (user_id, profile_id, library_id);
|
|
|
|
|
|
--
|
|
-- Name: user_profiles user_profiles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_profiles
|
|
ADD CONSTRAINT user_profiles_pkey PRIMARY KEY (user_id, id);
|
|
|
|
|
|
--
|
|
-- Name: user_ratings user_ratings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_ratings
|
|
ADD CONSTRAINT user_ratings_pkey PRIMARY KEY (user_id, profile_id, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: user_settings user_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_settings
|
|
ADD CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, key);
|
|
|
|
ALTER TABLE ONLY public.user_device_settings
|
|
ADD CONSTRAINT user_device_settings_pkey PRIMARY KEY (user_id, profile_id, device_id, key);
|
|
|
|
ALTER TABLE ONLY public.user_devices
|
|
ADD CONSTRAINT user_devices_pkey PRIMARY KEY (user_id, profile_id, device_id);
|
|
|
|
|
|
--
|
|
-- Name: user_subtitle_preferences user_subtitle_preferences_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_subtitle_preferences
|
|
ADD CONSTRAINT user_subtitle_preferences_pkey PRIMARY KEY (user_id, profile_id, series_id);
|
|
|
|
|
|
--
|
|
-- Name: user_taste_clusters user_taste_clusters_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_taste_clusters
|
|
ADD CONSTRAINT user_taste_clusters_pkey PRIMARY KEY (user_id, profile_id, cluster_idx);
|
|
|
|
|
|
--
|
|
-- Name: user_taste_profiles user_taste_profiles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_taste_profiles
|
|
ADD CONSTRAINT user_taste_profiles_pkey PRIMARY KEY (user_id, profile_id);
|
|
|
|
|
|
--
|
|
-- Name: user_watch_history user_watch_history_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_watch_history
|
|
ADD CONSTRAINT user_watch_history_pkey PRIMARY KEY (user_id, id);
|
|
|
|
|
|
--
|
|
-- Name: user_watch_progress user_watch_progress_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_watch_progress
|
|
ADD CONSTRAINT user_watch_progress_pkey PRIMARY KEY (user_id, profile_id, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: user_watchlist user_watchlist_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_watchlist
|
|
ADD CONSTRAINT user_watchlist_pkey PRIMARY KEY (user_id, profile_id, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.users
|
|
ADD CONSTRAINT users_email_key UNIQUE (email);
|
|
|
|
|
|
--
|
|
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.users
|
|
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
|
|
|
|
|
--
|
|
-- Name: users users_username_key; Type: CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.users
|
|
ADD CONSTRAINT users_username_key UNIQUE (username);
|
|
|
|
|
|
--
|
|
-- Name: admin_jobs_active_job_type_idx; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE UNIQUE INDEX admin_jobs_active_job_type_idx ON public.admin_jobs USING btree (job_type) WHERE (status = ANY (ARRAY['queued'::text, 'running'::text]));
|
|
|
|
|
|
--
|
|
-- Name: admin_jobs_expires_at_idx; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX admin_jobs_expires_at_idx ON public.admin_jobs USING btree (expires_at) WHERE (expires_at IS NOT NULL);
|
|
|
|
|
|
--
|
|
-- Name: admin_jobs_lookup_idx; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX admin_jobs_lookup_idx ON public.admin_jobs USING btree (job_type, status, requested_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_activity_log_client_ip; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_activity_log_client_ip ON public.activity_log USING btree (client_ip, "timestamp" DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_activity_log_timestamp; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_activity_log_timestamp ON public.activity_log USING btree ("timestamp" DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_activity_log_user_id; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_activity_log_user_id ON public.activity_log USING btree (user_id, "timestamp" DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_api_keys_api_key; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_api_keys_api_key ON public.api_keys USING btree (api_key);
|
|
|
|
|
|
--
|
|
-- Name: idx_api_keys_user_id; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_api_keys_user_id ON public.api_keys USING btree (user_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_cowatch_lookup; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_cowatch_lookup ON public.item_cowatch USING btree (item_id, jaccard_score DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_downloaded_subtitles_lang; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_downloaded_subtitles_lang ON public.downloaded_subtitles USING btree (media_file_id, language);
|
|
|
|
|
|
--
|
|
-- Name: idx_downloaded_subtitles_media; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_downloaded_subtitles_media ON public.downloaded_subtitles USING btree (media_file_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_episodes_series; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_episodes_series ON public.episodes USING btree (series_id, season_number, episode_number);
|
|
|
|
|
|
--
|
|
-- Name: idx_item_libraries_content; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_item_libraries_content ON public.media_item_libraries USING btree (content_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_item_libraries_folder; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_item_libraries_folder ON public.media_item_libraries USING btree (media_folder_id, first_seen_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_item_people_content_id; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_item_people_content_id ON public.item_people USING btree (content_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_item_people_person_id; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_item_people_person_id ON public.item_people USING btree (person_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_library_collection_items_order; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_library_collection_items_order ON public.library_collection_items USING btree (collection_id, "position", source_rank, media_item_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_library_collection_sync_runs_collection; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_library_collection_sync_runs_collection ON public.library_collection_sync_runs USING btree (collection_id, created_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_library_collections_library_order; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_library_collections_library_order ON public.library_collections USING btree (library_id, sort_order, title);
|
|
|
|
|
|
--
|
|
-- Name: idx_library_collections_visibility; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_library_collections_visibility ON public.library_collections USING btree (library_id, visibility, featured DESC, sort_order);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_files_content; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_files_content ON public.media_files USING btree (content_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_files_episode; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_files_episode ON public.media_files USING btree (episode_id) WHERE (episode_id IS NOT NULL);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_files_folder; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_files_folder ON public.media_files USING btree (media_folder_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_files_hash; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_files_hash ON public.media_files USING btree (file_hash);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_files_unlinked; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_files_unlinked ON public.media_files USING btree (content_id) WHERE ((content_id IS NOT NULL) AND (episode_id IS NULL));
|
|
|
|
|
|
--
|
|
-- Name: idx_media_folder_paths_folder; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_folder_paths_folder ON public.media_folder_paths USING btree (media_folder_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_item_embeddings_hnsw; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_item_embeddings_hnsw ON public.media_item_embeddings USING hnsw (embedding public.vector_cosine_ops);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_genres; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_genres ON public.media_items USING gin (genres);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_last_refreshed; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_last_refreshed ON public.media_items USING btree (last_refreshed NULLS FIRST) WHERE (last_refreshed IS NOT NULL);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_search; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_search ON public.media_items USING gin (to_tsvector('english'::regconfig, ((title || ' '::text) || COALESCE(overview, ''::text))));
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_search_exact_title; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_search_exact_title ON public.media_items USING btree (lower(title));
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_search_overview; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_search_overview ON public.media_items USING gin (to_tsvector('english'::regconfig, COALESCE(overview, ''::text)));
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_search_title_fields; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_search_title_fields ON public.media_items USING gin ((((setweight(to_tsvector('simple'::regconfig, COALESCE(title, ''::text)), 'A'::"char") || setweight(to_tsvector('simple'::regconfig, COALESCE(original_title, ''::text)), 'A'::"char")) || setweight(to_tsvector('simple'::regconfig, COALESCE(sort_title, ''::text)), 'B'::"char"))));
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_status; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_status ON public.media_items USING btree (status) WHERE (status <> 'matched'::text);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_type_created; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_type_created ON public.media_items USING btree (type, created_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_type_rating_imdb; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_type_rating_imdb ON public.media_items USING btree (type, rating_imdb DESC NULLS LAST);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_type_rating_tmdb; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_type_rating_tmdb ON public.media_items USING btree (type, rating_tmdb DESC NULLS LAST);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_type_sort_title; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_type_sort_title ON public.media_items USING btree (type, sort_title);
|
|
|
|
|
|
--
|
|
-- Name: idx_media_items_type_year; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_media_items_type_year ON public.media_items USING btree (type, year DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_page_sections_library; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_page_sections_library ON public.page_sections USING btree (library_id, "position") WHERE ((enabled = true) AND (library_id IS NOT NULL));
|
|
|
|
|
|
--
|
|
-- Name: idx_page_sections_scope; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_page_sections_scope ON public.page_sections USING btree (scope, "position") WHERE (enabled = true);
|
|
|
|
|
|
--
|
|
-- Name: idx_people_imdb_id; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE UNIQUE INDEX idx_people_imdb_id ON public.people USING btree (imdb_id) WHERE (imdb_id <> ''::text);
|
|
|
|
|
|
--
|
|
-- Name: idx_people_name; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_people_name ON public.people USING btree (name);
|
|
|
|
|
|
--
|
|
-- Name: idx_people_tmdb_id; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE UNIQUE INDEX idx_people_tmdb_id ON public.people USING btree (tmdb_id) WHERE (tmdb_id <> ''::text);
|
|
|
|
|
|
--
|
|
-- Name: idx_playback_history_admin_ended; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_playback_history_admin_ended ON public.playback_history_admin USING btree (ended_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_playback_history_admin_user_ended; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_playback_history_admin_user_ended ON public.playback_history_admin USING btree (user_id, ended_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_playback_history_admin_user_profile_ended; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_playback_history_admin_user_profile_ended ON public.playback_history_admin USING btree (user_id, profile_id, ended_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_playback_sync_node; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_playback_sync_node ON public.playback_sessions_sync USING btree (reporting_node, last_sync_at);
|
|
|
|
|
|
--
|
|
-- Name: idx_seasons_series; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_seasons_series ON public.seasons USING btree (series_id, season_number);
|
|
|
|
|
|
--
|
|
-- Name: idx_taste_clusters_embedding; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_taste_clusters_embedding ON public.user_taste_clusters USING hnsw (embedding public.vector_cosine_ops);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_collections_profile; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_collections_profile ON public.user_personal_collections USING btree (user_id, profile_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_favorites_profile; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_favorites_profile ON public.user_favorites USING btree (user_id, profile_id, added_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_device_settings_user_key; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_device_settings_user_key ON public.user_device_settings USING btree (user_id, key);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_profile_allowed_libraries_lookup; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_profile_allowed_libraries_lookup ON public.user_profile_allowed_libraries USING btree (user_id, profile_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_ratings_profile; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_ratings_profile ON public.user_ratings USING btree (user_id, profile_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_taste_profiles_hnsw; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_taste_profiles_hnsw ON public.user_taste_profiles USING hnsw (embedding public.vector_cosine_ops);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_watch_history_profile; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_watch_history_profile ON public.user_watch_history USING btree (user_id, profile_id, watched_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_watch_progress_profile; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_watch_progress_profile ON public.user_watch_progress USING btree (user_id, profile_id);
|
|
|
|
|
|
--
|
|
-- Name: idx_user_watchlist_profile; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX idx_user_watchlist_profile ON public.user_watchlist USING btree (user_id, profile_id, added_at DESC);
|
|
|
|
|
|
--
|
|
-- Name: jellycompat_sessions_expires_at_idx; Type: INDEX; Schema: public; Owner: -
|
|
--
|
|
|
|
CREATE INDEX jellycompat_sessions_expires_at_idx ON public.jellycompat_sessions USING btree (expires_at);
|
|
|
|
|
|
--
|
|
-- Name: activity_log activity_log_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.activity_log
|
|
ADD CONSTRAINT activity_log_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE SET NULL;
|
|
|
|
|
|
--
|
|
-- Name: api_keys api_keys_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.api_keys
|
|
ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: auth_sessions auth_sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.auth_sessions
|
|
ADD CONSTRAINT auth_sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles downloaded_subtitles_downloaded_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.downloaded_subtitles
|
|
ADD CONSTRAINT downloaded_subtitles_downloaded_by_fkey FOREIGN KEY (downloaded_by) REFERENCES public.users(id) ON DELETE SET NULL;
|
|
|
|
|
|
--
|
|
-- Name: downloaded_subtitles downloaded_subtitles_media_file_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.downloaded_subtitles
|
|
ADD CONSTRAINT downloaded_subtitles_media_file_id_fkey FOREIGN KEY (media_file_id) REFERENCES public.media_files(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: episodes episodes_season_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.episodes
|
|
ADD CONSTRAINT episodes_season_id_fkey FOREIGN KEY (season_id) REFERENCES public.seasons(content_id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: episodes episodes_series_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.episodes
|
|
ADD CONSTRAINT episodes_series_id_fkey FOREIGN KEY (series_id) REFERENCES public.media_items(content_id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: invite_codes invite_codes_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.invite_codes
|
|
ADD CONSTRAINT invite_codes_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id);
|
|
|
|
|
|
--
|
|
-- Name: item_people item_people_content_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.item_people
|
|
ADD CONSTRAINT item_people_content_id_fkey FOREIGN KEY (content_id) REFERENCES public.media_items(content_id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: item_people item_people_person_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.item_people
|
|
ADD CONSTRAINT item_people_person_id_fkey FOREIGN KEY (person_id) REFERENCES public.people(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: library_collection_items library_collection_items_collection_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collection_items
|
|
ADD CONSTRAINT library_collection_items_collection_id_fkey FOREIGN KEY (collection_id) REFERENCES public.library_collections(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: library_collection_items library_collection_items_media_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collection_items
|
|
ADD CONSTRAINT library_collection_items_media_item_id_fkey FOREIGN KEY (media_item_id) REFERENCES public.media_items(content_id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: library_collection_sync_runs library_collection_sync_runs_collection_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collection_sync_runs
|
|
ADD CONSTRAINT library_collection_sync_runs_collection_id_fkey FOREIGN KEY (collection_id) REFERENCES public.library_collections(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: library_collections library_collections_library_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_collections
|
|
ADD CONSTRAINT library_collections_library_id_fkey FOREIGN KEY (library_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: library_provider_chains library_provider_chains_media_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_provider_chains
|
|
ADD CONSTRAINT library_provider_chains_media_folder_id_fkey FOREIGN KEY (media_folder_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: library_provider_chains library_provider_chains_provider_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.library_provider_chains
|
|
ADD CONSTRAINT library_provider_chains_provider_id_fkey FOREIGN KEY (provider_id) REFERENCES public.metadata_providers(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: media_files media_files_episode_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_files
|
|
ADD CONSTRAINT media_files_episode_id_fkey FOREIGN KEY (episode_id) REFERENCES public.episodes(content_id) ON DELETE SET NULL;
|
|
|
|
|
|
--
|
|
-- Name: media_files media_files_media_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_files
|
|
ADD CONSTRAINT media_files_media_folder_id_fkey FOREIGN KEY (media_folder_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: media_folder_paths media_folder_paths_media_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_folder_paths
|
|
ADD CONSTRAINT media_folder_paths_media_folder_id_fkey FOREIGN KEY (media_folder_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: media_item_libraries media_item_libraries_content_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_item_libraries
|
|
ADD CONSTRAINT media_item_libraries_content_id_fkey FOREIGN KEY (content_id) REFERENCES public.media_items(content_id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: media_item_libraries media_item_libraries_media_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.media_item_libraries
|
|
ADD CONSTRAINT media_item_libraries_media_folder_id_fkey FOREIGN KEY (media_folder_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: page_sections page_sections_library_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.page_sections
|
|
ADD CONSTRAINT page_sections_library_id_fkey FOREIGN KEY (library_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: playback_history_admin playback_history_admin_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.playback_history_admin
|
|
ADD CONSTRAINT playback_history_admin_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: seasons seasons_series_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.seasons
|
|
ADD CONSTRAINT seasons_series_id_fkey FOREIGN KEY (series_id) REFERENCES public.media_items(content_id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_aggregates user_aggregates_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_aggregates
|
|
ADD CONSTRAINT user_aggregates_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_downloads user_downloads_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_downloads
|
|
ADD CONSTRAINT user_downloads_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_favorites user_favorites_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_favorites
|
|
ADD CONSTRAINT user_favorites_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_personal_collection_items user_personal_collection_items_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_personal_collection_items
|
|
ADD CONSTRAINT user_personal_collection_items_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_personal_collections user_personal_collections_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_personal_collections
|
|
ADD CONSTRAINT user_personal_collections_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_playback_sessions user_playback_sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_playback_sessions
|
|
ADD CONSTRAINT user_playback_sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_profile_allowed_libraries user_profile_allowed_libraries_library_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_profile_allowed_libraries
|
|
ADD CONSTRAINT user_profile_allowed_libraries_library_id_fkey FOREIGN KEY (library_id) REFERENCES public.media_folders(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_profile_allowed_libraries user_profile_allowed_libraries_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_profile_allowed_libraries
|
|
ADD CONSTRAINT user_profile_allowed_libraries_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_profiles user_profiles_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_profiles
|
|
ADD CONSTRAINT user_profiles_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_ratings user_ratings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_ratings
|
|
ADD CONSTRAINT user_ratings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_settings user_settings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_settings
|
|
ADD CONSTRAINT user_settings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
ALTER TABLE ONLY public.user_device_settings
|
|
ADD CONSTRAINT user_device_settings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
ALTER TABLE ONLY public.user_device_settings
|
|
ADD CONSTRAINT user_device_settings_profile_fkey FOREIGN KEY (user_id, profile_id) REFERENCES public.user_profiles(user_id, id) ON DELETE CASCADE;
|
|
|
|
ALTER TABLE ONLY public.user_devices
|
|
ADD CONSTRAINT user_devices_profile_fkey FOREIGN KEY (user_id, profile_id) REFERENCES public.user_profiles(user_id, id) ON DELETE CASCADE;
|
|
|
|
ALTER TABLE ONLY public.user_devices
|
|
ADD CONSTRAINT user_devices_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_subtitle_preferences user_subtitle_preferences_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_subtitle_preferences
|
|
ADD CONSTRAINT user_subtitle_preferences_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_taste_clusters user_taste_clusters_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_taste_clusters
|
|
ADD CONSTRAINT user_taste_clusters_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_taste_profiles user_taste_profiles_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_taste_profiles
|
|
ADD CONSTRAINT user_taste_profiles_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_watch_history user_watch_history_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_watch_history
|
|
ADD CONSTRAINT user_watch_history_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_watch_progress user_watch_progress_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_watch_progress
|
|
ADD CONSTRAINT user_watch_progress_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- Name: user_watchlist user_watchlist_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
|
--
|
|
|
|
ALTER TABLE ONLY public.user_watchlist
|
|
ADD CONSTRAINT user_watchlist_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
|
|
|
|
|
|
--
|
|
-- PostgreSQL database dump complete
|
|
--
|
|
|
|
|
|
|
|
-- Seed data
|
|
INSERT INTO public.server_settings (key, value) VALUES
|
|
('auth.access_token_expiry', '1h'),
|
|
('auth.refresh_token_expiry', '30d'),
|
|
('database.max_connections', '20'),
|
|
('s3.path_style', 'true'),
|
|
('s3.metadata_presign_expiry', '4h'),
|
|
('metadb.url', 'https://metadb.example.invalid'),
|
|
('playback.ffmpeg_path', '/usr/lib/jellyfin-ffmpeg/ffmpeg'),
|
|
('playback.transcode_dir', '/tmp/streamapp-transcode'),
|
|
('playback.hw_accel', 'auto'),
|
|
('playback.transcode_enabled', 'true'),
|
|
('playback.allow_hevc_encoding', 'false'),
|
|
('playback.transcode_ahead_segments', '30'),
|
|
('playback.segment_duration', '6'),
|
|
('scanner.schedule', '*/15 * * * *'),
|
|
('scanner.workers', '8'),
|
|
('scanner.file_removal_grace', '24h'),
|
|
('matcher.workers', '8'),
|
|
('matcher.batch_size', '500'),
|
|
('jellyfin_compat.public_url', 'http://127.0.0.1:8096'),
|
|
('jellyfin_compat.emulated_server_version', '10.12.0'),
|
|
('jellyfin_compat.server_name', 'StreamApp'),
|
|
('jellyfin_compat.session_ttl', '87600h'),
|
|
('jellyfin_compat.playback_session_ttl', '6h'),
|
|
('userdb.backend', 'postgres'),
|
|
('userdb.pool_max_open', '500'),
|
|
('userdb.idle_timeout', '12h'),
|
|
('userdb.litestream_sync', '1s'),
|
|
('userdb.stale_grace_seconds', '120')
|
|
ON CONFLICT (key) DO NOTHING;
|
|
INSERT INTO public.server_settings (key, value) VALUES ('playback.watched_threshold', '90')
|
|
ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- Restore search_path after pg_dump's set_config('search_path', '', false) on line 15
|
|
SELECT pg_catalog.set_config('search_path', 'public', false);
|