import { Body, Controller, Delete, HttpCode, Param, Patch, Post, Put, } from '@nestjs/common'; import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { User } from '@task-stream/api/identity/domain'; import { ApiPireonAuth, CurrentUser, } from '@task-stream/api/identity/presenters'; import { ApiCommandErrors } from '@task-stream/api/shared/presenters'; import { CommentsService } from '@task-stream/api/tracker/application'; import { REACTIONS } from '@task-stream/api/tracker/domain'; import { CommentBodyDto, ClosePollDto, ReactToCommentDto, ResolveThreadDto, TurnCommentIntoIssueDto, VotePollDto, } from './dto/requests.dto'; import { CommandResultDto } from './dto/responses.dto'; /** REST adapter of the comment use cases (listing and adding live under /issues). */ @ApiTags('Comments') @ApiPireonAuth() @Controller('comments') export class CommentsController { constructor(private readonly comments: CommentsService) {} @Patch(':commentId') @ApiOperation({ summary: 'Edit a comment (author only)' }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() edit( @CurrentUser() me: User, @Param('commentId') commentId: string, @Body() body: CommentBodyDto, ) { return this.comments.edit(me.id, commentId, body.body); } @Post(':commentId/reactions') @HttpCode(200) @ApiOperation({ summary: 'React to a comment, or take the reaction back (members)', description: `One of ${REACTIONS.join(' ')}; repeating it changes nothing.`, }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() react( @CurrentUser() me: User, @Param('commentId') commentId: string, @Body() body: ReactToCommentDto, ) { return this.comments.react(me.id, commentId, body.emoji, body.on); } @Put(':commentId/decision') @ApiOperation({ summary: 'Mark a comment as a decision of its ticket', description: 'Whoever can change the ticket. Decisions are shown above the ticket and searchable (GET …/decisions).', }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() markDecision(@CurrentUser() me: User, @Param('commentId') commentId: string) { return this.comments.markDecision(me.id, commentId, true); } @Delete(':commentId/decision') @ApiOperation({ summary: 'It is no longer a decision' }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() unmarkDecision( @CurrentUser() me: User, @Param('commentId') commentId: string, ) { return this.comments.markDecision(me.id, commentId, false); } @Post(':commentId/poll/vote') @HttpCode(200) @ApiOperation({ summary: "Vote in a comment's poll (members, viewers too)", description: 'option: its index; null takes the vote back.', }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() votePoll( @CurrentUser() me: User, @Param('commentId') commentId: string, @Body() body: VotePollDto, ) { return this.comments.votePoll(me.id, commentId, body.option); } @Post(':commentId/poll/close') @HttpCode(200) @ApiOperation({ summary: 'Close a poll (whoever started it, or an admin)', description: 'With decide (default), the winner stays as a decision of the ticket.', }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() closePoll( @CurrentUser() me: User, @Param('commentId') commentId: string, @Body() body: ClosePollDto, ) { return this.comments.closePoll(me.id, commentId, body.decide ?? true); } @Post(':commentId/issue') @ApiOperation({ summary: 'Make a ticket of a comment', description: 'Its text becomes the description, as a subtask of its ticket or linked to it (RELATES_TO); the comment shows where it went. Answers the new ticket. Whoever can change tickets.', }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() turnIntoIssue( @CurrentUser() me: User, @Param('commentId') commentId: string, @Body() body: TurnCommentIntoIssueDto, ) { return this.comments.turnIntoIssue( me.id, commentId, body.title, body.type, body.asSubtask ?? false, ); } @Put(':commentId/resolution') @ApiOperation({ summary: 'Settle a conversation (a top-level comment and its replies)', description: 'Whoever can change the ticket. A conclusion is added as a reply marked as a decision. The web folds a settled conversation.', }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() resolveThread( @CurrentUser() me: User, @Param('commentId') commentId: string, @Body() body: ResolveThreadDto, ) { return this.comments.resolveThread( me.id, commentId, true, body.conclusion ?? null, ); } @Delete(':commentId/resolution') @ApiOperation({ summary: 'Reopen a conversation' }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() reopenThread(@CurrentUser() me: User, @Param('commentId') commentId: string) { return this.comments.resolveThread(me.id, commentId, false); } @Delete(':commentId') @ApiOperation({ summary: 'Delete a comment (author or project admin)' }) @ApiOkResponse({ type: CommandResultDto }) @ApiCommandErrors() delete(@CurrentUser() me: User, @Param('commentId') commentId: string) { return this.comments.delete(me.id, commentId); } }