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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ NEXT_PUBLIC_CHATBOT_NAME=FEDI
CERT_ORG=
NEXT_PUBLIC_CERT_ORG=

# When the current-year batch registration restriction went live. Registrations
# for emails starting with the year's two digits (e.g. 26…@ in 2026) made before
# this timestamp do not receive attendance QR codes.
BATCH_REGISTRATION_RESTRICTION_ENABLED_AT=2026-08-21T00:00:00.000Z

# --- Server -----------------------------------------------------------------
# Port the Next.js server listens on, for both `npm run dev` and `npm start`.
#
Expand Down
27 changes: 27 additions & 0 deletions app/api/form/attendance-events/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
getAttendanceStats,
listAttendanceEvents,
} from "@/lib/services/attendance";
import { expressError, handle, json } from "@/lib/api/express";
import { getCurrentUser, isAdmin } from "@/lib/auth/access";

/**
* GET /api/form/attendance-events
*
* Lightweight event list for the attendance scanner — id + info only, no
* sections JSON. Avoids loading every form's full payload on door duty.
*/
export async function GET() {
return handle(async () => {
const user = await getCurrentUser();
if (!user) return expressError(401, "Token is required");
if (!isAdmin(user)) return expressError(403, "Unauthorized");

const events = await listAttendanceEvents();
return json({
success: true,
message: "Events fetched successfully",
events,
});
});
}
24 changes: 24 additions & 0 deletions app/api/form/attendance-stats/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getAttendanceStats } from "@/lib/services/attendance";
import { expressError, handle, json } from "@/lib/api/express";
import { getCurrentUser, isAdmin } from "@/lib/auth/access";

/**
* GET /api/form/attendance-stats/:id
*
* Live present / registered counts while scanning at the door.
*/
export async function GET(
_request: Request,
ctx: RouteContext<"/api/form/attendance-stats/[id]">,
) {
return handle(async () => {
const user = await getCurrentUser();
if (!user) return expressError(401, "Token is required");
if (!isAdmin(user)) return expressError(403, "Unauthorized");

const { id } = await ctx.params;
const stats = await getAttendanceStats(id);

return json({ success: true, ...stats });
});
}
8 changes: 8 additions & 0 deletions app/api/form/register/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { sendMail } from "@/lib/email/mailer";
import { registrationEmail } from "@/lib/email/templates";
import { uploadImage } from "@/lib/services/upload";
import type { EventInfo } from "@/lib/types/event";
import {
batchRegistrationErrorMessage,
isBatchRegistrationBlocked,
} from "@/lib/batch-restriction";

/**
* POST /api/form/register
Expand Down Expand Up @@ -121,6 +125,10 @@ export async function POST(request: Request) {
);
}

if (isBatchRegistrationBlocked(user.email)) {
return expressError(400, batchRegistrationErrorMessage());
}

if (
tracker?.regUserEmails.includes(user.email) ||
user.regForm.includes(formId)
Expand Down
Loading
Loading