mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 04:33:42 +08:00
[questions][feat] add list crud (#393)
Co-authored-by: Jeff Sieu <jeffsy00@gmail.com>
This commit is contained in:
@ -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;
|
@ -60,6 +60,7 @@ model User {
|
|||||||
questionsAnswerCommentVotes QuestionsAnswerCommentVote[]
|
questionsAnswerCommentVotes QuestionsAnswerCommentVote[]
|
||||||
OffersProfile OffersProfile[]
|
OffersProfile OffersProfile[]
|
||||||
offersDiscussion OffersReply[]
|
offersDiscussion OffersReply[]
|
||||||
|
questionsLists QuestionsList[]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Vote {
|
enum Vote {
|
||||||
@ -406,11 +407,12 @@ model QuestionsQuestion {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||||
encounters QuestionsQuestionEncounter[]
|
encounters QuestionsQuestionEncounter[]
|
||||||
votes QuestionsQuestionVote[]
|
votes QuestionsQuestionVote[]
|
||||||
comments QuestionsQuestionComment[]
|
comments QuestionsQuestionComment[]
|
||||||
answers QuestionsAnswer[]
|
answers QuestionsAnswer[]
|
||||||
|
QuestionsListQuestionEntry QuestionsListQuestionEntry[]
|
||||||
|
|
||||||
@@index([lastSeenAt, id])
|
@@index([lastSeenAt, id])
|
||||||
@@index([upvotes, id])
|
@@index([upvotes, id])
|
||||||
@ -532,4 +534,30 @@ model QuestionsAnswerCommentVote {
|
|||||||
@@unique([answerCommentId, userId])
|
@@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.
|
// End of Questions project models.
|
||||||
|
199
apps/portal/src/server/router/questions-list-crud.ts
Normal file
199
apps/portal/src/server/router/questions-list-crud.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
Reference in New Issue
Block a user