Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .cursor/rules/sync-openapi-docs.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
description: Regenerate Fumadocs OpenAPI pages when the HTTP API or schemas change
globs:
- src/**/*
- "!src/test/**"
alwaysApply: false
---

# Sync OpenAPI docs

After editing routes, schemas, OpenAPI config, services, or API errors under `src/`:

1. Run from repo root: `npm run openapi:sync`
2. Commit any changes under:
- `apps/docs/openapi/openapi.json`
- `apps/docs/content/docs/api/`
3. Run `npm run build:docs` to verify.

Do not hand-edit generated `apps/docs/content/docs/api/*.mdx` files.

Full agent workflow: [AGENTS.md](../../AGENTS.md). Deployment: [docs/vercel-deploy.md](../../docs/vercel-deploy.md).
24 changes: 24 additions & 0 deletions .github/workflows/publish-client.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Publish client

on:
release:
types: [published]
workflow_dispatch:

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run build -w @timeforschool/client
- run: npm publish -w @timeforschool/client --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
72 changes: 72 additions & 0 deletions .github/workflows/sync-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Sync API docs

on:
push:
branches: [main]
paths:
- "src/**"
- "scripts/export-openapi.ts"
pull_request:
paths:
- "src/**"
- "scripts/export-openapi.ts"
- "apps/docs/openapi/**"
- "apps/docs/content/docs/api/**"
- "apps/docs/scripts/**"
- ".github/workflows/sync-docs.yml"

permissions:
contents: write

jobs:
pr-check:
name: Verify OpenAPI docs are up to date
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- run: npm ci
- run: npm run openapi:sync

- name: Fail if generated docs differ
run: |
if ! git diff --exit-code -- apps/docs/openapi apps/docs/content/docs/api; then
echo "::error::OpenAPI docs are out of date. Run 'npm run openapi:sync' locally and commit."
git diff --stat -- apps/docs/openapi apps/docs/content/docs/api
exit 1
fi

main-sync:
name: Auto-commit OpenAPI docs on main
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm

- run: npm ci
- run: npm run openapi:sync

- name: Commit and push if needed
run: |
if git diff --quiet -- apps/docs/openapi apps/docs/content/docs/api; then
echo "Docs already in sync."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add apps/docs/openapi apps/docs/content/docs/api
git commit -m "chore(docs): sync OpenAPI from API [skip ci]"
git push
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
node_modules/
dist/
apps/docs/.next/
apps/docs/out/
apps/docs/.source/
__pycache__/
*.pyc
.env
Expand Down
93 changes: 93 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Agent instructions (TimeForSchool monorepo)

Instructions for Cursor Cloud Agents and other automation working in this repository.

## Repository map

| Area | Path | Purpose |
| ---- | ---- | ------- |
| HTTP API | `src/` | Elysia app; OpenAPI at `/docs/json` |
| Client library | `packages/client/` | `@timeforschool/client` (npm) |
| Docs site | `apps/docs/` | Fumadocs + Next.js |
| OpenAPI artifact | `apps/docs/openapi/openapi.json` | Committed spec for docs |
| Generated API MDX | `apps/docs/content/docs/api/` | Do not hand-edit |

Deployment plan: **[docs/vercel-deploy.md](docs/vercel-deploy.md)**.

---

## Keep docs in sync with the API

When a change **might** affect the public HTTP API or its OpenAPI description, refresh docs **in the same PR** before finishing.

### Triggers (run sync if you touch any of these)

- `src/app.ts` — routes, handlers, validation
- `src/openapi-config.ts` — titles, tags, servers, descriptions
- `src/schemas/**` — request/response models
- `src/services/**` — behavior that changes API outputs or errors
- `src/errors/**` — error codes/messages exposed to clients

**Not required** for-only changes: `packages/client/` (unless mirroring new public API behavior in hand-written client MDX), `apps/docs/content/docs/client/**`, tests, README typos.

### Command

From repository root:

```bash
npm run openapi:sync
```

This runs `build:client` → `scripts/export-openapi.ts` → `apps/docs` OpenAPI MDX generation.

### Commit these paths when they change

```
apps/docs/openapi/openapi.json
apps/docs/content/docs/api/**
```

Do **not** delete `apps/docs/content/docs/api/meta.mdx` if regenerated—it is the “Meta” tag page. Keep `apps/docs/content/docs/api/meta.json` in sync with the generated page list.

### Hand-written docs

Update `apps/docs/content/docs/client/**` when you change the **client** API surface (new exports, renames, error classes). No generator for that yet.

### Verify

```bash
npm run build:docs
```

---

## Vercel

- **Two projects** from one repo: API root `.`, docs root `apps/docs`. See [docs/vercel-deploy.md](docs/vercel-deploy.md).
- Do not merge API and docs into one Vercel project.
- Docs production build must run `openapi:sync` (already in `apps/docs/vercel.json` `buildCommand`).

---

## npm client

- Publish only `packages/client` (`@timeforschool/client`).
- Before release: `npm run build -w @timeforschool/client`, version bump in `packages/client/package.json`.
- CI publish: `.github/workflows/publish-client.yml` on GitHub Release + `NPM_TOKEN`.

