From 750c324b1a278d68ee7943382efed55e797880c4 Mon Sep 17 00:00:00 2001 From: Pranav Joglekar Date: Sat, 18 Nov 2023 05:54:26 +0530 Subject: [PATCH] Feat/5870 list all comments (#6081) --- .../__snapshots__/comments.test.ts.snap | 10 ++ server/routes/api/comments/comments.test.ts | 123 ++++++++++++++++++ server/routes/api/comments/comments.ts | 62 +++++++-- server/routes/api/comments/schema.ts | 3 +- server/test/factories.ts | 29 +++++ 5 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 server/routes/api/comments/__snapshots__/comments.test.ts.snap create mode 100644 server/routes/api/comments/comments.test.ts diff --git a/server/routes/api/comments/__snapshots__/comments.test.ts.snap b/server/routes/api/comments/__snapshots__/comments.test.ts.snap new file mode 100644 index 000000000..614104c04 --- /dev/null +++ b/server/routes/api/comments/__snapshots__/comments.test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#comments.list should require authentication 1`] = ` +{ + "error": "authentication_required", + "message": "Authentication required", + "ok": false, + "status": 401, +} +`; diff --git a/server/routes/api/comments/comments.test.ts b/server/routes/api/comments/comments.test.ts new file mode 100644 index 000000000..1cae8c60e --- /dev/null +++ b/server/routes/api/comments/comments.test.ts @@ -0,0 +1,123 @@ +import { + buildCollection, + buildComment, + buildDocument, + buildTeam, + buildUser, +} from "@server/test/factories"; +import { getTestServer } from "@server/test/support"; + +const server = getTestServer(); + +describe("#comments.list", () => { + it("should require authentication", async () => { + const res = await server.post("/api/comments.list"); + const body = await res.json(); + expect(res.status).toEqual(401); + expect(body).toMatchSnapshot(); + }); + it("should return all comments for a document", async () => { + const team = await buildTeam(); + const user = await buildUser({ teamId: team.id }); + const document = await buildDocument({ + userId: user.id, + teamId: user.teamId, + }); + const comment = await buildComment({ + userId: user.id, + teamId: team.id, + documentId: document.id, + }); + const res = await server.post("/api/comments.list", { + body: { + token: user.getJwtToken(), + documentId: document.id, + }, + }); + const body = await res.json(); + + expect(res.status).toEqual(200); + expect(body.data.length).toEqual(1); + expect(body.data[0].id).toEqual(comment.id); + expect(body.policies.length).toEqual(1); + expect(body.policies[0].abilities.read).toEqual(true); + }); + it("should return all comments for a collection", async () => { + const team = await buildTeam(); + const user = await buildUser({ teamId: team.id }); + const collection = await buildCollection({ + userId: user.id, + teamId: team.id, + }); + const document = await buildDocument({ + userId: user.id, + teamId: user.teamId, + collectionId: collection.id, + }); + const comment = await buildComment({ + userId: user.id, + teamId: team.id, + documentId: document.id, + }); + const res = await server.post("/api/comments.list", { + body: { + token: user.getJwtToken(), + collectionId: collection.id, + }, + }); + const body = await res.json(); + + expect(res.status).toEqual(200); + expect(body.data.length).toEqual(1); + expect(body.data[0].id).toEqual(comment.id); + expect(body.policies.length).toEqual(1); + expect(body.policies[0].abilities.read).toEqual(true); + }); + it("should return all comments", async () => { + const team = await buildTeam(); + const user = await buildUser({ teamId: team.id }); + const collection1 = await buildCollection({ + userId: user.id, + teamId: team.id, + }); + const collection2 = await buildCollection({ + userId: user.id, + teamId: team.id, + }); + const document1 = await buildDocument({ + userId: user.id, + teamId: user.teamId, + collectionId: collection1.id, + }); + const document2 = await buildDocument({ + userId: user.id, + teamId: user.teamId, + collectionId: collection2.id, + }); + const comment1 = await buildComment({ + userId: user.id, + teamId: team.id, + documentId: document1.id, + }); + const comment2 = await buildComment({ + userId: user.id, + teamId: team.id, + documentId: document2.id, + }); + const res = await server.post("/api/comments.list", { + body: { + token: user.getJwtToken(), + }, + }); + const body = await res.json(); + + expect(res.status).toEqual(200); + expect(body.data.length).toEqual(2); + expect([body.data[0].id, body.data[1].id].sort()).toEqual( + [comment1.id, comment2.id].sort() + ); + expect(body.policies.length).toEqual(2); + expect(body.policies[0].abilities.read).toEqual(true); + expect(body.policies[1].abilities.read).toEqual(true); + }); +}); diff --git a/server/routes/api/comments/comments.ts b/server/routes/api/comments/comments.ts index 5f2dbb594..6fe4401d3 100644 --- a/server/routes/api/comments/comments.ts +++ b/server/routes/api/comments/comments.ts @@ -1,5 +1,6 @@ import { Next } from "koa"; import Router from "koa-router"; +import { Op } from "sequelize"; import { TeamPreference } from "@shared/types"; import commentCreator from "@server/commands/commentCreator"; import commentDestroyer from "@server/commands/commentDestroyer"; @@ -9,7 +10,7 @@ import auth from "@server/middlewares/authentication"; import { rateLimiter } from "@server/middlewares/rateLimiter"; import { transaction } from "@server/middlewares/transaction"; import validate from "@server/middlewares/validate"; -import { Document, Comment } from "@server/models"; +import { Document, Comment, Collection } from "@server/models"; import { authorize } from "@server/policies"; import { presentComment, presentPolicies } from "@server/presenters"; import { APIContext } from "@server/types"; @@ -61,18 +62,57 @@ router.post( checkCommentingEnabled(), validate(T.CollectionsListSchema), async (ctx: APIContext) => { - const { sort, direction, documentId } = ctx.input.body; + const { sort, direction, documentId, collectionId } = ctx.input.body; const { user } = ctx.state.auth; - const document = await Document.findByPk(documentId, { userId: user.id }); - authorize(user, "read", document); - - const comments = await Comment.findAll({ - where: { documentId }, - order: [[sort, direction]], - offset: ctx.state.pagination.offset, - limit: ctx.state.pagination.limit, - }); + let comments; + if (documentId) { + const document = await Document.findByPk(documentId, { userId: user.id }); + authorize(user, "read", document); + comments = await Comment.findAll({ + where: { + documentId: document.id, + }, + order: [[sort, direction]], + offset: ctx.state.pagination.offset, + limit: ctx.state.pagination.limit, + }); + } else if (collectionId) { + const collection = await Collection.findByPk(collectionId); + authorize(user, "read", collection); + comments = await Comment.findAll({ + include: [ + { + model: Document, + required: true, + where: { + teamId: user.teamId, + collectionId, + }, + }, + ], + order: [[sort, direction]], + offset: ctx.state.pagination.offset, + limit: ctx.state.pagination.limit, + }); + } else { + const accessibleCollectionIds = await user.collectionIds(); + comments = await Comment.findAll({ + include: [ + { + model: Document, + required: true, + where: { + teamId: user.teamId, + collectionId: { [Op.in]: accessibleCollectionIds }, + }, + }, + ], + order: [[sort, direction]], + offset: ctx.state.pagination.offset, + limit: ctx.state.pagination.limit, + }); + } ctx.body = { pagination: ctx.state.pagination, diff --git a/server/routes/api/comments/schema.ts b/server/routes/api/comments/schema.ts index bb0eef7b2..f686ba45f 100644 --- a/server/routes/api/comments/schema.ts +++ b/server/routes/api/comments/schema.ts @@ -57,7 +57,8 @@ export type CommentsDeleteReq = z.infer; export const CollectionsListSchema = BaseSchema.extend({ body: CollectionsSortParamsSchema.extend({ /** Id of a document to list comments for */ - documentId: z.string(), + documentId: z.string().optional(), + collectionId: z.string().uuid().optional(), }), }); diff --git a/server/test/factories.ts b/server/test/factories.ts index 8a67f5d27..ba78a6f94 100644 --- a/server/test/factories.ts +++ b/server/test/factories.ts @@ -32,6 +32,7 @@ import { Notification, SearchQuery, Pin, + Comment, } from "@server/models"; import AttachmentHelper from "@server/models/helpers/AttachmentHelper"; @@ -395,6 +396,34 @@ export async function buildDocument( return document; } +export async function buildComment(overrides: { + userId: string; + teamId: string; + documentId: string; +}) { + const comment = await Comment.create({ + teamId: overrides.teamId, + documentId: overrides.documentId, + data: { + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "test", + }, + ], + }, + ], + }, + createdById: overrides.userId, + }); + + return comment; +} + export async function buildFileOperation( overrides: Partial = {} ) {