mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
import type { ActionFunction } from "react-router";
|
|
import { requireUser } from "~/features/auth/core/user.server";
|
|
import type { PlusVoteFromFE } from "~/features/plus-voting/core";
|
|
import {
|
|
nextNonCompletedVoting,
|
|
rangeToMonthYear,
|
|
} from "~/features/plus-voting/core";
|
|
import { isVotingActive } from "~/features/plus-voting/core/voting-time";
|
|
import * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
|
|
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
|
import invariant from "~/utils/invariant";
|
|
import { badRequestIfFalsy, parseRequestPayload } from "~/utils/remix.server";
|
|
import { PLUS_UPVOTE } from "../plus-voting-constants";
|
|
import { votingActionSchema } from "../plus-voting-schemas";
|
|
|
|
export const action: ActionFunction = async ({ request }) => {
|
|
const user = requireUser();
|
|
const data = await parseRequestPayload({
|
|
request,
|
|
schema: votingActionSchema,
|
|
});
|
|
|
|
if (!isVotingActive()) {
|
|
throw new Response(null, { status: 400 });
|
|
}
|
|
|
|
invariant(user.plusTier, "User should have plusTier");
|
|
|
|
const usersForVoting = await PlusVotingRepository.usersForVoting({
|
|
id: user.id,
|
|
plusTier: user.plusTier,
|
|
});
|
|
|
|
// this should not be needed but makes the voting a bit more resilient
|
|
// if there is a bug that causes some user to show up twice, or some user to show up who should not be included
|
|
const seen = new Set<number>();
|
|
const filteredVotes = data.votes.filter((vote) => {
|
|
if (seen.has(vote.votedId)) {
|
|
return false;
|
|
}
|
|
seen.add(vote.votedId);
|
|
return usersForVoting.some((u) => u.user.id === vote.votedId);
|
|
});
|
|
|
|
validateVotes({ votes: filteredVotes, usersForVoting });
|
|
|
|
// freebie +1 for yourself if you vote
|
|
const votesForDb = [...filteredVotes].concat({
|
|
votedId: user.id,
|
|
score: PLUS_UPVOTE,
|
|
});
|
|
|
|
const votingRange = badRequestIfFalsy(nextNonCompletedVoting(new Date()));
|
|
const { month, year } = rangeToMonthYear(votingRange);
|
|
await PlusVotingRepository.upsertMany(
|
|
votesForDb.map((vote) => ({
|
|
...vote,
|
|
authorId: user.id,
|
|
month,
|
|
year,
|
|
tier: user.plusTier!, // no clue why i couldn't make narrowing the type down above work
|
|
validAfter: dateToDatabaseTimestamp(votingRange.endDate),
|
|
})),
|
|
);
|
|
|
|
return null;
|
|
};
|
|
|
|
function validateVotes({
|
|
votes,
|
|
usersForVoting,
|
|
}: {
|
|
votes: PlusVoteFromFE[];
|
|
usersForVoting?: PlusVotingRepository.UsersForVoting;
|
|
}) {
|
|
if (!usersForVoting) throw new Response(null, { status: 400 });
|
|
|
|
// converting it to set also handles the check for duplicate ids
|
|
const votedUserIds = new Set(votes.map((v) => v.votedId));
|
|
|
|
if (votedUserIds.size !== usersForVoting.length) {
|
|
throw new Response(null, { status: 400 });
|
|
}
|
|
|
|
for (const { user } of usersForVoting) {
|
|
if (!votedUserIds.has(user.id)) {
|
|
throw new Response(null, { status: 400 });
|
|
}
|
|
}
|
|
}
|