badges improvements

This commit is contained in:
2026-08-23 20:31:44 -07:00
parent ac2e61731c
commit e3fea18ab2
9 changed files with 366 additions and 76 deletions
+84 -39
View File
@@ -70,16 +70,6 @@ export function createBadgesService(pb: PocketBase) {
return true;
},
async clearAllBadgeProgress(): Promise<number> {
const existingProgress = await pb.collection("badge_progress").getFullList({
filter: 'badge.type != "manual"',
});
for (const progress of existingProgress) {
await pb.collection("badge_progress").delete(progress.id);
}
return existingProgress.length;
},
async calculateBadgeProgress(playerId: string, badge: Badge): Promise<number> {
if (badge.type === "manual") {
return 0;
@@ -651,27 +641,36 @@ export function createBadgesService(pb: PocketBase) {
return transformBadgeProgress(created);
},
async migrateBadgeProgress(): Promise<{
success: boolean;
async migrateBadgeProgress(
onProgress?: (playersProcessed: number, totalPlayers: number) => void
): Promise<{
playersProcessed: number;
progressRecordsCreated: number;
totalBadgesEarned: number;
created: number;
updated: number;
deleted: number;
averageBadgesPerPlayer: string;
}> {
await this.clearAllBadgeProgress();
const badges = await this.listBadges();
const autoBadges = badges.filter((badge) => badge.type !== "manual");
const players = await pb.collection("players").getFullList();
const allPlayers = await pb.collection("players").getFullList();
const uniquePlayers = new Set(allPlayers.map((p: any) => p.id));
// Phase 1: read-only calculation. Existing badge_progress rows stay
// untouched until the full desired state is known.
const desired = new Map<string, {
badge: string;
player: string;
progress: number;
earned: boolean;
}>();
const failedKeys = new Set<string>();
let playersProcessed = 0;
let totalProgressRecords = 0;
let totalBadgesEarned = 0;
for (const playerId of uniquePlayers) {
for (const badge of badges) {
for (const player of players) {
for (const badge of autoBadges) {
const key = `${player.id}|${badge.id}`;
try {
const progress = await this.calculateBadgeProgress(playerId, badge);
const progress = await this.calculateBadgeProgress(player.id, badge);
const target = this.getTargetProgress(badge);
const isPlacementBadge = badge.criteria.placement !== undefined;
@@ -681,31 +680,77 @@ export function createBadgesService(pb: PocketBase) {
: progress === target;
if (progress > 0 || earned) {
await this.createBadgeProgress({
desired.set(key, {
badge: badge.id,
player: playerId,
progress: progress,
earned: earned,
player: player.id,
progress,
earned,
});
totalProgressRecords++;
if (earned) {
totalBadgesEarned++;
}
}
} catch (error: any) {
console.error(`Error processing badge "${badge.name}" for player ${playerId}:`, error.message);
// Keep whatever row already exists when a calculation fails.
failedKeys.add(key);
console.error(`Error processing badge "${badge.name}" for player ${player.id}:`, error.message);
}
}
playersProcessed++;
onProgress?.(playersProcessed, players.length);
}
// Phase 2: apply as a diff — update changed rows, create missing ones,
// delete stale/duplicate ones. No window where all badges are gone.
const existing = await pb.collection("badge_progress").getFullList({
filter: 'badge.type != "manual"',
});
const existingByKey = new Map<string, { id: string; progress: number; earned: boolean }>();
const toDelete: string[] = [];
for (const row of existing) {
const key = `${row.player}|${row.badge}`;
if (existingByKey.has(key)) {
toDelete.push(row.id);
continue;
}
existingByKey.set(key, { id: row.id, progress: row.progress, earned: row.earned });
if (!desired.has(key) && !failedKeys.has(key)) {
toDelete.push(row.id);
}
}
let created = 0;
let updated = 0;
let totalBadgesEarned = 0;
for (const [key, want] of desired) {
if (want.earned) {
totalBadgesEarned++;
}
const row = existingByKey.get(key);
if (!row) {
await this.createBadgeProgress(want);
created++;
} else if (row.progress !== want.progress || row.earned !== want.earned) {
await this.updateBadgeProgress(row.id, {
progress: want.progress,
earned: want.earned,
});
updated++;
}
}
for (const id of toDelete) {
await this.deleteBadgeProgress(id);
}
return {
success: true,
playersProcessed: uniquePlayers.size,
progressRecordsCreated: totalProgressRecords,
totalBadgesEarned: totalBadgesEarned,
averageBadgesPerPlayer: (totalBadgesEarned / uniquePlayers.size).toFixed(2),
playersProcessed: players.length,
totalBadgesEarned,
created,
updated,
deleted: toDelete.length,
averageBadgesPerPlayer: players.length
? (totalBadgesEarned / players.length).toFixed(2)
: "0",
};
},
};