mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-11-12 22:29:53 -06:00
Keep the tournament link of badge wins when a badge's owners are edited
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import * as R from "remeda";
|
||||
import { beforeEach, describe, expect, test } from "vitest";
|
||||
import * as BadgeFactory from "~/db/seed/factories/BadgeFactory";
|
||||
import * as TournamentFactory from "~/db/seed/factories/TournamentFactory";
|
||||
import * as UserFactory from "~/db/seed/factories/UserFactory";
|
||||
import * as XRankPlacementFactory from "~/db/seed/factories/XRankPlacementFactory";
|
||||
import * as FeedRepository from "~/features/feed/FeedRepository.server";
|
||||
import * as BadgeRepository from "./BadgeRepository.server";
|
||||
import { SPLATOON_3_XP_BADGE_VALUES } from "./badges-constants";
|
||||
|
||||
@@ -73,8 +76,76 @@ describe("replaceOwners", () => {
|
||||
const updated = await BadgeRepository.findById(badge.id);
|
||||
expect(updated?.owners).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe("with a badge won at a tournament", () => {
|
||||
const users = UserFactory.pool();
|
||||
const winnerId = () => users.id(1);
|
||||
const loserId = () => users.id(2);
|
||||
const addedByManagerId = () => users.id(3);
|
||||
let badgeId: number;
|
||||
|
||||
beforeEach(async () => {
|
||||
await users.create(3);
|
||||
badgeId = (await BadgeFactory.create()).id;
|
||||
await TournamentFactory.createPlayed(
|
||||
{ authorId: winnerId(), minMembersPerTeam: 1, badges: [badgeId] },
|
||||
{ teamRosters: [[winnerId()], [loserId()]], playedOut: "all" },
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps the win tied to the tournament when another owner is added", async () => {
|
||||
await BadgeRepository.replaceOwners({
|
||||
badgeId,
|
||||
ownerIds: [winnerId(), addedByManagerId()],
|
||||
});
|
||||
|
||||
expect(await tournamentWinnerIds()).toEqual([winnerId()]);
|
||||
expect(await ownerCounts(badgeId)).toEqual([
|
||||
{ id: winnerId(), count: 1 },
|
||||
{ id: addedByManagerId(), count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("grants extra copies to a winner on top of their tournament win", async () => {
|
||||
await BadgeRepository.replaceOwners({
|
||||
badgeId,
|
||||
ownerIds: [winnerId(), winnerId(), winnerId()],
|
||||
});
|
||||
|
||||
expect(await tournamentWinnerIds()).toEqual([winnerId()]);
|
||||
expect(await ownerCounts(badgeId)).toEqual([
|
||||
{ id: winnerId(), count: 3 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("removes the win when the winner is removed from the owners", async () => {
|
||||
await BadgeRepository.replaceOwners({
|
||||
badgeId,
|
||||
ownerIds: [addedByManagerId()],
|
||||
});
|
||||
|
||||
expect(await tournamentWinnerIds()).toEqual([]);
|
||||
expect(await ownerCounts(badgeId)).toEqual([
|
||||
{ id: addedByManagerId(), count: 1 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function tournamentWinnerIds() {
|
||||
const owners =
|
||||
await FeedRepository.findAllTournamentBadgeOwnersStartedAfter(0);
|
||||
return owners.map((owner) => owner.userId);
|
||||
}
|
||||
|
||||
async function ownerCounts(badgeId: number) {
|
||||
const badge = await BadgeRepository.findById(badgeId);
|
||||
return R.sortBy(
|
||||
badge!.owners.map(({ id, count }) => ({ id, count })),
|
||||
(owner) => owner.id,
|
||||
);
|
||||
}
|
||||
|
||||
/** Gives the user a linked X Rank player whose one placement is worth `power`. */
|
||||
const givePeakXp = (userId: number, power: number) =>
|
||||
XRankPlacementFactory.create(
|
||||
|
||||
@@ -196,6 +196,12 @@ export function replaceManagers({
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who owns the badge and how many times, one `ownerIds` entry per copy. Copies won at a
|
||||
* tournament keep their tournament link: a user's tournament wins count first towards their new
|
||||
* total and the rest is a manual grant. Lowering a total below the user's wins removes the most
|
||||
* recent ones.
|
||||
*/
|
||||
export function replaceOwners({
|
||||
badgeId,
|
||||
ownerIds,
|
||||
@@ -204,26 +210,58 @@ export function replaceOwners({
|
||||
ownerIds: number[];
|
||||
}) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
await trx
|
||||
.deleteFrom("TournamentBadgeOwner")
|
||||
const newCounts = new Map<number, number>();
|
||||
for (const userId of ownerIds) {
|
||||
newCounts.set(userId, (newCounts.get(userId) ?? 0) + 1);
|
||||
}
|
||||
|
||||
const tournamentWins = await trx
|
||||
.selectFrom("TournamentBadgeOwner")
|
||||
.select(["userId", "tournamentId"])
|
||||
.where("badgeId", "=", badgeId)
|
||||
.where("tournamentId", "is not", null)
|
||||
.$narrowType<{ tournamentId: NotNull }>()
|
||||
.orderBy("tournamentId", "asc")
|
||||
.execute();
|
||||
|
||||
const counts = new Map<number, number>();
|
||||
for (const userId of ownerIds) {
|
||||
counts.set(userId, (counts.get(userId) ?? 0) + 1);
|
||||
const keptWinCounts = new Map<number, number>();
|
||||
const removedWins: typeof tournamentWins = [];
|
||||
for (const win of tournamentWins) {
|
||||
const keptCount = keptWinCounts.get(win.userId) ?? 0;
|
||||
if (keptCount < (newCounts.get(win.userId) ?? 0)) {
|
||||
keptWinCounts.set(win.userId, keptCount + 1);
|
||||
} else {
|
||||
removedWins.push(win);
|
||||
}
|
||||
}
|
||||
|
||||
await trx
|
||||
.insertInto("TournamentBadgeOwner")
|
||||
.values(
|
||||
Array.from(counts, ([userId, count]) => ({
|
||||
badgeId,
|
||||
userId,
|
||||
count,
|
||||
})),
|
||||
)
|
||||
.deleteFrom("TournamentBadgeOwner")
|
||||
.where("badgeId", "=", badgeId)
|
||||
.where("tournamentId", "is", null)
|
||||
.execute();
|
||||
|
||||
for (const win of removedWins) {
|
||||
await trx
|
||||
.deleteFrom("TournamentBadgeOwner")
|
||||
.where("badgeId", "=", badgeId)
|
||||
.where("userId", "=", win.userId)
|
||||
.where("tournamentId", "=", win.tournamentId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
const manualGrants = Array.from(newCounts, ([userId, count]) => ({
|
||||
badgeId,
|
||||
userId,
|
||||
count: count - (keptWinCounts.get(userId) ?? 0),
|
||||
})).filter((grant) => grant.count > 0);
|
||||
|
||||
if (manualGrants.length > 0) {
|
||||
await trx
|
||||
.insertInto("TournamentBadgeOwner")
|
||||
.values(manualGrants)
|
||||
.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user