Add a verification interface for review
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { Activity, Gender, inToCm, lbToKg, minToMs, secToMs } from "@blade-and-brawn/domain";
|
||||
import { db } from "../database/db";
|
||||
import { env, log } from "../util";
|
||||
import type { AccountRole } from "../services/accounts";
|
||||
import { VerificationsService } from "../services/verifications";
|
||||
|
||||
const TEST_DISCORD_ID = "test-verification-seed";
|
||||
const TEST_ASSESSMENT_NAME = "Test Verification Player";
|
||||
|
||||
async function seedTestAccount(): Promise<string> {
|
||||
const existing = await db.selectFrom("accounts")
|
||||
.select("id")
|
||||
.where("discord_id", "=", TEST_DISCORD_ID)
|
||||
.executeTakeFirst();
|
||||
if (existing) {
|
||||
log.info({ id: existing.id }, "test account already seeded, reusing");
|
||||
return existing.id;
|
||||
}
|
||||
|
||||
const account = await db.insertInto("accounts")
|
||||
.values({
|
||||
discord_id: TEST_DISCORD_ID,
|
||||
name: "Test Player",
|
||||
role: "user" satisfies AccountRole,
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
log.info({ id: account.id }, "seeded test account");
|
||||
return account.id;
|
||||
}
|
||||
|
||||
async function seedTestAssessment(accountId: string): Promise<string> {
|
||||
const existing = await db.selectFrom("assessments")
|
||||
.select("id")
|
||||
.where("account_id", "=", accountId)
|
||||
.where("name", "=", TEST_ASSESSMENT_NAME)
|
||||
.executeTakeFirst();
|
||||
if (existing) {
|
||||
log.info({ id: existing.id }, "test assessment already seeded, reusing");
|
||||
return existing.id;
|
||||
}
|
||||
|
||||
const result = await db.insertInto("assessments")
|
||||
.values({
|
||||
account_id: accountId,
|
||||
name: TEST_ASSESSMENT_NAME,
|
||||
gender: Gender.Male,
|
||||
age: 22,
|
||||
weight: lbToKg(180),
|
||||
perf_back_squat: lbToKg(225),
|
||||
perf_deadlift: lbToKg(315),
|
||||
perf_bench_press: lbToKg(185),
|
||||
perf_broad_jump: inToCm(96),
|
||||
perf_run: minToMs(7) + secToMs(30),
|
||||
perf_cone_drill: secToMs(7),
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
log.info({ id: result.id }, "seeded test assessment");
|
||||
return result.id;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const answer = prompt(`Seed a test verification request in the database (${env.DATABASE_URL})? (y/N)`);
|
||||
if (answer?.trim().toLowerCase() !== "y") {
|
||||
log.info("Aborted");
|
||||
await db.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const accountId = await seedTestAccount();
|
||||
const assessmentId = await seedTestAssessment(accountId);
|
||||
|
||||
const verifications = new VerificationsService();
|
||||
const verification = await verifications.request(assessmentId, accountId, {
|
||||
[Activity.BackSquat]: "https://example.com/videos/back-squat.mp4",
|
||||
[Activity.Deadlift]: "https://example.com/videos/deadlift.mp4",
|
||||
[Activity.BenchPress]: "https://example.com/videos/bench-press.mp4",
|
||||
[Activity.Run]: "https://example.com/videos/run.mp4",
|
||||
[Activity.BroadJump]: "https://example.com/videos/broad-jump.mp4",
|
||||
[Activity.ConeDrill]: "https://example.com/videos/cone-drill.mp4",
|
||||
});
|
||||
if (!verification) throw new Error("Failed to seed verification request — assessment ownership check failed");
|
||||
|
||||
log.info({ id: verification.id, assessmentId, accountId }, "seeded test verification request");
|
||||
}
|
||||
catch (err) {
|
||||
log.error({ err }, "seed failed");
|
||||
process.exit(1);
|
||||
}
|
||||
finally {
|
||||
await db.destroy();
|
||||
}
|
||||
})();
|
||||
@@ -555,6 +555,13 @@ export const app = new Elysia()
|
||||
offset: t.Optional(t.Numeric()),
|
||||
})
|
||||
})
|
||||
.get("/:id", async ({ params: { id } }) => {
|
||||
const verification = await s.Verifications.get(id);
|
||||
if (!verification) throw new NotFoundError("Verification not found");
|
||||
return verification;
|
||||
}, {
|
||||
params: t.Object({ id: t.String() })
|
||||
})
|
||||
.post("/:id/request-action", async ({ params: { id }, body: { reviewerNotes, activityVerifications } }) => {
|
||||
const updated = await s.Verifications.requestAction(id, reviewerNotes ?? null, activityVerifications);
|
||||
if (!updated) throw new NotFoundError("Verification not found");
|
||||
@@ -684,3 +691,4 @@ app.listen(3000, async () => {
|
||||
|
||||
export type API = typeof app
|
||||
export { type EventStatus } from "./services/events";
|
||||
export { type VerificationStatus } from "./services/verifications";
|
||||
|
||||
@@ -59,17 +59,60 @@ export class VerificationsService {
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
// Joined with the assessment being verified, since reviewing a request
|
||||
// requires seeing the player's claimed performance alongside the videos.
|
||||
private static baseQuery() {
|
||||
return db.selectFrom("verifications")
|
||||
.innerJoin("assessments", "assessments.id", "verifications.assessment_id")
|
||||
.select([
|
||||
"verifications.id",
|
||||
"verifications.assessment_id",
|
||||
"verifications.status",
|
||||
"verifications.created_at",
|
||||
"verifications.completed_at",
|
||||
"verifications.completed_by",
|
||||
"verifications.reviewer_notes",
|
||||
"verifications.url_back_squat",
|
||||
"verifications.url_deadlift",
|
||||
"verifications.url_bench_press",
|
||||
"verifications.url_broad_jump",
|
||||
"verifications.url_run",
|
||||
"verifications.url_cone_drill",
|
||||
"verifications.verf_back_squat",
|
||||
"verifications.verf_deadlift",
|
||||
"verifications.verf_bench_press",
|
||||
"verifications.verf_broad_jump",
|
||||
"verifications.verf_run",
|
||||
"verifications.verf_cone_drill",
|
||||
"assessments.name",
|
||||
"assessments.gender",
|
||||
"assessments.age",
|
||||
"assessments.weight",
|
||||
"assessments.perf_back_squat",
|
||||
"assessments.perf_deadlift",
|
||||
"assessments.perf_bench_press",
|
||||
"assessments.perf_broad_jump",
|
||||
"assessments.perf_run",
|
||||
"assessments.perf_cone_drill",
|
||||
]);
|
||||
}
|
||||
|
||||
async get(id: string) {
|
||||
return await VerificationsService.baseQuery()
|
||||
.where("verifications.id", "=", id)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async list(opt: {
|
||||
filter?: { status?: VerificationStatus },
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
} = {}) {
|
||||
return await db.selectFrom("verifications")
|
||||
.selectAll()
|
||||
return await VerificationsService.baseQuery()
|
||||
.$if(opt.filter?.status !== undefined, (qb) => qb
|
||||
.where("status", "=", opt.filter!.status!)
|
||||
.where("verifications.status", "=", opt.filter!.status!)
|
||||
)
|
||||
.orderBy("created_at", "desc")
|
||||
.orderBy("verifications.created_at", "desc")
|
||||
.$if(opt.limit !== undefined, (qb) => qb.limit(opt.limit!))
|
||||
.$if(opt.offset !== undefined, (qb) => qb.offset(opt.offset!))
|
||||
.execute();
|
||||
|
||||
Reference in New Issue
Block a user