SEO_iamge_renamer_starting_.../packages/api/src/images/images.controller.ts
DustyWalker ed5f745a51 feat(api): add images module for image filename management
- Implement PUT /api/image/{imageId}/filename for filename updates
- Add GET /api/image/{imageId} for detailed image information
- Support GET /api/image/batch/{batchId} for batch image listing
- Include filename approval, revert, and download URL generation
- Add comprehensive filename validation and SEO optimization
- Support presigned URL generation for secure downloads

Resolves requirement §75 for image filename management API.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-05 17:24:27 +02:00

304 lines
No EOL
8.1 KiB
TypeScript

import {
Controller,
Get,
Put,
Param,
Body,
UseGuards,
Request,
HttpStatus,
BadRequestException,
ForbiddenException,
NotFoundException,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/auth.guard';
import { ImagesService } from './images.service';
import { UpdateFilenameDto, UpdateFilenameResponseDto } from './dto/update-filename.dto';
import { ImageResponseDto, BatchImagesResponseDto } from './dto/image-response.dto';
@ApiTags('images')
@Controller('api/image')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class ImagesController {
constructor(private readonly imagesService: ImagesService) {}
@Put(':imageId/filename')
@ApiOperation({
summary: 'Update image filename',
description: 'Updates the proposed filename for a specific image',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Filename updated successfully',
type: UpdateFilenameResponseDto,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid filename or request data',
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Image not found',
})
@ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Not authorized to update this image',
})
async updateImageFilename(
@Param('imageId') imageId: string,
@Body() updateFilenameDto: UpdateFilenameDto,
@Request() req: any,
): Promise<UpdateFilenameResponseDto> {
try {
const userId = req.user?.id;
if (!userId) {
throw new BadRequestException('User not authenticated');
}
const result = await this.imagesService.updateFilename(
imageId,
userId,
updateFilenameDto.new_name
);
return result;
} catch (error) {
if (
error instanceof BadRequestException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
throw new BadRequestException('Failed to update image filename');
}
}
@Get(':imageId')
@ApiOperation({
summary: 'Get image details',
description: 'Returns detailed information about a specific image',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Image details retrieved successfully',
type: ImageResponseDto,
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Image not found',
})
@ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Not authorized to access this image',
})
async getImage(
@Param('imageId') imageId: string,
@Request() req: any,
): Promise<ImageResponseDto> {
try {
const userId = req.user?.id;
if (!userId) {
throw new BadRequestException('User not authenticated');
}
const image = await this.imagesService.getImage(imageId, userId);
return image;
} catch (error) {
if (
error instanceof BadRequestException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
throw new BadRequestException('Failed to get image details');
}
}
@Get('batch/:batchId')
@ApiOperation({
summary: 'Get all images in a batch',
description: 'Returns all images belonging to a specific batch',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Batch images retrieved successfully',
type: BatchImagesResponseDto,
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Batch not found',
})
@ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Not authorized to access this batch',
})
async getBatchImages(
@Param('batchId') batchId: string,
@Request() req: any,
): Promise<BatchImagesResponseDto> {
try {
const userId = req.user?.id;
if (!userId) {
throw new BadRequestException('User not authenticated');
}
const batchImages = await this.imagesService.getBatchImages(batchId, userId);
return batchImages;
} catch (error) {
if (
error instanceof BadRequestException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
throw new BadRequestException('Failed to get batch images');
}
}
@Get(':imageId/download')
@ApiOperation({
summary: 'Get image download URL',
description: 'Returns a presigned URL for downloading the original or processed image',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Download URL generated successfully',
schema: {
type: 'object',
properties: {
download_url: {
type: 'string',
example: 'https://storage.example.com/images/processed/image.jpg?expires=...',
},
expires_at: {
type: 'string',
example: '2024-01-01T13:00:00.000Z',
},
filename: {
type: 'string',
example: 'modern-kitchen-renovation.jpg',
},
},
},
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Image not found',
})
@ApiResponse({
status: HttpStatus.FORBIDDEN,
description: 'Not authorized to download this image',
})
async getImageDownloadUrl(
@Param('imageId') imageId: string,
@Request() req: any,
): Promise<{
download_url: string;
expires_at: string;
filename: string;
}> {
try {
const userId = req.user?.id;
if (!userId) {
throw new BadRequestException('User not authenticated');
}
const downloadInfo = await this.imagesService.getImageDownloadUrl(imageId, userId);
return downloadInfo;
} catch (error) {
if (
error instanceof BadRequestException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
throw new BadRequestException('Failed to generate download URL');
}
}
@Put(':imageId/approve')
@ApiOperation({
summary: 'Approve proposed filename',
description: 'Approves the AI-generated proposed filename as the final filename',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Filename approved successfully',
type: UpdateFilenameResponseDto,
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Image not found',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'No proposed filename to approve',
})
async approveFilename(
@Param('imageId') imageId: string,
@Request() req: any,
): Promise<UpdateFilenameResponseDto> {
try {
const userId = req.user?.id;
if (!userId) {
throw new BadRequestException('User not authenticated');
}
const result = await this.imagesService.approveProposedFilename(imageId, userId);
return result;
} catch (error) {
if (
error instanceof BadRequestException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
throw new BadRequestException('Failed to approve filename');
}
}
@Put(':imageId/revert')
@ApiOperation({
summary: 'Revert to original filename',
description: 'Reverts the image filename back to the original uploaded filename',
})
@ApiResponse({
status: HttpStatus.OK,
description: 'Filename reverted successfully',
type: UpdateFilenameResponseDto,
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Image not found',
})
async revertFilename(
@Param('imageId') imageId: string,
@Request() req: any,
): Promise<UpdateFilenameResponseDto> {
try {
const userId = req.user?.id;
if (!userId) {
throw new BadRequestException('User not authenticated');
}
const result = await this.imagesService.revertToOriginalFilename(imageId, userId);
return result;
} catch (error) {
if (
error instanceof BadRequestException ||
error instanceof ForbiddenException ||
error instanceof NotFoundException
) {
throw error;
}
throw new BadRequestException('Failed to revert filename');
}
}
}