1313├── server/
1414│ ├── _app.ts # Root router composing all feature routers
1515│ ├── routers/ # tRPC route definitions (13 routers)
16- │ │ ├── badge.ts # OpenBadges management
17- │ │ ├── featured.ts # Featured talks & speakers
18- │ │ ├── gallery.ts # Photo gallery
19- │ │ ├── proposal.ts # CFP proposals + admin sub-router
16+ │ │ ├── badge.ts # OpenBadges verification + admin sub-router
17+ │ │ ├── featured.ts # Featured talks & speakers (admin sub-router)
18+ │ │ ├── gallery.ts # Photo gallery + admin sub-router
19+ │ │ ├── proposal.ts # CFP proposals + admin & invitation sub-routers
2020│ │ ├── registration.ts # Attendee registration
2121│ │ ├── schedule.ts # Schedule management
2222│ │ ├── signing.ts # Contract signing
23- │ │ ├── speaker.ts # Speaker profiles, email, CLI auth + admin sub-router
24- │ │ ├── sponsor.ts # Sponsors, tiers, CRM, email + admin sub-routers
25- │ │ ├── tickets.ts # Ticket management
26- │ │ ├── travelSupport.ts # Travel support applications
27- │ │ ├── volunteer.ts # Volunteer signups
28- │ │ └── workshop.ts # Workshop management
23+ │ │ ├── speaker.ts # Speaker profiles + admin sub-router
24+ │ │ ├── sponsor.ts # Sponsors + tiers, crm, emailTemplates, contractTemplates sub-routers
25+ │ │ ├── tickets.ts # Ticket management (admin sub-router)
26+ │ │ ├── travelSupport.ts # Travel support + admin sub-router
27+ │ │ ├── volunteer.ts # Volunteer signups + admin sub-router
28+ │ │ └── workshop.ts # Workshop management + admin sub-router
2929│ ├── schemas/ # Zod validation schemas
3030│ │ ├── common.ts # Shared schemas (IdParamSchema, etc.)
3131│ │ ├── speaker.ts # Speaker input/update schemas
5050
5151### Router Organization
5252
53- One router per domain, registered in ` src/server/_app.ts ` . Use sub- routers for scoped operations:
53+ One router per domain, registered in ` src/server/_app.ts ` . All routers with admin operations use a standardized ` admin ` sub-router to separate organizer-only procedures from user-facing ones :
5454
5555``` typescript
5656export const speakerRouter = router ({
57- // Top-level: user-facing procedures (protectedProcedure)
57+ // Top-level: user-facing procedures (protectedProcedure / publicProcedure )
5858 getCurrent: protectedProcedure .query (... ),
5959 update: protectedProcedure .input (SpeakerInputSchema ).mutation (... ),
60- getEmails: protectedProcedure .query (... ),
61- updateEmail: protectedProcedure .input (EmailUpdateSchema ).mutation (... ),
62- generateCliToken: protectedProcedure .mutation (... ),
6360
6461 // admin sub-router: organizer-only operations (adminProcedure)
6562 admin: router ({
@@ -70,26 +67,27 @@ export const speakerRouter = router({
7067 update: adminProcedure .input (... ).mutation (... ),
7168 delete: adminProcedure .input (IdParamSchema ).mutation (... ),
7269 sendEmail: adminProcedure .input (... ).mutation (... ),
73- broadcastEmail: adminProcedure .input (... ).mutation (... ),
74- syncAudience: adminProcedure .mutation (... ),
7570 }),
7671})
7772```
7873
74+ ** All 10 routers with admin procedures use this pattern:** badge, featured, gallery, proposal, speaker, tickets, travelSupport, volunteer, workshop. The sponsor router uses domain-specific sub-routers (tiers, crm, emailTemplates, contractTemplates) instead. Three routers stay flat: registration (2 admin ops), signing (0 admin), schedule (1 admin).
75+
7976### Naming Conventions
8077
8178** Routers** — singular noun, camelCase: ` speaker ` , ` proposal ` , ` sponsor ` , ` travelSupport ` .
8279
8380** Sub-routers** — group related operations under a descriptive key:
8481
85- | Pattern | Example | Purpose |
86- | ---------------- | ----------------------------- | ---------------------------------- |
87- | ` admin ` | ` speaker.admin.list ` | Organizer-only CRUD and management |
88- | ` crm ` | ` sponsor.crm.sendEmail ` | CRM-specific operations |
89- | ` tiers ` | ` sponsor.tiers.create ` | Domain sub-entity management |
90- | ` invitation ` | ` proposal.invitation.send ` | Feature-specific workflows |
91- | ` activities ` | ` sponsor.crm.activities.list ` | Nested sub-entities |
92- | ` emailTemplates ` | ` sponsor.emailTemplates.list ` | Configuration management |
82+ | Pattern | Example | Purpose |
83+ | ------------------- | -------------------------------- | ---------------------------------- |
84+ | ` admin ` | ` speaker.admin.list ` | Organizer-only CRUD and management |
85+ | ` crm ` | ` sponsor.crm.sendEmail ` | CRM-specific operations |
86+ | ` tiers ` | ` sponsor.tiers.create ` | Domain sub-entity management |
87+ | ` invitation ` | ` proposal.invitation.send ` | Feature-specific workflows |
88+ | ` activities ` | ` sponsor.crm.activities.list ` | Nested sub-entities |
89+ | ` emailTemplates ` | ` sponsor.emailTemplates.list ` | Configuration management |
90+ | ` contractTemplates ` | ` sponsor.contractTemplates.list ` | Template management |
9391
9492** Procedures** — use verb or verb+noun, camelCase:
9593
@@ -457,7 +455,7 @@ When migrating a REST route to tRPC:
457455
4584561 . ** Read the route handler** — identify auth checks, input parsing, business logic, response shape
4594572 . ** Pick the right router** — add to existing feature router, not a new one
460- 3 . ** Pick the right sub-router** — ` admin ` for organizer ops, domain-specific (` crm ` , ` tiers ` ) for grouped features
458+ 3 . ** Pick the right sub-router** — ` admin ` for organizer ops (standardized across all routers) , domain-specific (` crm ` , ` tiers ` ) only for sponsor
4614594 . ** Choose procedure type** — ` adminProcedure ` for organizer-only, ` protectedProcedure ` for authenticated users
4624605 . ** Convert input validation** — ` req.body ` → Zod schema in ` .input() ` . Do not use ` .passthrough() ` on schemas that feed typed interfaces
4634616 . ** Handle service return types** — if the existing service returns ` Response ` objects (REST-oriented), check ` .ok ` and throw ` TRPCError ` on failure
0 commit comments