Skip to content

Commit 1e64bc2

Browse files
committed
allow repeats option
1 parent e109cb8 commit 1e64bc2

File tree

7 files changed

+108
-7
lines changed

7 files changed

+108
-7
lines changed

docs/swagger.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,49 @@ paths:
3434
summary: Returns the authenticated user's username.
3535
operationId: pingAuthenticated
3636

37+
responses:
38+
200:
39+
description: OK
40+
41+
x-amazon-apigateway-auth:
42+
type: NONE
43+
44+
x-amazon-apigateway-integration:
45+
responses:
46+
default:
47+
statusCode: 200
48+
passthroughBehavior: when_no_match
49+
httpMethod: POST
50+
contentHandling: CONVERT_TO_TEXT
51+
type: aws_proxy
52+
uri:
53+
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ApplicationPrefix}-lambda/invocations"
54+
/api/v1/events:
55+
get:
56+
summary: Get all ACM Events
57+
operationId: getAllEvents
58+
59+
responses:
60+
200:
61+
description: OK
62+
63+
x-amazon-apigateway-auth:
64+
type: NONE
65+
66+
x-amazon-apigateway-integration:
67+
responses:
68+
default:
69+
statusCode: 200
70+
passthroughBehavior: when_no_match
71+
httpMethod: POST
72+
contentHandling: CONVERT_TO_TEXT
73+
type: aws_proxy
74+
uri:
75+
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ApplicationPrefix}-lambda/invocations"
76+
post:
77+
summary: Add an ACM event conforming to the Zod schema.
78+
operationId: addEvent
79+
3780
responses:
3881
200:
3982
description: OK

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"jsonwebtoken": "^9.0.2",
4747
"jwks-rsa": "^3.1.0",
4848
"zod": "^3.23.8",
49-
"zod-to-json-schema": "^3.23.2"
49+
"zod-to-json-schema": "^3.23.2",
50+
"zod-validation-error": "^3.3.1"
5051
}
5152
}

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RunEnvironment, runEnvironments } from "./roles.js";
99
import { InternalServerError } from "./errors/index.js";
1010
import eventsPlugin from "./routes/events.js";
1111
import cors from "@fastify/cors";
12+
import fastifyZodValidationPlugin from "./plugins/validate.js";
1213

1314
const now = () => Date.now();
1415

@@ -27,6 +28,7 @@ async function init() {
2728
},
2829
});
2930
await app.register(fastifyAuthPlugin);
31+
await app.register(fastifyZodValidationPlugin);
3032
await app.register(FastifyAuthProvider);
3133
await app.register(errorHandlerPlugin);
3234
if (!process.env.RunEnvironment) {
@@ -73,10 +75,10 @@ async function init() {
7375
if (import.meta.url === `file://${process.argv[1]}`) {
7476
// local development
7577
const app = await init();
76-
app.listen({ port: 3000 }, (err) => {
78+
app.listen({ port: 8080 }, (err) => {
7779
/* eslint no-console: ["error", {"allow": ["log", "error"]}] */
7880
if (err) console.error(err);
79-
console.log("Server listening on 3000");
81+
console.log("Server listening on 8080");
8082
});
8183
}
8284
export default init;

src/plugins/validate.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
2+
import fp from "fastify-plugin";
3+
import { InternalServerError, ValidationError } from "../errors/index.js";
4+
import { z, ZodError } from "zod";
5+
import { fromError } from "zod-validation-error";
6+
7+
const zodValidationPlugin: FastifyPluginAsync = async (fastify, _options) => {
8+
fastify.decorate(
9+
"zodValidateBody",
10+
async function (
11+
request: FastifyRequest,
12+
_reply: FastifyReply,
13+
zodSchema: z.ZodTypeAny,
14+
): Promise<void> {
15+
try {
16+
await zodSchema.parseAsync(request.body);
17+
} catch (e: unknown) {
18+
if (e instanceof ZodError) {
19+
throw new ValidationError({
20+
message: fromError(e).toString().replace("Validation error: ", ""),
21+
});
22+
} else if (e instanceof Error) {
23+
request.log.error(`Error validating request body: ${e.toString()}`);
24+
throw new InternalServerError({
25+
message: "Could not validate request body.",
26+
});
27+
}
28+
throw e;
29+
}
30+
},
31+
);
32+
};
33+
34+
const fastifyZodValidationPlugin = fp(zodValidationPlugin);
35+
export default fastifyZodValidationPlugin;

src/routes/events.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,26 @@ import { randomUUID } from "crypto";
1717

1818
const repeatOptions = ["weekly", "biweekly"] as const;
1919

20-
const requestBodySchema = z.object({
20+
const baseBodySchema = z.object({
2121
title: z.string().min(1),
2222
description: z.string().min(1),
2323
start: z.string(),
2424
end: z.optional(z.string()),
2525
location: z.string(),
2626
locationLink: z.optional(z.string().url()),
27-
repeats: z.optional(z.enum(repeatOptions)),
2827
host: z.enum(OrganizationList),
2928
featured: z.boolean().default(false),
3029
});
31-
const requestJsonSchema = zodToJsonSchema(requestBodySchema);
30+
31+
const requestBodySchema = baseBodySchema
32+
.extend({
33+
repeats: z.optional(z.enum(repeatOptions)),
34+
repeatEnds: z.string().optional(),
35+
})
36+
.refine((data) => (data.repeatEnds ? data.repeats !== undefined : true), {
37+
message: "repeats is required when repeatEnds is defined",
38+
});
39+
3240
type EventPostRequest = z.infer<typeof requestBodySchema>;
3341

3442
const responseJsonSchema = zodToJsonSchema(
@@ -51,9 +59,11 @@ const eventsPlugin: FastifyPluginAsync = async (fastify, _options) => {
5159
"/:id?",
5260
{
5361
schema: {
54-
body: requestJsonSchema,
5562
response: { 200: responseJsonSchema },
5663
},
64+
preValidation: async (request, reply) => {
65+
await fastify.zodValidateBody(request, reply, requestBodySchema);
66+
},
5767
onRequest: async (request, reply) => {
5868
await fastify.authorize(request, reply, [AppRoles.MANAGER]);
5969
},

src/types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ declare module "fastify" {
1212
reply: FastifyReply,
1313
validRoles: AppRoles[],
1414
) => Promise<void>;
15+
zodValidateBody: (
16+
request: FastifyRequest,
17+
_reply: FastifyReply,
18+
zodSchema: Zod.ZodTypeAny,
19+
) => Promise<void>;
1520
runEnvironment: RunEnvironment;
1621
}
1722
interface FastifyRequest {

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4724,6 +4724,11 @@ zod-to-json-schema@^3.23.2:
47244724
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz#bc7e379c8050462538383e382964c03d8fe008f9"
47254725
integrity sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==
47264726

4727+
zod-validation-error@^3.3.1:
4728+
version "3.3.1"
4729+
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.3.1.tgz#86adc781129d1a7fed3c3e567e8dbe7c4a15eaa4"
4730+
integrity sha512-uFzCZz7FQis256dqw4AhPQgD6f3pzNca/Zh62RNELavlumQB3nDIUFbF5JQfFLcMbO1s02Q7Xg/gpcOBlEnYZA==
4731+
47274732
zod@^3.23.8:
47284733
version "3.23.8"
47294734
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"

0 commit comments

Comments
 (0)