import { Body, Controller, Delete, Get, Param, Post, Query, UseGuards, } from '@nestjs/common'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam, } from '@nestjs/swagger'; import { type JwtPayload, CurrentUser, JwtAuthGuard } from '@modules/auth'; import { CreateReviewCommand } from '../../application/commands/create-review/create-review.command'; import { type CreateReviewResult } from '../../application/commands/create-review/create-review.handler'; import { DeleteReviewCommand } from '../../application/commands/delete-review/delete-review.command'; import { GetAverageRatingQuery } from '../../application/queries/get-average-rating/get-average-rating.query'; import { GetReviewsByTargetQuery } from '../../application/queries/get-reviews-by-target/get-reviews-by-target.query'; import { GetReviewsByUserQuery } from '../../application/queries/get-reviews-by-user/get-reviews-by-user.query'; import { type ReviewItemData, type ReviewStatsData } from '../../domain/repositories/review-read.dto'; import { type PaginatedResult } from '../../domain/repositories/review.repository'; import { CreateReviewDto } from '../dto/create-review.dto'; import { ListReviewsByTargetDto, ReviewStatsDto } from '../dto/list-reviews.dto'; @ApiTags('reviews') @Controller('reviews') export class ReviewsController { constructor( private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, ) {} @ApiBearerAuth('JWT') @ApiOperation({ summary: 'Create a review' }) @ApiResponse({ status: 201, description: 'Review created successfully' }) @ApiResponse({ status: 400, description: 'Validation error' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 409, description: 'Already reviewed this target' }) @UseGuards(JwtAuthGuard) @Post() async createReview( @Body() dto: CreateReviewDto, @CurrentUser() user: JwtPayload, ): Promise { return this.commandBus.execute( new CreateReviewCommand( user.sub, dto.targetType, dto.targetId, dto.rating, dto.comment ?? null, ), ); } @ApiOperation({ summary: 'List reviews by target' }) @ApiResponse({ status: 200, description: 'Paginated list of reviews' }) @Get() async getReviewsByTarget( @Query() dto: ListReviewsByTargetDto, ): Promise> { return this.queryBus.execute( new GetReviewsByTargetQuery( dto.targetType, dto.targetId, dto.page ?? 1, dto.limit ?? 20, ), ); } @ApiOperation({ summary: 'Get aggregate rating stats for a target' }) @ApiResponse({ status: 200, description: 'Rating statistics' }) @Get('stats') async getStats( @Query() dto: ReviewStatsDto, ): Promise { return this.queryBus.execute( new GetAverageRatingQuery(dto.targetType, dto.targetId), ); } @ApiBearerAuth('JWT') @ApiOperation({ summary: 'Get reviews by authenticated user' }) @ApiResponse({ status: 200, description: 'Paginated list of user reviews' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @UseGuards(JwtAuthGuard) @Get('me') async getMyReviews( @CurrentUser() user: JwtPayload, @Query('page') page?: number, @Query('limit') limit?: number, ): Promise> { return this.queryBus.execute( new GetReviewsByUserQuery(user.sub, page ?? 1, limit ?? 20), ); } @ApiBearerAuth('JWT') @ApiOperation({ summary: 'Delete own review' }) @ApiParam({ name: 'id', description: 'Review ID' }) @ApiResponse({ status: 200, description: 'Review deleted' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 403, description: 'Cannot delete another user\'s review' }) @ApiResponse({ status: 404, description: 'Review not found' }) @UseGuards(JwtAuthGuard) @Delete(':id') async deleteReview( @Param('id') id: string, @CurrentUser() user: JwtPayload, ): Promise<{ deleted: boolean }> { await this.commandBus.execute(new DeleteReviewCommand(id, user.sub)); return { deleted: true }; } }