Merge branch 'main' into weilin/question-list

pull/468/head
Jeff Sieu 3 years ago
commit a505e81cc0

@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "QuestionsQuestion" ADD COLUMN "contentSearch" TSVECTOR
GENERATED ALWAYS AS
to_tsvector('english', coalesce(content, ''))
STORED;
-- CreateIndex
CREATE INDEX "QuestionsQuestion_contentSearch_idx" ON "QuestionsQuestion" USING GIN("textSearch");

@ -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[]
OffersProfile OffersProfile[]
offersDiscussion OffersReply[]
questionsLists QuestionsList[]
}
enum Vote {
@ -406,12 +407,16 @@ 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[]
contentSearch Unsupported("TSVECTOR")?
@@index([contentSearch])
@@index([lastSeenAt, id])
@@index([upvotes, id])
}
@ -532,4 +537,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.

@ -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,
},
});
},
});

@ -316,6 +316,31 @@ export const questionsQuestionRouter = createProtectedRouter()
return question;
},
})
.query('getRelatedQuestionsByContent', {
input: z.object({
content: z.string(),
}),
async resolve({ ctx, input }) {
const escapeChars = /[()|&:*!]/g;
const query =
input.content
.replace(escapeChars, " ")
.trim()
.split(/\s+/)
.join(" | ");
const relatedQuestions = await ctx.prisma.$queryRaw`
SELECT * FROM "QuestionsQuestion"
WHERE
"contentSearch" @@ to_tsquery('english', ${query})
ORDER BY ts_rank("textSearch", to_tsquery('english', ${query})) DESC
`;
return relatedQuestions;
}
})
.mutation('create', {
input: z.object({
companyId: z.string(),
@ -537,7 +562,7 @@ export const questionsQuestionRouter = createProtectedRouter()
const incrementValue = voteToDelete.vote === Vote.UPVOTE ? -1 : 1;
const [questionVote] = await ctx.prisma.$transaction([
const [ questionVote ] = await ctx.prisma.$transaction([
ctx.prisma.questionsQuestionVote.delete({
where: {
id: input.id,

Loading…
Cancel
Save