Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1478aa2
docs: Add notes on tiers and their function
aureateAnatidae Dec 26, 2025
027120a
refactor: Reorganize seeds for random data in testing, rethink how te…
aureateAnatidae Dec 27, 2025
4025aed
docs: rethink seasons, multiple guilds can participate in one season
aureateAnatidae Dec 27, 2025
9be40ec
docs: fix relation in diagram. add season name
aureateAnatidae Dec 27, 2025
ba9e364
feat: Season table and season query function w/ tests. Refactor test …
aureateAnatidae Dec 27, 2025
20d49e6
bug: fix getSeasons for `during` and `season_name` queries, add tests…
aureateAnatidae Dec 28, 2025
99f4058
test: Rename some test variables, add a test for after being ahead of…
aureateAnatidae Dec 28, 2025
d1b432a
test: Add some non-comprehensive boundary tests for getSeasons query …
aureateAnatidae Dec 31, 2025
79d8535
typo
aureateAnatidae Jan 5, 2026
7c4327a
fix: use development main branch of knexjs
aureateAnatidae Jan 5, 2026
79245b8
Revert knex dependency to outdated stable for now
aureateAnatidae Jan 6, 2026
f255753
refactor: rework API, getSeasons should return the season_ids instead…
aureateAnatidae Jan 6, 2026
12db820
feat: APIs and tests for season create and read operations
aureateAnatidae Jan 6, 2026
a122a9c
chore: rename default db filename
aureateAnatidae Jan 6, 2026
d694805
refactor: reconsider. Do we need guild and user APIs?
aureateAnatidae Jan 6, 2026
803d3c5
docs: Add design of GuildSeason table to docsite
aureateAnatidae Jan 6, 2026
e682b66
refactor: won't need user routes till long time later
aureateAnatidae Jan 6, 2026
0665f4c
feat: GuildSeason table. Everything else is WIP
aureateAnatidae Jan 7, 2026
f547650
WIP: implements untested getGuildSeason to get a guild's currently pa…
aureateAnatidae Jan 8, 2026
0141850
feat: Implementation and tests for getting a guild's currently active…
aureateAnatidae Jan 9, 2026
dd0ce94
test: Complete nominal and negative tests for insertGuildSeason
aureateAnatidae Jan 11, 2026
73bc4ad
feat: test and functionality for updating guildseasons
aureateAnatidae Jan 12, 2026
7f7a9d9
style: check and fix w/ biome
aureateAnatidae Jan 12, 2026
f7acf59
style: remove unused imports in tests
aureateAnatidae Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/backend/.swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
},
"baseUrl": "src",
"paths": {
"@seeds/*": ["../seeds/*"],
"@test/*": ["test/*"],
"@db/*": ["db/*"],
"@v1/*": ["v1/*"]
Expand Down
10 changes: 5 additions & 5 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
},
"dependencies": {
"@hono/node-server": "^1.19.7",
"@hono/standard-validator": "^0.2.0",
"@hono/zod-validator": "^0.7.5",
"@hono/standard-validator": "^0.2.1",
"@hono/zod-validator": "^0.7.6",
"@logtape/hono": "^1.3.5",
"@logtape/logtape": "^1.3.5",
"better-sqlite3": "^12.5.0",
"hono": "^4.10.8",
"hono": "^4.11.3",
"hono-openapi": "^1.1.2",
"knex": "^3.1.0",
"zod": "^4.1.13"
"zod": "^4.3.5"
},
"devDependencies": {
"@faker-js/faker": "^10.1.0"
"@faker-js/faker": "^10.2.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { seed as SSBUCharacters_seed } from "@db/seeds/SSBUCharacters";
import { getLogger } from "@logtape/logtape";
import { seed as SSBUCharacters_seed } from "@seeds/SSBUCharacters";
import type { Knex } from "knex";

const log = getLogger(["grindcord", "db"]);

// Seek more customizable API where list of seeds can be provided to the seedsource
export class SeedSource {
export class BaseSeedSource {
getSeeds() {
return Promise.resolve(["SSBUCharacters"]);
}
Expand Down
20 changes: 18 additions & 2 deletions apps/backend/src/db/init_tables.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { knexDb } from "@db/knexfile";
import { getLogger } from "@logtape/logtape";
import { GuildSeasonTable } from "@v1/guild/models";
import {
MatchCharacterTable,
MatchPlayerTable,
MatchTable,
SSBUCharTable,
} from "@v1/match/models";
import { MatchReportView, MatchWinnerView } from "@v1/match/views";
import { SeasonTable } from "@v1/season/models";
import type { Knex } from "knex";

const log = getLogger(["grindcord", "db"]);

const tables = [MatchTable, MatchPlayerTable, MatchCharacterTable, SSBUCharTable];
const tables = [
MatchTable,
MatchPlayerTable,
MatchCharacterTable,
SSBUCharTable,
SeasonTable,
GuildSeasonTable,
];
const views = [MatchWinnerView, MatchReportView];

async function create_table_if_notexists(
Expand All @@ -31,7 +40,7 @@ async function create_table_if_notexists(
}
}

export async function init_tables(db: Knex = knexDb) {
export async function init_tables(db: Knex = knexDb): Promise<void> {
const trx = await db.transaction();
for (const table of tables) {
await create_table_if_notexists(trx, table.table_name, table.initialize);
Expand All @@ -40,6 +49,13 @@ export async function init_tables(db: Knex = knexDb) {
await trx.commit();
}

export async function seed_db(
seedSource: Knex.SeedSource<unknown>,
db: Knex = knexDb,
): Promise<void> {
await db.seed.run({ seedSource });
}

export async function init_views(db: Knex = knexDb) {
const trx = await db.transaction();
for (const _view of views) {
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/src/db/knexfile.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { SeedSource } from "@db/SeedSource";
import knex, { type Knex } from "knex";
import { BaseSeedSource } from "./BaseSeedSource";

// TODO: https://knexjs.org/guide/#log
export const config: Knex.Config<Knex.Sqlite3ConnectionConfig> = {
client: "better-sqlite3",
connection: {
filename: "./bot_data.db",
filename: "./grindcord.db",
},
seeds: {
seedSource: new SeedSource(),
seedSource: new BaseSeedSource(),
},
useNullAsDefault: true,
};
Expand Down
24 changes: 0 additions & 24 deletions apps/backend/src/db/schemas.ts

This file was deleted.

20 changes: 0 additions & 20 deletions apps/backend/src/db/test/unit.test.ts

This file was deleted.

13 changes: 10 additions & 3 deletions apps/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { init_tables, init_views, teardown } from "@db/init_tables";
import { BaseSeedSource as SSBUCharacterSeedSource } from "@db/BaseSeedSource";
import { init_tables, init_views, seed_db } from "@db/init_tables";
import { serve } from "@hono/node-server";
import { honoLogger } from "@logtape/hono";
import { configure, getConsoleSink } from "@logtape/logtape";
import guild_router from "@v1/guild/router";
import match_router from "@v1/match/router";
import user_router from "@v1/user/router";
import season_router from "@v1/season/router";
import { Hono } from "hono";
import { requestId } from "hono/request-id";
import { openAPIRouteHandler } from "hono-openapi";
Expand All @@ -18,8 +20,10 @@ await configure({

const app = new Hono({ strict: false });

await teardown();
await init_tables();

await seed_db(new SSBUCharacterSeedSource());

await init_views();

app.use(requestId());
Expand All @@ -30,6 +34,9 @@ app.get("/", (c) => {
});

app.route("/match", match_router);
app.route("/season", season_router);
app.route("/guild", guild_router);
// app.route("/user", _router);

app.get(
"/openapi.json",
Expand Down
32 changes: 0 additions & 32 deletions apps/backend/src/test/MockSeedSource.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// General utilities for generating mock data
// General utilities for generating test data

import { ssbu_character_names } from "@db/seeds/SSBUCharacters";
import { faker } from "@faker-js/faker";
import { ssbu_character_names } from "@seeds/SSBUCharacters";

export const snowflake = () =>
faker.string.numeric({
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/test/test_knexfile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MockSeedSource } from "@test/MockSeedSource";
import { BaseSeedSource } from "@db/BaseSeedSource";
import knex, { type Knex } from "knex";

export const test_config: Knex.Config = {
Expand All @@ -7,7 +7,7 @@ export const test_config: Knex.Config = {
filename: ":memory:",
},
seeds: {
seedSource: new MockSeedSource(),
seedSource: new BaseSeedSource(),
},
useNullAsDefault: true,
};
Expand Down
24 changes: 24 additions & 0 deletions apps/backend/src/v1/guild/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Knex } from "knex";
import { z } from "zod";

export const GuildSeasonRecord = z.object({
guild_id: z.string(),
season_id: z.int(),
});
export type GuildSeasonRecord = z.infer<typeof GuildSeasonRecord>;
export const GuildSeasonTable = {
table_name: "GuildSeason",
initialize(table: Knex.TableBuilder) {
table.primary(["guild_id"]);

table.string("guild_id");
table.integer("season_id");

table
.foreign("season_id")
.references("season_id")
.inTable("Season")
.onUpdate("CASCADE")
.onDelete("RESTRICT");
},
};
84 changes: 84 additions & 0 deletions apps/backend/src/v1/guild/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { GuildId, GuildSeason } from "@v1/guild/schemas";
import {
getGuildSeason,
insertGuildSeason,
updateGuildSeason,
} from "@v1/guild/service";
import { SeasonId } from "@v1/season/schemas";
import { Hono } from "hono";
import { describeRoute, resolver, validator } from "hono-openapi";

const app = new Hono();

app.get(
"/season/:guild_id",
describeRoute({
description: "Get the guild's currently active season",
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: resolver(GuildSeason),
},
},
},
400: {
description: "No GuildSeason found for the provided `guild_id`",
},
},
}),
validator("param", GuildId),
async (c) => {
const { guild_id } = c.req.valid("param");
const guild_season: GuildSeason | null = await getGuildSeason(guild_id);
return guild_season ? c.json({ guild_season }) : c.notFound();
},
);

app.post(
"/season/:guild_id",
describeRoute({
description: "Create a guild's registration to a season",
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: resolver(GuildSeason),
},
},
},
},
}),
validator("param", GuildId),
validator("json", SeasonId),
async (c) => {
const { guild_id } = c.req.valid("param");
const { season_id } = c.req.valid("json");
const guild_season: GuildSeason = await insertGuildSeason(guild_id, season_id);
return c.json({ guild_season });
},
);

app.patch(
"/season/:guild_id",
describeRoute({
description: "Change a guild's currently active season",
responses: {
200: {
description: "Successful response",
content: {},
},
},
}),
validator("param", GuildId),
validator("json", SeasonId),
async (c) => {
const { guild_id } = c.req.valid("param");
const { season_id } = c.req.valid("json");
const affected_rows: number = await updateGuildSeason(guild_id, season_id);
return affected_rows === 0 ? c.notFound() : c.status(200);
},
);
export default app;
12 changes: 12 additions & 0 deletions apps/backend/src/v1/guild/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { z } from "zod";

export const GuildSeason = z.object({
guild_id: z.string(),
season_id: z.int(),
});
export type GuildSeason = z.infer<typeof GuildSeason>;

export const GuildId = z.object({
guild_id: z.string(),
});
export type GuildId = z.infer<typeof GuildId>;
Loading