@@ -4,6 +4,7 @@ import { z } from "zod";
4
4
import { zodToJsonSchema } from "zod-to-json-schema" ;
5
5
import { OrganizationList } from "../orgs.js" ;
6
6
import {
7
+ DeleteItemCommand ,
7
8
DynamoDBClient ,
8
9
PutItemCommand ,
9
10
ScanCommand ,
@@ -18,7 +19,7 @@ import moment from "moment-timezone";
18
19
19
20
const repeatOptions = [ "weekly" , "biweekly" ] as const ;
20
21
21
- const baseBodySchema = z . object ( {
22
+ const baseSchema = z . object ( {
22
23
title : z . string ( ) . min ( 1 ) ,
23
24
description : z . string ( ) . min ( 1 ) ,
24
25
start : z . string ( ) ,
@@ -30,16 +31,20 @@ const baseBodySchema = z.object({
30
31
paidEventId : z . optional ( z . string ( ) . min ( 1 ) ) ,
31
32
} ) ;
32
33
33
- const requestBodySchema = baseBodySchema
34
- . extend ( {
35
- repeats : z . optional ( z . enum ( repeatOptions ) ) ,
36
- repeatEnds : z . string ( ) . optional ( ) ,
37
- } )
38
- . refine ( ( data ) => ( data . repeatEnds ? data . repeats !== undefined : true ) , {
34
+ const requestSchema = baseSchema . extend ( {
35
+ repeats : z . optional ( z . enum ( repeatOptions ) ) ,
36
+ repeatEnds : z . string ( ) . optional ( ) ,
37
+ } ) ;
38
+
39
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
40
+ const postRequestSchema = requestSchema . refine (
41
+ ( data ) => ( data . repeatEnds ? data . repeats !== undefined : true ) ,
42
+ {
39
43
message : "repeats is required when repeatEnds is defined" ,
40
- } ) ;
44
+ } ,
45
+ ) ;
41
46
42
- type EventPostRequest = z . infer < typeof requestBodySchema > ;
47
+ type EventPostRequest = z . infer < typeof postRequestSchema > ;
43
48
44
49
const responseJsonSchema = zodToJsonSchema (
45
50
z . object ( {
@@ -49,9 +54,15 @@ const responseJsonSchema = zodToJsonSchema(
49
54
) ;
50
55
51
56
// GET
52
- const getResponseBodySchema = z . array ( requestBodySchema ) ;
53
- const getResponseJsonSchema = zodToJsonSchema ( getResponseBodySchema ) ;
54
- export type EventGetResponse = z . infer < typeof getResponseBodySchema > ;
57
+ const getEventSchema = requestSchema . extend ( {
58
+ id : z . string ( ) ,
59
+ } ) ;
60
+
61
+ export type EventGetResponse = z . infer < typeof getEventSchema > ;
62
+ const getEventJsonSchema = zodToJsonSchema ( getEventSchema ) ;
63
+
64
+ const getEventsSchema = z . array ( getEventSchema ) ;
65
+ export type EventsGetResponse = z . infer < typeof getEventsSchema > ;
55
66
type EventsGetQueryParams = { upcomingOnly ?: boolean } ;
56
67
57
68
const dynamoClient = new DynamoDBClient ( {
@@ -66,7 +77,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
66
77
response : { 200 : responseJsonSchema } ,
67
78
} ,
68
79
preValidation : async ( request , reply ) => {
69
- await fastify . zodValidateBody ( request , reply , requestBodySchema ) ;
80
+ await fastify . zodValidateBody ( request , reply , postRequestSchema ) ;
70
81
} ,
71
82
onRequest : async ( request , reply ) => {
72
83
await fastify . authorize ( request , reply , [ AppRoles . MANAGER ] ) ;
@@ -101,6 +112,84 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
101
112
}
102
113
} ,
103
114
) ;
115
+ type EventGetRequest = {
116
+ Params : { id : string } ;
117
+ Querystring : undefined ;
118
+ Body : undefined ;
119
+ } ;
120
+ fastify . get < EventGetRequest > (
121
+ "/:id" ,
122
+ {
123
+ schema : {
124
+ response : { 200 : getEventJsonSchema } ,
125
+ } ,
126
+ } ,
127
+ async ( request : FastifyRequest < EventGetRequest > , reply ) => {
128
+ const id = request . params . id ;
129
+ try {
130
+ const response = await dynamoClient . send (
131
+ new ScanCommand ( {
132
+ TableName : genericConfig . DynamoTableName ,
133
+ FilterExpression : "#id = :id" ,
134
+ ExpressionAttributeNames : {
135
+ "#id" : "id" ,
136
+ } ,
137
+ ExpressionAttributeValues : marshall ( { ":id" : id } ) ,
138
+ } ) ,
139
+ ) ;
140
+ const items = response . Items ?. map ( ( item ) => unmarshall ( item ) ) ;
141
+ if ( items ?. length !== 1 ) {
142
+ throw new Error ( "Event not found" ) ;
143
+ }
144
+ reply . send ( items [ 0 ] ) ;
145
+ } catch ( e : unknown ) {
146
+ if ( e instanceof Error ) {
147
+ request . log . error ( "Failed to get from DynamoDB: " + e . toString ( ) ) ;
148
+ }
149
+ throw new DatabaseFetchError ( {
150
+ message : "Failed to get event from Dynamo table." ,
151
+ } ) ;
152
+ }
153
+ } ,
154
+ ) ;
155
+ type EventDeleteRequest = {
156
+ Params : { id : string } ;
157
+ Querystring : undefined ;
158
+ Body : undefined ;
159
+ } ;
160
+ fastify . delete < EventDeleteRequest > (
161
+ "/:id" ,
162
+ {
163
+ schema : {
164
+ response : { 200 : responseJsonSchema } ,
165
+ } ,
166
+ onRequest : async ( request , reply ) => {
167
+ await fastify . authorize ( request , reply , [ AppRoles . MANAGER ] ) ;
168
+ } ,
169
+ } ,
170
+ async ( request : FastifyRequest < EventDeleteRequest > , reply ) => {
171
+ const id = request . params . id ;
172
+ try {
173
+ await dynamoClient . send (
174
+ new DeleteItemCommand ( {
175
+ TableName : genericConfig . DynamoTableName ,
176
+ Key : marshall ( { id } ) ,
177
+ } ) ,
178
+ ) ;
179
+ reply . send ( {
180
+ id,
181
+ resource : `/api/v1/event/${ id } ` ,
182
+ } ) ;
183
+ } catch ( e : unknown ) {
184
+ if ( e instanceof Error ) {
185
+ request . log . error ( "Failed to delete from DynamoDB: " + e . toString ( ) ) ;
186
+ }
187
+ throw new DatabaseInsertError ( {
188
+ message : "Failed to delete event from Dynamo table." ,
189
+ } ) ;
190
+ }
191
+ } ,
192
+ ) ;
104
193
type EventsGetRequest = {
105
194
Body : undefined ;
106
195
Querystring ?: EventsGetQueryParams ;
@@ -112,7 +201,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
112
201
querystring : {
113
202
upcomingOnly : { type : "boolean" } ,
114
203
} ,
115
- response : { 200 : getResponseJsonSchema } ,
204
+ response : { 200 : getEventsSchema } ,
116
205
} ,
117
206
} ,
118
207
async ( request : FastifyRequest < EventsGetRequest > , reply ) => {
@@ -123,7 +212,7 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
123
212
) ;
124
213
const items = response . Items ?. map ( ( item ) => unmarshall ( item ) ) ;
125
214
const currentTimeChicago = moment ( ) . tz ( "America/Chicago" ) ;
126
- let parsedItems = getResponseBodySchema . parse ( items ) ;
215
+ let parsedItems = getEventsSchema . parse ( items ) ;
127
216
if ( upcomingOnly ) {
128
217
parsedItems = parsedItems . filter ( ( item ) => {
129
218
try {
0 commit comments