Skip to content

Commit b243c5e

Browse files
committed
use openapi refs for docs
1 parent 844afb0 commit b243c5e

File tree

9 files changed

+84
-64
lines changed

9 files changed

+84
-64
lines changed

src/api/components/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,52 @@
11
import { AppRoles } from "common/roles.js";
22
import { FastifyZodOpenApiSchema } from "fastify-zod-openapi";
33
import * as z from "zod/v4";
4+
import { validateNetId } from "api/functions/validation.js";
5+
import { CoreOrganizationList } from "@acm-uiuc/js-shared";
46

57
export const ts = z.coerce.number().min(0).optional().meta({
68
description:
79
"Staleness bound as Unix epoch time (requires authentication to specify)",
810
example: 1752248256,
11+
id: "AcmStalenessBoundTimestamp",
912
});
13+
1014
export const groupId = z.string().min(1).meta({
1115
description: "Entra ID Group ID",
1216
example: "d8cbb7c9-2f6d-4b7e-8ba6-b54f8892003b",
17+
id: "EntraGroupId",
1318
});
1419

20+
export const illinoisNetId = z
21+
.string()
22+
.min(3, { message: "NetID must be at least 3 characters." })
23+
.max(8, { message: "NetID cannot be more than 8 characters." })
24+
.regex(/^[a-zA-Z]{2}[a-zA-Z-]*(?:[2-9]|[1-9][0-9]{1,2})?$/, {
25+
message: "NetID is not valid!",
26+
})
27+
.meta({
28+
description: "Valid Illinois NetID",
29+
example: "rjjones",
30+
id: "IllinoisNetId",
31+
});
32+
33+
export const acmCoreOrganization = z
34+
.enum(CoreOrganizationList as [string, ...string[]])
35+
.meta({
36+
description: "ACM Organization",
37+
id: "AcmOrganization",
38+
examples: ["ACM", "Infrastructure Committee"],
39+
});
40+
41+
export const semesterId = z
42+
.string()
43+
.min(1)
44+
.meta({
45+
description: "Short semester slug for a given semester.",
46+
id: "IllinoisSemesterId",
47+
examples: ["sp25", "fa24"],
48+
});
49+
1550
export function withTags<T extends FastifyZodOpenApiSchema>(
1651
tags: string[],
1752
schema: T,

src/api/functions/validation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as z from "zod/v4";
22

33
export function validateEmail(email: string): boolean {
4-
const emailSchema = z.string().email();
4+
const emailSchema = z.email();
55
const result = emailSchema.safeParse(email);
66
return result.success;
77
}

src/api/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,7 @@ async function init(prettyPrint: boolean = false, initClients: boolean = true) {
102102
});
103103
app.setValidatorCompiler(validatorCompiler);
104104
app.setSerializerCompiler(serializerCompiler);
105-
await app.register(authorizeFromSchemaPlugin);
106-
await app.register(fastifyAuthPlugin);
107-
await app.register(FastifyAuthProvider);
108-
await app.register(evaluatePoliciesPlugin);
109-
await app.register(errorHandlerPlugin);
110-
await app.register(fastifyZodOpenApiPlugin);
111-
await app.register(locationPlugin);
105+
112106
if (!isRunningInLambda) {
113107
try {
114108
const fastifySwagger = import("@fastify/swagger");
@@ -224,7 +218,13 @@ async function init(prettyPrint: boolean = false, initClients: boolean = true) {
224218
app.log.warn("Fastify Swagger not created!");
225219
}
226220
}
227-
221+
await app.register(authorizeFromSchemaPlugin);
222+
await app.register(fastifyAuthPlugin);
223+
await app.register(FastifyAuthProvider);
224+
await app.register(evaluatePoliciesPlugin);
225+
await app.register(errorHandlerPlugin);
226+
await app.register(fastifyZodOpenApiPlugin);
227+
await app.register(locationPlugin);
228228
await app.register(fastifyStatic, {
229229
root: path.join(__dirname, "public"),
230230
prefix: "/",

src/api/routes/events.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { FastifyPluginAsync, FastifyRequest } from "fastify";
22
import { AppRoles } from "../../common/roles.js";
33
import * as z from "zod/v4";
4-
import { CoreOrganizationList } from "@acm-uiuc/js-shared";
54
import {
65
DeleteItemCommand,
76
GetItemCommand,
@@ -39,7 +38,12 @@ import {
3938
FastifyZodOpenApiSchema,
4039
FastifyZodOpenApiTypeProvider,
4140
} from "fastify-zod-openapi";
42-
import { ts, withRoles, withTags } from "api/components/index.js";
41+
import {
42+
acmCoreOrganization,
43+
ts,
44+
withRoles,
45+
withTags,
46+
} from "api/components/index.js";
4347
import { metadataSchema } from "common/types/events.js";
4448
import { evaluateAllRequestPolicies } from "api/plugins/evaluatePolicies.js";
4549

@@ -108,8 +112,9 @@ const baseSchema = z.object({
108112
description: "Google Maps link for easy navigation to the event location.",
109113
example: "https://maps.app.goo.gl/dwbBBBkfjkgj8gvA8",
110114
}),
111-
host: z.enum(CoreOrganizationList as [string, ...string[]]),
115+
host: acmCoreOrganization,
112116
featured: z.boolean().default(false).meta({
117+
ref: "acmOrganizationList",
113118
description:
114119
"Whether or not the event should be shown on the ACM @ UIUC website home page (and added to Discord, as available).",
115120
}),
@@ -166,12 +171,9 @@ const eventsPlugin: FastifyPluginAsyncZodOpenApi = async (
166171
description:
167172
"If true, only get events which are marked as featured.",
168173
}),
169-
host: z
170-
.enum(CoreOrganizationList as [string, ...string[]])
171-
.optional()
172-
.meta({
173-
description: "Retrieve events only for a specific host.",
174-
}),
174+
host: z.optional(acmCoreOrganization).meta({
175+
description: "Retrieve events only for this organization.",
176+
}),
175177
ts,
176178
includeMetadata: zodIncludeMetadata,
177179
}),

src/api/routes/ics.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
FastifyZodOpenApiSchema,
2323
FastifyZodOpenApiTypeProvider,
2424
} from "fastify-zod-openapi";
25-
import { withTags } from "api/components/index.js";
25+
import { acmCoreOrganization, withTags } from "api/components/index.js";
2626
import * as z from "zod/v4";
2727

