From ca44354240e878f4b98e15e1e22e1f2e8793cdbb Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Tue, 17 Mar 2026 11:14:03 -0400 Subject: [PATCH] refactor: move system health procedure from system to admin router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `system.health` → `admin.systemHealth` and gate it behind the admin middleware - Remove `tmdbConfigured` from `system.status` response; update description to reflect its purpose - Update all call sites on web and native to use `orpc.admin.systemHealth` - Rename and relocate the docs page from `system/system.health.mdx` to `admin/admin.systemHealth.mdx` --- .../src/app/(tabs)/(settings)/index.tsx | 2 +- apps/server/src/orpc/procedures/admin.ts | 9 + apps/server/src/orpc/procedures/status.ts | 9 +- apps/server/src/orpc/router.ts | 2 +- .../components/settings/danger-section.tsx | 2 +- .../settings/system-health-section.tsx | 4 +- .../admin.systemHealth.mdx} | 6 +- .../content/docs/api/system/system.status.mdx | 6 +- docs/public/openapi.json | 158 +++++++++++------- packages/api/src/contract.ts | 28 ++-- packages/api/src/schemas.ts | 7 +- 11 files changed, 135 insertions(+), 98 deletions(-) rename docs/content/docs/api/{system/system.health.mdx => admin/admin.systemHealth.mdx} (75%) diff --git a/apps/native/src/app/(tabs)/(settings)/index.tsx b/apps/native/src/app/(tabs)/(settings)/index.tsx index 5aa0871..5cbd864 100644 --- a/apps/native/src/app/(tabs)/(settings)/index.tsx +++ b/apps/native/src/app/(tabs)/(settings)/index.tsx @@ -86,7 +86,7 @@ export default function SettingsScreen() { hasPassword && !(authConfig.data?.passwordLoginDisabled ?? true); const systemHealth = useQuery({ - ...orpc.system.health.queryOptions(), + ...orpc.admin.systemHealth.queryOptions(), enabled: isAdmin, }); diff --git a/apps/server/src/orpc/procedures/admin.ts b/apps/server/src/orpc/procedures/admin.ts index 23e8cd1..01d188b 100644 --- a/apps/server/src/orpc/procedures/admin.ts +++ b/apps/server/src/orpc/procedures/admin.ts @@ -13,6 +13,7 @@ import { purgeMetadataCache as purgeMetadataFn, } from "@sofa/core/cache"; import { getSetting, setSetting } from "@sofa/core/settings"; +import { getSystemHealth } from "@sofa/core/system-health"; import { isTelemetryEnabled } from "@sofa/core/telemetry"; import { getCachedUpdateCheck, @@ -173,3 +174,11 @@ export const purgeMetadataCache = os.admin.purgeMetadataCache export const purgeImageCache = os.admin.purgeImageCache .use(admin) .handler(async () => purgeImagesFn()); + +// ─── System Health ─────────────────────────────────────────────── + +export const systemHealth = os.admin.systemHealth + .use(admin) + .handler(async () => { + return await getSystemHealth(); + }); diff --git a/apps/server/src/orpc/procedures/status.ts b/apps/server/src/orpc/procedures/status.ts index 83a523e..ce78d7b 100644 --- a/apps/server/src/orpc/procedures/status.ts +++ b/apps/server/src/orpc/procedures/status.ts @@ -1,15 +1,8 @@ -import { getSystemHealth } from "@sofa/core/system-health"; -import { isTmdbConfigured } from "@sofa/tmdb/config"; import { os } from "../context"; -import { admin, authed } from "../middleware"; +import { authed } from "../middleware"; export const status = os.system.status.use(authed).handler(() => { return { - tmdbConfigured: isTmdbConfigured(), publicApiUrl: process.env.PUBLIC_API_URL || "https://public-api.sofa.watch", }; }); - -export const health = os.system.health.use(admin).handler(async () => { - return await getSystemHealth(); -}); diff --git a/apps/server/src/orpc/router.ts b/apps/server/src/orpc/router.ts index eff6e0d..84d3efc 100644 --- a/apps/server/src/orpc/router.ts +++ b/apps/server/src/orpc/router.ts @@ -55,7 +55,6 @@ export const implementedRouter = { publicInfo: system.publicInfo, authConfig: system.authConfig, status: status.status, - health: status.health, }, integrations: { list: integrations.list, @@ -81,6 +80,7 @@ export const implementedRouter = { triggerJob: admin.triggerJob, purgeMetadataCache: admin.purgeMetadataCache, purgeImageCache: admin.purgeImageCache, + systemHealth: admin.systemHealth, }, account: { updateName: account.updateName, diff --git a/apps/web/src/components/settings/danger-section.tsx b/apps/web/src/components/settings/danger-section.tsx index d01223c..b8cf928 100644 --- a/apps/web/src/components/settings/danger-section.tsx +++ b/apps/web/src/components/settings/danger-section.tsx @@ -29,7 +29,7 @@ export function CacheSection() { const queryClient = useQueryClient(); const invalidateHealth = () => - queryClient.invalidateQueries({ queryKey: orpc.system.health.key() }); + queryClient.invalidateQueries({ queryKey: orpc.admin.systemHealth.key() }); const purgeMetadata = useMutation( orpc.admin.purgeMetadataCache.mutationOptions({ diff --git a/apps/web/src/components/settings/system-health-section.tsx b/apps/web/src/components/settings/system-health-section.tsx index 78fcd87..0d02e2d 100644 --- a/apps/web/src/components/settings/system-health-section.tsx +++ b/apps/web/src/components/settings/system-health-section.tsx @@ -130,11 +130,11 @@ function LiveTimeAgo({ export function SystemHealthCards() { const queryClient = useQueryClient(); const { data, isPending, isFetching } = useQuery( - orpc.system.health.queryOptions(), + orpc.admin.systemHealth.queryOptions(), ); const isRefreshing = isFetching; const refresh = () => - queryClient.invalidateQueries({ queryKey: orpc.system.health.key() }); + queryClient.invalidateQueries({ queryKey: orpc.admin.systemHealth.key() }); if (isPending || !data) return ; diff --git a/docs/content/docs/api/system/system.health.mdx b/docs/content/docs/api/admin/admin.systemHealth.mdx similarity index 75% rename from docs/content/docs/api/system/system.health.mdx rename to docs/content/docs/api/admin/admin.systemHealth.mdx index c7d918d..df7147d 100644 --- a/docs/content/docs/api/system/system.health.mdx +++ b/docs/content/docs/api/admin/admin.systemHealth.mdx @@ -9,11 +9,11 @@ _openapi: contents: - content: >- Comprehensive health check covering database, TMDB connectivity, cron - jobs, image cache, backups, and environment. Admin only. + jobs, image cache, backups, and environment. --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} -Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment. Admin only. +Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment. - \ No newline at end of file + \ No newline at end of file diff --git a/docs/content/docs/api/system/system.status.mdx b/docs/content/docs/api/system/system.status.mdx index 1bbc40e..8eac90e 100644 --- a/docs/content/docs/api/system/system.status.mdx +++ b/docs/content/docs/api/system/system.status.mdx @@ -1,5 +1,5 @@ --- -title: Get system status +title: Get internal system config full: true _openapi: method: GET @@ -8,12 +8,12 @@ _openapi: headings: [] contents: - content: >- - Quick check of whether TMDB is configured. Does not require + Returns internal configuration such as the public API URL. Requires authentication. --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} -Quick check of whether TMDB is configured. Does not require authentication. +Returns internal configuration such as the public API URL. Requires authentication. \ No newline at end of file diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 4c481d5..9390882 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -4287,60 +4287,28 @@ "/system/status": { "get": { "operationId": "system.status", - "summary": "Get system status", - "description": "Quick check of whether TMDB is configured. Does not require authentication.", + "summary": "Get internal system config", + "description": "Returns internal configuration such as the public API URL. Requires authentication.", "tags": [ "System" ], "responses": { "200": { - "description": "TMDB configuration status", + "description": "Internal system configuration", "content": { "application/json": { "schema": { "type": "object", "properties": { - "tmdbConfigured": { - "type": "boolean", - "description": "Whether a TMDB API token is configured" - }, "publicApiUrl": { "type": "string", "description": "Base URL of the centralized public API" } }, "required": [ - "tmdbConfigured", "publicApiUrl" ], - "description": "Quick TMDB configuration check" - } - } - } - } - }, - "security": [ - { - "session": [] - } - ] - } - }, - "/system/health": { - "get": { - "operationId": "system.health", - "summary": "Get system health report", - "description": "Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment. Admin only.", - "tags": [ - "System" - ], - "responses": { - "200": { - "description": "Full system health report", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemHealth" + "description": "Internal system configuration for authenticated clients" } } } @@ -5620,6 +5588,33 @@ ] } }, + "/admin/system-health": { + "get": { + "operationId": "admin.systemHealth", + "summary": "Get system health report", + "description": "Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment.", + "tags": [ + "Admin" + ], + "responses": { + "200": { + "description": "Full system health report", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemHealth" + } + } + } + } + }, + "security": [ + { + "session": [] + } + ] + } + }, "/account/name": { "put": { "operationId": "account.updateName", @@ -5831,10 +5826,12 @@ }, "watchedAt": { "type": "string", + "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", + "format": "date", "description": "YYYY-MM-DD date-only" } }, @@ -5865,16 +5862,22 @@ "type": "number" }, "seasonNumber": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 }, "episodeNumber": { - "type": "number" + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 }, "watchedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "watchedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -5951,10 +5954,12 @@ "description": "Sofa 1-5 star rating" }, "ratedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "ratedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -6087,10 +6092,12 @@ }, "watchedAt": { "type": "string", + "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", + "format": "date", "description": "YYYY-MM-DD date-only" } }, @@ -6121,16 +6128,22 @@ "type": "number" }, "seasonNumber": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 }, "episodeNumber": { - "type": "number" + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 }, "watchedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "watchedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -6207,10 +6220,12 @@ "description": "Sofa 1-5 star rating" }, "ratedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "ratedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -6276,10 +6291,12 @@ }, "watchedAt": { "type": "string", + "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", + "format": "date", "description": "YYYY-MM-DD date-only" } }, @@ -6310,16 +6327,22 @@ "type": "number" }, "seasonNumber": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 }, "episodeNumber": { - "type": "number" + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 }, "watchedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "watchedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -6396,10 +6419,12 @@ "description": "Sofa 1-5 star rating" }, "ratedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "ratedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -6532,10 +6557,12 @@ }, "watchedAt": { "type": "string", + "format": "date-time", "description": "ISO 8601 timestamp" }, "watchedOn": { "type": "string", + "format": "date", "description": "YYYY-MM-DD date-only" } }, @@ -6566,16 +6593,22 @@ "type": "number" }, "seasonNumber": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 }, "episodeNumber": { - "type": "number" + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991 }, "watchedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "watchedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -6652,10 +6685,12 @@ "description": "Sofa 1-5 star rating" }, "ratedAt": { - "type": "string" + "type": "string", + "format": "date-time" }, "ratedOn": { - "type": "string" + "type": "string", + "format": "date" } }, "required": [ @@ -7145,7 +7180,8 @@ "type": { "enum": [ "progress", - "complete" + "complete", + "timeout" ] }, "job": { diff --git a/packages/api/src/contract.ts b/packages/api/src/contract.ts index 64676f8..40316fa 100644 --- a/packages/api/src/contract.ts +++ b/packages/api/src/contract.ts @@ -406,23 +406,12 @@ export const contract = { method: "GET", path: "/system/status", tags: ["System"], - summary: "Get system status", + summary: "Get internal system config", description: - "Quick check of whether TMDB is configured. Does not require authentication.", - successDescription: "TMDB configuration status", + "Returns internal configuration such as the public API URL. Requires authentication.", + successDescription: "Internal system configuration", }) .output(SystemStatusOutput), - health: oc - .route({ - method: "GET", - path: "/system/health", - tags: ["System"], - summary: "Get system health report", - description: - "Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment. Admin only.", - successDescription: "Full system health report", - }) - .output(SystemHealthOutput), }, integrations: { list: oc @@ -653,6 +642,17 @@ export const contract = { }) .input(z.void()) .output(PurgeImageCacheOutput), + systemHealth: oc + .route({ + method: "GET", + path: "/admin/system-health", + tags: ["Admin"], + summary: "Get system health report", + description: + "Comprehensive health check covering database, TMDB connectivity, cron jobs, image cache, backups, and environment.", + successDescription: "Full system health report", + }) + .output(SystemHealthOutput), }, account: { updateName: oc diff --git a/packages/api/src/schemas.ts b/packages/api/src/schemas.ts index 12b1a4e..f5f66bd 100644 --- a/packages/api/src/schemas.ts +++ b/packages/api/src/schemas.ts @@ -884,12 +884,11 @@ export const SystemHealthSchema = z export const SystemStatusOutput = z .object({ - tmdbConfigured: z - .boolean() - .describe("Whether a TMDB API token is configured"), publicApiUrl: z.string().describe("Base URL of the centralized public API"), }) - .meta({ description: "Quick TMDB configuration check" }); + .meta({ + description: "Internal system configuration for authenticated clients", + }); export const SystemHealthOutput = SystemHealthSchema;