From 6339c79dc89993f412c6a809220baa92684b9d33 Mon Sep 17 00:00:00 2001 From: dyzhuu <119282717+dyzhuu@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:25:33 +1300 Subject: [PATCH] refactor: approve/reject apis * disable rate-limiting on dev * 429 error toast for bookings --- src/app/api/bookings/route.ts | 4 +- .../[userId]/membership/approve/route.ts | 53 ++++++++----------- .../users/[userId]/membership/reject/route.ts | 49 +++++++---------- .../select-play-level/client-page.tsx | 7 +++ src/lib/rate-limit.ts | 3 ++ 5 files changed, 54 insertions(+), 62 deletions(-) diff --git a/src/app/api/bookings/route.ts b/src/app/api/bookings/route.ts index 15447d87..c4726ce6 100644 --- a/src/app/api/bookings/route.ts +++ b/src/app/api/bookings/route.ts @@ -26,7 +26,7 @@ import { userCache } from "@/services/user"; import type { User } from "@/types/next-auth"; const limiter = rateLimit({ - interval: 5 * 60 * 1000, // 60 seconds + interval: 10 * 60 * 1000, }); const bookingSchema = z.array( @@ -46,7 +46,7 @@ export const POST = userRouteWrapper( return responses.forbidden(); } - const isRateLimited = limiter.check(5); // max 5 requests per 5 minutes per IP + const isRateLimited = limiter.check(10); // max 10 requests per 10 minutes per IP if (isRateLimited) return responses.tooManyRequests({ diff --git a/src/app/api/users/[userId]/membership/approve/route.ts b/src/app/api/users/[userId]/membership/approve/route.ts index ae606750..86f253f3 100644 --- a/src/app/api/users/[userId]/membership/approve/route.ts +++ b/src/app/api/users/[userId]/membership/approve/route.ts @@ -3,50 +3,41 @@ import { eq } from "drizzle-orm"; import { z } from "zod"; import { sendMemberApprovalEmail } from "@/emails"; +import { responses } from "@/lib/api/responses"; import { db } from "@/lib/db"; import { users } from "@/lib/db/schema"; -import { getCurrentUser } from "@/lib/session"; +import { adminRouteWrapper } from "@/lib/wrappers"; import { userCache } from "@/services/user"; const approveUserSchema = z.object({ prepaidSessions: z.number(), }); -export async function PATCH( - req: NextRequest, - { params }: { params: { userId: string } } -) { - const currentUser = await getCurrentUser(); +export const PATCH = adminRouteWrapper( + async (req: NextRequest, { params }: { params: { userId: string } }) => { + const { userId } = params; - if (!currentUser) { - return new Response("ERROR: Unauthorized request", { status: 401 }); - } + const body = await req.json(); - if (currentUser.role !== "admin") { - return new Response("ERROR: No valid permissions", { status: 403 }); - } + const { prepaidSessions } = approveUserSchema.parse(body); - const { userId } = params; + const [user] = await db + .update(users) + .set({ verified: true, prepaidSessions }) + .where(eq(users.id, userId)) + .returning(); - const body = await req.json(); + if (!user) { + return responses.notFound({ + resourceType: "user", + resourceId: userId, + }); + } - const { prepaidSessions } = approveUserSchema.parse(body); + userCache.revalidate(user.email); - const [user] = await db - .update(users) - .set({ verified: true, prepaidSessions }) - .where(eq(users.id, userId)) - .returning(); + await sendMemberApprovalEmail(user, prepaidSessions); - if (!user) { - return new Response(`No user found with id: ${userId}`, { - status: 404, - }); + return new Response(null, { status: 204 }); } - - userCache.revalidate(user.email); - - await sendMemberApprovalEmail(user, prepaidSessions); - - return new Response(null, { status: 204 }); -} +); diff --git a/src/app/api/users/[userId]/membership/reject/route.ts b/src/app/api/users/[userId]/membership/reject/route.ts index 28bf4f07..3bffa95e 100644 --- a/src/app/api/users/[userId]/membership/reject/route.ts +++ b/src/app/api/users/[userId]/membership/reject/route.ts @@ -2,42 +2,33 @@ import type { NextRequest } from "next/server"; import { eq } from "drizzle-orm"; import { sendMemberRejectionEmail } from "@/emails"; +import { responses } from "@/lib/api/responses"; import { db } from "@/lib/db"; import { users } from "@/lib/db/schema"; -import { getCurrentUser } from "@/lib/session"; +import { adminRouteWrapper } from "@/lib/wrappers"; import { userCache } from "@/services/user"; -export async function PATCH( - _req: NextRequest, - { params }: { params: { userId: string } } -) { - const currentUser = await getCurrentUser(); +export const PATCH = adminRouteWrapper( + async (_req: NextRequest, { params }: { params: { userId: string } }) => { + const { userId } = params; - if (!currentUser) { - return new Response("ERROR: Unauthorized request", { status: 401 }); - } + const [user] = await db + .update(users) + .set({ member: false }) + .where(eq(users.id, userId)) + .returning(); - if (currentUser.role !== "admin") { - return new Response("ERROR: No valid permissions", { status: 403 }); - } + if (!user) { + return responses.notFound({ + resourceType: "user", + resourceId: userId, + }); + } - const { userId } = params; + userCache.revalidate(user.email); - const [user] = await db - .update(users) - .set({ member: false }) - .where(eq(users.id, userId)) - .returning(); + await sendMemberRejectionEmail(user); - if (!user) { - return new Response(`No user found with id: ${userId}`, { - status: 404, - }); + return new Response(null, { status: 204 }); } - - userCache.revalidate(user.email); - - await sendMemberRejectionEmail(user); - - return new Response(null, { status: 204 }); -} +); diff --git a/src/app/sessions/select-play-level/client-page.tsx b/src/app/sessions/select-play-level/client-page.tsx index de3b4692..ebb6079a 100644 --- a/src/app/sessions/select-play-level/client-page.tsx +++ b/src/app/sessions/select-play-level/client-page.tsx @@ -71,6 +71,13 @@ export default function ClientSelectPlayLevelPage() { description: `You have already reached the session booking limit for this week.`, variant: "destructive", }); + } else if (code === "TOO_MANY_REQUESTS") { + toast({ + title: "Too Many Requests", + description: + "You have made too many booking requests in a short period. Please wait a moment and try again.", + variant: "destructive", + }); } else { toast({ title: "Something went wrong.", diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts index 7d7a51d4..23a18f4f 100644 --- a/src/lib/rate-limit.ts +++ b/src/lib/rate-limit.ts @@ -1,6 +1,8 @@ import { headers } from "next/headers"; import { LRUCache } from "lru-cache"; +import { env } from "@/env"; + type Options = { uniqueTokenPerInterval?: number; interval?: number; @@ -25,6 +27,7 @@ export function rateLimit(options?: Options) { return { check: (limit: number, token?: string) => { + if (env.ENVIRONMENT === "DEVELOPMENT") return false; const resolvedToken = token || IP(); const tokenCount = (tokenCache.get(resolvedToken) as number[]) || [0]; if (tokenCount[0] === 0) {