Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions src/app/api/dashboard/judge/assignments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { and, asc, eq, inArray } from "drizzle-orm";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { adminProtected } from "~/auth/routes-wrapper";
import db from "~/db";
import {
dashboardUserRoles,
dashboardUsers,
judgeRoundAssignments,
judges,
roles,
teams,
} from "~/db/schema";

const updateAssignmentsSchema = z.object({
judgeRoundId: z.string().min(1, "Judge round is required"),
judgeUserId: z.string().min(1, "Judge user is required"),
teamIds: z.array(z.string()).default([]),
});

export const GET = adminProtected(async (req: NextRequest) => {
try {
const { searchParams } = new URL(req.url);
const judgeRoundId = searchParams.get("judgeRoundId");
const judgeUserId = searchParams.get("judgeUserId");

const judgeUsers = await db
.select({
id: dashboardUsers.id,
name: dashboardUsers.name,
username: dashboardUsers.username,
})
.from(dashboardUsers)
.innerJoin(
dashboardUserRoles,
and(
eq(dashboardUserRoles.dashboardUserId, dashboardUsers.id),
eq(dashboardUserRoles.isActive, true),
),
)
.innerJoin(roles, eq(roles.id, dashboardUserRoles.roleId))
.where(inArray(roles.name, ["JUDGE", "FINAL_JUDGE"]))
.orderBy(asc(dashboardUsers.name));

const uniqueJudgeUsers = Array.from(
new Map(judgeUsers.map((user) => [user.id, user])).values(),
);

const allTeams = await db
.select({ id: teams.id, name: teams.name })
.from(teams)
.orderBy(asc(teams.name));

let assignedTeamIds: string[] = [];

if (judgeRoundId && judgeUserId) {
const judge = await db.query.judges.findFirst({
where: (j, { eq }) => eq(j.dashboardUserId, judgeUserId),
});

if (judge) {
const assignments = await db
.select({ teamId: judgeRoundAssignments.teamId })
.from(judgeRoundAssignments)
.where(
and(
eq(judgeRoundAssignments.judgeRoundId, judgeRoundId),
eq(judgeRoundAssignments.judgeId, judge.id),
),
);

assignedTeamIds = assignments.map((assignment) => assignment.teamId);
}
}

return NextResponse.json(
{
judgeUsers: uniqueJudgeUsers,
teams: allTeams,
assignedTeamIds,
},
{ status: 200 },
);
} catch (error) {
console.error("Error fetching judge assignments:", error);
return NextResponse.json(
{ message: "Failed to fetch judge assignments" },
{ status: 500 },
);
}
});

