From 352f8a03ad5923797ffc601263158ca31c07d2c3 Mon Sep 17 00:00:00 2001 From: hpkoh <53825802+hpkoh@users.noreply.github.com> Date: Wed, 26 Oct 2022 15:38:28 +0800 Subject: [PATCH] [questions][feat] add encounters transaction for crud (#409) * [questions][chore] refactor question queries * [questions][chore] destructure values from input * [questions][feat] add sorting * [question][fix] fix frontend * [questions][feat] add sorting * [questions][feat] add sorting index * [questions][chore] push migration file * [questions][fix] fix ci issues * [questions][fix] fix import errors * [questions][feat] add encounters transaction for crud * [questions][chore] fix import * [questions][chore] update error handling * [questions][feat] parallelize queries * [questions][fix] update to use corrcet client * Update questions-question-encounter-router.ts * Update questions-question-encounter-router.ts Co-authored-by: Jeff Sieu --- .../migration.sql | 2 + .../migration.sql | 8 ++ .../migrations/20221025014050_/migration.sql | 8 ++ apps/portal/prisma/schema.prisma | 5 +- .../questions-question-encounter-router.ts | 130 +++++++++++++++--- 5 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 apps/portal/prisma/migrations/20221023100925_update_last_seen_val_to_be_optional/migration.sql create mode 100644 apps/portal/prisma/migrations/20221025013857_add_full_text_search/migration.sql create mode 100644 apps/portal/prisma/migrations/20221025014050_/migration.sql diff --git a/apps/portal/prisma/migrations/20221023100925_update_last_seen_val_to_be_optional/migration.sql b/apps/portal/prisma/migrations/20221023100925_update_last_seen_val_to_be_optional/migration.sql new file mode 100644 index 00000000..e4c76fc9 --- /dev/null +++ b/apps/portal/prisma/migrations/20221023100925_update_last_seen_val_to_be_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "QuestionsQuestion" ALTER COLUMN "lastSeenAt" DROP NOT NULL; diff --git a/apps/portal/prisma/migrations/20221025013857_add_full_text_search/migration.sql b/apps/portal/prisma/migrations/20221025013857_add_full_text_search/migration.sql new file mode 100644 index 00000000..8f58d7f9 --- /dev/null +++ b/apps/portal/prisma/migrations/20221025013857_add_full_text_search/migration.sql @@ -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("contentSearch"); diff --git a/apps/portal/prisma/migrations/20221025014050_/migration.sql b/apps/portal/prisma/migrations/20221025014050_/migration.sql new file mode 100644 index 00000000..6d3a3407 --- /dev/null +++ b/apps/portal/prisma/migrations/20221025014050_/migration.sql @@ -0,0 +1,8 @@ +-- DropIndex +DROP INDEX "QuestionsQuestion_contentSearch_idx"; + +-- AlterTable +ALTER TABLE "QuestionsQuestion" ALTER COLUMN "contentSearch" DROP DEFAULT; + +-- CreateIndex +CREATE INDEX "QuestionsQuestion_contentSearch_idx" ON "QuestionsQuestion"("contentSearch"); diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index c3902fe0..bf57a55d 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -1,7 +1,8 @@ // Refer to the Prisma schema docs: https://pris.ly/d/prisma-schema generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" + previewFeatures = ["interactiveTransactions"] } datasource db { @@ -402,7 +403,7 @@ model QuestionsQuestion { userId String? content String @db.Text questionType QuestionsQuestionType - lastSeenAt DateTime + lastSeenAt DateTime? upvotes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/portal/src/server/router/questions-question-encounter-router.ts b/apps/portal/src/server/router/questions-question-encounter-router.ts index 8fa4a0e4..6198d866 100644 --- a/apps/portal/src/server/router/questions-question-encounter-router.ts +++ b/apps/portal/src/server/router/questions-question-encounter-router.ts @@ -4,6 +4,8 @@ import { TRPCError } from '@trpc/server'; import { createProtectedRouter } from './context'; import type { AggregatedQuestionEncounter } from '~/types/questions'; +import { SortOrder } from '~/types/questions.d'; + export const questionsQuestionEncounterRouter = createProtectedRouter() .query('getAggregatedEncounters', { @@ -68,11 +70,40 @@ export const questionsQuestionEncounterRouter = createProtectedRouter() async resolve({ ctx, input }) { const userId = ctx.session?.user?.id; - return await ctx.prisma.questionsQuestionEncounter.create({ - data: { - ...input, - userId, - }, + return await ctx.prisma.$transaction(async (tx) => { + const [questionToUpdate, questionEncounterCreated] = await Promise.all([ + tx.questionsQuestion.findUnique({ + where: { + id: input.questionId, + }, + }), + tx.questionsQuestionEncounter.create({ + data: { + ...input, + userId, + }, + }) + ]); + + + if (questionToUpdate === null) { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'Question does not exist', + }); + } + + if (!questionToUpdate.lastSeenAt || questionToUpdate.lastSeenAt < input.seenAt) { + await tx.questionsQuestion.update({ + data: { + lastSeenAt : input.seenAt, + }, + where: { + id: input.questionId, + }, + }); + } + return questionEncounterCreated; }); }, }) @@ -101,14 +132,48 @@ export const questionsQuestionEncounterRouter = createProtectedRouter() }); } - return await ctx.prisma.questionsQuestionEncounter.update({ - data: { - ...input, - }, - where: { - id: input.id, - }, + return await ctx.prisma.$transaction(async (tx) => { + const [questionToUpdate, questionEncounterUpdated] = await Promise.all([ + tx.questionsQuestion.findUnique({ + where: { + id: questionEncounterToUpdate.questionId, + }, + }), + tx.questionsQuestionEncounter.update({ + data: { + ...input, + }, + where: { + id: input.id, + }, + }) + ]); + + + if (questionToUpdate!.lastSeenAt === questionEncounterToUpdate.seenAt) { + const latestEncounter = await ctx.prisma.questionsQuestionEncounter.findFirst({ + orderBy: { + seenAt: SortOrder.DESC, + }, + where: { + questionId: questionToUpdate!.id, + }, + }); + + await tx.questionsQuestion.update({ + data: { + lastSeenAt : latestEncounter!.seenAt, + }, + where: { + id: questionToUpdate!.id, + }, + }); + } + + + return questionEncounterUpdated; }); + }, }) .mutation('delete', { @@ -132,10 +197,43 @@ export const questionsQuestionEncounterRouter = createProtectedRouter() }); } - return await ctx.prisma.questionsQuestionEncounter.delete({ - where: { - id: input.id, - }, + return await ctx.prisma.$transaction(async (tx) => { + const [questionToUpdate, questionEncounterDeleted] = await Promise.all([ + tx.questionsQuestion.findUnique({ + where: { + id: questionEncounterToDelete.questionId, + }, + }), + tx.questionsQuestionEncounter.delete({ + where: { + id: input.id, + }, + }) + ]); + + if (questionToUpdate!.lastSeenAt === questionEncounterToDelete.seenAt) { + const latestEncounter = await ctx.prisma.questionsQuestionEncounter.findFirst({ + orderBy: { + seenAt: SortOrder.DESC, + }, + where: { + questionId: questionToUpdate!.id, + }, + }); + + const lastSeenVal = latestEncounter ? latestEncounter!.seenAt : null; + + await tx.questionsQuestion.update({ + data: { + lastSeenAt : lastSeenVal, + }, + where: { + id: questionToUpdate!.id, + }, + }); + } + + return questionEncounterDeleted; }); }, });