diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 5f19866..0000000 --- a/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - collectCoverage: true, - coverageDirectory: 'coverage', - coverageReporters: ['text', 'lcov'], - }; - \ No newline at end of file diff --git a/src/app/api/(verification)/send-mail/route.ts b/src/app/api/(verification)/send-mail/route.ts index ce54ff0..7b64a72 100644 --- a/src/app/api/(verification)/send-mail/route.ts +++ b/src/app/api/(verification)/send-mail/route.ts @@ -1,53 +1,69 @@ +import { MailUsingResend } from "@/lib/resend-mailer"; +import prisma from "@/server/db"; +import getErrorMessage from "@/utils/getErrorMessage"; +import { emailSchema } from "@/utils/zod-schemas"; import { NextRequest, NextResponse } from "next/server"; -import sendEmail from "@/lib/sendMail"; import otpGenerator from "otp-generator"; -import prisma from "@/server/db"; -import { addToQueue } from "@/jobs"; -import { MailUsingResend } from "@/lib/resend-mailer"; +import { z } from "zod"; export async function POST(req: NextRequest) { - const body = await req.json(); - const otp = otpGenerator.generate(6, { - upperCaseAlphabets: false, - lowerCaseAlphabets: false, - specialChars: false, - }); - - const expiresIn = 10; // 10 minutes - const expiresAt = new Date(Date.now() + expiresIn * 60 * 1000); - - await prisma.verificationRequest.create({ - data: { - identifier: body.email, - otp, - expires: expiresAt, - }, - }); - if (!body.email) { + try { + const body = await req.json(); + const parsedBody = emailSchema.parse(body); + + const otp = otpGenerator.generate(6, { + upperCaseAlphabets: false, + lowerCaseAlphabets: false, + specialChars: false, + }); + + const expiresIn = 10; // OTP valid for 10 minutes + const expiresAt = new Date(Date.now() + expiresIn * 60 * 1000); + + await prisma.verificationRequest.create({ + data: { + identifier: parsedBody.email, + otp, + expires: expiresAt, + }, + }); + + const mailResponse = await MailUsingResend({ + email: parsedBody.email, + name: parsedBody.name, + OTP: otp, + }); + + // const mailResponse1 = await addToQueue({ + // email: parsedBody.email, + // name: parsedBody.name, + // OTP: otp, + // }) + + return NextResponse.json({ + message: "Email sent successfully!", + mailResponse, + }); + + } catch (error:unknown) { + const errorMessage = getErrorMessage(error); + + if (error instanceof z.ZodError) { + return NextResponse.json( + { message: "Validation error", errors: errorMessage }, + { status: 400 } + ); + } + + // Handle general server errors return NextResponse.json( - { message: "No recipients defined", status: 400 }, - { status: 400 } + { message: "Internal Server Error", errorMessage }, + { status: 500 } ); } - console.log(body); - // const mailResponse1 = await addToQueue({ - // email: body.email, - // name: body.name, - // OTP: otp, - // }); - const mailResponse2 = await MailUsingResend({ - email: body.email, - name: body.name, - OTP: otp, - }); - - return NextResponse.json({ - message: "Email sent successfully!", - // mailResponse1, - mailResponse2, - }); } +// Test endpoint export async function GET() { return NextResponse.json({ message: "Hello from the Send mail!" }); } diff --git a/src/app/api/(verification)/verify-mail/route.ts b/src/app/api/(verification)/verify-mail/route.ts index 84fa233..84c02f6 100644 --- a/src/app/api/(verification)/verify-mail/route.ts +++ b/src/app/api/(verification)/verify-mail/route.ts @@ -1,10 +1,17 @@ -import { NextRequest, NextResponse } from "next/server"; import prisma from "@/server/db"; +import getErrorMessage from "@/utils/getErrorMessage"; +import { NextRequest, NextResponse } from "next/server"; export async function POST(req: NextRequest) { const body = await req.json(); console.log(body); const { identifier, otp } = body; + if (!identifier || !otp) { + return NextResponse.json( + { message: "Identifier and OTP are required", status: 400 }, + { status: 400 } + ); + } try { await prisma.$transaction(async (tx) => { const request = await tx.verificationRequest.findFirst({ @@ -17,20 +24,19 @@ export async function POST(req: NextRequest) { }, orderBy: { created_at: "desc", - }, }); if (!request) { - throw new Error("Invalid or expired OTP"); + throw new Error("Verification failed: Invalid or expired OTP"); } await tx.form.updateMany({ where: { - email: identifier, + email: identifier, }, data: { - emailVerified: true, + emailVerified: true, }, }); @@ -40,7 +46,7 @@ export async function POST(req: NextRequest) { }, }); }); - + return NextResponse.json( { message: "OTP verified successfully back!", @@ -48,10 +54,12 @@ export async function POST(req: NextRequest) { }, { status: 200 } ); - } catch (error: any) { + } catch (error: unknown) { + const errorMessage = getErrorMessage(error); + console.error("OTP verification failed:", errorMessage); return NextResponse.json( - { message: error.message, status: 400 }, - { status: 200 } + { message: errorMessage, status: 400 }, + { status: 400 } ); } } diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index 2449bcd..804e89a 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -1,20 +1,47 @@ import prisma from "@/server/db"; - import { NextResponse } from "next/server"; + export async function GET(req: Request) { - const { searchParams } = new URL(req.url); - const page = parseInt(searchParams.get("page") || "1"); - const search = searchParams.get("search") || ""; - const limit = 10; + try { + const { searchParams } = new URL(req.url); + + const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10)); + const search = searchParams.get("search") || ""; + const limit = 10; + + const [users, totalCount] = await Promise.all([ + prisma.user.findMany({ + skip: (page - 1) * limit, + take: limit, + where: { + name: { + contains: search, + }, + }, + }), + prisma.user.count({ // Get the total number of users for pagination + where: { + name: { + contains: search, + }, + }, + }) + ]); + + const totalPages = Math.ceil(totalCount / limit); + + return NextResponse.json({ + users, + pagination: { + currentPage: page, + totalPages, + totalCount, + limit + } + }); - const users = await prisma.user.findMany({ - skip: (page - 1) * limit, - take: limit, - where: { - name: { - contains: search, - }, - }, - }); - return NextResponse.json({ users }); + } catch (error) { + console.error("Failed to fetch users:", error); + return NextResponse.json({ message: "Failed to fetch users", status: 500 }, { status: 500 }); + } } diff --git a/src/middleware.ts b/src/middleware.ts index 3ad4bba..ddadea8 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -8,7 +8,7 @@ export async function middleware(request: NextRequest) { const token = await getToken({ req: request }); const url = request.nextUrl; - if (url.pathname === "/admin") { + if (url.pathname.startsWith("/admin")) { if (token?.role!=="ADMIN") { return NextResponse.redirect(new URL("/", request.url)); } diff --git a/src/utils/getErrorMessage.ts b/src/utils/getErrorMessage.ts index 1a36b22..9bcb781 100644 --- a/src/utils/getErrorMessage.ts +++ b/src/utils/getErrorMessage.ts @@ -1,7 +1,12 @@ +import { z } from "zod"; + const getErrorMessage = (error: unknown): string => { let message: string; - - if (error instanceof Error) { + if (error instanceof z.ZodError) { + message = error.errors + .map((err) => `${err.path.join(".")} - ${err.message}`) + .join(", "); + } else if (error instanceof Error) { message = error.message; } else if (error && typeof error === "object" && "message" in error) { message = String(error.message); diff --git a/src/utils/zod-schemas.ts b/src/utils/zod-schemas.ts index 39eb96b..4088e1b 100644 --- a/src/utils/zod-schemas.ts +++ b/src/utils/zod-schemas.ts @@ -21,3 +21,9 @@ export const RegistrationFormSchema = z.object({ }); export type TRegistrationForm = z.infer; + + +export const emailSchema = z.object({ + email: z.string().email(), + name: z.string().min(1), +}); \ No newline at end of file