feat(server): serve normalized OpenAPI spec at /api/v1/spec.json with shared schema components

Extract spec generation into `openapi-spec.ts` with a `generateOpenApiSpec` function that registers common `$ref` schemas (Title, Person, Episode, Season, etc.) to deduplicate inline definitions across the spec. Add a `/api/v1/spec.json` interceptor that returns the fully normalized document, and point the Scalar docs UI at it instead of the plugin's raw generator output.

Also strip oRPC's impossible `{ not: {} }` void placeholders from request/response bodies via `normalizeOpenApiSpec`, and fix the handler registration so both `/api/v1` and `/api/v1/*` routes are matched.
This commit is contained in:
2026-03-14 20:05:44 -04:00
parent aede4fc90a
commit 33fd871114
20 changed files with 5153 additions and 2928 deletions
+9 -37
View File
@@ -1,43 +1,15 @@
import { OpenAPIGenerator } from "@orpc/openapi";
import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
import { contract } from "@sofa/api/contract";
import packageJson from "../apps/server/package.json" with { type: "json" };
import {
generateOpenApiSpec,
openApiTags,
} from "../apps/server/src/orpc/openapi-spec";
const generator = new OpenAPIGenerator({
schemaConverters: [new ZodToJsonSchemaConverter()],
});
const spec = await generator.generate(contract, {
info: { title: "Sofa API", version: packageJson.version || "1.0.0" },
const spec = await generateOpenApiSpec({
title: "Sofa API",
version: packageJson.version || "1.0.0",
servers: [{ url: "https://sofa.example.com/api/v1" }],
tags: [
{ name: "Titles", description: "Movie and TV show management" },
{ name: "Episodes", description: "Episode watch tracking" },
{ name: "Seasons", description: "Season watch tracking" },
{ name: "People", description: "Cast and crew information" },
{ name: "Dashboard", description: "User dashboard data" },
{ name: "Explore", description: "Discover trending and popular content" },
{ name: "Search", description: "Search for movies and TV shows" },
{
name: "Discover",
description: "Advanced content discovery with filters",
},
{ name: "System", description: "Server status and configuration" },
{ name: "Integrations", description: "Media server integrations" },
{ name: "Admin", description: "Server administration" },
{ name: "Account", description: "User account management" },
],
components: {
securitySchemes: {
session: {
type: "apiKey",
name: "better-auth.session_token",
in: "cookie",
description: "Better Auth session cookie",
},
},
},
sessionCookieName: "better-auth.session_token",
tags: [...openApiTags],
});
// Normalize file upload media types that fumadocs-openapi doesn't support