2828
const repeatingIcalMap: Record<EventRepeatOptions, ICalEventJSONRepeatingData> =
@@ -52,9 +52,9 @@ const icalPlugin: FastifyPluginAsync = async (fastify, _options) => {
5252
{
5353
schema: withTags(["iCalendar Integration"], {
5454
params: z.object({
55-
host: z
56-
.optional(z.enum(CoreOrganizationList as [string, ...string[]]))
57-
.meta({ description: "Host to get calendar for." }),
55+
host: z.optional(acmCoreOrganization).meta({
56+
description: "Organization to retrieve calendar for",
57+
}),
5858
}),
5959
summary:
6060
"Retrieve the calendar for ACM @ UIUC or a specific sub-organization.",

src/api/routes/membership.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
setPaidMembershipInTable,
66
MEMBER_CACHE_SECONDS,
77
} from "api/functions/membership.js";
8-
import { validateNetId } from "api/functions/validation.js";
98
import { FastifyPluginAsync } from "fastify";
109
import {
1110
BaseError,
@@ -26,7 +25,7 @@ import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
2625
import rawbody from "fastify-raw-body";
2726
import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
2827
import * as z from "zod/v4";
29-
import { withTags } from "api/components/index.js";
28+
import { illinoisNetId, withTags } from "api/components/index.js";
3029
import { getKey, setKey } from "api/functions/redisCache.js";
3130

3231
const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
@@ -76,12 +75,7 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
7675
"/checkout/:netId",
7776
{
7877
schema: withTags(["Membership"], {
79-
params: z
80-
.object({ netId: z.string().min(1) })
81-
.refine((data) => validateNetId(data.netId), {
82-
message: "NetID is not valid!",
83-
path: ["netId"],
84-
}),
78+
params: z.object({ netId: illinoisNetId }),
8579
summary:
8680
"Create a checkout session to purchase an ACM @ UIUC membership.",
8781
}),
@@ -201,12 +195,7 @@ const membershipPlugin: FastifyPluginAsync = async (fastify, _options) => {
201195
"/:netId",
202196
{
203197
schema: withTags(["Membership"], {
204-
params: z
205-
.object({ netId: z.string().min(1) })
206-
.refine((data) => validateNetId(data.netId), {
207-
message: "NetID is not valid!",
208-
path: ["netId"],
209-
}),
198+
params: z.object({ netId: illinoisNetId }),
210199
querystring: z.object({
211200
list: z.string().min(1).optional().meta({
212201
description:

src/api/routes/roomRequests.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { genericConfig, notificationRecipients } from "common/config.js";
2525
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
2626
import { AvailableSQSFunctions, SQSPayload } from "common/types/sqsMessage.js";
2727
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
28-
import { withRoles, withTags } from "api/components/index.js";
28+
import { semesterId, withRoles, withTags } from "api/components/index.js";
2929
import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
3030
import * as z from "zod/v4";
3131
import { buildAuditLogTransactPut } from "api/functions/auditLog.js";
@@ -54,10 +54,7 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
5454
description: "Room request ID.",
5555
example: "6667e095-8b04-4877-b361-f636f459ba42",
5656
}),
57-
semesterId: z.string().min(1).meta({
58-
description: "Short semester slug for a given semester.",
59-
example: "sp25",
60-
}),
57+
semesterId,
6158
}),
6259
body: roomRequestStatusUpdateRequest,
6360
}),
@@ -185,10 +182,7 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
185182
withTags(["Room Requests"], {
186183
summary: "Get room requests for a specific semester.",
187184
params: z.object({
188-
semesterId: z.string().min(1).meta({
189-
description: "Short semester slug for a given semester.",
190-
example: "sp25",
191-
}),
185+
semesterId,
192186
}),
193187
querystring: z.object(
194188
getDefaultFilteringQuerystring({

src/common/types/iam.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ export type InviteUserPostRequest = z.infer<typeof invitePostRequestSchema>;
3030

3131
export const groupMappingCreatePostSchema = z.object({
3232
roles: z.union([
33-
z.
34-
array(z.nativeEnum(AppRoles)).
35-
min(1).
36-
refine((items) => new Set(items).size === items.length, {
37-
message: "All roles must be unique, no duplicate values allowed"
38-
}),
39-
z.tuple([z.literal("all")])]
33+
z.
34+
array(z.nativeEnum(AppRoles)).
35+
min(1).
36+
refine((items) => new Set(items).size === items.length, {
37+
message: "All roles must be unique, no duplicate values allowed"
38+
}),
39+
z.tuple([z.literal("all")])]
4040
)
4141
});
4242

@@ -47,13 +47,13 @@ export type GroupMappingCreatePostRequest = z.infer<
4747
export const entraActionResponseSchema = z.object({
4848
success: z.array(z.object({ email: z.string() })).optional(),
4949
failure: z.
50-
array(z.object({ email: z.string(), message: z.string() })).
51-
optional()
50+
array(z.object({ email: z.string(), message: z.string() })).
51+
optional()
5252
});
5353

5454
export type EntraActionResponse = z.infer<typeof entraActionResponseSchema>;
5555

56-
export type GroupGetResponse = {id: string;displayName: string;}[];
56+
export type GroupGetResponse = { id: string; displayName: string; }[];
5757

5858
export const groupModificationPatchSchema = z.object({
5959
add: z.array(z.string()),
@@ -79,7 +79,7 @@ export const entraProfilePatchRequest = z.object({
7979
displayName: z.string().min(1),
8080
givenName: z.string().min(1),
8181
surname: z.string().min(1),
82-
mail: z.string().email()
82+
mail: z.email()
8383
});
8484

85-
export type ProfilePatchRequest = z.infer<typeof entraProfilePatchRequest>;
85+
export type ProfilePatchRequest = z.infer<typeof entraProfilePatchRequest>;

src/common/types/linkry.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@ export const getRequest = z.object({
1313
});
1414

1515
export const linkrySlug = z.string().min(1).max(LINKRY_MAX_SLUG_LENGTH).meta({ description: "Linkry shortened URL path.", example: "shortened_url" });
16-
export const linkryAccessList = z.array(z.string()).meta({
16+
export const linkryAccessList = z.array(z.string().min(1)).meta({
1717
description: "List of groups to which access has been delegated.", example: ["c6a21a09-97c1-4f10-8ddd-fca11f967dc3", "88019d41-6c0b-4783-925c-3eb861a1ca0d"]
1818
});
1919

2020

2121
export const createRequest = z.object({
2222
slug: linkrySlug,
2323
access: linkryAccessList,
24-
redirect: z.string().url().min(1).meta({ description: "Full URL to redirect to when the short URL is visited.", example: "https://google.com" })
24+
redirect: z.url().min(1).meta({ description: "Full URL to redirect to when the short URL is visited.", example: "https://google.com" })
2525
});
2626

2727
export const linkRecord = z.object({
2828
access: linkryAccessList,
2929
slug: linkrySlug,
30-
createdAt: z.string().datetime(),
31-
updatedAt: z.string().datetime(),
32-
redirect: z.string().url(),
33-
owner: z.string()
30+
createdAt: z.iso.datetime(),
31+
updatedAt: z.iso.datetime(),
32+
redirect: z.url(),
33+
owner: z.string().min(1)
3434
});
3535

3636
export const delegatedLinkRecord = linkRecord.extend({
@@ -44,4 +44,4 @@ export type DelegatedLinkRecord = z.infer<typeof delegatedLinkRecord>;
4444
export const getLinksResponse = z.object({
4545
ownedLinks: z.array(linkRecord),
4646
delegatedLinks: z.array(linkRecord)
47-
});
47+
});

0 commit comments

Comments
 (0)