export const POST = adminProtected(async (req: NextRequest) => {
try {
const body = await req.json();
const result = updateAssignmentsSchema.safeParse(body);

if (!result.success) {
return NextResponse.json(
{ message: "Invalid input", errors: result.error.format() },
{ status: 400 },
);
}

const { judgeRoundId, judgeUserId, teamIds } = result.data;

const existingRound = await db.query.judgeRounds.findFirst({
where: (round, { eq }) => eq(round.id, judgeRoundId),
});

if (!existingRound) {
return NextResponse.json(
{ message: "Judge round not found" },
{ status: 404 },
);
}

if (existingRound.status === "Completed") {
return NextResponse.json(
{ message: "Round is completed and cannot be modified" },
{ status: 409 },
);
}

const judgeUser = await db.query.dashboardUsers.findFirst({
where: (u, { eq }) => eq(u.id, judgeUserId),
});

if (!judgeUser) {
return NextResponse.json(
{ message: "Judge user not found" },
{ status: 404 },
);
}

let judge = await db.query.judges.findFirst({
where: (j, { eq }) => eq(j.dashboardUserId, judgeUserId),
});

if (!judge) {
const [createdJudge] = await db
.insert(judges)
.values({ dashboardUserId: judgeUserId })
.returning();
judge = createdJudge;
}

const existingAssignments = await db
.select({
id: judgeRoundAssignments.id,
teamId: judgeRoundAssignments.teamId,
})
.from(judgeRoundAssignments)
.where(
and(
eq(judgeRoundAssignments.judgeRoundId, judgeRoundId),
eq(judgeRoundAssignments.judgeId, judge.id),
),
);

const existingTeamIds = new Set(existingAssignments.map((a) => a.teamId));
const requestedTeamIds = new Set(teamIds);

const toRemove = existingAssignments
.filter((assignment) => !requestedTeamIds.has(assignment.teamId))
.map((assignment) => assignment.id);

const toAdd = teamIds.filter((teamId) => !existingTeamIds.has(teamId));

if (toRemove.length > 0) {
await db
.delete(judgeRoundAssignments)
.where(inArray(judgeRoundAssignments.id, toRemove));
}

if (toAdd.length > 0) {
await db.insert(judgeRoundAssignments).values(
toAdd.map((teamId) => ({
judgeId: judge.id,
teamId,
judgeRoundId,
})),
);
}

return NextResponse.json(
{
message: "Assignments updated successfully",
assignedTeamIds: teamIds,
},
{ status: 200 },
);
} catch (error) {
console.error("Error updating judge assignments:", error);
return NextResponse.json(
{ message: "Failed to update judge assignments" },
{ status: 500 },
);
}
});
104 changes: 104 additions & 0 deletions src/app/api/dashboard/judge/criteria/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { asc, eq } from "drizzle-orm";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { adminProtected } from "~/auth/routes-wrapper";
import db from "~/db";
import { judgeCriterias } from "~/db/schema";

const createJudgeCriteriaSchema = z.object({
judgeRoundId: z.string().min(1, "Judge round is required"),
criteriaName: z.string().min(1, "Criteria name is required").max(120),
maxScore: z.number().int().min(1).max(100),
});

export const GET = adminProtected(async (req: NextRequest) => {
try {
const { searchParams } = new URL(req.url);
const judgeRoundId = searchParams.get("judgeRoundId");

if (!judgeRoundId) {
return NextResponse.json([], { status: 200 });
}

const criteria = await db
.select()
.from(judgeCriterias)
.where(eq(judgeCriterias.judgeRoundId, judgeRoundId))
.orderBy(asc(judgeCriterias.criteriaName));

return NextResponse.json(criteria, { status: 200 });
} catch (error) {
console.error("Error fetching judge criteria:", error);
return NextResponse.json(
{ message: "Failed to fetch judge criteria" },
{ status: 500 },
);
}
});

export const POST = adminProtected(async (req: NextRequest) => {
try {
const body = await req.json();
const result = createJudgeCriteriaSchema.safeParse(body);

if (!result.success) {
return NextResponse.json(
{ message: "Invalid input", errors: result.error.format() },
{ status: 400 },
);
}

const existingRound = await db.query.judgeRounds.findFirst({
where: (round, { eq }) => eq(round.id, result.data.judgeRoundId),
});

if (!existingRound) {
return NextResponse.json(
{ message: "Judge round not found" },
{ status: 404 },
);
}

if (existingRound.status !== "Draft") {
return NextResponse.json(
{
message:
"Round is locked. Criteria can only be changed while status is Draft.",
},
{ status: 409 },
);
}

const duplicate = await db.query.judgeCriterias.findFirst({
where: (criteria, { eq, and }) =>
and(
eq(criteria.judgeRoundId, result.data.judgeRoundId),
eq(criteria.criteriaName, result.data.criteriaName),
),
});

if (duplicate) {
return NextResponse.json(
{ message: "Criteria already exists for this round" },
{ status: 409 },
);
}

const [created] = await db
.insert(judgeCriterias)
.values({
judgeRoundId: result.data.judgeRoundId,
criteriaName: result.data.criteriaName,
maxScore: result.data.maxScore,
})
.returning();

return NextResponse.json(created, { status: 201 });
} catch (error) {
console.error("Error creating judge criteria:", error);
return NextResponse.json(
{ message: "Failed to create judge criteria" },
{ status: 500 },
);
}
});
Loading
Loading