Initial commit

This commit is contained in:
Developer
2026-02-06 21:44:04 -06:00
commit f85e93c7a6
151 changed files with 22916 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
import { NextRequest } from "next/server";
import { z } from "zod";
import { NextResponse } from "next/server";
import { getPrisma } from "../../../../lib/db";
import { getSession } from "../../../../lib/auth/session";
import { ok, fail } from "../../../../lib/http";
export const runtime = "nodejs";
const QuerySchema = z.object({
page: z.string().default("1").transform(Number),
search: z.string().optional(),
});
const UpdateUserSchema = z.object({
role: z.enum(["USER", "ADMIN"]).optional(),
isActive: z.boolean().optional(),
});
export async function GET(req: NextRequest) {
const session = await getSession();
if (!session || session.role !== "ADMIN") {
return fail(new Error("Unauthorized"), { status: 401 });
}
const prisma = await getPrisma();
if (!prisma) return fail(new Error("Database not configured"), { status: 503 });
const parsed = QuerySchema.safeParse(
Object.fromEntries(new URL(req.url).searchParams)
);
if (!parsed.success) return fail(new Error("Invalid query parameters"));
const appSetup = await prisma.appSetup.findUnique({ where: { id: 1 } });
const pageSize = appSetup?.paginationItemsPerPage || 10;
const page = Math.max(1, parsed.data.page);
const skip = (page - 1) * pageSize;
const searchFilter = parsed.data.search
? {
OR: [
{ email: { contains: parsed.data.search, mode: "insensitive" as const } },
{ firstName: { contains: parsed.data.search, mode: "insensitive" as const } },
{ lastName: { contains: parsed.data.search, mode: "insensitive" as const } },
],
}
: undefined;
const [users, total] = await Promise.all([
prisma.user.findMany({
where: searchFilter,
select: {
id: true,
email: true,
firstName: true,
lastName: true,
role: true,
isActive: true,
emailVerified: true,
createdAt: true,
gender: true,
dob: true,
address: true,
image: true,
},
orderBy: { createdAt: "desc" },
skip,
take: pageSize,
}),
prisma.user.count({ where: searchFilter }),
]);
// Fetch webinars for each user
const usersWithWebinars = await Promise.all(
users.map(async (user) => {
const registrations = await prisma.webinarRegistration.findMany({
where: { userId: user.id, status: { not: "CANCELLED" } },
});
return {
...user,
_count: {
webinarRegistrations: registrations.length,
},
registeredWebinars: await prisma.webinarRegistration.findMany({
where: { userId: user.id, status: { not: "CANCELLED" } },
select: {
id: true,
status: true,
webinar: {
select: {
id: true,
title: true,
startAt: true,
},
},
},
orderBy: { createdAt: "desc" },
take: 5,
}),
};
})
);
return ok({
users: usersWithWebinars,
pagination: {
page,
pageSize,
total,
pages: Math.ceil(total / pageSize),
hasMore: page < Math.ceil(total / pageSize),
},
});
}
export async function PATCH(req: NextRequest) {
const session = await getSession();
if (!session || session.role !== "ADMIN") {
return fail(new Error("Unauthorized"), { status: 401 });
}
const prisma = await getPrisma();
if (!prisma) return fail(new Error("Database not configured"), { status: 503 });
const body = await req.json().catch(() => ({}));
const { userId, ...updateData } = body;
if (!userId) return fail(new Error("userId is required"));
const parsed = UpdateUserSchema.safeParse(updateData);
if (!parsed.success) return fail(new Error("Invalid update data"));
// Prevent disabling the current admin
if (session.sub === userId && parsed.data.isActive === false) {
return fail(new Error("Cannot disable your own account"), { status: 400 });
}
// Prevent removing admin role from self
if (session.sub === userId && parsed.data.role && parsed.data.role !== "ADMIN") {
return fail(new Error("Cannot change your own role"), { status: 400 });
}
const user = await prisma.user.update({
where: { id: userId },
data: {
...(parsed.data.role && { role: parsed.data.role }),
...(parsed.data.isActive !== undefined && { isActive: parsed.data.isActive }),
},
select: {
id: true,
email: true,
firstName: true,
lastName: true,
role: true,
isActive: true,
},
});
return ok({
message:
parsed.data.isActive === false
? "User blocked successfully"
: "User updated successfully",
user,
});
}