From dce333e128d7eb91e6e0b7387d201addeb127fb0 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Thu, 26 Mar 2026 15:44:05 -0400 Subject: [PATCH] refactor: organize API around operation domains (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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) * 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) * 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) * 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) --- README.md | 4 +- apps/native/package.json | 4 +- apps/native/src/app/(auth)/login.tsx | 10 +- .../native/src/app/(tabs)/(explore)/index.tsx | 11 +- apps/native/src/app/(tabs)/(home)/index.tsx | 22 +- .../native/src/app/(tabs)/(home)/upcoming.tsx | 4 +- .../native/src/app/(tabs)/(library)/index.tsx | 9 +- apps/native/src/app/(tabs)/(search)/index.tsx | 10 +- .../src/app/(tabs)/(settings)/index.tsx | 72 +- apps/native/src/app/person/[id].tsx | 10 +- apps/native/src/app/title/[id].tsx | 21 +- .../dashboard/horizontal-poster-row.tsx | 9 +- .../components/dashboard/upcoming-section.tsx | 2 +- .../explore/filterable-title-row.tsx | 2 +- .../src/components/explore/hero-banner.tsx | 2 +- .../components/settings/integration-card.tsx | 12 +- .../settings/integrations-section.tsx | 2 +- .../components/titles/season-accordion.tsx | 12 +- apps/native/src/hooks/use-title-actions.ts | 45 +- apps/native/src/lib/title-actions.ts | 25 +- apps/native/src/lib/widgets.test.ts | 2 +- apps/native/src/lib/widgets.ts | 4 +- apps/server/src/orpc/openapi-spec.ts | 18 +- apps/server/src/orpc/procedures/account.ts | 46 +- apps/server/src/orpc/procedures/admin.ts | 80 +- apps/server/src/orpc/procedures/dashboard.ts | 92 - apps/server/src/orpc/procedures/discover.ts | 355 +- apps/server/src/orpc/procedures/episodes.ts | 16 - apps/server/src/orpc/procedures/explore.ts | 177 - .../src/orpc/procedures/integrations.ts | 42 - apps/server/src/orpc/procedures/library.ts | 54 + apps/server/src/orpc/procedures/people.ts | 2 +- apps/server/src/orpc/procedures/platforms.ts | 19 - apps/server/src/orpc/procedures/search.ts | 161 - apps/server/src/orpc/procedures/seasons.ts | 12 - apps/server/src/orpc/procedures/status.ts | 8 - apps/server/src/orpc/procedures/system.ts | 15 +- apps/server/src/orpc/procedures/titles.ts | 80 +- apps/server/src/orpc/procedures/tracking.ts | 124 + apps/server/src/orpc/router.ts | 109 +- apps/web/package.json | 8 +- apps/web/src/components/command-palette.tsx | 2 +- .../dashboard/continue-watching-section.tsx | 2 +- .../dashboard/recommendations-section.tsx | 2 +- .../components/dashboard/stats-display.tsx | 35 +- .../components/dashboard/stats-section.tsx | 28 +- .../components/dashboard/upcoming-section.tsx | 2 +- .../components/explore/discover-section.tsx | 6 +- .../explore/filterable-title-row.tsx | 2 +- .../people/person-detail-client.tsx | 2 +- .../components/settings/integration-card.tsx | 6 +- .../settings/integrations-section.tsx | 2 +- .../settings/registration-section.tsx | 8 +- .../settings/streaming-services-section.tsx | 2 +- .../settings/update-check-section.tsx | 8 +- apps/web/src/components/title-card.tsx | 12 +- .../src/components/titles/title-context.tsx | 2 +- .../titles/title-recommendations.tsx | 2 +- .../components/titles/use-title-actions.ts | 43 +- apps/web/src/components/update-toast.tsx | 4 +- apps/web/src/routes/_app.tsx | 3 +- apps/web/src/routes/_app/dashboard.tsx | 14 +- apps/web/src/routes/_app/explore.tsx | 20 +- apps/web/src/routes/_app/onboarding.tsx | 4 +- apps/web/src/routes/_app/people.$id.tsx | 2 +- apps/web/src/routes/_app/settings.tsx | 4 +- apps/web/src/routes/_app/titles.$id.tsx | 4 +- apps/web/src/routes/_app/upcoming.tsx | 4 +- apps/web/src/routes/_auth/login.tsx | 16 +- apps/web/src/routes/_auth/register.tsx | 14 +- bun.lock | 390 +- docs/bun.lock | 16 +- .../account.integrations.create.mdx} | 2 +- .../account.integrations.delete.mdx} | 2 +- .../account.integrations.list.mdx} | 2 +- .../account.integrations.regenerateToken.mdx} | 2 +- .../docs/api/admin/admin.registration.mdx | 17 - ...n.telemetry.mdx => admin.settings.get.mdx} | 10 +- .../docs/api/admin/admin.settings.update.mdx | 19 + .../api/admin/admin.toggleRegistration.mdx | 17 - .../docs/api/admin/admin.toggleTelemetry.mdx | 17 - .../api/admin/admin.toggleUpdateCheck.mdx | 17 - .../docs/api/dashboard/dashboard.stats.mdx | 19 - .../{discover.mdx => discover.browse.mdx} | 4 +- .../discover.genres.mdx} | 2 +- .../discover.platforms.mdx} | 10 +- .../discover.popular.mdx} | 2 +- .../discover.recommendations.mdx} | 2 +- .../discover.search.mdx} | 2 +- .../discover.trending.mdx} | 2 +- .../docs/api/episodes/episodes.batchWatch.mdx | 19 - .../docs/api/episodes/episodes.unwatch.mdx | 17 - .../docs/api/episodes/episodes.watch.mdx | 17 - .../api/explore/explore.watchProviders.mdx | 19 - .../library.continueWatching.mdx} | 2 +- .../library.stats.mdx} | 8 +- .../library.upcoming.mdx} | 2 +- .../{people.detail.mdx => people.get.mdx} | 0 .../docs/api/seasons/seasons.unwatch.mdx | 19 - .../docs/api/seasons/seasons.watch.mdx | 17 - .../docs/api/system/system.authConfig.mdx | 20 - .../docs/api/system/system.publicInfo.mdx | 6 +- .../{titles.detail.mdx => titles.get.mdx} | 0 .../docs/api/titles/titles.quickAdd.mdx | 20 - ...recommendations.mdx => titles.similar.mdx} | 4 +- .../docs/api/titles/titles.watchAll.mdx | 19 - .../docs/api/titles/titles.watchMovie.mdx | 17 - .../tracking.rate.mdx} | 2 +- .../tracking.stats.mdx} | 8 +- .../docs/api/tracking/tracking.unwatch.mdx | 19 + .../tracking.updateStatus.mdx} | 2 +- .../tracking.userInfo.mdx} | 4 +- .../docs/api/tracking/tracking.watch.mdx | 20 + docs/content/docs/mobile-app.mdx | 110 +- docs/next.config.mjs | 7 +- docs/package.json | 6 +- docs/public/openapi.json | 3917 +++++++---------- package.json | 24 +- packages/api/src/contract.ts | 767 ++-- packages/api/src/schemas.ts | 150 +- packages/core/src/tracking.ts | 20 + packages/core/test/tracking.test.ts | 89 + packages/db/src/queries/tracking.ts | 19 + 123 files changed, 3566 insertions(+), 4325 deletions(-) delete mode 100644 apps/server/src/orpc/procedures/dashboard.ts delete mode 100644 apps/server/src/orpc/procedures/episodes.ts delete mode 100644 apps/server/src/orpc/procedures/explore.ts delete mode 100644 apps/server/src/orpc/procedures/integrations.ts delete mode 100644 apps/server/src/orpc/procedures/platforms.ts delete mode 100644 apps/server/src/orpc/procedures/search.ts delete mode 100644 apps/server/src/orpc/procedures/seasons.ts delete mode 100644 apps/server/src/orpc/procedures/status.ts create mode 100644 apps/server/src/orpc/procedures/tracking.ts rename docs/content/docs/api/{integrations/integrations.create.mdx => account/account.integrations.create.mdx} (93%) rename docs/content/docs/api/{integrations/integrations.delete.mdx => account/account.integrations.delete.mdx} (89%) rename docs/content/docs/api/{integrations/integrations.list.mdx => account/account.integrations.list.mdx} (93%) rename docs/content/docs/api/{integrations/integrations.regenerateToken.mdx => account/account.integrations.regenerateToken.mdx} (88%) delete mode 100644 docs/content/docs/api/admin/admin.registration.mdx rename docs/content/docs/api/admin/{admin.telemetry.mdx => admin.settings.get.mdx} (57%) create mode 100644 docs/content/docs/api/admin/admin.settings.update.mdx delete mode 100644 docs/content/docs/api/admin/admin.toggleRegistration.mdx delete mode 100644 docs/content/docs/api/admin/admin.toggleTelemetry.mdx delete mode 100644 docs/content/docs/api/admin/admin.toggleUpdateCheck.mdx delete mode 100644 docs/content/docs/api/dashboard/dashboard.stats.mdx rename docs/content/docs/api/discover/{discover.mdx => discover.browse.mdx} (88%) rename docs/content/docs/api/{explore/explore.genres.mdx => discover/discover.genres.mdx} (93%) rename docs/content/docs/api/{admin/admin.updateCheck.mdx => discover/discover.platforms.mdx} (50%) rename docs/content/docs/api/{explore/explore.popular.mdx => discover/discover.popular.mdx} (93%) rename docs/content/docs/api/{dashboard/dashboard.recommendations.mdx => discover/discover.recommendations.mdx} (91%) rename docs/content/docs/api/{search/search.mdx => discover/discover.search.mdx} (93%) rename docs/content/docs/api/{explore/explore.trending.mdx => discover/discover.trending.mdx} (93%) delete mode 100644 docs/content/docs/api/episodes/episodes.batchWatch.mdx delete mode 100644 docs/content/docs/api/episodes/episodes.unwatch.mdx delete mode 100644 docs/content/docs/api/episodes/episodes.watch.mdx delete mode 100644 docs/content/docs/api/explore/explore.watchProviders.mdx rename docs/content/docs/api/{dashboard/dashboard.continueWatching.mdx => library/library.continueWatching.mdx} (91%) rename docs/content/docs/api/{platforms/platforms.list.mdx => library/library.stats.mdx} (58%) rename docs/content/docs/api/{dashboard/dashboard.upcoming.mdx => library/library.upcoming.mdx} (93%) rename docs/content/docs/api/people/{people.detail.mdx => people.get.mdx} (100%) delete mode 100644 docs/content/docs/api/seasons/seasons.unwatch.mdx delete mode 100644 docs/content/docs/api/seasons/seasons.watch.mdx delete mode 100644 docs/content/docs/api/system/system.authConfig.mdx rename docs/content/docs/api/titles/{titles.detail.mdx => titles.get.mdx} (100%) delete mode 100644 docs/content/docs/api/titles/titles.quickAdd.mdx rename docs/content/docs/api/titles/{titles.recommendations.mdx => titles.similar.mdx} (86%) delete mode 100644 docs/content/docs/api/titles/titles.watchAll.mdx delete mode 100644 docs/content/docs/api/titles/titles.watchMovie.mdx rename docs/content/docs/api/{titles/titles.updateRating.mdx => tracking/tracking.rate.mdx} (90%) rename docs/content/docs/api/{dashboard/dashboard.watchHistory.mdx => tracking/tracking.stats.mdx} (72%) create mode 100644 docs/content/docs/api/tracking/tracking.unwatch.mdx rename docs/content/docs/api/{titles/titles.updateStatus.mdx => tracking/tracking.updateStatus.mdx} (91%) rename docs/content/docs/api/{titles/titles.userInfo.mdx => tracking/tracking.userInfo.mdx} (85%) create mode 100644 docs/content/docs/api/tracking/tracking.watch.mdx diff --git a/README.md b/README.md index 1a97b18..d06ce67 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Crowdin

