diff --git a/src/app/api/attachments/attachments.service.ts b/src/app/api/attachments/attachments.service.ts index 256db7c7c..92c377a09 100644 --- a/src/app/api/attachments/attachments.service.ts +++ b/src/app/api/attachments/attachments.service.ts @@ -86,4 +86,18 @@ export class AttachmentsService extends BaseService { const { data } = await supabase.supabase.storage.from(supabaseBucket).createSignedUrl(filePath, signedUrlTtl) return data?.signedUrl } + + async deleteAttachmentsOfComment(commentId: string) { + const policyGate = new PoliciesService(this.user) + policyGate.authorize(UserAction.Delete, Resource.Attachments) + + const commentAttachment = await this.db.attachment.findMany({ + where: { commentId: commentId, workspaceId: this.user.workspaceId }, + }) + await this.db.attachment.deleteMany({ + where: { commentId: commentId, workspaceId: this.user.workspaceId }, + }) + + return commentAttachment + } } diff --git a/src/app/api/comment/comment.service.ts b/src/app/api/comment/comment.service.ts index 2f23a5424..aa96aa3e7 100755 --- a/src/app/api/comment/comment.service.ts +++ b/src/app/api/comment/comment.service.ts @@ -1,3 +1,4 @@ +import { AttachmentsService } from '@/app/api/attachments/attachments.service' import { sendCommentCreateNotifications } from '@/jobs/notifications' import { sendReplyCreateNotifications } from '@/jobs/notifications/send-reply-create-notifications' import { InitiatedEntity } from '@/types/common' @@ -13,7 +14,7 @@ import { PoliciesService } from '@api/core/services/policies.service' import { Resource } from '@api/core/types/api' import { UserAction } from '@api/core/types/user' import { TasksService } from '@api/tasks/tasks.service' -import { ActivityType, Comment, CommentInitiator, Prisma } from '@prisma/client' +import { ActivityType, Comment, CommentInitiator, Prisma, PrismaClient } from '@prisma/client' import httpStatus from 'http-status' import { z } from 'zod' @@ -80,27 +81,54 @@ export class CommentService extends BaseService { const policyGate = new PoliciesService(this.user) policyGate.authorize(UserAction.Delete, Resource.Comment) - const replyCounts = await this.getReplyCounts([id]) - const comment = await this.db.comment.delete({ where: { id } }) + const commentExists = await this.db.comment.findFirst({ where: { id } }) + if (!commentExists) throw new APIError(httpStatus.NOT_FOUND, 'The comment to delete was not found') - // Delete corresponding activity log as well, so as to remove comment from UI - // If activity log exists but comment has a `deletedAt`, show "Comment was deleted" card instead - if (!replyCounts[id]) { - // If there are 0 replies, key won't be in object - await this.deleteRelatedActivityLogs(id) - } + // transaction that deletes the comment and its attachments + const { comment, attachments } = await this.db.$transaction(async (tx) => { + this.setTransaction(tx as PrismaClient) + const comment = await this.db.comment.delete({ where: { id } }) + + // delete the related attachments as well + const attachmentService = new AttachmentsService(this.user) + attachmentService.setTransaction(tx as PrismaClient) + + const attachments = await attachmentService.deleteAttachmentsOfComment(comment.id) + attachmentService.unsetTransaction() + + this.unsetTransaction() + return { comment, attachments } + }) + + // transaction that deletes the activity logs + return await this.db.$transaction(async (tx) => { + this.setTransaction(tx as PrismaClient) + const replyCounts = await this.getReplyCounts([id]) - // If parent comment now has no replies and is also deleted, delete parent as well - if (comment.parentId) { - const parent = await this.db.comment.findFirst({ where: { id: comment.parentId, deletedAt: undefined } }) - if (parent?.deletedAt) { - await this.deleteEmptyParentActivityLog(parent) + // Delete corresponding activity log as well, so as to remove comment from UI + // If activity log exists but comment has a `deletedAt`, show "Comment was deleted" card instead + if (!replyCounts[id]) { + // If there are 0 replies, key won't be in object + await this.deleteRelatedActivityLogs(id) } - } - const tasksService = new TasksService(this.user) - await tasksService.setNewLastActivityLogUpdated(comment.taskId) - return comment + // If parent comment now has no replies and is also deleted, delete parent as well + if (comment.parentId) { + const parent = await this.db.comment.findFirst({ where: { id: comment.parentId, deletedAt: undefined } }) + if (parent?.deletedAt) { + await this.deleteEmptyParentActivityLog(parent) + } + } + + const tasksService = new TasksService(this.user) + tasksService.setTransaction(tx as PrismaClient) + + await tasksService.setNewLastActivityLogUpdated(comment.taskId) + tasksService.unsetTransaction() + + this.unsetTransaction() + return { ...comment, attachments } + }) } private async deleteEmptyParentActivityLog(parent: Comment) { diff --git a/src/app/api/comment/public/[id]/route.ts b/src/app/api/comment/public/[id]/route.ts index 6c155e5d9..095c665c5 100644 --- a/src/app/api/comment/public/[id]/route.ts +++ b/src/app/api/comment/public/[id]/route.ts @@ -1,4 +1,5 @@ -import { getOneCommentPublicForTask } from '@/app/api/comment/public/comment-public.controller' +import { deleteOneCommentPublic, getOneCommentPublicForTask } from '@/app/api/comment/public/comment-public.controller' import { withErrorHandler } from '@/app/api/core/utils/withErrorHandler' export const GET = withErrorHandler(getOneCommentPublicForTask) +export const DELETE = withErrorHandler(deleteOneCommentPublic) diff --git a/src/app/api/comment/public/comment-public.controller.ts b/src/app/api/comment/public/comment-public.controller.ts index cc613dfe3..aab068853 100644 --- a/src/app/api/comment/public/comment-public.controller.ts +++ b/src/app/api/comment/public/comment-public.controller.ts @@ -56,3 +56,12 @@ export const getOneCommentPublicForTask = async (req: NextRequest, { params }: I return NextResponse.json({ data: await PublicCommentSerializer.serialize(comment) }) } + +export const deleteOneCommentPublic = async (req: NextRequest, { params }: IdParams) => { + const { id } = await params + const user = await authenticate(req) + + const commentService = new CommentService(user) + const deletedComment = await commentService.delete(id) + return NextResponse.json({ ...(await PublicCommentSerializer.serialize(deletedComment)) }) +}