badges improvements
This commit is contained in:
@@ -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",
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user