diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index 9ce74736..23942ba7 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -60,6 +60,7 @@ model User { questionsAnswerCommentVotes QuestionsAnswerCommentVote[] OffersProfile OffersProfile[] offersDiscussion OffersReply[] + QuestionsList QuestionsList[] } enum Vote { @@ -369,11 +370,12 @@ model QuestionsQuestion { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - user User? @relation(fields: [userId], references: [id], onDelete: SetNull) - encounters QuestionsQuestionEncounter[] - votes QuestionsQuestionVote[] - comments QuestionsQuestionComment[] - answers QuestionsAnswer[] + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + encounters QuestionsQuestionEncounter[] + votes QuestionsQuestionVote[] + comments QuestionsQuestionComment[] + answers QuestionsAnswer[] + QuestionsListQuestion QuestionsListQuestion[] } model QuestionsQuestionEncounter { @@ -488,4 +490,30 @@ model QuestionsAnswerCommentVote { @@unique([answerCommentId, userId]) } +model QuestionsList { + id String @id @default(cuid()) + userId String + name String @db.VarChar(256) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + QuestionsListQuestion QuestionsListQuestion[] + + @@unique([userId, name]) +} + +model QuestionsListQuestionEntry { + id String @id @default(cuid()) + listId String + questionId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + list QuestionsList @relation(fields: [listId], references: [id], onDelete: Cascade) + question QuestionsQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade) + + @@unique([listId, questionId]) +} + // End of Questions project models. diff --git a/apps/portal/src/server/router/questions-list-crud.ts b/apps/portal/src/server/router/questions-list-crud.ts new file mode 100644 index 00000000..c822407f --- /dev/null +++ b/apps/portal/src/server/router/questions-list-crud.ts @@ -0,0 +1,287 @@ +import { z } from 'zod'; +import { Vote } from '@prisma/client'; +import { TRPCError } from '@trpc/server'; + +import { createProtectedRouter } from './context'; + +import type { Answer } from '~/types/questions'; + +export const questionListRouter = createProtectedRouter() + .query('getListsByUser', { + input: z.object({ + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const listsData = await ctx.prisma.questionsList.findMany({ + include: { + questions: true, + }, + orderBy: { + createdAt: 'asc', + }, + where: { + id: userId, + }, + }); + return listsData.map((data) => { + const votes: number = data.votes.reduce( + (previousValue: number, currentValue) => { + let result: number = previousValue; + + switch (currentValue.vote) { + case Vote.UPVOTE: + result += 1; + break; + case Vote.DOWNVOTE: + result -= 1; + break; + } + return result; + }, + 0, + ); + + const answer: Answer = { + content: data.content, + createdAt: data.createdAt, + id: data.id, + numComments: data._count.comments, + numVotes: votes, + user: data.user?.name ?? '', + userImage: data.user?.image ?? '', + }; + return answer; + }); + }, + }) + .query('getAnswerById', { + input: z.object({ + answerId: z.string(), + }), + async resolve({ ctx, input }) { + const answerData = await ctx.prisma.questionsAnswer.findUnique({ + include: { + _count: { + select: { + comments: true, + }, + }, + user: { + select: { + image: true, + name: true, + }, + }, + votes: true, + }, + where: { + id: input.answerId, + }, + }); + if (!answerData) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: 'Answer not found', + }); + } + const votes: number = answerData.votes.reduce( + (previousValue: number, currentValue) => { + let result: number = previousValue; + + switch (currentValue.vote) { + case Vote.UPVOTE: + result += 1; + break; + case Vote.DOWNVOTE: + result -= 1; + break; + } + return result; + }, + 0, + ); + + const answer: Answer = { + content: answerData.content, + createdAt: answerData.createdAt, + id: answerData.id, + numComments: answerData._count.comments, + numVotes: votes, + user: answerData.user?.name ?? '', + userImage: answerData.user?.image ?? '', + }; + return answer; + }, + }) + .mutation('create', { + input: z.object({ + content: z.string(), + questionId: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const { content, questionId } = input; + + return await ctx.prisma.questionsAnswer.create({ + data: { + content, + questionId, + userId, + }, + }); + }, + }) + .mutation('update', { + input: z.object({ + content: z.string().optional(), + id: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + const { content, id } = input; + + const answerToUpdate = await ctx.prisma.questionsAnswer.findUnique({ + where: { + id: input.id, + }, + }); + + if (answerToUpdate?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsAnswer.update({ + data: { + content, + }, + where: { + id, + }, + }); + }, + }) + .mutation('delete', { + input: z.object({ + id: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const answerToDelete = await ctx.prisma.questionsAnswer.findUnique({ + where: { + id: input.id, + }, + }); + + if (answerToDelete?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsAnswer.delete({ + where: { + id: input.id, + }, + }); + }, + }) + .query('getVote', { + input: z.object({ + answerId: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + const { answerId } = input; + + return await ctx.prisma.questionsAnswerVote.findUnique({ + where: { + answerId_userId: { answerId, userId }, + }, + }); + }, + }) + .mutation('createQuestionEntry', { + input: z.object({ + answerId: z.string(), + vote: z.nativeEnum(Vote), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const { answerId, vote } = input; + + return await ctx.prisma.questionsAnswerVote.create({ + data: { + answerId, + userId, + vote, + }, + }); + }, + }) + .mutation('updateQuestionEntry', { + input: z.object({ + id: z.string(), + vote: z.nativeEnum(Vote), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + const { id, vote } = input; + + const voteToUpdate = await ctx.prisma.questionsAnswerVote.findUnique({ + where: { + id: input.id, + }, + }); + + if (voteToUpdate?.userId !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsAnswerVote.update({ + data: { + vote, + }, + where: { + id, + }, + }); + }, + }) + .mutation('deleteQuestionEntry', { + input: z.object({ + id: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const voteToDelete = await ctx.prisma.questionsAnswerVote.findUnique({ + where: { + id: input.id, + }, + }); + + if (voteToDelete?.userId !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsAnswerVote.delete({ + where: { + id: input.id, + }, + }); + }, + });