Skip to content

Commit 874d911

Browse files
authored
feat(api): add endpoint to get votes (#416)
* feat(api): add endpoint to get votes * feat(openapi-types): generate new openapi types * refactor(api): remove unnecessary comment * refactor(api): remove code diplication in questions
1 parent a3a05cd commit 874d911

File tree

4 files changed

+127
-54
lines changed

4 files changed

+127
-54
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Prisma } from "@prisma/client";
2+
import { GetQuestionsQuery } from "./questions.schemas";
3+
4+
export const getQuestionsPrismaParams = (
5+
{ category, level, status = "accepted", limit, offset, order, orderBy }: GetQuestionsQuery,
6+
userRole: string | undefined,
7+
) => {
8+
const levels = level?.split(",");
9+
10+
return {
11+
where: {
12+
...(category && { categoryId: category }),
13+
...(levels && { levelId: { in: levels } }),
14+
...(status && userRole === "admin" ? { statusId: status } : { statusId: "accepted" }),
15+
},
16+
take: limit,
17+
skip: offset,
18+
...(order &&
19+
orderBy && {
20+
orderBy: {
21+
...(orderBy === "votesCount"
22+
? {
23+
QuestionVote: {
24+
_count: order,
25+
},
26+
}
27+
: {
28+
[orderBy]: order,
29+
}),
30+
},
31+
}),
32+
} satisfies Prisma.QuestionFindManyArgs;
33+
};

apps/api/modules/questions/questions.routes.ts

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
generateGetQuestionByIdSchema,
1111
upvoteQuestionSchema,
1212
downvoteQuestionSchema,
13+
generateGetQuestionsVotesSchema,
1314
} from "./questions.schemas.js";
15+
import { getQuestionsPrismaParams } from "./questions.params.js";
1416

1517
const questionsPlugin: FastifyPluginAsync = async (fastify) => {
1618
await fastify.register(import("./questions.utils.js"));
@@ -31,40 +33,14 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
3133
method: "GET",
3234
schema: generateGetQuestionsSchema(args),
3335
async handler(request, reply) {
34-
const { category, level, status = "accepted", limit, offset, order, orderBy } = request.query;
35-
const levels = level?.split(",");
36-
37-
const where = {
38-
...(category && { categoryId: category }),
39-
...(levels && { levelId: { in: levels } }),
40-
...(status && request.session.data?._user._roleId === "admin"
41-
? { statusId: status }
42-
: { statusId: "accepted" }),
43-
};
36+
const params = getQuestionsPrismaParams(request.query, request.session.data?._user._roleId);
4437

45-
// @todo also get votes
4638
const [total, questions] = await Promise.all([
4739
fastify.db.question.count({
48-
where,
40+
where: params.where,
4941
}),
5042
fastify.db.question.findMany({
51-
where,
52-
take: limit,
53-
skip: offset,
54-
...(order &&
55-
orderBy && {
56-
orderBy: {
57-
...(orderBy === "votesCount"
58-
? {
59-
QuestionVote: {
60-
_count: order,
61-
},
62-
}
63-
: {
64-
[orderBy]: order,
65-
}),
66-
},
67-
}),
43+
...params,
6844
select: {
6945
id: true,
7046
question: true,
@@ -77,11 +53,6 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
7753
QuestionVote: true,
7854
},
7955
},
80-
QuestionVote: {
81-
where: {
82-
userId: request.session.data?._user.id || 0,
83-
},
84-
},
8556
},
8657
}),
8758
]);
@@ -95,7 +66,6 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
9566
_statusId: q.statusId,
9667
acceptedAt: q.acceptedAt?.toISOString(),
9768
votesCount: q._count.QuestionVote,
98-
currentUserVotedOn: q.QuestionVote.length > 0,
9969
};
10070
});
10171

@@ -126,14 +96,47 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
12696
_levelId: newQuestion.levelId,
12797
_statusId: newQuestion.statusId,
12898
acceptedAt: newQuestion.acceptedAt?.toISOString(),
129-
currentUserVotedOn: false,
13099
votesCount: 0,
131100
};
132101

133102
return { data };
134103
},
135104
});
136105

106+
fastify.withTypeProvider<TypeBoxTypeProvider>().route({
107+
url: "/questions/votes",
108+
method: "GET",
109+
schema: generateGetQuestionsVotesSchema(args),
110+
async handler(request, reply) {
111+
const params = getQuestionsPrismaParams(request.query, request.session.data?._user._roleId);
112+
113+
const questions = await fastify.db.question.findMany({
114+
...params,
115+
select: {
116+
id: true,
117+
_count: {
118+
select: {
119+
QuestionVote: true,
120+
},
121+
},
122+
QuestionVote: {
123+
where: {
124+
userId: request.session.data?._user.id || 0,
125+
},
126+
},
127+
},
128+
});
129+
130+
const data = questions.map((q) => ({
131+
id: q.id,
132+
votesCount: q._count.QuestionVote,
133+
currentUserVotedOn: q.QuestionVote.length > 0,
134+
}));
135+
136+
return { data };
137+
},
138+
});
139+
137140
fastify.withTypeProvider<TypeBoxTypeProvider>().route({
138141
url: "/questions/:id",
139142
method: "PATCH",
@@ -163,11 +166,6 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
163166
QuestionVote: true,
164167
},
165168
},
166-
QuestionVote: {
167-
where: {
168-
userId: request.session.data?._user.id || 0,
169-
},
170-
},
171169
},
172170
});
173171

