diff --git a/src/lib/awards/index.ts b/src/lib/awards/index.ts index bc7fbc2..60dbb74 100644 --- a/src/lib/awards/index.ts +++ b/src/lib/awards/index.ts @@ -160,3 +160,85 @@ export async function notifyAwardGrants( console.error("[Awards] Failed to send award-grant notifications:", err); } } + +export interface CleanupResult { + profilesChecked: number; + profilesModified: number; + duplicatesRemoved: number; + details: { profileId: number; removed: number }[]; +} + +export async function cleanupDuplicateAwards( + payload: PayloadType, +): Promise { + const result: CleanupResult = { + profilesChecked: 0, + profilesModified: 0, + duplicatesRemoved: 0, + details: [], + }; + + let page = 1; + let hasMore = true; + + while (hasMore) { + const profiles = await payload.find({ + collection: "profiles", + limit: 500, + page, + depth: 0, + overrideAccess: true, + }); + + result.profilesChecked += profiles.totalDocs; + + for (const profile of profiles.docs) { + const awards = (profile as unknown as ProgressionDoc).progression?.awards; + if (!awards || awards.length <= 1) continue; + + const seen = new Map(); + const indicesToRemove: number[] = []; + + for (let i = 0; i < awards.length; i++) { + const entry = awards[i]; + const id = awardIdOf(entry); + if (id === null) continue; + + const existing = seen.get(id); + if (existing) { + if (entry.awardedAt < existing.entry.awardedAt) { + indicesToRemove.push(existing.index); + seen.set(id, { entry, index: i }); + } else { + indicesToRemove.push(i); + } + } else { + seen.set(id, { entry, index: i }); + } + } + + if (indicesToRemove.length > 0) { + const cleaned = awards.filter((_, i) => !indicesToRemove.includes(i)); + await payload.update({ + collection: "profiles", + id: profile.id, + data: { + progression: { + ...(profile as unknown as ProgressionDoc).progression, + awards: cleaned, + }, + }, + overrideAccess: true, + }); + result.profilesModified += 1; + result.duplicatesRemoved += indicesToRemove.length; + result.details.push({ profileId: profile.id, removed: indicesToRemove.length }); + } + } + + hasMore = page < profiles.totalPages; + page += 1; + } + + return result; +} diff --git a/src/tools/cli/grant_award.ts b/src/tools/cli/grant_award.ts new file mode 100644 index 0000000..3f93e00 --- /dev/null +++ b/src/tools/cli/grant_award.ts @@ -0,0 +1,14 @@ +import { getPayload } from "payload"; +import config from "@payload-config"; + +(async () => { + const payload = await getPayload({ config }); + + // payload.logger.log("Retrieving user profiles..."); + + // await payload.find({ + // collection: "profiles", + // overrideAccess: true, + // select + // }) +})();