From 7589e9b078f1841c96fd923f978a3d21d6cd0f99 Mon Sep 17 00:00:00 2001 From: hpkoh <53825802+hpkoh@users.noreply.github.com> Date: Tue, 25 Oct 2022 01:21:34 +0800 Subject: [PATCH] [questions][feat] add list crud (#393) Co-authored-by: Jeff Sieu --- .../migration.sql | 36 ++++ apps/portal/prisma/schema.prisma | 38 +++- .../src/server/router/questions-list-crud.ts | 199 ++++++++++++++++++ 3 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 apps/portal/prisma/migrations/20221023102619_add_list_schema/migration.sql create mode 100644 apps/portal/src/server/router/questions-list-crud.ts diff --git a/apps/portal/prisma/migrations/20221023102619_add_list_schema/migration.sql b/apps/portal/prisma/migrations/20221023102619_add_list_schema/migration.sql new file mode 100644 index 00000000..ccc20bf1 --- /dev/null +++ b/apps/portal/prisma/migrations/20221023102619_add_list_schema/migration.sql @@ -0,0 +1,36 @@ +-- CreateTable +CREATE TABLE "QuestionsList" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "name" VARCHAR(256) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "QuestionsList_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "QuestionsListQuestionEntry" ( + "id" TEXT NOT NULL, + "listId" TEXT NOT NULL, + "questionId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "QuestionsListQuestionEntry_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "QuestionsList_userId_name_key" ON "QuestionsList"("userId", "name"); + +-- CreateIndex +CREATE UNIQUE INDEX "QuestionsListQuestionEntry_listId_questionId_key" ON "QuestionsListQuestionEntry"("listId", "questionId"); + +-- AddForeignKey +ALTER TABLE "QuestionsList" ADD CONSTRAINT "QuestionsList_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuestionsListQuestionEntry" ADD CONSTRAINT "QuestionsListQuestionEntry_listId_fkey" FOREIGN KEY ("listId") REFERENCES "QuestionsList"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuestionsListQuestionEntry" ADD CONSTRAINT "QuestionsListQuestionEntry_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "QuestionsQuestion"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index c2c6938b..c3902fe0 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[] + questionsLists QuestionsList[] } enum Vote { @@ -406,11 +407,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[] + QuestionsListQuestionEntry QuestionsListQuestionEntry[] @@index([lastSeenAt, id]) @@index([upvotes, id]) @@ -532,4 +534,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) + questionEntries QuestionsListQuestionEntry[] + + @@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..1f375497 --- /dev/null +++ b/apps/portal/src/server/router/questions-list-crud.ts @@ -0,0 +1,199 @@ +import { z } from 'zod'; +import { TRPCError } from '@trpc/server'; + +import { createProtectedRouter } from './context'; + +export const questionListRouter = createProtectedRouter() + .query('getListsByUser', { + async resolve({ ctx }) { + const userId = ctx.session?.user?.id; + + return await ctx.prisma.questionsList.findMany({ + include: { + questionEntries: { + include: { + question: true, + }, + } + }, + orderBy: { + createdAt: 'asc', + }, + where: { + id: userId, + }, + }); + } + }) + .query('getListById', { + input: z.object({ + listId: z.string(), + }), + async resolve({ ctx }) { + const userId = ctx.session?.user?.id; + + return await ctx.prisma.questionsList.findMany({ + include: { + questionEntries: { + include: { + question: true, + }, + } + }, + orderBy: { + createdAt: 'asc', + }, + where: { + id: userId, + }, + }); + } + }) + .mutation('create', { + input: z.object({ + name: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const { name } = input; + + return await ctx.prisma.questionsList.create({ + data: { + name, + userId, + }, + }); + }, + }) + .mutation('update', { + input: z.object({ + id: z.string(), + name: z.string().optional(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + const { name, id } = input; + + const listToUpdate = await ctx.prisma.questionsList.findUnique({ + where: { + id: input.id, + }, + }); + + if (listToUpdate?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsList.update({ + data: { + name, + }, + where: { + id, + }, + }); + }, + }) + .mutation('delete', { + input: z.object({ + id: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const listToDelete = await ctx.prisma.questionsList.findUnique({ + where: { + id: input.id, + }, + }); + + if (listToDelete?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsList.delete({ + where: { + id: input.id, + }, + }); + }, + }) + .mutation('createQuestionEntry', { + input: z.object({ + listId: z.string(), + questionId: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const listToAugment = await ctx.prisma.questionsList.findUnique({ + where: { + id: input.listId, + }, + }); + + if (listToAugment?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + const { questionId, listId } = input; + + return await ctx.prisma.questionsListQuestionEntry.create({ + data: { + listId, + questionId, + }, + }); + }, + }) + .mutation('deleteQuestionEntry', { + input: z.object({ + id: z.string(), + }), + async resolve({ ctx, input }) { + const userId = ctx.session?.user?.id; + + const entryToDelete = await ctx.prisma.questionsListQuestionEntry.findUnique({ + where: { + id: input.id, + }, + }); + + if (entryToDelete?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + + const listToAugment = await ctx.prisma.questionsList.findUnique({ + where: { + id: entryToDelete.listId, + }, + }); + + if (listToAugment?.id !== userId) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'User have no authorization to record.', + }); + } + + return await ctx.prisma.questionsListQuestionEntry.delete({ + where: { + id: input.id, + }, + }); + }, + });