@@ -179,7 +177,6 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
179177
_statusId: q.statusId,
180178
acceptedAt: q.acceptedAt?.toISOString(),
181179
votesCount: q._count.QuestionVote,
182-
currentUserVotedOn: q.QuestionVote.length > 0,
183180
};
184181

185182
return { data };
@@ -210,11 +207,6 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
210207
QuestionVote: true,
211208
},
212209
},
213-
QuestionVote: {
214-
where: {
215-
userId: request.session.data?._user.id || 0,
216-
},
217-
},
218210
},
219211
});
220212

@@ -230,7 +222,6 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
230222
_statusId: q.statusId,
231223
acceptedAt: q.acceptedAt?.toISOString(),
232224
votesCount: q._count.QuestionVote,
233-
currentUserVotedOn: q.QuestionVote.length > 0,
234225
};
235226

236227
return { data };

apps/api/modules/questions/questions.schemas.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ const generateQuestionResponseSchema = <
7171
Type.Object({
7272
...generateQuestionShape(args),
7373
votesCount: Type.Integer(),
74-
currentUserVotedOn: Type.Boolean(),
7574
});
7675

7776
export const generateGetQuestionsSchema = <
@@ -115,6 +114,31 @@ export const generatePostQuestionsSchema = <
115114
} as const;
116115
};
117116

117+
export const generateGetQuestionsVotesSchema = <
118+
Categories extends readonly string[],
119+
Levels extends readonly string[],
120+
Statuses extends readonly string[],
121+
>(args: {
122+
categories: Categories;
123+
levels: Levels;
124+
statuses: Statuses;
125+
}) => {
126+
return {
127+
querystring: generateGetQuestionsQuerySchema(args),
128+
response: {
129+
200: Type.Object({
130+
data: Type.Array(
131+
Type.Object({
132+
id: Type.Integer(),
133+
votesCount: Type.Integer(),
134+
currentUserVotedOn: Type.Boolean(),
135+
}),
136+
),
137+
}),
138+
},
139+
};
140+
};
141+
118142
export const generatePatchQuestionByIdSchema = <
119143
Categories extends readonly string[],
120144
Levels extends readonly string[],

packages/openapi-types/types.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ export interface paths {
9797
/** Format: date-time */
9898
acceptedAt?: string;
9999
votesCount: number;
100-
currentUserVotedOn: boolean;
101100
}[];
102101
meta: {
103102
total: number;
@@ -131,14 +130,42 @@ export interface paths {
131130
/** Format: date-time */
132131
acceptedAt?: string;
133132
votesCount: number;
134-
currentUserVotedOn: boolean;
135133
};
136134
};
137135
};
138136
};
139137
};
140138
};
141139
};
140+
"/questions/votes": {
141+
get: {
142+
parameters?: {
143+
query?: {
144+
category?: "html" | "css" | "js" | "angular" | "react" | "git" | "other";
145+
status?: "pending" | "accepted";
146+
level?: string;
147+
limit?: number;
148+
offset?: number;
149+
orderBy?: "acceptedAt" | "level" | "votesCount";
150+
order?: "asc" | "desc";
151+
};
152+
};
153+
responses: {
154+
/** @description Default Response */
155+
200: {
156+
content: {
157+
"application/json": {
158+
data: {
159+
id: number;
160+
votesCount: number;
161+
currentUserVotedOn: boolean;
162+
}[];
163+
};
164+
};
165+
};
166+
};
167+
};
168+
};
142169
"/questions/{id}": {
143170
get: {
144171
parameters: {
@@ -160,7 +187,6 @@ export interface paths {
160187
/** Format: date-time */
161188
acceptedAt?: string;
162189
votesCount: number;
163-
currentUserVotedOn: boolean;
164190
};
165191
};
166192
};
@@ -208,7 +234,6 @@ export interface paths {
208234
/** Format: date-time */
209235
acceptedAt?: string;
210236
votesCount: number;
211-
currentUserVotedOn: boolean;
212237
};
213238
};
214239
};
@@ -270,7 +295,7 @@ export interface paths {
270295
export type webhooks = Record<string, never>;
271296

272297
export interface components {
273-
schemas: never;
298+
schemas: {};
274299
responses: never;
275300
parameters: never;
276301
requestBodies: never;

0 commit comments

Comments
 (0)