diff --git a/lib/api.ts b/lib/api.ts index 271d7f9ef..11f925503 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -2,6 +2,6 @@ import { User } from "@prisma/client"; import { NextApiRequest } from "next"; import { getSession } from "next-auth/client"; -export const getMySession = (req?: NextApiRequest): Promise => +export const getMySession = (req: NextApiRequest): Promise => // @ts-expect-error getSession({ req }); diff --git a/lib/errors.ts b/lib/errors.ts new file mode 100644 index 000000000..9569aa039 --- /dev/null +++ b/lib/errors.ts @@ -0,0 +1,6 @@ +export class UserError extends Error { + constructor(message: string) { + super(message); + this.name = "UserError"; + } +} diff --git a/lib/validators/suggestion.ts b/lib/validators/suggestion.ts index d4cc17659..b94f00f03 100644 --- a/lib/validators/suggestion.ts +++ b/lib/validators/suggestion.ts @@ -4,7 +4,7 @@ export const SUGGESTION_DESCRIPTION_LIMIT = 500; export const suggestionSchema = z.object({ description: z.string().max(SUGGESTION_DESCRIPTION_LIMIT), - suggestedUserId: z.number().int(), + suggestedId: z.number().int(), tier: z.number().int().min(1).max(3), region: z.enum(["NA", "EU"]), }); diff --git a/pages/api/plus/suggestions.ts b/pages/api/plus/suggestions.ts new file mode 100644 index 000000000..b9683db2d --- /dev/null +++ b/pages/api/plus/suggestions.ts @@ -0,0 +1,42 @@ +import { getMySession } from "lib/api"; +import { UserError } from "lib/errors"; +import { NextApiRequest, NextApiResponse } from "next"; +import plusService from "services/plus"; +import { ZodError } from "zod"; + +const suggestionsHandler = async ( + req: NextApiRequest, + res: NextApiResponse +) => { + const user = await getMySession(req); + + switch (req.method) { + case "POST": + await postHandler(req, res); + break; + default: + res.status(405).end(); + } + + async function postHandler(req: NextApiRequest, res: NextApiResponse) { + if (!user) return res.status(401).end(); + + try { + await plusService.addSuggestion({ data: req.body, userId: user.id }); + } catch (e) { + if (e instanceof ZodError) { + res.status(400).json({ message: e.message }); + } else if (e instanceof UserError) { + res.status(400).json({ message: e.message }); + } else { + res.status(500).end(); + } + + return; + } + + res.status(200).end(); + } +}; + +export default suggestionsHandler; diff --git a/services/plus.ts b/services/plus.ts index cae3f9694..67d5bd046 100644 --- a/services/plus.ts +++ b/services/plus.ts @@ -1,6 +1,8 @@ import { Prisma } from "@prisma/client"; +import { UserError } from "lib/errors"; import { getPercentageFromCounts } from "lib/plus"; import { userBasicSelection } from "lib/prisma"; +import { suggestionSchema } from "lib/validators/suggestion"; import prisma from "prisma/client"; export type VotingSummariesByMonthAndTier = Prisma.PromiseReturnType< @@ -102,25 +104,44 @@ const addSuggestion = async ({ data, userId, }: { - data: Prisma.PlusSuggestionUncheckedCreateInput; + data: unknown; userId: number; }) => { - const existingSuggestion = await prisma.plusSuggestion.findUnique({ - where: { tier_suggestedId_suggesterId: data }, - }); + const parsedData = { ...suggestionSchema.parse(data), suggesterId: userId }; + const [suggestions, plusStatus] = await Promise.all([ + prisma.plusSuggestion.findMany({}), + prisma.plusStatus.findUnique({ where: { userId } }), + ]); + const existingSuggestion = suggestions.find( + ({ tier, suggestedId }) => + tier === parsedData.tier && suggestedId === parsedData.suggestedId + ); // every user can only send one new suggestion per month if (!existingSuggestion) { - const usersSuggestion = await prisma.plusSuggestion.findFirst({ - where: { isResuggestion: false, suggesterId: userId }, - }); + const usersSuggestion = suggestions.find( + ({ isResuggestion, suggesterId }) => + isResuggestion === false && suggesterId === userId + ); if (usersSuggestion) { - throw Error("Already made a new suggestion"); + throw new UserError("already made a new suggestion"); } } + if ( + !plusStatus || + !plusStatus.membershipTier || + plusStatus.membershipTier > parsedData.tier + ) { + throw new UserError( + "not a member of high enough tier to suggest for this tier" + ); + } + + // TODO voting has started + return prisma.plusSuggestion.create({ - data: { ...data, isResuggestion: !!existingSuggestion }, + data: { ...parsedData, isResuggestion: !!existingSuggestion }, }); };