Files
goodgo-platform/apps/api/src/modules/auth/presentation/controllers/user-data.controller.ts
Ho Ngoc Hai 312532b1cb fix(api): resolve NestJS DI + ValidationPipe bugs from type-only imports
- Remove `type` modifier from imports used as DI constructor params
  across ~235 files (@Injectable, @Controller, @Module, @Catch,
  @CommandHandler, @QueryHandler, @EventsHandler, @WebSocketGateway).
  TypeScript emitDecoratorMetadata strips type-only imports, leaving
  Reflect.metadata with Function placeholder and breaking Nest DI.
- Fix controllers: DTOs used with @Body/@Query/@Param must be runtime
  imports so ValidationPipe can whitelist properties. Previously
  returned 400 "property X should not exist" on every request.
- Register ProjectsModule in AppModule (was defined but never wired).
- Add approve()/reject() methods to TransferListingEntity referenced by
  ModerateTransferListingHandler.
- Export BankTransferConfirmedEvent from payments barrel for
  subscription activation handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:50:30 +07:00

88 lines
3.5 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
Param,
Post,
UseGuards,
} from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { CancelUserDeletionCommand } from '../../application/commands/cancel-user-deletion/cancel-user-deletion.command';
import { ExportUserDataCommand } from '../../application/commands/export-user-data/export-user-data.command';
import { type UserDataExport } from '../../application/commands/export-user-data/export-user-data.handler';
import { ForceDeleteUserCommand } from '../../application/commands/force-delete-user/force-delete-user.command';
import { RequestUserDeletionCommand } from '../../application/commands/request-user-deletion/request-user-deletion.command';
import { type JwtPayload } from '../../infrastructure/services/token.service';
import { CurrentUser } from '../decorators/current-user.decorator';
import { Roles } from '../decorators/roles.decorator';
import { ForceDeleteUserDto } from '../dto/force-delete-user.dto';
import { RequestDeletionDto } from '../dto/request-deletion.dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
@ApiTags('users')
@Controller('users')
export class UserDataController {
constructor(private readonly commandBus: CommandBus) {}
@Delete('me')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT')
@ApiOperation({ summary: 'Request account deletion (30-day grace period)' })
@ApiResponse({ status: 200, description: 'Deletion scheduled' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async requestDeletion(
@CurrentUser() user: JwtPayload,
@Body() dto: RequestDeletionDto,
): Promise<{ scheduledAt: Date; message: string }> {
const result = await this.commandBus.execute(
new RequestUserDeletionCommand(user.sub, dto.reason),
);
return { ...result, message: 'Tài khoản sẽ bị xóa sau 30 ngày' };
}
@Post('me/cancel-deletion')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT')
@ApiOperation({ summary: 'Cancel pending account deletion' })
@ApiResponse({ status: 201, description: 'Deletion cancelled' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async cancelDeletion(
@CurrentUser() user: JwtPayload,
): Promise<{ message: string }> {
return this.commandBus.execute(new CancelUserDeletionCommand(user.sub));
}
@Get('me/export')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT')
@ApiOperation({ summary: 'Export user data (GDPR Article 20)' })
@ApiResponse({ status: 200, description: 'User data exported as JSON' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async exportData(
@CurrentUser() user: JwtPayload,
): Promise<UserDataExport> {
return this.commandBus.execute(new ExportUserDataCommand(user.sub));
}
@Delete(':id/force')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN')
@ApiBearerAuth('JWT')
@ApiOperation({ summary: 'Force-delete user immediately (admin only)' })
@ApiResponse({ status: 200, description: 'User force-deleted' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 403, description: 'Forbidden — admin only' })
async forceDelete(
@Param('id') id: string,
@CurrentUser() admin: JwtPayload,
@Body() dto: ForceDeleteUserDto,
): Promise<{ message: string }> {
return this.commandBus.execute(
new ForceDeleteUserCommand(id, admin.sub, dto.reason),
);
}
}