156 lines
7.0 KiB
TypeScript
156 lines
7.0 KiB
TypeScript
import { Activity, type ActivityVerifications, type ActivityVideoUrls } from "@blade-and-brawn/domain";
|
|
import type { Static } from "@sinclair/typebox";
|
|
import { t } from "elysia";
|
|
import { db } from "../database/db";
|
|
|
|
export const VerificationStatusSchema = t.Union([
|
|
t.Literal("request"),
|
|
t.Literal("in_review"),
|
|
t.Literal("action_requested"),
|
|
t.Literal("completed"),
|
|
]);
|
|
export type VerificationStatus = Static<typeof VerificationStatusSchema>;
|
|
|
|
export class VerificationsService {
|
|
async request(assessmentId: string, accountId: string, activityVideoUrls: ActivityVideoUrls) {
|
|
// Only the owning account may request verification of their own assessment.
|
|
const assessment = await db.selectFrom("assessments")
|
|
.select("id")
|
|
.where("id", "=", assessmentId)
|
|
.where("account_id", "=", accountId)
|
|
.executeTakeFirst();
|
|
if (!assessment) return undefined;
|
|
|
|
return await db.insertInto("verifications")
|
|
.values({
|
|
assessment_id: assessmentId,
|
|
status: "request" satisfies VerificationStatus,
|
|
url_back_squat: activityVideoUrls[Activity.BackSquat] ?? null,
|
|
url_deadlift: activityVideoUrls[Activity.Deadlift] ?? null,
|
|
url_bench_press: activityVideoUrls[Activity.BenchPress] ?? null,
|
|
url_broad_jump: activityVideoUrls[Activity.BroadJump] ?? null,
|
|
url_run: activityVideoUrls[Activity.Run] ?? null,
|
|
url_cone_drill: activityVideoUrls[Activity.ConeDrill] ?? null,
|
|
})
|
|
.onConflict((oc) => oc
|
|
.column("assessment_id")
|
|
.doUpdateSet((eb) => ({
|
|
status: "request" satisfies VerificationStatus,
|
|
url_back_squat: eb.ref("excluded.url_back_squat"),
|
|
url_deadlift: eb.ref("excluded.url_deadlift"),
|
|
url_bench_press: eb.ref("excluded.url_bench_press"),
|
|
url_broad_jump: eb.ref("excluded.url_broad_jump"),
|
|
url_run: eb.ref("excluded.url_run"),
|
|
url_cone_drill: eb.ref("excluded.url_cone_drill"),
|
|
// Re-requesting means new videos are under review, so the
|
|
// previous cycle's per-activity judgments and completion
|
|
// info no longer apply.
|
|
verf_back_squat: null,
|
|
verf_deadlift: null,
|
|
verf_bench_press: null,
|
|
verf_broad_jump: null,
|
|
verf_run: null,
|
|
verf_cone_drill: null,
|
|
completed_at: null,
|
|
completed_by: null,
|
|
}))
|
|
)
|
|
.returning("id")
|
|
.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 VerificationsService.baseQuery()
|
|
.$if(opt.filter?.status !== undefined, (qb) => qb
|
|
.where("verifications.status", "=", opt.filter!.status!)
|
|
)
|
|
.orderBy("verifications.created_at", "desc")
|
|
.$if(opt.limit !== undefined, (qb) => qb.limit(opt.limit!))
|
|
.$if(opt.offset !== undefined, (qb) => qb.offset(opt.offset!))
|
|
.execute();
|
|
}
|
|
|
|
async requestAction(id: string, reviewerNotes: string | null, activityVerifications: ActivityVerifications) {
|
|
const result = await db.updateTable("verifications")
|
|
.where("id", "=", id)
|
|
.set({
|
|
status: "action_requested" satisfies VerificationStatus,
|
|
reviewer_notes: reviewerNotes,
|
|
verf_back_squat: activityVerifications[Activity.BackSquat] ?? null,
|
|
verf_deadlift: activityVerifications[Activity.Deadlift] ?? null,
|
|
verf_bench_press: activityVerifications[Activity.BenchPress] ?? null,
|
|
verf_broad_jump: activityVerifications[Activity.BroadJump] ?? null,
|
|
verf_run: activityVerifications[Activity.Run] ?? null,
|
|
verf_cone_drill: activityVerifications[Activity.ConeDrill] ?? null,
|
|
})
|
|
.executeTakeFirst();
|
|
return result.numUpdatedRows > 0n;
|
|
}
|
|
|
|
async complete(id: string, completedBy: string, activityVerifications: ActivityVerifications) {
|
|
const result = await db.updateTable("verifications")
|
|
.where("id", "=", id)
|
|
.set({
|
|
status: "completed" satisfies VerificationStatus,
|
|
completed_at: new Date(),
|
|
completed_by: completedBy,
|
|
verf_back_squat: activityVerifications[Activity.BackSquat] ?? null,
|
|
verf_deadlift: activityVerifications[Activity.Deadlift] ?? null,
|
|
verf_bench_press: activityVerifications[Activity.BenchPress] ?? null,
|
|
verf_broad_jump: activityVerifications[Activity.BroadJump] ?? null,
|
|
verf_run: activityVerifications[Activity.Run] ?? null,
|
|
verf_cone_drill: activityVerifications[Activity.ConeDrill] ?? null,
|
|
})
|
|
.executeTakeFirst();
|
|
return result.numUpdatedRows > 0n;
|
|
}
|
|
}
|