import { Controller, Post, Get, Param, Body, UploadedFiles, UseInterceptors, UseGuards, Request, HttpStatus, BadRequestException, PayloadTooLargeException, ForbiddenException, } from '@nestjs/common'; import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiTags, ApiOperation, ApiResponse, ApiConsumes, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/auth.guard'; import { BatchesService } from './batches.service'; import { CreateBatchDto, BatchUploadResponseDto } from './dto/create-batch.dto'; import { BatchStatusResponseDto, BatchListResponseDto } from './dto/batch-status.dto'; @ApiTags('batches') @Controller('api/batch') @UseGuards(JwtAuthGuard) @ApiBearerAuth() export class BatchesController { constructor(private readonly batchesService: BatchesService) {} @Post() @UseInterceptors(FilesInterceptor('files', 1000)) // Max 1000 files per batch @ApiOperation({ summary: 'Upload batch of images for processing', description: 'Uploads multiple images and starts batch processing with AI analysis and SEO filename generation' }) @ApiConsumes('multipart/form-data') @ApiResponse({ status: HttpStatus.OK, description: 'Batch created successfully', type: BatchUploadResponseDto, }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid files or missing data', }) @ApiResponse({ status: HttpStatus.PAYLOAD_TOO_LARGE, description: 'File size or count exceeds limits', }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Insufficient quota remaining', }) async uploadBatch( @UploadedFiles() files: Express.Multer.File[], @Body() createBatchDto: CreateBatchDto, @Request() req: any, ): Promise { try { const userId = req.user?.id; if (!userId) { throw new BadRequestException('User not authenticated'); } // Validate files are provided if (!files || files.length === 0) { throw new BadRequestException('No files provided'); } // Check file count limits if (files.length > 1000) { throw new PayloadTooLargeException('Maximum 1000 files per batch'); } // Process the batch upload const result = await this.batchesService.createBatch(userId, files, createBatchDto); return result; } catch (error) { if (error instanceof BadRequestException || error instanceof PayloadTooLargeException || error instanceof ForbiddenException) { throw error; } throw new BadRequestException('Failed to process batch upload'); } } @Get(':batchId/status') @ApiOperation({ summary: 'Get batch processing status', description: 'Returns current status and progress of batch processing' }) @ApiResponse({ status: HttpStatus.OK, description: 'Batch status retrieved successfully', type: BatchStatusResponseDto, }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Batch not found', }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Not authorized to access this batch', }) async getBatchStatus( @Param('batchId') batchId: string, @Request() req: any, ): Promise { try { const userId = req.user?.id; if (!userId) { throw new BadRequestException('User not authenticated'); } const status = await this.batchesService.getBatchStatus(batchId, userId); return status; } catch (error) { if (error instanceof BadRequestException || error instanceof ForbiddenException) { throw error; } throw new BadRequestException('Failed to get batch status'); } } @Get() @ApiOperation({ summary: 'List user batches', description: 'Returns list of all batches for the authenticated user' }) @ApiResponse({ status: HttpStatus.OK, description: 'Batches retrieved successfully', type: [BatchListResponseDto], }) async getUserBatches( @Request() req: any, ): Promise { try { const userId = req.user?.id; if (!userId) { throw new BadRequestException('User not authenticated'); } const batches = await this.batchesService.getUserBatches(userId); return batches; } catch (error) { throw new BadRequestException('Failed to get user batches'); } } @Post(':batchId/cancel') @ApiOperation({ summary: 'Cancel batch processing', description: 'Cancels ongoing batch processing' }) @ApiResponse({ status: HttpStatus.OK, description: 'Batch cancelled successfully', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Batch not found', }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Not authorized to cancel this batch', }) async cancelBatch( @Param('batchId') batchId: string, @Request() req: any, ): Promise<{ message: string }> { try { const userId = req.user?.id; if (!userId) { throw new BadRequestException('User not authenticated'); } await this.batchesService.cancelBatch(batchId, userId); return { message: 'Batch cancelled successfully' }; } catch (error) { if (error instanceof BadRequestException || error instanceof ForbiddenException) { throw error; } throw new BadRequestException('Failed to cancel batch'); } } @Post(':batchId/retry') @ApiOperation({ summary: 'Retry failed batch processing', description: 'Retries processing for failed images in a batch' }) @ApiResponse({ status: HttpStatus.OK, description: 'Batch retry started successfully', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Batch not found', }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Batch is not in a retryable state', }) async retryBatch( @Param('batchId') batchId: string, @Request() req: any, ): Promise<{ message: string; retry_count: number }> { try { const userId = req.user?.id; if (!userId) { throw new BadRequestException('User not authenticated'); } const retryCount = await this.batchesService.retryBatch(batchId, userId); return { message: 'Batch retry started successfully', retry_count: retryCount }; } catch (error) { if (error instanceof BadRequestException || error instanceof ForbiddenException) { throw error; } throw new BadRequestException('Failed to retry batch'); } } @Get(':batchId/download') @ApiOperation({ summary: 'Download processed batch as ZIP', description: 'Returns a ZIP file containing all processed images with new filenames' }) @ApiResponse({ status: HttpStatus.OK, description: 'ZIP file download started', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Batch not found', }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Batch processing not completed', }) async downloadBatch( @Param('batchId') batchId: string, @Request() req: any, ): Promise<{ download_url: string; expires_at: string }> { try { const userId = req.user?.id; if (!userId) { throw new BadRequestException('User not authenticated'); } const downloadInfo = await this.batchesService.generateBatchDownload(batchId, userId); return downloadInfo; } catch (error) { if (error instanceof BadRequestException || error instanceof ForbiddenException) { throw error; } throw new BadRequestException('Failed to generate batch download'); } } }