Skip to content

feat(api): add GET /content/schema/{post,collection,author} endpoints - #180

Merged
fennifith merged 5 commits into
playfulprogramming:mainfrom
bbornino:94-frontmatter-schema-endpoints
Jul 11, 2026
Merged

feat(api): add GET /content/schema/{post,collection,author} endpoints#180
fennifith merged 5 commits into
playfulprogramming:mainfrom
bbornino:94-frontmatter-schema-endpoints

Conversation

@bbornino

@bbornino bbornino commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds three new endpoints — GET /content/schema/post, GET /content/schema/collection, GET /content/schema/author — each returning the corresponding TypeBox frontmatter schema (PostMetaSchema, CollectionMetaSchema, AuthorMetaSchema) directly as the response body.
  • One route file per schema (schema-post.ts, schema-collection.ts, schema-author.ts), following the existing profiles.ts pattern and the project's one-route-per-file convention.
  • Registered in createApp.ts alongside the existing content routes.

These three schemas are exposed as-is with no transformation — downstream consumers should treat them as the source of truth for frontmatter shape, since they're the exact schemas the sync worker validates against.

Implementation note

Each route's response[200] schema is a permissive object (additionalProperties: true) with the real MetaSchema attached via examples, rather than using the MetaSchema itself as the response schema. Using the frontmatter schema directly as the Fastify response schema causes a 500 (fast-json-stringify treats it as the shape of post/collection/author data, not a JSON Schema document, and complains about missing required fields like title). The permissive-wrapper approach keeps runtime serialization working and still surfaces the real schema shape in the generated OpenAPI docs.

Design question for James

