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(); 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 }); } } }