The Board tab and the Roadmap's first page get public read routes — /api/public/p/{identifier}/board and …/roadmap with no cursor
Opened by Zhu Yue ·
Two of the five tabs a public project page renders have no public endpoint that returns their first page, so a renderer outside this repository cannot draw them at all.
- Board.
publicProjectsService.getBoard(identifier, actorUserId)exists and is tested, and nothing underapp/api/public/calls it. It was reached directly by the deletedapp/(public)/p/[identifier]/board/page.tsx. - Roadmap.
app/api/public/p/[identifier]/roadmap/route.tsexists but serves only the per-column pagination — it requiresbucketandcursorand answers400 MISSING_ROADMAP_CURSORwithout them. The tab's initial four columns come fromgetRoadmap, which likewise has no route.
This card adds the two reads. MOTIR-3951's own service sweep names both as "the public project READ/WRITE contract that MOTIR-3877's motir-marketing /p/* pages will re-expose through new API routes" — this is that card, for the read half of the tab payloads.
Follow the shape of the route beside them exactly (app/api/public/p/[identifier]/route.ts, MOTIR-3945): the capability gate FIRST (publicSurfaceUnavailable() — before the rate limit and before any session read), then getSession() for an optional actorUserId ?? null, then one service call, then map ProjectNotFoundError → 404. Anonymous on READ, viewer-aware only for personalisation. HTTP layer only — no logic outside the service (the 4-layer rule, motir-core/CLAUDE.md).
The roadmap route already exists, so extend it rather than adding a second one: with no bucket/cursor it returns the whole PublicRoadmapDto; with both it keeps today's per-column page and today's two 400s. That keeps one path per resource and leaves the shipped pagination contract untouched.
Every new operation owes its contract entry. tests/api/public/contract-coverage.test.ts fails on a route with no declared operation, so lib/api/public/openapi/operations.ts gains the board operation and the roadmap operation's response shape gains its no-cursor arm. tests/api/public/cloud-gate-totality.test.ts and anonymous-posture.test.ts enumerate the surface too — a new route that is not in them is a hole in the gate, not a passing test.
Acceptance criteria
GET /api/public/p/{identifier}/boardanswers200withPublicBoardDtofor an anonymous caller on a public project, and404 { code }for an unknown or non-public one, with no existence leak.GET /api/public/p/{identifier}/roadmapwith nobucketand nocursoranswers200with the fullPublicRoadmapDto; with both it behaves exactly as it does onorigin/maintoday, includingINVALID_ROADMAP_BUCKETandINVALID_ROADMAP_CURSOR→ 400. A regression test pins the existing arm.- Both routes call
publicSurfaceUnavailable()before any session read, and both answer the gate's own response whenMOTIR_CLOUDis unset. lib/api/public/openapi/operations.tsdeclares the board operation and the roadmap's no-cursor response;tests/api/public/contract-coverage.test.ts,contract-drift.test.ts,cloud-gate-totality.test.tsandanonymous-posture.test.tsare green without an exemption entry being added for either route.- The routes contain no business logic: each is parse → gate → one
publicProjectsServicecall → error mapping. - No file outside
motir-coreis touched.
Context refs
motir-core/app/api/public/p/[identifier]/route.ts— the shape to copy, and its posture commentmotir-core/app/api/public/p/[identifier]/roadmap/route.ts— the route being extendedmotir-core/lib/services/publicProjectsService.ts—getBoard(:474),getRoadmap(:814),getRoadmapColumn(:844)motir-core/lib/dto/publicProjects.ts—PublicBoardDto,PublicRoadmapDto,PUBLIC_ROADMAP_BUCKET_KEYSmotir-core/lib/publicProjects/cloudGate.ts—publicSurfaceUnavailable()motir-core/lib/api/public/openapi/operations.ts— the twelve declared operationsmotir-core/tests/api/public/—contract-coverage,contract-drift,cloud-gate-totality,anonymous-posturemotir-core/docs/decisions/public-surface-hosts.mdAMENDMENT 1 — the contract this surface is published under- the five tabs on motir.co — the consumer these two reads exist for
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.