Summary
The admin routes for events and announcements are 80%+ identical: zod validation schemas, list-query where builder, scope-by-author-class defaulting, pagination shape, audit-row writing on PATCH. Plans 4 (forms) and 5 (broadcast) will add more parallel surfaces; extract before that hardens.
Requirements
Context
Flagged in both the Plan 2 and Plan 3 final reviews. Parallel artifact types are intentionally separated at the table level, but the route-construction code wants to be shared.
Implementation Notes
Keep the per-artifact-type sub-app structure (one folder per artifact under routes/admin/) — only the helpers move out. Don't introduce a polymorphic router; that fights Drizzle's type narrowing.
Summary
The admin routes for
eventsandannouncementsare 80%+ identical: zod validation schemas, list-querywherebuilder, scope-by-author-class defaulting, pagination shape, audit-row writing on PATCH. Plans 4 (forms) and 5 (broadcast) will add more parallel surfaces; extract before that hardens.Requirements
buildArtifactListWhere(table, query, allowedStatuses, allowedScopes)to a shared moduleinferDefaultScope(actor)helperslugify(name)topackages/api/src/lib/slug.ts(currently duplicated inadmin/events/index.tsandeventsSubmit.ts)admin/events/index.ts,admin/events/byId.ts,admin/announcements/index.ts,admin/announcements/byId.ts, andeventsSubmit.tsto use the shared helpersContext
Flagged in both the Plan 2 and Plan 3 final reviews. Parallel artifact types are intentionally separated at the table level, but the route-construction code wants to be shared.
Implementation Notes
Keep the per-artifact-type sub-app structure (one folder per artifact under
routes/admin/) — only the helpers move out. Don't introduce a polymorphic router; that fights Drizzle's type narrowing.