Skip to content

Commit

Permalink
refactor: approve/reject apis
Browse files Browse the repository at this point in the history
* disable rate-limiting on dev
* 429 error toast for bookings
  • Loading branch information
dyzhuu committed Oct 14, 2024
1 parent 226b914 commit 6339c79
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 62 deletions.
4 changes: 2 additions & 2 deletions src/app/api/bookings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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({
Expand Down
53 changes: 22 additions & 31 deletions src/app/api/users/[userId]/membership/approve/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
);
49 changes: 20 additions & 29 deletions src/app/api/users/[userId]/membership/reject/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
);
7 changes: 7 additions & 0 deletions src/app/sessions/select-play-level/client-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
3 changes: 3 additions & 0 deletions src/lib/rate-limit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { headers } from "next/headers";
import { LRUCache } from "lru-cache";

import { env } from "@/env";

type Options = {
uniqueTokenPerInterval?: number;
interval?: number;
Expand All @@ -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) {
Expand Down

0 comments on commit 6339c79

Please sign in to comment.