1+ import "zod-openapi/extend" ;
12import { FastifyPluginAsync , FastifyRequest } from "fastify" ;
23import { AppRoles } from "../../common/roles.js" ;
34import { z } from "zod" ;
@@ -32,6 +33,14 @@ import {
3233} from "api/functions/cache.js" ;
3334import { createAuditLogEntry } from "api/functions/auditLog.js" ;
3435import { Modules } from "common/modules.js" ;
36+ import {
37+ FastifyPluginAsyncZodOpenApi ,
38+ FastifyZodOpenApiSchema ,
39+ FastifyZodOpenApiTypeProvider ,
40+ serializerCompiler ,
41+ validatorCompiler ,
42+ } from "fastify-zod-openapi" ;
43+ import { ts , withTags } from "api/components/index.js" ;
3544
3645const repeatOptions = [ "weekly" , "biweekly" ] as const ;
3746export const CLIENT_HTTP_CACHE_POLICY = `public, max-age=${ EVENT_CACHED_DURATION } , stale-while-revalidate=420, stale-if-error=3600` ;
@@ -54,77 +63,57 @@ const requestSchema = baseSchema.extend({
5463 repeatEnds : z . string ( ) . optional ( ) ,
5564} ) ;
5665
57- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5866const postRequestSchema = requestSchema . refine (
5967 ( data ) => ( data . repeatEnds ? data . repeats !== undefined : true ) ,
6068 {
6169 message : "repeats is required when repeatEnds is defined" ,
6270 } ,
6371) ;
64-
6572export type EventPostRequest = z . infer < typeof postRequestSchema > ;
66- type EventGetRequest = {
67- Params : { id : string } ;
68- Querystring : { ts ?: number } ;
69- Body : undefined ;
70- } ;
71-
72- type EventDeleteRequest = {
73- Params : { id : string } ;
74- Querystring : undefined ;
75- Body : undefined ;
76- } ;
77-
78- const responseJsonSchema = zodToJsonSchema (
79- z . object ( {
80- id : z . string ( ) ,
81- resource : z . string ( ) ,
82- } ) ,
83- ) ;
8473
85- // GET
8674const getEventSchema = requestSchema . extend ( {
8775 id : z . string ( ) ,
8876} ) ;
89-
9077export type EventGetResponse = z . infer < typeof getEventSchema > ;
91- const getEventJsonSchema = zodToJsonSchema ( getEventSchema ) ;
9278
9379const getEventsSchema = z . array ( getEventSchema ) ;
9480export type EventsGetResponse = z . infer < typeof getEventsSchema > ;
95- type EventsGetRequest = {
96- Body : undefined ;
97- Querystring ?: {
98- upcomingOnly ?: boolean ;
99- featuredOnly ?: boolean ;
100- host ?: string ;
101- ts ?: number ;
102- } ;
103- } ;
10481
105- const eventsPlugin : FastifyPluginAsync = async ( fastify , _options ) => {
82+ const eventsPlugin : FastifyPluginAsyncZodOpenApi = async (
83+ fastify ,
84+ _options ,
85+ ) => {
86+ fastify . setValidatorCompiler ( validatorCompiler ) ;
87+ fastify . setSerializerCompiler ( serializerCompiler ) ;
10688 const limitedRoutes : FastifyPluginAsync = async ( fastify ) => {
10789 fastify . register ( rateLimiter , {
10890 limit : 30 ,
10991 duration : 60 ,
11092 rateLimitIdentifier : "events" ,
11193 } ) ;
112- fastify . get < EventsGetRequest > (
94+ fastify . withTypeProvider < FastifyZodOpenApiTypeProvider > ( ) . get (
11395 "/" ,
11496 {
115- schema : {
116- querystring : {
117- type : "object" ,
118- properties : {
119- upcomingOnly : { type : "boolean" } ,
120- host : { type : "string" } ,
121- ts : { type : "number" } ,
122- } ,
123- } ,
97+ schema : withTags ( [ "Events" ] , {
98+ querystring : z . object ( {
99+ upcomingOnly : z . coerce . boolean ( ) . optional ( ) . openapi ( {
100+ description :
101+ "If true, only get events which end after the current time." ,
102+ } ) ,
103+ featuredOnly : z . coerce . boolean ( ) . optional ( ) . openapi ( {
104+ description :
105+ "If true, only get events which are marked as featured." ,
106+ } ) ,
107+ host : z
108+ . enum ( OrganizationList as [ string , ...string [ ] ] )
109+ . optional ( )
110+ . openapi ( { description : "Event host filter." } ) ,
111+ ts,
112+ } ) ,
124113 response : { 200 : getEventsSchema } ,
125- } ,
114+ } ) ,
126115 } ,
127- async ( request : FastifyRequest < EventsGetRequest > , reply ) => {
116+ async ( request , reply ) => {
128117 const upcomingOnly = request . query ?. upcomingOnly || false ;
129118 const featuredOnly = request . query ?. featuredOnly || false ;
130119 const host = request . query ?. host ;
@@ -230,15 +219,18 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
230219 ) ;
231220 } ;
232221
233- fastify . post < { Body : EventPostRequest } > (
222+ fastify . withTypeProvider < FastifyZodOpenApiTypeProvider > ( ) . post (
234223 "/:id?" ,
235224 {
236- schema : {
237- response : { 201 : responseJsonSchema } ,
238- } ,
239- preValidation : async ( request , reply ) => {
240- await fastify . zodValidateBody ( request , reply , postRequestSchema ) ;
241- } ,
225+ schema : withTags ( [ "Events" ] , {
226+ response : {
227+ 201 : z . object ( {
228+ id : z . string ( ) ,
229+ resource : z . string ( ) ,
230+ } ) ,
231+ } ,
232+ body : postRequestSchema ,
233+ } ) satisfies FastifyZodOpenApiSchema ,
242234 onRequest : async ( request , reply ) => {
243235 await fastify . authorize ( request , reply , [ AppRoles . EVENTS_MANAGER ] ) ;
244236 } ,
@@ -361,17 +353,28 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
361353 }
362354 } ,
363355 ) ;
364- fastify . delete < EventDeleteRequest > (
356+ fastify . withTypeProvider < FastifyZodOpenApiTypeProvider > ( ) . delete (
365357 "/:id" ,
366358 {
367- schema : {
368- response : { 201 : responseJsonSchema } ,
369- } ,
359+ schema : withTags ( [ "Events" ] , {
360+ params : z . object ( {
361+ id : z . string ( ) . min ( 1 ) . openapi ( {
362+ description : "Event ID to delete." ,
363+ example : "6667e095-8b04-4877-b361-f636f459ba42" ,
364+ } ) ,
365+ } ) ,
366+ response : {
367+ 201 : z . object ( {
368+ id : z . string ( ) ,
369+ resource : z . string ( ) ,
370+ } ) ,
371+ } ,
372+ } ) satisfies FastifyZodOpenApiSchema ,
370373 onRequest : async ( request , reply ) => {
371374 await fastify . authorize ( request , reply , [ AppRoles . EVENTS_MANAGER ] ) ;
372375 } ,
373376 } ,
374- async ( request : FastifyRequest < EventDeleteRequest > , reply ) => {
377+ async ( request , reply ) => {
375378 const id = request . params . id ;
376379 if ( ! request . username ) {
377380 throw new UnauthenticatedError ( { message : "Username not found." } ) ;
@@ -421,20 +424,23 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
421424 ) ;
422425 } ,
423426 ) ;
424- fastify . get < EventGetRequest > (
427+ fastify . withTypeProvider < FastifyZodOpenApiTypeProvider > ( ) . get (
425428 "/:id" ,
426429 {
427- schema : {
428- querystring : {
429- type : "object" ,
430- properties : {
431- ts : { type : "number" } ,
432- } ,
433- } ,
434- response : { 200 : getEventJsonSchema } ,
435- } ,
430+ schema : withTags ( [ "Events" ] , {
431+ params : z . object ( {
432+ id : z . string ( ) . min ( 1 ) . openapi ( {
433+ description : "Event ID to delete." ,
434+ example : "6667e095-8b04-4877-b361-f636f459ba42" ,
435+ } ) ,
436+ } ) ,
437+ querystring : z . object ( {
438+ ts,
439+ } ) ,
440+ response : { 200 : getEventSchema } ,
441+ } ) ,
436442 } ,
437- async ( request : FastifyRequest < EventGetRequest > , reply ) => {
443+ async ( request , reply ) => {
438444 const id = request . params . id ;
439445 const ts = request . query ?. ts ;
440446
@@ -485,7 +491,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
485491 reply . header ( "etag" , etag ) ;
486492 }
487493
488- return reply . send ( item ) ;
494+ return reply . send ( item as z . infer < typeof getEventSchema > ) ;
489495 } catch ( e ) {
490496 if ( e instanceof BaseError ) {
491497 throw e ;
0 commit comments