diff --git a/apps/portal/prisma/migrations/20221021150358_add_vote_count_and_last_seen/migration.sql b/apps/portal/prisma/migrations/20221021150358_add_vote_count_and_last_seen/migration.sql new file mode 100644 index 00000000..a6319a17 --- /dev/null +++ b/apps/portal/prisma/migrations/20221021150358_add_vote_count_and_last_seen/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Added the required column `upvotes` to the `QuestionsQuestion` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "QuestionsQuestion" ADD COLUMN "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "upvotes" INTEGER NOT NULL; + +-- AlterTable +ALTER TABLE "QuestionsQuestionEncounter" ADD COLUMN "netVotes" INTEGER NOT NULL DEFAULT 0; diff --git a/apps/portal/prisma/migrations/20221021151424_delete_extra_encounter_fields/migration.sql b/apps/portal/prisma/migrations/20221021151424_delete_extra_encounter_fields/migration.sql new file mode 100644 index 00000000..ef9e4229 --- /dev/null +++ b/apps/portal/prisma/migrations/20221021151424_delete_extra_encounter_fields/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `netVotes` on the `QuestionsQuestionEncounter` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "QuestionsQuestion" ALTER COLUMN "lastSeenAt" DROP DEFAULT, +ALTER COLUMN "upvotes" SET DEFAULT 0; + +-- AlterTable +ALTER TABLE "QuestionsQuestionEncounter" DROP COLUMN "netVotes"; diff --git a/apps/portal/prisma/migrations/20221021155717_add_sorting_index/migration.sql b/apps/portal/prisma/migrations/20221021155717_add_sorting_index/migration.sql new file mode 100644 index 00000000..6ae3366a --- /dev/null +++ b/apps/portal/prisma/migrations/20221021155717_add_sorting_index/migration.sql @@ -0,0 +1,5 @@ +-- CreateIndex +CREATE INDEX "QuestionsQuestion_lastSeenAt_id_idx" ON "QuestionsQuestion"("lastSeenAt", "id"); + +-- CreateIndex +CREATE INDEX "QuestionsQuestion_upvotes_id_idx" ON "QuestionsQuestion"("upvotes", "id"); diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma index 67a4f6d3..fb263f80 100644 --- a/apps/portal/prisma/schema.prisma +++ b/apps/portal/prisma/schema.prisma @@ -404,6 +404,8 @@ model QuestionsQuestion { userId String? content String @db.Text questionType QuestionsQuestionType + lastSeenAt DateTime + upvotes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -412,6 +414,9 @@ model QuestionsQuestion { votes QuestionsQuestionVote[] comments QuestionsQuestionComment[] answers QuestionsAnswer[] + + @@index([lastSeenAt, id]) + @@index([upvotes, id]) } model QuestionsQuestionEncounter { diff --git a/apps/portal/src/pages/questions/index.tsx b/apps/portal/src/pages/questions/index.tsx index 69caac9f..feaffa80 100644 --- a/apps/portal/src/pages/questions/index.tsx +++ b/apps/portal/src/pages/questions/index.tsx @@ -26,6 +26,8 @@ import { } from '~/utils/questions/useSearchFilter'; import { trpc } from '~/utils/trpc'; +import { SortOrder, SortType } from '~/types/questions.d'; + export default function QuestionsHomePage() { const router = useRouter(); @@ -70,6 +72,9 @@ export default function QuestionsHomePage() { locations: selectedLocations, questionTypes: selectedQuestionTypes, roles: [], + // TODO: Implement sort order and sort type choices + sortOrder: SortOrder.DESC, + sortType: SortType.NEW, startDate, }, ], diff --git a/apps/portal/src/server/router/questions-question-router.ts b/apps/portal/src/server/router/questions-question-router.ts index 75fb7d17..52c8017c 100644 --- a/apps/portal/src/server/router/questions-question-router.ts +++ b/apps/portal/src/server/router/questions-question-router.ts @@ -5,18 +5,33 @@ import { TRPCError } from '@trpc/server'; import { createProtectedRouter } from './context'; import type { Question } from '~/types/questions'; +import { SortOrder, SortType } from '~/types/questions.d'; + +const TWO_WEEK_IN_MS = 12096e5; export const questionsQuestionRouter = createProtectedRouter() .query('getQuestionsByFilter', { input: z.object({ companyNames: z.string().array(), - endDate: z.date(), + endDate: z.date().default(new Date()), locations: z.string().array(), + pageSize: z.number().default(50), questionTypes: z.nativeEnum(QuestionsQuestionType).array(), roles: z.string().array(), - startDate: z.date().optional(), + sortOrder: z.nativeEnum(SortOrder), + sortType: z.nativeEnum(SortType), + startDate: z.date().default(new Date(Date.now() - TWO_WEEK_IN_MS)), }), async resolve({ ctx, input }) { + const sortCondition = + input.sortType === SortType.TOP + ? { + upvotes: input.sortOrder, + } + : { + lastSeenAt: input.sortOrder, + }; + const questionsData = await ctx.prisma.questionsQuestion.findMany({ include: { _count: { @@ -41,7 +56,7 @@ export const questionsQuestionRouter = createProtectedRouter() votes: true, }, orderBy: { - createdAt: 'desc', + ...sortCondition, }, where: { ...(input.questionTypes.length > 0 @@ -53,6 +68,10 @@ export const questionsQuestionRouter = createProtectedRouter() : {}), encounters: { some: { + seenAt: { + gte: input.startDate, + lte: input.endDate, + }, ...(input.companyNames.length > 0 ? { company: { @@ -204,24 +223,23 @@ export const questionsQuestionRouter = createProtectedRouter() data: { content: input.content, encounters: { - create: [ - { - company: { - connect: { - id: input.companyId, - }, + create: { + company: { + connect: { + id: input.companyId, }, - location: input.location, - role: input.role, - seenAt: input.seenAt, - user: { - connect: { - id: userId, - }, + }, + location: input.location, + role: input.role, + seenAt: input.seenAt, + user: { + connect: { + id: userId, }, }, - ], + }, }, + lastSeenAt: input.seenAt, questionType: input.questionType, userId, }, @@ -316,13 +334,28 @@ export const questionsQuestionRouter = createProtectedRouter() const userId = ctx.session?.user?.id; const { questionId, vote } = input; - return await ctx.prisma.questionsQuestionVote.create({ - data: { - questionId, - userId, - vote, - }, - }); + const incrementValue = vote === Vote.UPVOTE ? 1 : -1; + + const [questionVote] = await ctx.prisma.$transaction([ + ctx.prisma.questionsQuestionVote.create({ + data: { + questionId, + userId, + vote, + }, + }), + ctx.prisma.questionsQuestion.update({ + data: { + upvotes: { + increment: incrementValue, + }, + }, + where: { + id: questionId, + }, + }), + ]); + return questionVote; }, }) .mutation('updateVote', { @@ -347,14 +380,30 @@ export const questionsQuestionRouter = createProtectedRouter() }); } - return await ctx.prisma.questionsQuestionVote.update({ - data: { - vote, - }, - where: { - id, - }, - }); + const incrementValue = vote === Vote.UPVOTE ? 2 : -2; + + const [questionVote] = await ctx.prisma.$transaction([ + ctx.prisma.questionsQuestionVote.update({ + data: { + vote, + }, + where: { + id, + }, + }), + ctx.prisma.questionsQuestion.update({ + data: { + upvotes: { + increment: incrementValue, + }, + }, + where: { + id: voteToUpdate.questionId, + }, + }), + ]); + + return questionVote; }, }) .mutation('deleteVote', { @@ -377,10 +426,25 @@ export const questionsQuestionRouter = createProtectedRouter() }); } - return await ctx.prisma.questionsQuestionVote.delete({ - where: { - id: input.id, - }, - }); + const incrementValue = voteToDelete.vote === Vote.UPVOTE ? -1 : 1; + + const [questionVote] = await ctx.prisma.$transaction([ + ctx.prisma.questionsQuestionVote.delete({ + where: { + id: input.id, + }, + }), + ctx.prisma.questionsQuestion.update({ + data: { + upvotes: { + increment: incrementValue, + }, + }, + where: { + id: voteToDelete.questionId, + }, + }), + ]); + return questionVote; }, }); diff --git a/apps/portal/src/types/questions.d.ts b/apps/portal/src/types/questions.d.ts index 521f3b8b..d75d82de 100644 --- a/apps/portal/src/types/questions.d.ts +++ b/apps/portal/src/types/questions.d.ts @@ -20,7 +20,7 @@ export type AggregatedQuestionEncounter = { companyCounts: Record; locationCounts: Record; roleCounts: Record; -} +}; export type AnswerComment = { content: string; @@ -50,3 +50,13 @@ export type QuestionComment = { user: string; userImage: string; }; + +export enum SortOrder { + ASC = 'asc', + DESC = 'desc', +} + +export enum SortType { + TOP, + NEW, +}