These three route files import PostMetaSchema/CollectionMetaSchema/AuthorMetaSchema directly from apps/worker/src/tasks/*/types.ts via a relative path, since that's where the schemas already live. This is the first cross-app import in the codebase — elsewhere, sharing goes through packages/*, with apps depending on packages rather than on each other directly. Flagging in case you'd rather these schemas move into packages/common (or a new small package) as a follow-up; Docker builds both apps from the same checkout today, but that's not a guarantee this pattern should rely on long-term.

Test plan

  • pnpm test:unit passes (lint, knip, publint, sherif, vitest across all NX projects)
  • pnpm prettier --check . passes on all changed files
  • Manually hit all three endpoints locally and confirmed 200 responses matching the exported schema objects
  • Inspected generated /openapi.json to confirm the response is documented sensibly (permissive schema + concrete example) rather than misleadingly requiring frontmatter fields on the response itself

Summary by CodeRabbit

  • New Features

    • Added new API endpoints that return JSON schema metadata for post, collection, and author frontmatter.
    • These routes are now available under /content/schema/*.
  • Tests

    • Added Jest test coverage for each new schema endpoint to confirm HTTP 200 responses and exact JSON payload matching the expected schema metadata.

Exposes the sync worker's TypeBox frontmatter schemas directly so
downstream consumers have a single source of truth for valid post,
collection, and author frontmatter shape.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fennifith, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f3294cfe-6f1c-4695-a476-20a622d3610e

📥 Commits

Reviewing files that changed from the base of the PR and between afc39a5 and 1cbddd5.

📒 Files selected for processing (13)
  • apps/api/src/routes/content/schema-author.test.ts
  • apps/api/src/routes/content/schema-author.ts
  • apps/api/src/routes/content/schema-collection.test.ts
  • apps/api/src/routes/content/schema-collection.ts
  • apps/api/src/routes/content/schema-post.test.ts
  • apps/api/src/routes/content/schema-post.ts
  • apps/worker/src/tasks/sync-author/processor.ts
  • apps/worker/src/tasks/sync-collection/processor.ts
  • apps/worker/src/tasks/sync-post/processor.ts
  • packages/common/src/frontmatter/author.ts
  • packages/common/src/frontmatter/collection.ts
  • packages/common/src/frontmatter/post.ts
  • packages/common/src/index.ts
📝 Walkthrough

Walkthrough

Three new content schema routes are added for post, collection, and author frontmatter, all built through a shared route factory. The app registers the new routes, and each endpoint has a corresponding test.

Changes

Content Schema Routes

Layer / File(s) Summary
Schema route factory
apps/api/src/routes/content/createSchemaRoute.ts
Adds a shared Fastify plugin factory that defines the 200 JSON response schema and serves the provided schema on GET requests.
Content schema routes and tests
apps/api/src/routes/content/schema-post.ts, apps/api/src/routes/content/schema-collection.ts, apps/api/src/routes/content/schema-author.ts, apps/api/src/routes/content/*.test.ts
Defines the post, collection, and author schema route plugins using the shared factory, and adds Fastify tests that assert each endpoint returns the expected schema.
App wiring
apps/api/src/createApp.ts
Imports and registers the three new schema route modules in createApp.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
    participant createApp
    participant createSchemaRoute
    participant Fastify
    participant Client

    createApp->>Fastify: register schema route plugins
    createSchemaRoute->>Fastify: define GET /content/schema/*
    Client->>Fastify: GET request
    Fastify-->>Client: 200 JSON schema response
Loading

Related PRs: None.

Suggested labels: api, enhancement

Suggested reviewers: None.

Poem:
Three schemas bloom in route-lined rows,
A shared factory quietly glows,
Post, collection, author sing,
Tests confirm each serving thing,
And createApp gives them wings.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the three new GET schema endpoints added to the API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
apps/api/src/routes/content/schema-post.ts (2)

5-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stashing the real schema in examples is semantically off.

examples is meant to hold sample response instances, not the schema definition itself. Consumers/tools generating OpenAPI docs or client SDKs from examples may render the schema object as if it were example data, which is confusing. If avoiding Fastify's response validation is the real goal, consider a dedicated OpenAPI vendor extension (e.g. x-schema) instead of repurposing examples.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/routes/content/schema-post.ts` around lines 5 - 12, The
SchemaPostResponseSchema definition is using examples to store the schema
itself, which is the wrong semantic for response examples. Update the
SchemaPostResponseSchema in schema-post.ts so the real schema is not placed in
examples; instead, use a dedicated OpenAPI vendor extension like x-schema or
another non-example metadata field while keeping the response shape and
description intact.

5-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate this route-registration boilerplate across all three schema route files.

schema-post.ts, schema-collection.ts, and schema-author.ts are structurally identical apart from path, description, and schema reference. Consider extracting a small factory, e.g. createSchemaRoute(path, description, schema), to avoid triplicated logic and keep future changes (e.g., adjusting the response-schema workaround) in one place.

♻️ Example factory approach
// apps/api/src/routes/content/createSchemaRoute.ts
import type { FastifyPluginAsync } from "fastify";
import { Type, type TSchema } from "typebox";

export function createSchemaRoute(
	path: string,
	description: string,
	schema: TSchema,
): FastifyPluginAsync {
	const ResponseSchema = Type.Object(
		{},
		{ additionalProperties: true, description, examples: [schema] },
	);

	return async (fastify) => {
		fastify.get(
			path,
			{
				schema: {
					description,
					response: {
							description: "Successful",
							content: { "application/json": { schema: ResponseSchema } },
						},
					},
				},
			},
			async (_request, reply) => {
				reply.code(200);
				reply.send(schema);
			},
		);
	};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/routes/content/schema-post.ts` around lines 5 - 38, The three
schema route files duplicate the same Fastify route-registration logic, so
extract a shared factory like createSchemaRoute and use it from schema-post,
schema-collection, and schema-author. Move the common fastify.get setup,
response-schema workaround, and reply.send behavior into the factory,
parameterized by path, description, and the schema reference (e.g.
PostMetaSchema/CollectionMetaSchema/AuthorMetaSchema), so future changes only
happen in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/api/src/routes/content/schema-post.ts`:
- Around line 5-12: The SchemaPostResponseSchema definition is using examples to
store the schema itself, which is the wrong semantic for response examples.
Update the SchemaPostResponseSchema in schema-post.ts so the real schema is not
placed in examples; instead, use a dedicated OpenAPI vendor extension like
x-schema or another non-example metadata field while keeping the response shape
and description intact.
- Around line 5-38: The three schema route files duplicate the same Fastify
route-registration logic, so extract a shared factory like createSchemaRoute and
use it from schema-post, schema-collection, and schema-author. Move the common
fastify.get setup, response-schema workaround, and reply.send behavior into the
factory, parameterized by path, description, and the schema reference (e.g.
PostMetaSchema/CollectionMetaSchema/AuthorMetaSchema), so future changes only
happen in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2f16cea-4877-4b83-ad4b-7b4e7b52e628

📥 Commits

Reviewing files that changed from the base of the PR and between 086e182 and 09d6595.

📒 Files selected for processing (7)
  • apps/api/src/createApp.ts
  • apps/api/src/routes/content/schema-author.test.ts
  • apps/api/src/routes/content/schema-author.ts
  • apps/api/src/routes/content/schema-collection.test.ts
  • apps/api/src/routes/content/schema-collection.ts
  • apps/api/src/routes/content/schema-post.test.ts
  • apps/api/src/routes/content/schema-post.ts

Addresses CodeRabbit's suggestion on PR playfulprogramming#180 to de-duplicate the
identical route/response-wrapper boilerplate across schema-post.ts,
schema-collection.ts, and schema-author.ts.
@@ -0,0 +1,10 @@
import { AuthorMetaSchema } from "../../../../worker/src/tasks/sync-author/types.ts";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid importing files from outside the current package like this. I'd move these schemas to packages/common (will need to add the typebox dependency) and import them through @playfulprogramming/common instead of referencing the file path.

PostMetaSchema, CollectionMetaSchema, and AuthorMetaSchema move from
apps/worker's per-task types.ts files into packages/common, resolving
the cross-app import flagged in the PR description (apps/api was
reaching into apps/worker via a relative path). typebox is already a
packages/common dependency via the catalog, so no manifest change is
needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@fennifith fennifith linked an issue Jul 10, 2026 that may be closed by this pull request
@fennifith
fennifith added this pull request to the merge queue Jul 10, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 10, 2026
@fennifith
fennifith enabled auto-merge July 11, 2026 16:51
@fennifith
fennifith added this pull request to the merge queue Jul 11, 2026
Merged via the queue into playfulprogramming:main with commit d1b4e96 Jul 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON Schema endpoint for post/collection/author frontmatter

2 participants