mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-22 11:05:09 -05:00
Remove unused scripts
This commit is contained in:
@@ -143,7 +143,7 @@ function calculateSkills(args: {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function calculateIndividualPlayerSkills({
|
||||
function calculateIndividualPlayerSkills({
|
||||
results,
|
||||
queryCurrentUserRating,
|
||||
}: {
|
||||
@@ -645,7 +645,7 @@ function spDiffs({
|
||||
return spDiffs;
|
||||
}
|
||||
|
||||
export function setResults({
|
||||
function setResults({
|
||||
results,
|
||||
teams,
|
||||
}: {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const TIER_THRESHOLDS = {
|
||||
C: Number.NEGATIVE_INFINITY,
|
||||
} as const;
|
||||
|
||||
export const TOP_TEAMS_COUNT = 8;
|
||||
const TOP_TEAMS_COUNT = 8;
|
||||
export const MIN_TEAMS_FOR_TIERING = 8;
|
||||
export const TIER_HISTORY_LENGTH = 5;
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import * as AdminRepository from "~/features/admin/AdminRepository.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
async function main() {
|
||||
const input = process.argv[2]?.trim();
|
||||
|
||||
const userIds = input.split(",").map((id) => Number(id));
|
||||
|
||||
for (const userId of userIds) {
|
||||
await AdminRepository.makeTournamentOrganizerByUserId(userId);
|
||||
}
|
||||
logger.info(`Added TOs: ${userIds}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,135 +0,0 @@
|
||||
import { db } from "../app/db/sql";
|
||||
import * as Seasons from "../app/features/mmr/core/Seasons";
|
||||
import {
|
||||
queryCurrentTeamRating,
|
||||
queryCurrentUserRating,
|
||||
queryCurrentUserSeedingRating,
|
||||
queryTeamPlayerRatingAverage,
|
||||
} from "../app/features/mmr/mmr-utils.server";
|
||||
import * as Standings from "../app/features/tournament/core/Standings";
|
||||
import { tournamentSummary } from "../app/features/tournament-bracket/core/summarizer.server";
|
||||
import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server";
|
||||
import * as TournamentMatchRepository from "../app/features/tournament-match/TournamentMatchRepository.server";
|
||||
import invariant from "../app/utils/invariant";
|
||||
import { logger } from "../app/utils/logger";
|
||||
|
||||
async function main() {
|
||||
logger.info("Starting to backfill tournament result divisions");
|
||||
|
||||
const tournaments = await db
|
||||
.selectFrom("Tournament")
|
||||
.select("id")
|
||||
.where("isFinalized", "=", 1)
|
||||
.execute();
|
||||
|
||||
let recalculatedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const { id: tournamentId } of tournaments) {
|
||||
try {
|
||||
const tournament = await tournamentFromDB({
|
||||
tournamentId,
|
||||
user: undefined,
|
||||
});
|
||||
|
||||
const uniqueStartingBracketIndexes = new Set(
|
||||
tournament.ctx.teams
|
||||
.map((team) => team.startingBracketIdx)
|
||||
.filter((idx) => idx !== null && idx !== undefined),
|
||||
);
|
||||
|
||||
if (uniqueStartingBracketIndexes.size <= 1) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
recalculatedCount++;
|
||||
|
||||
await db
|
||||
.deleteFrom("TournamentResult")
|
||||
.where("tournamentId", "=", tournamentId)
|
||||
.execute();
|
||||
|
||||
const results =
|
||||
await TournamentMatchRepository.allResultsByTournamentId(tournamentId);
|
||||
invariant(results.length > 0, "No results found");
|
||||
|
||||
const season = Seasons.current(tournament.ctx.startTime)?.nth;
|
||||
const seedingSkillCountsFor = tournament.skillCountsFor;
|
||||
|
||||
const standingsResult = Standings.tournamentStandings(tournament);
|
||||
if (standingsResult.type === "single") {
|
||||
throw new Error(
|
||||
`Expected multiple starting brackets for tournament ${tournamentId}`,
|
||||
);
|
||||
}
|
||||
const finalStandings = Standings.flattenStandings(standingsResult);
|
||||
const summary = tournamentSummary({
|
||||
teams: tournament.ctx.teams,
|
||||
finalStandings,
|
||||
results,
|
||||
calculateSeasonalStats: false,
|
||||
queryCurrentTeamRating: (identifier) =>
|
||||
queryCurrentTeamRating({ identifier, season: season! }).rating,
|
||||
queryCurrentUserRating: (userId) =>
|
||||
queryCurrentUserRating({ userId, season: season! }),
|
||||
queryTeamPlayerRatingAverage: (identifier) =>
|
||||
queryTeamPlayerRatingAverage({
|
||||
identifier,
|
||||
season: season!,
|
||||
}),
|
||||
queryCurrentSeedingRating: (userId) =>
|
||||
queryCurrentUserSeedingRating({
|
||||
userId,
|
||||
type: seedingSkillCountsFor!,
|
||||
}),
|
||||
seedingSkillCountsFor,
|
||||
progression: tournament.ctx.settings.bracketProgression,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Inserting ${summary.tournamentResults.length} results for tournament ${tournamentId}`,
|
||||
);
|
||||
for (const tournamentResult of summary.tournamentResults) {
|
||||
const setResults = summary.setResults.get(tournamentResult.userId);
|
||||
|
||||
if (setResults?.every((result) => !result)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await db
|
||||
.insertInto("TournamentResult")
|
||||
.values({
|
||||
tournamentId,
|
||||
userId: tournamentResult.userId,
|
||||
placement: tournamentResult.placement,
|
||||
participantCount: tournamentResult.participantCount,
|
||||
tournamentTeamId: tournamentResult.tournamentTeamId,
|
||||
setResults: setResults ? JSON.stringify(setResults) : "[]",
|
||||
spDiff: null,
|
||||
div: tournamentResult.div,
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (recalculatedCount % 10 === 0) {
|
||||
logger.info(
|
||||
`Processed ${recalculatedCount} tournaments with multiple starting brackets (skipped ${skippedCount})`,
|
||||
);
|
||||
}
|
||||
} catch (thrown) {
|
||||
if (thrown instanceof Response) continue;
|
||||
|
||||
logger.error(`Error processing tournament ${tournamentId}`, thrown);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Done. Recalculated ${recalculatedCount} tournaments with multiple starting brackets. Skipped ${skippedCount} tournaments.`,
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error("Error in backfill-tournament-result-divisions.ts", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,258 +0,0 @@
|
||||
/** biome-ignore-all lint/suspicious/noConsole: CLI script output */
|
||||
/**
|
||||
* Backfill Tournament Tiers Script
|
||||
*
|
||||
* Run with: pnpm exec vite-node scripts/backfill-tournament-tiers.ts
|
||||
*
|
||||
* Retroactively calculates and sets tiers for all finalized tournaments,
|
||||
* then populates series tier history based on those tiers.
|
||||
*/
|
||||
|
||||
import { sql } from "~/db/sql";
|
||||
import {
|
||||
calculateAdjustedScore,
|
||||
calculateTierNumber,
|
||||
MIN_TEAMS_FOR_TIERING,
|
||||
TIER_HISTORY_LENGTH,
|
||||
TOP_TEAMS_COUNT,
|
||||
} from "../app/features/tournament/core/tiering";
|
||||
|
||||
const dryRun = process.argv.includes("--dry-run");
|
||||
|
||||
interface TournamentScore {
|
||||
tournamentId: number;
|
||||
teamCount: number;
|
||||
top8AvgOrdinal: number | null;
|
||||
}
|
||||
|
||||
interface TournamentWithOrg {
|
||||
tournamentId: number;
|
||||
name: string;
|
||||
organizationId: number | null;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
interface Series {
|
||||
id: number;
|
||||
organizationId: number;
|
||||
substringMatches: string;
|
||||
}
|
||||
|
||||
function getTournamentScores(): TournamentScore[] {
|
||||
const query = `
|
||||
WITH TeamSkills AS (
|
||||
SELECT
|
||||
tt.tournamentId,
|
||||
tt.id as teamId,
|
||||
AVG(ss.ordinal) as avg_team_ordinal
|
||||
FROM TournamentTeam tt
|
||||
JOIN TournamentTeamMember ttm ON ttm.tournamentTeamId = tt.id
|
||||
LEFT JOIN SeedingSkill ss ON ss.userId = ttm.userId AND ss.type = 'RANKED'
|
||||
WHERE tt.droppedOut = 0
|
||||
GROUP BY tt.tournamentId, tt.id
|
||||
),
|
||||
TeamCounts AS (
|
||||
SELECT tournamentId, COUNT(*) as team_count
|
||||
FROM TeamSkills
|
||||
WHERE avg_team_ordinal IS NOT NULL
|
||||
GROUP BY tournamentId
|
||||
),
|
||||
RankedTeams AS (
|
||||
SELECT
|
||||
ts.tournamentId,
|
||||
ts.avg_team_ordinal,
|
||||
tc.team_count,
|
||||
ROW_NUMBER() OVER (PARTITION BY ts.tournamentId ORDER BY ts.avg_team_ordinal DESC) as rank
|
||||
FROM TeamSkills ts
|
||||
JOIN TeamCounts tc ON tc.tournamentId = ts.tournamentId
|
||||
WHERE ts.avg_team_ordinal IS NOT NULL
|
||||
),
|
||||
TournamentScores AS (
|
||||
SELECT
|
||||
tournamentId,
|
||||
AVG(avg_team_ordinal) as top8_avg_ordinal,
|
||||
MAX(team_count) as team_count
|
||||
FROM RankedTeams
|
||||
WHERE rank <= ${TOP_TEAMS_COUNT}
|
||||
GROUP BY tournamentId
|
||||
)
|
||||
SELECT
|
||||
t.id as tournamentId,
|
||||
COALESCE(ts.team_count, 0) as teamCount,
|
||||
ts.top8_avg_ordinal as top8AvgOrdinal
|
||||
FROM Tournament t
|
||||
LEFT JOIN TournamentScores ts ON ts.tournamentId = t.id
|
||||
WHERE t.isFinalized = 1
|
||||
`;
|
||||
|
||||
return sql.prepare(query).all() as TournamentScore[];
|
||||
}
|
||||
|
||||
function getTournamentsWithOrg(): TournamentWithOrg[] {
|
||||
const query = /* sql */ `
|
||||
SELECT
|
||||
t.id as tournamentId,
|
||||
ce.name,
|
||||
ce.organizationId,
|
||||
ced.startTime
|
||||
FROM Tournament t
|
||||
INNER JOIN CalendarEvent ce ON ce.tournamentId = t.id
|
||||
INNER JOIN CalendarEventDate ced ON ced.eventId = ce.id
|
||||
WHERE t.isFinalized = 1
|
||||
AND ce.hidden = 0
|
||||
ORDER BY ced.startTime ASC
|
||||
`;
|
||||
return sql.prepare(query).all() as TournamentWithOrg[];
|
||||
}
|
||||
|
||||
function getAllSeries(): Series[] {
|
||||
const query = /* sql */ `
|
||||
SELECT id, organizationId, substringMatches
|
||||
FROM TournamentOrganizationSeries
|
||||
`;
|
||||
return sql.prepare(query).all() as Series[];
|
||||
}
|
||||
|
||||
function matchesSubstring(
|
||||
eventName: string,
|
||||
substringMatches: string[],
|
||||
): boolean {
|
||||
const eventNameLower = eventName.toLowerCase();
|
||||
return substringMatches.some((match) =>
|
||||
eventNameLower.includes(match.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log("=== Backfilling Tournament Tiers ===\n");
|
||||
if (dryRun) {
|
||||
console.log("DRY RUN - no changes will be made\n");
|
||||
}
|
||||
|
||||
const tournaments = getTournamentScores();
|
||||
console.log(`Found ${tournaments.length} finalized tournaments\n`);
|
||||
|
||||
const updateTierStatement = sql.prepare(
|
||||
/* sql */ `UPDATE "Tournament" SET tier = @tier WHERE id = @tournamentId`,
|
||||
);
|
||||
|
||||
const tierCounts: Record<string, number> = {};
|
||||
const tournamentTiers = new Map<number, number>();
|
||||
let updatedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const t of tournaments) {
|
||||
const meetsMinTeams = t.teamCount >= MIN_TEAMS_FOR_TIERING;
|
||||
|
||||
let tierNumber: number | null = null;
|
||||
if (t.top8AvgOrdinal !== null && meetsMinTeams) {
|
||||
const adjustedScore = calculateAdjustedScore(
|
||||
t.top8AvgOrdinal,
|
||||
t.teamCount,
|
||||
);
|
||||
tierNumber = calculateTierNumber(adjustedScore);
|
||||
}
|
||||
|
||||
if (tierNumber !== null) {
|
||||
tierCounts[tierNumber] = (tierCounts[tierNumber] || 0) + 1;
|
||||
tournamentTiers.set(t.tournamentId, tierNumber);
|
||||
updatedCount++;
|
||||
} else {
|
||||
skippedCount++;
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
updateTierStatement.run({
|
||||
tier: tierNumber,
|
||||
tournamentId: t.tournamentId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Tier distribution:");
|
||||
const tierNames: Record<number, string> = {
|
||||
1: "X",
|
||||
2: "S+",
|
||||
3: "S",
|
||||
4: "A+",
|
||||
5: "A",
|
||||
6: "B+",
|
||||
7: "B",
|
||||
8: "C+",
|
||||
9: "C",
|
||||
};
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
console.log(` ${tierNames[i]}: ${tierCounts[i] || 0}`);
|
||||
}
|
||||
|
||||
console.log(`\nUpdated: ${updatedCount} tournaments`);
|
||||
console.log(`Skipped (untiered): ${skippedCount} tournaments`);
|
||||
|
||||
console.log("\n=== Backfilling Series Tier History ===\n");
|
||||
|
||||
const allSeries = getAllSeries();
|
||||
const tournamentsWithOrg = getTournamentsWithOrg();
|
||||
console.log(`Found ${allSeries.length} series`);
|
||||
console.log(
|
||||
`Found ${tournamentsWithOrg.filter((t) => t.organizationId !== null).length} tournaments with organizations\n`,
|
||||
);
|
||||
|
||||
const updateSeriesStatement = sql.prepare(
|
||||
/* sql */ "UPDATE TournamentOrganizationSeries SET tierHistory = @tierHistory WHERE id = @seriesId",
|
||||
);
|
||||
|
||||
const seriesByOrg = new Map<number, Series[]>();
|
||||
for (const series of allSeries) {
|
||||
const existing = seriesByOrg.get(series.organizationId) ?? [];
|
||||
existing.push(series);
|
||||
seriesByOrg.set(series.organizationId, existing);
|
||||
}
|
||||
|
||||
let seriesUpdatedCount = 0;
|
||||
let seriesSkippedCount = 0;
|
||||
|
||||
for (const [organizationId, orgSeries] of seriesByOrg.entries()) {
|
||||
const orgTournaments = tournamentsWithOrg.filter(
|
||||
(t) => t.organizationId === organizationId,
|
||||
);
|
||||
|
||||
for (const series of orgSeries) {
|
||||
const substringMatches = JSON.parse(series.substringMatches) as string[];
|
||||
const matchingTournaments = orgTournaments
|
||||
.filter((t) => matchesSubstring(t.name, substringMatches))
|
||||
.filter((t) => tournamentTiers.has(t.tournamentId));
|
||||
|
||||
if (matchingTournaments.length === 0) {
|
||||
seriesSkippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const tierHistory = matchingTournaments
|
||||
.slice(-TIER_HISTORY_LENGTH)
|
||||
.map((t) => tournamentTiers.get(t.tournamentId)!);
|
||||
|
||||
console.log(
|
||||
`Series ${series.id} (org ${organizationId}): ${matchingTournaments.length} matching tournaments, tierHistory = [${tierHistory.join(", ")}]`,
|
||||
);
|
||||
|
||||
if (!dryRun) {
|
||||
updateSeriesStatement.run({
|
||||
seriesId: series.id,
|
||||
tierHistory: JSON.stringify(tierHistory),
|
||||
});
|
||||
}
|
||||
seriesUpdatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nSeries updated: ${seriesUpdatedCount}`);
|
||||
console.log(
|
||||
`Series skipped (no matching tournaments): ${seriesSkippedCount}`,
|
||||
);
|
||||
|
||||
if (dryRun) {
|
||||
console.log("\nRun without --dry-run to apply changes");
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,89 +0,0 @@
|
||||
import { ordinal, type Rating, rating } from "openskill";
|
||||
import { db } from "../app/db/sql";
|
||||
import type { Tables } from "../app/db/tables";
|
||||
import { calculateIndividualPlayerSkills } from "../app/features/tournament-bracket/core/summarizer.server";
|
||||
import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server";
|
||||
import * as TournamentMatchRepository from "../app/features/tournament-match/TournamentMatchRepository.server";
|
||||
import invariant from "../app/utils/invariant";
|
||||
import { logger } from "../app/utils/logger";
|
||||
|
||||
async function main() {
|
||||
const result: Tables["SeedingSkill"][] = [];
|
||||
|
||||
for (const type of ["RANKED", "UNRANKED"] as const) {
|
||||
const ratings = new Map<number, Rating>();
|
||||
let count = 0;
|
||||
|
||||
for await (const tournament of tournaments(type)) {
|
||||
count++;
|
||||
const results = await TournamentMatchRepository.allResultsByTournamentId(
|
||||
tournament.ctx.id,
|
||||
);
|
||||
invariant(results.length > 0, "No results found");
|
||||
|
||||
const skills = calculateIndividualPlayerSkills({
|
||||
queryCurrentUserRating(userId) {
|
||||
return { rating: ratings.get(userId) ?? rating(), matchesCount: 0 };
|
||||
},
|
||||
results,
|
||||
});
|
||||
|
||||
for (const { userId, mu, sigma } of skills) {
|
||||
ratings.set(userId, rating({ mu, sigma }));
|
||||
}
|
||||
}
|
||||
logger.info(`Processed ${count} tournaments`);
|
||||
|
||||
for (const [userId, { mu, sigma }] of ratings) {
|
||||
result.push({
|
||||
mu,
|
||||
sigma,
|
||||
ordinal: ordinal(rating({ mu, sigma })),
|
||||
type,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await db.transaction().execute(async (trx) => {
|
||||
await trx.deleteFrom("SeedingSkill").execute();
|
||||
for (const skill of result) {
|
||||
await trx.insertInto("SeedingSkill").values(skill).execute();
|
||||
}
|
||||
});
|
||||
logger.info(`Done. Total of ${result.length} seeding skills inserted`);
|
||||
}
|
||||
|
||||
async function* tournaments(type: "RANKED" | "UNRANKED") {
|
||||
const maxId = await db
|
||||
.selectFrom("Tournament")
|
||||
.select(({ fn }) => fn.max("id").as("maxId"))
|
||||
.executeTakeFirstOrThrow()
|
||||
.then((row) => row.maxId);
|
||||
|
||||
for (let tournamentId = 1; tournamentId <= maxId; tournamentId++) {
|
||||
try {
|
||||
const tournament = await tournamentFromDB({
|
||||
tournamentId,
|
||||
user: undefined,
|
||||
});
|
||||
|
||||
if (!tournament.ctx.isFinalized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tournament.skillCountsFor === "RANKED" && type === "RANKED") {
|
||||
yield tournament;
|
||||
} else if (
|
||||
tournament.skillCountsFor === "UNRANKED" &&
|
||||
type === "UNRANKED"
|
||||
) {
|
||||
yield tournament;
|
||||
}
|
||||
} catch {
|
||||
// logger.info(`Skipped tournament with id ${tournamentId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,191 +0,0 @@
|
||||
import { sql } from "kysely";
|
||||
import { db } from "../app/db/sql";
|
||||
import {
|
||||
setResults,
|
||||
type TournamentSummary,
|
||||
} from "../app/features/tournament-bracket/core/summarizer.server";
|
||||
import { tournamentFromDB } from "../app/features/tournament-bracket/core/Tournament.server";
|
||||
import * as TournamentMatchRepository from "../app/features/tournament-match/TournamentMatchRepository.server";
|
||||
import invariant from "../app/utils/invariant";
|
||||
import { logger } from "../app/utils/logger";
|
||||
|
||||
async function main() {
|
||||
logger.info(
|
||||
"Starting to fix tournamentTeamId in TournamentMatchGameResultParticipant",
|
||||
);
|
||||
await tournamentTeamIdsToTournamentMatchGameResultParticipantTable();
|
||||
logger.info("Fixed tournamentTeamId in TournamentMatchGameResultParticipant");
|
||||
|
||||
const result: Array<
|
||||
{ tournamentId: number } & Pick<TournamentSummary, "setResults">
|
||||
> = [];
|
||||
|
||||
let count = 0;
|
||||
for await (const tournament of tournaments()) {
|
||||
count++;
|
||||
const results = await TournamentMatchRepository.allResultsByTournamentId(
|
||||
tournament.ctx.id,
|
||||
);
|
||||
invariant(results.length > 0, "No results found");
|
||||
|
||||
result.push({
|
||||
tournamentId: tournament.ctx.id,
|
||||
setResults: setResults({ results, teams: tournament.ctx.teams }),
|
||||
});
|
||||
|
||||
if (count % 100 === 0) {
|
||||
logger.info(`Processed ${count} tournaments`);
|
||||
}
|
||||
}
|
||||
|
||||
await db.transaction().execute(async (trx) => {
|
||||
await trx
|
||||
.updateTable("TournamentResult")
|
||||
.set({
|
||||
setResults: JSON.stringify([]),
|
||||
})
|
||||
.execute();
|
||||
for (const { tournamentId, setResults } of result) {
|
||||
for (const [userId, setResult] of setResults.entries()) {
|
||||
await trx
|
||||
.updateTable("TournamentResult")
|
||||
.set({
|
||||
setResults: JSON.stringify(setResult),
|
||||
})
|
||||
.where("tournamentId", "=", tournamentId)
|
||||
.where("userId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.info(`Done. Total of ${result.length} results inserted.`);
|
||||
|
||||
await wipeEmptyResults();
|
||||
}
|
||||
|
||||
async function* tournaments() {
|
||||
const maxId = await db
|
||||
.selectFrom("Tournament")
|
||||
.select(({ fn }) => fn.max("id").as("maxId"))
|
||||
.executeTakeFirstOrThrow()
|
||||
.then((row) => row.maxId);
|
||||
|
||||
for (let tournamentId = 1; tournamentId <= maxId; tournamentId++) {
|
||||
if (tournamentId === 1483) {
|
||||
// broken one
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const tournament = await tournamentFromDB({
|
||||
tournamentId,
|
||||
user: undefined,
|
||||
});
|
||||
|
||||
if (!tournament.ctx.isFinalized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield tournament;
|
||||
} catch (thrown) {
|
||||
if (thrown instanceof Response) continue;
|
||||
|
||||
throw thrown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/sendou-ink/sendou.ink/commit/96781122e2c5f9cd90564c9b57a45b74557fc400
|
||||
async function tournamentTeamIdsToTournamentMatchGameResultParticipantTable() {
|
||||
await db
|
||||
.updateTable("TournamentMatchGameResultParticipant")
|
||||
.set((eb) => ({
|
||||
tournamentTeamId: eb
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.innerJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
"TournamentTeam.id",
|
||||
)
|
||||
// exclude teams that have not checked in
|
||||
.innerJoin(
|
||||
"TournamentTeamCheckIn",
|
||||
"TournamentTeamCheckIn.tournamentTeamId",
|
||||
"TournamentTeam.id",
|
||||
)
|
||||
.select("TournamentTeam.id")
|
||||
.whereRef(
|
||||
"TournamentTeamMember.userId",
|
||||
"=",
|
||||
"TournamentMatchGameResultParticipant.userId",
|
||||
)
|
||||
.whereRef(
|
||||
"TournamentTeam.tournamentId",
|
||||
"=",
|
||||
eb
|
||||
.selectFrom("TournamentMatchGameResult")
|
||||
.innerJoin(
|
||||
"TournamentMatch",
|
||||
"TournamentMatchGameResult.matchId",
|
||||
"TournamentMatch.id",
|
||||
)
|
||||
.innerJoin(
|
||||
"TournamentStage",
|
||||
"TournamentStage.id",
|
||||
"TournamentMatch.stageId",
|
||||
)
|
||||
.innerJoin(
|
||||
"Tournament",
|
||||
"Tournament.id",
|
||||
"TournamentStage.tournamentId",
|
||||
)
|
||||
.whereRef(
|
||||
"TournamentMatchGameResult.id",
|
||||
"=",
|
||||
"TournamentMatchGameResultParticipant.matchGameResultId",
|
||||
)
|
||||
.select("Tournament.id")
|
||||
.limit(1),
|
||||
),
|
||||
}))
|
||||
.where("TournamentMatchGameResultParticipant.tournamentTeamId", "is", null)
|
||||
.execute();
|
||||
|
||||
// manual fixes, not sure why these are needed
|
||||
await db
|
||||
.updateTable("TournamentMatchGameResultParticipant")
|
||||
.set({
|
||||
tournamentTeamId: 13077,
|
||||
})
|
||||
.where("userId", "=", 44085)
|
||||
.where("tournamentTeamId", "is", null)
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.updateTable("TournamentMatchGameResultParticipant")
|
||||
.set({
|
||||
tournamentTeamId: 14589,
|
||||
})
|
||||
.where("userId", "=", 10585)
|
||||
.where("tournamentTeamId", "is", null)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async function wipeEmptyResults() {
|
||||
logger.info("Wiping empty results from TournamentResult table...");
|
||||
|
||||
const { numDeletedRows } = await db
|
||||
.deleteFrom("TournamentResult")
|
||||
.where(sql<boolean>`instr(setResults, 'W') = 0`)
|
||||
.where(sql<boolean>`instr(setResults, 'L') = 0`)
|
||||
.executeTakeFirst();
|
||||
|
||||
logger.info(
|
||||
`Wiped ${numDeletedRows} empty results from TournamentResult table.`,
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error("Error in calc-tournament-summary-result-arrays.ts", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
async function main() {
|
||||
const skills = await db
|
||||
.selectFrom("Skill")
|
||||
.leftJoin("TournamentResult", (join) =>
|
||||
join
|
||||
.onRef("TournamentResult.tournamentId", "=", "Skill.tournamentId")
|
||||
.onRef("TournamentResult.userId", "=", "Skill.userId"),
|
||||
)
|
||||
.select(["Skill.id"])
|
||||
.where("Skill.tournamentId", "is not", null)
|
||||
.where("TournamentResult.tournamentId", "is", null)
|
||||
.execute();
|
||||
|
||||
logger.info(`Found ${skills.length} skills without results`);
|
||||
|
||||
await db
|
||||
.updateTable("Skill")
|
||||
.set({
|
||||
tournamentId: null,
|
||||
})
|
||||
.where(
|
||||
"id",
|
||||
"in",
|
||||
skills.map((skill) => skill.id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
logger.info("Deleted skills without results");
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,23 +0,0 @@
|
||||
import { sql } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const rawTournamentTeamId = process.argv[2]?.trim();
|
||||
|
||||
invariant(rawTournamentTeamId, "tournament team is required (argument 1)");
|
||||
|
||||
const tournamentTeamId = Number(rawTournamentTeamId);
|
||||
|
||||
invariant(
|
||||
!Number.isNaN(tournamentTeamId),
|
||||
"tournament team id must be a number",
|
||||
);
|
||||
|
||||
const deleteMapPoolStm = sql.prepare(/*sql*/ `
|
||||
delete from "MapPoolMap"
|
||||
where "tournamentTeamId" = @tournamentTeamId
|
||||
`);
|
||||
|
||||
deleteMapPoolStm.run({ tournamentTeamId });
|
||||
|
||||
logger.info(`Deleted map pool of tournament team with id: ${tournamentTeamId}`);
|
||||
@@ -1,23 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const id = Number(process.argv[2]?.trim());
|
||||
|
||||
invariant(id, "team id is required (argument 1)");
|
||||
invariant(Number.isInteger(id), "team id must be an integer");
|
||||
|
||||
async function main() {
|
||||
await db
|
||||
.updateTable("AllTeam")
|
||||
.set({
|
||||
deletedAt: dateToDatabaseTimestamp(new Date()),
|
||||
})
|
||||
.where("id", "=", id)
|
||||
.execute();
|
||||
|
||||
logger.info(`Disbanded team with id: ${id}`);
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,37 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const friendCode = process.argv[2]?.trim();
|
||||
|
||||
invariant(friendCode, "friend code is required (argument 1)");
|
||||
|
||||
async function main() {
|
||||
const allFcs = await db
|
||||
.selectFrom("UserFriendCode")
|
||||
.innerJoin("User", "User.id", "UserFriendCode.userId")
|
||||
.select([
|
||||
"UserFriendCode.friendCode",
|
||||
"User.id as userId",
|
||||
"User.discordId",
|
||||
"User.discordUniqueName",
|
||||
])
|
||||
.orderBy("UserFriendCode.createdAt", "asc")
|
||||
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
|
||||
.execute();
|
||||
|
||||
const matches = allFcs.filter((fc) => fc.friendCode === friendCode);
|
||||
|
||||
if (matches.length === 0) {
|
||||
logger.info("No matches found");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const match of matches) {
|
||||
logger.info(
|
||||
`${match.friendCode} - ${match.discordUniqueName} - ${match.discordId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,126 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import { BANNED_MAPS } from "~/features/match-profile/banned-maps";
|
||||
import { modesShort } from "~/modules/in-game-lists/modes";
|
||||
import { stageIds } from "~/modules/in-game-lists/stage-ids";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
|
||||
import {
|
||||
databaseTimestampToDate,
|
||||
dateToDatabaseTimestamp,
|
||||
} from "~/utils/dates";
|
||||
import { logger } from "~/utils/logger";
|
||||
import names from "../locales/en/game-misc.json";
|
||||
|
||||
const SEASON_2_START = new Date("2023-12-04T17:00:00.000Z");
|
||||
|
||||
async function main() {
|
||||
const appearance = await db
|
||||
.selectFrom("GroupMatchMap")
|
||||
.innerJoin("GroupMatch", "GroupMatchMap.matchId", "GroupMatch.id")
|
||||
.select(({ fn }) => [
|
||||
"GroupMatchMap.mode",
|
||||
"GroupMatchMap.stageId",
|
||||
fn.countAll<number>().as("count"),
|
||||
])
|
||||
.groupBy(["GroupMatchMap.stageId", "GroupMatchMap.mode"])
|
||||
.where("GroupMatch.createdAt", ">", dateToDatabaseTimestamp(SEASON_2_START))
|
||||
.execute();
|
||||
|
||||
const usage: Record<
|
||||
ModeShort | "ALL",
|
||||
{ stageId: StageId; count: number }[]
|
||||
> = {
|
||||
TW: [],
|
||||
SZ: [],
|
||||
TC: [],
|
||||
RM: [],
|
||||
CB: [],
|
||||
ALL: [],
|
||||
};
|
||||
|
||||
const ageRow = await db
|
||||
.selectFrom("Build")
|
||||
.select((eb) => eb.fn.max("Build.updatedAt").as("age"))
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
const dbAgeDate = databaseTimestampToDate(ageRow.age);
|
||||
|
||||
for (const mode of modesShort) {
|
||||
for (const stageId of stageIds) {
|
||||
const count =
|
||||
appearance.find((row) => row.stageId === stageId && row.mode === mode)
|
||||
?.count ?? 0;
|
||||
|
||||
usage[mode].push({
|
||||
stageId,
|
||||
count,
|
||||
});
|
||||
|
||||
const existingAllCount = usage.ALL.find((row) => row.stageId === stageId);
|
||||
|
||||
if (!existingAllCount) {
|
||||
usage.ALL.push({
|
||||
stageId,
|
||||
count,
|
||||
});
|
||||
} else {
|
||||
existingAllCount.count += count;
|
||||
}
|
||||
}
|
||||
|
||||
usage[mode].sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
usage.ALL.sort((a, b) => b.count - a.count);
|
||||
|
||||
logger.info(`DB Age: ${dbAgeDate.toISOString()}\n`);
|
||||
|
||||
// all modes
|
||||
|
||||
logger.info("All");
|
||||
let banCount = 0;
|
||||
for (const [i, { stageId, count }] of usage.ALL.entries()) {
|
||||
const name = names[`STAGE_${stageId}`];
|
||||
|
||||
const partlyBanned = Object.values(BANNED_MAPS).some((arr) =>
|
||||
arr.includes(stageId as any),
|
||||
);
|
||||
|
||||
if (partlyBanned) banCount++;
|
||||
|
||||
logger.info(
|
||||
`${i < 9 ? " " : ""}${i + 1}) ${
|
||||
partlyBanned ? "🔴" : " "
|
||||
} ${name}: ${count}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Banned maps (at least one mode): ${banCount}`);
|
||||
logger.info();
|
||||
|
||||
// modes
|
||||
for (const mode of modesShort) {
|
||||
// if (usage[mode].every((e) => e.count === 0)) continue;
|
||||
|
||||
logger.info(mode);
|
||||
let banCount = 0;
|
||||
for (const [i, { stageId, count }] of usage[mode].entries()) {
|
||||
const name = names[`STAGE_${stageId}`];
|
||||
|
||||
const isBanned = BANNED_MAPS[mode].includes(stageId);
|
||||
if (isBanned) banCount++;
|
||||
|
||||
logger.info(
|
||||
`${i < 9 ? " " : ""}${i + 1}) ${
|
||||
isBanned ? "❌" : " "
|
||||
} ${name}: ${count}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Banned maps: ${banCount}`);
|
||||
logger.info(
|
||||
Object.values(usage[mode]).reduce((acc, cur) => acc + cur.count, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,108 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import { BANNED_MAPS } from "~/features/match-profile/banned-maps";
|
||||
import { modesShort } from "~/modules/in-game-lists/modes";
|
||||
import { stageIds } from "~/modules/in-game-lists/stage-ids";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import { logger } from "~/utils/logger";
|
||||
import { cutToNDecimalPlaces } from "~/utils/number";
|
||||
import names from "../locales/en/game-misc.json";
|
||||
|
||||
async function main() {
|
||||
const appearance = await db
|
||||
.selectFrom("MapPoolMap")
|
||||
.select(({ fn }) => [
|
||||
"MapPoolMap.stageId",
|
||||
"MapPoolMap.mode",
|
||||
fn.countAll<number>().as("count"),
|
||||
])
|
||||
.where("MapPoolMap.calendarEventId", "is not", null)
|
||||
.groupBy(["MapPoolMap.stageId", "MapPoolMap.mode"])
|
||||
.execute();
|
||||
|
||||
const usage: Record<
|
||||
ModeShort,
|
||||
{ stageId: StageId; count: number; relativeCount: number }[]
|
||||
> = {
|
||||
TW: [],
|
||||
SZ: [],
|
||||
TC: [],
|
||||
RM: [],
|
||||
CB: [],
|
||||
};
|
||||
|
||||
const ageRow = await db
|
||||
.selectFrom("Build")
|
||||
.select((eb) => eb.fn.max("Build.updatedAt").as("age"))
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
const dbAgeDate = databaseTimestampToDate(ageRow.age);
|
||||
|
||||
for (const mode of modesShort) {
|
||||
for (const stageId of stageIds) {
|
||||
const count =
|
||||
appearance.find((row) => row.stageId === stageId && row.mode === mode)
|
||||
?.count ?? 0;
|
||||
|
||||
const firstAppear = await db
|
||||
.selectFrom("MapPoolMap")
|
||||
.innerJoin(
|
||||
"CalendarEvent",
|
||||
"MapPoolMap.calendarEventId",
|
||||
"CalendarEvent.id",
|
||||
)
|
||||
.innerJoin(
|
||||
"CalendarEventDate",
|
||||
"CalendarEvent.id",
|
||||
"CalendarEventDate.eventId",
|
||||
)
|
||||
.select((eb) =>
|
||||
eb.fn.min("CalendarEventDate.startTime").as("firstAppear"),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
const firstAppearDate = firstAppear
|
||||
? databaseTimestampToDate(firstAppear.firstAppear)
|
||||
: null;
|
||||
|
||||
const datesSinceFirstAppear = firstAppearDate
|
||||
? Math.floor((dbAgeDate.getTime() - firstAppearDate.getTime()) / 864e5)
|
||||
: null;
|
||||
|
||||
usage[mode].push({
|
||||
stageId,
|
||||
count,
|
||||
relativeCount: datesSinceFirstAppear
|
||||
? cutToNDecimalPlaces((count / datesSinceFirstAppear) * 30, 3)
|
||||
: 0,
|
||||
});
|
||||
}
|
||||
|
||||
usage[mode].sort((a, b) => b.relativeCount - a.relativeCount);
|
||||
}
|
||||
|
||||
logger.info(`DB Age: ${dbAgeDate.toISOString()}\n`);
|
||||
for (const mode of modesShort) {
|
||||
logger.info(mode);
|
||||
let banCount = 0;
|
||||
for (const [i, { stageId, count, relativeCount }] of usage[
|
||||
mode
|
||||
].entries()) {
|
||||
const name = names[`STAGE_${stageId}`];
|
||||
|
||||
const isBanned = BANNED_MAPS[mode].includes(stageId);
|
||||
if (isBanned) banCount++;
|
||||
|
||||
logger.info(
|
||||
`${i < 9 ? " " : ""}${i + 1}) ${
|
||||
isBanned ? "❌" : " "
|
||||
} ${name}: ${relativeCount} (${count})`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Banned maps: ${banCount}`);
|
||||
logger.info();
|
||||
}
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,170 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import { db } from "~/db/sql";
|
||||
import { uploadStreamToS3 } from "~/features/img-upload/s3.server";
|
||||
import { databaseTimestampNow } from "~/utils/dates";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const TOURNAMENT_LOGO_PATH = "public/static-assets/img/tournament-logos";
|
||||
const ADMIN_ID = 1;
|
||||
|
||||
const LOGO_IDENTIFIER_TO_PATTERN: Record<string, string[]> = {
|
||||
sf: ["sendouq"],
|
||||
pp: ["paddling pool"],
|
||||
itz: ["in the zone"],
|
||||
pn: ["picnic"],
|
||||
pg: ["proving grounds"],
|
||||
tc: ["triton"],
|
||||
sos: ["swim or sink"],
|
||||
ftiu: ["from the ink up"],
|
||||
cc: ["coral clash"],
|
||||
lu: ["level up"],
|
||||
a41: ["all 4 one"],
|
||||
fb: ["fry basket"],
|
||||
d: ["the depths"],
|
||||
e: ["eclipse"],
|
||||
hc: ["homecoming"],
|
||||
bio: ["bad ideas"],
|
||||
ai: ["tenoch"],
|
||||
mm: ["megalodon monday"],
|
||||
ho: ["heaven 2 ocean"],
|
||||
kr: ["kraken royale"],
|
||||
mr: ["menu royale"],
|
||||
bc: ["barracuda co"],
|
||||
ci: ["crimson ink"],
|
||||
me: ["mesozoic mayhem"],
|
||||
ros: ["rain or shine"],
|
||||
sj: ["squid junction"],
|
||||
ss: ["silly sausage"],
|
||||
ul: ["united-lan"],
|
||||
sc: ["soul cup"],
|
||||
};
|
||||
|
||||
async function uploadLogoFile(
|
||||
filePath: string,
|
||||
identifier: string,
|
||||
): Promise<string> {
|
||||
logger.info(`Uploading ${identifier}.png to S3...`);
|
||||
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const stream = Readable.from(fileBuffer);
|
||||
|
||||
const fileName = `tournament-logo-${identifier}.png`;
|
||||
|
||||
const s3Url = await uploadStreamToS3(stream, fileName);
|
||||
|
||||
invariant(s3Url, `Failed to upload ${identifier}.png to S3`);
|
||||
|
||||
logger.info(`Uploaded ${identifier}.png to ${s3Url}`);
|
||||
return fileName;
|
||||
}
|
||||
|
||||
async function createImageRecord(url: string): Promise<number> {
|
||||
const result = await db
|
||||
.insertInto("UnvalidatedUserSubmittedImage")
|
||||
.values({
|
||||
url,
|
||||
validatedAt: databaseTimestampNow(),
|
||||
submitterUserId: ADMIN_ID,
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return result.id;
|
||||
}
|
||||
|
||||
async function updateCalendarEvents(
|
||||
imageId: number,
|
||||
patterns: string[],
|
||||
): Promise<number> {
|
||||
const events = await db
|
||||
.selectFrom("CalendarEvent")
|
||||
.select(["id", "name"])
|
||||
.where("avatarImgId", "is", null)
|
||||
.execute();
|
||||
|
||||
let updateCount = 0;
|
||||
|
||||
for (const event of events) {
|
||||
const normalizedEventName = event.name.toLowerCase();
|
||||
const matches = patterns.some((pattern) =>
|
||||
normalizedEventName.includes(pattern),
|
||||
);
|
||||
|
||||
if (matches) {
|
||||
await db
|
||||
.updateTable("CalendarEvent")
|
||||
.set({ avatarImgId: imageId })
|
||||
.where("id", "=", event.id)
|
||||
.execute();
|
||||
|
||||
logger.info(`Updated CalendarEvent ${event.id}: "${event.name}"`);
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return updateCount;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
logger.info("Starting tournament logo migration to S3...");
|
||||
|
||||
const defaultLogoPath = path.join(TOURNAMENT_LOGO_PATH, "default.png");
|
||||
if (fs.existsSync(defaultLogoPath)) {
|
||||
logger.info("\n=== Uploading default tournament logo ===");
|
||||
const defaultS3Url = await uploadLogoFile(defaultLogoPath, "default");
|
||||
const defaultImageId = await createImageRecord(defaultS3Url);
|
||||
logger.info(
|
||||
`Created UnvalidatedUserSubmittedImage record for default logo with ID ${defaultImageId}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
const logoFiles = fs
|
||||
.readdirSync(TOURNAMENT_LOGO_PATH)
|
||||
.filter((file) => file.endsWith(".png") && file !== "default.png");
|
||||
|
||||
logger.info(`Found ${logoFiles.length} logo files to migrate`);
|
||||
|
||||
let totalUpdated = 0;
|
||||
|
||||
for (const file of logoFiles) {
|
||||
const identifier = file.replace(".png", "");
|
||||
const patterns = LOGO_IDENTIFIER_TO_PATTERN[identifier];
|
||||
|
||||
if (!patterns) {
|
||||
logger.warn(`No pattern mapping found for ${identifier}, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(TOURNAMENT_LOGO_PATH, file);
|
||||
|
||||
const s3Url = await uploadLogoFile(filePath, identifier);
|
||||
|
||||
const imageId = await createImageRecord(s3Url);
|
||||
logger.info(
|
||||
`Created UnvalidatedUserSubmittedImage record with ID ${imageId}`,
|
||||
);
|
||||
|
||||
const updatedCount = await updateCalendarEvents(imageId, patterns);
|
||||
totalUpdated += updatedCount;
|
||||
|
||||
logger.info(
|
||||
`Updated ${updatedCount} CalendarEvent records for ${identifier}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.info("\n=== Migration Complete ===");
|
||||
logger.info(`Total CalendarEvent records updated: ${totalUpdated}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {
|
||||
logger.info("Script completed successfully");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("Script failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
async function main() {
|
||||
if (Seasons.current()) {
|
||||
throw new Error("There is a current season");
|
||||
}
|
||||
|
||||
if (Seasons.next()?.nth !== 8) {
|
||||
throw new Error("Next season is not 8. Script needs modifying");
|
||||
}
|
||||
|
||||
const wrongSeasonStartDate = new Date("2025-06-14T18:00:00.000Z");
|
||||
|
||||
const allMatches = await db
|
||||
.selectFrom("GroupMatch")
|
||||
.selectAll()
|
||||
.where(
|
||||
"GroupMatch.createdAt",
|
||||
">=",
|
||||
dateToDatabaseTimestamp(wrongSeasonStartDate),
|
||||
)
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.deleteFrom("Skill")
|
||||
.where(
|
||||
"Skill.groupMatchId",
|
||||
"in",
|
||||
allMatches.map((m) => m.id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
for (const match of allMatches) {
|
||||
await SQMatchRepository.lockMatchWithoutSkillChange(match.id);
|
||||
}
|
||||
|
||||
logger.info(`All done with nuking the season (${allMatches.length} matches)`);
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,58 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
async function main() {
|
||||
const allFcs = await db
|
||||
.selectFrom("UserFriendCode")
|
||||
.innerJoin("User", "User.id", "UserFriendCode.userId")
|
||||
.select(["UserFriendCode.friendCode", "User.id as userId"])
|
||||
.orderBy("UserFriendCode.createdAt", "asc")
|
||||
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
|
||||
.execute();
|
||||
|
||||
const fcMap = new Map<string, number[]>();
|
||||
for (const fc of allFcs) {
|
||||
const fcs = fcMap.get(fc.friendCode) ?? [];
|
||||
fcs.push(fc.userId);
|
||||
fcMap.set(fc.friendCode, fcs);
|
||||
}
|
||||
|
||||
const friendCodeAdders = await db
|
||||
.selectFrom("UserFriendCode")
|
||||
.innerJoin("User", "User.id", "UserFriendCode.userId")
|
||||
.select([
|
||||
"UserFriendCode.friendCode",
|
||||
"UserFriendCode.createdAt",
|
||||
"User.id",
|
||||
"User.discordId",
|
||||
"User.discordUniqueName",
|
||||
])
|
||||
.orderBy("UserFriendCode.createdAt", "desc")
|
||||
.whereRef("User.id", "=", "UserFriendCode.submitterUserId")
|
||||
.limit(90)
|
||||
.execute();
|
||||
|
||||
let result = "";
|
||||
|
||||
let date = "";
|
||||
for (const [i, friendCodeAdder] of friendCodeAdders.entries()) {
|
||||
const utc = databaseTimestampToDate(
|
||||
friendCodeAdder.createdAt,
|
||||
).toUTCString();
|
||||
const newDate = utc.split(",")[0];
|
||||
if (date !== newDate) {
|
||||
date = newDate;
|
||||
result += "\n";
|
||||
}
|
||||
|
||||
const isDuplicate =
|
||||
(fcMap.get(friendCodeAdder.friendCode) ?? [])?.length > 1;
|
||||
|
||||
result += `${i < 9 ? "0" : ""}${i + 1}) ${utc} - ${friendCodeAdder.friendCode}${isDuplicate ? " >>DUPLICATE<<" : ""} - ${friendCodeAdder.discordUniqueName} - ${friendCodeAdder.discordId}\n`;
|
||||
}
|
||||
|
||||
logger.info(result);
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,18 +0,0 @@
|
||||
/** biome-ignore-all lint/suspicious/noConsole: CLI script */
|
||||
import * as SplatoonRotationRepository from "~/features/splatoon-rotations/SplatoonRotationRepository.server";
|
||||
import { fetchRotations } from "~/features/splatoon-rotations/splatoon-rotations.server";
|
||||
|
||||
async function main() {
|
||||
console.log("Fetching splatoon rotations from splatoon3.ink...");
|
||||
|
||||
const rotations = await fetchRotations();
|
||||
console.log(`Fetched ${rotations.length} rotations`);
|
||||
|
||||
await SplatoonRotationRepository.replaceAll(rotations);
|
||||
console.log("Rotations synced to database");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("Failed to sync rotations:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
// usage: npx vite-node ./scripts/sync-tournament-vods.ts <tournamentId>
|
||||
import { processOneTournament } from "~/routines/syncTournamentVods";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const tournamentId = Number(process.argv[2]?.trim());
|
||||
invariant(
|
||||
tournamentId && !Number.isNaN(tournamentId),
|
||||
"tournament id is required (argument 1)",
|
||||
);
|
||||
|
||||
logger.info(`Syncing VODs for tournament ${tournamentId}...`);
|
||||
await processOneTournament(tournamentId);
|
||||
logger.info("Done");
|
||||
@@ -1,7 +0,0 @@
|
||||
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
void (async () => {
|
||||
await BadgeRepository.syncXPBadges();
|
||||
logger.info("Synced XP badges");
|
||||
})();
|
||||
@@ -1,406 +0,0 @@
|
||||
/** biome-ignore-all lint/suspicious/noConsole: CLI script output */
|
||||
/**
|
||||
* Tournament Tiering Experiment Script
|
||||
*
|
||||
* Run with: pnpm exec vite-node scripts/tournament-tiers-experiment.ts
|
||||
*
|
||||
* Calculates tournament tiers based on top teams' average SeedingSkill.
|
||||
* Tweak the THRESHOLDS object to experiment with different tier distributions.
|
||||
*/
|
||||
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
const db = new Database("db-prod.sqlite3", { readonly: true });
|
||||
|
||||
// ============================================================================
|
||||
// CONFIGURATION - Tweak these values to experiment
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Minimum ordinal thresholds for each tier.
|
||||
* A tournament is assigned the highest tier where its score meets the threshold.
|
||||
*
|
||||
* Current values are based on percentile analysis:
|
||||
* - X: Top ~1% of tournaments
|
||||
* - S+: Top ~3%
|
||||
* - S: Top ~8%
|
||||
* - A+: Top ~15%
|
||||
* - A: Top ~25%
|
||||
* - B+: Top ~40%
|
||||
* - B: Top ~55%
|
||||
* - C+: Top ~75%
|
||||
* - C: Everything else
|
||||
*/
|
||||
const THRESHOLDS = {
|
||||
X: 32,
|
||||
"S+": 29,
|
||||
S: 26,
|
||||
"A+": 24,
|
||||
A: 21,
|
||||
"B+": 15,
|
||||
B: 10,
|
||||
"C+": 5,
|
||||
C: Number.NEGATIVE_INFINITY, // Catch-all
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* How many top teams to consider for the tournament score.
|
||||
* Using 8 captures the competitive core of most tournaments.
|
||||
*/
|
||||
const TOP_TEAMS_COUNT = 8;
|
||||
|
||||
/**
|
||||
* Minimum number of teams required for a tournament to be tiered.
|
||||
* Tournaments with fewer teams will be marked as "UNTIERED".
|
||||
*/
|
||||
const MIN_TEAMS_FOR_TIERING = 8;
|
||||
|
||||
/**
|
||||
* Size bonus configuration.
|
||||
* The bonus scales inversely with skill level - X-tier gets no bonus,
|
||||
* lower tiers get increasingly more bonus for larger tournaments.
|
||||
*
|
||||
* NO_BONUS_ABOVE: Score threshold above which no size bonus applies (X-tier)
|
||||
* MAX_BONUS_PER_10_TEAMS: Maximum bonus per 10 teams above minimum (applied at score 0)
|
||||
*
|
||||
* The bonus scales linearly: at NO_BONUS_ABOVE, multiplier is 0.
|
||||
* As score decreases toward 0, multiplier approaches MAX_BONUS_PER_10_TEAMS.
|
||||
*
|
||||
* Formula: bonus = scaleFactor * MAX_BONUS * (teamsAboveMin / 10)
|
||||
* where scaleFactor = max(0, (NO_BONUS_ABOVE - rawScore) / NO_BONUS_ABOVE)
|
||||
*/
|
||||
const SIZE_BONUS = {
|
||||
NO_BONUS_ABOVE: 32, // X-tier threshold - no bonus at this level
|
||||
MAX_BONUS_PER_10_TEAMS: 1.5, // Max points added per 10 extra teams (at score ~0)
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter to only include tournaments after this date (ISO string).
|
||||
* Set to null to include all tournaments.
|
||||
*/
|
||||
const MIN_DATE: string | null = null; // e.g., "2024-01-01"
|
||||
|
||||
// ============================================================================
|
||||
// IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
type Tier = keyof typeof THRESHOLDS;
|
||||
|
||||
interface TournamentData {
|
||||
tournamentId: number;
|
||||
eventId: number;
|
||||
name: string;
|
||||
startTime: number;
|
||||
teamCount: number;
|
||||
top8AvgOrdinal: number | null;
|
||||
adjustedScore: number | null;
|
||||
tier: Tier | "UNTIERED";
|
||||
}
|
||||
|
||||
function calculateTier(score: number | null): Tier | "UNTIERED" {
|
||||
if (score === null) return "UNTIERED";
|
||||
|
||||
const tiers = Object.entries(THRESHOLDS) as [Tier, number][];
|
||||
for (const [tier, threshold] of tiers) {
|
||||
if (score >= threshold) return tier;
|
||||
}
|
||||
return "C";
|
||||
}
|
||||
|
||||
function calculateAdjustedScore(rawScore: number, teamCount: number): number {
|
||||
if (SIZE_BONUS.MAX_BONUS_PER_10_TEAMS === 0) return rawScore;
|
||||
|
||||
// Scale factor: 0 at NO_BONUS_ABOVE, approaches 1 as score approaches 0
|
||||
const scaleFactor = Math.max(
|
||||
0,
|
||||
(SIZE_BONUS.NO_BONUS_ABOVE - rawScore) / SIZE_BONUS.NO_BONUS_ABOVE,
|
||||
);
|
||||
|
||||
const teamsAboveMin = Math.max(0, teamCount - MIN_TEAMS_FOR_TIERING);
|
||||
const bonus =
|
||||
scaleFactor * SIZE_BONUS.MAX_BONUS_PER_10_TEAMS * (teamsAboveMin / 10);
|
||||
|
||||
return rawScore + bonus;
|
||||
}
|
||||
|
||||
function getTournamentData(): TournamentData[] {
|
||||
const query = `
|
||||
WITH TeamSkills AS (
|
||||
SELECT
|
||||
tt.tournamentId,
|
||||
tt.id as teamId,
|
||||
AVG(ss.ordinal) as avg_team_ordinal
|
||||
FROM TournamentTeam tt
|
||||
JOIN TournamentTeamMember ttm ON ttm.tournamentTeamId = tt.id
|
||||
LEFT JOIN SeedingSkill ss ON ss.userId = ttm.userId AND ss.type = 'RANKED'
|
||||
WHERE tt.droppedOut = 0
|
||||
GROUP BY tt.tournamentId, tt.id
|
||||
),
|
||||
TeamCounts AS (
|
||||
SELECT tournamentId, COUNT(*) as team_count
|
||||
FROM TeamSkills
|
||||
WHERE avg_team_ordinal IS NOT NULL
|
||||
GROUP BY tournamentId
|
||||
),
|
||||
RankedTeams AS (
|
||||
SELECT
|
||||
ts.tournamentId,
|
||||
ts.avg_team_ordinal,
|
||||
tc.team_count,
|
||||
ROW_NUMBER() OVER (PARTITION BY ts.tournamentId ORDER BY ts.avg_team_ordinal DESC) as rank
|
||||
FROM TeamSkills ts
|
||||
JOIN TeamCounts tc ON tc.tournamentId = ts.tournamentId
|
||||
WHERE ts.avg_team_ordinal IS NOT NULL
|
||||
),
|
||||
TournamentScores AS (
|
||||
SELECT
|
||||
tournamentId,
|
||||
AVG(avg_team_ordinal) as top8_avg_ordinal,
|
||||
MAX(team_count) as team_count
|
||||
FROM RankedTeams
|
||||
WHERE rank <= ${TOP_TEAMS_COUNT}
|
||||
GROUP BY tournamentId
|
||||
)
|
||||
SELECT
|
||||
t.id as tournamentId,
|
||||
ce.id as eventId,
|
||||
ce.name,
|
||||
ced.startTime,
|
||||
ts.team_count as teamCount,
|
||||
ts.top8_avg_ordinal as top8AvgOrdinal
|
||||
FROM Tournament t
|
||||
JOIN CalendarEvent ce ON ce.tournamentId = t.id
|
||||
JOIN CalendarEventDate ced ON ced.eventId = ce.id
|
||||
LEFT JOIN TournamentScores ts ON ts.tournamentId = t.id
|
||||
WHERE t.isFinalized = 1
|
||||
${MIN_DATE ? `AND ced.startTime >= strftime('%s', '${MIN_DATE}') * 1000` : ""}
|
||||
GROUP BY t.id
|
||||
ORDER BY ced.startTime DESC
|
||||
`;
|
||||
|
||||
const rows = db.prepare(query).all() as Array<{
|
||||
tournamentId: number;
|
||||
eventId: number;
|
||||
name: string;
|
||||
startTime: number;
|
||||
teamCount: number | null;
|
||||
top8AvgOrdinal: number | null;
|
||||
}>;
|
||||
|
||||
return rows.map((row) => {
|
||||
const teamCount = row.teamCount ?? 0;
|
||||
const meetsMinTeams = teamCount >= MIN_TEAMS_FOR_TIERING;
|
||||
|
||||
let adjustedScore: number | null = null;
|
||||
if (row.top8AvgOrdinal !== null && meetsMinTeams) {
|
||||
adjustedScore = calculateAdjustedScore(row.top8AvgOrdinal, teamCount);
|
||||
}
|
||||
|
||||
return {
|
||||
tournamentId: row.tournamentId,
|
||||
eventId: row.eventId,
|
||||
name: row.name,
|
||||
startTime: row.startTime,
|
||||
teamCount,
|
||||
top8AvgOrdinal: row.top8AvgOrdinal,
|
||||
adjustedScore,
|
||||
tier: meetsMinTeams ? calculateTier(adjustedScore) : "UNTIERED",
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function printDistribution(tournaments: TournamentData[]) {
|
||||
const distribution: Record<string, number> = {};
|
||||
const tiers = [...Object.keys(THRESHOLDS), "UNTIERED"];
|
||||
|
||||
for (const tier of tiers) {
|
||||
distribution[tier] = 0;
|
||||
}
|
||||
|
||||
for (const t of tournaments) {
|
||||
distribution[t.tier]++;
|
||||
}
|
||||
|
||||
const total = tournaments.length;
|
||||
const tiered = total - distribution.UNTIERED;
|
||||
|
||||
console.log(`\n${"=".repeat(60)}`);
|
||||
console.log("TIER DISTRIBUTION");
|
||||
console.log("=".repeat(60));
|
||||
console.log(`Total tournaments: ${total}`);
|
||||
console.log(`Tiered (${MIN_TEAMS_FOR_TIERING}+ teams): ${tiered}`);
|
||||
console.log(
|
||||
`Untiered (< ${MIN_TEAMS_FOR_TIERING} teams): ${distribution.UNTIERED}`,
|
||||
);
|
||||
console.log("-".repeat(60));
|
||||
|
||||
for (const tier of tiers) {
|
||||
if (tier === "UNTIERED") continue;
|
||||
const count = distribution[tier];
|
||||
const pctOfTiered = tiered > 0 ? ((count / tiered) * 100).toFixed(1) : "0";
|
||||
const bar = "█".repeat(Math.round(count / 20));
|
||||
console.log(
|
||||
`${tier.padEnd(3)} | ${String(count).padStart(4)} | ${pctOfTiered.padStart(5)}% of tiered | ${bar}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(timestamp: number): string {
|
||||
// timestamps are stored in seconds, not milliseconds
|
||||
return new Date(timestamp * 1000).toISOString().split("T")[0];
|
||||
}
|
||||
|
||||
function printTopTournaments(
|
||||
tournaments: TournamentData[],
|
||||
tier: Tier,
|
||||
limit = 10,
|
||||
) {
|
||||
const filtered = tournaments
|
||||
.filter((t) => t.tier === tier)
|
||||
.sort((a, b) => (b.adjustedScore ?? 0) - (a.adjustedScore ?? 0))
|
||||
.slice(0, limit);
|
||||
|
||||
console.log(`\n${"=".repeat(60)}`);
|
||||
console.log(`TOP ${limit} ${tier}-TIER TOURNAMENTS`);
|
||||
console.log("=".repeat(60));
|
||||
|
||||
for (const t of filtered) {
|
||||
const date = formatDate(t.startTime);
|
||||
console.log(
|
||||
`[${date}] ${t.name.substring(0, 40).padEnd(40)} | ${t.teamCount} teams | score: ${t.adjustedScore?.toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function printBottomOfTier(
|
||||
tournaments: TournamentData[],
|
||||
tier: Tier,
|
||||
limit = 5,
|
||||
) {
|
||||
const filtered = tournaments
|
||||
.filter((t) => t.tier === tier)
|
||||
.sort((a, b) => (a.adjustedScore ?? 0) - (b.adjustedScore ?? 0))
|
||||
.slice(0, limit);
|
||||
|
||||
console.log(`\n${"-".repeat(60)}`);
|
||||
console.log(`BOTTOM ${limit} OF ${tier}-TIER (borderline)`);
|
||||
console.log("-".repeat(60));
|
||||
|
||||
for (const t of filtered) {
|
||||
const date = formatDate(t.startTime);
|
||||
console.log(
|
||||
`[${date}] ${t.name.substring(0, 40).padEnd(40)} | ${t.teamCount} teams | score: ${t.adjustedScore?.toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function printThresholds() {
|
||||
console.log(`\n${"=".repeat(60)}`);
|
||||
console.log("CURRENT THRESHOLDS");
|
||||
console.log("=".repeat(60));
|
||||
for (const [tier, threshold] of Object.entries(THRESHOLDS)) {
|
||||
if (threshold === Number.NEGATIVE_INFINITY) {
|
||||
console.log(`${tier}: < ${THRESHOLDS["C+"]}`);
|
||||
} else {
|
||||
console.log(`${tier}: >= ${threshold}`);
|
||||
}
|
||||
}
|
||||
console.log(`\nTop teams considered: ${TOP_TEAMS_COUNT}`);
|
||||
console.log(`Min teams for tiering: ${MIN_TEAMS_FOR_TIERING}`);
|
||||
console.log("\nSize bonus (scales inversely with skill):");
|
||||
console.log(` No bonus above score: ${SIZE_BONUS.NO_BONUS_ABOVE}`);
|
||||
console.log(
|
||||
` Max bonus per 10 teams: ${SIZE_BONUS.MAX_BONUS_PER_10_TEAMS} points`,
|
||||
);
|
||||
|
||||
// Show example bonus calculations
|
||||
console.log("\nExample bonuses for 50-team tournament:");
|
||||
const exampleTeams = 50;
|
||||
for (const rawScore of [32, 28, 24, 20, 15, 10, 5, 0]) {
|
||||
const adjusted = calculateAdjustedScore(rawScore, exampleTeams);
|
||||
const bonus = adjusted - rawScore;
|
||||
console.log(
|
||||
` Raw ${rawScore.toString().padStart(2)} -> ${adjusted.toFixed(2)} (+${bonus.toFixed(2)})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN
|
||||
// ============================================================================
|
||||
|
||||
function main() {
|
||||
console.log("Tournament Tiering Experiment");
|
||||
console.log("==============================\n");
|
||||
|
||||
printThresholds();
|
||||
|
||||
const tournaments = getTournamentData();
|
||||
printDistribution(tournaments);
|
||||
|
||||
// Show examples from top tiers
|
||||
printTopTournaments(tournaments, "X", 15);
|
||||
printBottomOfTier(tournaments, "X", 5);
|
||||
|
||||
printTopTournaments(tournaments, "S+", 10);
|
||||
printBottomOfTier(tournaments, "S+", 5);
|
||||
|
||||
printTopTournaments(tournaments, "S", 10);
|
||||
|
||||
// Show tournaments promoted by size bonus
|
||||
console.log(`\n${"=".repeat(60)}`);
|
||||
console.log("TOURNAMENTS PROMOTED BY SIZE BONUS");
|
||||
console.log("=".repeat(60));
|
||||
const promoted = tournaments
|
||||
.filter((t) => t.tier !== "UNTIERED" && t.top8AvgOrdinal !== null)
|
||||
.filter((t) => {
|
||||
const rawTier = calculateTier(t.top8AvgOrdinal);
|
||||
return rawTier !== t.tier;
|
||||
})
|
||||
.sort((a, b) => (b.adjustedScore ?? 0) - (a.adjustedScore ?? 0))
|
||||
.slice(0, 20);
|
||||
|
||||
if (promoted.length === 0) {
|
||||
console.log("No tournaments were promoted by size bonus.");
|
||||
} else {
|
||||
for (const t of promoted) {
|
||||
const rawTier = calculateTier(t.top8AvgOrdinal);
|
||||
const bonus = (t.adjustedScore ?? 0) - (t.top8AvgOrdinal ?? 0);
|
||||
console.log(
|
||||
`${rawTier.toString().padEnd(3)} -> ${t.tier.padEnd(3)} | ${t.name.substring(0, 35).padEnd(35)} | ${t.teamCount} teams | +${bonus.toFixed(2)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Recent tournaments analysis
|
||||
console.log(`\n${"=".repeat(60)}`);
|
||||
console.log("RECENT TOURNAMENTS (last 30 tiered)");
|
||||
console.log("=".repeat(60));
|
||||
tournaments
|
||||
.filter((t) => t.tier !== "UNTIERED")
|
||||
.sort((a, b) => b.startTime - a.startTime)
|
||||
.slice(0, 30)
|
||||
.forEach((t) => {
|
||||
const date = formatDate(t.startTime);
|
||||
const safeName = t.name.substring(0, 35).padEnd(35);
|
||||
console.log(
|
||||
`${t.tier.padEnd(3)} | [${date}] ${safeName} | ${String(t.teamCount).padStart(3)} teams | ${t.adjustedScore?.toFixed(1)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// Full CSV dump ordered by score
|
||||
console.log(`\n${"=".repeat(60)}`);
|
||||
console.log("FULL CSV DUMP (ordered by score descending)");
|
||||
console.log("=".repeat(60));
|
||||
console.log("name,score,tier");
|
||||
tournaments
|
||||
.filter((t) => t.tier !== "UNTIERED")
|
||||
.sort((a, b) => (b.adjustedScore ?? 0) - (a.adjustedScore ?? 0))
|
||||
.forEach((t) => {
|
||||
const safeName = t.name.replace(/,/g, ";").replace(/"/g, "'");
|
||||
console.log(`"${safeName}",${t.adjustedScore?.toFixed(2)},${t.tier}`);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
db.close();
|
||||
@@ -1,47 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
async function main() {
|
||||
const weaponPools = await db
|
||||
.selectFrom("UserWeapon")
|
||||
.select([
|
||||
"UserWeapon.userId",
|
||||
"UserWeapon.weaponSplId",
|
||||
"UserWeapon.userId",
|
||||
])
|
||||
.where("UserWeapon.order", "!=", 5)
|
||||
.orderBy("UserWeapon.order", "asc")
|
||||
.execute();
|
||||
|
||||
// group by userId
|
||||
const weaponPoolsByUserId = weaponPools.reduce(
|
||||
(acc, weaponPool) => {
|
||||
if (!acc[weaponPool.userId]) {
|
||||
acc[weaponPool.userId] = [];
|
||||
}
|
||||
|
||||
acc[weaponPool.userId].push(weaponPool);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, typeof weaponPools>,
|
||||
);
|
||||
|
||||
for (const [userId, weaponPools] of Object.entries(weaponPoolsByUserId)) {
|
||||
const weaponPoolIds = weaponPools.map(
|
||||
(weaponPool) => weaponPool.weaponSplId,
|
||||
);
|
||||
|
||||
await db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
weaponPool: JSON.stringify(weaponPoolIds),
|
||||
})
|
||||
.where("User.id", "=", Number(userId))
|
||||
.execute();
|
||||
}
|
||||
|
||||
logger.info("done with the transfer");
|
||||
}
|
||||
|
||||
void main();
|
||||
@@ -1,69 +0,0 @@
|
||||
// usage: pnpm exec vite-node ./scripts/unlock-league-matches.ts <parentTournamentId>
|
||||
import { sql } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const parentTournamentId = Number(process.argv[2]?.trim());
|
||||
invariant(
|
||||
parentTournamentId && !Number.isNaN(parentTournamentId),
|
||||
"parent tournament id is required (argument 1)",
|
||||
);
|
||||
|
||||
const divisions = sql
|
||||
.prepare(
|
||||
'SELECT id FROM "Tournament" WHERE parentTournamentId = @parentTournamentId',
|
||||
)
|
||||
.all({ parentTournamentId }) as { id: number }[];
|
||||
|
||||
invariant(
|
||||
divisions.length > 0,
|
||||
`No divisions found for tournament ${parentTournamentId}`,
|
||||
);
|
||||
logger.info(`Found ${divisions.length} divisions`);
|
||||
|
||||
const updateMatch = sql.prepare(
|
||||
'UPDATE "TournamentMatch" SET status = 2 WHERE id = @id',
|
||||
);
|
||||
|
||||
const unlockAll = sql.transaction(() => {
|
||||
let totalUnlocked = 0;
|
||||
|
||||
for (const division of divisions) {
|
||||
const stages = sql
|
||||
.prepare(
|
||||
'SELECT id FROM "TournamentStage" WHERE tournamentId = @tournamentId',
|
||||
)
|
||||
.all({ tournamentId: division.id }) as { id: number }[];
|
||||
|
||||
if (stages.length === 0) continue;
|
||||
const stageIds = stages.map((s) => s.id);
|
||||
|
||||
const lockedMatches = sql
|
||||
.prepare(
|
||||
`SELECT id, opponentOne, opponentTwo FROM "TournamentMatch"
|
||||
WHERE stageId IN (${stageIds.map(() => "?").join(",")})
|
||||
AND status = 0`,
|
||||
)
|
||||
.all(...stageIds) as {
|
||||
id: number;
|
||||
opponentOne: string;
|
||||
opponentTwo: string;
|
||||
}[];
|
||||
|
||||
let divUnlocked = 0;
|
||||
for (const match of lockedMatches) {
|
||||
const o1 = JSON.parse(match.opponentOne);
|
||||
const o2 = JSON.parse(match.opponentTwo);
|
||||
if (o1?.id == null || o2?.id == null) continue;
|
||||
updateMatch.run({ id: match.id });
|
||||
divUnlocked++;
|
||||
}
|
||||
|
||||
logger.info(`Division ${division.id}: unlocked ${divUnlocked} match(es)`);
|
||||
totalUnlocked += divUnlocked;
|
||||
}
|
||||
|
||||
logger.info(`Total unlocked: ${totalUnlocked} match(es)`);
|
||||
});
|
||||
|
||||
unlockAll();
|
||||
@@ -1,34 +0,0 @@
|
||||
import { db } from "~/db/sql";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const rawEventId = process.argv[2]?.trim();
|
||||
invariant(rawEventId, "eventId is required (argument 1)");
|
||||
|
||||
const eventId = Number(rawEventId);
|
||||
invariant(!Number.isNaN(eventId), "eventId must be a number");
|
||||
|
||||
const newName = process.argv[3]?.trim();
|
||||
invariant(newName, "newName is required (argument 2)");
|
||||
|
||||
async function main() {
|
||||
const oldName = (
|
||||
await db
|
||||
.selectFrom("CalendarEvent")
|
||||
.select(["CalendarEvent.name"])
|
||||
.where("id", "=", eventId)
|
||||
.executeTakeFirstOrThrow()
|
||||
).name;
|
||||
|
||||
await db
|
||||
.updateTable("CalendarEvent")
|
||||
.set({ name: newName })
|
||||
.where("CalendarEvent.id", "=", eventId)
|
||||
.execute();
|
||||
|
||||
logger.info(
|
||||
`Event name updated from "${oldName}" to "${newName}" for event ID: ${eventId}`,
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user