diff --git a/apps/blade/src/app/_components/admin/club/members/scanner.tsx b/apps/blade/src/app/_components/admin/club/members/scanner.tsx deleted file mode 100644 index 508ea9971..000000000 --- a/apps/blade/src/app/_components/admin/club/members/scanner.tsx +++ /dev/null @@ -1,369 +0,0 @@ -"use client"; - -import { useRef, useState } from "react"; -import { Scanner } from "@yudiel/react-qr-scanner"; -import { AwardIcon, WrenchIcon } from "lucide-react"; -import { z } from "zod"; - -import type { HackerClass } from "@forge/db/schemas/knight-hacks"; -import { HACKER_CLASSES } from "@forge/db/schemas/knight-hacks"; -import { Button } from "@forge/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@forge/ui/dialog"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - useForm, -} from "@forge/ui/form"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@forge/ui/tabs"; -import { toast } from "@forge/ui/toast"; -import { hackathons } from "@forge/utils"; - -import ToggleButton from "~/app/_components/admin/hackathon/hackers/toggle-button"; -import { api } from "~/trpc/react"; - -const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { - const { data: events } = api.event.getEvents.useQuery(); - - const filteredEvents = events?.filter((v) => { - if (eventType == "Member") return !v.hackathonId; - else return v.hackathonId; - }); - - const [open, setOpen] = useState(false); - const [openPersistentDialog, setOpenPersistentDialog] = useState(false); - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); - const [assignedClass, setAssignedClass] = useState(""); - const [checkInMessage, setCheckInMessage] = useState(""); - const [toggleRepeatedCheckIn, setToggleRepeatedCheckIn] = useState(false); - const [errorColor, setErrorColor] = useState("text-red-500"); - - const scanningRef = useRef(false); - - const now = new Date(); - const currentEvents = (filteredEvents ?? []).filter((event) => { - const eventEndTime = new Date(event.end_datetime); - const dayAfterEvent = new Date(eventEndTime); - dayAfterEvent.setDate(dayAfterEvent.getDate() + 1); - return dayAfterEvent >= now; - }); - const previousEvents = (filteredEvents ?? []).filter((event) => { - const eventEndTime = new Date(event.end_datetime); - const dayAfterEvent = new Date(eventEndTime); - dayAfterEvent.setDate(dayAfterEvent.getDate() + 1); - return dayAfterEvent < now; - }); - - const memberCheckIn = api.member.eventCheckIn.useMutation({ - onSuccess(opts) { - toast.success(opts.message); - return; - }, - onError(opts) { - toast.error(opts.message, { - icon: "⚠️", - }); - }, - }); - const hackerEventCheckIn = api.hackerMutation.eventCheckIn.useMutation({ - onSuccess(opts) { - toast.success(opts.message); - setErrorColor( - opts.messageforHackers.includes("[ERROR]") ? "text-red-500" : "", - ); - setFirstName(opts.firstName); - setLastName(opts.lastName); - setAssignedClass(opts.class ?? "No class assigned"); - setCheckInMessage(opts.messageforHackers); - setOpenPersistentDialog(true); - - return; - }, - onError(opts) { - if (!openPersistentDialog) { - toast.error(opts.message, { - icon: "⚠️", - }); - setErrorColor("text-red-500"); - setCheckInMessage(opts.message); - setFirstName("Error"); - setLastName("Error"); - setAssignedClass(""); - setOpenPersistentDialog(true); - } - toast.error(opts.message, { - icon: "⚠️", - }); - }, - }); - const AssignedClassCheckinSchema = z.union([ - z.literal("All"), - z.enum(HACKER_CLASSES), - ]); - const form = useForm({ - schema: z.object({ - userId: z.string(), - eventId: z.string(), - eventPoints: z.number(), - hackathonId: z.string(), - assignedClassCheckin: AssignedClassCheckinSchema, - repeatedCheckin: z.boolean(), - }), - defaultValues: { - eventId: "", - userId: "", - eventPoints: 0, - hackathonId: "", - assignedClassCheckin: "All", - repeatedCheckin: false, - }, - }); - const closeModal = () => { - setOpen(false); - window.location.reload(); - }; - const closePersistentDialog = () => { - setOpenPersistentDialog(false); - }; - - const renderEventSelect = (filteredEvents: typeof events) => ( - ( - - - - Event - - This event only accepts{" "} - {eventType} QR - codes. - - - - - - - - - )} - /> - ); - const renderClassCheckinSelect = () => ( - ( - - Check-in class - - - - - - )} - /> - ); - - return ( - - - - - - - Check-in {eventType} - -
- { - const result = (detectedCodes as { rawValue: string }[])[0]; - if (!result) return; - if (scanningRef.current) return; - scanningRef.current = true; - try { - const userId = result.rawValue.substring(5); - form.setValue("userId", userId); - const eventId = form.getValues("eventId"); - if (eventId) { - if (eventType === "Hacker") { - await form.handleSubmit((data) => - hackerEventCheckIn.mutate(data), - )(); - } else { - await form.handleSubmit((data) => - memberCheckIn.mutate(data), - )(); - } - } else { - toast.error("Please select an event first!"); - } - } finally { - setTimeout(() => { - scanningRef.current = false; - }, 3000); - } - }} - /> -
-
- { - if (eventType == "Hacker") hackerEventCheckIn.mutate(data); - else memberCheckIn.mutate(data); - })} - className="space-y-4" - > - - - Upcoming Events - Previous Events - - - {renderEventSelect(currentEvents)} - {eventType === "Hacker" && renderClassCheckinSelect()} - {eventType === "Hacker" && ( - { - setToggleRepeatedCheckIn(value); - form.setValue("repeatedCheckin", value); - }} - /> - )} - - - {renderEventSelect(previousEvents)} - {eventType === "Hacker" && renderClassCheckinSelect()} - {eventType === "Hacker" && ( - { - setToggleRepeatedCheckIn(value); - form.setValue("repeatedCheckin", value); - }} - /> - )} - - -
- -
- -
- {openPersistentDialog && ( -
- -
- CHECKED IN -
-
-
FIRST
-
{firstName}
-
-
-
LAST
-
{lastName}
-
-
-
CLASS
-
- {assignedClass} -
-
-
- {errorColor != "" ? ( -
- {checkInMessage} -
- ) : ( -
- {checkInMessage} -
- )} -
- )} - -
- ); -}; - -export default ScannerPopUp; diff --git a/apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx b/apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx index 0ee7853a8..8b92792bc 100644 --- a/apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx +++ b/apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx @@ -4,7 +4,6 @@ import { useEffect, useRef, useState } from "react"; import { Check, ChevronsUpDown, WrenchIcon } from "lucide-react"; import { z } from "zod"; -import { HACKER_CLASSES } from "@forge/db/schemas/knight-hacks"; import { Button } from "@forge/ui/button"; import { Command, @@ -102,18 +101,12 @@ export function ManualEntryForm() { }, }); - const AssignedClassCheckinSchema = z.union([ - z.literal("All"), - z.enum(HACKER_CLASSES), - ]); - const form = useForm({ schema: z.object({ userId: z.string().min(1, "Please select a hacker"), eventId: z.string().min(1, "Event is required"), eventPoints: z.number(), hackathonId: z.string(), - assignedClassCheckin: AssignedClassCheckinSchema, repeatedCheckin: z.boolean(), }), defaultValues: { @@ -121,7 +114,6 @@ export function ManualEntryForm() { userId: "", eventPoints: 0, hackathonId: "", - assignedClassCheckin: "All", repeatedCheckin: false, }, }); @@ -131,7 +123,6 @@ export function ManualEntryForm() { eventId: string; eventPoints: number; hackathonId: string; - assignedClassCheckin: string; repeatedCheckin: boolean; }) => { hackerEventCheckIn.mutate(data); @@ -188,42 +179,6 @@ export function ManualEntryForm() { /> ); - const renderClassCheckinSelect = () => ( - ( - - Check-in class - - - - - - )} - /> - ); - return (
{/* Hackathon Events Header */} @@ -343,7 +298,6 @@ export function ManualEntryForm() { {renderEventSelect(currentEvents)} - {renderClassCheckinSelect()} { @@ -354,7 +308,6 @@ export function ManualEntryForm() { {renderEventSelect(previousEvents)} - {renderClassCheckinSelect()} { diff --git a/apps/blade/src/app/_components/shared/scanner.tsx b/apps/blade/src/app/_components/shared/scanner.tsx index a0240808b..332bbc6ff 100644 --- a/apps/blade/src/app/_components/shared/scanner.tsx +++ b/apps/blade/src/app/_components/shared/scanner.tsx @@ -5,8 +5,6 @@ import { Scanner } from "@yudiel/react-qr-scanner"; import { AwardIcon, WrenchIcon } from "lucide-react"; import { z } from "zod"; -import type { HackerClass } from "@forge/db/schemas/knight-hacks"; -import { HACKER_CLASSES } from "@forge/db/schemas/knight-hacks"; import { Button } from "@forge/ui/button"; import { Dialog, @@ -26,7 +24,6 @@ import { } from "@forge/ui/form"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@forge/ui/tabs"; import { toast } from "@forge/ui/toast"; -import { hackathons } from "@forge/utils"; import ToggleButton from "~/app/_components/admin/hackathon/hackers/toggle-button"; import { api } from "~/trpc/react"; @@ -43,7 +40,6 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { const [openPersistentDialog, setOpenPersistentDialog] = useState(false); const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); - const [assignedClass, setAssignedClass] = useState(""); const [checkInMessage, setCheckInMessage] = useState(""); const [toggleRepeatedCheckIn, setToggleRepeatedCheckIn] = useState(false); const [errorColor, setErrorColor] = useState("text-red-500"); @@ -83,7 +79,6 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { ); setFirstName(opts.firstName); setLastName(opts.lastName); - setAssignedClass(opts.class ?? "No class assigned"); setCheckInMessage(opts.messageforHackers); setOpenPersistentDialog(true); @@ -98,7 +93,6 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { setCheckInMessage(opts.message); setFirstName("Error"); setLastName("Error"); - setAssignedClass(""); setOpenPersistentDialog(true); } toast.error(opts.message, { @@ -106,17 +100,12 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { }); }, }); - const AssignedClassCheckinSchema = z.union([ - z.literal("All"), - z.enum(HACKER_CLASSES), - ]); const form = useForm({ schema: z.object({ userId: z.string(), eventId: z.string(), eventPoints: z.number(), hackathonId: z.string(), - assignedClassCheckin: AssignedClassCheckinSchema, repeatedCheckin: z.boolean(), }), defaultValues: { @@ -124,7 +113,6 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { userId: "", eventPoints: 0, hackathonId: "", - assignedClassCheckin: "All", repeatedCheckin: false, }, }); @@ -182,43 +170,6 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { )} /> ); - const renderClassCheckinSelect = () => ( - ( - - Check-in class - - - - - - )} - /> - ); - return ( @@ -283,7 +234,6 @@ const ScannerPopUp = ({ eventType }: { eventType: "Member" | "Hacker" }) => { {renderEventSelect(currentEvents)} - {eventType === "Hacker" && renderClassCheckinSelect()} {eventType === "Hacker" && ( { {renderEventSelect(previousEvents)} - {eventType === "Hacker" && renderClassCheckinSelect()} {eventType === "Hacker" && ( {
LAST
{lastName}
-
-
CLASS
-
- {assignedClass} -
-
{errorColor != "" ? (
{ - //Idk man leave me alone - if (!AssignedClassCheckinSchema.safeParse(v).success) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Invalid assignedClassCheckin", - }); - } - }), repeatedCheckin: z.boolean(), }), ) @@ -550,7 +538,8 @@ export const hackerMutationRouter = { code: "NOT_FOUND", message: `Event with ID ${input.eventId} not found.`, }); - if (!event.hackathonId) { + const hackathonId = event.hackathonId; + if (!hackathonId) { throw new TRPCError({ code: "UNAUTHORIZED", message: `Event with ID ${input.eventId} is not a hackathon event.`, @@ -566,7 +555,6 @@ export const hackerMutationRouter = { lastName: Hacker.lastName, discordUser: Hacker.discordUser, status: HackerAttendee.status, - class: HackerAttendee.class, points: HackerAttendee.points, hackerAttendeeId: HackerAttendee.id, }) @@ -575,7 +563,7 @@ export const hackerMutationRouter = { .where( and( eq(Hacker.userId, input.userId), - eq(HackerAttendee.hackathonId, event.hackathonId), + eq(HackerAttendee.hackathonId, hackathonId), ), ) .limit(1); @@ -593,17 +581,12 @@ export const hackerMutationRouter = { const hackerAttendee = { id: result.hackerAttendeeId, status: result.status, - class: result.class, points: result.points, - hackathonId: event.hackathonId, + hackathonId, hackerId: hacker.id, }; const eventTag = event.tag; - const discordId = await discord.resolveDiscordUserId(hacker.discordUser); - const isVIP = discordId ? await discord.isDiscordVIP(discordId) : false; - - let assignedClass: HackerClass | null = hackerAttendee.class ?? null; if ( hackerAttendee.status !== "confirmed" && @@ -622,106 +605,75 @@ export const hackerMutationRouter = { } if (hackerAttendee.status === "confirmed" && eventTag === "Check-in") { - await db.transaction(async (tx) => { - // Use the already fetched hackerAttendee data instead of querying again - if (hackerAttendee.class && hackerAttendee.class in HACKER_CLASSES) { - assignedClass = hackerAttendee.class; - return; - } - - const totalHackerinClass = await Promise.all( - HACKER_CLASSES.map(async (cls) => { - const rows = await tx - .select({ c: count() }) - .from(HackerAttendee) - .where( - and( - eq(HackerAttendee.hackathonId, input.hackathonId), - eq(HackerAttendee.class, cls), - ), - ); - return { cls, count: Number(rows[0]?.c ?? 0) } as const; - }), - ); - - const leastPopulatedClass = Math.min( - ...totalHackerinClass.map((c) => c.count), - ); - const candidates = totalHackerinClass - .filter((c) => c.count === leastPopulatedClass) - .map((c) => c.cls); - - const pick: HackerClass = - candidates[Math.floor(Math.random() * candidates.length)] ?? - HACKER_CLASSES[0]; - - await tx - .update(HackerAttendee) - .set({ class: pick, status: "checkedin" }) - .where( - and( - eq(HackerAttendee.hackerId, hacker.id), - eq(HackerAttendee.hackathonId, input.hackathonId), - ), - ); - - assignedClass = pick; + const hackathon = await db.query.Hackathon.findFirst({ + columns: { name: true }, + where: (t, { eq }) => eq(t.id, hackathonId), }); - if (!discordId) { - await discord.log({ - title: "Discord role assign skipped", - message: `Could not resolve Discord ID for "${hacker.discordUser}".`, - color: "uhoh_red", - userId: ctx.session.user.discordUserId, + if (!hackathon) { + throw new TRPCError({ + code: "NOT_FOUND", + message: `Hackathon with ID ${hackathonId} not found.`, }); - } else { + } + + await db + .update(HackerAttendee) + .set({ status: "checkedin" }) + .where(eq(HackerAttendee.id, hackerAttendee.id)); + + if (hackathon.name === HACKATHONS.BLOOMKNIGHTS.HACKATHON_NAME) { try { - await discord.addRoleToMember( - discordId, - HACKATHONS.KNIGHT_HACKS_8.KH_EVENT_ROLE_ID, - ); - logger.log( - `Assigned role ${HACKATHONS.KNIGHT_HACKS_8.KH_EVENT_ROLE_ID} to user ${discordId}`, + const discordId = await discord.resolveDiscordUserId( + hacker.discordUser, ); - // VIP will already be given the discord role ahead of time, so no need to assign again - if (assignedClass) { + + if (!discordId) { + try { + await discord.log({ + title: "Discord role assign skipped", + message: `Could not resolve Discord ID for "${hacker.discordUser}".`, + color: "uhoh_red", + userId: ctx.session.user.discordUserId, + }); + } catch (logError) { + logger.error( + "Failed to log skipped BloomKnights role assignment:", + logError instanceof Error + ? logError.message + : "Unknown error", + ); + } + } else { await discord.addRoleToMember( discordId, - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - HACKATHONS.KNIGHT_HACKS_8.CLASS_ROLE_ID[ - assignedClass as HACKATHONS.KNIGHT_HACKS_8.AssignableHackerClass - ] ?? "", + HACKATHONS.BLOOMKNIGHTS.EVENT_ROLE_ID, + ); + logger.log( + `Assigned role ${HACKATHONS.BLOOMKNIGHTS.EVENT_ROLE_ID} to user ${discordId}`, ); } } catch (e) { - await discord.log({ - title: "Discord role assign failed", - message: `Failed to assign Discord roles for "${hacker.discordUser}".`, - color: "uhoh_red", - userId: ctx.session.user.discordUserId, - }); logger.error( - "Failed to assign Discord roles:", - (e as Error).message, + "Failed to assign the BloomKnights role:", + e instanceof Error ? e.message : "Unknown error", ); + try { + await discord.log({ + title: "Discord role assign failed", + message: `Failed to assign the BloomKnights role for "${hacker.discordUser}".`, + color: "uhoh_red", + userId: ctx.session.user.discordUserId, + }); + } catch (logError) { + logger.error( + "Failed to log BloomKnights role assignment failure:", + logError instanceof Error ? logError.message : "Unknown error", + ); + } } } } - if ( - input.assignedClassCheckin !== "All" && - hackerAttendee.class !== input.assignedClassCheckin && - !isVIP - ) { - return { - message: `Hacker ${hacker.firstName} ${hacker.lastName} has already checked into this event!`, - firstName: hacker.firstName, - lastName: hacker.lastName, - class: assignedClass, - messageforHackers: `[ERROR]\nOnly ${input.assignedClassCheckin} hackers can check in. Hacker has class ${hackerAttendee.class}`, - eventName: eventTag, - }; - } const duplicates = await db .select({ id: HackerEventAttendee.id }) @@ -738,7 +690,6 @@ export const hackerMutationRouter = { message: `Hacker ${hacker.firstName} ${hacker.lastName} has already checked into this event!`, firstName: hacker.firstName, lastName: hacker.lastName, - class: assignedClass, messageforHackers: "[ERROR]\nThe hacker has already checked into this event.", eventName: eventTag, @@ -746,7 +697,7 @@ export const hackerMutationRouter = { await db.insert(HackerEventAttendee).values({ hackerAttId: hackerAttendee.id, eventId: input.eventId, - hackathonId: event.hackathonId, + hackathonId, }); await db .update(HackerAttendee) @@ -756,20 +707,15 @@ export const hackerMutationRouter = { if (eventTag === "Check-in") { await discord.log({ title: `Hacker Checked-In`, - message: `${hacker.firstName} ${hacker.lastName} has been checked in to Hackathon ${ - assignedClass ? ` (Class: ${assignedClass}).` : "" - }`, + message: `${hacker.firstName} ${hacker.lastName} has been checked in to the hackathon.`, color: "success_green", userId: ctx.session.user.discordUserId, }); return { - message: `${hacker.firstName} ${hacker.lastName} has been checked in to this Hackathon!${ - assignedClass ? ` Assigned class: ${assignedClass}.` : "" - }`, + message: `${hacker.firstName} ${hacker.lastName} has been checked in to this hackathon!`, firstName: hacker.firstName, lastName: hacker.lastName, - class: assignedClass, - messageforHackers: "Check ID, and send them to correct lanyard area", + messageforHackers: "Check their ID and give them their badge.", eventName: eventTag, }; } @@ -783,7 +729,6 @@ export const hackerMutationRouter = { message: `Hacker ${hacker.firstName} ${hacker.lastName} has been checked in to this event!`, firstName: hacker.firstName, lastName: hacker.lastName, - class: assignedClass, messageforHackers: "Check their badge and send them to event area", eventName: eventTag, }; diff --git a/packages/api/src/routers/hackers/queries.ts b/packages/api/src/routers/hackers/queries.ts index 13bdc164f..ffca18b8a 100644 --- a/packages/api/src/routers/hackers/queries.ts +++ b/packages/api/src/routers/hackers/queries.ts @@ -2,13 +2,9 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { FORMS } from "@forge/consts"; -import { and, count, desc, eq, gt, or, sum } from "@forge/db"; +import { and, count, eq } from "@forge/db"; import { db } from "@forge/db/client"; -import { - Hacker, - HACKER_CLASSES, - HackerAttendee, -} from "@forge/db/schemas/knight-hacks"; +import { Hacker, HackerAttendee } from "@forge/db/schemas/knight-hacks"; import { permissions } from "@forge/utils"; import { permProcedure, protectedProcedure } from "../../trpc"; @@ -80,7 +76,6 @@ export const hackerQueryRouter = { dateCreated: Hacker.dateCreated, timeCreated: Hacker.timeCreated, status: HackerAttendee.status, - class: HackerAttendee.class, points: HackerAttendee.points, }) .from(Hacker) @@ -229,194 +224,6 @@ export const hackerQueryRouter = { return hackers; }), - getPointsByClass: protectedProcedure - .input(z.object({ hackathonName: z.string().optional() })) - .query(async ({ input }) => { - let hackathon; - const points: number[] = []; - - HACKER_CLASSES.forEach(() => { - points.push(0); - }); - - if (input.hackathonName) { - // If a hackathon name is provided, grab that hackathon - hackathon = await db.query.Hackathon.findFirst({ - where: (t, { eq }) => eq(t.name, input.hackathonName ?? ""), - }); - - if (!hackathon) { - return points; - } - } else { - // If not provided, grab a FUTURE hackathon with a start date CLOSEST to now - const now = new Date(); - const futureHackathons = await db.query.Hackathon.findMany({ - where: (t, { gt }) => gt(t.startDate, now), - orderBy: (t, { asc }) => [asc(t.startDate)], - limit: 1, - }); - hackathon = futureHackathons[0]; - - if (!hackathon) { - return points; - } - } - - for (let i = 0; i < HACKER_CLASSES.length; i++) { - const c = HACKER_CLASSES[i]; - const s = await db - .select({ - sum: sum(HackerAttendee.points).mapWith(Number), - }) - .from(HackerAttendee) - .where( - and( - eq(HackerAttendee.hackathonId, hackathon.id), - eq(HackerAttendee.class, c ?? "Alchemist"), - ), - ); - - points[i] = s.at(0)?.sum ?? 0; - } - - return points; - }), - - getTopHackers: protectedProcedure - .input( - z.object({ - hackathonName: z.string().optional(), - hPoints: z.number(), - hClass: z.string(), - }), - ) - .query(async ({ input }) => { - let hackathon; - - if (input.hackathonName) { - // If a hackathon name is provided, grab that hackathon - hackathon = await db.query.Hackathon.findFirst({ - where: (t, { eq }) => eq(t.name, input.hackathonName ?? ""), - }); - - if (!hackathon) { - return { topA: [], topB: [], place: [0, 0, 0] }; - } - } else { - // If not provided, grab a FUTURE hackathon with a start date CLOSEST to now - const now = new Date(); - const futureHackathons = await db.query.Hackathon.findMany({ - where: (t, { gt }) => gt(t.startDate, now), - orderBy: (t, { asc }) => [asc(t.startDate)], - limit: 1, - }); - hackathon = futureHackathons[0]; - - if (!hackathon) { - return { topA: [], topB: [], place: [0, 0, 0] }; - } - } - - // this code is going to start looking really stupid - // but its all so that we dont have to send like half the DB of hackers to the client - // and hopefully save performance - - const topA = await db - .select({ - firstName: Hacker.firstName, - lastName: Hacker.lastName, - points: HackerAttendee.points, - class: HackerAttendee.class, - id: Hacker.id, - }) - .from(HackerAttendee) - .innerJoin(Hacker, eq(Hacker.id, HackerAttendee.hackerId)) - .where( - and( - eq(HackerAttendee.hackathonId, hackathon.id), - or( - eq(HackerAttendee.class, HACKER_CLASSES[0]), - eq(HackerAttendee.class, HACKER_CLASSES[1]), - eq(HackerAttendee.class, HACKER_CLASSES[2]), - ), - ), - ) - .orderBy(desc(HackerAttendee.points)) - .limit(5); - - const topB = await db - .select({ - firstName: Hacker.firstName, - lastName: Hacker.lastName, - points: HackerAttendee.points, - class: HackerAttendee.class, - id: Hacker.id, - }) - .from(HackerAttendee) - .innerJoin(Hacker, eq(Hacker.id, HackerAttendee.hackerId)) - .where( - and( - eq(HackerAttendee.hackathonId, hackathon.id), - or( - eq(HackerAttendee.class, HACKER_CLASSES[3]), - eq(HackerAttendee.class, HACKER_CLASSES[4]), - eq(HackerAttendee.class, HACKER_CLASSES[5]), - ), - ), - ) - .orderBy(desc(HackerAttendee.points)) - .limit(5); - - // stores your place in each sorted leaderboard - // 0: team A, 2: overall, 3: team B - - let ind = 0; - HACKER_CLASSES.forEach((v, i) => { - if (v == input.hClass) ind = i; - }); - - const place = [ - ind >= 3 - ? -1 - : await db.$count( - HackerAttendee, - and( - eq(HackerAttendee.hackathonId, hackathon.id), - gt(HackerAttendee.points, input.hPoints), - or( - eq(HackerAttendee.class, HACKER_CLASSES[0]), - eq(HackerAttendee.class, HACKER_CLASSES[1]), - eq(HackerAttendee.class, HACKER_CLASSES[2]), - ), - ), - ), - await db.$count( - HackerAttendee, - and( - eq(HackerAttendee.hackathonId, hackathon.id), - gt(HackerAttendee.points, input.hPoints), - ), - ), - ind < 3 - ? -1 - : await db.$count( - HackerAttendee, - and( - eq(HackerAttendee.hackathonId, hackathon.id), - gt(HackerAttendee.points, input.hPoints), - or( - eq(HackerAttendee.class, HACKER_CLASSES[3]), - eq(HackerAttendee.class, HACKER_CLASSES[4]), - eq(HackerAttendee.class, HACKER_CLASSES[5]), - ), - ), - ), - ]; - - return { topA: topA, topB: topB, place: place }; - }), - statusCountByHackathonId: permProcedure .input(z.string()) .query(async ({ ctx, input: hackathonId }) => { diff --git a/packages/consts/src/hackathons/bloomknights.ts b/packages/consts/src/hackathons/bloomknights.ts new file mode 100644 index 000000000..0232a0b25 --- /dev/null +++ b/packages/consts/src/hackathons/bloomknights.ts @@ -0,0 +1,6 @@ +import { IS_PROD } from "../util"; + +export const HACKATHON_NAME = "bloomknights"; +export const PROD_DISCORD_ROLE = "1510751065786683412"; +export const DEV_DISCORD_ROLE = "1520492619921227836"; +export const EVENT_ROLE_ID = IS_PROD ? PROD_DISCORD_ROLE : DEV_DISCORD_ROLE; diff --git a/packages/consts/src/hackathons/index.ts b/packages/consts/src/hackathons/index.ts index 34c4df60e..cb4daeacc 100644 --- a/packages/consts/src/hackathons/index.ts +++ b/packages/consts/src/hackathons/index.ts @@ -1,4 +1,4 @@ -export * as KNIGHT_HACKS_8 from "./kh8"; +export * as BLOOMKNIGHTS from "./bloomknights"; export const APPLICATION_BACKGROUND_OPTIONS = [ { diff --git a/packages/consts/src/hackathons/kh8.ts b/packages/consts/src/hackathons/kh8.ts deleted file mode 100644 index 8d232fdef..000000000 --- a/packages/consts/src/hackathons/kh8.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Because @forge/db requires @forge/consts we can't import @forge/db right -// here. Ideally there is another way but im not too sure. -// TODO: look into not doing this -import type { HackerClass } from "../../../db/src/schemas/knight-hacks"; -import { IS_PROD } from "../util"; - -export const PROD_DISCORD_ROLE_KNIGHT_HACKS_8 = "1408025502119231498"; -export const DEV_DISCORD_ROLE_KNIGHT_HACKS_8 = "1420819573816692857"; -export const KH_EVENT_ROLE_ID = IS_PROD - ? PROD_DISCORD_ROLE_KNIGHT_HACKS_8 - : DEV_DISCORD_ROLE_KNIGHT_HACKS_8; - -export const PROD_DISCORD_ROLE_OPERATORS = "1415702220825038879"; -export const DEV_DISCORD_ROLE_OPERATORS = "1420819261223600239"; - -export const PROD_DISCORD_ROLE_MACHINIST = "1415702276433248406"; -export const DEV_DISCORD_ROLE_MACHINIST = "1420819223797698683"; - -export const PROD_DISCORD_ROLE_SENTINELS = "1415702308494250136"; -export const DEV_DISCORD_ROLE_SENTINELS = "1420819277279137892"; - -export const PROD_DISCORD_ROLE_HARBINGER = "1415702341214011392"; -export const DEV_DISCORD_ROLE_HARBINGER = "1420819326075801670"; - -export const PROD_DISCORD_ROLE_MONSTOLOGIST = "1415702361653121044"; -export const DEV_DISCORD_ROLE_MONSTOLOGIST = "1420819295759237222"; - -export const PROD_DISCORD_ROLE_ALCHEMIST = "1415702383274491934"; -export const DEV_DISCORD_ROLE_ALCHEMIST = "1420819309965611140"; - -export const PROD_DISCORD_SUPERADMIN = "486629374758748180"; -export const DEV_DISCORD_SUPERADMIN = "1246637685011906560"; - -export type AssignableHackerClass = Exclude; - -export const CLASS_ROLE_ID: Record = { - Operator: IS_PROD ? PROD_DISCORD_ROLE_OPERATORS : DEV_DISCORD_ROLE_OPERATORS, - Mechanist: IS_PROD ? PROD_DISCORD_ROLE_MACHINIST : DEV_DISCORD_ROLE_MACHINIST, - Sentinel: IS_PROD ? PROD_DISCORD_ROLE_SENTINELS : DEV_DISCORD_ROLE_SENTINELS, - Harbinger: IS_PROD ? PROD_DISCORD_ROLE_HARBINGER : DEV_DISCORD_ROLE_HARBINGER, - Monstologist: IS_PROD - ? PROD_DISCORD_ROLE_MONSTOLOGIST - : DEV_DISCORD_ROLE_MONSTOLOGIST, - Alchemist: IS_PROD ? PROD_DISCORD_ROLE_ALCHEMIST : DEV_DISCORD_ROLE_ALCHEMIST, -} as const satisfies Record; - -export interface ClassInfo { - team: string; - teamColor: string; - classPfp: string; -} - -export const HACKER_CLASS_INFO: Record = { - Mechanist: { - team: "Humanity", - teamColor: "#228be6", - classPfp: "/khviii/mechanist.jpg", - }, - Operator: { - team: "Humanity", - teamColor: "#228be6", - classPfp: "/khviii/operator.jpg", - }, - Sentinel: { - team: "Humanity", - teamColor: "#228be6", - classPfp: "/khviii/sentinel.jpg", - }, - Monstologist: { - team: "Monstrosity", - teamColor: "#e03131", - classPfp: "/khviii/monstologist.jpg", - }, - Harbinger: { - team: "Monstrosity", - teamColor: "#e03131", - classPfp: "/khviii/harbinger.jpg", - }, - Alchemist: { - team: "Monstrosity", - teamColor: "#e03131", - classPfp: "/khviii/alchemist.jpg", - }, -}; diff --git a/packages/db/src/schemas/knight-hacks.ts b/packages/db/src/schemas/knight-hacks.ts index 07d875614..2133ddd70 100644 --- a/packages/db/src/schemas/knight-hacks.ts +++ b/packages/db/src/schemas/knight-hacks.ts @@ -225,27 +225,6 @@ export const EventAttendee = createTable("event_attendee", (t) => ({ }), })); -export const HACKER_TEAMS = ["Humanity", "Monstrosity"] as const; -export const HACKER_CLASSES = [ - "Operator", - "Mechanist", - "Sentinel", - "Harbinger", - "Monstologist", - "Alchemist", -] as const; -export const SPECIAL_HACKER_CLASSES = ["VIP"] as const; -export const HACKER_CLASSES_ALL = [ - ...HACKER_CLASSES, - ...SPECIAL_HACKER_CLASSES, -] as const; -export type HackerClass = (typeof HACKER_CLASSES_ALL)[number]; -export type RepeatPolicy = "none" | "all" | "class"; -export const AssignedClassCheckinSchema = z.union([ - z.literal("All"), - z.enum(HACKER_CLASSES), -]); - export const HackerAttendee = createTable("hacker_attendee", (t) => ({ id: t.uuid().notNull().primaryKey().defaultRandom(), hackerId: t @@ -269,7 +248,7 @@ export const HackerAttendee = createTable("hacker_attendee", (t) => ({ timeApplied: t.timestamp().notNull().defaultNow(), timeConfirmed: t.timestamp(), points: t.integer().notNull().default(0), - class: t.varchar({ length: 20 }).$type().default(null), + class: t.varchar({ length: 20 }).$type().default(null), })); export const HackerEventAttendee = createTable( diff --git a/packages/utils/src/hackathons.ts b/packages/utils/src/hackathons.ts deleted file mode 100644 index 180e9e54e..000000000 --- a/packages/utils/src/hackathons.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { HackerClass } from "@forge/db/schemas/knight-hacks"; - -/** - * Gets the team information for a hackathon class. - * - * @param {HackerClass} tag - The hacker class. - * @returns {object} Team information including name, color, and image URL. - * - * @example - * getClassTeam("Harbinger") // { team: "Monstrosity", teamColor: "#e03131", imgUrl: "/khviii/lenneth.jpg" } - */ -export const getClassTeam = (tag: HackerClass) => { - if (["Harbinger", "Alchemist", "Monstologist"].includes(tag)) { - return { - team: "Monstrosity", - teamColor: "#e03131", - imgUrl: "/khviii/lenneth.jpg", - }; - } - return { - team: "Humanity", - teamColor: "#228be6", - imgUrl: "/khviii/tkhero.jpg", - }; -}; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index c805bd0e0..e56d8ee1a 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,5 +1,4 @@ export * as events from "./events"; -export * as hackathons from "./hackathons"; export { logger } from "./logger"; export * as permissions from "./permissions"; export * as time from "./time";