1.1.0 #29

Merged
Xominus merged 25 commits from discord into main 2026-08-22 18:00:22 +00:00
3 changed files with 109 additions and 7 deletions
Showing only changes of commit e96c40c9c7 - Show all commits
+11 -5
View File
@@ -11,18 +11,24 @@ export async function up(db: Kysely<any>): Promise<void> {
) )
.addColumn("discord_id", "text", (cb) => cb.unique()) .addColumn("discord_id", "text", (cb) => cb.unique())
.addColumn("email", "text", (cb) => cb.unique()) .addColumn("email", "text", (cb) => cb.unique())
.addColumn("name", "text", (cb) => cb.notNull().defaultTo("")) .addColumn("name", "text", (cb) => cb.notNull().defaultTo("Anonymous"))
.addColumn("gender", "text", (cb) => cb.notNull()) .addColumn("gender", "text")
.execute(); .execute();
// TABLE: ASSESSMENTS // TABLE: ASSESSMENTS
await db.schema.createTable("assessments") await db.schema.createTable("assessments")
.$call(addDefaultColumns) .$call(addDefaultColumns)
.addColumn("account_id", "uuid") .addColumn("account_id", "uuid")
.addColumn("name", "text", (cb) => cb.notNull().defaultTo("")) .addColumn("name", "text", (cb) => cb.notNull().defaultTo("Anonymous"))
.addColumn("gender", "text", (cb) => cb.notNull()) .addColumn("gender", "text", (cb) => cb.notNull())
.addColumn("age", "numeric", (cb) => cb.notNull()) .addColumn("age", "integer", (cb) => cb.notNull())
.addColumn("weight", "numeric", (cb) => cb.notNull()) .addColumn("weight", "float8", (cb) => cb.notNull())
.addColumn("perf_back_squat", "float8") // kg
.addColumn("perf_deadlift", "float8") // kg
.addColumn("perf_bench_press", "float8") // kg
.addColumn("perf_run", "float8") // ms
.addColumn("perf_broad_jump", "float8") // cm
.addColumn("perf_cone_drill", "float8") // ms
.addForeignKeyConstraint( .addForeignKeyConstraint(
"fk_assessments_account_id", "fk_assessments_account_id",
["account_id"], ["account_id"],
+25 -2
View File
@@ -20,10 +20,11 @@ import { CalculatorService, CalculatorUnavailableError } from "./services/calcul
import { StandardsParamsSchema } from "@blade-and-brawn/calculator"; import { StandardsParamsSchema } from "@blade-and-brawn/calculator";
import { StandardsService } from "./services/standards"; import { StandardsService } from "./services/standards";
import { EventsService, EventStatusSchema } from "./services/events"; import { EventsService, EventStatusSchema } from "./services/events";
import { AccountsService } from "./services/accounts";
// CONSTANTS // CONSTANTS
// ----------------------- // -----------------------
const EVENT_QUEUE_MANAGE_DELAY_MS = 1000 // 1 sec const EVENT_QUEUE_MANAGE_DELAY_MS = 1000 // 1 sec
const JWT_EXP = "1d"; const JWT_EXP = "1d";
// SERVICES // SERVICES
@@ -32,8 +33,9 @@ const s = (() => {
const Standards = new StandardsService(); const Standards = new StandardsService();
const Calculator = new CalculatorService(DEFAULT_NAME); const Calculator = new CalculatorService(DEFAULT_NAME);
const Commerce = new CommerceService(); const Commerce = new CommerceService();
const Accounts = new AccountsService(Calculator);
const Events = new EventsService(); const Events = new EventsService();
return { Standards, Calculator, Commerce, Events }; return { Standards, Calculator, Commerce, Accounts, Events };
})(); })();
// QUEUES // QUEUES
@@ -337,6 +339,27 @@ export const app = new Elysia()
.get("/groups", async ({ }) => queues.map((q) => q.group)) .get("/groups", async ({ }) => queues.map((q) => q.group))
) )
// ACCOUNTS
.group("/accounts", (app) => app
// Non-authenticated
.get("/:id/stats", async ({ params: { id } }) => {
const stats = await s.Accounts.stats(id);
if (!stats) throw new NotFoundError("Account stats not found");
return stats;
}, {
params: t.Object({ id: t.String() })
})
// Authenticated
.guard({ auth: true })
.get("/:id", async ({ params: { id } }) => {
const account = await s.Accounts.get(id);
if (!account) throw new NotFoundError("Account not found");
return account;
}, {
params: t.Object({ id: t.String() })
})
)
// WEBHOOKS // WEBHOOKS
.post("/webhooks/printful", async ({ body, query }) => { .post("/webhooks/printful", async ({ body, query }) => {
// https://webflow.com/integrations/printful // https://webflow.com/integrations/printful
+73
View File
@@ -0,0 +1,73 @@
import { Activity, Gender, type Player } from "@blade-and-brawn/domain";
import { db } from "../database/db";
import type { CalculatorService } from "./calculator";
import { Value } from "@sinclair/typebox/value";
import { t } from "elysia";
export class AccountsService {
constructor(private Calculator: CalculatorService) { }
async get(id: string) {
return await (db).selectFrom("accounts")
.selectAll()
.where("id", "=", id)
.executeTakeFirst();
}
async stats(id: string) {
const latestAssessment = await db.selectFrom("accounts")
.innerJoin("assessments", "assessments.account_id", "accounts.id")
.select([
"assessments.age", "assessments.gender", "assessments.weight", "assessments.name",
"assessments.perf_back_squat", "assessments.perf_bench_press", "assessments.perf_broad_jump",
"assessments.perf_cone_drill", "assessments.perf_deadlift", "assessments.perf_run"
])
.where("accounts.id", "=", id)
.orderBy("assessments.created_at", "desc")
.limit(1)
.executeTakeFirst();
if (!latestAssessment) return;
Value.Assert(t.Enum(Gender), latestAssessment.gender);
return this.Calculator.calculate(
{
name: latestAssessment.name,
metrics: {
age: latestAssessment.age,
weight: latestAssessment.weight,
gender: latestAssessment.gender
}
},
[
// Strength
{
activity: Activity.BenchPress,
performance: latestAssessment.perf_bench_press ?? 0
},
{
activity: Activity.Deadlift,
performance: latestAssessment.perf_deadlift ?? 0
},
{
activity: Activity.BackSquat,
performance: latestAssessment.perf_back_squat ?? 0
},
// Power
{
activity: Activity.BroadJump,
performance: latestAssessment.perf_broad_jump ?? 0
},
// Endurance
{
activity: Activity.Run,
performance: latestAssessment.perf_run ?? 0
},
// Agility
{
activity: Activity.ConeDrill,
performance: latestAssessment.perf_cone_drill ?? 0
},
]
);
}
}