Skip to content

feat(write-api): POST/PATCH/DELETE endpoints + indexing + reconcile + authz#29

Merged
themightychris merged 8 commits into
mainfrom
feat/write-api
May 16, 2026
Merged

feat(write-api): POST/PATCH/DELETE endpoints + indexing + reconcile + authz#29
themightychris merged 8 commits into
mainfrom
feat/write-api

Conversation

@themightychris
Copy link
Copy Markdown
Member

Summary

Implements plan: write-api (plans/write-api.md).

Every documented POST/PATCH/DELETE across projects, members, updates, buzz, help-wanted, people, and tags is now wired through store.transact, producing one gitsheets commit per mutation with the documented author + trailer policy. Newsletter PATCH is the lone private-only mutation (no public commit). Authorization is enforced via a new requireAuth(marker, ctx?) helper at the route and service boundaries per behaviors/authorization.md.

Absorbed deferrals from upstream plans:

  • Sheet.defineIndex calls wired for all secondary indices in data-model.md (apps/api/src/store/sheet-indices.ts).
  • apps/api/scripts/reconcile-private-store.ts walks public Person records and flags / --fix-es missing private profiles.
  • invalidateFacets() + FTS upsert/remove are dispatched via a small StateApply helper that runs after the transaction successfully commits so in-memory state and gitsheets stay in lockstep on commit and on rollback.

Tag handling: user-supplied unknown slugs return 422 tag_not_found; staff-supplied unknown slugs auto-create the tag in the same commit per behaviors/tags.md.

Schemas for sheets carrying denormalized path-template fields (memberships, updates, buzz, help-wanted roles + interest) gain .passthrough() so projectSlug / personSlug survive validation and reach the gitsheets path renderer. JSON Schema exports regenerated (additionalProperties: {}).

Authenticated permissions cross-check from the read-api absorbed deferral is exercised by cross-cutting > GET project permissions flip across anonymous → maintainer → staff.

Test plan

  • POST /api/projects (anon → 401, user → 201 + founder membership, slug collision → 409, reserved slug → 422, unknown tag for non-staff → 422, auto-create tag for staff)
  • PATCH /api/projects/:slug (maintainer can edit, staff slug rename writes new + deletes old + writes SlugHistory; non-staff slug change → 422)
  • DELETE /api/projects/:slug (staff soft-delete → subsequent GET 404; non-staff → 403)
  • POST /api/projects/:slug/members (duplicate add → 409 already_member)
  • DELETE /api/projects/:slug/members/:slug (current maintainer → 409 cannot_remove_maintainer)
  • Project update create (member can post; non-member non-staff cannot)
  • Project buzz create + duplicate URL → 409
  • Help-wanted create + FTS pick-up; express-interest 30-day rate cap; fill side effects; cannot express interest on filled role
  • Tag create (staff only), merge namespace mismatch → 409
  • Newsletter PATCH → 404 when no private profile exists
  • Facet count invalidation: list total bumps by one after a create
  • FTS upsert: new project surfaces via ?q=
  • permissions block flips with caller account level on GET /api/projects/:slug
  • npm run lint, npm run -w apps/api type-check, npm run build — all clean
  • Full vitest suite: 142 / 142 tests pass

🤖 Generated with Claude Code

themightychris added a commit that referenced this pull request May 16, 2026
The gitsheets path template renders against the validated record. With Zod's
default object behavior, denormalized path fields (projectSlug, personSlug,
etc.) supplied by write services would be stripped before the renderer ran,
causing PathTemplateError. Use .passthrough() on ProjectMembership,
ProjectUpdate, ProjectBuzz, HelpWantedRole, and HelpWantedInterestExpression
so those extras survive validation.

Regenerated the corresponding JSON Schema exports (additionalProperties:{}
instead of false).
@themightychris themightychris merged commit 183bb19 into main May 16, 2026
1 check passed
@themightychris themightychris deleted the feat/write-api branch May 16, 2026 22:58
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.

1 participant