102 lines
5.1 KiB
TypeScript
102 lines
5.1 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
ForbiddenException,
|
|
Get,
|
|
HttpCode,
|
|
HttpStatus,
|
|
type MessageEvent,
|
|
NotFoundException,
|
|
Param,
|
|
ParseIntPipe,
|
|
Patch,
|
|
Post,
|
|
Query,
|
|
Sse,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import { Session, type UserSession } from '@thallesp/nestjs-better-auth';
|
|
import { map, type Observable } from 'rxjs';
|
|
import { CreateCommentUseCase } from '../../application/use-cases/create-comment/create-comment.use-case.js';
|
|
import { ListCommentsUseCase } from '../../application/use-cases/list-comments/list-comments.use-case.js';
|
|
import { UpdateCommentUseCase } from '../../application/use-cases/update-comment/update-comment.use-case.js';
|
|
import { DeleteCommentUseCase } from '../../application/use-cases/delete-comment/delete-comment.use-case.js';
|
|
import { WatchIssueCommentsUseCase } from '../../application/use-cases/watch-issue-comments/watch-issue-comments.use-case.js';
|
|
import { CreateCommentCommand } from '../../application/use-cases/create-comment/create-comment.command.js';
|
|
import { ListCommentsQuery } from '../../application/use-cases/list-comments/list-comments.query.js';
|
|
import { UpdateCommentCommand } from '../../application/use-cases/update-comment/update-comment.command.js';
|
|
import { DeleteCommentCommand } from '../../application/use-cases/delete-comment/delete-comment.command.js';
|
|
import { WatchIssueCommentsQuery } from '../../application/use-cases/watch-issue-comments/watch-issue-comments.query.js';
|
|
import { CreateCommentRequestDto } from '../dto/create-comment.dto.js';
|
|
import { UpdateCommentRequestDto } from '../dto/update-comment.dto.js';
|
|
import { ListCommentsDto } from '../dto/list-comments.dto.js';
|
|
import { CommentNotFoundException } from '../../domain/exceptions/comment-not-found.exception.js';
|
|
import { CommentForbiddenException } from '../../domain/exceptions/comment-forbidden.exception.js';
|
|
import { IssueNotFoundException } from '../../../issue/domain/exceptions/issue-not-found.exception.js';
|
|
import { IssueVisibleGuard } from '../guards/issue-visible.guard.js';
|
|
|
|
@Controller()
|
|
export class CommentController {
|
|
constructor(
|
|
private readonly createCommentUseCase: CreateCommentUseCase,
|
|
private readonly listCommentsUseCase: ListCommentsUseCase,
|
|
private readonly updateCommentUseCase: UpdateCommentUseCase,
|
|
private readonly deleteCommentUseCase: DeleteCommentUseCase,
|
|
private readonly watchIssueCommentsUseCase: WatchIssueCommentsUseCase,
|
|
) {}
|
|
|
|
@Get('issues/:issueId/comments')
|
|
list(@Param('issueId', ParseIntPipe) issueId: number, @Query() query: ListCommentsDto, @Session() session: UserSession) {
|
|
return this.handleErrors(() =>
|
|
this.listCommentsUseCase.execute(new ListCommentsQuery(issueId, session.user.id, Number(query.limit), query.cursor)),
|
|
);
|
|
}
|
|
|
|
@Post('issues/:issueId/comments')
|
|
create(@Param('issueId', ParseIntPipe) issueId: number, @Body() dto: CreateCommentRequestDto, @Session() session: UserSession) {
|
|
return this.handleErrors(() => this.createCommentUseCase.execute(new CreateCommentCommand(issueId, session.user.id, dto.content)));
|
|
}
|
|
|
|
/**
|
|
* SSE-поток комментариев одной задачи: клиент открывает его на странице
|
|
* задачи и закрывает при уходе. После переподключения клиенту стоит
|
|
* перезапросить список — события, пришедшие во время обрыва, не повторяются.
|
|
* Доступ проверяет IssueVisibleGuard — так клиент без доступа получает 404.
|
|
*/
|
|
@Sse('issues/:issueId/comments/events')
|
|
@UseGuards(IssueVisibleGuard)
|
|
events(@Param('issueId', ParseIntPipe) issueId: number, @Session() session: UserSession): Promise<Observable<MessageEvent>> {
|
|
return this.handleErrors(async () => {
|
|
const events$ = await this.watchIssueCommentsUseCase.execute(new WatchIssueCommentsQuery(issueId, session.user.id));
|
|
return events$.pipe(map((event) => ({ type: event.type, data: event.comment })));
|
|
});
|
|
}
|
|
|
|
@Patch('comments/:id')
|
|
update(@Param('id') id: string, @Body() dto: UpdateCommentRequestDto, @Session() session: UserSession) {
|
|
return this.handleErrors(() => this.updateCommentUseCase.execute(new UpdateCommentCommand(id, session.user.id, dto.content)));
|
|
}
|
|
|
|
@Delete('comments/:id')
|
|
@HttpCode(HttpStatus.NO_CONTENT)
|
|
async delete(@Param('id') id: string, @Session() session: UserSession) {
|
|
await this.handleErrors(() => this.deleteCommentUseCase.execute(new DeleteCommentCommand(id, session.user.id)));
|
|
}
|
|
|
|
/** Транслирует доменные исключения в HTTP-ответ — это забота presentation-слоя */
|
|
private async handleErrors<T>(fn: () => Promise<T>): Promise<T> {
|
|
try {
|
|
return await fn();
|
|
} catch (error) {
|
|
if (error instanceof CommentNotFoundException || error instanceof IssueNotFoundException) {
|
|
throw new NotFoundException(error.message);
|
|
}
|
|
if (error instanceof CommentForbiddenException) {
|
|
throw new ForbiddenException(error.message);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
}
|