mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Migrate synxXPBadges function to Kysely
This commit is contained in:
parent
31244b2d7c
commit
04c14e9cdf
|
|
@ -1,10 +1,10 @@
|
|||
import type { Transaction } from "kysely";
|
||||
import { db, sql } from "~/db/sql";
|
||||
import type { DB, Tables, TablesInsertable } from "~/db/tables";
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import * as BuildRepository from "~/features/builds/BuildRepository.server";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { syncXPBadges } from "../badges/queries/syncXPBadges.server";
|
||||
|
||||
const removeOldLikesStm = sql.prepare(/*sql*/ `
|
||||
delete from
|
||||
|
|
@ -239,7 +239,7 @@ export async function linkUserAndPlayer({
|
|||
.where("SplatoonPlayer.id", "=", playerId)
|
||||
.execute();
|
||||
|
||||
syncXPBadges();
|
||||
await BadgeRepository.syncXPBadges();
|
||||
await BuildRepository.recalculateAllTop500();
|
||||
}
|
||||
|
||||
|
|
|
|||
76
app/features/badges/BadgeRepository.server.test.ts
Normal file
76
app/features/badges/BadgeRepository.server.test.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { db } from "~/db/sql";
|
||||
import { dbInsertUsers, dbReset } from "~/utils/Test";
|
||||
import * as BadgeRepository from "./BadgeRepository.server";
|
||||
import { SPLATOON_3_XP_BADGE_VALUES } from "./badges-constants";
|
||||
|
||||
describe("syncXPBadges", () => {
|
||||
beforeEach(async () => {
|
||||
await dbInsertUsers(3);
|
||||
await insertXPBadges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dbReset();
|
||||
});
|
||||
|
||||
test("assigns badge to user with qualifying peakXp", async () => {
|
||||
await insertSplatoonPlayer({ splId: "abc123", userId: 1, peakXp: 3000 });
|
||||
|
||||
await BadgeRepository.syncXPBadges();
|
||||
|
||||
const badge = await findBadgeByCode("3000");
|
||||
expect(badge?.owners).toHaveLength(1);
|
||||
expect(badge?.owners[0].id).toBe(1);
|
||||
});
|
||||
|
||||
test("assigns highest qualifying badge when peakXp exceeds threshold", async () => {
|
||||
await insertSplatoonPlayer({ splId: "abc123", userId: 1, peakXp: 3250 });
|
||||
|
||||
await BadgeRepository.syncXPBadges();
|
||||
|
||||
const badge3200 = await findBadgeByCode("3200");
|
||||
const badge3300 = await findBadgeByCode("3300");
|
||||
|
||||
expect(badge3200?.owners).toHaveLength(1);
|
||||
expect(badge3300?.owners).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("does not assign badge when peakXp is below minimum threshold", async () => {
|
||||
await insertSplatoonPlayer({ splId: "abc123", userId: 1, peakXp: 2500 });
|
||||
|
||||
await BadgeRepository.syncXPBadges();
|
||||
|
||||
const badge2600 = await findBadgeByCode("2600");
|
||||
expect(badge2600?.owners).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
async function insertXPBadges() {
|
||||
await db
|
||||
.insertInto("Badge")
|
||||
.values(
|
||||
SPLATOON_3_XP_BADGE_VALUES.map((value) => ({
|
||||
code: String(value),
|
||||
displayName: `${value}+ XP`,
|
||||
hue: null,
|
||||
authorId: null,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async function insertSplatoonPlayer(args: {
|
||||
splId: string;
|
||||
userId: number | null;
|
||||
peakXp: number | null;
|
||||
}) {
|
||||
await db.insertInto("SplatoonPlayer").values(args).execute();
|
||||
}
|
||||
|
||||
async function findBadgeByCode(code: string) {
|
||||
const badges = await BadgeRepository.all();
|
||||
const badge = badges.find((b) => b.code === code);
|
||||
if (!badge) return null;
|
||||
return BadgeRepository.findById(badge.id);
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import type { ExpressionBuilder } from "kysely";
|
||||
import type { ExpressionBuilder, NotNull } from "kysely";
|
||||
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
||||
import { db } from "~/db/sql";
|
||||
import type { DB } from "~/db/tables";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
||||
import { SPLATOON_3_XP_BADGE_VALUES } from "./badges-constants";
|
||||
import { findSplatoon3XpBadgeValue } from "./badges-utils";
|
||||
|
||||
const addPermissions = <T extends { managers: { userId: number }[] }>(
|
||||
row: T,
|
||||
|
|
@ -160,3 +163,46 @@ export function replaceOwners({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncXPBadges() {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
for (const value of SPLATOON_3_XP_BADGE_VALUES) {
|
||||
const badge = await trx
|
||||
.selectFrom("Badge")
|
||||
.select("id")
|
||||
.where("code", "=", String(value))
|
||||
.executeTakeFirst();
|
||||
|
||||
invariant(badge, `Badge ${value} not found`);
|
||||
|
||||
await trx
|
||||
.deleteFrom("TournamentBadgeOwner")
|
||||
.where("badgeId", "=", badge.id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
const userTopXPowers = await trx
|
||||
.selectFrom("SplatoonPlayer")
|
||||
.select(["userId", "peakXp"])
|
||||
.where("userId", "is not", null)
|
||||
.where("peakXp", "is not", null)
|
||||
.$narrowType<{ userId: NotNull; peakXp: NotNull }>()
|
||||
.execute();
|
||||
|
||||
for (const { userId, peakXp } of userTopXPowers) {
|
||||
const badgeValue = findSplatoon3XpBadgeValue(peakXp!);
|
||||
if (!badgeValue) continue;
|
||||
|
||||
await trx
|
||||
.insertInto("TournamentBadgeOwner")
|
||||
.values((eb) => ({
|
||||
badgeId: eb
|
||||
.selectFrom("Badge")
|
||||
.select("id")
|
||||
.where("code", "=", String(badgeValue)),
|
||||
userId,
|
||||
}))
|
||||
.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
import { sql } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { SPLATOON_3_XP_BADGE_VALUES } from "../badges-constants";
|
||||
import { findSplatoon3XpBadgeValue } from "../badges-utils";
|
||||
|
||||
const badgeCodeToIdStm = sql.prepare(/* sql */ `
|
||||
select "id"
|
||||
from "Badge"
|
||||
where "code" = @code
|
||||
`);
|
||||
|
||||
const deleteBadgeOwnerStm = sql.prepare(/* sql */ `
|
||||
delete from "TournamentBadgeOwner"
|
||||
where "badgeId" = @badgeId
|
||||
`);
|
||||
|
||||
const userTopXPowersStm = sql.prepare(/* sql */ `
|
||||
select
|
||||
"SplatoonPlayer"."userId",
|
||||
"SplatoonPlayer"."peakXp" as "xPower"
|
||||
from
|
||||
"SplatoonPlayer"
|
||||
where "SplatoonPlayer"."userId" is not null
|
||||
and "SplatoonPlayer"."peakXp" is not null
|
||||
`);
|
||||
|
||||
const addXPBadgeStm = sql.prepare(/* sql */ `
|
||||
insert into "TournamentBadgeOwner" ("badgeId", "userId")
|
||||
values (
|
||||
(select "id" from "Badge" where "code" = @code),
|
||||
@userId
|
||||
)
|
||||
`);
|
||||
|
||||
export const syncXPBadges = sql.transaction(() => {
|
||||
for (const value of SPLATOON_3_XP_BADGE_VALUES) {
|
||||
const badgeId = (badgeCodeToIdStm.get({ code: String(value) }) as any)
|
||||
.id as number;
|
||||
|
||||
invariant(badgeId, `Badge ${value} not found`);
|
||||
|
||||
deleteBadgeOwnerStm.run({ badgeId });
|
||||
}
|
||||
|
||||
const userTopXPowers = userTopXPowersStm.all() as Array<{
|
||||
userId: number;
|
||||
xPower: number;
|
||||
}>;
|
||||
|
||||
for (const { userId, xPower } of userTopXPowers) {
|
||||
const badgeValue = findSplatoon3XpBadgeValue(xPower);
|
||||
if (!badgeValue) continue;
|
||||
|
||||
addXPBadgeStm.run({ code: String(badgeValue), userId });
|
||||
}
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ActionFunctionArgs } from "react-router";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { syncXPBadges } from "~/features/badges/queries/syncXPBadges.server";
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
import {
|
||||
errorToastIfFalsy,
|
||||
|
|
@ -35,7 +35,7 @@ export const action = async ({ params }: ActionFunctionArgs) => {
|
|||
|
||||
await XRankPlacementRepository.unlinkPlayerByUserId(user.id);
|
||||
|
||||
syncXPBadges();
|
||||
await BadgeRepository.syncXPBadges();
|
||||
|
||||
return successToast("Unlink successful");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import "dotenv/config";
|
|||
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { syncXPBadges } from "~/features/badges/queries/syncXPBadges.server";
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import * as BuildRepository from "~/features/builds/BuildRepository.server";
|
||||
import * as XRankPlacementRepository from "~/features/top-search/XRankPlacementRepository.server";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists/types";
|
||||
|
|
@ -55,7 +55,7 @@ async function main() {
|
|||
|
||||
addPlacements(placements);
|
||||
await XRankPlacementRepository.refreshAllPeakXp();
|
||||
syncXPBadges();
|
||||
await BadgeRepository.syncXPBadges();
|
||||
await BuildRepository.recalculateAllTop500();
|
||||
logger.info(`done reading in ${placements.length} placements`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import "dotenv/config";
|
||||
import { syncXPBadges } from "~/features/badges/queries/syncXPBadges.server";
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
syncXPBadges();
|
||||
|
||||
logger.info("Synced XP badges");
|
||||
void (async () => {
|
||||
await BadgeRepository.syncXPBadges();
|
||||
logger.info("Synced XP badges");
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user