From b68cfc2aa4227d4a7e2ed2e657a5fd129bb96e7f Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Mon, 19 Jul 2021 19:50:11 +0300 Subject: [PATCH] Make ending plus voting a script --- package.json | 1 + pages/admin.tsx | 18 --- prisma/scripts/endPlusVoting.ts | 208 ++++++++++++++++++++++++++++++++ services/plus.ts | 191 ----------------------------- 4 files changed, 209 insertions(+), 209 deletions(-) create mode 100644 prisma/scripts/endPlusVoting.ts diff --git a/package.json b/package.json index cbae25f7b..d8d56fdc7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "migrate:reset": "prisma migrate reset", "gen": "npx prisma generate", "prebuild": "ts-node prisma/scripts/preBuild.ts", + "results": "ts-node prisma/scripts/endPlusVoting.ts", "mongo": "ts-node prisma/scripts/dataFromMongo.ts", "top500": "ts-node prisma/scripts/top500jsons.ts", "league": "cross-env NODE_OPTIONS=--max-old-space-size=8192 ts-node prisma/scripts/leagueJsons.ts", diff --git a/pages/admin.tsx b/pages/admin.tsx index 003009b47..167bcb790 100644 --- a/pages/admin.tsx +++ b/pages/admin.tsx @@ -5,7 +5,6 @@ import { useState } from "react"; import { ADMIN_DISCORD_ID } from "utils/constants"; import { getToastOptions } from "utils/objects"; import { sendData } from "utils/postData"; -import { trpc } from "utils/trpc"; const AdminPage = () => { const toast = useToast(); @@ -15,16 +14,6 @@ const AdminPage = () => { switchAccountId: "", discordId: "", }); - const endVoting = trpc.useMutation("plus.endVoting", { - onSuccess: () => console.log("success"), - onError: (e) => console.error(e.message), - }); - - const handleEndVoting = () => { - if (!window.confirm("End voting?")) return; - - endVoting.mutate(null); - }; if (!user || user.discordId !== ADMIN_DISCORD_ID) return null; @@ -60,13 +49,6 @@ const AdminPage = () => { - End voting - ); diff --git a/prisma/scripts/endPlusVoting.ts b/prisma/scripts/endPlusVoting.ts new file mode 100644 index 000000000..8c8349721 --- /dev/null +++ b/prisma/scripts/endPlusVoting.ts @@ -0,0 +1,208 @@ +import { Prisma } from "@prisma/client"; +import { getPercentageFromCounts } from "../../utils/plus"; +import { VOUCH_CRITERIA } from "../../utils/constants"; +import prisma from "../client"; + +// Include Prisma's .env file as well, so we can fetch the DATABASE_URL +require("dotenv").config({ path: "prisma/.env" }); + +const main = async () => { + const [ballots, statuses, suggestions] = await Promise.all([ + prisma.plusBallot.findMany({ + where: { isStale: false }, + include: { voterUser: { include: { plusStatus: true } } }, + }), + prisma.plusStatus.findMany({}), + prisma.plusSuggestion.findMany({}), + ]); + + const month = new Date().getMonth() + 1; + const year = new Date().getFullYear(); + + const summariesByTier = [ + new Map(), + new Map(), + new Map(), + new Map(), + ]; + + for (const ballot of ballots) { + if (!summariesByTier[ballot.tier].has(ballot.votedId)) { + summariesByTier[ballot.tier].set(ballot.votedId, { + month, + tier: ballot.tier, + userId: ballot.votedId, + wasSuggested: suggestions.some( + (suggestion) => + suggestion.tier === ballot.tier && + suggestion.suggestedId === ballot.votedId + ), + wasVouched: statuses.some( + (status) => + status.vouchTier === ballot.tier && status.userId === ballot.votedId + ), + year, + countsEU: [0, 0, 0, 0], + countsNA: [0, 0, 0, 0], + }); + } + + const summary = summariesByTier[ballot.tier].get(ballot.votedId); + if (!summary) throw Error("unexpected no summary"); + + const isNA = ballot.voterUser.plusStatus!.region === "NA"; + + const scoreToIndex = { "-2": 0, "-1": 1, "1": 2, "2": 3 } as const; + const arrToChange = (isNA ? summary.countsNA : summary.countsEU) as [ + number, + number, + number, + number + ]; + const key = ("" + ballot.score) as keyof typeof scoreToIndex; + arrToChange[scoreToIndex[key]]++; + } + + const members: [number[], number[], number[], number[]] = [[], [], [], []]; + const canVouch: [null, number[], number[], number[]] = [null, [], [], []]; + const vouchRevoked: number[] = []; + + const alreadyMember = new Set(); + + for (const [i, summaries] of [ + Array.from(summariesByTier[1].values()), + Array.from(summariesByTier[2].values()), + Array.from(summariesByTier[3].values()), + ].entries()) { + const tier = i + 1; + + for (const summary of summaries) { + const plusStatus = statuses.find( + (status) => status.userId === summary.userId + ); + if (!plusStatus) { + throw Error("unexpected no plusStatus in loop"); + } + + const passedVoting = + getPercentageFromCounts( + summary.countsNA as number[], + summary.countsEU as number[], + plusStatus.region + ) >= 50; + + if (passedVoting) { + if (!alreadyMember.has(summary.userId)) { + members[tier].push(summary.userId); + alreadyMember.add(summary.userId); + } + } else { + // get put to a lower tier only if not suggestion or vouch + if ( + !summary.wasSuggested && + !summary.wasVouched && + !alreadyMember.has(summary.userId) + ) { + const tierToDemoteTo = tier === 3 ? 0 : tier + 1; + members[tierToDemoteTo].push(summary.userId); + alreadyMember.add(summary.userId); + } + + // if they were vouched time for whoever vouched them to have their perms revoked for 6 months + if (plusStatus.vouchTier === tier) { + if (!plusStatus.voucherId) { + throw Error("unexpexted no voucher id"); + } + vouchRevoked.push(plusStatus.voucherId); + } + + continue; + } + + const gotVouchPermits = + getPercentageFromCounts( + summary.countsNA as number[], + summary.countsEU as number[], + plusStatus.region + ) >= VOUCH_CRITERIA[tier]; + + if (gotVouchPermits) { + canVouch[tier]!.push(summary.userId); + } + } + } + + console.log("deleting stale plus ballots and setting can vouch for null"); + + await Promise.all([ + prisma.plusBallot.deleteMany({ where: { isStale: true } }), + prisma.plusStatus.updateMany({ + data: { canVouchFor: null }, + }), + ]); + + const now = new Date(); + + console.log("doing a lot of stuff"); + + await prisma.$transaction([ + prisma.plusBallot.updateMany({ data: { isStale: true } }), + prisma.plusSuggestion.deleteMany({}), + prisma.plusVotingSummary.createMany({ + data: [ + ...Array.from(summariesByTier[1].values()), + ...Array.from(summariesByTier[2].values()), + ...Array.from(summariesByTier[3].values()), + ], + }), + prisma.plusStatus.updateMany({ + data: { vouchTier: null, voucherId: null }, + }), + prisma.plusStatus.updateMany({ + where: { canVouchAgainAfter: { lt: new Date() } }, + data: { canVouchAgainAfter: null }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: members[0] } }, + data: { membershipTier: null }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: members[1] } }, + data: { membershipTier: 1 }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: members[2] } }, + data: { membershipTier: 2 }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: members[3] } }, + data: { membershipTier: 3 }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: canVouch[1] } }, + data: { canVouchFor: 1 }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: canVouch[2] } }, + data: { canVouchFor: 2 }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: canVouch[3] } }, + data: { canVouchFor: 3 }, + }), + prisma.plusStatus.updateMany({ + where: { userId: { in: vouchRevoked } }, + data: { + canVouchAgainAfter: new Date(now.getFullYear(), now.getMonth() + 5, 1), + }, + }), + ]); + + console.log("done with a lot of stuff"); +}; + +main() + .catch((e) => console.error(e)) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/services/plus.ts b/services/plus.ts index 8d9dd9f19..d22542a8e 100644 --- a/services/plus.ts +++ b/services/plus.ts @@ -2,7 +2,6 @@ import { PlusRegion, Prisma } from "@prisma/client"; import { httpError } from "@trpc/server"; import prisma from "prisma/client"; import { shuffleArray } from "utils/arrays"; -import { ADMIN_ID, VOUCH_CRITERIA } from "utils/constants"; import { getPercentageFromCounts, getVotingRange } from "utils/plus"; import { userBasicSelection } from "utils/prisma"; import { suggestionFullSchema } from "utils/validators/suggestion"; @@ -603,195 +602,6 @@ const editVote = async ({ }); }; -const endVoting = async (userId: number) => { - if (userId !== ADMIN_ID) throw httpError.badRequest("not admin"); - - const [ballots, statuses, suggestions] = await Promise.all([ - prisma.plusBallot.findMany({ - where: { isStale: false }, - include: { voterUser: { include: { plusStatus: true } } }, - }), - prisma.plusStatus.findMany({}), - prisma.plusSuggestion.findMany({}), - ]); - - const month = new Date().getMonth() + 1; - const year = new Date().getFullYear(); - - const summariesByTier = [ - new Map(), - new Map(), - new Map(), - new Map(), - ]; - - for (const ballot of ballots) { - if (!summariesByTier[ballot.tier].has(ballot.votedId)) { - summariesByTier[ballot.tier].set(ballot.votedId, { - month, - tier: ballot.tier, - userId: ballot.votedId, - wasSuggested: suggestions.some( - (suggestion) => - suggestion.tier === ballot.tier && - suggestion.suggestedId === ballot.votedId - ), - wasVouched: statuses.some( - (status) => - status.vouchTier === ballot.tier && status.userId === ballot.votedId - ), - year, - countsEU: [0, 0, 0, 0], - countsNA: [0, 0, 0, 0], - }); - } - - const summary = summariesByTier[ballot.tier].get(ballot.votedId); - if (!summary) throw httpError.badRequest("unexpected no summary"); - - const isNA = ballot.voterUser.plusStatus!.region === "NA"; - - const scoreToIndex = { "-2": 0, "-1": 1, "1": 2, "2": 3 } as const; - const arrToChange = (isNA ? summary.countsNA : summary.countsEU) as [ - number, - number, - number, - number - ]; - const key = ("" + ballot.score) as keyof typeof scoreToIndex; - arrToChange[scoreToIndex[key]]++; - } - - const members: [number[], number[], number[], number[]] = [[], [], [], []]; - const canVouch: [null, number[], number[], number[]] = [null, [], [], []]; - const vouchRevoked: number[] = []; - - const alreadyMember = new Set(); - - for (const [i, summaries] of [ - Array.from(summariesByTier[1].values()), - Array.from(summariesByTier[2].values()), - Array.from(summariesByTier[3].values()), - ].entries()) { - const tier = i + 1; - - for (const summary of summaries) { - const plusStatus = statuses.find( - (status) => status.userId === summary.userId - ); - if (!plusStatus) - throw httpError.badRequest("unexpected no plusStatus in loop"); - - const passedVoting = - getPercentageFromCounts( - summary.countsNA as number[], - summary.countsEU as number[], - plusStatus.region - ) >= 50; - - if (passedVoting) { - if (!alreadyMember.has(summary.userId)) { - members[tier].push(summary.userId); - alreadyMember.add(summary.userId); - } - } else { - // get put to a lower tier only if not suggestion or vouch - if ( - !summary.wasSuggested && - !summary.wasVouched && - !alreadyMember.has(summary.userId) - ) { - const tierToDemoteTo = tier === 3 ? 0 : tier + 1; - members[tierToDemoteTo].push(summary.userId); - alreadyMember.add(summary.userId); - } - - // if they were vouched time for whoever vouched them to have their perms revoked for 6 months - if (plusStatus.vouchTier === tier) { - if (!plusStatus.voucherId) - throw httpError.badRequest("unexpexted no voucher id"); - vouchRevoked.push(plusStatus.voucherId); - } - - continue; - } - - const gotVouchPermits = - getPercentageFromCounts( - summary.countsNA as number[], - summary.countsEU as number[], - plusStatus.region - ) >= VOUCH_CRITERIA[tier]; - - if (gotVouchPermits) { - canVouch[tier]!.push(summary.userId); - } - } - } - - await Promise.all([ - prisma.plusBallot.deleteMany({ where: { isStale: true } }), - prisma.plusStatus.updateMany({ - data: { canVouchFor: null }, - }), - ]); - - const now = new Date(); - - return prisma.$transaction([ - prisma.plusBallot.updateMany({ data: { isStale: true } }), - prisma.plusSuggestion.deleteMany({}), - prisma.plusVotingSummary.createMany({ - data: [ - ...Array.from(summariesByTier[1].values()), - ...Array.from(summariesByTier[2].values()), - ...Array.from(summariesByTier[3].values()), - ], - }), - prisma.plusStatus.updateMany({ - data: { vouchTier: null, voucherId: null }, - }), - prisma.plusStatus.updateMany({ - where: { canVouchAgainAfter: { lt: new Date() } }, - data: { canVouchAgainAfter: null }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: members[0] } }, - data: { membershipTier: null }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: members[1] } }, - data: { membershipTier: 1 }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: members[2] } }, - data: { membershipTier: 2 }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: members[3] } }, - data: { membershipTier: 3 }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: canVouch[1] } }, - data: { canVouchFor: 1 }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: canVouch[2] } }, - data: { canVouchFor: 2 }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: canVouch[3] } }, - data: { canVouchFor: 3 }, - }), - prisma.plusStatus.updateMany({ - where: { userId: { in: vouchRevoked } }, - data: { - canVouchAgainAfter: new Date(now.getFullYear(), now.getMonth() + 5, 1), - }, - }), - ]); -}; - const plusService = { getPlusStatuses, getSuggestions, @@ -805,7 +615,6 @@ const plusService = { addVotes, editVote, votedUserScores, - endVoting, }; export default plusService;