deckhearth/.cursor/rules/api-routes.mdc
Randall Stillwell 1944b1ed48 bootstrap: agent pipeline v0.5.0 + ship-readiness review
Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0):

L1 — Context (curated brain)
- AGENTS.md: orientation, conventions, 8 explicit gotchas
- .cursor/rules/: no-go-zones, api-routes, auth-and-permissions,
  db-and-schema, ui-and-theming, schema-map
- .cursor/skills/: add-api-route, add-page recipes
- docs/agent-context/README.md: layer explainer
- docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference
  (replaces Prisma schema map since stack is raw SQL)

L2 — Subagent roles (copied verbatim from upstream templates)
- 9 .cursor/agents/role-*.md files: Conductor, IA-Architect,
  UX-Reviewer, Architect, Implementer, Reviewer,
  Design-System-Auditor, A11y-Auditor, Doc-Writer

L3 — Pipeline scaffolding (Vercel variant)
- CI: lint + schema-map-drift only (no duplicate build —
  Vercel handles it). Test job commented out until vitest lands.
- preview-smoke + visual-diff via wait-for-vercel-preview
- pr-health-rollup sticky comment aggregator
- agent-context-drift weekly cron
- PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged)
- .convoys/ folder + seed ship-readiness.md review
- lib/flags/index.js (JS — converted from TS template)
- scripts/wt.sh (Cursor 3.2 deprecation stub),
  scripts/log-convoy-event.sh
- tests/smoke/app.smoke.spec.ts (Playwright skeleton)

Manifest
- .agent-context-manifest.yml: tracks 31 artifacts by sha256
  for future sync-agent-context drift detection

Review
- .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers,
  5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with
  proposed 13-convoy launch sequence.

No production code changed in this commit. All findings in
the ship-readiness review will be addressed in follow-up convoys
starting with fix-auth-bypass.

Structural brain: user-code-review-graph MCP has indexed the
codebase (122 files, 628 nodes, 5602 edges, 11 communities,
84 flows). Per-developer; not committed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 02:31:26 -05:00

106 lines
3.7 KiB
Text

---
description: Conventions for Next.js Pages-router API route handlers in tcg-vault
globs: pages/api/**/*.js
---
# API Route Conventions
Pages router handlers; `(req, res)` signature; Vercel serverless functions.
## Authentication & Authorization
Two paths are in use; both go through `lib/permission-middleware.js`.
```js
// 1. Generic auth — every protected route
import { getUserFromRequest } from '../../lib/permission-middleware';
export default async function handler(req, res) {
const user = await getUserFromRequest(req);
if (!user) return res.status(401).json({ error: 'Authentication required' });
// user = { userId, email, role }
// ...
}
```
```js
// 2. Collection-scoped auth — when the route operates on a specific collection
import { withCollectionPermission } from '../../lib/permission-middleware';
async function handler(req, res) {
// req.user and req.permission are populated by the wrapper
const { userId, role } = req.user;
}
export default withCollectionPermission('viewer')(handler);
// 'viewer' | 'editor' | 'owner' — checks owner OR is_public OR explicit collection_permissions row
```
**CRITICAL — known bug:** `getUserFromRequest` currently has a development-mode fallback that returns a hardcoded admin user when no Bearer token is present. Until that's fixed, callers MUST also assert `req.headers.authorization` exists when the route is sensitive (admin operations, deletes). See `AGENTS.md` Gotcha #2.
## Request validation
No schema validator is installed (no zod / yup / valibot). Validate manually:
```js
const { name, game } = req.body || {};
if (!name || typeof name !== 'string' || name.trim().length === 0) {
return res.status(400).json({ error: 'name is required' });
}
if (!['mtg', 'pokemon', 'lorcana'].includes(game)) {
return res.status(400).json({ error: 'invalid game' });
}
```
When adding zod (planned in `.convoys/`), define schemas at the top of the file.
## Method gating
Reject unsupported methods explicitly — Next.js will otherwise call the handler for any method:
```js
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
```
## Error handling
Wrap the handler body in `try/catch`. Never throw unhandled — leaks stack traces in the Vercel response.
```js
export default async function handler(req, res) {
try {
// ...
} catch (err) {
console.error('[POST /api/collections]', err);
return res.status(500).json({ error: 'Internal server error' });
}
}
```
## Database access
- **Prefer:** `import { sql } from '@vercel/postgres'` and tagged templates: `` await sql`SELECT … WHERE id = ${id}` ``.
- **Avoid:** `import { db } from '../../lib/database'`. Its `query(str, params)` API uses `sql.unsafe` after manual interpolation — SQL-injection vector. Slated for removal in a convoy.
- **Always select narrow columns** — don't `SELECT *` from `cards` (large `oracle_text`, `colors` JSONB).
## Response shape
- Success (GET / read): `res.status(200).json({ data: ... })` OR direct payload — codebase is inconsistent; match the surrounding route's existing shape.
- Created (POST): `res.status(201).json({ data: ... })`.
- Errors: `res.status(<4xx|5xx>).json({ error: string, details?: unknown })`.
## Activity logging
Any handler that mutates a collection must call `logCollectionActivity`:
```js
import { logCollectionActivity } from '../../lib/permission-middleware';
await logCollectionActivity(collectionId, userId, 'card_added', { cardId, quantity });
```
## Dev/test endpoints
`pages/api/simple.js`, `pages/api/test-auth.js`, `pages/api/test-db.js`, `pages/api/setup-database.js` — these are dev-only endpoints currently shipped to prod. Don't add more. Existing ones should be deleted or admin-gated before public launch.