Finish adding config endpoints

This commit is contained in:
Dominic Ferrando
2026-07-04 02:32:59 -04:00
parent f85225e08f
commit c1d3bed404
4 changed files with 131 additions and 7 deletions
@@ -31,7 +31,7 @@ export async function up(db: Kysely<any>): Promise<void> {
await db.schema.createTable("calculator_configs") await db.schema.createTable("calculator_configs")
.$call(addDefaultColumns) .$call(addDefaultColumns)
.addColumn("standards_config", "jsonb", (cb) => cb.notNull()) .addColumn("standards_config", "jsonb", (cb) => cb.notNull())
.addColumn("is_selected", "boolean", (cb) => cb.notNull()) .addColumn("is_selected", "boolean", (cb) => cb.notNull().defaultTo(false))
.execute(); .execute();
// INDEX: ONE_SELECTED_CALCULATOR_CONFIG // INDEX: ONE_SELECTED_CALCULATOR_CONFIG
+22 -5
View File
@@ -18,6 +18,7 @@ import { CommerceService } from "./services/commerce";
import cluster from "node:cluster"; import cluster from "node:cluster";
import { randomUUIDv7 } from "bun"; import { randomUUIDv7 } from "bun";
import { CalculatorService } from "./services/calculator"; import { CalculatorService } from "./services/calculator";
import { StandardsConfigSchema } from "@blade-and-brawn/calculator";
// SERVICES // SERVICES
// ----------- // -----------
@@ -100,6 +101,7 @@ export const app = new Elysia()
// CALCULATOR // CALCULATOR
.group("/calculator", .group("/calculator",
(app) => app (app) => app
// Non-authenticated
.post("/calculate", async ({ body }) => { .post("/calculate", async ({ body }) => {
return { levels: calculatorService.calculate(body.player, body.activityPerformances) }; return { levels: calculatorService.calculate(body.player, body.activityPerformances) };
}, { }, {
@@ -108,15 +110,30 @@ export const app = new Elysia()
activityPerformances: t.Array(ActivityPerformanceSchema) activityPerformances: t.Array(ActivityPerformanceSchema)
}), }),
}) })
// Authenticated
.resolve(async ({ jwt, cookie: { auth } }) => { .resolve(async ({ jwt, cookie: { auth } }) => {
const token = auth.value && await jwt.verify(auth.value); const token = auth.value && await jwt.verify(auth.value);
if (!token || !token.sessionId) throw status(401, "Unauthorized"); if (!token || !token.sessionId) throw status(401, "Unauthorized");
return { sessionId: token.sessionId.toString() }; return { sessionId: token.sessionId.toString() };
}) })
.post("/config", () => { }) .put("/config/:id", async ({ params: { id }, body: { standardsConfig } }) => {
await calculatorService.setConfig(id, standardsConfig);
}, {
params: t.Object({ id: t.String() }),
body: t.Object({ standardsConfig: StandardsConfigSchema })
})
.post("/config", async ({ body: { standardsConfig } }) => {
await calculatorService.createConfig(standardsConfig);
}, {
body: t.Object({ standardsConfig: StandardsConfigSchema })
})
.post("/config/:id/select", async ({ params: { id } }) => {
await calculatorService.selectConfig(id);
}, {
params: t.Object({ id: t.String() })
})
.get("/config", async () => { .get("/config", async () => {
const config = await calculatorService.getConfig(); return await calculatorService.getSelectedConfig();
return config;
}) })
) )
@@ -150,8 +167,8 @@ export const app = new Elysia()
}); });
}, { params: t.Object({ printfulProductId: t.Optional(t.Numeric()) }) }), }, { params: t.Object({ printfulProductId: t.Optional(t.Numeric()) }) }),
) )
.get("/:printfulProductId", async ({ params }) => { .get("/:printfulProductId", async ({ params: { printfulProductId } }) => {
const printfulProduct = await commerceService.printful.Products.get(params.printfulProductId); const printfulProduct = await commerceService.printful.Products.get(printfulProductId);
if (!printfulProduct) throw new NotFoundError("Missing printful product"); if (!printfulProduct) throw new NotFoundError("Missing printful product");
const webflowProductId = printfulProduct.sync_product.external_id.split("-")[0]; const webflowProductId = printfulProduct.sync_product.external_id.split("-")[0];
+29 -1
View File
@@ -21,13 +21,41 @@ export class CalculatorService {
return this.levelCalculator.calculate(player, activityPerformances); return this.levelCalculator.calculate(player, activityPerformances);
} }
async getConfig(opt: { skipCache?: boolean } = {}): Promise<StandardsConfig> { async getSelectedConfig(opt: { skipCache?: boolean } = {}): Promise<StandardsConfig> {
if (!opt.skipCache) return this.levelCalculator.standards.cfg; if (!opt.skipCache) return this.levelCalculator.standards.cfg;
await this.refreshConfig(); await this.refreshConfig();
return this.levelCalculator.standards.cfg; return this.levelCalculator.standards.cfg;
} }
async selectConfig(id: string) {
await db.transaction().execute(async (trx) => {
await trx.updateTable("calculator_configs")
.set({ is_selected: false })
.where("is_selected", "=", true)
.execute();
const result = await trx.updateTable("calculator_configs")
.set({ is_selected: true })
.where("id", "=", id)
.execute();
if (result[0]?.numUpdatedRows === 0n) throw new Error(`No config with id "${id}"`);
});
}
async setConfig(id: string, standardsConfig: StandardsConfig) {
const result = await db.updateTable("calculator_configs")
.set({ standards_config: standardsConfig })
.where("id", "=", id)
.execute();
if (result[0]?.numUpdatedRows === 0n) throw new Error(`No config with id "${id}"`);
}
async createConfig(standardsConfig: StandardsConfig) {
await db.insertInto("calculator_configs")
.values({ standards_config: standardsConfig })
.execute();
}
async refreshConfig(opt: { onlyIfStale?: boolean } = {}) { async refreshConfig(opt: { onlyIfStale?: boolean } = {}) {
if (opt.onlyIfStale) if (opt.onlyIfStale)
if (Date.now() - this.lastRefreshedAt < CalculatorService.CONFIG_REFRESH_INTERVAL_MS) return; if (Date.now() - this.lastRefreshedAt < CalculatorService.CONFIG_REFRESH_INTERVAL_MS) return;
@@ -0,0 +1,79 @@
{
"global": {
"maxLevel": 100
},
"activity": {
"BackSquat": {
"enableGeneration": true,
"weightModifier": 0,
"weightSkew": 0,
"ageModifier": 0,
"difficultyModifier": 0.3,
"peakAge": 0,
"stretch": {
"upper": 0,
"lower": 1
}
},
"BenchPress": {
"enableGeneration": true,
"weightModifier": 0,
"weightSkew": 0,
"ageModifier": 0,
"difficultyModifier": 0.05,
"peakAge": 0,
"stretch": {
"upper": 1,
"lower": 0
}
},
"Deadlift": {
"enableGeneration": true,
"weightModifier": 0,
"weightSkew": 0,
"ageModifier": 0,
"difficultyModifier": -0.05,
"peakAge": 0,
"stretch": {
"upper": 1,
"lower": 0
}
},
"Run": {
"enableGeneration": true,
"weightModifier": 0.1,
"weightSkew": 0,
"ageModifier": 0,
"difficultyModifier": 0.35,
"peakAge": 0,
"stretch": {
"upper": 1,
"lower": 1
}
},
"BroadJump": {
"enableGeneration": true,
"weightModifier": -0.1,
"weightSkew": 0,
"ageModifier": -0.25,
"difficultyModifier": -0.15,
"peakAge": 23,
"stretch": {
"upper": 3,
"lower": 1
}
},
"ConeDrill": {
"enableGeneration": true,
"weightModifier": -0.1,
"weightSkew": 0,
"ageModifier": -0.25,
"difficultyModifier": 0.15,
"peakAge": 23,
"stretch": {
"upper": 0,
"lower": 2
}
}
}
}