Restructuring calculator data in database and improve api surface
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { LevelCalculator, Standards, type StandardsParams, StandardsParamsSchema, StandardsDataSchema, type StandardsConfig, type LevelCalculatorOutput } from "@blade-and-brawn/calculator";
|
||||
import { LevelCalculator, Standards, type StandardsParams, StandardsParamsSchema, StandardsDataSchema, type StandardsData, type StandardsConfig, type LevelCalculatorOutput } from "@blade-and-brawn/calculator";
|
||||
import type { ActivityPerformance, Player } from "@blade-and-brawn/domain";
|
||||
import { log } from "../util";
|
||||
import { db } from "../database/db";
|
||||
import { Value } from "@sinclair/typebox/value";
|
||||
import { log } from "../util";
|
||||
|
||||
export class CalculatorUnavailableError extends Error {
|
||||
status: number = 503
|
||||
@@ -10,9 +10,10 @@ export class CalculatorUnavailableError extends Error {
|
||||
|
||||
export class CalculatorService {
|
||||
private calculator: LevelCalculator | null = null;
|
||||
Standards: StandardsService = new StandardsService();
|
||||
Standards: CalculatorStandardsService;
|
||||
|
||||
constructor() {
|
||||
constructor(private name: string) {
|
||||
this.Standards = new CalculatorStandardsService(this.name);
|
||||
// eagerly load
|
||||
this.refresh().catch((err) => log.error({ err }, "failed to load initial standards config"));
|
||||
}
|
||||
@@ -38,15 +39,15 @@ export class CalculatorService {
|
||||
}
|
||||
}
|
||||
|
||||
class StandardsService {
|
||||
Config: StandardsConfigService;
|
||||
class CalculatorStandardsService {
|
||||
Config: CalculatorStandardsConfigService;
|
||||
|
||||
constructor() {
|
||||
this.Config = new StandardsConfigService();
|
||||
constructor(private name: string) {
|
||||
this.Config = new CalculatorStandardsConfigService(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardsConfigService {
|
||||
class CalculatorStandardsConfigService {
|
||||
private cache = {
|
||||
refreshInterval: 30_000,
|
||||
lastRefreshedAt: 0,
|
||||
@@ -62,39 +63,27 @@ class StandardsConfigService {
|
||||
},
|
||||
}
|
||||
|
||||
async create(datasetId: string, params: StandardsParams) {
|
||||
await db.insertInto("standards_configs")
|
||||
.values({ params, dataset_id: datasetId })
|
||||
.execute();
|
||||
}
|
||||
constructor(private name: string) { }
|
||||
|
||||
/** Retrieves the currently selected configuration */
|
||||
async get(): Promise<StandardsConfig> {
|
||||
await this.refresh({ onlyIfStale: true });
|
||||
if (!this.cache.data.config) throw new CalculatorUnavailableError("Unable to determine the selected standards configuration");
|
||||
if (!this.cache.data.config) throw new CalculatorUnavailableError("Unable to determine the calculator's standards configuration");
|
||||
return this.cache.data.config;
|
||||
}
|
||||
|
||||
async update(id: string, datasetId: string, params: StandardsParams) {
|
||||
const result = await db.updateTable("standards_configs")
|
||||
.set({ params, dataset_id: datasetId })
|
||||
.where("id", "=", id)
|
||||
.execute();
|
||||
if (result[0]?.numUpdatedRows === 0n) throw new Error(`No config with id "${id}"`);
|
||||
}
|
||||
|
||||
async refresh(opt: { onlyIfStale?: boolean } = {}): Promise<void> {
|
||||
if (!this.cache.isIncomplete() && (opt.onlyIfStale && !this.cache.isStale())) return;
|
||||
|
||||
try {
|
||||
const result = await db.selectFrom("standards_configs")
|
||||
const result = await db.selectFrom("calculators")
|
||||
.innerJoin("standards_configs", "standards_configs.id", "calculators.standards_config_id")
|
||||
.innerJoin("standards_datasets", "standards_datasets.id", "standards_configs.dataset_id")
|
||||
.select([
|
||||
"standards_configs.dataset_id as datasetId",
|
||||
"standards_configs.params as params",
|
||||
"standards_datasets.data as data",
|
||||
])
|
||||
.where("standards_configs.is_selected", "=", true)
|
||||
.where("calculators.name", "=", this.name)
|
||||
.limit(1)
|
||||
.executeTakeFirstOrThrow({ errorConstructor: () => new Error("No standards config selected") });
|
||||
Value.Assert(StandardsParamsSchema, result.params);
|
||||
@@ -109,21 +98,15 @@ class StandardsConfigService {
|
||||
// config; a failed attempt must not make stale data look fresh
|
||||
this.cache.lastRefreshedAt = Date.now();
|
||||
} catch (err) {
|
||||
throw new CalculatorUnavailableError("Unable to determine the selected standards configuration", { cause: err });
|
||||
throw new CalculatorUnavailableError("Unable to determine the calculator's standards configuration", { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
async switch(id: string) {
|
||||
await db.transaction().execute(async (trx) => {
|
||||
await trx.updateTable("standards_configs")
|
||||
.set({ is_selected: false })
|
||||
.where("is_selected", "=", true)
|
||||
.execute();
|
||||
const result = await trx.updateTable("standards_configs")
|
||||
.set({ is_selected: true })
|
||||
.where("id", "=", id)
|
||||
.execute();
|
||||
if (result[0]?.numUpdatedRows === 0n) throw new Error(`No standards config with id "${id}"`);
|
||||
});
|
||||
async switch(standardsConfigId: string) {
|
||||
const result = await db.updateTable("calculators")
|
||||
.set({ standards_config_id: standardsConfigId })
|
||||
.where("name", "=", this.name)
|
||||
.execute();
|
||||
if (result[0]?.numUpdatedRows === 0n) throw new Error(`No calculator with name "${this.name}"`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { StandardsDataSchema, StandardsParamsSchema, type StandardsConfig, type StandardsData, type StandardsParams } from "@blade-and-brawn/calculator";
|
||||
import { db } from "../database/db";
|
||||
import { Value } from "@sinclair/typebox/value";
|
||||
|
||||
export class StandardsService {
|
||||
Configs: StandardsConfigService;
|
||||
Datasets: StandardsDatasetService;
|
||||
|
||||
constructor() {
|
||||
this.Configs = new StandardsConfigService();
|
||||
this.Datasets = new StandardsDatasetService();
|
||||
}
|
||||
}
|
||||
|
||||
class StandardsConfigService {
|
||||
async create(name: string, datasetId: string, params: StandardsParams): Promise<{ id: string }> {
|
||||
return await db.insertInto("standards_configs")
|
||||
.values({ name, params, dataset_id: datasetId })
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async list(): Promise<{ id: string, name: string, datasetId: string }[]> {
|
||||
return await db.selectFrom("standards_configs")
|
||||
.select([
|
||||
"id",
|
||||
"name",
|
||||
"dataset_id as datasetId",
|
||||
])
|
||||
.orderBy("name")
|
||||
.execute();
|
||||
}
|
||||
|
||||
async get(id: string): Promise<StandardsConfig & { id: string, name: string, datasetId: string }> {
|
||||
const result = await db.selectFrom("standards_configs")
|
||||
.innerJoin("standards_datasets", "standards_datasets.id", "standards_configs.dataset_id")
|
||||
.select([
|
||||
"standards_configs.id as id",
|
||||
"standards_configs.name as name",
|
||||
"standards_configs.dataset_id as datasetId",
|
||||
"standards_configs.params as params",
|
||||
"standards_datasets.data as data",
|
||||
])
|
||||
.where("standards_configs.id", "=", id)
|
||||
.executeTakeFirstOrThrow({ errorConstructor: () => new Error(`No config with id "${id}"`) });
|
||||
Value.Assert(StandardsParamsSchema, result.params);
|
||||
Value.Assert(StandardsDataSchema, result.data);
|
||||
return {
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
datasetId: result.datasetId,
|
||||
params: result.params,
|
||||
data: result.data,
|
||||
};
|
||||
}
|
||||
|
||||
async update(id: string, name: string, datasetId: string, params: StandardsParams) {
|
||||
const result = await db.updateTable("standards_configs")
|
||||
.set({ name, params, dataset_id: datasetId })
|
||||
.where("id", "=", id)
|
||||
.execute();
|
||||
if (result[0]?.numUpdatedRows === 0n) throw new Error(`No config with id "${id}"`);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardsDatasetService {
|
||||
async list(): Promise<{ id: string, name: string }[]> {
|
||||
return await db.selectFrom("standards_datasets")
|
||||
.select(["id", "name"])
|
||||
.orderBy("name")
|
||||
.execute();
|
||||
}
|
||||
|
||||
async get(id: string): Promise<{ id: string, name: string, data: StandardsData }> {
|
||||
const result = await db.selectFrom("standards_datasets")
|
||||
.select(["id", "name", "data"])
|
||||
.where("id", "=", id)
|
||||
.executeTakeFirstOrThrow({ errorConstructor: () => new Error(`No dataset with id "${id}"`) });
|
||||
Value.Assert(StandardsDataSchema, result.data);
|
||||
return { id: result.id, name: result.name, data: result.data };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user