Skip to content

Commit b19291b

Browse files
committed
enable select queries
1 parent e77e636 commit b19291b

File tree

3 files changed

+66
-4
lines changed

3 files changed

+66
-4
lines changed

src/api/routes/roomRequests.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ import { FastifyZodOpenApiTypeProvider } from "fastify-zod-openapi";
3030
import { z } from "zod";
3131
import { buildAuditLogTransactPut } from "api/functions/auditLog.js";
3232
import { Modules } from "common/modules.js";
33+
import {
34+
generateProjectionParams,
35+
getDefaultFilteringQuerystring,
36+
nonEmptyCommaSeparatedStringSchema,
37+
} from "common/utils.js";
3338

3439
const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
3540
await fastify.register(rateLimiter, {
@@ -182,12 +187,19 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
182187
example: "sp25",
183188
}),
184189
}),
190+
querystring: z.object(
191+
getDefaultFilteringQuerystring({
192+
defaultSelect: ["requestId", "title"],
193+
}),
194+
),
185195
}),
186196
),
187197
onRequest: fastify.authorizeFromSchema,
188198
},
189199
async (request, reply) => {
190200
const semesterId = request.params.semesterId;
201+
const { ProjectionExpression, ExpressionAttributeNames } =
202+
generateProjectionParams({ userFields: request.query.select });
191203
if (!request.username) {
192204
throw new InternalServerError({
193205
message: "Could not retrieve username.",
@@ -198,7 +210,8 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
198210
command = new QueryCommand({
199211
TableName: genericConfig.RoomRequestsTableName,
200212
KeyConditionExpression: "semesterId = :semesterValue",
201-
ProjectionExpression: "requestId, host, title, semester",
213+
ProjectionExpression,
214+
ExpressionAttributeNames,
202215
ExpressionAttributeValues: {
203216
":semesterValue": { S: semesterId },
204217
},
@@ -210,8 +223,9 @@ const roomRequestRoutes: FastifyPluginAsync = async (fastify, _options) => {
210223
"semesterId = :semesterValue AND begins_with(#sortKey, :username)",
211224
ExpressionAttributeNames: {
212225
"#sortKey": "userId#requestId",
226+
...ExpressionAttributeNames,
213227
},
214-
ProjectionExpression: "requestId, host, title, semester",
228+
ProjectionExpression,
215229
ExpressionAttributeValues: {
216230
":semesterValue": { S: semesterId },
217231
":username": { S: request.username },

src/common/utils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { z } from "zod";
12
export function transformCommaSeperatedName(name: string) {
23
if (name.includes(",")) {
34
try {
@@ -12,3 +13,42 @@ export function transformCommaSeperatedName(name: string) {
1213
}
1314
return name;
1415
}
16+
17+
type GenerateProjectionParamsInput = {
18+
userFields?: string[];
19+
}
20+
/**
21+
* Generates DynamoDB projection parameters for select filters, while safely handle reserved keywords.
22+
*/
23+
export const generateProjectionParams = ({ userFields }: GenerateProjectionParamsInput) => {
24+
const attributes = userFields || [];
25+
const expressionAttributeNames: Record<string, string> = {};
26+
const projectionExpression = attributes
27+
.map((attr, index) => {
28+
const placeholder = `#proj${index}`;
29+
expressionAttributeNames[placeholder] = attr;
30+
return placeholder;
31+
})
32+
.join(',');
33+
return {
34+
ProjectionExpression: projectionExpression,
35+
ExpressionAttributeNames: expressionAttributeNames,
36+
};
37+
};
38+
39+
40+
export const nonEmptyCommaSeparatedStringSchema = z.preprocess(
41+
(val) => String(val).split(',').map(item => item.trim()),
42+
z.array(z.string()).nonempty()
43+
);
44+
45+
type GettDefaultFilteringQuerystringInput = {
46+
defaultSelect: string[];
47+
}
48+
export const getDefaultFilteringQuerystring = ({ defaultSelect }: GettDefaultFilteringQuerystringInput) => {
49+
return {
50+
select: z.optional(nonEmptyCommaSeparatedStringSchema).default(defaultSelect.join(',')).openapi({
51+
description: "Comma-seperated list of attributes to return",
52+
})
53+
}
54+
}

src/ui/pages/roomRequest/RoomRequestLanding.page.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
RoomRequestFormValues,
1212
RoomRequestGetAllResponse,
1313
RoomRequestPostResponse,
14+
type RoomRequestStatus,
1415
} from "@common/types/roomRequest";
1516

1617
export const ManageRoomRequestsPage: React.FC = () => {
@@ -29,8 +30,15 @@ export const ManageRoomRequestsPage: React.FC = () => {
2930
const getRoomRequests = async (
3031
semester: string,
3132
): Promise<RoomRequestGetAllResponse> => {
32-
const response = await api.get(`/api/v1/roomRequests/${semester}`);
33-
return response.data;
33+
const response = await api.get<
34+
{
35+
requestId: string;
36+
title: string;
37+
host: string;
38+
status: RoomRequestStatus;
39+
}[]
40+
>(`/api/v1/roomRequests/${semester}?select=requestId,title,host,status`);
41+
return response.data.map((x) => ({ ...x, semester }));
3442
};
3543

3644
useEffect(() => {

0 commit comments

Comments
 (0)