This commit is contained in:
Dominic Ferrando
2026-09-26 18:27:33 -04:00
parent b66980c0fb
commit 930bd61e62
4 changed files with 87 additions and 81 deletions
+77 -75
View File
@@ -5,19 +5,20 @@ import {
PlayerSchema, PlayerSchema,
} from "@blade-and-brawn/domain" } from "@blade-and-brawn/domain"
import { cors } from "@elysiajs/cors"; import { cors } from "@elysiajs/cors";
import { Elysia, NotFoundError, redirect, status, t } from "elysia"; import { Elysia, redirect, status, t } from "elysia";
import { import {
PrintfulError, PrintfulError,
WebflowError, WebflowError,
Printful, Printful,
Webflow, Webflow,
} from "@blade-and-brawn/commerce"; } from "@blade-and-brawn/commerce";
import { DEFAULT_NAME, DUMMY_PASSWORD_HASH, env, log } from "./util"; import { BigIntIdSchema, DEFAULT_NAME, DUMMY_PASSWORD_HASH, env, log } from "./util";
import serverTiming from "@elysia/server-timing"; import serverTiming from "@elysia/server-timing";
import jwt from "@elysia/jwt"; import jwt from "@elysia/jwt";
import { CommerceService, WOrderStatusSchema } from "./services/commerce"; import { CommerceService, WOrderStatusSchema } from "./services/commerce";
import cluster from "node:cluster"; import cluster from "node:cluster";
import { randomUUIDv7, sleep } from "bun"; import { randomUUIDv7, sleep } from "bun";
import { DatabaseError } from "pg";
import { CalculatorService, CalculatorUnavailableError } from "./services/calculator"; import { CalculatorService, CalculatorUnavailableError } from "./services/calculator";
import { StandardsParamsSchema } from "@blade-and-brawn/calculator"; import { StandardsParamsSchema } from "@blade-and-brawn/calculator";
import { StandardsService } from "./services/standards"; import { StandardsService } from "./services/standards";
@@ -62,14 +63,14 @@ const authPlugin = new Elysia({ name: "auth" })
authAdmin: { authAdmin: {
async resolve({ jwt, cookie: { auth } }) { async resolve({ jwt, cookie: { auth } }) {
const token = auth.value && await jwt.verify(auth.value); const token = auth.value && await jwt.verify(auth.value);
if (!token || token.role !== "admin" || !token.accountId || !token.sessionId) throw status(401, "Unauthorized"); if (!token || token.role !== "admin" || !token.accountId || !token.sessionId) return status(401, { error: "Unauthorized" });
return { role: token.role.toString(), sessionId: token.sessionId.toString(), accountId: token.accountId.toString() }; return { role: token.role.toString(), sessionId: token.sessionId.toString(), accountId: token.accountId.toString() };
} }
}, },
auth: { auth: {
async resolve({ jwt, cookie: { auth } }) { async resolve({ jwt, cookie: { auth } }) {
const token = auth.value && await jwt.verify(auth.value); const token = auth.value && await jwt.verify(auth.value);
if (!token || !token.role || !token.accountId || !token.sessionId) throw status(401, "Unauthorized"); if (!token || !token.role || !token.accountId || !token.sessionId) return status(401, { error: "Unauthorized" });
return { role: token.role.toString(), sessionId: token.sessionId.toString(), accountId: token.accountId.toString() }; return { role: token.role.toString(), sessionId: token.sessionId.toString(), accountId: token.accountId.toString() };
} }
} }
@@ -106,6 +107,11 @@ export const app = new Elysia()
}) })
.onError(({ code, error }) => { .onError(({ code, error }) => {
// Database Errors
if (error instanceof DatabaseError && error.code === "23505")
return status(409, { error: "Conflicts with an existing record" });
// Custom Errors
switch (code) { switch (code) {
case "PrintfulError": case "PrintfulError":
case "WebflowError": case "WebflowError":
@@ -126,16 +132,18 @@ export const app = new Elysia()
} }
}) })
.onAfterResponse(({ request, status, path }) => { .onAfterResponse(({ request, set, path, responseValue }) => {
if (env.NODE_ENV === "development") { if (env.NODE_ENV === "development") {
const skip: Record<string, string[]> = { "/commerce/products/sync/": ["GET"] }; const skip: Record<string, string[]> = { "/commerce/products/sync/": ["GET"] };
if (skip[path]?.includes(request.method)) return; if (skip[path]?.includes(request.method)) return;
} }
log.info({ const failed = Number(set.status) >= 400;
log[failed ? "warn" : "info"]({
method: request.method, method: request.method,
path, path,
status status: set.status,
...(failed && { response: responseValue }),
}, "request"); }, "request");
}) })
@@ -146,7 +154,7 @@ export const app = new Elysia()
.post("/auth/login", async ({ jwt, body: { password, email }, cookie: { auth } }) => { .post("/auth/login", async ({ jwt, body: { password, email }, cookie: { auth } }) => {
const account = await s.Accounts.getByEmail(email); const account = await s.Accounts.getByEmail(email);
const password_match = await Bun.password.verify(password, account?.password_hash ?? DUMMY_PASSWORD_HASH); const password_match = await Bun.password.verify(password, account?.password_hash ?? DUMMY_PASSWORD_HASH);
if (!account || !password_match) throw status(401, "Invalid credentials"); if (!account || !password_match) return status(401, { error: "Invalid credentials" });
auth.set({ auth.set({
value: await jwt.sign({ role: account.role, sessionId: randomUUIDv7(), accountId: account.id, exp: JWT_EXP }), value: await jwt.sign({ role: account.role, sessionId: randomUUIDv7(), accountId: account.id, exp: JWT_EXP }),
@@ -184,10 +192,10 @@ export const app = new Elysia()
return redirect(url.toString(), 302); return redirect(url.toString(), 302);
}) })
.get("/callback", async ({ query, cookie: { authDiscord, auth }, jwt }) => { .get("/callback", async ({ query, cookie: { authDiscord, auth }, jwt }) => {
if (query.error) throw status(400, { error: query.error_description ? `${query.error}: ${query.error_description}` : query.error }); if (query.error) return status(400, { error: query.error_description ? `${query.error}: ${query.error_description}` : query.error });
if (!query.state) throw status(400, "No Discord OAuth2 state query parameter provided"); if (!query.state) return status(400, { error: "No Discord OAuth2 state query parameter provided" });
if (query.state !== authDiscord.value) throw status(400, "Invalid Discord OAuth state"); if (query.state !== authDiscord.value) return status(400, { error: "Invalid Discord OAuth state" });
const tokenRes = await fetch("https://discord.com/api/oauth2/token", { const tokenRes = await fetch("https://discord.com/api/oauth2/token", {
method: "POST", method: "POST",
@@ -204,7 +212,7 @@ export const app = new Elysia()
}); });
if (!tokenRes.ok) { if (!tokenRes.ok) {
const errorBody = await tokenRes.json().catch(() => null); const errorBody = await tokenRes.json().catch(() => null);
throw status(502, { error: errorBody ?? "Discord token exchange failed" }); return status(502, { error: errorBody ?? "Discord token exchange failed" });
} }
const tokenResPayload = await tokenRes.json(); const tokenResPayload = await tokenRes.json();
@@ -215,7 +223,7 @@ export const app = new Elysia()
}); });
if (!identityRes.ok) { if (!identityRes.ok) {
const errorBody = await identityRes.json().catch(() => null); const errorBody = await identityRes.json().catch(() => null);
throw status(502, { error: errorBody ?? "Failed to fetch Discord identity" }); return status(502, { error: errorBody ?? "Failed to fetch Discord identity" });
} }
const identityResPayload = await identityRes.json(); const identityResPayload = await identityRes.json();
@@ -274,7 +282,7 @@ export const app = new Elysia()
.post("/standards/config/switch", async ({ body: { standardsConfigId } }) => { .post("/standards/config/switch", async ({ body: { standardsConfigId } }) => {
await s.Calculator.Standards.Config.switch(standardsConfigId); await s.Calculator.Standards.Config.switch(standardsConfigId);
}, { }, {
body: t.Object({ standardsConfigId: t.String() }) body: t.Object({ standardsConfigId: BigIntIdSchema })
}) })
) )
) )
@@ -282,7 +290,7 @@ export const app = new Elysia()
.post("/configs", async ({ body: { name, datasetId, params } }) => { .post("/configs", async ({ body: { name, datasetId, params } }) => {
return await s.Standards.Configs.create(name, datasetId, params); return await s.Standards.Configs.create(name, datasetId, params);
}, { }, {
body: t.Object({ name: t.String(), datasetId: t.String(), params: StandardsParamsSchema }) body: t.Object({ name: t.String(), datasetId: BigIntIdSchema, params: StandardsParamsSchema })
}) })
.get("/configs", async () => { .get("/configs", async () => {
return await s.Standards.Configs.list(); return await s.Standards.Configs.list();
@@ -290,29 +298,29 @@ export const app = new Elysia()
.get("/configs/:id", async ({ params: { id } }) => { .get("/configs/:id", async ({ params: { id } }) => {
return await s.Standards.Configs.get(id); return await s.Standards.Configs.get(id);
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
.put("/configs/:id", async ({ params: { id }, body: { name, datasetId, params: parameters } }) => { .put("/configs/:id", async ({ params: { id }, body: { name, datasetId, params: parameters } }) => {
await s.Standards.Configs.update(id, name, datasetId, parameters); await s.Standards.Configs.update(id, name, datasetId, parameters);
}, { }, {
params: t.Object({ id: t.String() }), params: t.Object({ id: BigIntIdSchema }),
body: t.Object({ name: t.String(), datasetId: t.String(), params: StandardsParamsSchema }) body: t.Object({ name: t.String(), datasetId: BigIntIdSchema, params: StandardsParamsSchema })
}) })
.delete("/configs/:id", async ({ params: { id } }) => { .delete("/configs/:id", async ({ params: { id } }) => {
await s.Standards.Configs.delete(id); await s.Standards.Configs.delete(id);
}, { params: t.Object({ id: t.String() }) }) }, { params: t.Object({ id: BigIntIdSchema }) })
.get("/datasets", async () => { .get("/datasets", async () => {
return await s.Standards.Datasets.list(); return await s.Standards.Datasets.list();
}) })
.get("/datasets/:id", async ({ params: { id } }) => { .get("/datasets/:id", async ({ params: { id } }) => {
return await s.Standards.Datasets.get(id); return await s.Standards.Datasets.get(id);
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
.patch("/datasets/:id", async ({ params: { id }, body: { name } }) => { .patch("/datasets/:id", async ({ params: { id }, body: { name } }) => {
await s.Standards.Datasets.update(id, name); await s.Standards.Datasets.update(id, name);
}, { }, {
params: t.Object({ id: t.String() }), params: t.Object({ id: BigIntIdSchema }),
body: t.Object({ name: t.String() }) body: t.Object({ name: t.String() })
}) })
) )
@@ -343,7 +351,7 @@ export const app = new Elysia()
// Sync status // Sync status
.get("/", async ({ sessionId }) => { .get("/", async ({ sessionId }) => {
const latestSyncState = await s.Commerce.Apparel.Syncs.getLatestSyncState(sessionId); const latestSyncState = await s.Commerce.Apparel.Syncs.getLatestSyncState(sessionId);
if (!latestSyncState) throw new NotFoundError("No product sync found for the provided session"); if (!latestSyncState) return status(404, { error: "No product sync found for the provided session" });
return latestSyncState; return latestSyncState;
}) })
// Run sync // Run sync
@@ -362,10 +370,10 @@ export const app = new Elysia()
) )
.get("/:pProductId", async ({ params: { pProductId } }) => { .get("/:pProductId", async ({ params: { pProductId } }) => {
const pProduct = await s.Commerce.Printful.Products.get(pProductId); const pProduct = await s.Commerce.Printful.Products.get(pProductId);
if (!pProduct) throw new NotFoundError("Missing printful product"); if (!pProduct) return status(404, { error: "Missing printful product" });
const wProductId = pProduct.sync_product.external_id.split("-")[0]; const wProductId = pProduct.sync_product.external_id.split("-")[0];
if (!wProductId) throw new NotFoundError("Missing webflow product ID"); if (!wProductId) return status(404, { error: "Missing webflow product ID" });
const wProduct = await s.Commerce.Webflow.Products.get(wProductId); const wProduct = await s.Commerce.Webflow.Products.get(wProductId);
@@ -398,7 +406,7 @@ export const app = new Elysia()
}) })
.get("/:wOrderId", async ({ params: { wOrderId } }) => { .get("/:wOrderId", async ({ params: { wOrderId } }) => {
const wOrder = await s.Commerce.Webflow.Orders.get(wOrderId); const wOrder = await s.Commerce.Webflow.Orders.get(wOrderId);
if (!wOrder) throw new NotFoundError("Missing webflow order"); if (!wOrder) return status(404, { error: "Missing webflow order" });
const pOrder = await s.Commerce.Printful.Orders.get(`@${wOrder.orderId}`); const pOrder = await s.Commerce.Printful.Orders.get(`@${wOrder.orderId}`);
@@ -406,10 +414,10 @@ export const app = new Elysia()
}, { params: t.Object({ wOrderId: t.String() }) }) }, { params: t.Object({ wOrderId: t.String() }) })
.post("/sync/:wOrderId", async ({ params: { wOrderId } }) => { .post("/sync/:wOrderId", async ({ params: { wOrderId } }) => {
const wOrder = await s.Commerce.Webflow.Orders.get(wOrderId); const wOrder = await s.Commerce.Webflow.Orders.get(wOrderId);
if (!wOrder) throw new NotFoundError("Missing webflow order"); if (!wOrder) return status(404, { error: "Missing webflow order" });
const pOrder = await s.Commerce.Printful.Orders.get(`@${wOrder.orderId}`); const pOrder = await s.Commerce.Printful.Orders.get(`@${wOrder.orderId}`);
if (pOrder) throw status(409, { error: "Cannot sync an already synced webflow order" }); if (pOrder) return status(409, { error: "Cannot sync an already synced webflow order" });
await s.Commerce.Apparel.Orders.Queue.enqueue({ await s.Commerce.Apparel.Orders.Queue.enqueue({
type: "apparel_order_create", type: "apparel_order_create",
@@ -441,16 +449,16 @@ export const app = new Elysia()
}) })
.get("/:id", async ({ params: { id } }) => { .get("/:id", async ({ params: { id } }) => {
const event = await s.Events.get(id); const event = await s.Events.get(id);
if (!event) throw new NotFoundError("Event not found"); if (!event) return status(404, { error: "Event not found" });
return { ...event, status: EventsService.status(event) }; return { ...event, status: EventsService.status(event) };
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
.post("/:id/retry", async ({ params: { id } }) => { .post("/:id/retry", async ({ params: { id } }) => {
const retried = await s.Events.retry(id); const retried = await s.Events.retry(id);
if (!retried) throw new NotFoundError("Event not found or not in a failed state"); if (!retried) return status(404, { error: "Event not found or not in a failed state" });
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
.get("/groups", async ({ }) => queues.map((q) => q.group)) .get("/groups", async ({ }) => queues.map((q) => q.group))
) )
@@ -460,7 +468,7 @@ export const app = new Elysia()
// TODO: for added security, could enforce bot or admin only access // TODO: for added security, could enforce bot or admin only access
.get("/:id/stats", async ({ params: { id } }) => { .get("/:id/stats", async ({ params: { id } }) => {
const stats = await s.Accounts.stats(id); const stats = await s.Accounts.stats(id);
if (!stats) throw new NotFoundError("Account stats not found"); if (!stats) return status(404, { error: "Account stats not found" });
return stats; return stats;
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: t.String() })
@@ -468,7 +476,7 @@ export const app = new Elysia()
.guard({ authAdmin: true }, (app) => app .guard({ authAdmin: true }, (app) => app
.get("/:id", async ({ params: { id } }) => { .get("/:id", async ({ params: { id } }) => {
const account = await s.Accounts.get(id); const account = await s.Accounts.get(id);
if (!account) throw new NotFoundError("Account not found"); if (!account) return status(404, { error: "Account not found" });
return account; return account;
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: t.String() })
@@ -477,7 +485,7 @@ export const app = new Elysia()
.group("/me", { auth: true }, (app) => app .group("/me", { auth: true }, (app) => app
.get("/stats", async ({ accountId }) => { .get("/stats", async ({ accountId }) => {
const stats = await s.Accounts.stats(accountId); const stats = await s.Accounts.stats(accountId);
if (!stats) throw new NotFoundError("Account stats not found"); if (!stats) return status(404, { error: "Account stats not found" });
return stats; return stats;
}) })
.post("/assessments", async ({ body: { player, activityPerformances }, accountId }) => { .post("/assessments", async ({ body: { player, activityPerformances }, accountId }) => {
@@ -494,55 +502,48 @@ export const app = new Elysia()
}) })
.delete("/assessments/:id", async ({ params: { id }, accountId }) => { .delete("/assessments/:id", async ({ params: { id }, accountId }) => {
const deleted = await s.Assessments.delete(id, accountId); const deleted = await s.Assessments.delete(id, accountId);
if (!deleted) throw new NotFoundError("Assessment not found"); if (!deleted) return status(404, { error: "Assessment not found" });
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
.post("/verifications", async ({ body: { assessmentId, activityMediaKeys }, accountId }) => { .post("/verifications", async ({ body: { assessmentId, activityMediaKeys }, accountId }) => {
const assessment = await s.Assessments.get(assessmentId); const assessment = await s.Assessments.get(assessmentId, accountId);
if (!assessment || assessment.account_id !== accountId) throw new NotFoundError("Assessment not found"); if (!assessment) return status(404, { error: "Assessment not found" });
// TODO: only allow a single active non-completed verification at a time // TODO: only allow a single active non-completed verification at a time
// TODO: return R2 POST urls for the media // TODO: return R2 POST urls for the media
const created = await s.Verifications.create(assessmentId, activityMediaKeys); const created = await s.Verifications.create(assessmentId, activityMediaKeys);
if (!created) throw status(409, { error: "This assessment already has a verification" }); if (!created) return status(409, { error: "This assessment already has a verification" });
return created; return created;
}, { }, {
body: t.Object({ body: t.Object({
assessmentId: t.String(), assessmentId: BigIntIdSchema,
activityMediaKeys: ActivityMediaKeysSchema, activityMediaKeys: ActivityMediaKeysSchema,
}) })
}) })
.patch("/verifications/:id", async ({ params: { id }, body: { assessmentId, activityMediaKeys }, accountId }) => { .patch("/verifications/:id", async ({ params: { id }, body: { assessmentId, activityMediaKeys }, accountId }) => {
const verification = await s.Verifications.get(id); const verification = await s.Verifications.get(id, accountId);
if (!verification || verification.account_id !== accountId) throw new NotFoundError("Verification not found"); if (!verification) return status(404, { error: "Verification not found" });
if (assessmentId === undefined && Object.keys(activityMediaKeys ?? {}).length === 0) if (assessmentId === undefined && Object.keys(activityMediaKeys ?? {}).length === 0)
throw status(400, { error: "Nothing to update" }); return status(400, { error: "Nothing to update" });
if (assessmentId !== undefined) { if (assessmentId !== undefined && !await s.Assessments.get(assessmentId, accountId))
const assessment = await s.Assessments.get(assessmentId); return status(404, { error: "Assessment not found" });
if (!assessment || assessment.account_id !== accountId) throw new NotFoundError("Assessment not found");
}
const updated = await s.Verifications.update(id, { assessmentId, activityMediaKeys }) const updated = await s.Verifications.update(id, { assessmentId, activityMediaKeys });
.catch((err) => { if (!updated) return status(409, { error: `Cannot update a ${verification.status} verification` });
if (err?.code === "23505") throw status(409, { error: "That assessment already has a verification" });
throw err;
});
if (!updated) throw status(409, { error: `Cannot update a ${verification.status} verification` });
return updated; return updated;
}, { }, {
params: t.Object({ id: t.String() }), params: t.Object({ id: BigIntIdSchema }),
body: t.Object({ body: t.Object({
assessmentId: t.Optional(t.String()), assessmentId: t.Optional(BigIntIdSchema),
activityMediaKeys: t.Optional(ActivityMediaKeysSchema), activityMediaKeys: t.Optional(ActivityMediaKeysSchema),
}) })
}) })
.post("/verifications/:id/submit", async ({ params: { id }, accountId }) => { .post("/verifications/:id/submit", async ({ params: { id }, accountId }) => {
const verification = await s.Verifications.get(id); if (!await s.Verifications.get(id, accountId)) return status(404, { error: "Verification not found" });
if (!verification || verification.account_id !== accountId) throw new NotFoundError("Verification not found");
const submitted = await s.Verifications.submit(id); const submitted = await s.Verifications.submit(id);
if (submitted) { if (submitted) {
@@ -550,18 +551,18 @@ export const app = new Elysia()
return submitted; return submitted;
} }
const current = await s.Verifications.get(id); const current = await s.Verifications.get(id, accountId);
if (!current) throw new NotFoundError("Verification not found"); if (!current) return status(404, { error: "Verification not found" });
if (current.status === "submitted") return { id: current.id, status: current.status }; if (current.status === "submitted") return { id: current.id, status: current.status };
const missingActivityMediaKeys = VerificationsService.missingActivityMediaKeys(current); const missingActivityMediaKeys = VerificationsService.missingActivityMediaKeys(current);
throw status(409, { return status(409, {
error: current.status === "draft" && missingActivityMediaKeys.length > 0 error: current.status === "draft" && missingActivityMediaKeys.length > 0
? `Missing media for: ${missingActivityMediaKeys.join(", ")}` ? `Missing media for: ${missingActivityMediaKeys.join(", ")}`
: `Cannot submit a ${current.status} verification` : `Cannot submit a ${current.status} verification`
}); });
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
) )
) )
@@ -569,7 +570,7 @@ export const app = new Elysia()
// ASSESSMENTS // ASSESSMENTS
.group("/assessments", { authAdmin: true }, (app) => app .group("/assessments", { authAdmin: true }, (app) => app
.post("/", async ({ body: { player, activityPerformances, id } }) => { .post("/", async ({ body: { player, activityPerformances, id } }) => {
await s.Assessments.create(player, activityPerformances, id); return await s.Assessments.create(player, activityPerformances, id);
}, { }, {
body: t.Object({ body: t.Object({
player: PlayerSchema, player: PlayerSchema,
@@ -579,9 +580,10 @@ export const app = new Elysia()
}) })
.put("/:id", async ({ body: { player, activityPerformances }, params: { id } }) => { .put("/:id", async ({ body: { player, activityPerformances }, params: { id } }) => {
const updated = await s.Assessments.update(id, player, activityPerformances); const updated = await s.Assessments.update(id, player, activityPerformances);
if (!updated) throw new NotFoundError("Assessment not found"); if (!updated) return status(404, { error: "Assessment not found" });
return updated;
}, { }, {
params: t.Object({ id: t.String() }), params: t.Object({ id: BigIntIdSchema }),
body: t.Object({ body: t.Object({
player: PlayerSchema, player: PlayerSchema,
activityPerformances: t.Array(ActivityPerformanceSchema), activityPerformances: t.Array(ActivityPerformanceSchema),
@@ -606,20 +608,20 @@ export const app = new Elysia()
}) })
.get("/:id", async ({ params: { id } }) => { .get("/:id", async ({ params: { id } }) => {
const verification = await s.Verifications.get(id); const verification = await s.Verifications.get(id);
if (!verification) throw new NotFoundError("Verification not found"); if (!verification) return status(404, { error: "Verification not found" });
return verification; return verification;
}, { }, {
params: t.Object({ id: t.String() }) params: t.Object({ id: BigIntIdSchema })
}) })
.post("/:id/request-action", async ({ params: { id }, body: { reviewerNotes, activityVerifications } }) => { .post("/:id/request-action", async ({ params: { id }, body: { reviewerNotes, activityVerifications } }) => {
const updated = await s.Verifications.requestAction(id, reviewerNotes ?? null, activityVerifications); const updated = await s.Verifications.requestAction(id, reviewerNotes ?? null, activityVerifications);
if (updated) return updated; if (updated) return updated;
const verification = await s.Verifications.get(id); const verification = await s.Verifications.get(id);
if (!verification) throw new NotFoundError("Verification not found"); if (!verification) return status(404, { error: "Verification not found" });
throw status(409, { error: `Cannot request action on a ${verification.status} verification` }); return status(409, { error: `Cannot request action on a ${verification.status} verification` });
}, { }, {
params: t.Object({ id: t.String() }), params: t.Object({ id: BigIntIdSchema }),
body: t.Object({ body: t.Object({
reviewerNotes: t.Optional(t.String()), reviewerNotes: t.Optional(t.String()),
activityVerifications: ActivityVerificationsSchema, activityVerifications: ActivityVerificationsSchema,
@@ -630,10 +632,10 @@ export const app = new Elysia()
if (updated) return updated; if (updated) return updated;
const verification = await s.Verifications.get(id); const verification = await s.Verifications.get(id);
if (!verification) throw new NotFoundError("Verification not found"); if (!verification) return status(404, { error: "Verification not found" });
throw status(409, { error: `Cannot complete a ${verification.status} verification` }); return status(409, { error: `Cannot complete a ${verification.status} verification` });
}, { }, {
params: t.Object({ id: t.String() }), params: t.Object({ id: BigIntIdSchema }),
body: t.Object({ body: t.Object({
activityVerifications: ActivityVerificationsSchema, activityVerifications: ActivityVerificationsSchema,
}) })
@@ -644,7 +646,7 @@ export const app = new Elysia()
.post("/webhooks/printful", async ({ body, query }) => { .post("/webhooks/printful", async ({ body, query }) => {
// https://webflow.com/integrations/printful // https://webflow.com/integrations/printful
if (!s.Commerce.Printful.Util.verifySecret(query.secret)) if (!s.Commerce.Printful.Util.verifySecret(query.secret))
throw status(400, "Invalid secret"); return status(400, { error: "Invalid secret" });
const payload = body as Printful.Webhook.EventPayload; const payload = body as Printful.Webhook.EventPayload;
@@ -667,7 +669,7 @@ export const app = new Elysia()
const pProduct = payload.data.sync_product; const pProduct = payload.data.sync_product;
const wProductId = pProduct.external_id.split("-")[0]; const wProductId = pProduct.external_id.split("-")[0];
log.info({ externalId: payload.data.sync_product.external_id, wProductId }, "printful webhook: product deleted"); log.info({ externalId: payload.data.sync_product.external_id, wProductId }, "printful webhook: product deleted");
if (!wProductId) throw new NotFoundError("Missing webflow product ID"); if (!wProductId) return status(404, { error: "Missing webflow product ID" });
await s.Commerce.Apparel.Syncs.Queue.enqueue({ await s.Commerce.Apparel.Syncs.Queue.enqueue({
type: "apparel_sync_delete", type: "apparel_sync_delete",
@@ -699,7 +701,7 @@ export const app = new Elysia()
}, { query: t.Object({ secret: t.String() }) }) }, { query: t.Object({ secret: t.String() }) })
.post("/webhooks/webflow", async ({ request, body }) => { .post("/webhooks/webflow", async ({ request, body }) => {
if (!s.Commerce.Webflow.Util.verifySecret(request, body)) if (!s.Commerce.Webflow.Util.verifySecret(request, body))
throw status(400, "Invalid signature"); return status(400, { error: "Invalid signature" });
const payload = body as Webflow.Webhook.EventPayload; const payload = body as Webflow.Webhook.EventPayload;
+4 -3
View File
@@ -24,10 +24,11 @@ export class AssessmentsService {
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
} }
async get(id: string) { async get(id: string, accountId?: string) {
return await db.selectFrom("assessments") return await db.selectFrom("assessments")
.selectAll() .selectAll()
.where("id", "=", id) .where("id", "=", id)
.$if(accountId !== undefined, (qb) => qb.where("account_id", "=", accountId!))
.executeTakeFirst(); .executeTakeFirst();
} }
@@ -48,7 +49,7 @@ export class AssessmentsService {
} }
async update(id: string, player: Player, activityPerformances: ActivityPerformance[]) { async update(id: string, player: Player, activityPerformances: ActivityPerformance[]) {
const result = await db.updateTable("assessments") return await db.updateTable("assessments")
.where("id", "=", id) .where("id", "=", id)
.set({ .set({
name: player.name ?? "Anonymous", name: player.name ?? "Anonymous",
@@ -62,8 +63,8 @@ export class AssessmentsService {
perf_run: AssessmentsService.performanceFor(activityPerformances, Activity.Run), perf_run: AssessmentsService.performanceFor(activityPerformances, Activity.Run),
perf_cone_drill: AssessmentsService.performanceFor(activityPerformances, Activity.ConeDrill), perf_cone_drill: AssessmentsService.performanceFor(activityPerformances, Activity.ConeDrill),
}) })
.returning("id")
.executeTakeFirst(); .executeTakeFirst();
return result.numUpdatedRows > 0n;
} }
async delete(id: string, accountId: string): Promise<boolean> { async delete(id: string, accountId: string): Promise<boolean> {
+3 -3
View File
@@ -14,7 +14,7 @@ export const VerificationStatusSchema = t.Union([
]); ]);
export type VerificationStatus = Static<typeof VerificationStatusSchema>; export type VerificationStatus = Static<typeof VerificationStatusSchema>;
export const REQUIRED_MEDIA_ACTIVITIES = Object.values(Activity); const REQUIRED_MEDIA_ACTIVITIES = Object.values(Activity);
const MEDIA_KEY_COLUMN = { const MEDIA_KEY_COLUMN = {
[Activity.BackSquat]: "media_key_back_squat", [Activity.BackSquat]: "media_key_back_squat",
@@ -80,9 +80,10 @@ export class VerificationsService {
.executeTakeFirst(); .executeTakeFirst();
} }
async get(id: string) { async get(id: string, accountId?: string) {
return await VerificationsService.baseQuery() return await VerificationsService.baseQuery()
.where("verifications.id", "=", id) .where("verifications.id", "=", id)
.$if(accountId !== undefined, (qb) => qb.where("assessments.account_id", "=", accountId!))
.executeTakeFirst(); .executeTakeFirst();
} }
@@ -173,7 +174,6 @@ export class VerificationsService {
"verifications.verf_broad_jump", "verifications.verf_broad_jump",
"verifications.verf_run", "verifications.verf_run",
"verifications.verf_cone_drill", "verifications.verf_cone_drill",
"assessments.account_id",
"assessments.name", "assessments.name",
"assessments.gender", "assessments.gender",
"assessments.age", "assessments.age",
+3
View File
@@ -2,6 +2,7 @@ import cluster from 'node:cluster';
import { createHash } from 'node:crypto'; import { createHash } from 'node:crypto';
import { pino } from 'pino'; import { pino } from 'pino';
import os from 'node:os' import os from 'node:os'
import { t } from 'elysia';
export const log = pino({ export const log = pino({
level: Bun.env.LOG_LEVEL ?? "info", level: Bun.env.LOG_LEVEL ?? "info",
@@ -37,6 +38,8 @@ export const WORKER_COUNT = Math.min(os.availableParallelism(), +env.MAX_WORKER_
export const DEFAULT_NAME = "Default"; export const DEFAULT_NAME = "Default";
export const DUMMY_PASSWORD_HASH = await Bun.password.hash("Dummy"); export const DUMMY_PASSWORD_HASH = await Bun.password.hash("Dummy");
export const BigIntIdSchema = t.String({ pattern: "^\\d+$" });
function requireEnv(key: string): string { function requireEnv(key: string): string {
const val = Bun.env[key]; const val = Bun.env[key];
if (!val) { if (!val) {