---

## Quality bar before opening/updating a PR

1. If API triggers above applied → `npm run openapi:sync` and commit generated files.
2. `npm run build` (API + client compile).
3. `npm run build:docs` when `apps/docs/**` or API surface changed.
4. `npm test` when touching runtime behavior.

---

## Stop conditions

- Do not commit secrets (`.env`, `NEIS_API_KEY`).
- Do not edit generated `apps/docs/content/docs/api/*.mdx` by hand—re-run `openapi:sync`.
- If `openapi:sync` fails, fix the Elysia app export first (`app.handle` → `/docs/json` must return 200).
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,36 @@ Built with [Elysia](https://elysiajs.com). Interactive reference UI is [Scalar](
| Package | Path | Description |
|---------|------|-------------|
| `timeforschool-api` | repo root | HTTP API (`src/`) |
| `@timeforschool/client` | `packages/client/` | NEIS + Comcigan client library |
| `@timeforschool/client` | `packages/client/` | NEIS + Comcigan client library (publishable to npm) |
| `@timeforschool/docs` | `apps/docs/` | Fumadocs site (client + OpenAPI reference) |

## Documentation site

[Fumadocs](https://www.fumadocs.dev/) + Next.js at `apps/docs/`:

```bash
npm run dev:docs # http://localhost:3000
npm run build:docs
```

When API routes or schemas change, refresh the committed OpenAPI artifact and generated MDX:

```bash
npm run openapi:sync
```

Deploy on Vercel as **two projects** (API at repo root, docs at `apps/docs`). Full plan: [docs/vercel-deploy.md](docs/vercel-deploy.md).

Agents: see [AGENTS.md](AGENTS.md) — run `npm run openapi:sync` when changing `src/` API code.

## Publishing `@timeforschool/client`

```bash
npm run build -w @timeforschool/client
npm publish -w @timeforschool/client --access public
```

Requires an npm account with access to the `@timeforschool` scope. CI publish on GitHub Release uses `.github/workflows/publish-client.yml` and `NPM_TOKEN`.

Use the client standalone:

Expand All @@ -31,7 +60,7 @@ OpenAPI follows [Elysia’s OpenAPI pattern](https://elysiajs.com/patterns/opena
| OpenAPI 3 JSON | `/docs/json` |
| Service metadata | `GET /` |

Production docs: https://api.timefor.school/docs
Documentation site: https://docs.timefor.school (Scalar on API: https://api.timefor.school/docs)

- **Servers** — Production and `localhost:8000` are listed in the OpenAPI `servers` block and Scalar’s server picker.
- **Tags** — Sidebar groups (Meta, School, Classes, …) from `documentation.tags` + each route’s `detail.tags`.
Expand Down
8 changes: 8 additions & 0 deletions apps/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/node_modules
/.next
/out
.source
*.tsbuildinfo
next-env.d.ts
.env*.local
.vercel
28 changes: 28 additions & 0 deletions apps/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# @timeforschool/docs

[Fumadocs](https://www.fumadocs.dev/) documentation site for:

- **`@timeforschool/client`** — hand-written MDX under `content/docs/client/`
- **HTTP API** — OpenAPI-driven pages from `openapi/openapi.json`

## Commands

From the repository root:

```bash
npm run dev:docs
npm run build:docs
npm run openapi:sync # export spec from Elysia + regenerate API MDX
```

## Deploy (Vercel)

This is the **docs** Vercel project only (separate from the Elysia API project). See [docs/vercel-deploy.md](../../docs/vercel-deploy.md).

Set **Root Directory** to `apps/docs`. The install/build commands in `vercel.json` run from the monorepo root so workspaces resolve.

Optional env:

| Variable | Description |
| -------- | ----------- |
| `NEXT_PUBLIC_DOCS_URL` | Canonical site URL for metadata (default `https://docs.timefor.school`) |
6 changes: 6 additions & 0 deletions apps/docs/app/(home)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';

export default function Layout({ children }: LayoutProps<'/'>) {
return <HomeLayout {...baseOptions()}>{children}</HomeLayout>;
}
30 changes: 30 additions & 0 deletions apps/docs/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Link from 'next/link';
import { apiProductionUrl } from '@/lib/shared';

export default function HomePage() {
return (
<div className="flex flex-col justify-center text-center flex-1 px-6">
<h1 className="text-3xl font-bold mb-3">TimeForSchool</h1>
<p className="text-fd-muted-foreground mb-8 max-w-lg mx-auto">
Documentation for the TypeScript client and the production HTTP API
(NEIS + Comcigan).
</p>
<div className="flex flex-wrap gap-3 justify-center">
<Link
href="/docs"
className="rounded-lg bg-fd-primary px-4 py-2 text-sm font-medium text-fd-primary-foreground"
>
Read the docs
</Link>
<a
href={apiProductionUrl}
className="rounded-lg border px-4 py-2 text-sm font-medium"
rel="noreferrer"
target="_blank"
>
Open live API
</a>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions apps/docs/app/api/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';

export const { GET } = createFromSource(source, {
// https://docs.orama.com/docs/orama-js/supported-languages
language: 'english',
});
Loading
Loading