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
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
redis:
image: redis:8.2-alpine
image: redis:8.6
container_name: hackfest-redis
ports:
- "6379:6379"
Expand Down
Binary file added public/logos/Logotext@3x-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 3 additions & 24 deletions src/app/api/events/[id]/solo/[action]/route.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
import type { NextRequest } from "next/server";
import { protectedEventRoute } from "~/auth/route-handlers";
import { eventRegistrationOpen, findByEventId } from "~/db/data/event";
import { registrationOpenEventRoute } from "~/auth/route-handlers";
import { findByEvent } from "~/db/data/event-users";
import { createEventTeam, deleteEventTeam } from "~/db/services/event-services";
import { AppError } from "~/lib/errors/app-error";
import { errorResponse } from "~/lib/response/error";

export const POST = protectedEventRoute(
export const POST = registrationOpenEventRoute(
async (_req: NextRequest, context, user) => {
const registrationsOpen = await eventRegistrationOpen();

if (!registrationsOpen) {
return errorResponse(
new AppError("Registration closed", 403, {
title: "Registration Closed",
description: "Event registration is currently closed.",
}),
);
}

const { id: eventId, action } = await context.params;
const event = await findByEventId(eventId);
if (event?.status === "Draft") {
return errorResponse(
new AppError("Event not found", 404, {
title: "Event Not Found",
description: "The specified event does not exist.",
}),
);
}

const eventUser = await findByEvent(eventId, user.id);

Expand Down Expand Up @@ -56,7 +35,7 @@ export const POST = protectedEventRoute(
);
}

return await deleteEventTeam(eventUser.teamId);
return await deleteEventTeam(eventUser.teamId, user.id);
}
default:
return errorResponse(
Expand Down
125 changes: 59 additions & 66 deletions src/app/api/events/[id]/teams/[action]/route.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,23 @@
import type { NextRequest } from "next/server";
import { protectedEventRoute } from "~/auth/route-handlers";
import { eventRegistrationOpen, findByEventId } from "~/db/data/event";
import { findByEvent } from "~/db/data/event-users";
import { registrationOpenEventRoute } from "~/auth/route-handlers";
import {
confirmEventTeam,
createEventTeam,
deleteEventTeam,
eventRegistrationChecker,
joinEventTeam,
kickMemberFromTeam,
leaveEventTeam,
} from "~/db/services/event-services";
import { AppError } from "~/lib/errors/app-error";
import { errorResponse } from "~/lib/response/error";

export const POST = protectedEventRoute(
export const POST = registrationOpenEventRoute(
async (req: NextRequest, context, user) => {
const registrationsOpen = await eventRegistrationOpen();
if (!registrationsOpen) {
return errorResponse(
new AppError("Registrations closed", 403, {
title: "Registrations Closed",
description: "Event registrations are currently closed.",
}),
);
}

const { id: eventId, action } = await context.params;
const event = await findByEventId(eventId);
if (event?.status === "Draft") {
return errorResponse(
new AppError("Event not found", 404, {
title: "Event Not Found",
description: "The specified event does not exist.",
}),
);
}

const eventUser = await findByEvent(eventId, user.id);

if (!eventUser && action !== "create") {
return errorResponse(
new AppError("Unauthorized", 401, {
title: "Unauthorized",
description: "You are not registered for this event.",
}),
);
} else if (eventUser) {
if (action === "create") {
return errorResponse(
new AppError("Already Registered", 400, {
title: "Already Registered",
description: "You are already registered for this event.",
}),
);
}

if (["confirm", "delete"].includes(action) && !eventUser.isLeader) {
return errorResponse(
new AppError("Forbidden", 403, {
title: "Forbidden",
description: "Only team leaders can perform this action.",
}),
);
}
}

const notRegisteredError = errorResponse(
new AppError("Unauthorized", 401, {
title: "Unauthorized",
description: "You are not registered for this event.",
}),
);
const eventUser = await eventRegistrationChecker(eventId, user.id, action);
if (eventUser instanceof AppError) return errorResponse(eventUser);

try {
switch (action) {
Expand All @@ -78,8 +26,11 @@ export const POST = protectedEventRoute(
return await createEventTeam(eventId, user.id, teamName);
}
case "leave": {
if (!eventUser) return notRegisteredError;
return await leaveEventTeam(eventId, user.id, eventUser.teamId);
return await leaveEventTeam(
eventId,
eventUser?.teamId ?? "",
user.id,
);
}
case "join": {
const { teamId } = await req.json();
Expand All @@ -90,13 +41,21 @@ export const POST = protectedEventRoute(
teamId,
);
}
case "confirm": {
if (!eventUser) return notRegisteredError;
return await confirmEventTeam(eventId, eventUser.teamId);
case "kick": {
const { memberId } = await req.json();
return await kickMemberFromTeam(
eventId,
eventUser?.teamId ?? "",
user.id,
memberId,
);
}
case "delete": {
if (!eventUser) return notRegisteredError;
return await deleteEventTeam(eventUser.teamId);
case "confirm": {
return await confirmEventTeam(
eventId,
eventUser?.teamId ?? "",
user.id,
);
}
default:
return errorResponse(
Expand All @@ -117,3 +76,37 @@ export const POST = protectedEventRoute(
}
},
);

export const DELETE = registrationOpenEventRoute(
async (_req: NextRequest, context, user) => {
const { id: eventId, action } = await context.params;

if (action === "delete") {
const eventUser = await eventRegistrationChecker(
eventId,
user.id,
"delete",
);
if (eventUser instanceof AppError) return errorResponse(eventUser);

try {
return await deleteEventTeam(eventUser?.teamId ?? "", user.id);
} catch (err) {
return errorResponse(
new AppError("Delete failed", 500, {
title: "Delete Failed",
description:
err instanceof Error ? err.message : "An unknown error occurred.",
}),
);
}
}

return errorResponse(
new AppError("Unknown action", 400, {
title: "Unknown Action",
description: "The specified action is not recognized.",
}),
);
},
);
9 changes: 3 additions & 6 deletions src/app/api/events/getAll/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ import type { NextRequest } from "next/server";
import { auth } from "~/auth/event-config";
import { publicRoute } from "~/auth/route-handlers";
import { getAllEvents } from "~/db/services/event-services";
import { successResponse } from "~/lib/response/success";

export const GET = publicRoute(async (_req: NextRequest) => {
const session = await auth();

if (session?.eventUser) {
const events = await getAllEvents(session.eventUser.id);
return successResponse({ events }, { toast: false });
if (session?.eventUser?.id) {
return await getAllEvents(session.eventUser.id);
}

const events = await getAllEvents();
return successResponse({ events }, { toast: false });
return await getAllEvents();
});
2 changes: 1 addition & 1 deletion src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { redirect } from "next/navigation";
import { auth, signOut } from "~/auth/dashboard-config";
import { DashboardContent } from "~/components/dashboard/dashboard-content";
import { LiveClock } from "~/components/dashboard/live-clock";
import { LiveClock } from "~/components/dashboard/other/live-clock";
import { Button } from "~/components/ui/button";
import {
Card,
Expand Down
2 changes: 1 addition & 1 deletion src/app/dashboard/payments/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
import { auth } from "~/auth/dashboard-config";
import { PaymentsTable } from "~/components/dashboard/payments-table";
import { PaymentsTable } from "~/components/dashboard/tables/payments-table";
import { getPayments } from "~/db/services/payment-services";
import { isAdmin } from "~/lib/auth/check-access";

Expand Down
2 changes: 1 addition & 1 deletion src/app/dashboard/teams/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { redirect } from "next/navigation";
import { auth } from "~/auth/dashboard-config";
import { TeamsTable } from "~/components/dashboard/teams-table";
import { TeamsTable } from "~/components/dashboard/tables/teams-table";
import { fetchTeams } from "~/db/services/team-services";
import { hasPermission, isAdmin } from "~/lib/auth/check-access";

Expand Down
9 changes: 1 addition & 8 deletions src/app/events/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ export default function EventsLayout({
children: React.ReactNode;
}) {
return (
<SessionProvider basePath="/api/auth/event">
<div
className="bg-cover bg-center bg-no-repeat bg-fixed"
style={{ backgroundImage: "url('/images/underwater.webp')" }}
>
{children}
</div>
</SessionProvider>
<SessionProvider basePath="/api/auth/event">{children}</SessionProvider>
);
}
14 changes: 14 additions & 0 deletions src/app/events/login/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "Login",
description: "Sign in to your Hackfest'26 Google Account.",
};

export default function LoginLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}
21 changes: 21 additions & 0 deletions src/app/events/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { signIn } from "next-auth/react";
import { useEffect } from "react";

export default function EventsLoginPage() {
useEffect(() => {
signIn("google", { callbackUrl: "/events" });
}, []);

return (
<div className="flex min-h-screen flex-col items-center justify-center bg-black">
<div className="flex flex-col items-center gap-4 animate-pulse">
<div className="h-10 w-10 animate-spin rounded-full border-4 border-cyan-500 border-t-transparent" />
<p className="font-pirate text-2xl tracking-widest text-cyan-400">
Redirecting to Google...
</p>
</div>
</div>
);
}
5 changes: 4 additions & 1 deletion src/app/events/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Suspense } from "react";
import { auth } from "~/auth/event-config";
import Events from "~/components/events/layout";

export default async function EventsPage({
searchParams,
}: {
searchParams: Promise<{ error?: string }>;
}) {
const session = await auth();

return (
<Suspense fallback={<div>Loading...</div>}>
<Events searchParams={searchParams} />
<Events session={session} searchParams={searchParams} />
</Suspense>
);
}
1 change: 1 addition & 0 deletions src/auth/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
clientSecret: env.GITHUB_CLIENT_SECRET,
}),
],
trustHost: true,
events: {
async signIn({ user, account, profile }) {
try {
Expand Down
Loading
Loading