Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d876523
Merge remote-tracking branch 'origin/main' into develop
typeofweb Jan 5, 2023
13bfdbf
fix(app): change logout trigger (#486)
grzegorzpokorski Jan 5, 2023
08ca431
feat(api): add question answers likes and dislikes (#468)
xStrixU Jan 6, 2023
7778dea
fix: voting on single questions
typeofweb Jan 6, 2023
2a09b97
fix: position right section
typeofweb Jan 6, 2023
ceb0fab
fix(app): fix mobile actions z-index (#490)
AdiPol1359 Jan 6, 2023
f04d7b3
fix: make voting button more visible
typeofweb Jan 6, 2023
4bc8dd6
fix(app): fix colors (#493)
grzegorzpokorski Jan 8, 2023
a0ec16d
chore: update contributors (#492)
AdiPol1359 Jan 8, 2023
3660d0c
feat(app): add answer editor to question answer (#491)
AdiPol1359 Jan 11, 2023
081bc8f
feat(app): add focus trap to main nav and QuestionsSidebar; add small…
grzegorzpokorski Jan 11, 2023
97a5e87
feat(app): close modal on press Escape key (#497)
grzegorzpokorski Jan 12, 2023
a0db81e
feat: add editing own questions (#496)
xStrixU Jan 12, 2023
9e453b6
feat(app): add user context menu (#495)
grzegorzpokorski Jan 12, 2023
48deec8
fix(app): fix styles in admin and user panel (#501)
grzegorzpokorski Jan 13, 2023
2c8b610
feat(app): new hook 'useOnKeydown'; implement new hook in 'BaseModal'…
grzegorzpokorski Jan 13, 2023
9d7ae9e
feat: add ability to sort questions by update / edit date
grzegorzpokorski Jan 22, 2023
51ee25c
feat: add ability to sort questions in user and admin dashboard
grzegorzpokorski Jan 23, 2023
41778f1
Fix: 'CloseButton' a11y improvements (#505)
grzegorzpokorski Jan 23, 2023
57c9b9f
Update apps/app/src/hooks/useGetAllQuestions.ts
grzegorzpokorski Jan 23, 2023
e708de7
Merge branch 'develop' into 507-issue
grzegorzpokorski Jan 23, 2023
b3b68ca
add "SelectLabel" component
grzegorzpokorski Jan 23, 2023
764b5c6
add "SortBySelect" component
grzegorzpokorski Jan 23, 2023
89c1b67
implement newly created components
grzegorzpokorski Jan 23, 2023
a18b490
rename sortByLabels
grzegorzpokorski Jan 23, 2023
3ef42b0
Fix 'sortByLabels' labels
grzegorzpokorski Jan 23, 2023
7c98b84
feat(app): add ability to edit question on questions page and single …
grzegorzpokorski Jan 24, 2023
dc52c62
Merge branch 'develop' into 507-issue
grzegorzpokorski Jan 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@
"login": "AdiPol1359",
"name": "Adrian Polak",
"avatar_url": "https://avatars.githubusercontent.com/u/27779154?v=4",
"profile": "https://projectcode.pl/",
"profile": "https://github.com/AdiPol1359",
"contributions": [
"code"
]
},
{
"login": "xStrixU",
"name": "xStrixU",
"name": "Kacper Polak",
"avatar_url": "https://avatars.githubusercontent.com/u/41890821?v=4",
"profile": "https://github.com/xStrixU",
"contributions": [
Expand Down
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
"titleBar.inactiveBackground": "#401886",
"titleBar.activeForeground": "#ffffff",
"titleBar.inactiveForeground": "#ffffff"
}
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
},
"prettier.configPath": "prettier.config.js"
}
6 changes: 5 additions & 1 deletion apps/api/modules/answers/answers.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ export const dbAnswerToDto = ({
content,
sources,
createdAt,
QuestionAnswerVote,
CreatedBy: { socialLogin, ...createdBy },
}: Prisma.QuestionAnswerGetPayload<{ select: typeof answerSelect }>) => {
_count,
}: Prisma.QuestionAnswerGetPayload<{ select: ReturnType<typeof answerSelect> }>) => {
return {
id,
content,
sources,
createdAt: createdAt.toISOString(),
votesCount: _count.QuestionAnswerVote,
currentUserVotedOn: QuestionAnswerVote.length > 0,
createdBy: {
socialLogin: socialLogin as Record<string, string | number>,
...createdBy,
Expand Down
124 changes: 111 additions & 13 deletions apps/api/modules/answers/answers.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,30 @@ import {
createAnswerSchema,
deleteAnswerSchema,
updateAnswerSchema,
upvoteAnswerSchema,
} from "./answers.schemas.js";

export const answerSelect = {
id: true,
content: true,
sources: true,
createdAt: true,
CreatedBy: {
select: { id: true, firstName: true, lastName: true, socialLogin: true },
},
} satisfies Prisma.QuestionAnswerSelect;
export const answerSelect = (userId: number) => {
return {
id: true,
content: true,
sources: true,
createdAt: true,
CreatedBy: {
select: { id: true, firstName: true, lastName: true, socialLogin: true },
},
_count: {
select: {
QuestionAnswerVote: true,
},
},
QuestionAnswerVote: {
where: {
userId: userId,
},
},
} satisfies Prisma.QuestionAnswerSelect;
};

const answersPlugin: FastifyPluginAsync = async (fastify) => {
const checkAnswerUserHook: preHandlerAsyncHookHandler = async (request) => {
Expand Down Expand Up @@ -53,15 +66,16 @@ const answersPlugin: FastifyPluginAsync = async (fastify) => {
async handler(request) {
const {
params: { id },
session: { data: sessionData },
} = request;

const answers = await fastify.db.questionAnswer.findMany({
where: { questionId: id },
select: answerSelect,
select: answerSelect(request.session.data?._user.id || 0),
});

return {
data: answers.map(dbAnswerToDto),
data: answers.map((answer) => dbAnswerToDto(answer)),
};
},
});
Expand All @@ -84,7 +98,7 @@ const answersPlugin: FastifyPluginAsync = async (fastify) => {
try {
const answer = await fastify.db.questionAnswer.create({
data: { questionId: id, createdById: sessionData._user.id, content, sources },
select: answerSelect,
select: answerSelect(request.session.data?._user.id || 0),
});

return { data: dbAnswerToDto(answer) };
Expand All @@ -109,12 +123,13 @@ const answersPlugin: FastifyPluginAsync = async (fastify) => {
const {
params: { id },
body: { content, sources },
session: { data: sessionData },
} = request;

const answer = await fastify.db.questionAnswer.update({
where: { id },
data: { content, sources },
select: answerSelect,
select: answerSelect(request.session.data?._user.id || 0),
});

return { data: dbAnswerToDto(answer) };
Expand All @@ -138,6 +153,89 @@ const answersPlugin: FastifyPluginAsync = async (fastify) => {
return reply.status(204).send();
},
});

fastify.withTypeProvider<TypeBoxTypeProvider>().route({
url: "/answers/:id/votes",
method: "POST",
schema: upvoteAnswerSchema,
async handler(request, reply) {
const {
params: { id },
session: { data: sessionData },
} = request;

if (!sessionData) {
throw fastify.httpErrors.unauthorized();
}

try {
const questionAnswerVote = await fastify.db.questionAnswerVote.upsert({
where: {
userId_questionAnswerId: {
userId: sessionData._user.id,
questionAnswerId: id,
},
},
create: {
userId: sessionData._user.id,
questionAnswerId: id,
},
update: {
userId: sessionData._user.id,
questionAnswerId: id,
},
});

return {
data: {
userId: sessionData._user.id,
answerId: id,
},
};
} catch (err) {
if (isPrismaError(err) && PrismaErrorCode.ForeignKeyViolation) {
throw fastify.httpErrors.notFound(`Answer vote with id: ${id} not found!`);
}

throw err;
}
},
});

fastify.withTypeProvider<TypeBoxTypeProvider>().route({
url: "/answers/:id/votes",
method: "DELETE",
schema: deleteAnswerSchema,
async handler(request, reply) {
const {
params: { id },
session: { data: sessionData },
} = request;

if (!sessionData) {
throw fastify.httpErrors.unauthorized();
}

const questionAnswer = await fastify.db.questionAnswer.findFirst({
where: {
id,
},
});

if (!questionAnswer) {
throw fastify.httpErrors.notFound(`Answer vote with id: ${id} not found!`);
}

await fastify.db.questionAnswerVote.deleteMany({
where: {
userId: sessionData._user.id,
questionAnswerId: id,
},
});

return reply.status(204).send();
},
});
};

export default answersPlugin;
25 changes: 25 additions & 0 deletions apps/api/modules/answers/answers.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const answerSchema = Type.Object({
content: Type.String(),
sources: Type.Array(Type.String()),
createdAt: Type.String({ format: "date-time" }),
votesCount: Type.Integer(),
currentUserVotedOn: Type.Boolean(),
createdBy: Type.Object({
id: Type.Integer(),
firstName: Type.Union([Type.String(), Type.Null()]),
Expand Down Expand Up @@ -64,3 +66,26 @@ export const deleteAnswerSchema = {
204: Type.Never(),
},
};

export const upvoteAnswerSchema = {
params: Type.Object({
id: Type.Integer(),
}),
response: {
200: Type.Object({
data: Type.Object({
userId: Type.Integer(),
answerId: Type.Integer(),
}),
}),
},
};

export const downvoteAnswerSchema = {
params: Type.Object({
id: Type.Integer(),
}),
response: {
204: Type.Never(),
},
};
12 changes: 11 additions & 1 deletion apps/api/modules/questions/questions.params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import { kv } from "../../utils.js";
import { GetQuestionsQuery } from "./questions.schemas.js";

export const getQuestionsPrismaParams = (
{ category, level, status = "accepted", limit, offset, order, orderBy }: GetQuestionsQuery,
{
category,
level,
status = "accepted",
limit,
offset,
order,
orderBy,
userId,
}: GetQuestionsQuery,
userRole: string | undefined,
) => {
const levels = level?.split(",");
Expand All @@ -13,6 +22,7 @@ export const getQuestionsPrismaParams = (
...(category && { categoryId: category }),
...(levels && { levelId: { in: levels } }),
...(status && userRole === "admin" ? { statusId: status } : { statusId: "accepted" }),
...(userId && { createdById: userId }),
},
take: limit,
skip: offset,
Expand Down
2 changes: 2 additions & 0 deletions apps/api/modules/questions/questions.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
levelId: true,
statusId: true,
acceptedAt: true,
updatedAt: true,
_count: {
select: {
QuestionVote: true,
Expand All @@ -66,6 +67,7 @@ const questionsPlugin: FastifyPluginAsync = async (fastify) => {
_levelId: q.levelId,
_statusId: q.statusId,
acceptedAt: q.acceptedAt?.toISOString(),
updatedAt: q.updatedAt?.toISOString(),
votesCount: q._count.QuestionVote,
};
});
Expand Down
3 changes: 3 additions & 0 deletions apps/api/modules/questions/questions.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ const generateGetQuestionsQuerySchema = <
Type.Literal("acceptedAt"),
Type.Literal("level"),
Type.Literal("votesCount"),
Type.Literal("updatedAt"),
]),
order: Type.Union([Type.Literal("asc"), Type.Literal("desc")]),
userId: Type.Integer(),
}),
);
export type GetQuestionsQuery = Static<ReturnType<typeof generateGetQuestionsQuerySchema>>;
Expand All @@ -50,6 +52,7 @@ const generateQuestionShape = <
_levelId: Type.Union(args.levels.map((val) => Type.Literal(val))),
_statusId: Type.Union(args.statuses.map((val) => Type.Literal(val))),
acceptedAt: Type.Optional(Type.String({ format: "date-time" })),
updatedAt: Type.Optional(Type.String({ format: "date-time" })),
} as const;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "QuestionAnswerVote" (
"_userId" INTEGER NOT NULL,
"_questionAnswerId" INTEGER NOT NULL,
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "QuestionAnswerVote_pkey" PRIMARY KEY ("_userId","_questionAnswerId")
);

-- AddForeignKey
ALTER TABLE "QuestionAnswerVote" ADD CONSTRAINT "QuestionAnswerVote__questionAnswerId_fkey" FOREIGN KEY ("_questionAnswerId") REFERENCES "QuestionAnswer"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "QuestionAnswerVote" ADD CONSTRAINT "QuestionAnswerVote__userId_fkey" FOREIGN KEY ("_userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Loading