diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index aa6cd8a..cf291f5 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -10,7 +10,7 @@ import { Printful, Webflow, } from "@blade-and-brawn/commerce"; -import { DEFAULT_NAME, DUMMY_PASSWORD_HASH, env, log, sha256Sum } from "./util"; +import { 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"; @@ -22,6 +22,7 @@ import { StandardsService } from "./services/standards"; import { EventsService, EventStatusSchema } from "./services/events"; import { AccountsService, type AccountRole } from "./services/accounts"; import { Value } from "@sinclair/typebox/value"; +import { AssessmentsService } from "./services/assessments"; // CONSTANTS // ----------------------- @@ -37,7 +38,8 @@ const s = (() => { const Commerce = new CommerceService(); const Accounts = new AccountsService(Calculator); const Events = new EventsService(); - return { Standards, Calculator, Commerce, Accounts, Events }; + const Assessments = new AssessmentsService(); + return { Standards, Calculator, Commerce, Accounts, Events, Assessments }; })(); // QUEUES @@ -476,6 +478,36 @@ export const app = new Elysia() ) ) + // ASSESSMENTS + .group("/assessments", (app) => app + .guard({ auth: true }, (app) => app + .post("/me", async ({ body: { player, activityPerformances }, accountId }) => { + await s.Assessments.create(player, activityPerformances, accountId); + }, { + body: t.Object({ + player: PlayerSchema, + activityPerformances: t.Array(ActivityPerformanceSchema) + }) + }) + .get("/me", async ({ accountId }) => { + const assessments = await s.Assessments.list({ filter: { accountId } }); + if (!assessments) throw new NotFoundError("Assessments not found"); + return assessments; + }) + ) + .guard({ authAdmin: true }, (app) => app + .post("/", async ({ body: { player, activityPerformances, id } }) => { + await s.Assessments.create(player, activityPerformances, id); + }, { + body: t.Object({ + player: PlayerSchema, + activityPerformances: t.Array(ActivityPerformanceSchema), + id: t.Optional(t.String()), + }) + }) + ) + ) + // WEBHOOKS .post("/webhooks/printful", async ({ body, query }) => { // https://webflow.com/integrations/printful diff --git a/apps/api/src/services/assessments.ts b/apps/api/src/services/assessments.ts new file mode 100644 index 0000000..db6b218 --- /dev/null +++ b/apps/api/src/services/assessments.ts @@ -0,0 +1,42 @@ +import { Activity, type ActivityPerformance, type Player } from "@blade-and-brawn/domain"; +import { db } from "../database/db"; + +export class AssessmentsService { + async create(player: Player, activityPerformances: ActivityPerformance[], accountId?: string) { + const performanceFor = (activity: Activity) => + activityPerformances.find((p) => p.activity === activity)?.performance ?? null; + + return await db.insertInto("assessments") + .values({ + account_id: accountId ?? null, + name: player.name ?? "Anonymous", + age: player.metrics.age, + weight: player.metrics.weight, + gender: player.metrics.gender, + perf_back_squat: performanceFor(Activity.BackSquat), + perf_deadlift: performanceFor(Activity.Deadlift), + perf_bench_press: performanceFor(Activity.BenchPress), + perf_broad_jump: performanceFor(Activity.BroadJump), + perf_run: performanceFor(Activity.Run), + perf_cone_drill: performanceFor(Activity.ConeDrill), + }) + .returning("id") + .executeTakeFirstOrThrow(); + } + + async list(opt: { + filter?: { accountId?: string }, + limit?: number, + offset?: number, + } = {}) { + return await db.selectFrom("assessments") + .selectAll() + .$if(opt.filter?.accountId !== undefined, (qb) => qb + .where("account_id", "=", opt.filter!.accountId!) + ) + .orderBy("created_at", "desc") + .$if(opt.limit !== undefined, (qb) => qb.limit(opt.limit!)) + .$if(opt.offset !== undefined, (qb) => qb.offset(opt.offset!)) + .execute(); + } +}