mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-20 01:55:26 -05:00
Merge remote-tracking branch 'origin/main' into ingest
# Conflicts: # app/components/match-page/MatchTimeline.tsx # app/db/tables.ts # app/features/tournament-match/components/TournamentMatchTabs.tsx # app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts # app/features/vods/loaders/vods.new.server.ts # db-test.sqlite3 # e2e/seeds/db-seed-AB_RR.sqlite3 # e2e/seeds/db-seed-DEFAULT.sqlite3 # e2e/seeds/db-seed-FINALIZED_BRACKET.sqlite3 # e2e/seeds/db-seed-IN_SQ_MATCH.sqlite3 # e2e/seeds/db-seed-NO_SCRIMS.sqlite3 # e2e/seeds/db-seed-NO_SQ_GROUPS.sqlite3 # e2e/seeds/db-seed-NO_TOURNAMENT_TEAMS.sqlite3 # e2e/seeds/db-seed-NZAP_IN_TEAM.sqlite3 # e2e/seeds/db-seed-REG_OPEN.sqlite3 # e2e/seeds/db-seed-SMALL_SOS.sqlite3 # e2e/seeds/db-seed-TEAM_MAP_PREFS.sqlite3 # knip.ts # package.json # pnpm-lock.yaml # vite.config.ts
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -12,23 +12,24 @@ invariant(
|
||||
"discordIds must be a comma separated list of discord ids",
|
||||
);
|
||||
|
||||
const stm = sql.prepare(
|
||||
/* sql */ `insert into "TournamentBadgeOwner" ("badgeId", "userId") values (@badgeId, (select "id" from "User" where "discordId" = @userId))`,
|
||||
);
|
||||
|
||||
const userStm = sql.prepare(
|
||||
/* sql */ `select "id" from "User" where "discordId" = @discordId`,
|
||||
);
|
||||
|
||||
const users = discordIds.split(",");
|
||||
|
||||
for (const userId of users) {
|
||||
const user = userStm.get({ discordId: userId });
|
||||
for (const discordId of users) {
|
||||
const user = await db
|
||||
.selectFrom("User")
|
||||
.select("id")
|
||||
.where("discordId", "=", discordId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!user) {
|
||||
logger.info(`User with discord id ${userId} not found`);
|
||||
logger.info(`User with discord id ${discordId} not found`);
|
||||
continue;
|
||||
}
|
||||
stm.run({ badgeId: Number(badgeId), userId });
|
||||
|
||||
await db
|
||||
.insertInto("TournamentBadgeOwner")
|
||||
.values({ badgeId: Number(badgeId), userId: user.id })
|
||||
.execute();
|
||||
}
|
||||
|
||||
logger.info(`Added ${users.length} owners to the badge`);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -20,10 +20,11 @@ invariant(
|
||||
"hue must be between -360 and 360",
|
||||
);
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
"insert into badge (code, displayName, hue) values ($code, $displayName, $hue)",
|
||||
)
|
||||
.run({ code, displayName, hue: parsedHue ?? null });
|
||||
await BadgeRepository.insert({
|
||||
code,
|
||||
displayName,
|
||||
hue: parsedHue ?? null,
|
||||
authorId: null,
|
||||
});
|
||||
|
||||
logger.info(`Added new badge: ${displayName}`);
|
||||
|
||||
@@ -50,7 +50,7 @@ async function main() {
|
||||
const season = Seasons.currentOrPrevious();
|
||||
invariant(season, "No current or previous season found");
|
||||
|
||||
const leaderboard = await LeaderboardRepository.teamLeaderboardBySeason({
|
||||
const leaderboard = await LeaderboardRepository.findTeamLeaderboardBySeason({
|
||||
season: season.nth,
|
||||
onlyOneEntryPerUser: true,
|
||||
});
|
||||
@@ -84,7 +84,9 @@ async function main() {
|
||||
invariant(user.friendCode, `User ${member.username} has no friend code`);
|
||||
|
||||
if (tournament.ctx.settings.requireInGameNames) {
|
||||
const inGameName = await UserRepository.inGameNameByUserId(member.id);
|
||||
const inGameName = await UserRepository.findInGameNameByUserId(
|
||||
member.id,
|
||||
);
|
||||
invariant(
|
||||
inGameName,
|
||||
`User ${member.username} has no in-game name (required by tournament)`,
|
||||
@@ -107,7 +109,7 @@ async function main() {
|
||||
const tournamentTeam = await userAsyncLocalStorage.run(
|
||||
{ user: adminUser },
|
||||
() =>
|
||||
TournamentTeamRepository.create({
|
||||
TournamentTeamRepository.insert({
|
||||
team: {
|
||||
name: teamName,
|
||||
prefersNotToHost: 0,
|
||||
|
||||
@@ -6,6 +6,12 @@ import { type BenchmarkCase, buildCases } from "./benchmark-db/cases";
|
||||
import { resolveFixtures } from "./benchmark-db/fixtures";
|
||||
|
||||
const DEFAULT_ITERATIONS = 10;
|
||||
const SUMMARY_BUCKETS = [
|
||||
{ label: ">=100ms", minMs: 100 },
|
||||
{ label: "10-100ms", minMs: 10 },
|
||||
{ label: "1-10ms", minMs: 1 },
|
||||
{ label: "<1ms", minMs: 0 },
|
||||
];
|
||||
|
||||
interface CaseResult {
|
||||
name: string;
|
||||
@@ -55,6 +61,7 @@ async function main() {
|
||||
);
|
||||
|
||||
printResults(results, skipped);
|
||||
printSummary(results, skipped);
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -131,10 +138,23 @@ function erroredResult(name: string, error: unknown): CaseResult {
|
||||
function stats(durations: number[]) {
|
||||
const sorted = [...durations].sort((a, b) => a - b);
|
||||
const mean = sorted.reduce((acc, cur) => acc + cur, 0) / sorted.length;
|
||||
const p95 =
|
||||
sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * 0.95) - 1)];
|
||||
|
||||
return { min: sorted[0], mean, p95 };
|
||||
return { min: sorted[0], mean, p95: percentile(sorted, 0.95) };
|
||||
}
|
||||
|
||||
function percentile(sortedValues: number[], fraction: number) {
|
||||
const index = Math.ceil(sortedValues.length * fraction) - 1;
|
||||
|
||||
return sortedValues[Math.min(sortedValues.length - 1, Math.max(0, index))];
|
||||
}
|
||||
|
||||
function geometricMean(values: number[]) {
|
||||
const logSum = values.reduce(
|
||||
(acc, cur) => acc + Math.log(Math.max(cur, Number.EPSILON)),
|
||||
0,
|
||||
);
|
||||
|
||||
return Math.exp(logSum / values.length);
|
||||
}
|
||||
|
||||
function printResults(results: CaseResult[], skipped: string[]) {
|
||||
@@ -176,6 +196,45 @@ function printResults(results: CaseResult[], skipped: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function printSummary(results: CaseResult[], skipped: string[]) {
|
||||
const timed = results.filter((result) => !result.error);
|
||||
if (timed.length === 0) return;
|
||||
|
||||
const means = timed.map((result) => result.mean).sort((a, b) => a - b);
|
||||
const total = means.reduce((acc, cur) => acc + cur, 0);
|
||||
const slowest = timed.reduce((acc, cur) => (cur.mean > acc.mean ? cur : acc));
|
||||
|
||||
const rows: Array<[string, string]> = [
|
||||
[
|
||||
"cases timed",
|
||||
`${timed.length} (${skipped.length} skipped, ${results.length - timed.length} errored)`,
|
||||
],
|
||||
["total", formatMs(total)],
|
||||
["geometric mean", formatMs(geometricMean(means))],
|
||||
["median", formatMs(percentile(means, 0.5))],
|
||||
["p95", formatMs(percentile(means, 0.95))],
|
||||
["slowest", `${formatMs(slowest.mean)} (${slowest.name})`],
|
||||
];
|
||||
|
||||
let unbucketed = means;
|
||||
for (const bucket of SUMMARY_BUCKETS) {
|
||||
const inBucket = unbucketed.filter((mean) => mean >= bucket.minMs);
|
||||
rows.push([bucket.label, `${inBucket.length} cases`]);
|
||||
unbucketed = unbucketed.filter((mean) => mean < bucket.minMs);
|
||||
}
|
||||
|
||||
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
||||
|
||||
logger.info("");
|
||||
logger.info("Summary (mean per case)");
|
||||
for (const [label, value] of rows) {
|
||||
logger.info(`${label.padEnd(labelWidth)} ${value}`);
|
||||
}
|
||||
logger.info(
|
||||
"total is dominated by the slowest cases, geometric mean weighs every case equally",
|
||||
);
|
||||
}
|
||||
|
||||
function formatMs(ms: number) {
|
||||
return `${roundToNDecimalPlaces(ms)}ms`;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepos
|
||||
import * as LFGRepository from "~/features/lfg/LFGRepository.server";
|
||||
import * as LiveStreamRepository from "~/features/live-streams/LiveStreamRepository.server";
|
||||
import * as MatchProfileRepository from "~/features/match-profile/MatchProfileRepository.server";
|
||||
import * as MmrSkillRepository from "~/features/mmr/SkillRepository.server";
|
||||
import * as SkillRepository from "~/features/mmr/SkillRepository.server";
|
||||
import * as NotificationRepository from "~/features/notifications/NotificationRepository.server";
|
||||
import * as PlusSuggestionRepository from "~/features/plus-suggestions/PlusSuggestionRepository.server";
|
||||
import * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
|
||||
@@ -38,6 +38,7 @@ import * as TournamentMatchVodRepository from "~/features/tournament-bracket/Tou
|
||||
import * as TournamentLFGRepository from "~/features/tournament-lfg/TournamentLFGRepository.server";
|
||||
import * as TournamentMatchRepository from "~/features/tournament-match/TournamentMatchRepository.server";
|
||||
import * as TournamentOrganizationRepository from "~/features/tournament-organization/TournamentOrganizationRepository.server";
|
||||
import * as TrophyRepository from "~/features/trophies/TrophyRepository.server";
|
||||
import * as UserCardRepository from "~/features/user-card/UserCardRepository.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import * as VodRepository from "~/features/vods/VodRepository.server";
|
||||
@@ -79,26 +80,28 @@ export function buildCases(fx: Fixtures): {
|
||||
}
|
||||
|
||||
// AdminRepository
|
||||
addStatic("AdminRepository.allBannedUsers", () =>
|
||||
AdminRepository.allBannedUsers(),
|
||||
addStatic("AdminRepository.findAllBannedUsers", () =>
|
||||
AdminRepository.findAllBannedUsers(),
|
||||
);
|
||||
add("AdminRepository.findModeNoteById", fx.modNoteId, (id) =>
|
||||
AdminRepository.findModeNoteById(id),
|
||||
add("AdminRepository.findModNoteById", fx.modNoteId, (id) =>
|
||||
AdminRepository.findModNoteById(id),
|
||||
);
|
||||
|
||||
// ExternalStreamRepository
|
||||
addStatic("ExternalStreamRepository.all", () =>
|
||||
ExternalStreamRepository.all(),
|
||||
addStatic("ExternalStreamRepository.findAll", () =>
|
||||
ExternalStreamRepository.findAll(),
|
||||
);
|
||||
addStatic("ExternalStreamRepository.forSidebar", () =>
|
||||
ExternalStreamRepository.forSidebar(),
|
||||
addStatic("ExternalStreamRepository.findAllForSidebar", () =>
|
||||
ExternalStreamRepository.findAllForSidebar(),
|
||||
);
|
||||
|
||||
// ApiRepository
|
||||
add("ApiRepository.findTokenByUserId", fx.apiTokenUserId, (userId) =>
|
||||
ApiRepository.findTokenByUserId(userId, "read"),
|
||||
);
|
||||
addStatic("ApiRepository.allApiTokens", () => ApiRepository.allApiTokens());
|
||||
addStatic("ApiRepository.findAllApiTokens", () =>
|
||||
ApiRepository.findAllApiTokens(),
|
||||
);
|
||||
|
||||
// ArtRepository
|
||||
addStatic("ArtRepository.findShowcaseArts", () =>
|
||||
@@ -147,7 +150,7 @@ export function buildCases(fx: Fixtures): {
|
||||
);
|
||||
|
||||
// BadgeRepository
|
||||
addStatic("BadgeRepository.all", () => BadgeRepository.all());
|
||||
addStatic("BadgeRepository.findAll", () => BadgeRepository.findAll());
|
||||
add("BadgeRepository.findById", fx.heavyBadgeId, (badgeId) =>
|
||||
BadgeRepository.findById(badgeId),
|
||||
);
|
||||
@@ -165,30 +168,31 @@ export function buildCases(fx: Fixtures): {
|
||||
);
|
||||
|
||||
// BuildRepository
|
||||
add("BuildRepository.allByUserId", fx.heavyBuildUserId, (userId) =>
|
||||
BuildRepository.allByUserId(userId, {
|
||||
add("BuildRepository.findAllByUserId", fx.heavyBuildUserId, (userId) =>
|
||||
BuildRepository.findAllByUserId(userId, {
|
||||
showPrivate: true,
|
||||
sortAbilities: true,
|
||||
}),
|
||||
);
|
||||
add("BuildRepository.ownerIdById", fx.buildId, (buildId) =>
|
||||
BuildRepository.ownerIdById(buildId),
|
||||
add("BuildRepository.findOwnerIdById", fx.buildId, (buildId) =>
|
||||
BuildRepository.findOwnerIdById(buildId),
|
||||
);
|
||||
addStatic("BuildRepository.abilityPointAverages.all", () =>
|
||||
BuildRepository.abilityPointAverages(),
|
||||
addStatic("BuildRepository.findAllAbilityPointAverages.all", () =>
|
||||
BuildRepository.findAllAbilityPointAverages(),
|
||||
);
|
||||
add(
|
||||
"BuildRepository.abilityPointAverages.byWeapon",
|
||||
"BuildRepository.findAllAbilityPointAverages.byWeapon",
|
||||
fx.heavyWeaponSplId,
|
||||
(weaponSplId) => BuildRepository.abilityPointAverages(weaponSplId),
|
||||
(weaponSplId) => BuildRepository.findAllAbilityPointAverages(weaponSplId),
|
||||
);
|
||||
add(
|
||||
"BuildRepository.popularAbilitiesByWeaponId",
|
||||
"BuildRepository.findAllPopularAbilitiesByWeaponId",
|
||||
fx.heavyWeaponSplId,
|
||||
(weaponSplId) => BuildRepository.popularAbilitiesByWeaponId(weaponSplId),
|
||||
(weaponSplId) =>
|
||||
BuildRepository.findAllPopularAbilitiesByWeaponId(weaponSplId),
|
||||
);
|
||||
add("BuildRepository.allByWeaponId", fx.heavyWeaponSplId, (weaponSplId) =>
|
||||
BuildRepository.allByWeaponId(weaponSplId, {
|
||||
add("BuildRepository.findAllByWeaponId", fx.heavyWeaponSplId, (weaponSplId) =>
|
||||
BuildRepository.findAllByWeaponId(weaponSplId, {
|
||||
limit: 60,
|
||||
sortAbilities: true,
|
||||
}),
|
||||
@@ -276,8 +280,8 @@ export function buildCases(fx: Fixtures): {
|
||||
addStatic("ImageRepository.countAllUnvalidated", () =>
|
||||
ImageRepository.countAllUnvalidated(),
|
||||
);
|
||||
addStatic("ImageRepository.unvalidatedImages", () =>
|
||||
ImageRepository.unvalidatedImages(),
|
||||
addStatic("ImageRepository.findAllUnvalidated", () =>
|
||||
ImageRepository.findAllUnvalidated(),
|
||||
);
|
||||
add(
|
||||
"ImageRepository.countUnvalidatedBySubmitterUserId",
|
||||
@@ -286,40 +290,42 @@ export function buildCases(fx: Fixtures): {
|
||||
);
|
||||
|
||||
// LeaderboardRepository
|
||||
add("LeaderboardRepository.teamLeaderboardBySeason", fx.sq, (sq) =>
|
||||
LeaderboardRepository.teamLeaderboardBySeason({
|
||||
add("LeaderboardRepository.findTeamLeaderboardBySeason", fx.sq, (sq) =>
|
||||
LeaderboardRepository.findTeamLeaderboardBySeason({
|
||||
season: sq.season,
|
||||
onlyOneEntryPerUser: true,
|
||||
}),
|
||||
);
|
||||
add("LeaderboardRepository.userHasEnoughSqMatches", fx.sq, (sq) =>
|
||||
LeaderboardRepository.userHasEnoughSqMatches(sq.userId),
|
||||
add("LeaderboardRepository.hasEnoughSqMatchesByUserId", fx.sq, (sq) =>
|
||||
LeaderboardRepository.hasEnoughSqMatchesByUserId(sq.userId),
|
||||
);
|
||||
add("LeaderboardRepository.seasonsParticipatedInByUserId", fx.sq, (sq) =>
|
||||
LeaderboardRepository.seasonsParticipatedInByUserId(sq.userId),
|
||||
add("LeaderboardRepository.findSeasonsParticipatedInByUserId", fx.sq, (sq) =>
|
||||
LeaderboardRepository.findSeasonsParticipatedInByUserId(sq.userId),
|
||||
);
|
||||
addStatic("LeaderboardRepository.allXPLeaderboard", () =>
|
||||
LeaderboardRepository.allXPLeaderboard(),
|
||||
addStatic("LeaderboardRepository.findAllXPLeaderboard", () =>
|
||||
LeaderboardRepository.findAllXPLeaderboard(),
|
||||
);
|
||||
addStatic("LeaderboardRepository.modeXPLeaderboard", () =>
|
||||
LeaderboardRepository.modeXPLeaderboard("SZ"),
|
||||
addStatic("LeaderboardRepository.findModeXPLeaderboard", () =>
|
||||
LeaderboardRepository.findModeXPLeaderboard("SZ"),
|
||||
);
|
||||
add(
|
||||
"LeaderboardRepository.weaponXPLeaderboard",
|
||||
"LeaderboardRepository.findWeaponXPLeaderboard",
|
||||
fx.heavyWeaponSplId,
|
||||
(weaponSplId) => LeaderboardRepository.weaponXPLeaderboard(weaponSplId),
|
||||
(weaponSplId) => LeaderboardRepository.findWeaponXPLeaderboard(weaponSplId),
|
||||
);
|
||||
add("LeaderboardRepository.userSPLeaderboard", fx.sq, (sq) =>
|
||||
LeaderboardRepository.userSPLeaderboard(sq.season),
|
||||
add("LeaderboardRepository.findUserSPLeaderboard", fx.sq, (sq) =>
|
||||
LeaderboardRepository.findUserSPLeaderboard(sq.season),
|
||||
);
|
||||
add("LeaderboardRepository.seasonPopularUsersWeapon", fx.sq, (sq) =>
|
||||
LeaderboardRepository.seasonPopularUsersWeapon(sq.season),
|
||||
add("LeaderboardRepository.findSeasonPopularUsersWeapon", fx.sq, (sq) =>
|
||||
LeaderboardRepository.findSeasonPopularUsersWeapon(sq.season),
|
||||
);
|
||||
|
||||
// LFGRepository
|
||||
addStatic("LFGRepository.posts.anon", () => LFGRepository.posts());
|
||||
add("LFGRepository.posts.loggedIn", fx.heavyUser, (user) =>
|
||||
LFGRepository.posts({ id: user.id, plusTier: 1 }),
|
||||
addStatic("LFGRepository.findAllPosts.anon", () =>
|
||||
LFGRepository.findAllPosts(),
|
||||
);
|
||||
add("LFGRepository.findAllPosts.loggedIn", fx.heavyUser, (user) =>
|
||||
LFGRepository.findAllPosts({ id: user.id, plusTier: 1 }),
|
||||
);
|
||||
add("LFGRepository.findByAuthorUserId", fx.lfgAuthorId, (authorId) =>
|
||||
LFGRepository.findByAuthorUserId(authorId),
|
||||
@@ -331,13 +337,43 @@ export function buildCases(fx: Fixtures): {
|
||||
);
|
||||
|
||||
// MatchProfileRepository
|
||||
add("MatchProfileRepository.settingsByUserId", fx.heavyUser, (user) =>
|
||||
MatchProfileRepository.settingsByUserId(user.id),
|
||||
add("MatchProfileRepository.findSettingsByUserId", fx.heavyUser, (user) =>
|
||||
MatchProfileRepository.findSettingsByUserId(user.id),
|
||||
);
|
||||
|
||||
// MmrSkillRepository (app/features/mmr)
|
||||
add("MmrSkillRepository.seasonProgressionByUserId", fx.sq, (sq) =>
|
||||
MmrSkillRepository.seasonProgressionByUserId(sq),
|
||||
// SkillRepository (app/features/mmr)
|
||||
add("SkillRepository.findCurrentUserSkills", fx.skillBatch, (skillBatch) =>
|
||||
SkillRepository.findCurrentUserSkills({
|
||||
season: skillBatch.season,
|
||||
userIds: skillBatch.userIds,
|
||||
}),
|
||||
);
|
||||
add("SkillRepository.findCurrentTeamSkills", fx.skillBatch, (skillBatch) =>
|
||||
SkillRepository.findCurrentTeamSkills({
|
||||
season: skillBatch.season,
|
||||
identifiers: skillBatch.identifiers,
|
||||
}),
|
||||
);
|
||||
add(
|
||||
"SkillRepository.findOrderedUserOrdinalsBySeason",
|
||||
fx.skillBatch,
|
||||
(skillBatch) =>
|
||||
SkillRepository.findOrderedUserOrdinalsBySeason(skillBatch.season),
|
||||
);
|
||||
add("SkillRepository.existsBySeason", fx.skillBatch, (skillBatch) =>
|
||||
SkillRepository.existsBySeason(skillBatch.season),
|
||||
);
|
||||
add("SkillRepository.findSeedingSkills", fx.skillBatch, (skillBatch) =>
|
||||
SkillRepository.findSeedingSkills({
|
||||
type: "RANKED",
|
||||
userIds: skillBatch.userIds,
|
||||
}),
|
||||
);
|
||||
add("SkillRepository.findSeasonProgressionByUserId", fx.sq, (sq) =>
|
||||
SkillRepository.findSeasonProgressionByUserId(sq),
|
||||
);
|
||||
add("SkillRepository.findSeasonActiveDaysByUserId", fx.sq, (sq) =>
|
||||
SkillRepository.findSeasonActiveDaysByUserId(sq),
|
||||
);
|
||||
|
||||
// NotificationRepository
|
||||
@@ -348,9 +384,9 @@ export function buildCases(fx: Fixtures): {
|
||||
NotificationRepository.findAllByType(notification.type),
|
||||
);
|
||||
add(
|
||||
"NotificationRepository.subscriptionsByUserIds",
|
||||
"NotificationRepository.findAllSubscriptionsByUserIds",
|
||||
fx.manyUserIds,
|
||||
(userIds) => NotificationRepository.subscriptionsByUserIds(userIds),
|
||||
(userIds) => NotificationRepository.findAllSubscriptionsByUserIds(userIds),
|
||||
);
|
||||
|
||||
// PlusSuggestionRepository
|
||||
@@ -361,14 +397,16 @@ export function buildCases(fx: Fixtures): {
|
||||
);
|
||||
|
||||
// PlusVotingRepository
|
||||
addStatic("PlusVotingRepository.allPlusTiersFromLatestVoting", () =>
|
||||
PlusVotingRepository.allPlusTiersFromLatestVoting(),
|
||||
addStatic("PlusVotingRepository.findAllPlusTiersFromLatestVoting", () =>
|
||||
PlusVotingRepository.findAllPlusTiersFromLatestVoting(),
|
||||
);
|
||||
add("PlusVotingRepository.resultsByMonthYear", fx.plusVoting, (voting) =>
|
||||
PlusVotingRepository.resultsByMonthYear(voting),
|
||||
add("PlusVotingRepository.findResultsByMonthYear", fx.plusVoting, (voting) =>
|
||||
PlusVotingRepository.findResultsByMonthYear(voting),
|
||||
);
|
||||
add("PlusVotingRepository.usersForVoting", fx.plusTierOneUser, (user) =>
|
||||
PlusVotingRepository.usersForVoting(user),
|
||||
add(
|
||||
"PlusVotingRepository.findAllUsersForVoting",
|
||||
fx.plusTierOneUser,
|
||||
(user) => PlusVotingRepository.findAllUsersForVoting(user),
|
||||
);
|
||||
add("PlusVotingRepository.hasVoted", fx.plusVoting, (voting) =>
|
||||
PlusVotingRepository.hasVoted({
|
||||
@@ -427,23 +465,35 @@ export function buildCases(fx: Fixtures): {
|
||||
|
||||
// GroupMatchContinueVoteRepository
|
||||
add(
|
||||
"GroupMatchContinueVoteRepository.findForGroups",
|
||||
"GroupMatchContinueVoteRepository.findAllByGroupIds",
|
||||
fx.heavyGroupIds,
|
||||
(groupIds) => GroupMatchContinueVoteRepository.findForGroups(groupIds),
|
||||
(groupIds) => GroupMatchContinueVoteRepository.findAllByGroupIds(groupIds),
|
||||
);
|
||||
|
||||
// PlayerStatRepository
|
||||
add("PlayerStatRepository.seasonMapWinrateByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.seasonMapWinrateByUserId(sq),
|
||||
add("PlayerStatRepository.findSeasonMapWinrateByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonMapWinrateByUserId(sq),
|
||||
);
|
||||
add("PlayerStatRepository.seasonSetWinrateByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.seasonSetWinrateByUserId(sq),
|
||||
add("PlayerStatRepository.findSeasonSetWinrateByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonSetWinrateByUserId(sq),
|
||||
);
|
||||
add("PlayerStatRepository.seasonStagesByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.seasonStagesByUserId(sq),
|
||||
add("PlayerStatRepository.findSeasonStagesByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonStagesByUserId(sq),
|
||||
);
|
||||
add("PlayerStatRepository.seasonMatesEnemiesByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.seasonMatesEnemiesByUserId({ ...sq, type: "MATE" }),
|
||||
add("PlayerStatRepository.findSeasonMatesEnemiesByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonMatesEnemiesByUserId({
|
||||
...sq,
|
||||
type: "MATE",
|
||||
}),
|
||||
);
|
||||
add("PlayerStatRepository.findSeasonSetScoresByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonSetScoresByUserId(sq),
|
||||
);
|
||||
add("PlayerStatRepository.findSeasonBestSetsByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonBestSetsByUserId({ ...sq, limit: 3 }),
|
||||
);
|
||||
add("PlayerStatRepository.findSeasonTournamentRunsByUserId", fx.sq, (sq) =>
|
||||
PlayerStatRepository.findSeasonTournamentRunsByUserId(sq),
|
||||
);
|
||||
|
||||
// ReportedWeaponRepository
|
||||
@@ -457,14 +507,16 @@ export function buildCases(fx: Fixtures): {
|
||||
fx.heavyTournamentMatchId,
|
||||
(matchId) => ReportedWeaponRepository.findByTournamentMatchId(matchId),
|
||||
);
|
||||
add("ReportedWeaponRepository.seasonReportedWeaponsByUserId", fx.sq, (sq) =>
|
||||
ReportedWeaponRepository.seasonReportedWeaponsByUserId(sq),
|
||||
add(
|
||||
"ReportedWeaponRepository.findSeasonReportedWeaponsByUserId",
|
||||
fx.sq,
|
||||
(sq) => ReportedWeaponRepository.findSeasonReportedWeaponsByUserId(sq),
|
||||
);
|
||||
add(
|
||||
"ReportedWeaponRepository.weaponUsageStats",
|
||||
"ReportedWeaponRepository.findAllWeaponUsageStats",
|
||||
both(fx.sq, fx.heavyStageModeCombo),
|
||||
([sq, combo]) =>
|
||||
ReportedWeaponRepository.weaponUsageStats({
|
||||
ReportedWeaponRepository.findAllWeaponUsageStats({
|
||||
userId: sq.userId,
|
||||
season: sq.season,
|
||||
mode: combo.mode,
|
||||
@@ -476,36 +528,51 @@ export function buildCases(fx: Fixtures): {
|
||||
add("SQMatchRepository.findById", fx.heavyGroupMatchId, (matchId) =>
|
||||
SQMatchRepository.findById(matchId),
|
||||
);
|
||||
add("SQMatchRepository.seasonResultPagesByUserId", fx.sq, (sq) =>
|
||||
SQMatchRepository.seasonResultPagesByUserId(sq),
|
||||
add("SQMatchRepository.countSeasonResultPagesByUserId", fx.sq, (sq) =>
|
||||
SQMatchRepository.countSeasonResultPagesByUserId(sq),
|
||||
);
|
||||
add("SQMatchRepository.seasonResultsByUserId", fx.sq, (sq) =>
|
||||
SQMatchRepository.seasonResultsByUserId({ ...sq, page: 1 }),
|
||||
add("SQMatchRepository.findSeasonResultsByUserId", fx.sq, (sq) =>
|
||||
SQMatchRepository.findSeasonResultsByUserId({ ...sq, page: 1 }),
|
||||
);
|
||||
add("SQMatchRepository.seasonCanceledMatchesByUserId", fx.sq, (sq) =>
|
||||
SQMatchRepository.seasonCanceledMatchesByUserId(sq),
|
||||
add("SQMatchRepository.findSeasonCanceledMatchesByUserId", fx.sq, (sq) =>
|
||||
SQMatchRepository.findSeasonCanceledMatchesByUserId(sq),
|
||||
);
|
||||
add(
|
||||
"SQMatchRepository.findCancelReportsByGroupMatchId",
|
||||
fx.heavyGroupMatchId,
|
||||
(matchId) => SQMatchRepository.findCancelReportsByGroupMatchId(matchId),
|
||||
);
|
||||
add(
|
||||
"SQMatchRepository.findCancelNominationCountsByUserIds",
|
||||
both(fx.manyUserIds, fx.sq),
|
||||
([userIds, sq]) =>
|
||||
SQMatchRepository.findCancelNominationCountsByUserIds({
|
||||
userIds,
|
||||
season: sq.season,
|
||||
}),
|
||||
);
|
||||
|
||||
// QStreamsRepository
|
||||
addStatic("QStreamsRepository.activeMatchPlayers", () =>
|
||||
QStreamsRepository.activeMatchPlayers(),
|
||||
addStatic("QStreamsRepository.findAllActiveMatchPlayers", () =>
|
||||
QStreamsRepository.findAllActiveMatchPlayers(),
|
||||
);
|
||||
|
||||
// PrivateUserNoteRepository (relies on the benchmark's actor context)
|
||||
addStatic("PrivateUserNoteRepository.ownNotes.all", () =>
|
||||
PrivateUserNoteRepository.ownNotes(),
|
||||
addStatic("PrivateUserNoteRepository.findAllOwn.all", () =>
|
||||
PrivateUserNoteRepository.findAllOwn(),
|
||||
);
|
||||
add(
|
||||
"PrivateUserNoteRepository.ownNotes.byTargets",
|
||||
"PrivateUserNoteRepository.findAllOwn.byTargets",
|
||||
fx.manyUserIds,
|
||||
(userIds) => PrivateUserNoteRepository.ownNotes(userIds),
|
||||
(userIds) => PrivateUserNoteRepository.findAllOwn(userIds),
|
||||
);
|
||||
|
||||
// SQGroupRepository
|
||||
add(
|
||||
"SQGroupRepository.mapModePreferencesByGroupId",
|
||||
"SQGroupRepository.findMapModePreferencesByGroupId",
|
||||
fx.heavyGroupIds,
|
||||
(groupIds) => SQGroupRepository.mapModePreferencesByGroupId(groupIds[0]),
|
||||
(groupIds) =>
|
||||
SQGroupRepository.findMapModePreferencesByGroupId(groupIds[0]),
|
||||
);
|
||||
addStatic("SQGroupRepository.findCurrentGroups", () =>
|
||||
SQGroupRepository.findCurrentGroups(),
|
||||
@@ -513,14 +580,14 @@ export function buildCases(fx: Fixtures): {
|
||||
addStatic("SQGroupRepository.findActiveGroupMembers", () =>
|
||||
SQGroupRepository.findActiveGroupMembers(),
|
||||
);
|
||||
add("SQGroupRepository.allLikesByGroupId", fx.heavyGroupIds, (groupIds) =>
|
||||
SQGroupRepository.allLikesByGroupId(groupIds[0]),
|
||||
add("SQGroupRepository.findAllLikesByGroupId", fx.heavyGroupIds, (groupIds) =>
|
||||
SQGroupRepository.findAllLikesByGroupId(groupIds[0]),
|
||||
);
|
||||
add("SQGroupRepository.friendsAndTeammates", fx.sq, (sq) =>
|
||||
SQGroupRepository.friendsAndTeammates(sq.userId),
|
||||
add("SQGroupRepository.findFriendsAndTeammates", fx.sq, (sq) =>
|
||||
SQGroupRepository.findFriendsAndTeammates(sq.userId),
|
||||
);
|
||||
add("SQGroupRepository.mapModePreferencesBySeasonNth", fx.sq, (sq) =>
|
||||
SQGroupRepository.mapModePreferencesBySeasonNth(sq.season),
|
||||
add("SQGroupRepository.findAllMapModePreferencesBySeasonNth", fx.sq, (sq) =>
|
||||
SQGroupRepository.findAllMapModePreferencesBySeasonNth(sq.season),
|
||||
);
|
||||
addStatic("SQGroupRepository.findRecentlyFinishedMatches", () =>
|
||||
SQGroupRepository.findRecentlyFinishedMatches(),
|
||||
@@ -555,16 +622,19 @@ export function buildCases(fx: Fixtures): {
|
||||
add("TeamRepository.findResultsById", fx.heavyTeam, (team) =>
|
||||
TeamRepository.findResultsById(team.id),
|
||||
);
|
||||
add("TeamRepository.teamsByMemberUserId", fx.heavyTeam, (team) =>
|
||||
TeamRepository.teamsByMemberUserId(team.memberUserId),
|
||||
add("TeamRepository.findAllByMemberUserId", fx.heavyTeam, (team) =>
|
||||
TeamRepository.findAllByMemberUserId(team.memberUserId),
|
||||
);
|
||||
|
||||
// XRankPlacementRepository
|
||||
add("XRankPlacementRepository.isPlayerLinkedByUserId", fx.xrank, (xrank) =>
|
||||
XRankPlacementRepository.isPlayerLinkedByUserId(xrank.userId),
|
||||
);
|
||||
add("XRankPlacementRepository.peakVerifiedXpByUserId", fx.xrank, (xrank) =>
|
||||
XRankPlacementRepository.peakVerifiedXpByUserId(xrank.userId),
|
||||
add(
|
||||
"XRankPlacementRepository.findPeakVerifiedXpByUserId",
|
||||
fx.xrank,
|
||||
(xrank) =>
|
||||
XRankPlacementRepository.findPeakVerifiedXpByUserId(xrank.userId),
|
||||
);
|
||||
add("XRankPlacementRepository.findPlacementsOfMonth", fx.xrank, (xrank) =>
|
||||
XRankPlacementRepository.findPlacementsOfMonth({
|
||||
@@ -580,8 +650,8 @@ export function buildCases(fx: Fixtures): {
|
||||
add("XRankPlacementRepository.findPlacementsByUserId", fx.xrank, (xrank) =>
|
||||
XRankPlacementRepository.findPlacementsByUserId(xrank.userId),
|
||||
);
|
||||
addStatic("XRankPlacementRepository.monthYears", () =>
|
||||
XRankPlacementRepository.monthYears(),
|
||||
addStatic("XRankPlacementRepository.findAllMonthYears", () =>
|
||||
XRankPlacementRepository.findAllMonthYears(),
|
||||
);
|
||||
add("XRankPlacementRepository.findPeaksByUserId", fx.xrank, (xrank) =>
|
||||
XRankPlacementRepository.findPeaksByUserId(xrank.userId, "both"),
|
||||
@@ -628,14 +698,14 @@ export function buildCases(fx: Fixtures): {
|
||||
add("TournamentLFGRepository.findSubGroups", fx.lfgTournament, (lfg) =>
|
||||
TournamentLFGRepository.findSubGroups(lfg.tournamentId),
|
||||
);
|
||||
add("TournamentLFGRepository.allLikesByTeamId", fx.lfgTournament, (lfg) =>
|
||||
TournamentLFGRepository.allLikesByTeamId(lfg.teamId),
|
||||
add("TournamentLFGRepository.findAllLikesByTeamId", fx.lfgTournament, (lfg) =>
|
||||
TournamentLFGRepository.findAllLikesByTeamId(lfg.teamId),
|
||||
);
|
||||
add(
|
||||
"TournamentLFGRepository.getSubsForTournament",
|
||||
fx.subsTournamentId,
|
||||
(tournamentId) =>
|
||||
TournamentLFGRepository.getSubsForTournament(tournamentId),
|
||||
"TournamentLFGRepository.findAllSubsByTournamentId",
|
||||
fx.lfgTournament,
|
||||
(lfg) =>
|
||||
TournamentLFGRepository.findAllSubsByTournamentId(lfg.tournamentId),
|
||||
);
|
||||
|
||||
// TournamentMatchRepository
|
||||
@@ -655,16 +725,18 @@ export function buildCases(fx: Fixtures): {
|
||||
(matchId) => TournamentMatchRepository.findResultsByMatchId(matchId),
|
||||
);
|
||||
add(
|
||||
"TournamentMatchRepository.allResultsByTournamentId",
|
||||
"TournamentMatchRepository.findAllResultsByTournamentId",
|
||||
fx.heavyTournamentId,
|
||||
(tournamentId) =>
|
||||
TournamentMatchRepository.allResultsByTournamentId(tournamentId),
|
||||
TournamentMatchRepository.findAllResultsByTournamentId(tournamentId),
|
||||
);
|
||||
add(
|
||||
"TournamentMatchRepository.userParticipationByTournamentId",
|
||||
"TournamentMatchRepository.findUserParticipationByTournamentId",
|
||||
fx.heavyTournamentId,
|
||||
(tournamentId) =>
|
||||
TournamentMatchRepository.userParticipationByTournamentId(tournamentId),
|
||||
TournamentMatchRepository.findUserParticipationByTournamentId(
|
||||
tournamentId,
|
||||
),
|
||||
);
|
||||
add(
|
||||
"TournamentMatchRepository.findByTournamentTeamId",
|
||||
@@ -728,10 +800,12 @@ export function buildCases(fx: Fixtures): {
|
||||
}),
|
||||
);
|
||||
add(
|
||||
"TournamentOrganizationRepository.allBannedUsersByOrganizationId",
|
||||
"TournamentOrganizationRepository.findAllBannedUsersByOrganizationId",
|
||||
fx.heavyOrg,
|
||||
(org) =>
|
||||
TournamentOrganizationRepository.allBannedUsersByOrganizationId(org.id),
|
||||
TournamentOrganizationRepository.findAllBannedUsersByOrganizationId(
|
||||
org.id,
|
||||
),
|
||||
);
|
||||
add(
|
||||
"TournamentOrganizationRepository.isUserBannedByOrganization",
|
||||
@@ -768,8 +842,10 @@ export function buildCases(fx: Fixtures): {
|
||||
add("SavedCalendarEventRepository.countByUserId", fx.heavyUser, (user) =>
|
||||
SavedCalendarEventRepository.countByUserId(user.id),
|
||||
);
|
||||
add("SavedCalendarEventRepository.upcoming", fx.heavyUser, (user) =>
|
||||
SavedCalendarEventRepository.upcoming(user.id),
|
||||
add(
|
||||
"SavedCalendarEventRepository.findAllUpcomingByUserId",
|
||||
fx.heavyUser,
|
||||
(user) => SavedCalendarEventRepository.findAllUpcomingByUserId(user.id),
|
||||
);
|
||||
|
||||
// TournamentAuditLogRepository
|
||||
@@ -851,13 +927,13 @@ export function buildCases(fx: Fixtures): {
|
||||
(tournamentId) => TournamentRepository.findPreparedMapsById(tournamentId),
|
||||
);
|
||||
add(
|
||||
"TournamentRepository.relatedUsersByTournamentIds",
|
||||
"TournamentRepository.findRelatedUsersByTournamentIds",
|
||||
fx.recentTournamentIds,
|
||||
(tournamentIds) =>
|
||||
TournamentRepository.relatedUsersByTournamentIds(tournamentIds),
|
||||
TournamentRepository.findRelatedUsersByTournamentIds(tournamentIds),
|
||||
);
|
||||
addStatic("TournamentRepository.forShowcase", () =>
|
||||
TournamentRepository.forShowcase(),
|
||||
addStatic("TournamentRepository.findAllForShowcase", () =>
|
||||
TournamentRepository.findAllForShowcase(),
|
||||
);
|
||||
add(
|
||||
"TournamentRepository.findAllBetweenTwoTimestamps",
|
||||
@@ -865,21 +941,21 @@ export function buildCases(fx: Fixtures): {
|
||||
(window) => TournamentRepository.findAllBetweenTwoTimestamps(window),
|
||||
);
|
||||
add(
|
||||
"TournamentRepository.topThreeResultsByTournamentId",
|
||||
"TournamentRepository.findTopThreeResultsByTournamentId",
|
||||
fx.heavyTournamentId,
|
||||
(tournamentId) =>
|
||||
TournamentRepository.topThreeResultsByTournamentId(tournamentId),
|
||||
TournamentRepository.findTopThreeResultsByTournamentId(tournamentId),
|
||||
);
|
||||
add(
|
||||
"TournamentRepository.friendCodesByTournamentId",
|
||||
"TournamentRepository.findFriendCodesByTournamentId",
|
||||
fx.heavyTournamentId,
|
||||
(tournamentId) =>
|
||||
TournamentRepository.friendCodesByTournamentId(tournamentId),
|
||||
TournamentRepository.findFriendCodesByTournamentId(tournamentId),
|
||||
);
|
||||
add(
|
||||
"TournamentRepository.pickBanEventsByMatchId",
|
||||
"TournamentRepository.findPickBanEventsByMatchId",
|
||||
fx.heavyTournamentMatchId,
|
||||
(matchId) => TournamentRepository.pickBanEventsByMatchId(matchId),
|
||||
(matchId) => TournamentRepository.findPickBanEventsByMatchId(matchId),
|
||||
);
|
||||
addStatic("TournamentRepository.searchByName", () =>
|
||||
TournamentRepository.searchByName(SEARCH_QUERY),
|
||||
@@ -901,24 +977,42 @@ export function buildCases(fx: Fixtures): {
|
||||
TournamentTeamRepository.findRecentlyPlayedMapsByIds({ teamIds }),
|
||||
);
|
||||
|
||||
// TrophyRepository
|
||||
addStatic("TrophyRepository.all", () => TrophyRepository.all());
|
||||
add("TrophyRepository.findById", fx.trophy, (trophy) =>
|
||||
TrophyRepository.findById(trophy.heavyTrophyId),
|
||||
);
|
||||
add("TrophyRepository.findByOwnerUserId", fx.trophy, (trophy) =>
|
||||
TrophyRepository.findByOwnerUserId(trophy.ownerUserId),
|
||||
);
|
||||
add("TrophyRepository.findTournamentsByTrophyId", fx.trophy, (trophy) =>
|
||||
TrophyRepository.findTournamentsByTrophyId(trophy.heavyTrophyId),
|
||||
);
|
||||
add("TrophyRepository.findWinsByOwner", fx.trophy, (trophy) =>
|
||||
TrophyRepository.findWinsByOwner(trophy.wins),
|
||||
);
|
||||
|
||||
// UserCardRepository
|
||||
add("UserCardRepository.userCards", fx.manyUserIds, (userIds) =>
|
||||
UserCardRepository.userCards({
|
||||
add("UserCardRepository.findAllByUserIds", fx.manyUserIds, (userIds) =>
|
||||
UserCardRepository.findAllByUserIds({
|
||||
userIds,
|
||||
include: { friendCode: true },
|
||||
includeHiddenStats: true,
|
||||
}),
|
||||
);
|
||||
add("UserCardRepository.cardEditExtras", fx.heavyUser, (user) =>
|
||||
UserCardRepository.cardEditExtras(user.id),
|
||||
add("UserCardRepository.findCardEditExtrasByUserId", fx.heavyUser, (user) =>
|
||||
UserCardRepository.findCardEditExtrasByUserId(user.id),
|
||||
);
|
||||
|
||||
// UserRepository
|
||||
add("UserRepository.identifierToUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.identifierToUserId(user.identifier),
|
||||
add("UserRepository.findIdByIdentifier", fx.heavyUser, (user) =>
|
||||
UserRepository.findIdByIdentifier(user.identifier),
|
||||
);
|
||||
add("UserRepository.identifierToBuildFields", fx.heavyUser, (user) =>
|
||||
UserRepository.identifierToBuildFields(user.identifier),
|
||||
add("UserRepository.findCountriesByUserIds", fx.skillBatch, (skillBatch) =>
|
||||
UserRepository.findCountriesByUserIds(skillBatch.userIds),
|
||||
);
|
||||
add("UserRepository.findBuildFieldsByIdentifier", fx.heavyUser, (user) =>
|
||||
UserRepository.findBuildFieldsByIdentifier(user.identifier),
|
||||
);
|
||||
add("UserRepository.findLayoutDataByIdentifier", fx.heavyUser, (user) =>
|
||||
UserRepository.findLayoutDataByIdentifier(user.identifier, user.id),
|
||||
@@ -926,20 +1020,20 @@ export function buildCases(fx: Fixtures): {
|
||||
add("UserRepository.findProfileByIdentifier", fx.heavyUser, (user) =>
|
||||
UserRepository.findProfileByIdentifier(user.identifier),
|
||||
);
|
||||
add("UserRepository.ownedBadgesByUserId", fx.badgeOwnerUserId, (userId) =>
|
||||
UserRepository.ownedBadgesByUserId(userId),
|
||||
add("UserRepository.findOwnedBadgesByUserId", fx.badgeOwnerUserId, (userId) =>
|
||||
UserRepository.findOwnedBadgesByUserId(userId),
|
||||
);
|
||||
add("UserRepository.widgetsEnabledByIdentifier", fx.heavyUser, (user) =>
|
||||
UserRepository.widgetsEnabledByIdentifier(user.identifier),
|
||||
add("UserRepository.findEnabledWidgetsByIdentifier", fx.heavyUser, (user) =>
|
||||
UserRepository.findEnabledWidgetsByIdentifier(user.identifier),
|
||||
);
|
||||
add("UserRepository.preferencesByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.preferencesByUserId(user.id),
|
||||
add("UserRepository.findPreferencesByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findPreferencesByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.storedWidgetsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.storedWidgetsByUserId(user.id),
|
||||
add("UserRepository.findStoredWidgetsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findStoredWidgetsByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.widgetsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.widgetsByUserId(user.identifier),
|
||||
add("UserRepository.findWidgetsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findWidgetsByUserId(user.identifier),
|
||||
);
|
||||
add("UserRepository.findByCustomUrl", fx.userCustomUrl, (customUrl) =>
|
||||
UserRepository.findByCustomUrl(customUrl),
|
||||
@@ -978,32 +1072,32 @@ export function buildCases(fx: Fixtures): {
|
||||
add("UserRepository.searchExact", fx.userCustomUrl, (customUrl) =>
|
||||
UserRepository.searchExact({ customUrl }),
|
||||
);
|
||||
add("UserRepository.currentFriendCodeByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.currentFriendCodeByUserId(user.id),
|
||||
add("UserRepository.findCurrentFriendCodeByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findCurrentFriendCodeByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.friendCodesByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.friendCodesByUserId(user.id),
|
||||
add("UserRepository.findFriendCodesByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findFriendCodesByUserId(user.id),
|
||||
);
|
||||
addStatic("UserRepository.allCurrentFriendCodes", () =>
|
||||
UserRepository.allCurrentFriendCodes(),
|
||||
addStatic("UserRepository.findAllCurrentFriendCodes", () =>
|
||||
UserRepository.findAllCurrentFriendCodes(),
|
||||
);
|
||||
add("UserRepository.inGameNameByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.inGameNameByUserId(user.id),
|
||||
add("UserRepository.findInGameNameByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findInGameNameByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.patronSinceByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.patronSinceByUserId(user.id),
|
||||
add("UserRepository.findPatronStartedAtByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findPatronStartedAtByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.joinOrderByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.joinOrderByUserId(user.id),
|
||||
add("UserRepository.findJoinOrderByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findJoinOrderByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.commissionsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.commissionsByUserId(user.id),
|
||||
add("UserRepository.findCommissionsByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findCommissionsByUserId(user.id),
|
||||
);
|
||||
add("UserRepository.anyUserPrefersNoScreen", fx.manyUserIds, (userIds) =>
|
||||
UserRepository.anyUserPrefersNoScreen(userIds),
|
||||
);
|
||||
add("UserRepository.socialLinksByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.socialLinksByUserId(user.id),
|
||||
add("UserRepository.findSocialLinksByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findSocialLinksByUserId(user.id),
|
||||
);
|
||||
add(
|
||||
"UserRepository.findIdsByTwitchUsernames",
|
||||
@@ -1011,8 +1105,8 @@ export function buildCases(fx: Fixtures): {
|
||||
(twitchUsernames) =>
|
||||
UserRepository.findIdsByTwitchUsernames(twitchUsernames),
|
||||
);
|
||||
add("UserRepository.weaponPoolByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.weaponPoolByUserId(user.id),
|
||||
add("UserRepository.findWeaponPoolByUserId", fx.heavyUser, (user) =>
|
||||
UserRepository.findWeaponPoolByUserId(user.id),
|
||||
);
|
||||
|
||||
// VodRepository
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { sub } from "date-fns";
|
||||
import { db } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import type { SkillTeamIdentifier } from "~/features/mmr/mmr-utils";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
ModeShort,
|
||||
@@ -18,6 +19,11 @@ export interface Fixtures {
|
||||
buildId: number | null;
|
||||
heavyWeaponSplId: MainWeaponId | null;
|
||||
sq: { userId: number; season: number } | null;
|
||||
skillBatch: {
|
||||
season: number;
|
||||
userIds: number[];
|
||||
identifiers: SkillTeamIdentifier[];
|
||||
} | null;
|
||||
heavyGroupMatchId: number | null;
|
||||
heavyGroupIds: [number, number] | null;
|
||||
heavyStageModeCombo: { stageId: StageId; mode: ModeShort } | null;
|
||||
@@ -56,6 +62,11 @@ export interface Fixtures {
|
||||
badgeOwnerUserId: number | null;
|
||||
badgeAuthorId: number | null;
|
||||
badgeManagerUserId: number | null;
|
||||
trophy: {
|
||||
heavyTrophyId: number;
|
||||
ownerUserId: number;
|
||||
wins: { trophyId: number; userId: number };
|
||||
} | null;
|
||||
manyUserIds: number[] | null;
|
||||
notification: { userId: number; type: Tables["Notification"]["type"] } | null;
|
||||
heavyAssociation: {
|
||||
@@ -65,7 +76,6 @@ export interface Fixtures {
|
||||
} | null;
|
||||
lfgAuthorId: number | null;
|
||||
lfgTournament: { tournamentId: number; teamId: number } | null;
|
||||
subsTournamentId: number | null;
|
||||
auditTournamentId: number | null;
|
||||
xrank: {
|
||||
userId: number;
|
||||
@@ -106,6 +116,7 @@ export async function resolveFixtures(): Promise<Fixtures> {
|
||||
buildId: await resolveBuildId(),
|
||||
heavyWeaponSplId: await resolveHeavyWeaponSplId(),
|
||||
sq: await resolveSq(),
|
||||
skillBatch: await resolveSkillBatch(),
|
||||
heavyGroupMatchId,
|
||||
heavyGroupIds: await resolveHeavyGroupIds(heavyGroupMatchId),
|
||||
heavyStageModeCombo: await resolveHeavyStageModeCombo(),
|
||||
@@ -137,12 +148,12 @@ export async function resolveFixtures(): Promise<Fixtures> {
|
||||
badgeOwnerUserId: await resolveBadgeOwnerUserId(),
|
||||
badgeAuthorId: await resolveBadgeAuthorId(),
|
||||
badgeManagerUserId: await resolveBadgeManagerUserId(),
|
||||
trophy: await resolveTrophy(),
|
||||
manyUserIds: await resolveManyUserIds(heavyTournamentId),
|
||||
notification: await resolveNotification(),
|
||||
heavyAssociation: await resolveHeavyAssociation(),
|
||||
lfgAuthorId: await resolveLfgAuthorId(),
|
||||
lfgTournament: await resolveLfgTournament(),
|
||||
subsTournamentId: (await resolveSubsTournamentId()) ?? heavyTournamentId,
|
||||
auditTournamentId: (await resolveAuditTournamentId()) ?? heavyTournamentId,
|
||||
xrank: await resolveXRank(),
|
||||
heavyArtUserId: await resolveHeavyArtUserId(),
|
||||
@@ -282,6 +293,50 @@ async function resolveSq() {
|
||||
return { userId: userRow.userId, season: seasonRow.season };
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch sizes a big tournament's finalization asks for: every player of the event and
|
||||
* every roster that appeared on a map (several per set, hence far more than teams).
|
||||
*/
|
||||
const SKILL_BATCH_USERS = 300;
|
||||
const SKILL_BATCH_IDENTIFIERS = 500;
|
||||
|
||||
async function resolveSkillBatch() {
|
||||
const seasonRow = await db
|
||||
.selectFrom("Skill")
|
||||
.select(({ fn }) => ["season", fn.countAll<number>().as("count")])
|
||||
.groupBy("season")
|
||||
.orderBy("count", "desc")
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
if (!seasonRow) return null;
|
||||
|
||||
const userRows = await db
|
||||
.selectFrom("Skill")
|
||||
.select("userId")
|
||||
.distinct()
|
||||
.where("season", "=", seasonRow.season)
|
||||
.where("userId", "is not", null)
|
||||
.limit(SKILL_BATCH_USERS)
|
||||
.execute();
|
||||
|
||||
const identifierRows = await db
|
||||
.selectFrom("Skill")
|
||||
.select("identifier")
|
||||
.distinct()
|
||||
.where("season", "=", seasonRow.season)
|
||||
.where("identifier", "is not", null)
|
||||
.limit(SKILL_BATCH_IDENTIFIERS)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
season: seasonRow.season,
|
||||
userIds: userRows.map((row) => row.userId as number),
|
||||
identifiers: identifierRows.map(
|
||||
(row) => row.identifier as SkillTeamIdentifier,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveHeavyGroupMatchId() {
|
||||
const row = await db
|
||||
.selectFrom("GroupMatchMap")
|
||||
@@ -505,7 +560,7 @@ async function resolveCalendarAuthorId() {
|
||||
async function resolveCalendarWindow() {
|
||||
const row = await db
|
||||
.selectFrom("CalendarEventDate")
|
||||
.select(({ fn }) => fn.max("startTime").as("maxStartTime"))
|
||||
.select(({ fn }) => fn.max("startsAt").as("maxStartTime"))
|
||||
.executeTakeFirst();
|
||||
if (typeof row?.maxStartTime !== "number") return null;
|
||||
|
||||
@@ -517,7 +572,7 @@ async function resolveCalendarWindow() {
|
||||
async function resolveScrimWindow() {
|
||||
const row = await db
|
||||
.selectFrom("ScrimPost")
|
||||
.select(({ fn }) => fn.max("at").as("maxAt"))
|
||||
.select(({ fn }) => fn.max("startsAt").as("maxAt"))
|
||||
.executeTakeFirst();
|
||||
if (typeof row?.maxAt !== "number") return null;
|
||||
|
||||
@@ -596,15 +651,15 @@ async function resolveHeavyOrg() {
|
||||
"CalendarEventDate.eventId",
|
||||
"CalendarEvent.id",
|
||||
)
|
||||
.select(["CalendarEvent.name", "CalendarEventDate.startTime"])
|
||||
.select(["CalendarEvent.name", "CalendarEventDate.startsAt"])
|
||||
.where("CalendarEvent.organizationId", "=", orgRow.id)
|
||||
.orderBy("CalendarEventDate.startTime", "desc")
|
||||
.orderBy("CalendarEventDate.startsAt", "desc")
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
if (!latestEvent) return null;
|
||||
|
||||
const latestEventDate = databaseTimestampToDate(latestEvent.startTime);
|
||||
const windowStart = latestEvent.startTime - 90 * 24 * 60 * 60;
|
||||
const latestEventDate = databaseTimestampToDate(latestEvent.startsAt);
|
||||
const windowStart = latestEvent.startsAt - 90 * 24 * 60 * 60;
|
||||
|
||||
return {
|
||||
id: orgRow.id,
|
||||
@@ -614,7 +669,7 @@ async function resolveHeavyOrg() {
|
||||
eventMonth: latestEventDate.getUTCMonth(),
|
||||
eventYear: latestEventDate.getUTCFullYear(),
|
||||
windowStart,
|
||||
windowEnd: latestEvent.startTime,
|
||||
windowEnd: latestEvent.startsAt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -734,6 +789,45 @@ async function resolveBadgeManagerUserId() {
|
||||
return row?.userId ?? null;
|
||||
}
|
||||
|
||||
async function resolveTrophy() {
|
||||
const heavyTrophyRow = await db
|
||||
.selectFrom("TrophyOwner")
|
||||
.select(({ fn }) => ["trophyId", fn.countAll<number>().as("count")])
|
||||
.groupBy("trophyId")
|
||||
.orderBy("count", "desc")
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
if (!heavyTrophyRow) return null;
|
||||
|
||||
const ownerRow = await db
|
||||
.selectFrom("TrophyOwner")
|
||||
.select(({ fn }) => ["userId", fn.countAll<number>().as("count")])
|
||||
.groupBy("userId")
|
||||
.orderBy("count", "desc")
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
if (!ownerRow) return null;
|
||||
|
||||
const winsRow = await db
|
||||
.selectFrom("TrophyOwner")
|
||||
.select(({ fn }) => [
|
||||
"trophyId",
|
||||
"userId",
|
||||
fn.countAll<number>().as("count"),
|
||||
])
|
||||
.groupBy(["trophyId", "userId"])
|
||||
.orderBy("count", "desc")
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
if (!winsRow) return null;
|
||||
|
||||
return {
|
||||
heavyTrophyId: heavyTrophyRow.trophyId,
|
||||
ownerUserId: ownerRow.userId,
|
||||
wins: { trophyId: winsRow.trophyId, userId: winsRow.userId },
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveManyUserIds(heavyTournamentId: number | null) {
|
||||
if (heavyTournamentId !== null) {
|
||||
const rows = await db
|
||||
@@ -841,18 +935,6 @@ async function resolveLfgTournament() {
|
||||
return { tournamentId: row.tournamentId, teamId: row.id };
|
||||
}
|
||||
|
||||
async function resolveSubsTournamentId() {
|
||||
const row = await db
|
||||
.selectFrom("TournamentSub")
|
||||
.select(({ fn }) => ["tournamentId", fn.countAll<number>().as("count")])
|
||||
.groupBy("tournamentId")
|
||||
.orderBy("count", "desc")
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
|
||||
return row?.tournamentId ?? null;
|
||||
}
|
||||
|
||||
async function resolveAuditTournamentId() {
|
||||
const row = await db
|
||||
.selectFrom("TournamentAuditLog")
|
||||
|
||||
@@ -55,11 +55,11 @@ const tournaments = await db
|
||||
"Tournament.settings",
|
||||
"Tournament.isFinalized",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
])
|
||||
.where("CalendarEventDate.startTime", ">=", seasonStartTimestamp)
|
||||
.where("CalendarEventDate.startTime", "<=", seasonEndTimestamp)
|
||||
.orderBy("CalendarEventDate.startTime")
|
||||
.where("CalendarEventDate.startsAt", ">=", seasonStartTimestamp)
|
||||
.where("CalendarEventDate.startsAt", "<=", seasonEndTimestamp)
|
||||
.orderBy("CalendarEventDate.startsAt")
|
||||
.execute();
|
||||
|
||||
const unfinalizedTournaments = tournaments.filter(
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/** biome-ignore-all lint/suspicious/noConsole: Biome v2 migration */
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ROOT_DIR = path.join(__dirname, "..");
|
||||
|
||||
const MIGRATIONS_DIR = path.join(ROOT_DIR, "migrations");
|
||||
const DB_FILES = [
|
||||
path.join(ROOT_DIR, "db-test.sqlite3"),
|
||||
...fs
|
||||
.readdirSync(path.join(ROOT_DIR, "e2e", "seeds"))
|
||||
.filter((f) => f.startsWith("db-seed-") && f.endsWith(".sqlite3"))
|
||||
.map((f) => path.join(ROOT_DIR, "e2e", "seeds", f)),
|
||||
];
|
||||
|
||||
const migrationFilesOnDisk = fs
|
||||
.readdirSync(MIGRATIONS_DIR)
|
||||
.filter((f) => f.endsWith(".js"))
|
||||
.sort();
|
||||
|
||||
let hasErrors = false;
|
||||
|
||||
for (const dbPath of DB_FILES) {
|
||||
const relativePath = path.relative(ROOT_DIR, dbPath);
|
||||
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
console.warn(`Warning: ${relativePath} does not exist, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
const rows = db
|
||||
.prepare("SELECT name FROM migrations ORDER BY id ASC")
|
||||
.all() as Array<{ name: string }>;
|
||||
db.close();
|
||||
|
||||
const migrationsInDb = new Set(rows.map((r) => r.name));
|
||||
const missingMigrations = migrationFilesOnDisk.filter(
|
||||
(name) => !migrationsInDb.has(name),
|
||||
);
|
||||
|
||||
if (missingMigrations.length > 0) {
|
||||
hasErrors = true;
|
||||
console.error(
|
||||
`\n${relativePath} is missing ${missingMigrations.length} migration(s):`,
|
||||
);
|
||||
for (const name of missingMigrations) {
|
||||
console.error(` - ${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"\nRun `pnpm run test:e2e:generate-seeds` to regenerate test databases.",
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("All test databases have the latest migrations.");
|
||||
}
|
||||
@@ -64,7 +64,7 @@ async function main() {
|
||||
for (const [div, divsTeams] of grouped) {
|
||||
logger.info(`Creating division ${div}...`);
|
||||
|
||||
const createdEvent = await CalendarRepository.create({
|
||||
const createdEvent = await CalendarRepository.insert({
|
||||
parentTournamentId: tournament.ctx.id,
|
||||
authorId: tournament.ctx.author.id,
|
||||
bracketProgression: tournament.ctx.settings.bracketProgression,
|
||||
@@ -75,7 +75,7 @@ async function main() {
|
||||
name: `${tournament.ctx.name} - ${div.startsWith("Division") ? div : `Division ${div}`}`,
|
||||
organizationId: tournament.ctx.organization?.id ?? null,
|
||||
rules: tournament.ctx.rules,
|
||||
startTimes: [dateToDatabaseTimestamp(tournament.ctx.startTime)],
|
||||
startTimes: [dateToDatabaseTimestamp(tournament.ctx.startsAt)],
|
||||
tags: null,
|
||||
tournamentToCopyId: tournament.ctx.id,
|
||||
avatarImgId: calendarEvent.avatarImgId ?? undefined,
|
||||
|
||||
68
scripts/create-migration.ts
Normal file
68
scripts/create-migration.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const MIGRATION_FOLDER = fileURLToPath(
|
||||
new URL("../migrations", import.meta.url),
|
||||
);
|
||||
|
||||
const TEMPLATE = `import type { Kysely } from "kysely";
|
||||
|
||||
/** TODO: describe what this migration changes */
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
// kysely does not wrap sqlite migrations in a transaction, so do it here
|
||||
await db.transaction().execute(async (trx) => {
|
||||
await trx.schema.alterTable("TODO").addColumn("TODO", "text").execute();
|
||||
});
|
||||
}
|
||||
`;
|
||||
|
||||
function main() {
|
||||
const description = toKebabCase(process.argv.slice(2).join(" "));
|
||||
if (!description) {
|
||||
throw new Error(
|
||||
'Missing migration description e.g. pnpm run migrate:new "add user pronouns"',
|
||||
);
|
||||
}
|
||||
|
||||
const fileName = `${timestamp(new Date())}-${description}.ts`;
|
||||
const filePath = path.join(MIGRATION_FOLDER, fileName);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
throw new Error(`${fileName} already exists`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, TEMPLATE);
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Created migrations/${fileName}`);
|
||||
}
|
||||
|
||||
/** Sortable UTC stamp, so two branches can never claim the same slot. */
|
||||
function timestamp(date: Date) {
|
||||
const pad = (value: number) => String(value).padStart(2, "0");
|
||||
|
||||
return [
|
||||
date.getUTCFullYear(),
|
||||
pad(date.getUTCMonth() + 1),
|
||||
pad(date.getUTCDate()),
|
||||
pad(date.getUTCHours()),
|
||||
pad(date.getUTCMinutes()),
|
||||
pad(date.getUTCSeconds()),
|
||||
].join("");
|
||||
}
|
||||
|
||||
function toKebabCase(input: string) {
|
||||
return input
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.error((error as Error).message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -6,10 +6,11 @@ const discordId = process.argv[2]?.trim();
|
||||
|
||||
invariant(discordId, "discord id is required (argument 1)");
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
'delete from "Skill" where "userId" = (select id from "User" where discordId = @discordId)',
|
||||
await db
|
||||
.deleteFrom("Skill")
|
||||
.where("userId", "in", (eb) =>
|
||||
eb.selectFrom("User").select("User.id").where("discordId", "=", discordId),
|
||||
)
|
||||
.run({ discordId });
|
||||
.execute();
|
||||
|
||||
logger.info(`Deleted skill of user with discord id: ${discordId}`);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -6,16 +6,18 @@ const discordId = process.argv[2]?.trim();
|
||||
|
||||
invariant(discordId, "discord id is required (argument 1)");
|
||||
|
||||
const user = sql
|
||||
.prepare('select id from "User" where discordId = @discordId')
|
||||
.get({ discordId }) as { id: number } | undefined;
|
||||
const user = await db
|
||||
.selectFrom("User")
|
||||
.select("id")
|
||||
.where("discordId", "=", discordId)
|
||||
.executeTakeFirst();
|
||||
|
||||
invariant(user, `user with discord id ${discordId} not found`);
|
||||
|
||||
const userId = user.id;
|
||||
|
||||
sql.prepare('delete from "Build" where ownerId = @userId').run({ userId });
|
||||
sql.prepare('delete from "UserWeapon" where userId = @userId').run({ userId });
|
||||
sql.prepare('delete from "User" where id = @userId').run({ userId });
|
||||
await db.deleteFrom("Build").where("ownerId", "=", userId).execute();
|
||||
await db.deleteFrom("UserWeapon").where("userId", "=", userId).execute();
|
||||
await db.deleteFrom("User").where("id", "=", userId).execute();
|
||||
|
||||
logger.info(`Deleted user with discord id: ${discordId}`);
|
||||
|
||||
101
scripts/ensure-test-db.ts
Normal file
101
scripts/ensure-test-db.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const ROOT_DIR = path.join(__dirname, "..");
|
||||
|
||||
const MIGRATIONS_DIR = path.join(ROOT_DIR, "migrations");
|
||||
const TEST_DB_PATH = path.join(ROOT_DIR, "db-test.sqlite3");
|
||||
|
||||
export function setup() {
|
||||
ensureMigratedDb(TEST_DB_PATH);
|
||||
|
||||
// Test workers only ever read this file, to copy its schema into their own
|
||||
// in-memory database. Taking it out of WAL drops the -wal/-shm sidecars, so
|
||||
// those concurrent opens need no write access and cannot contend.
|
||||
const db = new DatabaseSync(TEST_DB_PATH);
|
||||
db.exec("PRAGMA journal_mode = DELETE");
|
||||
db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the SQLite file at `dbPath` has every migration applied: creates it
|
||||
* if missing, applies pending migrations, and rebuilds it from scratch if it
|
||||
* contains a migration that no longer exists on disk.
|
||||
*/
|
||||
export function ensureMigratedDb(dbPath: string) {
|
||||
const resolvedPath = path.resolve(ROOT_DIR, dbPath);
|
||||
|
||||
if (!fs.existsSync(resolvedPath)) {
|
||||
migrateUp(resolvedPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const applied = appliedMigrations(resolvedPath);
|
||||
const onDisk = migrationFilesOnDisk();
|
||||
|
||||
const hasDrift =
|
||||
applied === null || applied.some((name) => !onDisk.has(name));
|
||||
if (hasDrift) {
|
||||
deleteDbFiles(resolvedPath);
|
||||
migrateUp(resolvedPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const appliedSet = new Set(applied);
|
||||
const hasPending = [...onDisk].some((name) => !appliedSet.has(name));
|
||||
if (hasPending) {
|
||||
migrateUp(resolvedPath);
|
||||
}
|
||||
}
|
||||
|
||||
function migrationFilesOnDisk() {
|
||||
return new Set(
|
||||
fs
|
||||
.readdirSync(MIGRATIONS_DIR)
|
||||
.filter((file) => file.endsWith(".ts"))
|
||||
// kysely tracks migrations by file name without the extension
|
||||
.map((file) => file.slice(0, -".ts".length)),
|
||||
);
|
||||
}
|
||||
|
||||
function appliedMigrations(dbPath: string) {
|
||||
const db = new DatabaseSync(dbPath, { readOnly: true });
|
||||
try {
|
||||
const hasMigrationsTable = db
|
||||
.prepare(
|
||||
"select 1 from sqlite_master where type = 'table' and name = 'kysely_migration'",
|
||||
)
|
||||
.get();
|
||||
if (!hasMigrationsTable) return null;
|
||||
|
||||
const rows = db
|
||||
.prepare("select name from kysely_migration")
|
||||
.all() as Array<{
|
||||
name: string;
|
||||
}>;
|
||||
return rows.map((row) => row.name);
|
||||
} catch {
|
||||
return null;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDbFiles(dbPath: string) {
|
||||
for (const suffix of ["", "-shm", "-wal"]) {
|
||||
fs.rmSync(`${dbPath}${suffix}`, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function migrateUp(dbPath: string) {
|
||||
execSync("pnpm run migrate up", {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: "inherit",
|
||||
env: { ...process.env, DB_PATH: dbPath },
|
||||
});
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import Database from "better-sqlite3";
|
||||
import { SEED_VARIATIONS } from "../app/features/api-private/constants";
|
||||
|
||||
const E2E_SEEDS_DIR = "e2e/seeds";
|
||||
const BASE_TEST_DB = "db-test.sqlite3";
|
||||
|
||||
const E2E_WORKER_DB_PATTERN = /^db-test-e2e-\d+\.sqlite3$/;
|
||||
|
||||
async function generatePreSeededDatabases() {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log("Generating pre-seeded databases for e2e tests...\n");
|
||||
|
||||
for (const file of fs.readdirSync(".")) {
|
||||
if (E2E_WORKER_DB_PATTERN.test(file)) {
|
||||
fs.unlinkSync(file);
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Deleted stale worker db: ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(E2E_SEEDS_DIR)) {
|
||||
fs.mkdirSync(E2E_SEEDS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const baseDbPath = path.resolve(BASE_TEST_DB);
|
||||
if (!fs.existsSync(baseDbPath)) {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.error(
|
||||
`Base test database not found: ${baseDbPath}. Run migrations first.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const variation of SEED_VARIATIONS) {
|
||||
const outputPath = path.join(E2E_SEEDS_DIR, `db-seed-${variation}.sqlite3`);
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Generating ${variation}...`);
|
||||
|
||||
// remove stale WAL/SHM sidecars so a fresh copy isn't paired with a leftover
|
||||
// WAL from a previously crashed run (which reads as "database disk image is malformed")
|
||||
for (const sidecar of [`${outputPath}-wal`, `${outputPath}-shm`]) {
|
||||
if (fs.existsSync(sidecar)) {
|
||||
fs.unlinkSync(sidecar);
|
||||
}
|
||||
}
|
||||
|
||||
fs.copyFileSync(baseDbPath, outputPath);
|
||||
|
||||
execSync(
|
||||
`pnpm exec vite-node scripts/seed-single-variation.ts -- ${variation} ${outputPath}`,
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
|
||||
const db = new Database(outputPath);
|
||||
db.pragma("wal_checkpoint(TRUNCATE)");
|
||||
db.close();
|
||||
|
||||
const stats = fs.statSync(outputPath);
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(
|
||||
` ✓ ${variation}: ${(stats.size / 1024 / 1024).toFixed(2)} MB\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Done! Pre-seeded databases saved to ${E2E_SEEDS_DIR}/`);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
generatePreSeededDatabases().catch(console.error);
|
||||
79
scripts/migrate.ts
Normal file
79
scripts/migrate.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { Kysely } from "kysely";
|
||||
import { FileMigrationProvider, Migrator } from "kysely/migration";
|
||||
import { NodeSqliteDialect } from "../app/db/node-sqlite-dialect.ts";
|
||||
|
||||
const MIGRATION_FOLDER = fileURLToPath(
|
||||
new URL("../migrations", import.meta.url),
|
||||
);
|
||||
|
||||
try {
|
||||
process.loadEnvFile();
|
||||
} catch {
|
||||
// .env is optional; in production DB_PATH comes from the host environment
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const command = process.argv[2] ?? "up";
|
||||
if (command !== "up") {
|
||||
throw new Error(
|
||||
`Unknown command "${command}". Only "up" is supported; migrations are never rolled back.`,
|
||||
);
|
||||
}
|
||||
|
||||
const dbPath = process.env.DB_PATH;
|
||||
if (!dbPath) {
|
||||
throw new Error("DB_PATH is not set");
|
||||
}
|
||||
|
||||
const database = new DatabaseSync(dbPath);
|
||||
database.exec("PRAGMA journal_mode = WAL");
|
||||
database.exec("PRAGMA foreign_keys = ON");
|
||||
database.exec("PRAGMA busy_timeout = 5000");
|
||||
|
||||
const db = new Kysely<any>({
|
||||
dialect: new NodeSqliteDialect({ database }),
|
||||
});
|
||||
|
||||
const migrator = new Migrator({
|
||||
db,
|
||||
provider: new FileMigrationProvider({
|
||||
fs,
|
||||
path,
|
||||
migrationFolder: MIGRATION_FOLDER,
|
||||
import: (filePath) => import(pathToFileURL(filePath).href),
|
||||
}),
|
||||
});
|
||||
|
||||
const { error, results } = await migrator.migrateToLatest();
|
||||
|
||||
for (const result of results ?? []) {
|
||||
if (result.status === "Success") {
|
||||
log(`↑ ${result.migrationName}`);
|
||||
} else if (result.status === "Error") {
|
||||
log(`✗ ${result.migrationName}`);
|
||||
}
|
||||
}
|
||||
|
||||
await db.destroy();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (!results?.length) {
|
||||
log(`No pending migrations for ${dbPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function log(message: string) {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -78,8 +78,8 @@ async function getParticipantsForOrgInMonth(
|
||||
)
|
||||
.select(({ fn }) => fn.count<number>("tmgrp.userId").distinct().as("count"))
|
||||
.where("ce.organizationId", "=", organizationId)
|
||||
.where("ced.startTime", ">=", startTimestamp)
|
||||
.where("ced.startTime", "<", endTimestamp)
|
||||
.where("ced.startsAt", ">=", startTimestamp)
|
||||
.where("ced.startsAt", "<", endTimestamp)
|
||||
.where("ttci.checkedInAt", "is not", null)
|
||||
.where("ttci.isCheckOut", "=", 0)
|
||||
.executeTakeFirst();
|
||||
@@ -98,7 +98,7 @@ async function getOrgsWithRecentTournaments(
|
||||
.select("ce.organizationId")
|
||||
.distinct()
|
||||
.where("ce.organizationId", "is not", null)
|
||||
.where("ced.startTime", ">=", earliestTimestamp)
|
||||
.where("ced.startsAt", ">=", earliestTimestamp)
|
||||
.execute();
|
||||
|
||||
return orgs.map((org) => org.organizationId!);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
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";
|
||||
@@ -17,9 +15,7 @@ invariant(
|
||||
"jsonNumber must be an integer (argument 1)",
|
||||
);
|
||||
|
||||
type Placements = Array<
|
||||
Omit<Tables["XRankPlacement"], "playerId" | "id"> & { playerSplId: string }
|
||||
>;
|
||||
type Placements = XRankPlacementRepository.XRankPlacementInsertArgs[];
|
||||
|
||||
const modes = ["splatzones", "towercontrol", "rainmaker", "clamblitz"] as const;
|
||||
const modeToShort = {
|
||||
@@ -35,7 +31,9 @@ void main();
|
||||
async function main() {
|
||||
const placements: Placements = [];
|
||||
|
||||
wipeMonthYearPlacements(resolveMonthYear(jsonNumber));
|
||||
await XRankPlacementRepository.deleteAllByMonthYear(
|
||||
resolveMonthYear(jsonNumber),
|
||||
);
|
||||
for (const mode of modes) {
|
||||
for (const region of regions) {
|
||||
for (const includeWeapon of [false]) {
|
||||
@@ -51,7 +49,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
addPlacements(placements);
|
||||
await XRankPlacementRepository.insertMany(placements);
|
||||
await XRankPlacementRepository.refreshAllPeakXp();
|
||||
await BadgeRepository.syncXPBadges();
|
||||
await BuildRepository.recalculateAllSortValues();
|
||||
@@ -139,67 +137,3 @@ function resolveMonthYear(number: number) {
|
||||
year: start.getFullYear(),
|
||||
};
|
||||
}
|
||||
|
||||
const addPlayerStm = sql.prepare(/* sql */ `
|
||||
insert into "SplatoonPlayer" ("splId")
|
||||
values (@splId)
|
||||
on conflict ("splId") do nothing
|
||||
`);
|
||||
|
||||
const addPlacementStm = sql.prepare(/* sql */ `
|
||||
insert into "XRankPlacement" (
|
||||
"weaponSplId",
|
||||
"name",
|
||||
"nameDiscriminator",
|
||||
"power",
|
||||
"rank",
|
||||
"title",
|
||||
"badges",
|
||||
"bannerSplId",
|
||||
"playerId",
|
||||
"month",
|
||||
"year",
|
||||
"region",
|
||||
"mode"
|
||||
)
|
||||
values (
|
||||
@weaponSplId,
|
||||
@name,
|
||||
@nameDiscriminator,
|
||||
@power,
|
||||
@rank,
|
||||
@title,
|
||||
@badges,
|
||||
@bannerSplId,
|
||||
(select "id" from "SplatoonPlayer" where "splId" = @playerSplId),
|
||||
@month,
|
||||
@year,
|
||||
@region,
|
||||
@mode
|
||||
)
|
||||
`);
|
||||
|
||||
function addPlacements(placements: Placements) {
|
||||
sql.transaction(() => {
|
||||
for (const placement of placements) {
|
||||
addPlayerStm.run({ splId: placement.playerSplId });
|
||||
addPlacementStm.run(placement);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function wipeMonthYearPlacements({
|
||||
month,
|
||||
year,
|
||||
}: {
|
||||
month: number;
|
||||
year: number;
|
||||
}) {
|
||||
const wipeMonthYearPlacementsStm = sql.prepare(/* sql */ `
|
||||
delete from "XRankPlacement"
|
||||
where "month" = @month
|
||||
and "year" = @year
|
||||
`);
|
||||
|
||||
wipeMonthYearPlacementsStm.run({ month, year });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -12,8 +12,10 @@ invariant(
|
||||
"displayName of badge must have at least one uppercase letter",
|
||||
);
|
||||
|
||||
sql
|
||||
.prepare("update badge set displayName = @newName where id = @id")
|
||||
.run({ id, newName });
|
||||
await db
|
||||
.updateTable("Badge")
|
||||
.set({ displayName: newName })
|
||||
.where("id", "=", Number(id))
|
||||
.execute();
|
||||
|
||||
logger.info(`Added updated name. New name: ${newName}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ordinal } from "openskill";
|
||||
import { db, sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { TIERS, type TierName } from "~/features/mmr/mmr-constants";
|
||||
import * as SkillRepository from "~/features/mmr/SkillRepository.server";
|
||||
@@ -14,38 +14,26 @@ invariant(rawNth, "nth of new season needed (argument 1)");
|
||||
const nth = Number(rawNth);
|
||||
invariant(!Number.isNaN(nth), "nth of new season must be a number");
|
||||
|
||||
const skillsExistStm = sql.prepare(/* sql */ `
|
||||
select
|
||||
1
|
||||
from "Skill"
|
||||
where
|
||||
"season" = @season
|
||||
limit 1
|
||||
`);
|
||||
|
||||
invariant(
|
||||
skillsExistStm.get({ season: nth - 1 }),
|
||||
await SkillRepository.existsBySeason(nth - 1),
|
||||
`No skills for season ${nth - 1}`,
|
||||
);
|
||||
invariant(
|
||||
!skillsExistStm.get({ season: nth }),
|
||||
!(await SkillRepository.existsBySeason(nth)),
|
||||
`Skills for season ${nth} already exist`,
|
||||
);
|
||||
|
||||
const activeMatchExistsStm = sql.prepare(/* sql */ `
|
||||
select
|
||||
"GroupMatch"."id"
|
||||
from "GroupMatch"
|
||||
left join "Skill" on "Skill"."groupMatchId" = "GroupMatch"."id"
|
||||
where
|
||||
"Skill"."id" is null
|
||||
`);
|
||||
const idsOfActiveMatches = activeMatchExistsStm
|
||||
.all()
|
||||
.map((row) => (row as any).id) as number[];
|
||||
const idsOfActiveMatches = (
|
||||
await db
|
||||
.selectFrom("GroupMatch")
|
||||
.leftJoin("Skill", "Skill.groupMatchId", "GroupMatch.id")
|
||||
.select("GroupMatch.id")
|
||||
.where("Skill.id", "is", null)
|
||||
.execute()
|
||||
).map((row) => row.id);
|
||||
|
||||
invariant(
|
||||
!activeMatchExistsStm.get(),
|
||||
idsOfActiveMatches.length === 0,
|
||||
`There are active matches: (ids: ${idsOfActiveMatches.join(", ")})`,
|
||||
);
|
||||
|
||||
@@ -70,11 +58,13 @@ const TIER_TO_NEW_TIER: Record<TierName, TierName> = {
|
||||
// - For +3 members, consider the last 2 seasons
|
||||
// - For non-plus members, consider the last season only
|
||||
const getAllSkills = async () => {
|
||||
const skills = [
|
||||
freshUserSkills(nth - 1).userSkills,
|
||||
freshUserSkills(nth - 2).userSkills,
|
||||
freshUserSkills(nth - 3).userSkills,
|
||||
];
|
||||
const skills = (
|
||||
await Promise.all([
|
||||
freshUserSkills(nth - 1),
|
||||
freshUserSkills(nth - 2),
|
||||
freshUserSkills(nth - 3),
|
||||
])
|
||||
).map((seasonSkills) => seasonSkills.userSkills);
|
||||
|
||||
const plusServerMembers = await db
|
||||
.selectFrom("PlusTier")
|
||||
@@ -136,28 +126,19 @@ const groupedSkills = skillsToConsider.reduce(
|
||||
{} as Record<TierName, typeof skillsToConsider>,
|
||||
);
|
||||
|
||||
const skillStm = sql.prepare(/* sql */ `
|
||||
select
|
||||
*
|
||||
from "Skill"
|
||||
where
|
||||
"userId" = @userId
|
||||
and "ordinal" = @ordinal
|
||||
`);
|
||||
const midPoints = Object.entries(groupedSkills).reduce(
|
||||
(acc, [tier, skills]) => {
|
||||
const midPoint = skills[Math.floor(skills.length / 2)];
|
||||
const midPointSkill = skillStm.get({
|
||||
userId: midPoint.userId,
|
||||
ordinal: midPoint.ordinal,
|
||||
}) as Tables["Skill"];
|
||||
invariant(midPointSkill, "midPointSkill not found");
|
||||
const midPoints = {} as Record<TierName, Tables["Skill"]>;
|
||||
for (const [tier, skills] of Object.entries(groupedSkills)) {
|
||||
const midPoint = skills[Math.floor(skills.length / 2)];
|
||||
const midPointSkill = await db
|
||||
.selectFrom("Skill")
|
||||
.selectAll()
|
||||
.where("userId", "=", midPoint.userId)
|
||||
.where("ordinal", "=", midPoint.ordinal)
|
||||
.executeTakeFirst();
|
||||
invariant(midPointSkill, "midPointSkill not found");
|
||||
|
||||
acc[tier as TierName] = midPointSkill;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<TierName, Tables["Skill"]>,
|
||||
);
|
||||
midPoints[tier as TierName] = midPointSkill;
|
||||
}
|
||||
|
||||
const newSkills = allSkills.map((s) => {
|
||||
const newTier = TIER_TO_NEW_TIER[s.tier.name];
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Config } from "~/config";
|
||||
|
||||
export const SEED_ART_URLS = [
|
||||
"https://images.unsplash.com/photo-1611627474565-2367887415d1?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8bmF0dXJlLDF8fHx8fHwxNjg4NTU1NTA2&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080",
|
||||
"https://images.unsplash.com/photo-1625120742520-3f085b6894ba?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8bmF0dXJlLDF8fHx8fHwxNjg4NTU1NTI2&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080",
|
||||
@@ -33,16 +35,43 @@ export function getArtFilename(index: number): string {
|
||||
return `art-${index}.jpg`;
|
||||
}
|
||||
|
||||
export const SEED_TEAM_IMAGES = [
|
||||
{ filename: "alliance-rogue.png", teamId: 1 },
|
||||
{ filename: "default.png", teamId: null },
|
||||
/** Logos in `app/db/seed/img`, uploaded to the local image storage under their own
|
||||
* name. Anything the seed sets as a logo has to be one of these to render in dev. */
|
||||
export const SEED_LOGO_FILENAMES = [
|
||||
"alliance-rogue.png",
|
||||
"default.png",
|
||||
"in-the-zone.png",
|
||||
"luti.png",
|
||||
"paddling-pool.png",
|
||||
"picnic.png",
|
||||
"swim-or-sink.png",
|
||||
"the-depths.png",
|
||||
];
|
||||
|
||||
export const SEED_TOURNAMENT_IMAGES = [
|
||||
{ filename: "picnic.png", tournamentId: 1 },
|
||||
{ filename: "in-the-zone.png", tournamentId: 2 },
|
||||
{ filename: "paddling-pool.png", tournamentId: 3 },
|
||||
{ filename: "swim-or-sink.png", tournamentId: 4 },
|
||||
{ filename: "the-depths.png", tournamentId: 5 },
|
||||
{ filename: "luti.png", tournamentId: 6 },
|
||||
];
|
||||
/** The logo of a tournament that has none of its own, uploaded under the name the
|
||||
* app looks it up by. */
|
||||
export const SEED_DEFAULT_TOURNAMENT_LOGO = {
|
||||
sourceFilename: "default.png",
|
||||
filename: Config.tournamentDefaultLogo,
|
||||
};
|
||||
|
||||
/** How many numbered logos are uploaded. A seed run needing more than this many
|
||||
* ends up with logos that 404 — raise it rather than reusing one, the url column
|
||||
* being unique. */
|
||||
export const SEED_NUMBERED_LOGO_COUNT = 200;
|
||||
|
||||
/** Filename of a logo the seed hands to something it has no particular logo for.
|
||||
* Numbered because every image row needs a url of its own, cycling the logos so
|
||||
* that a page full of them is not a page of one logo. */
|
||||
export function numberedLogoFilename(index: number) {
|
||||
return `seed-logo-${index}.png`;
|
||||
}
|
||||
|
||||
/** The uploads a numbered logo filename resolves to, in the storage the app reads. */
|
||||
export const SEED_NUMBERED_LOGOS = Array.from(
|
||||
{ length: SEED_NUMBERED_LOGO_COUNT },
|
||||
(_, index) => ({
|
||||
filename: numberedLogoFilename(index),
|
||||
sourceFilename: SEED_LOGO_FILENAMES[index % SEED_LOGO_FILENAMES.length],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -7,8 +7,9 @@ import { logger } from "~/utils/logger";
|
||||
import {
|
||||
getArtFilename,
|
||||
SEED_ART_URLS,
|
||||
SEED_TEAM_IMAGES,
|
||||
SEED_TOURNAMENT_IMAGES,
|
||||
SEED_DEFAULT_TOURNAMENT_LOGO,
|
||||
SEED_LOGO_FILENAMES,
|
||||
SEED_NUMBERED_LOGOS,
|
||||
} from "./seed-art-urls";
|
||||
|
||||
async function checkMinioConnection(): Promise<boolean> {
|
||||
@@ -192,17 +193,22 @@ export async function seedImages(): Promise<void> {
|
||||
`\n✅ Art image seeding complete: ${successCount} uploaded, ${skippedCount} already existed, ${failCount} failed`,
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`\n📥 Processing ${SEED_TEAM_IMAGES.length} team images and ${SEED_TOURNAMENT_IMAGES.length} tournament images`,
|
||||
);
|
||||
const localImages = [
|
||||
...SEED_LOGO_FILENAMES.map((filename) => ({
|
||||
filename,
|
||||
sourceFilename: filename,
|
||||
})),
|
||||
SEED_DEFAULT_TOURNAMENT_LOGO,
|
||||
...SEED_NUMBERED_LOGOS,
|
||||
];
|
||||
|
||||
const localImages = [...SEED_TEAM_IMAGES, ...SEED_TOURNAMENT_IMAGES];
|
||||
logger.info(`\n📥 Processing ${localImages.length} logo images`);
|
||||
|
||||
let localSuccessCount = 0;
|
||||
let localFailCount = 0;
|
||||
let localSkippedCount = 0;
|
||||
|
||||
for (const { filename } of localImages) {
|
||||
for (const { filename, sourceFilename } of localImages) {
|
||||
const smallFilename = filename.replace(/\.(\w+)$/, "-small.$1");
|
||||
|
||||
try {
|
||||
@@ -215,7 +221,7 @@ export async function seedImages(): Promise<void> {
|
||||
` ↷ Files ${filename} and ${smallFilename} already exist in Minio`,
|
||||
);
|
||||
} else {
|
||||
const imageBuffer = await readLocalImage(filename);
|
||||
const imageBuffer = await readLocalImage(sourceFilename);
|
||||
|
||||
if (!regularExists) {
|
||||
logger.info(` Uploading ${filename} to Minio...`);
|
||||
@@ -238,6 +244,6 @@ export async function seedImages(): Promise<void> {
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`\n✅ Local image seeding complete: ${localSuccessCount} uploaded, ${localSkippedCount} already existed, ${localFailCount} failed`,
|
||||
`\n✅ Logo image seeding complete: ${localSuccessCount} uploaded, ${localSkippedCount} already existed, ${localFailCount} failed`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { SeedVariation } from "~/features/api-private/routes/seed";
|
||||
|
||||
const variation = process.argv[2] as SeedVariation;
|
||||
const dbPath = process.argv[3];
|
||||
|
||||
if (!variation || !dbPath) {
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.error("Usage: seed-single-variation.ts <variation> <dbPath>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.env.DB_PATH = dbPath;
|
||||
|
||||
const { seed } = await import("../app/db/seed/index");
|
||||
await seed(variation === "DEFAULT" ? null : variation);
|
||||
8
scripts/seed.ts
Normal file
8
scripts/seed.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { seed } from "~/db/seed";
|
||||
|
||||
const startedAt = Date.now();
|
||||
|
||||
await seed();
|
||||
|
||||
// biome-ignore lint/suspicious/noConsole: CLI script output
|
||||
console.log(`Seeded in ${((Date.now() - startedAt) / 1000).toFixed(1)}s`);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
@@ -11,11 +11,11 @@ const currentSeasonNth = Seasons.currentOrPrevious()?.nth;
|
||||
|
||||
invariant(currentSeasonNth, "current season nth is required");
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
'update "User" set plusSkippedForSeasonNth = @plusSkippedForSeasonNth where discordId = @discordId',
|
||||
)
|
||||
.run({ discordId, plusSkippedForSeasonNth: currentSeasonNth });
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({ plusSkippedForSeasonNth: currentSeasonNth })
|
||||
.where("discordId", "=", discordId)
|
||||
.execute();
|
||||
|
||||
logger.info(
|
||||
`Plus Server admission will be skipped for Discord ID: ${discordId} (season ${currentSeasonNth})`,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
|
||||
const discordId = process.argv[2]?.trim();
|
||||
@@ -10,24 +10,16 @@ invariant(discordId !== discordId2, "discord ids must be different");
|
||||
|
||||
const tempDiscordId = "temp-discord-id";
|
||||
|
||||
const stm = sql.prepare(
|
||||
/** sql */ `update "User" set "discordId" = @newDiscordId where "discordId" = @discordId;`,
|
||||
);
|
||||
|
||||
// swap user discordIds
|
||||
sql.transaction(() => {
|
||||
stm.run({
|
||||
discordId: discordId,
|
||||
newDiscordId: tempDiscordId,
|
||||
});
|
||||
await db.transaction().execute(async (trx) => {
|
||||
const swap = (from: string, to: string) =>
|
||||
trx
|
||||
.updateTable("User")
|
||||
.set({ discordId: to })
|
||||
.where("discordId", "=", from)
|
||||
.execute();
|
||||
|
||||
stm.run({
|
||||
discordId: discordId2,
|
||||
newDiscordId: discordId,
|
||||
});
|
||||
|
||||
stm.run({
|
||||
discordId: tempDiscordId,
|
||||
newDiscordId: discordId2,
|
||||
});
|
||||
})();
|
||||
await swap(discordId, tempDiscordId);
|
||||
await swap(discordId2, discordId);
|
||||
await swap(tempDiscordId, discordId2);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { db } from "~/db/sql";
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const HOMEMADE_BADGES_URL =
|
||||
@@ -162,14 +163,7 @@ async function addBadge(badge: {
|
||||
displayName: string;
|
||||
authorId: number;
|
||||
}) {
|
||||
return db
|
||||
.insertInto("Badge")
|
||||
.values({
|
||||
code: badge.code,
|
||||
displayName: badge.displayName,
|
||||
authorId: badge.authorId,
|
||||
})
|
||||
.execute();
|
||||
return BadgeRepository.insert({ ...badge, hue: null });
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["./**/*.ts", "../types/**/*.d.ts"],
|
||||
"exclude": []
|
||||
"exclude": [],
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
@@ -6,10 +6,10 @@ const discordId = process.argv[2]?.trim();
|
||||
|
||||
invariant(discordId, "discord id is required (argument 1)");
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
'update "User" set plusSkippedForSeasonNth = null where discordId = @discordId',
|
||||
)
|
||||
.run({ discordId });
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({ plusSkippedForSeasonNth: null })
|
||||
.where("discordId", "=", discordId)
|
||||
.execute();
|
||||
|
||||
logger.info(`Plus Server admission unskipped for Discord ID: ${discordId}`);
|
||||
|
||||
Reference in New Issue
Block a user