Initial Commit
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# rsvp-schemas
|
||||
|
||||
## Purpose
|
||||
|
||||
Local Zod schemas for the RSVP / invitation surface. Owns the
|
||||
persisted `RsvpSchema`, the `CreateRsvpSchema` request body for
|
||||
`POST /:id/rsvp`, the `InviteUsersSchema` body for
|
||||
`POST /:id/invite`, the `AttendeeQuerySchema` for the attendee
|
||||
listing endpoint, and the joined `AttendeeSchema` that combines the
|
||||
RSVP row with optional sparse user fields (`user_name`,
|
||||
`user_email`, `user_image`).
|
||||
|
||||
The RSVP status is a four-value string enum (`invited`, `going`,
|
||||
`maybe`, `not_going`) — not the integer encoding used for event
|
||||
status — because RSVPs are read-heavy and the integer encoding gave
|
||||
no real-world benefit when the upstream surface was originally
|
||||
specced. The `InviteUsersSchema` enforces a 1..100 length range as
|
||||
the schema-level hard cap; the runtime cap (default 50) is enforced
|
||||
separately inside the `POST /:id/invite` handler so deployments can
|
||||
narrow without recompiling.
|
||||
|
||||
## Features
|
||||
|
||||
- 9 schemas spanning persistence, request, response, and pagination.
|
||||
- `RsvpStatusSchema` constrains status to the four documented values.
|
||||
- `InviteUsersSchema` enforces `1 <= length <= 100` on `user_ids`
|
||||
with non-empty-string members.
|
||||
- `AttendeeSchema` extends `RsvpSchema` with optional sparse user
|
||||
fields for joined listing endpoints.
|
||||
- `AttendeeQuerySchema` coerces query-string parameters.
|
||||
- `RsvpResponseSchema` carries a nullable `rsvp` field for
|
||||
"no current RSVP" representations.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { CreateRsvpSchema, RsvpStatusSchema } from "@suaveplan/api-plugin-events";
|
||||
|
||||
const body = CreateRsvpSchema.parse({ status: "going" });
|
||||
const isValid = RsvpStatusSchema.safeParse("yes").success; // false
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
AttendeeListResponseSchema,
|
||||
AttendeeQuerySchema,
|
||||
InviteResponseSchema,
|
||||
InviteUsersSchema,
|
||||
} from "@suaveplan/api-plugin-events";
|
||||
|
||||
// Validate an invite body, then enforce the runtime cap separately
|
||||
const parsed = InviteUsersSchema.parse({ user_ids: ["user:1", "user:2"] });
|
||||
if (parsed.user_ids.length > config.maxInvitesPerRequest) {
|
||||
return res.status(400).json({ error: "INVITE_LIMIT_EXCEEDED" });
|
||||
}
|
||||
|
||||
// Build a paginated attendee response
|
||||
const query = AttendeeQuerySchema.parse({ status: "going", limit: "100" });
|
||||
const response = AttendeeListResponseSchema.parse({
|
||||
data: [
|
||||
/* joined attendees */
|
||||
],
|
||||
total: 42,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `RsvpStatusSchema: z.ZodEnum<["invited", "going", "maybe", "not_going"]>`.
|
||||
- `RsvpSchema: z.ZodObject` — persisted RSVP row.
|
||||
- `CreateRsvpSchema: z.ZodObject` — `POST /:id/rsvp` body.
|
||||
- `InviteUsersSchema: z.ZodObject` — `POST /:id/invite` body
|
||||
(`user_ids: string[]`, length 1..100, non-empty members).
|
||||
- `AttendeeQuerySchema: z.ZodObject` — pagination + status filter.
|
||||
- `AttendeeSchema: z.ZodObject` — RSVP + sparse user fields.
|
||||
- `AttendeeListResponseSchema: z.ZodObject` — `{ data, total }`.
|
||||
- `RsvpResponseSchema: z.ZodObject` — `{ rsvp: RsvpSchema | null }`.
|
||||
- `InviteResponseSchema: z.ZodObject` — `{ invitations, total }`.
|
||||
|
||||
Every schema exports a matching `type Foo = z.infer<typeof FooSchema>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The schema-level cap on `InviteUsersSchema.user_ids` is 100 and the
|
||||
runtime cap (`config.maxInvitesPerRequest`) defaults to 50. The two
|
||||
limits coexist because the schema-level cap protects against
|
||||
abusive payloads (10 000-element arrays that would still parse
|
||||
correctly but exhaust the server) and the runtime cap encodes a
|
||||
per-deployment policy. The route handler enforces the runtime cap
|
||||
after the schema parse so the response code is `400` with
|
||||
`EVENTS_PLUGIN.INVITE_LIMIT_EXCEEDED`, not `422`/`400` from Zod.
|
||||
|
||||
`AttendeeSchema` extends `RsvpSchema` with optional joined columns
|
||||
because the suaveplan SurrealDB driver doesn't materialise FETCH
|
||||
expansions into nested objects by default — the route handler reads
|
||||
the raw projection from `findAttendees` which mixes RSVP fields with
|
||||
optional joined user fields.
|
||||
Reference in New Issue
Block a user