Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
813ee24
feat: create badge's schema
TheSKBroook Aug 2, 2026
8a67c4b
feat: export the badge schema
TheSKBroook Aug 2, 2026
f7807fe
feat: add create, update, delete and get function for badge
TheSKBroook Aug 3, 2026
5c888ba
feat: remove await for getSupabase error and add update badge function
TheSKBroook Aug 3, 2026
e90748b
feat: add more constrains on the badge data model
TheSKBroook Aug 3, 2026
2fe61e0
feat: update the functions to project style
TheSKBroook Aug 3, 2026
f075c12
feat: create member badge data model
TheSKBroook Aug 3, 2026
1aa70f7
feat: create member badge service
TheSKBroook Aug 3, 2026
e6eb412
fix: adapt partial() function, default re-write the original value
TheSKBroook Aug 3, 2026
93fecb8
feat: create uniting for badges and member badges service
TheSKBroook Aug 3, 2026
14bf665
Merge branch 'main' of https://github.com/UoaWDCC/web3 into badge-mod…
TheSKBroook Sep 10, 2026
64ccee2
fix: change member badge linking registration id instead of public pr…
TheSKBroook Sep 18, 2026
883c2ff
fix: add registration id in the public profile field
TheSKBroook Sep 18, 2026
2a64edf
feat: add badge-card component to profile page, and use member-badge-…
TheSKBroook Sep 23, 2026
c674fe7
fix: delete attendanceid in member-badge schema as the database does …
TheSKBroook Sep 23, 2026
57eb0d4
Merge branch 'badge-model-backend' of https://github.com/UoaWDCC/web3…
TheSKBroook Sep 23, 2026
c263ab9
fix: place the badges section align on the top
TheSKBroook Sep 23, 2026
7665781
feat: align style more to our design team.
TheSKBroook Sep 23, 2026
b56d7db
create function to upload badge image and testing cases
TheSKBroook Sep 25, 2026
f5fce5f
route for creating badge and badge images
TheSKBroook Sep 25, 2026
28c1ca9
feat: change the database, so that member badge and badge are cascade…
TheSKBroook Sep 30, 2026
25c83e8
feat: two helper function to get and delete badge image through its p…
TheSKBroook Sep 30, 2026
25eae36
feat: add the backend route and service event with badge save and del…
TheSKBroook Oct 1, 2026
bf2cbbb
feat: frontend design to include the add badge in creation of event.
TheSKBroook Oct 1, 2026
b4434dd
feat: load up badge when the editing the event
TheSKBroook Oct 1, 2026
59fa64d
feat: delete an event's image file when the vent is deleted
TheSKBroook Oct 1, 2026
8117ee9
Merge the badge related branch to here as well.
TheSKBroook Oct 1, 2026
32ac76c
Merge branch 'feature/badge-profile-frontend' into feature/badge-admi…
TheSKBroook Oct 7, 2026
600dfb0
Merge branch 'feature/badge-admin-backend' into feature/badge-admin-f…
TheSKBroook Oct 7, 2026
ea87f77
feat: feat: delete an event's badge with the event, and warn the admi…
TheSKBroook Oct 7, 2026
6dbd98b
core(badge): merge main to this branch and solve some conflict error
TheSKBroook Oct 7, 2026
7f18fec
fix: fix mismatching in field for member badge in the associate servi…
TheSKBroook Oct 7, 2026
f5c6608
fix: prevent finding registration id for searching for public profile
TheSKBroook Oct 8, 2026
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
345 changes: 245 additions & 100 deletions src/app/admin/events/page.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { NextRequest, NextResponse } from "next/server";
import { EventService } from "@/services/event-service";
import { isAllowedAdminAddress } from "@/lib/admin-auth";
import { verifyMessage } from "viem";
import { isAllowedAdminAddress } from "@/lib/admin-auth";
import BadgesService from "@/services/badges/badges-service";
import { getSupabaseAdmin } from "@/services/supabase-admin";

async function verifyAdminAuth(req: NextRequest) {
const address = req.headers.get("x-admin-address")?.toLowerCase();
Expand All @@ -26,30 +27,44 @@ async function verifyAdminAuth(req: NextRequest) {
}
}

// POST - upload a badge image, returning the public URL to save as imageurl
export async function POST(req: NextRequest) {
const isAuth = await verifyAdminAuth(req);
if (!isAuth)
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

try {
const formData = await req.formData();
const file = formData.get("image") as File | null;
const file = formData.get("image");

if (!file) {
if (!(file instanceof File)) {
return NextResponse.json(
{ error: "Missing image file" },
{ status: 400 },
);
}

const uploaded = await EventService.uploadEventImage(file);
// Unlike the events bucket, the badges bucket has no MIME restriction of
// its own, so this is the only thing keeping non-images out of it.
if (!file.type.startsWith("image/")) {
return NextResponse.json(
{ error: "Badge image must be an image file" },
{ status: 400 },
);
}

const uploaded = await new BadgesService(
getSupabaseAdmin(),
).uploadBadgeImage(file);

return NextResponse.json({
event_path: uploaded.imagePath,
event_url: uploaded.imageUrl,
image_path: uploaded.imagePath,
imageurl: uploaded.imageUrl,
});
} catch (err: any) {
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
return NextResponse.json(
{ error: err.message || "Image upload failed" },
{ error: message || "Image upload failed" },
{ status: 500 },
);
}
Expand Down
70 changes: 70 additions & 0 deletions src/app/api/admin/badges/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { NextRequest, NextResponse } from "next/server";
import { verifyMessage } from "viem";
import { z } from "zod";
import { isAllowedAdminAddress } from "@/lib/admin-auth";
import BadgesService from "@/services/badges/badges-service";
import { getSupabaseAdmin } from "@/services/supabase-admin";

// Postgres errors caused by the request's data rather than the server:
// foreign key (e.g. an eventid with no matching event) and check constraints.
const CLIENT_ERROR_CODES = new Set(["23503", "23514"]);

async function verifyAdminAuth(req: NextRequest) {
const address = req.headers.get("x-admin-address")?.toLowerCase();
const signature = req.headers.get("x-admin-signature");
const timestamp = req.headers.get("x-admin-timestamp");

if (!address || !signature || !timestamp || !isAllowedAdminAddress(address))
return false;

const now = Date.now();
if (now - parseInt(timestamp) > 5 * 60 * 1000) return false;

try {
const valid = await verifyMessage({
address: address as `0x${string}`,
message: `Admin Auth ${timestamp}`,
signature: signature as `0x${string}`,
});
return valid;
} catch {
return false;
}
}

// POST - create a badge. The body is validated by badgeInsertSchema, which
// also enforces that an EVENT badge carries an eventid.
export async function POST(req: NextRequest) {
const isAuth = await verifyAdminAuth(req);
if (!isAuth)
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

try {
const body = await req.json().catch(() => null);
if (!body || typeof body !== "object") {
return NextResponse.json(
{ error: "Request body must be a JSON object" },
{ status: 400 },
);
}

const badge = await new BadgesService(getSupabaseAdmin()).createBadge(body);
return NextResponse.json({ badge });
} catch (err: unknown) {
if (err instanceof z.ZodError) {
return NextResponse.json(
{ error: err.issues.map((issue) => issue.message).join("; ") },
{ status: 400 },
);
}

const code = (err as { code?: string } | null)?.code;
const message =
(err as { message?: string } | null)?.message || "Failed to create badge";

return NextResponse.json(
{ error: message },
{ status: code && CLIENT_ERROR_CODES.has(code) ? 400 : 500 },
);
}
}
Loading
Loading