refactor: organize API around operation domains (#24)

* feat: reorganize API around operation domains

Restructure the oRPC contract from 14 resource-oriented routers to 8
domain-oriented routers for a cleaner public API surface.

- Consolidate 7 watch procedures into unified tracking.watch/unwatch
  with scope + ids input (movie, episode, season, series)
- Split dashboard across tracking (stats, history), library
  (continueWatching, upcoming), and discover (recommendations)
- Merge explore + search + discover into single discover router
- Absorb integrations into account.integrations
- Merge system.authConfig into system.publicInfo
- Collapse 6 admin setting endpoints into admin.settings.get/update
- Deduplicate platforms.list + explore.watchProviders into
  discover.platforms
- Add symmetric unwatchMovie/unwatchSeries core functions
- Rename titles.detail→get, titles.recommendations→similar,
  people.detail→get

BREAKING CHANGE: All client API paths have changed. REST paths now
mirror router structure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback on API reorganization

- Fix updateRating invalidation in native app — was only invalidating
  title queries, now calls invalidateTitleQueries() to also refresh
  tracking.userInfo (drives the rating UI)
- Make unwatchMovie status revert consistent with unwatchSeries — revert
  any non-watchlist status, not just "completed"
- Add sync invariant comment on handleWatch/handleUnwatch loops
- Remove unused queryClient import in native use-title-actions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: updateStatus NOT_FOUND error + rate() invalidation in native

- updateStatus now throws NOT_FOUND when quickAddTitle returns null
  (title doesn't exist), instead of silently succeeding
- Add NOT_FOUND error to updateStatus contract definition
- Fix rate() in native title-actions.ts to call invalidateTitleQueries()
  instead of only invalidating orpc.titles.key() — mirrors the fix
  already applied to the hook-based path in d2ddc0f

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: expand mobile app docs and add Play Store badge

- Add the Google Play badge to the README alongside the App Store badge
- Expand the mobile app docs with the Android/Play Store release details
- Refresh docs site dependencies and related package versions for the updated docs stack

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 15:44:05 -04:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 3906f6021d
commit dce333e128
123 changed files with 3566 additions and 4325 deletions
+10 -6
View File
@@ -7,10 +7,10 @@
"dependencies": {
"@takumi-rs/image-response": "^0.73.1",
"@vercel/analytics": "^2.0.1",
"fumadocs-core": "^16.7.5",
"fumadocs-core": "^16.7.6",
"fumadocs-mdx": "^14.2.11",
"fumadocs-openapi": "^10.4.1",
"fumadocs-ui": "^16.7.5",
"fumadocs-openapi": "^10.5.0",
"fumadocs-ui": "^16.7.6",
"lucide-react": "^0.577.0",
"next": "16.2.1",
"react": "19.2.4",
@@ -568,13 +568,13 @@
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
"fumadocs-core": ["fumadocs-core@16.7.5", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.8.2", "@orama/orama": "^3.1.18", "@shikijs/rehype": "^4.0.2", "@shikijs/transformers": "^4.0.2", "estree-util-value-to-estree": "^3.5.0", "github-slugger": "^2.0.0", "hast-util-to-estree": "^3.1.3", "hast-util-to-jsx-runtime": "^2.3.6", "image-size": "^2.0.2", "mdast-util-mdx": "^3.0.0", "mdast-util-to-markdown": "^2.1.2", "negotiator": "^1.0.0", "npm-to-yarn": "^3.0.1", "path-to-regexp": "^8.3.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-rehype": "^11.1.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^4.0.2", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "@mdx-js/mdx": "*", "@mixedbread/sdk": "^0.46.0", "@orama/core": "1.x.x", "@oramacloud/client": "2.x.x", "@tanstack/react-router": "1.x.x", "@types/estree-jsx": "*", "@types/hast": "*", "@types/mdast": "*", "@types/react": "*", "algoliasearch": "5.x.x", "flexsearch": "*", "lucide-react": "*", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "7.x.x", "waku": "^0.26.0 || ^0.27.0 || ^1.0.0", "zod": "4.x.x" }, "optionalPeers": ["@mdx-js/mdx", "@mixedbread/sdk", "@orama/core", "@oramacloud/client", "@tanstack/react-router", "@types/estree-jsx", "@types/hast", "@types/mdast", "@types/react", "algoliasearch", "flexsearch", "lucide-react", "next", "react", "react-dom", "react-router", "waku", "zod"] }, "sha512-qOAKQyNUCZQ0wRun2Jnw7f6VLEgCiGFomU+kQXUZI36cX9bJPiymXboKaSjCxT1PuIusjzUyxYTGapVZxzd9TA=="],
"fumadocs-core": ["fumadocs-core@16.7.6", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.8.2", "@orama/orama": "^3.1.18", "@shikijs/rehype": "^4.0.2", "@shikijs/transformers": "^4.0.2", "estree-util-value-to-estree": "^3.5.0", "github-slugger": "^2.0.0", "hast-util-to-estree": "^3.1.3", "hast-util-to-jsx-runtime": "^2.3.6", "image-size": "^2.0.2", "mdast-util-mdx": "^3.0.0", "mdast-util-to-markdown": "^2.1.2", "negotiator": "^1.0.0", "npm-to-yarn": "^3.0.1", "path-to-regexp": "^8.3.0", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-rehype": "^11.1.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^4.0.2", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "@mdx-js/mdx": "*", "@mixedbread/sdk": "^0.46.0", "@orama/core": "1.x.x", "@oramacloud/client": "2.x.x", "@tanstack/react-router": "1.x.x", "@types/estree-jsx": "*", "@types/hast": "*", "@types/mdast": "*", "@types/react": "*", "algoliasearch": "5.x.x", "flexsearch": "*", "lucide-react": "*", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "7.x.x", "waku": "^0.26.0 || ^0.27.0 || ^1.0.0", "zod": "4.x.x" }, "optionalPeers": ["@mdx-js/mdx", "@mixedbread/sdk", "@orama/core", "@oramacloud/client", "@tanstack/react-router", "@types/estree-jsx", "@types/hast", "@types/mdast", "@types/react", "algoliasearch", "flexsearch", "lucide-react", "next", "react", "react-dom", "react-router", "waku", "zod"] }, "sha512-d4HtGupFpcSWQqLbWh184yoEg6D70pH68NP77Ct4mI0N61t/Uy63wYj9sbS1h/m6jlijUIXC6rz8D5JApOB9Wg=="],
"fumadocs-mdx": ["fumadocs-mdx@14.2.11", "", { "dependencies": { "@mdx-js/mdx": "^3.1.1", "@standard-schema/spec": "^1.1.0", "chokidar": "^5.0.0", "esbuild": "^0.27.3", "estree-util-value-to-estree": "^3.5.0", "js-yaml": "^4.1.1", "mdast-util-mdx": "^3.0.0", "mdast-util-to-markdown": "^2.1.2", "picocolors": "^1.1.1", "picomatch": "^4.0.3", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3", "zod": "^4.3.6" }, "peerDependencies": { "@fumadocs/mdx-remote": "^1.4.0", "@types/mdast": "*", "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "^15.0.0 || ^16.0.0", "mdast-util-directive": "*", "next": "^15.3.0 || ^16.0.0", "react": "*", "vite": "6.x.x || 7.x.x || 8.x.x" }, "optionalPeers": ["@fumadocs/mdx-remote", "@types/mdast", "@types/mdx", "@types/react", "mdast-util-directive", "next", "react", "vite"], "bin": { "fumadocs-mdx": "dist/bin.js" } }, "sha512-j0gHKs45c62ARteE8/yBM2Nu2I8AE2Cs37ktPEdc/8EX7TL66XP74un5OpHp6itLyWTu8Jur0imOiiIDq8+rDg=="],
"fumadocs-openapi": ["fumadocs-openapi@10.4.1", "", { "dependencies": { "@fastify/deepmerge": "^3.2.1", "@fumari/json-schema-ts": "^0.0.2", "@fumari/stf": "1.0.3", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.4", "@scalar/json-magic": "^0.12.4", "@scalar/openapi-upgrader": "^0.2.1", "ajv": "^8.18.0", "class-variance-authority": "^0.7.1", "dereference-json-schema": "^0.2.2", "github-slugger": "^2.0.0", "hast-util-to-jsx-runtime": "^2.3.6", "js-yaml": "^4.1.1", "lucide-react": "^0.577.0", "next-themes": "^0.4.6", "openapi-sampler": "^1.7.2", "react-hook-form": "^7.71.2", "remark": "^15.0.1", "remark-rehype": "^11.1.2", "tailwind-merge": "^3.5.0", "xml-js": "^1.6.11" }, "peerDependencies": { "@scalar/api-client-react": "*", "@types/react": "*", "fumadocs-core": "^16.7.0", "fumadocs-ui": "^16.7.0", "json-schema-typed": "*", "react": "^19.2.0", "react-dom": "^19.2.0", "shiki": "*" }, "optionalPeers": ["@scalar/api-client-react", "@types/react", "json-schema-typed", "shiki"] }, "sha512-iMEDyANcikDePUFlBE5sffjTbrUI5LELuIla+6kTZJYEuoQMLUK+plOnXq1iacPBMeCS7cSFadvE/Vp/h7oA+g=="],
"fumadocs-openapi": ["fumadocs-openapi@10.5.0", "", { "dependencies": { "@fastify/deepmerge": "^3.2.1", "@fumari/json-schema-ts": "^0.0.2", "@fumari/stf": "1.0.3", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-slot": "^1.2.4", "@scalar/json-magic": "^0.12.4", "@scalar/openapi-upgrader": "^0.2.2", "ajv": "^8.18.0", "class-variance-authority": "^0.7.1", "dereference-json-schema": "^0.2.2", "github-slugger": "^2.0.0", "hast-util-to-jsx-runtime": "^2.3.6", "js-yaml": "^4.1.1", "lucide-react": "^1.6.0", "next-themes": "^0.4.6", "openapi-sampler": "^1.7.2", "react-hook-form": "^7.72.0", "remark": "^15.0.1", "remark-rehype": "^11.1.2", "tailwind-merge": "^3.5.0", "xml-js": "^1.6.11" }, "peerDependencies": { "@scalar/api-client-react": "*", "@types/react": "*", "fumadocs-core": "^16.7.0", "fumadocs-ui": "^16.7.0", "json-schema-typed": "*", "react": "^19.2.0", "react-dom": "^19.2.0", "shiki": "*" }, "optionalPeers": ["@scalar/api-client-react", "@types/react", "json-schema-typed", "shiki"] }, "sha512-EG3/U/NI/ha0xhIUiUIKiRSmLS4AOjn+4xkZXOoiLB5rlV+HyHsq9qcN+wKgbK9u4qoNuAcMzTEYUXScoSUbXA=="],
"fumadocs-ui": ["fumadocs-ui@16.7.5", "", { "dependencies": { "@fumadocs/tailwind": "0.0.3", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-direction": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-presence": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "class-variance-authority": "^0.7.1", "lucide-react": "^0.577.0", "motion": "^12.38.0", "next-themes": "^0.4.6", "react-medium-image-zoom": "^5.4.1", "react-remove-scroll": "^2.7.2", "rehype-raw": "^7.0.0", "scroll-into-view-if-needed": "^3.1.0", "tailwind-merge": "^3.5.0", "unist-util-visit": "^5.1.0" }, "peerDependencies": { "@takumi-rs/image-response": "*", "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "16.7.5", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "shiki": "*" }, "optionalPeers": ["@takumi-rs/image-response", "@types/mdx", "@types/react", "next", "shiki"] }, "sha512-i9zvslCFc25hxYsAmkBCJUgAwvWbKwMn4Xd5L7LcnQwW60r8VPihkfeWB/DDtHv4wJTGOPl+neb1AgNkm1Cujw=="],
"fumadocs-ui": ["fumadocs-ui@16.7.6", "", { "dependencies": { "@fumadocs/tailwind": "0.0.3", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-direction": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-presence": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "class-variance-authority": "^0.7.1", "lucide-react": "^1.6.0", "motion": "^12.38.0", "next-themes": "^0.4.6", "react-medium-image-zoom": "^5.4.1", "react-remove-scroll": "^2.7.2", "rehype-raw": "^7.0.0", "scroll-into-view-if-needed": "^3.1.0", "tailwind-merge": "^3.5.0", "unist-util-visit": "^5.1.0" }, "peerDependencies": { "@takumi-rs/image-response": "*", "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "16.7.6", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "shiki": "*" }, "optionalPeers": ["@takumi-rs/image-response", "@types/mdx", "@types/react", "next", "shiki"] }, "sha512-wjZnm8SiX2lj5zWOlOHnzSZ0YBFwNqYGBX1u5F3mZtdIkmkDVs+3+JngCkRHNZzYJVBulXjp8t5wzBz0yDJa8w=="],
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
@@ -968,6 +968,10 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"fumadocs-openapi/lucide-react": ["lucide-react@1.7.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg=="],
"fumadocs-ui/lucide-react": ["lucide-react@1.7.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
@@ -16,4 +16,4 @@ _openapi:
Create a new media server integration or update an existing one. Generates a unique webhook token for the provider.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/integrations","method":"post"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/account/integrations","method":"post"}]} />
@@ -14,4 +14,4 @@ _openapi:
Remove a media server integration and all its event history.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/integrations/{provider}","method":"delete"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/account/integrations/{provider}","method":"delete"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch all configured media server integrations for the current user, including recent webhook/sync events for each.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/integrations","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/account/integrations","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Generate a new webhook token for an integration. The old token is immediately invalidated.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/integrations/{provider}/regenerate-token","method":"post"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/account/integrations/{provider}/regenerate-token","method":"post"}]} />
@@ -1,5 +1,5 @@
---
title: Get telemetry status
title: Get admin settings
full: true
_openapi:
method: GET
@@ -8,12 +8,12 @@ _openapi:
headings: []
contents:
- content: >-
Fetch whether anonymous telemetry is enabled and when the last report
was sent.
Fetch all admin-configurable settings: registration, update checks,
and telemetry.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch whether anonymous telemetry is enabled and when the last report was sent.
Fetch all admin-configurable settings: registration, update checks, and telemetry.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/telemetry","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/settings","method":"get"}]} />
@@ -0,0 +1,19 @@
---
title: Update admin settings
full: true
_openapi:
method: PATCH
toc: []
structuredData:
headings: []
contents:
- content: >-
Partially update admin settings. Only provided sections are changed;
omitted sections keep their current values.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Partially update admin settings. Only provided sections are changed; omitted sections keep their current values.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/settings","method":"patch"}]} />
@@ -1,17 +0,0 @@
---
title: Toggle registration
full: true
_openapi:
method: PUT
toc: []
structuredData:
headings: []
contents:
- content: Open or close new user registration.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Open or close new user registration.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/registration","method":"put"}]} />
@@ -1,17 +0,0 @@
---
title: Toggle telemetry
full: true
_openapi:
method: PUT
toc: []
structuredData:
headings: []
contents:
- content: Enable or disable anonymous telemetry reporting.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Enable or disable anonymous telemetry reporting.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/telemetry","method":"put"}]} />
@@ -1,17 +0,0 @@
---
title: Toggle update checks
full: true
_openapi:
method: PUT
toc: []
structuredData:
headings: []
contents:
- content: Enable or disable automatic update checks against the public API.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Enable or disable automatic update checks against the public API.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/update-check","method":"put"}]} />
@@ -1,19 +0,0 @@
---
title: Get dashboard statistics
full: true
_openapi:
method: GET
toc: []
structuredData:
headings: []
contents:
- content: >-
Fetch summary counts for the current user: movies watched this month,
episodes this week, library size, and completed titles.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch summary counts for the current user: movies watched this month, episodes this week, library size, and completed titles.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/dashboard/stats","method":"get"}]} />
@@ -1,5 +1,5 @@
---
title: Discover titles by genre
title: Browse titles with filters
full: true
_openapi:
method: GET
@@ -16,4 +16,4 @@ _openapi:
Browse movies or TV shows filtered by genre, sorted by popularity. Returns user statuses and episode progress.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/browse","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch the list of TMDB genres for movies or TV shows. Used to populate genre filters for discovery.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/explore/genres","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/genres","method":"get"}]} />
@@ -1,5 +1,5 @@
---
title: Get update check status
title: List available streaming platforms
full: true
_openapi:
method: GET
@@ -8,12 +8,12 @@ _openapi:
headings: []
contents:
- content: >-
Fetch whether automatic update checks are enabled, and the latest
cached check result if available.
Fetch all available streaming platforms, ordered by popularity.
Includes subscription status for filtering.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch whether automatic update checks are enabled, and the latest cached check result if available.
Fetch all available streaming platforms, ordered by popularity. Includes subscription status for filtering.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/update-check","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/platforms","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch currently popular movies or TV shows from TMDB with the user's tracking statuses.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/explore/popular","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/popular","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch personalized title recommendations based on the user's library and watch history.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/dashboard/recommendations","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/recommendations","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Full-text search across movies, TV shows, and people via TMDB. Optionally filter by media type.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/search","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/search","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch today's trending movies and/or TV shows from TMDB, including a featured hero title and the user's statuses.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/explore/trending","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/discover/trending","method":"get"}]} />
@@ -1,19 +0,0 @@
---
title: Batch mark episodes watched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: >-
Record watch events for multiple episodes at once. Useful for marking
a range of episodes as watched.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Record watch events for multiple episodes at once. Useful for marking a range of episodes as watched.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/episodes/batch-watch","method":"post"}]} />
@@ -1,17 +0,0 @@
---
title: Mark episode unwatched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: Remove the watch record for a single TV episode.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove the watch record for a single TV episode.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/episodes/{id}/unwatch","method":"post"}]} />
@@ -1,17 +0,0 @@
---
title: Mark episode watched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: Record a watch event for a single TV episode.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Record a watch event for a single TV episode.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/episodes/{id}/watch","method":"post"}]} />
@@ -1,19 +0,0 @@
---
title: List available watch providers
full: true
_openapi:
method: GET
toc: []
structuredData:
headings: []
contents:
- content: >-
Fetch the list of streaming providers available in the configured
region. Used to populate provider filter dropdowns.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch the list of streaming providers available in the configured region. Used to populate provider filter dropdowns.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/explore/watch-providers","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch TV shows the user is currently watching, with the next unwatched episode and progress for each.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/dashboard/continue-watching","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/library/continue-watching","method":"get"}]} />
@@ -1,5 +1,5 @@
---
title: Get registration status
title: Get library statistics
full: true
_openapi:
method: GET
@@ -7,11 +7,11 @@ _openapi:
structuredData:
headings: []
contents:
- content: Check whether new user registration is currently open or closed.
- content: 'Fetch aggregate library counts: total titles and completed titles.'
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Check whether new user registration is currently open or closed.
Fetch aggregate library counts: total titles and completed titles.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/admin/registration","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/library/stats","method":"get"}]} />
@@ -16,4 +16,4 @@ _openapi:
Fetch upcoming episodes and movie releases for titles in the user's library, sorted by date. Supports cursor-based pagination.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/dashboard/upcoming","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/library/upcoming","method":"get"}]} />
@@ -1,17 +0,0 @@
---
title: List all platforms
full: true
_openapi:
method: GET
toc: []
structuredData:
headings: []
contents:
- content: Fetch all available streaming platforms, ordered by popularity.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch all available streaming platforms, ordered by popularity.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/platforms","method":"get"}]} />
@@ -1,19 +0,0 @@
---
title: Mark season unwatched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: >-
Remove all watch records for episodes in a season in a single
operation.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove all watch records for episodes in a season in a single operation.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/seasons/{id}/unwatch","method":"post"}]} />
@@ -1,17 +0,0 @@
---
title: Mark season watched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: Mark all episodes in a season as watched in a single operation.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Mark all episodes in a season as watched in a single operation.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/seasons/{id}/watch","method":"post"}]} />
@@ -1,20 +0,0 @@
---
title: Get authentication config
full: true
_openapi:
method: GET
toc: []
structuredData:
headings: []
contents:
- content: >-
Fetch the authentication configuration including OIDC availability,
password login status, and registration state. Does not require
authentication.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch the authentication configuration including OIDC availability, password login status, and registration state. Does not require authentication.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/system/auth-config","method":"get"}]} />
@@ -8,12 +8,12 @@ _openapi:
headings: []
contents:
- content: >-
Fetch public information about this Sofa instance. Does not require
authentication. Used by the login screen to display instance details.
Fetch public information about this Sofa instance including
authentication configuration. Does not require authentication.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch public information about this Sofa instance. Does not require authentication. Used by the login screen to display instance details.
Fetch public information about this Sofa instance including authentication configuration. Does not require authentication.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/system/public-info","method":"get"}]} />
@@ -1,20 +0,0 @@
---
title: Quick add title to library
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: >-
Add a title to the user's watchlist and trigger a full TMDB import if
needed. If the title already exists in the user's library, returns
alreadyAdded: true.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Add a title to the user's watchlist and trigger a full TMDB import if needed. If the title already exists in the user's library, returns alreadyAdded: true.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/quick-add","method":"post"}]} />
@@ -1,5 +1,5 @@
---
title: Get title recommendations
title: Get similar titles
full: true
_openapi:
method: GET
@@ -16,4 +16,4 @@ _openapi:
Fetch similar titles based on locally cached recommendation data, along with the user's statuses for each.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/recommendations","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/similar","method":"get"}]} />
@@ -1,19 +0,0 @@
---
title: Mark all episodes watched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: >-
Mark every episode of a TV show as watched. Requires seasons to be
hydrated first.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Mark every episode of a TV show as watched. Requires seasons to be hydrated first.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/watch-all","method":"post"}]} />
@@ -1,17 +0,0 @@
---
title: Mark movie as watched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: Log a watch event for a movie. Automatically sets status to completed.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Log a watch event for a movie. Automatically sets status to completed.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/watch","method":"post"}]} />
@@ -14,4 +14,4 @@ _openapi:
Set a 0-5 star rating for a title. Use 0 to clear the rating.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/rating","method":"put"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/tracking/titles/{id}/rating","method":"put"}]} />
@@ -1,5 +1,5 @@
---
title: Get watch history
title: Get watch statistics
full: true
_openapi:
method: GET
@@ -9,11 +9,11 @@ _openapi:
contents:
- content: >-
Fetch the user's watch counts grouped by time period. Useful for
rendering activity charts.
rendering activity charts and dashboard counters.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Fetch the user's watch counts grouped by time period. Useful for rendering activity charts.
Fetch the user's watch counts grouped by time period. Useful for rendering activity charts and dashboard counters.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/dashboard/watch-history","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/tracking/stats","method":"get"}]} />
@@ -0,0 +1,19 @@
---
title: Remove watch records
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: >-
Remove watch records for one or more items. Use scope to indicate what
the IDs refer to: movie, episode, season, or series.
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove watch records for one or more items. Use scope to indicate what the IDs refer to: movie, episode, season, or series.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/tracking/unwatch","method":"post"}]} />
@@ -16,4 +16,4 @@ _openapi:
Set the user's tracking status for a title. Use null to remove the title from the library entirely.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/status","method":"put"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/tracking/titles/{id}/status","method":"put"}]} />
@@ -1,5 +1,5 @@
---
title: Get user's title info
title: Get user's tracking info for a title
full: true
_openapi:
method: GET
@@ -16,4 +16,4 @@ _openapi:
Fetch the current user's tracking status, rating, and watched episode IDs for a title.
<APIPage document={"./public/openapi.json"} operations={[{"path":"/titles/{id}/user-info","method":"get"}]} />
<APIPage document={"./public/openapi.json"} operations={[{"path":"/tracking/titles/{id}","method":"get"}]} />
@@ -0,0 +1,20 @@
---
title: Mark items as watched
full: true
_openapi:
method: POST
toc: []
structuredData:
headings: []
contents:
- content: >-
Mark one or more items as watched. Use scope to indicate what the IDs
refer to: movie (title IDs), episode (episode IDs), season (season
IDs), or series (title IDs to mark all episodes).
---
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Mark one or more items as watched. Use scope to indicate what the IDs refer to: movie (title IDs), episode (episode IDs), season (season IDs), or series (title IDs to mark all episodes).
<APIPage document={"./public/openapi.json"} operations={[{"path":"/tracking/watch","method":"post"}]} />
+109 -1
View File
@@ -4,10 +4,118 @@ description: Set up the iOS and Android app.
icon: Smartphone
---
export const AppStoreBadge = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 119.664 40"
{...props}
>
<path
d="M110.135 0H9.535Q8.986 0 8.44.002q-.457.004-.919.013A13 13 0 0 0 5.517.19a6.7 6.7 0 0 0-1.9.627 6.4 6.4 0 0 0-1.62 1.18A6.3 6.3 0 0 0 .82 3.617 6.6 6.6 0 0 0 .195 5.52a13 13 0 0 0-.179 2.002c-.01.307-.01.615-.015.921V31.56c.005.31.006.61.015.921a13 13 0 0 0 .18 2.002 6.6 6.6 0 0 0 .624 1.905A6.2 6.2 0 0 0 1.998 38a6.3 6.3 0 0 0 1.618 1.179 6.7 6.7 0 0 0 1.901.63 13.5 13.5 0 0 0 2.004.177c.31.007.613.011.919.011.366.002.728.002 1.095.002h100.6c.36 0 .724 0 1.084-.002.304 0 .617-.004.922-.01a13 13 0 0 0 2-.178 6.8 6.8 0 0 0 1.908-.63A6.3 6.3 0 0 0 117.666 38a6.4 6.4 0 0 0 1.182-1.614 6.6 6.6 0 0 0 .619-1.905 13.5 13.5 0 0 0 .185-2.002c.004-.31.004-.61.004-.921.008-.364.008-.725.008-1.094V9.536q.002-.549-.008-1.092.001-.46-.004-.92a13.5 13.5 0 0 0-.185-2.003 6.6 6.6 0 0 0-.62-1.903 6.47 6.47 0 0 0-2.798-2.8 6.8 6.8 0 0 0-1.908-.627 13 13 0 0 0-2-.176c-.305-.005-.618-.011-.922-.013-.36-.002-.725-.002-1.084-.002"
style={{
fill: "#a6a6a6",
}}
/>
<path d="M8.445 39.125c-.305 0-.602-.004-.904-.01a13 13 0 0 1-1.87-.164 5.9 5.9 0 0 1-1.656-.548 5.4 5.4 0 0 1-1.397-1.016 5.3 5.3 0 0 1-1.02-1.397 5.7 5.7 0 0 1-.544-1.657 12.4 12.4 0 0 1-.166-1.875c-.007-.21-.015-.913-.015-.913v-23.1s.009-.692.015-.895a12.4 12.4 0 0 1 .165-1.872 5.8 5.8 0 0 1 .544-1.662 5.4 5.4 0 0 1 1.015-1.398 5.6 5.6 0 0 1 1.402-1.023 5.8 5.8 0 0 1 1.653-.544A12.6 12.6 0 0 1 7.543.887l.902-.012h102.769l.913.013a12.4 12.4 0 0 1 1.858.162 6 6 0 0 1 1.671.548 5.6 5.6 0 0 1 2.415 2.42 5.8 5.8 0 0 1 .535 1.649 13 13 0 0 1 .174 1.887c.003.283.003.588.003.89.008.375.008.732.008 1.092v20.929c0 .363 0 .718-.008 1.075 0 .325 0 .623-.004.93a13 13 0 0 1-.17 1.853 5.7 5.7 0 0 1-.54 1.67 5.5 5.5 0 0 1-1.016 1.386 5.4 5.4 0 0 1-1.4 1.022 5.9 5.9 0 0 1-1.668.55 12.5 12.5 0 0 1-1.869.163c-.293.007-.6.011-.897.011l-1.084.002Z" />
<g>
<g>
<path
d="M24.769 20.3a4.95 4.95 0 0 1 2.356-4.151 5.07 5.07 0 0 0-3.99-2.158c-1.68-.176-3.308 1.005-4.164 1.005-.872 0-2.19-.988-3.608-.958a5.32 5.32 0 0 0-4.473 2.728c-1.934 3.348-.491 8.269 1.361 10.976.927 1.325 2.01 2.805 3.428 2.753 1.387-.058 1.905-.885 3.58-.885 1.658 0 2.144.885 3.59.852 1.489-.025 2.426-1.332 3.32-2.67a11 11 0 0 0 1.52-3.092 4.78 4.78 0 0 1-2.92-4.4m-2.732-8.09a4.87 4.87 0 0 0 1.115-3.49 4.96 4.96 0 0 0-3.208 1.66 4.64 4.64 0 0 0-1.144 3.36 4.1 4.1 0 0 0 3.237-1.53"
style={{
fill: "#fff",
}}
/>
</g>
<path
d="M42.302 27.14H37.57l-1.137 3.356h-2.005l4.484-12.418h2.083l4.483 12.418h-2.039Zm-4.243-1.55h3.752l-1.85-5.446h-.051Zm17.101.38c0 2.813-1.506 4.62-3.779 4.62a3.07 3.07 0 0 1-2.848-1.583h-.043v4.484h-1.86V21.442h1.8v1.506h.033a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622m-1.91 0c0-1.833-.948-3.038-2.393-3.038-1.42 0-2.375 1.23-2.375 3.038 0 1.824.955 3.046 2.375 3.046 1.445 0 2.393-1.197 2.393-3.046m11.876 0c0 2.813-1.506 4.62-3.779 4.62a3.07 3.07 0 0 1-2.848-1.583h-.043v4.484h-1.859V21.442h1.799v1.506h.034a3.21 3.21 0 0 1 2.883-1.6c2.298 0 3.813 1.816 3.813 4.622m-1.91 0c0-1.833-.948-3.038-2.393-3.038-1.42 0-2.375 1.23-2.375 3.038 0 1.824.955 3.046 2.375 3.046 1.445 0 2.392-1.197 2.392-3.046m8.496 1.066c.138 1.232 1.334 2.04 2.97 2.04 1.566 0 2.693-.808 2.693-1.919 0-.964-.68-1.54-2.29-1.936l-1.609-.388c-2.28-.55-3.339-1.617-3.339-3.348 0-2.142 1.867-3.614 4.519-3.614 2.624 0 4.423 1.472 4.483 3.614h-1.876c-.112-1.239-1.136-1.987-2.634-1.987s-2.521.757-2.521 1.858c0 .878.654 1.395 2.255 1.79l1.368.336c2.548.603 3.606 1.626 3.606 3.443 0 2.323-1.85 3.778-4.793 3.778-2.754 0-4.614-1.42-4.734-3.667ZM83.346 19.3v2.142h1.722v1.472h-1.722v4.991c0 .776.345 1.137 1.102 1.137a6 6 0 0 0 .611-.043v1.463a5 5 0 0 1-1.032.086c-1.833 0-2.548-.689-2.548-2.445v-5.189h-1.316v-1.472h1.316V19.3Zm2.719 6.67c0-2.849 1.678-4.639 4.294-4.639 2.625 0 4.295 1.79 4.295 4.639 0 2.856-1.661 4.638-4.295 4.638s-4.294-1.782-4.294-4.638m6.695 0c0-1.954-.895-3.108-2.401-3.108s-2.4 1.162-2.4 3.108c0 1.962.894 3.106 2.4 3.106s2.401-1.144 2.401-3.106m3.426-4.528h1.773v1.541h.043a2.16 2.16 0 0 1 2.177-1.635 3 3 0 0 1 .637.069v1.738a2.6 2.6 0 0 0-.835-.112 1.873 1.873 0 0 0-1.937 2.083v5.37h-1.858Zm13.198 6.395c-.25 1.643-1.85 2.771-3.898 2.771-2.634 0-4.269-1.764-4.269-4.595 0-2.84 1.644-4.682 4.19-4.682 2.506 0 4.08 1.72 4.08 4.466v.637h-6.394v.112a2.358 2.358 0 0 0 2.436 2.564 2.05 2.05 0 0 0 2.09-1.273Zm-6.282-2.702h4.526a2.177 2.177 0 0 0-2.22-2.298 2.29 2.29 0 0 0-2.306 2.298"
style={{
fill: "#fff",
}}
/>
</g>
<path
d="M37.826 8.731a2.64 2.64 0 0 1 2.808 2.965c0 1.906-1.03 3.002-2.808 3.002h-2.155V8.73Zm-1.228 5.123h1.125a1.876 1.876 0 0 0 1.967-2.146 1.88 1.88 0 0 0-1.967-2.134h-1.125Zm5.082-1.41a2.133 2.133 0 1 1 4.248 0 2.134 2.134 0 1 1-4.247 0m3.334 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.571-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55m6.558 2.254h-.922l-.93-3.317h-.07l-.927 3.317h-.913l-1.242-4.503h.902l.806 3.436h.067l.926-3.436h.852l.926 3.436h.07l.803-3.436h.889Zm2.281-4.503h.855v.715h.066a1.35 1.35 0 0 1 1.344-.802 1.465 1.465 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.033 1.033 0 0 0-1.075 1.141v2.635h-.888Zm5.24-1.758h.888v6.26h-.888Zm2.124 4.007a2.133 2.133 0 1 1 4.247 0 2.134 2.134 0 1 1-4.247 0m3.333 0c0-.976-.439-1.547-1.208-1.547-.773 0-1.207.571-1.207 1.547 0 .984.434 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55m1.849.98c0-.81.604-1.278 1.676-1.344l1.22-.07v-.389c0-.475-.315-.744-.922-.744-.497 0-.84.182-.939.5h-.86c.09-.773.818-1.27 1.84-1.27 1.128 0 1.765.563 1.765 1.514v3.077h-.855v-.633h-.07a1.52 1.52 0 0 1-1.353.707 1.36 1.36 0 0 1-1.501-1.348m2.895-.384v-.377l-1.1.07c-.62.042-.9.253-.9.65 0 .405.351.64.834.64a1.06 1.06 0 0 0 1.166-.983m2.052-.596c0-1.423.732-2.324 1.87-2.324a1.48 1.48 0 0 1 1.38.79h.067V8.437h.888v6.26h-.851v-.71h-.07a1.56 1.56 0 0 1-1.415.785c-1.145 0-1.869-.901-1.869-2.328m.918 0c0 .955.45 1.53 1.203 1.53.75 0 1.212-.583 1.212-1.526 0-.938-.468-1.53-1.212-1.53-.748 0-1.203.58-1.203 1.526m6.964 0a2.133 2.133 0 1 1 4.247 0 2.134 2.134 0 1 1-4.247 0m3.333 0c0-.976-.438-1.547-1.208-1.547-.772 0-1.207.571-1.207 1.547 0 .984.435 1.55 1.207 1.55.77 0 1.208-.57 1.208-1.55m2.107-2.249h.855v.715h.066a1.35 1.35 0 0 1 1.344-.802 1.465 1.465 0 0 1 1.559 1.675v2.915h-.889v-2.692c0-.724-.314-1.084-.972-1.084a1.033 1.033 0 0 0-1.075 1.141v2.635h-.889Zm8.845-1.121v1.141h.976v.749h-.976v2.315c0 .472.194.679.637.679a3 3 0 0 0 .339-.021v.74a3 3 0 0 1-.484.046c-.988 0-1.381-.348-1.381-1.216v-2.543h-.715v-.749h.715V9.074Zm2.19-.637h.88v2.481h.07a1.39 1.39 0 0 1 1.374-.806 1.483 1.483 0 0 1 1.55 1.679v2.907h-.889V12.01c0-.72-.335-1.084-.963-1.084a1.052 1.052 0 0 0-1.134 1.142v2.63h-.888Zm9.056 5.045a1.83 1.83 0 0 1-1.95 1.303 2.045 2.045 0 0 1-2.081-2.325 2.077 2.077 0 0 1 2.076-2.352c1.253 0 2.009.856 2.009 2.27v.31h-3.18v.05a1.19 1.19 0 0 0 1.2 1.29 1.08 1.08 0 0 0 1.07-.546Zm-3.126-1.451h2.275a1.086 1.086 0 0 0-1.109-1.167 1.15 1.15 0 0 0-1.166 1.167"
style={{
fill: "#fff",
}}
/>
</svg>
);
export const GooglePlayBadge = (props) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 238.96 70.87" {...props}>
<rect x={-0.11} width={239.17} height={70.87} rx={8.86} ry={8.86} />
<path
d="M230.21 1.42c4.1 0 7.44 3.34 7.44 7.44v53.15c0 4.1-3.34 7.44-7.44 7.44H8.75c-4.1 0-7.44-3.34-7.44-7.44V8.86c0-4.1 3.34-7.44 7.44-7.44zm0-1.42H8.75C3.88 0-.11 3.99-.11 8.86v53.15c0 4.87 3.99 8.86 8.86 8.86h221.46c4.87 0 8.86-3.99 8.86-8.86V8.86c0-4.87-3.99-8.86-8.86-8.86"
style={{
fill: "#a6a6a6",
}}
/>
<path
d="M83.9 18.15c0 1.48-.44 2.67-1.32 3.55-1 1.05-2.3 1.57-3.9 1.57s-2.84-.53-3.91-1.59-1.61-2.38-1.61-3.96.54-2.89 1.61-3.96c1.07-1.06 2.38-1.6 3.91-1.6.76 0 1.49.15 2.18.44.69.3 1.25.69 1.66 1.19l-.93.94q-1.05-1.26-2.91-1.26c-1.12 0-2.09.39-2.9 1.18s-1.22 1.81-1.22 3.06.41 2.28 1.22 3.06c.82.79 1.78 1.18 2.9 1.18 1.19 0 2.18-.4 2.97-1.19.51-.52.81-1.23.89-2.15h-3.86v-1.28h5.15c.05.28.07.54.07.8Zm8.17-4.44h-4.84v3.37h4.37v1.28h-4.37v3.37h4.84v1.31h-6.21V12.41h6.21v1.31Zm5.76 9.32h-1.37v-9.32h-2.97V12.4h7.3v1.31h-2.97v9.32Zm8.25 0V12.4h1.36v10.63zm7.43 0h-1.37v-9.32h-2.97V12.4h7.3v1.31h-2.97v9.32Zm16.79-1.37c-1.05 1.08-2.34 1.61-3.9 1.61s-2.85-.54-3.9-1.61-1.57-2.39-1.57-3.94.52-2.87 1.57-3.94c1.04-1.08 2.34-1.61 3.9-1.61s2.84.54 3.89 1.62 1.57 2.39 1.57 3.93-.52 2.87-1.57 3.94Zm-6.78-.89c.79.8 1.75 1.19 2.89 1.19s2.1-.4 2.89-1.19c.79-.8 1.18-1.81 1.18-3.05s-.4-2.26-1.18-3.05c-.78-.8-1.75-1.19-2.89-1.19s-2.1.4-2.89 1.19c-.78.8-1.18 1.81-1.18 3.05s.4 2.26 1.18 3.05Zm10.27 2.26V12.4h1.66l5.17 8.27h.06l-.06-2.05V12.4h1.37v10.63h-1.43l-5.41-8.67h-.06l.06 2.05v6.62h-1.37Z"
style={{
stroke: "#fff",
strokeMiterlimit: 10,
strokeWidth: ".2px",
fill: "#fff",
}}
/>
<path
d="M120.61 38.54c-4.17 0-7.56 3.17-7.56 7.53s3.4 7.53 7.56 7.53 7.56-3.2 7.56-7.53-3.4-7.53-7.56-7.53m0 12.1c-2.28 0-4.25-1.88-4.25-4.57s1.97-4.57 4.25-4.57 4.25 1.85 4.25 4.57-1.97 4.57-4.25 4.57m-16.5-12.1c-4.17 0-7.56 3.17-7.56 7.53s3.4 7.53 7.56 7.53 7.56-3.2 7.56-7.53-3.4-7.53-7.56-7.53m0 12.1c-2.28 0-4.25-1.88-4.25-4.57s1.97-4.57 4.25-4.57 4.25 1.85 4.25 4.57-1.97 4.57-4.25 4.57m-19.63-9.79v3.2h7.65c-.23 1.8-.83 3.11-1.74 4.03-1.11 1.11-2.85 2.34-5.91 2.34-4.71 0-8.39-3.8-8.39-8.51s3.68-8.51 8.39-8.51c2.54 0 4.4 1 5.76 2.28l2.26-2.26c-1.91-1.83-4.45-3.22-8.02-3.22-6.45 0-11.87 5.25-11.87 11.7s5.42 11.7 11.87 11.7c3.48 0 6.11-1.14 8.16-3.28 2.11-2.11 2.77-5.08 2.77-7.48 0-.74-.06-1.43-.17-2H84.48Zm80.27 2.48c-.63-1.68-2.54-4.8-6.45-4.8s-7.11 3.05-7.11 7.53c0 4.22 3.2 7.53 7.48 7.53 3.45 0 5.45-2.11 6.28-3.34l-2.57-1.71c-.86 1.26-2.03 2.08-3.71 2.08s-2.88-.77-3.65-2.28l10.08-4.17-.34-.86Zm-10.28 2.51c-.09-2.91 2.26-4.4 3.94-4.4 1.31 0 2.43.66 2.8 1.6zm-8.19 7.31h3.31V31h-3.31zm-5.42-12.93h-.11c-.74-.88-2.17-1.68-3.97-1.68-3.77 0-7.22 3.31-7.22 7.56s3.45 7.51 7.22 7.51c1.8 0 3.22-.8 3.97-1.71h.11v1.08c0 2.88-1.54 4.42-4.03 4.42-2.03 0-3.28-1.46-3.8-2.68l-2.88 1.2c.83 2 3.03 4.45 6.68 4.45 3.88 0 7.16-2.28 7.16-7.85V38.99h-3.14v1.23Zm-3.8 10.42c-2.28 0-4.2-1.91-4.2-4.54s1.91-4.6 4.2-4.6 4.03 1.94 4.03 4.6-1.77 4.54-4.03 4.54M180.26 31h-7.92v22.15h3.31v-8.39h4.62c3.66 0 7.27-2.65 7.27-6.88s-3.6-6.88-7.27-6.88Zm.08 10.68h-4.7v-7.59h4.7c2.47 0 3.87 2.05 3.87 3.8s-1.4 3.8-3.87 3.8Zm20.43-3.18c-2.39 0-4.87 1.05-5.9 3.39l2.94 1.23c.63-1.23 1.79-1.62 3.02-1.62 1.71 0 3.45 1.03 3.48 2.85v.23c-.6-.34-1.88-.86-3.45-.86-3.16 0-6.38 1.74-6.38 4.99 0 2.96 2.59 4.87 5.5 4.87 2.22 0 3.45-1 4.22-2.16h.11v1.71h3.19v-8.49c0-3.93-2.94-6.13-6.73-6.13Zm-.39 12.13c-1.08 0-2.59-.54-2.59-1.88 0-1.71 1.88-2.37 3.5-2.37 1.45 0 2.14.31 3.02.74-.26 2.05-2.02 3.5-3.93 3.5Zm18.74-11.65-3.79 9.6h-.11l-3.93-9.6h-3.56l5.9 13.42-3.36 7.47h3.45l9.09-20.89h-3.68Zm-29.77 14.17h3.31V31h-3.31z"
style={{
fill: "#fff",
}}
/>
<path
d="M36.6 34.41 17.74 54.43v.01a5.09 5.09 0 0 0 4.92 3.77c.94 0 1.83-.26 2.58-.7l.06-.04 21.23-12.25-9.94-10.82Z"
style={{
fill: "#ea4335",
}}
/>
<path
d="M55.68 31h-.02l-9.17-5.33-10.33 9.19 10.36 10.36 9.12-5.26c1.6-.86 2.68-2.55 2.68-4.49s-1.07-3.61-2.65-4.47Z"
style={{
fill: "#fbbc04",
}}
/>
<path
d="M17.73 16.44c-.11.42-.17.86-.17 1.31v35.38c0 .45.06.89.17 1.31l19.51-19.51z"
style={{
fill: "#4285f4",
}}
/>
<path
d="m36.74 35.43 9.76-9.76-21.21-12.3c-.77-.46-1.67-.73-2.63-.73-2.36 0-4.34 1.6-4.92 3.78s19 19 19 19Z"
style={{
fill: "#34a853",
}}
/>
</svg>
);
Sofa has a native mobile app for iOS and Android built with [Expo](https://expo.dev/) and React Native. It connects to your self-hosted Sofa server and provides the same core features as the web app.
<div style={{ width: "100%", display: "flex", justifyContent: "center", gap: 10 }}>
<a
href="https://apps.apple.com/us/app/sofa-tv-movie-tracker/id6760432427"
target="_blank"
rel="noopener noreferrer"
style={{ display: "block" }}
>
<AppStoreBadge style={{ height: 40 }} />
</a>
<a
href="https://play.google.com/store/apps/details?id=com.jakejarvis.sofa"
target="_blank"
rel="noopener noreferrer"
style={{ display: "block" }}
>
<GooglePlayBadge style={{ height: 40 }} />
</a>
</div>
<Callout type="info">
The mobile app is not currently available on the App Store or Google Play. You'll need to build it
The mobile app is not currently available on Google Play. You'll need to build it
from source.
</Callout>
+6 -1
View File
@@ -14,7 +14,7 @@ const config = {
return [
{
source: "/docs/api",
destination: "/docs/api/account/account.removeAvatar",
destination: "/docs/api/account/account.integrations.create",
permanent: false,
},
{
@@ -32,6 +32,11 @@ const config = {
destination: "https://testflight.apple.com/join/tjSddcaZ",
permanent: false,
},
{
source: "/android",
destination: "https://play.google.com/store/apps/details?id=com.jakejarvis.sofa",
permanent: false,
}
];
},
async rewrites() {
+3 -3
View File
@@ -16,10 +16,10 @@
"dependencies": {
"@takumi-rs/image-response": "^0.73.1",
"@vercel/analytics": "^2.0.1",
"fumadocs-core": "^16.7.5",
"fumadocs-core": "^16.7.6",
"fumadocs-mdx": "^14.2.11",
"fumadocs-openapi": "^10.4.1",
"fumadocs-ui": "^16.7.5",
"fumadocs-openapi": "^10.5.0",
"fumadocs-ui": "^16.7.6",
"lucide-react": "^0.577.0",
"next": "16.2.1",
"react": "19.2.4",
+1651 -2266
View File
File diff suppressed because it is too large Load Diff