- App Store
- Play Store soon™ + App Store + GetItOnGooglePlay_Badge_Web_color_English

[Sofa](https://sofa.watch) is a self-hosted movie and TV tracker for nerds. Track what you've watched, discover what's next, and plug your data into your existing home media stack. All without anything leaving your homelab. 🍿 diff --git a/apps/native/package.json b/apps/native/package.json index 3d55484..388522d 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -33,7 +33,7 @@ "@react-native-menu/menu": "2.0.0", "@react-navigation/elements": "2.9.11", "@react-navigation/native": "7.1.34", - "@sentry/react-native": "8.5.0", + "@sentry/react-native": "8.6.0", "@shopify/flash-list": "2.0.2", "@sofa/api": "workspace:*", "@sofa/i18n": "workspace:*", @@ -85,7 +85,7 @@ "react-native-worklets": "0.7.2", "tailwind-merge": "catalog:", "tailwindcss": "catalog:", - "uniwind": "1.6.0", + "uniwind": "1.6.1", "zeego": "3.0.6", "zod": "catalog:" }, diff --git a/apps/native/src/app/(auth)/login.tsx b/apps/native/src/app/(auth)/login.tsx index ec215cd..c8f0a1f 100644 --- a/apps/native/src/app/(auth)/login.tsx +++ b/apps/native/src/app/(auth)/login.tsx @@ -41,7 +41,7 @@ export default function LoginScreen() { const [errorFields, setErrorFields] = useState>(new Set()); const [isSignedIn, setIsSignedIn] = useState(false); - const authConfig = useQuery(orpc.system.authConfig.queryOptions()); + const publicInfo = useQuery(orpc.system.publicInfo.queryOptions()); const form = useForm({ defaultValues: { email: "", password: "" }, @@ -76,9 +76,9 @@ export default function LoginScreen() { const statusCompletedColor = useCSSVariable("--color-status-completed") as string; const serverHost = splitUrl(getServerUrl()).host; - const showPasswordLogin = !authConfig.data?.passwordLoginDisabled; - const showOidc = authConfig.data?.oidcEnabled; - const showRegister = authConfig.data?.registrationOpen; + const showPasswordLogin = !publicInfo.data?.passwordLoginDisabled; + const showOidc = publicInfo.data?.oidcEnabled; + const showRegister = publicInfo.data?.registrationOpen; const clearFieldError = (name: string) => { if (errorFields.has(name)) { @@ -94,7 +94,7 @@ export default function LoginScreen() { {showOidc && (() => { - const providerName = authConfig.data?.oidcProviderName ?? "SSO"; + const providerName = publicInfo.data?.oidcProviderName ?? "SSO"; return (