diff --git a/apps/api/src/database/migrations/2026-09-19.ts b/apps/api/src/database/migrations/2026-09-19.ts index a251af5..5d9a0be 100644 --- a/apps/api/src/database/migrations/2026-09-19.ts +++ b/apps/api/src/database/migrations/2026-09-19.ts @@ -8,6 +8,7 @@ export async function up(db: Kysely): Promise { .$call(addDefaultColumns) .addColumn("assessment_id", "bigint", (cb) => cb.notNull().unique()) .addColumn("status", "text", (cb) => cb.notNull().defaultTo("draft")) // draft | submitted | in_review | action_requested | completed + .addColumn("submitted_at", "timestamptz") .addColumn("completed_at", "timestamptz") .addColumn("completed_by", "uuid") .addColumn("media_key_back_squat", "text") diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 70d6009..95aa1c8 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -12,7 +12,7 @@ import { Printful, Webflow, } from "@blade-and-brawn/commerce"; -import { BigIntIdSchema, DEFAULT_NAME, DUMMY_PASSWORD_HASH, env, log } from "./util"; +import { AccountIdSchema, BigIntIdSchema, DEFAULT_NAME, DUMMY_PASSWORD_HASH, env, log } from "./util"; import serverTiming from "@elysia/server-timing"; import jwt from "@elysia/jwt"; import { CommerceService, WOrderStatusSchema } from "./services/commerce"; @@ -471,7 +471,7 @@ export const app = new Elysia() if (!stats) return status(404, { error: "Account stats not found" }); return stats; }, { - params: t.Object({ id: t.String() }) + params: t.Object({ id: AccountIdSchema }) }) .guard({ authAdmin: true }, (app) => app .get("/:id", async ({ params: { id } }) => { @@ -479,7 +479,7 @@ export const app = new Elysia() if (!account) return status(404, { error: "Account not found" }); return account; }, { - params: t.Object({ id: t.String() }) + params: t.Object({ id: AccountIdSchema }) }) ) .group("/me", { auth: true }, (app) => app diff --git a/apps/api/src/services/verifications.ts b/apps/api/src/services/verifications.ts index 9363c47..898b554 100644 --- a/apps/api/src/services/verifications.ts +++ b/apps/api/src/services/verifications.ts @@ -62,6 +62,7 @@ export class VerificationsService { .where("status", "in", ALLOWED_FROM.update) .set({ status: "draft" satisfies VerificationStatus, + submitted_at: null, assessment_id: assessmentId, media_key_back_squat: activityMediaKeys[Activity.BackSquat], media_key_deadlift: activityMediaKeys[Activity.Deadlift], @@ -96,6 +97,7 @@ export class VerificationsService { .$if(opt.filter?.status !== undefined, (qb) => qb .where("verifications.status", "=", opt.filter!.status!) ) + .orderBy("verifications.submitted_at", (ob) => ob.desc().nullsLast()) .orderBy("verifications.created_at", "desc") .$if(opt.limit !== undefined, (qb) => qb.limit(opt.limit!)) .$if(opt.offset !== undefined, (qb) => qb.offset(opt.offset!)) @@ -109,7 +111,7 @@ export class VerificationsService { .where((eb) => eb.and( REQUIRED_MEDIA_ACTIVITIES.map((activity) => eb(MEDIA_KEY_COLUMN[activity], "is not", null)) )) - .set({ status: "submitted" satisfies VerificationStatus }) + .set({ status: "submitted" satisfies VerificationStatus, submitted_at: new Date() }) .returning(["id", "status"]) .executeTakeFirst(); } @@ -159,6 +161,7 @@ export class VerificationsService { "verifications.assessment_id", "verifications.status", "verifications.created_at", + "verifications.submitted_at", "verifications.completed_at", "verifications.completed_by", "verifications.reviewer_notes", diff --git a/apps/api/src/util.ts b/apps/api/src/util.ts index 5118981..1ecaeca 100644 --- a/apps/api/src/util.ts +++ b/apps/api/src/util.ts @@ -39,6 +39,7 @@ export const DEFAULT_NAME = "Default"; export const DUMMY_PASSWORD_HASH = await Bun.password.hash("Dummy"); export const BigIntIdSchema = t.String({ pattern: "^\\d+$" }); +export const AccountIdSchema = t.String({ pattern: "^(@.+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$" }); function requireEnv(key: string): string { const val = Bun.env[key]; diff --git a/apps/portal/src/routes/(app)/verification/requests/+page.svelte b/apps/portal/src/routes/(app)/verification/requests/+page.svelte index d6a4588..3f2023a 100644 --- a/apps/portal/src/routes/(app)/verification/requests/+page.svelte +++ b/apps/portal/src/routes/(app)/verification/requests/+page.svelte @@ -137,7 +137,7 @@ Player Status Videos - Requested + Submitted @@ -151,7 +151,7 @@ {videoCount(row)} / {Object.values(Activity).length} - {formatDate(row.created_at)} + {formatDate(row.submitted_at)}