49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import {
|
||
BadRequestException,
|
||
type CanActivate,
|
||
type ExecutionContext,
|
||
Injectable,
|
||
NotFoundException,
|
||
UnauthorizedException,
|
||
} from '@nestjs/common';
|
||
import type { UserSession } from '@thallesp/nestjs-better-auth';
|
||
import { CommentAccessService } from '../../application/services/comment-access.service.js';
|
||
import { IssueNotFoundException } from '../../../issue/domain/exceptions/issue-not-found.exception.js';
|
||
|
||
/**
|
||
* Проверяет доступ к задаче из `:issueId` ДО вызова обработчика.
|
||
*
|
||
* Нужен SSE-эндпоинтам: при наличии глобального interceptor'а Nest вызывает
|
||
* @Sse-обработчик уже после отправки заголовков 200, и ошибка из него доходит
|
||
* до клиента только как `event: error`. Guard же отвечает честным 404.
|
||
*
|
||
* Сессию в request кладёт глобальный AuthGuard, который выполняется раньше.
|
||
*/
|
||
@Injectable()
|
||
export class IssueVisibleGuard implements CanActivate {
|
||
constructor(private readonly commentAccess: CommentAccessService) {}
|
||
|
||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||
const request = context
|
||
.switchToHttp()
|
||
.getRequest<{ params: Record<string, string | undefined>; session?: UserSession }>();
|
||
|
||
const userId = request.session?.user.id;
|
||
if (!userId) {
|
||
throw new UnauthorizedException();
|
||
}
|
||
|
||
// Пайпы (ParseIntPipe) выполняются после guard'ов — парсим сами
|
||
const issueId = Number(request.params.issueId);
|
||
if (!Number.isInteger(issueId)) {
|
||
throw new BadRequestException('Validation failed (numeric string is expected)');
|
||
}
|
||
|
||
if (!(await this.commentAccess.isIssueVisible(issueId, userId))) {
|
||
throw new NotFoundException(new IssueNotFoundException(issueId).message);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
}
|