From c5f722dee17e02ab74aa5f86c486310999e47574 Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Mon, 16 Sep 2024 10:45:56 -0400 Subject: [PATCH 1/4] refactor delete comment --- .../src/comment/DeleteComment.command.ts | 49 ++++++++++++ libs/model/src/comment/index.ts | 1 + libs/model/src/middleware/authorization.ts | 7 +- .../test/thread/thread-lifecycle.spec.ts | 34 ++++++++ libs/schemas/src/commands/comment.schemas.ts | 14 ++++ .../state/api/comments/deleteComment.ts | 80 ++++++------------- .../discussions/CommentTree/CommentTree.tsx | 8 +- packages/commonwealth/server/api/comment.ts | 1 + .../server/api/external-router.ts | 10 ++- .../server/api/integration-router.ts | 4 +- .../controllers/server_comments_controller.ts | 15 ---- .../server_comments_methods/delete_comment.ts | 77 ------------------ .../comments/delete_comment_bot_handler.ts | 28 ------- .../routes/comments/delete_comment_handler.ts | 27 ------- .../commonwealth/server/routing/router.ts | 9 --- .../integration/api/createComment.spec.ts | 3 +- .../server_comments_controller.spec.ts | 52 ------------ .../src/discord-consumer/handlers.ts | 3 + 18 files changed, 144 insertions(+), 278 deletions(-) create mode 100644 libs/model/src/comment/DeleteComment.command.ts delete mode 100644 packages/commonwealth/server/controllers/server_comments_methods/delete_comment.ts delete mode 100644 packages/commonwealth/server/routes/comments/delete_comment_bot_handler.ts delete mode 100644 packages/commonwealth/server/routes/comments/delete_comment_handler.ts diff --git a/libs/model/src/comment/DeleteComment.command.ts b/libs/model/src/comment/DeleteComment.command.ts new file mode 100644 index 00000000000..ddc006ddda0 --- /dev/null +++ b/libs/model/src/comment/DeleteComment.command.ts @@ -0,0 +1,49 @@ +import { InvalidActor, type Command } from '@hicommonwealth/core'; +import * as schemas from '@hicommonwealth/schemas'; +import { models } from '../database'; +import { isAuthorized, type AuthContext } from '../middleware'; +import { mustBeAuthorized, mustExist } from '../middleware/guards'; + +export function DeleteComment(): Command< + typeof schemas.DeleteComment, + AuthContext +> { + return { + ...schemas.DeleteComment, + auth: [isAuthorized({})], + body: async ({ actor, payload, auth }) => { + const { address } = mustBeAuthorized(actor, auth); + const { comment_id, message_id } = payload; + + const comment = await models.Comment.findOne({ + where: message_id + ? { discord_meta: { message_id } } + : { id: comment_id }, + include: [ + { + model: models.Thread, + attributes: ['community_id'], + required: true, + }, + ], + logging: true, + }); + mustExist('Comment', comment); + + if (comment.address_id !== address!.id && address.role === 'member') + throw new InvalidActor(actor, 'Not authorized author'); + + // == mutation transaction boundary == + await models.sequelize.transaction(async (transaction) => { + await models.CommentSubscription.destroy({ + where: { comment_id: comment.id }, + transaction, + }); + await comment.destroy({ transaction }); + }); + // == end of transaction boundary == + + return { comment_id: comment.id!, canvas_hash: comment.canvas_hash }; + }, + }; +} diff --git a/libs/model/src/comment/index.ts b/libs/model/src/comment/index.ts index ed1191b79b9..a61c205b4f5 100644 --- a/libs/model/src/comment/index.ts +++ b/libs/model/src/comment/index.ts @@ -1,5 +1,6 @@ export * from './CreateComment.command'; export * from './CreateCommentReaction.command'; +export * from './DeleteComment.command'; export * from './GetComments.query'; export * from './SearchComments.query'; export * from './UpdateComment.command'; diff --git a/libs/model/src/middleware/authorization.ts b/libs/model/src/middleware/authorization.ts index a9d3a707f35..a55e8eb38cf 100644 --- a/libs/model/src/middleware/authorization.ts +++ b/libs/model/src/middleware/authorization.ts @@ -249,16 +249,19 @@ export const isSuperAdmin: AuthHandler = async (ctx) => { * * @param roles specific community roles - all by default * @param action specific group permission action + * @param author when true, rejects members that are not the author * @param collaborators authorize thread collaborators * @throws InvalidActor when not authorized */ export function isAuthorized({ roles = ['admin', 'moderator', 'member'], action, + author = false, collaborators = false, }: { roles?: Role[]; action?: GroupPermissionAction; + author?: boolean; collaborators?: boolean; }): AuthHandler { return async (ctx) => { @@ -291,7 +294,9 @@ export function isAuthorized({ throw new InvalidActor(ctx.actor, 'Not authorized collaborator'); } + if (author) throw new InvalidActor(ctx.actor, 'Not authorized member'); + // at this point, the address is either a moderator or member - // without any action or collaboration requirements + // without any security requirements for action, author, or collaboration }; } diff --git a/libs/model/test/thread/thread-lifecycle.spec.ts b/libs/model/test/thread/thread-lifecycle.spec.ts index 231328ef26c..09b20c0ab08 100644 --- a/libs/model/test/thread/thread-lifecycle.spec.ts +++ b/libs/model/test/thread/thread-lifecycle.spec.ts @@ -7,6 +7,7 @@ const getNamespaceBalanceStub = sinon.stub( import { Actor, + InvalidActor, InvalidInput, InvalidState, command, @@ -22,6 +23,7 @@ import { CreateComment, CreateCommentErrors, CreateCommentReaction, + DeleteComment, MAX_COMMENT_DEPTH, UpdateComment, } from '../../src/comment'; @@ -545,6 +547,38 @@ describe('Thread lifecycle', () => { }), ).rejects.toThrowError(InvalidInput); }); + + it('should delete a comment', async () => { + const text = 'to be deleted'; + const tbd = await command(CreateComment(), { + actor: actors.member, + payload: { + thread_id: thread.id!, + text, + }, + }); + expect(tbd).to.include({ + thread_id: thread!.id, + text, + community_id: thread!.community_id, + }); + const deleted = await command(DeleteComment(), { + actor: actors.member, + payload: { comment_id: tbd!.id! }, + }); + expect(deleted).to.include({ comment_id: tbd!.id! }); + }); + + it('should throw delete when user is not author', async () => { + await expect( + command(DeleteComment(), { + actor: actors.rejected, + payload: { + comment_id: comment!.id, + }, + }), + ).rejects.toThrowError(InvalidActor); + }); }); describe('thread reaction', () => { diff --git a/libs/schemas/src/commands/comment.schemas.ts b/libs/schemas/src/commands/comment.schemas.ts index 813a171833a..045e151b842 100644 --- a/libs/schemas/src/commands/comment.schemas.ts +++ b/libs/schemas/src/commands/comment.schemas.ts @@ -40,3 +40,17 @@ export const CreateCommentReaction = { input: CommentCanvasReaction, output: Reaction.extend({ community_id: z.string() }), }; + +export const DeleteComment = { + input: z.object({ + comment_id: PG_INT, + + // discord integration + thread_id: PG_INT.optional(), + message_id: z.string().optional(), + }), + output: z.object({ + comment_id: PG_INT, + canvas_hash: z.string().nullish(), + }), +}; diff --git a/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts b/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts index a9d56ffc5f6..cba22cba3d8 100644 --- a/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts +++ b/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts @@ -1,60 +1,15 @@ import { toCanvasSignedDataApiArgs } from '@hicommonwealth/shared'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import axios from 'axios'; +import { useQueryClient } from '@tanstack/react-query'; +import { trpc } from 'client/scripts/utils/trpcClient'; import { signDeleteComment } from 'controllers/server/sessions'; import Comment from 'models/Comment'; import { IUniqueId } from 'models/interfaces'; -import { ApiEndpoints, SERVER_URL } from 'state/api/config'; +import { ApiEndpoints } from 'state/api/config'; import { useAuthModalStore } from '../../ui/modals'; import { userStore } from '../../ui/user'; import { updateThreadInAllCaches } from '../threads/helpers/cache'; import useFetchCommentsQuery from './fetchComments'; -interface DeleteCommentProps { - address: string; - communityId: string; - canvasHash: string; - commentId: number; - existingNumberOfComments: number; -} - -const deleteComment = async ({ - address, - communityId, - commentId, - canvasHash, -}: DeleteCommentProps) => { - const canvasSignedData = await signDeleteComment( - userStore.getState().activeAccount?.address || '', - { - comment_id: canvasHash, - }, - ); - - await axios.delete(`${SERVER_URL}/comments/${commentId}`, { - data: { - jwt: userStore.getState().jwt, - address: address, - community_id: communityId, - author_community_id: communityId, - }, - }); - - // Important: we render comments in a tree, if the deleted comment is a - // leaf node, remove it, but if it has replies, then preserve it with - // [deleted] msg. - return { - softDeleted: { - id: commentId, - deleted: true, - text: '[deleted]', - plaintext: '[deleted]', - versionHistory: [], - ...toCanvasSignedDataApiArgs(canvasSignedData), - }, - }; -}; - interface UseDeleteCommentMutationProps { communityId: string; threadId: number; @@ -74,18 +29,33 @@ const useDeleteCommentMutation = ({ const { checkForSessionKeyRevalidationErrors } = useAuthModalStore(); - return useMutation({ - mutationFn: deleteComment, + return trpc.comment.deleteComment.useMutation({ onSuccess: async (response) => { + // Important: we render comments in a tree, if the deleted comment is a + // leaf node, remove it, but if it has replies, then preserve it with + // [deleted] msg. + const canvasSignedData = await signDeleteComment( + userStore.getState().activeAccount?.address || '', + { comment_id: response.canvas_hash }, + ); + const softDeleted = { + id: response.comment_id, + deleted: true, + text: '[deleted]', + plaintext: '[deleted]', + versionHistory: [], + ...toCanvasSignedDataApiArgs(canvasSignedData), + }; + // find the existing comment index const foundCommentIndex = comments.findIndex( - (x) => x.id === response.softDeleted.id, + (x) => x.id === softDeleted.id, ); if (foundCommentIndex > -1) { const softDeletedComment = Object.assign( { ...comments[foundCommentIndex] }, - { ...response.softDeleted }, + { ...softDeleted }, ); // update fetch comments query state @@ -104,13 +74,11 @@ const useDeleteCommentMutation = ({ communityId, threadId, { - recentComments: [ - { id: response.softDeleted.id }, - ] as Comment[], + recentComments: [{ id: softDeleted.id }] as Comment[], }, 'removeFromExisting', ); - return response; + return softDeleted; }, onError: (error) => checkForSessionKeyRevalidationErrors(error), }); diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx index 882919bdf73..0207c610ebc 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx @@ -193,13 +193,7 @@ export const CommentTree = ({ buttonHeight: 'sm', onClick: async () => { try { - await deleteComment({ - communityId, - commentId: comment.id, - canvasHash: comment.canvasHash, - address: user.activeAccount?.address || '', - existingNumberOfComments: thread.numberOfComments, - }); + await deleteComment({ comment_id: comment.id }); } catch (err) { if (err instanceof SessionKeyError) { return; diff --git a/packages/commonwealth/server/api/comment.ts b/packages/commonwealth/server/api/comment.ts index bd3334cedd8..a1205b8532c 100644 --- a/packages/commonwealth/server/api/comment.ts +++ b/packages/commonwealth/server/api/comment.ts @@ -18,4 +18,5 @@ export const trpcRouter = trpc.router({ ), searchComments: trpc.query(Comment.SearchComments, trpc.Tag.Comment), getComments: trpc.query(Comment.GetComments, trpc.Tag.Comment), + deleteComment: trpc.command(Comment.DeleteComment, trpc.Tag.Comment), }); diff --git a/packages/commonwealth/server/api/external-router.ts b/packages/commonwealth/server/api/external-router.ts index a63c4b66ffa..76a5c4e8990 100644 --- a/packages/commonwealth/server/api/external-router.ts +++ b/packages/commonwealth/server/api/external-router.ts @@ -15,8 +15,13 @@ const { getMembers, } = community.trpcRouter; const { createThread, updateThread, createThreadReaction } = thread.trpcRouter; -const { createComment, createCommentReaction, updateComment, getComments } = - comment.trpcRouter; +const { + createComment, + createCommentReaction, + updateComment, + getComments, + deleteComment, +} = comment.trpcRouter; const api = { createCommunity, @@ -31,6 +36,7 @@ const api = { createComment, updateComment, createCommentReaction, + deleteComment, }; const PATH = '/api/v1'; diff --git a/packages/commonwealth/server/api/integration-router.ts b/packages/commonwealth/server/api/integration-router.ts index 595a75b1540..ee9ef9861be 100644 --- a/packages/commonwealth/server/api/integration-router.ts +++ b/packages/commonwealth/server/api/integration-router.ts @@ -4,7 +4,6 @@ import { RequestHandler, Router, raw } from 'express'; // TODO: remove as we migrate to tRPC commands import DatabaseValidationService from 'server/middleware/databaseValidationService'; -import { deleteBotCommentHandler } from 'server/routes/comments/delete_comment_bot_handler'; import { deleteBotThreadHandler } from 'server/routes/threads/delete_thread_bot_handler'; import { ServerControllers } from 'server/routing/router'; @@ -75,8 +74,7 @@ function build( router.delete( '/bot/comments/:message_id', isBotUser, - isAuthor, - deleteBotCommentHandler.bind(this, controllers), + express.command(Comment.DeleteComment()), ); return router; diff --git a/packages/commonwealth/server/controllers/server_comments_controller.ts b/packages/commonwealth/server/controllers/server_comments_controller.ts index ff03811363d..101d5dca915 100644 --- a/packages/commonwealth/server/controllers/server_comments_controller.ts +++ b/packages/commonwealth/server/controllers/server_comments_controller.ts @@ -1,9 +1,4 @@ import { DB, GlobalActivityCache } from '@hicommonwealth/model'; -import { - DeleteCommentOptions, - DeleteCommentResult, - __deleteComment, -} from './server_comments_methods/delete_comment'; import { SearchCommentsOptions, SearchCommentsResult, @@ -29,14 +24,4 @@ export class ServerCommentsController { ): Promise { return __searchComments.call(this, options); } - - /** - * Deletes a comment. - * - */ - async deleteComment( - options: DeleteCommentOptions, - ): Promise { - return __deleteComment.call(this, options); - } } diff --git a/packages/commonwealth/server/controllers/server_comments_methods/delete_comment.ts b/packages/commonwealth/server/controllers/server_comments_methods/delete_comment.ts deleted file mode 100644 index ad6acf4fdbe..00000000000 --- a/packages/commonwealth/server/controllers/server_comments_methods/delete_comment.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { AppError } from '@hicommonwealth/core'; -import { - AddressInstance, - CommentAttributes, - UserInstance, -} from '@hicommonwealth/model'; -import { WhereOptions } from 'sequelize'; -import { validateOwner } from 'server/util/validateOwner'; -import { ServerCommentsController } from '../server_comments_controller'; - -const Errors = { - CommentNotFound: 'Comment not found', - BanError: 'Ban error', - NotOwned: 'Not owned by this user', -}; - -export type DeleteCommentOptions = { - user: UserInstance; - address: AddressInstance; - commentId?: number; - messageId?: string; -}; - -export type DeleteCommentResult = void; - -export async function __deleteComment( - this: ServerCommentsController, - { user, address, commentId, messageId }: DeleteCommentOptions, -): Promise { - const commentWhere: WhereOptions = {}; - if (commentId) { - commentWhere.id = commentId; - } - if (messageId) { - commentWhere.discord_meta = { - message_id: messageId, - }; - } - - const comment = await this.models.Comment.findOne({ - where: commentWhere, - include: [{ model: this.models.Thread, attributes: ['community_id'] }], - }); - const community_id = comment?.Thread?.community_id; - - if (!comment || !community_id) { - throw new AppError(Errors.CommentNotFound); - } - - if (address.is_banned) throw new AppError('Banned User'); - - const isAdminOrOwner = await validateOwner({ - models: this.models, - user, - entity: comment, - communityId: community_id, - allowMod: true, - allowAdmin: true, - allowSuperAdmin: true, - }); - if (!isAdminOrOwner) { - throw new AppError(Errors.NotOwned); - } - - await this.models.sequelize.transaction(async (transaction) => { - // find and delete all associated subscriptions - await this.models.CommentSubscription.destroy({ - where: { - comment_id: comment.id!, - }, - transaction, - }); - - // actually delete - await comment.destroy({ transaction }); - }); -} diff --git a/packages/commonwealth/server/routes/comments/delete_comment_bot_handler.ts b/packages/commonwealth/server/routes/comments/delete_comment_bot_handler.ts deleted file mode 100644 index a9fabd69c4b..00000000000 --- a/packages/commonwealth/server/routes/comments/delete_comment_bot_handler.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CommentAttributes } from '@hicommonwealth/model'; -import { ServerControllers } from '../../routing/router'; -import { TypedRequestParams, TypedResponse, success } from '../../types'; - -type DeleteBotCommentRequestParams = { - message_id: string; -}; - -type DeleteCommentResponse = CommentAttributes; - -export const deleteBotCommentHandler = async ( - controllers: ServerControllers, - req: TypedRequestParams, - res: TypedResponse, -) => { - const { user, address } = req; - const { message_id } = req.params; - - await controllers.comments.deleteComment({ - // @ts-expect-error StrictNullChecks - user, - // @ts-expect-error StrictNullChecks - address, - messageId: message_id, // Discord bot only - }); - - return success(res, undefined); -}; diff --git a/packages/commonwealth/server/routes/comments/delete_comment_handler.ts b/packages/commonwealth/server/routes/comments/delete_comment_handler.ts deleted file mode 100644 index 15453ca14bf..00000000000 --- a/packages/commonwealth/server/routes/comments/delete_comment_handler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CommentAttributes } from '@hicommonwealth/model'; -import { ServerControllers } from '../../routing/router'; -import { TypedRequestParams, TypedResponse, success } from '../../types'; - -type DeleteCommentRequestParams = { - id: string; -}; -type DeleteCommentResponse = CommentAttributes; - -export const deleteCommentHandler = async ( - controllers: ServerControllers, - req: TypedRequestParams, - res: TypedResponse, -) => { - const { user, address } = req; - const { id: commentId } = req.params; - - await controllers.comments.deleteComment({ - // @ts-expect-error StrictNullChecks - user, - // @ts-expect-error StrictNullChecks - address, - commentId: parseInt(commentId, 10), - }); - - return success(res, undefined); -}; diff --git a/packages/commonwealth/server/routing/router.ts b/packages/commonwealth/server/routing/router.ts index 21ceee15369..609843949e7 100644 --- a/packages/commonwealth/server/routing/router.ts +++ b/packages/commonwealth/server/routing/router.ts @@ -91,7 +91,6 @@ import { getTopUsersHandler } from 'server/routes/admin/get_top_users_handler'; import { getNamespaceMetadata } from 'server/routes/communities/get_namespace_metadata'; import { config } from '../config'; import { getStatsHandler } from '../routes/admin/get_stats_handler'; -import { deleteCommentHandler } from '../routes/comments/delete_comment_handler'; import { searchCommentsHandler } from '../routes/comments/search_comments_handler'; import { createChainNodeHandler } from '../routes/communities/create_chain_node_handler'; import { deleteCommunityHandler } from '../routes/communities/delete_community_handler'; @@ -407,14 +406,6 @@ function setupRouter( registerRoute(router, 'get', '/profile/v2', getProfileNew.bind(this, models)); // comments - registerRoute( - router, - 'delete', - '/comments/:id', - passport.authenticate('jwt', { session: false }), - databaseValidationService.validateAuthor, - deleteCommentHandler.bind(this, serverControllers), - ); registerRoute( router, 'get', diff --git a/packages/commonwealth/test/integration/api/createComment.spec.ts b/packages/commonwealth/test/integration/api/createComment.spec.ts index 27de34ecdf3..9cbdfd575d1 100644 --- a/packages/commonwealth/test/integration/api/createComment.spec.ts +++ b/packages/commonwealth/test/integration/api/createComment.spec.ts @@ -57,7 +57,8 @@ describe('createComment Integration Tests', () => { }; return await chai .request(server.app) - .del(`/api/comments/${commentId}`) + .post(`/api/v1/DeleteComment`) + .set('address', validRequest.address) .send(validRequest); }; diff --git a/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts b/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts index cede0a42f4c..94885065336 100644 --- a/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts +++ b/packages/commonwealth/test/unit/server_controllers/server_comments_controller.spec.ts @@ -56,56 +56,4 @@ describe('ServerCommentsController', () => { expect(comments.totalResults).to.equal(11); }); }); - - describe('#deleteComment', () => { - test('should delete a comment', async () => { - let didDestroy = false; - const db = { - sequelize: { - query: Promise.resolve([]), - transaction: (callback) => Promise.resolve(callback()), - }, - Address: { - findAll: async () => [{ address_id: 1 }], // used in findOneRole - }, - Comment: { - findOne: async () => ({ - address_id: 1, - Thread: { community_id: 1 }, - destroy: async () => { - didDestroy = true; - }, - }), - update: () => ({}), - }, - CommentSubscription: { - destroy: async () => ({}), - }, - }; - - // @ts-expect-error ignore type - const serverCommentsController = new ServerCommentsController(db); - const user = { - getAddresses: async () => [{ id: 1, verified: true }], - }; - const address = { id: 1 }; - const commentId = 1; - await serverCommentsController.deleteComment({ - // @ts-expect-error ignore type - user, - // @ts-expect-error ignore type - address, - commentId, - }); - expect(didDestroy).to.be.true; - - serverCommentsController.deleteComment({ - // @ts-expect-error ignore type - user, - // @ts-expect-error ignore type - address, - commentId, - }); - }); - }); }); diff --git a/packages/discord-bot/src/discord-consumer/handlers.ts b/packages/discord-bot/src/discord-consumer/handlers.ts index 6eb2e84ad78..7edd82f1542 100644 --- a/packages/discord-bot/src/discord-consumer/handlers.ts +++ b/packages/discord-bot/src/discord-consumer/handlers.ts @@ -101,6 +101,9 @@ export async function handleCommentMessages( await axios.delete(`${bot_path}/comments/${message.message_id}`, { data: { ...sharedReqData, + comment_id: 0, // required in command + thread_id: thread.id, // to auth command + message_id: message.message_id, }, }); break; From 4c2dba4b0c366813745e4ba9ae823ee56f46eacb Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Mon, 16 Sep 2024 11:37:16 -0400 Subject: [PATCH 2/4] fix lint --- packages/commonwealth/server/api/integration-router.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/commonwealth/server/api/integration-router.ts b/packages/commonwealth/server/api/integration-router.ts index ee9ef9861be..ab5ee102083 100644 --- a/packages/commonwealth/server/api/integration-router.ts +++ b/packages/commonwealth/server/api/integration-router.ts @@ -17,9 +17,6 @@ function build( const isBotUser: RequestHandler = (req, res, next) => { validator.validateBotUser(req, res, next).catch(next); }; - const isAuthor: RequestHandler = (req, res, next) => { - validator.validateAuthor(req, res, next).catch(next); - }; const router = Router(); router.use(express.statsMiddleware); From ebc50e1b75518804a2e324c347c0cded282a905d Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Mon, 16 Sep 2024 11:52:00 -0400 Subject: [PATCH 3/4] use canvas message id --- libs/model/src/comment/DeleteComment.command.ts | 2 +- libs/schemas/src/commands/comment.schemas.ts | 2 +- .../client/scripts/state/api/comments/deleteComment.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/model/src/comment/DeleteComment.command.ts b/libs/model/src/comment/DeleteComment.command.ts index ddc006ddda0..ef688b2e453 100644 --- a/libs/model/src/comment/DeleteComment.command.ts +++ b/libs/model/src/comment/DeleteComment.command.ts @@ -43,7 +43,7 @@ export function DeleteComment(): Command< }); // == end of transaction boundary == - return { comment_id: comment.id!, canvas_hash: comment.canvas_hash }; + return { comment_id: comment.id!, canvas_hash: comment.canvas_msg_id }; }, }; } diff --git a/libs/schemas/src/commands/comment.schemas.ts b/libs/schemas/src/commands/comment.schemas.ts index a83a1b3e2f8..f18833245e4 100644 --- a/libs/schemas/src/commands/comment.schemas.ts +++ b/libs/schemas/src/commands/comment.schemas.ts @@ -54,6 +54,6 @@ export const DeleteComment = { }), output: z.object({ comment_id: PG_INT, - canvas_hash: z.string().nullish(), + canvas_msg_id: z.string().nullish(), }), }; diff --git a/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts b/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts index cba22cba3d8..dd835481505 100644 --- a/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts +++ b/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts @@ -36,7 +36,7 @@ const useDeleteCommentMutation = ({ // [deleted] msg. const canvasSignedData = await signDeleteComment( userStore.getState().activeAccount?.address || '', - { comment_id: response.canvas_hash }, + { comment_id: response.canvas_msg_id }, ); const softDeleted = { id: response.comment_id, From cc745b1359efe4571f463133db0fd5519a0edbaf Mon Sep 17 00:00:00 2001 From: rotorsoft Date: Tue, 17 Sep 2024 09:22:33 -0400 Subject: [PATCH 4/4] fix mutation handler, add another test --- .../src/comment/DeleteComment.command.ts | 7 ++++-- .../test/thread/thread-lifecycle.spec.ts | 23 ++++++++++++++++++- libs/schemas/src/commands/comment.schemas.ts | 1 + .../state/api/comments/deleteComment.ts | 10 ++------ .../discussions/CommentTree/CommentTree.tsx | 2 +- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/libs/model/src/comment/DeleteComment.command.ts b/libs/model/src/comment/DeleteComment.command.ts index ef688b2e453..e2fdb2096c9 100644 --- a/libs/model/src/comment/DeleteComment.command.ts +++ b/libs/model/src/comment/DeleteComment.command.ts @@ -26,7 +26,6 @@ export function DeleteComment(): Command< required: true, }, ], - logging: true, }); mustExist('Comment', comment); @@ -43,7 +42,11 @@ export function DeleteComment(): Command< }); // == end of transaction boundary == - return { comment_id: comment.id!, canvas_hash: comment.canvas_msg_id }; + return { + comment_id: comment.id!, + canvas_signed_data: comment.canvas_signed_data, + canvas_msg_id: comment.canvas_msg_id, + }; }, }; } diff --git a/libs/model/test/thread/thread-lifecycle.spec.ts b/libs/model/test/thread/thread-lifecycle.spec.ts index 9314f995042..f6250252242 100644 --- a/libs/model/test/thread/thread-lifecycle.spec.ts +++ b/libs/model/test/thread/thread-lifecycle.spec.ts @@ -593,7 +593,7 @@ describe('Thread lifecycle', () => { ).rejects.toThrowError(InvalidInput); }); - it('should delete a comment', async () => { + it('should delete a comment as author', async () => { const text = 'to be deleted'; const tbd = await command(CreateComment(), { actor: actors.member, @@ -614,6 +614,27 @@ describe('Thread lifecycle', () => { expect(deleted).to.include({ comment_id: tbd!.id! }); }); + it('should delete a comment as admin', async () => { + const text = 'to be deleted'; + const tbd = await command(CreateComment(), { + actor: actors.member, + payload: { + thread_id: thread.id!, + text, + }, + }); + expect(tbd).to.include({ + thread_id: thread!.id, + text, + community_id: thread!.community_id, + }); + const deleted = await command(DeleteComment(), { + actor: actors.admin, + payload: { comment_id: tbd!.id! }, + }); + expect(deleted).to.include({ comment_id: tbd!.id! }); + }); + it('should throw delete when user is not author', async () => { await expect( command(DeleteComment(), { diff --git a/libs/schemas/src/commands/comment.schemas.ts b/libs/schemas/src/commands/comment.schemas.ts index 080f34211c9..c6b1a155a58 100644 --- a/libs/schemas/src/commands/comment.schemas.ts +++ b/libs/schemas/src/commands/comment.schemas.ts @@ -54,6 +54,7 @@ export const DeleteComment = { }), output: z.object({ comment_id: PG_INT, + canvas_signed_data: z.string().nullish(), canvas_msg_id: z.string().nullish(), }), }; diff --git a/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts b/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts index dd835481505..c94eaffbf20 100644 --- a/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts +++ b/packages/commonwealth/client/scripts/state/api/comments/deleteComment.ts @@ -1,12 +1,9 @@ -import { toCanvasSignedDataApiArgs } from '@hicommonwealth/shared'; import { useQueryClient } from '@tanstack/react-query'; import { trpc } from 'client/scripts/utils/trpcClient'; -import { signDeleteComment } from 'controllers/server/sessions'; import Comment from 'models/Comment'; import { IUniqueId } from 'models/interfaces'; import { ApiEndpoints } from 'state/api/config'; import { useAuthModalStore } from '../../ui/modals'; -import { userStore } from '../../ui/user'; import { updateThreadInAllCaches } from '../threads/helpers/cache'; import useFetchCommentsQuery from './fetchComments'; @@ -34,17 +31,14 @@ const useDeleteCommentMutation = ({ // Important: we render comments in a tree, if the deleted comment is a // leaf node, remove it, but if it has replies, then preserve it with // [deleted] msg. - const canvasSignedData = await signDeleteComment( - userStore.getState().activeAccount?.address || '', - { comment_id: response.canvas_msg_id }, - ); const softDeleted = { id: response.comment_id, deleted: true, text: '[deleted]', plaintext: '[deleted]', versionHistory: [], - ...toCanvasSignedDataApiArgs(canvasSignedData), + canvas_signed_data: response.canvas_signed_data, + canvas_msg_id: response.canvas_msg_id, }; // find the existing comment index diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx index 9a7b772f6fe..35c2a4abe34 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/CommentTree/CommentTree.tsx @@ -201,7 +201,7 @@ export const CommentTree = ({ checkForSessionKeyRevalidationErrors(err); return; } - console.error(err.response.data.error || err?.message); + console.error(err.message); notifyError('Failed to delete comment'); } },