import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query, UseGuards, } from '@nestjs/common'; import { ApiBearerAuth, ApiTags, ApiQuery } from '@nestjs/swagger'; import { Roles } from '~/auth/enums'; import { IJwtPayload } from '~/auth/interfaces'; import { AllowedRoles, AuthenticatedUser, Public } from '~/common/decorators'; import { RolesGuard } from '~/common/guards'; import { ApiDataPageResponse, ApiDataResponse, ApiLangRequestHeader } from '~/core/decorators'; import { PageOptionsRequestDto } from '~/core/dtos'; import { CustomParseUUIDPipe } from '~/core/pipes'; import { ResponseFactory } from '~/core/utils'; import { CreateJuniorRequestDto, SetThemeRequestDto, TransferToJuniorRequestDto, UpdateJuniorRequestDto, } from '../dtos/request'; import { JuniorResponseDto, QrCodeValidationResponseDto, ThemeResponseDto, TransferToJuniorResponseDto, } from '../dtos/response'; import { WeeklySummaryResponseDto } from '../dtos/response/weekly-summary.response.dto'; import { JuniorHomeResponseDto, PagedChildTransfersResponseDto } from '~/card/dtos/responses'; import { JuniorService } from '../services'; @Controller('juniors') @ApiTags('Juniors') @ApiBearerAuth() @ApiLangRequestHeader() export class JuniorController { constructor(private readonly juniorService: JuniorService) {} @Post() @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataResponse(JuniorResponseDto) async createJunior(@Body() body: CreateJuniorRequestDto, @AuthenticatedUser() user: IJwtPayload) { const token = await this.juniorService.createJuniors(body, user.sub); return ResponseFactory.data(token); } @Get() @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataPageResponse(JuniorResponseDto) async findJuniors(@AuthenticatedUser() user: IJwtPayload, @Query() pageOptions: PageOptionsRequestDto) { const [juniors, count] = await this.juniorService.findJuniorsByGuardianId(user.sub, pageOptions); return ResponseFactory.dataPage( juniors.map((juniors) => new JuniorResponseDto(juniors)), { page: pageOptions.page, size: pageOptions.size, itemCount: count, }, ); } @Get(':juniorId') @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataResponse(JuniorResponseDto) async findJuniorById( @AuthenticatedUser() user: IJwtPayload, @Param('juniorId', CustomParseUUIDPipe) juniorId: string, ) { const junior = await this.juniorService.findJuniorById(juniorId, false, user.sub); return ResponseFactory.data(new JuniorResponseDto(junior)); } @Patch(':juniorId') @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataResponse(JuniorResponseDto) async updateJunior( @AuthenticatedUser() user: IJwtPayload, @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @Body() body: UpdateJuniorRequestDto, ) { const junior = await this.juniorService.updateJunior(juniorId, body, user.sub); return ResponseFactory.data(new JuniorResponseDto(junior)); } @Delete(':juniorId') @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @HttpCode(HttpStatus.NO_CONTENT) async deleteJunior(@AuthenticatedUser() user: IJwtPayload, @Param('juniorId', CustomParseUUIDPipe) juniorId: string) { await this.juniorService.deleteJunior(juniorId, user.sub); } @Post('set-theme') @UseGuards(RolesGuard) @AllowedRoles(Roles.JUNIOR) @ApiDataResponse(JuniorResponseDto) async setTheme(@Body() body: SetThemeRequestDto, @AuthenticatedUser() user: IJwtPayload) { const theme = await this.juniorService.setTheme(body, user.sub); return ResponseFactory.data(new ThemeResponseDto(theme)); } @Get(':juniorId/qr-code') @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataResponse('string') async generateQrCode(@Param('juniorId', CustomParseUUIDPipe) juniorId: string) { const qrCode = await this.juniorService.generateToken(juniorId); return ResponseFactory.data(qrCode); } @Get('qr-code/:token/validate') @Public() @ApiDataResponse(QrCodeValidationResponseDto) async validateToken(@Param('token') token: string) { const junior = await this.juniorService.validateToken(token); return ResponseFactory.data(new QrCodeValidationResponseDto(junior)); } @Post(':juniorId/transfer') @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataResponse(TransferToJuniorResponseDto) async transferToJunior( @AuthenticatedUser() user: IJwtPayload, @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @Body() body: TransferToJuniorRequestDto, ) { const newAmount = await this.juniorService.transferToJunior(juniorId, body, user.sub); return ResponseFactory.data(new TransferToJuniorResponseDto(newAmount)); } @Get(':juniorId/weekly-summary') @UseGuards(RolesGuard) @AllowedRoles(Roles.GUARDIAN) @ApiDataResponse(WeeklySummaryResponseDto) @ApiQuery({ name: 'startUtc', required: false, type: String, example: '2025-10-20T00:00:00.000Z', description: 'Start date (defaults to start of current week)' }) @ApiQuery({ name: 'endUtc', required: false, type: String, example: '2025-10-26T23:59:59.999Z', description: 'End date (defaults to end of current week)' }) async getWeeklySummary( @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @AuthenticatedUser() user: IJwtPayload, @Query('startUtc') startUtc?: string, @Query('endUtc') endUtc?: string, ) { const startDate = startUtc ? new Date(startUtc) : undefined; const endDate = endUtc ? new Date(endUtc) : undefined; const summary = await this.juniorService.getWeeklySummary(juniorId, user.sub, startDate, endDate); return ResponseFactory.data(summary); } @Get(':juniorId/home') @UseGuards(RolesGuard) @AllowedRoles(Roles.JUNIOR, Roles.GUARDIAN) @ApiQuery({ name: 'size', required: false, type: Number, example: 5 }) @ApiDataResponse(JuniorHomeResponseDto) async getJuniorHome( @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @AuthenticatedUser() user: IJwtPayload, @Query('size') size?: number, ) { const limit = Math.max(1, Math.min(Number(size) || 5, 20)); const res = await this.juniorService.getJuniorHome(juniorId, user.sub, limit); return ResponseFactory.data(res); } @Get(':juniorId/transfers') @UseGuards(RolesGuard) @AllowedRoles(Roles.JUNIOR, Roles.GUARDIAN) @ApiQuery({ name: 'page', required: false, type: Number, example: 1 }) @ApiQuery({ name: 'size', required: false, type: Number, example: 10 }) @ApiDataResponse(PagedChildTransfersResponseDto) async getJuniorTransfers( @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @AuthenticatedUser() user: IJwtPayload, @Query('page') page?: number, @Query('size') size?: number, ) { const pageNum = Math.max(1, Number(page) || 1); const pageSize = Math.max(1, Math.min(Number(size) || 10, 50)); const res = await this.juniorService.getJuniorTransfers(juniorId, user.sub, pageNum, pageSize); return ResponseFactory.data(res); } @Get(':juniorId/spending-history') @UseGuards(RolesGuard) @AllowedRoles(Roles.JUNIOR, Roles.GUARDIAN) @ApiQuery({ name: 'startUtc', required: true, type: String, example: '2025-01-01T00:00:00.000Z' }) @ApiQuery({ name: 'endUtc', required: true, type: String, example: '2025-01-31T23:59:59.999Z' }) async getSpendingHistory( @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @AuthenticatedUser() user: IJwtPayload, @Query('startUtc') startUtc: string, @Query('endUtc') endUtc: string, ) { const res = await this.juniorService.getSpendingHistory(juniorId, user.sub, new Date(startUtc), new Date(endUtc)); return ResponseFactory.data(res); } @Get(':juniorId/transactions/:transactionId') @UseGuards(RolesGuard) @AllowedRoles(Roles.JUNIOR, Roles.GUARDIAN) async getTransactionDetail( @Param('juniorId', CustomParseUUIDPipe) juniorId: string, @Param('transactionId', CustomParseUUIDPipe) transactionId: string, @AuthenticatedUser() user: IJwtPayload, ) { const res = await this.juniorService.getTransactionDetail(juniorId, user.sub, transactionId); return ResponseFactory.data(res); } }