mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 04:33:42 +08:00
[questions][feat] update question filter (#384)
* [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 Co-authored-by: Jeff Sieu <jeffsy00@gmail.com>
This commit is contained in:
@ -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;
|
@ -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";
|
@ -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");
|
@ -404,6 +404,8 @@ model QuestionsQuestion {
|
|||||||
userId String?
|
userId String?
|
||||||
content String @db.Text
|
content String @db.Text
|
||||||
questionType QuestionsQuestionType
|
questionType QuestionsQuestionType
|
||||||
|
lastSeenAt DateTime
|
||||||
|
upvotes Int @default(0)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@ -412,6 +414,9 @@ model QuestionsQuestion {
|
|||||||
votes QuestionsQuestionVote[]
|
votes QuestionsQuestionVote[]
|
||||||
comments QuestionsQuestionComment[]
|
comments QuestionsQuestionComment[]
|
||||||
answers QuestionsAnswer[]
|
answers QuestionsAnswer[]
|
||||||
|
|
||||||
|
@@index([lastSeenAt, id])
|
||||||
|
@@index([upvotes, id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model QuestionsQuestionEncounter {
|
model QuestionsQuestionEncounter {
|
||||||
|
@ -26,6 +26,8 @@ import {
|
|||||||
} from '~/utils/questions/useSearchFilter';
|
} from '~/utils/questions/useSearchFilter';
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
import { SortOrder, SortType } from '~/types/questions.d';
|
||||||
|
|
||||||
export default function QuestionsHomePage() {
|
export default function QuestionsHomePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -70,6 +72,9 @@ export default function QuestionsHomePage() {
|
|||||||
locations: selectedLocations,
|
locations: selectedLocations,
|
||||||
questionTypes: selectedQuestionTypes,
|
questionTypes: selectedQuestionTypes,
|
||||||
roles: [],
|
roles: [],
|
||||||
|
// TODO: Implement sort order and sort type choices
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
sortType: SortType.NEW,
|
||||||
startDate,
|
startDate,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -5,18 +5,33 @@ import { TRPCError } from '@trpc/server';
|
|||||||
import { createProtectedRouter } from './context';
|
import { createProtectedRouter } from './context';
|
||||||
|
|
||||||
import type { Question } from '~/types/questions';
|
import type { Question } from '~/types/questions';
|
||||||
|
import { SortOrder, SortType } from '~/types/questions.d';
|
||||||
|
|
||||||
|
const TWO_WEEK_IN_MS = 12096e5;
|
||||||
|
|
||||||
export const questionsQuestionRouter = createProtectedRouter()
|
export const questionsQuestionRouter = createProtectedRouter()
|
||||||
.query('getQuestionsByFilter', {
|
.query('getQuestionsByFilter', {
|
||||||
input: z.object({
|
input: z.object({
|
||||||
companyNames: z.string().array(),
|
companyNames: z.string().array(),
|
||||||
endDate: z.date(),
|
endDate: z.date().default(new Date()),
|
||||||
locations: z.string().array(),
|
locations: z.string().array(),
|
||||||
|
pageSize: z.number().default(50),
|
||||||
questionTypes: z.nativeEnum(QuestionsQuestionType).array(),
|
questionTypes: z.nativeEnum(QuestionsQuestionType).array(),
|
||||||
roles: z.string().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 }) {
|
async resolve({ ctx, input }) {
|
||||||
|
const sortCondition =
|
||||||
|
input.sortType === SortType.TOP
|
||||||
|
? {
|
||||||
|
upvotes: input.sortOrder,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
lastSeenAt: input.sortOrder,
|
||||||
|
};
|
||||||
|
|
||||||
const questionsData = await ctx.prisma.questionsQuestion.findMany({
|
const questionsData = await ctx.prisma.questionsQuestion.findMany({
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
@ -41,7 +56,7 @@ export const questionsQuestionRouter = createProtectedRouter()
|
|||||||
votes: true,
|
votes: true,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
...sortCondition,
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
...(input.questionTypes.length > 0
|
...(input.questionTypes.length > 0
|
||||||
@ -53,6 +68,10 @@ export const questionsQuestionRouter = createProtectedRouter()
|
|||||||
: {}),
|
: {}),
|
||||||
encounters: {
|
encounters: {
|
||||||
some: {
|
some: {
|
||||||
|
seenAt: {
|
||||||
|
gte: input.startDate,
|
||||||
|
lte: input.endDate,
|
||||||
|
},
|
||||||
...(input.companyNames.length > 0
|
...(input.companyNames.length > 0
|
||||||
? {
|
? {
|
||||||
company: {
|
company: {
|
||||||
@ -204,24 +223,23 @@ export const questionsQuestionRouter = createProtectedRouter()
|
|||||||
data: {
|
data: {
|
||||||
content: input.content,
|
content: input.content,
|
||||||
encounters: {
|
encounters: {
|
||||||
create: [
|
create: {
|
||||||
{
|
company: {
|
||||||
company: {
|
connect: {
|
||||||
connect: {
|
id: input.companyId,
|
||||||
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,
|
questionType: input.questionType,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
@ -316,13 +334,28 @@ export const questionsQuestionRouter = createProtectedRouter()
|
|||||||
const userId = ctx.session?.user?.id;
|
const userId = ctx.session?.user?.id;
|
||||||
const { questionId, vote } = input;
|
const { questionId, vote } = input;
|
||||||
|
|
||||||
return await ctx.prisma.questionsQuestionVote.create({
|
const incrementValue = vote === Vote.UPVOTE ? 1 : -1;
|
||||||
data: {
|
|
||||||
questionId,
|
const [questionVote] = await ctx.prisma.$transaction([
|
||||||
userId,
|
ctx.prisma.questionsQuestionVote.create({
|
||||||
vote,
|
data: {
|
||||||
},
|
questionId,
|
||||||
});
|
userId,
|
||||||
|
vote,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ctx.prisma.questionsQuestion.update({
|
||||||
|
data: {
|
||||||
|
upvotes: {
|
||||||
|
increment: incrementValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: questionId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
return questionVote;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.mutation('updateVote', {
|
.mutation('updateVote', {
|
||||||
@ -347,14 +380,30 @@ export const questionsQuestionRouter = createProtectedRouter()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await ctx.prisma.questionsQuestionVote.update({
|
const incrementValue = vote === Vote.UPVOTE ? 2 : -2;
|
||||||
data: {
|
|
||||||
vote,
|
const [questionVote] = await ctx.prisma.$transaction([
|
||||||
},
|
ctx.prisma.questionsQuestionVote.update({
|
||||||
where: {
|
data: {
|
||||||
id,
|
vote,
|
||||||
},
|
},
|
||||||
});
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ctx.prisma.questionsQuestion.update({
|
||||||
|
data: {
|
||||||
|
upvotes: {
|
||||||
|
increment: incrementValue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: voteToUpdate.questionId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return questionVote;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.mutation('deleteVote', {
|
.mutation('deleteVote', {
|
||||||
@ -377,10 +426,25 @@ export const questionsQuestionRouter = createProtectedRouter()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await ctx.prisma.questionsQuestionVote.delete({
|
const incrementValue = voteToDelete.vote === Vote.UPVOTE ? -1 : 1;
|
||||||
where: {
|
|
||||||
id: input.id,
|
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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
12
apps/portal/src/types/questions.d.ts
vendored
12
apps/portal/src/types/questions.d.ts
vendored
@ -20,7 +20,7 @@ export type AggregatedQuestionEncounter = {
|
|||||||
companyCounts: Record<string, number>;
|
companyCounts: Record<string, number>;
|
||||||
locationCounts: Record<string, number>;
|
locationCounts: Record<string, number>;
|
||||||
roleCounts: Record<string, number>;
|
roleCounts: Record<string, number>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type AnswerComment = {
|
export type AnswerComment = {
|
||||||
content: string;
|
content: string;
|
||||||
@ -50,3 +50,13 @@ export type QuestionComment = {
|
|||||||
user: string;
|
user: string;
|
||||||
userImage: string;
|
userImage: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum SortOrder {
|
||||||
|
ASC = 'asc',
|
||||||
|
DESC = 'desc',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SortType {
|
||||||
|
TOP,
|
||||||
|
NEW,
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user