Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/4rt21/backend-proyecto/llms.txt

Use this file to discover all available pages before exploring further.

Overview

FalconAlert supports file uploads for:
  • Profile pictures - User avatar images
  • Report pictures - Evidence images for fraud reports
Images are stored locally in the public/ directory and served as static assets.

Upload Configuration

The file upload system is implemented in src/images/ using NestJS’s built-in file handling with Multer.

Images Service

The core upload logic is in src/images/images.service.ts:
src/images/images.service.ts
import { Injectable } from '@nestjs/common';
import fs from 'node:fs';
import path from 'node:path';
import { randomName } from 'src/util/crypto/hash.util';

@Injectable()
export class ImagesService {
  async uploadFile(
    file: Express.Multer.File,
    folder: string,
  ): Promise<Record<string, string>> {
    const projectFolder = path.join(__dirname, '..', '..', 'public', folder);
    await fs.promises.mkdir(projectFolder, { recursive: true });

    const key = randomName();
    const ext = path.extname(file.originalname) || '.jpg';
    const filePath = path.join(projectFolder, `${key}${ext}`);

    await fs.promises.writeFile(filePath, file.buffer);
    return { path: path.join(folder, `${key}${ext}`) };
  }

  async deleteFile(filePath: string): Promise<void> {
    const fullPath = path.join(__dirname, '..', '..', 'public', filePath);

    try {
      await fs.promises.unlink(fullPath);
    } catch (err) {
      if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
        console.warn(`File not found: ${fullPath}`);
        return;
      }
      throw err;
    }
  }

  async modifyFile(
    file: Express.Multer.File,
    filePath: string,
  ): Promise<Record<string, string>> {
    if (filePath === 'profile-pictures/default.jpg') {
      const folder = path.dirname(filePath);
      const newFilePath = await this.uploadFile(file, folder);
      return newFilePath;
    }
    await this.deleteFile(filePath);
    const folder = path.dirname(filePath);
    const newFilePath = await this.uploadFile(file, folder);
    return newFilePath;
  }
}

Key Features

Secure File Names

Files are renamed using cryptographic hashing to prevent collisions and security issues.

Directory Management

Automatically creates directories if they don’t exist using recursive: true.

Protected Defaults

Default profile pictures are protected from deletion.

Error Handling

Gracefully handles missing files and other errors.

Upload Endpoints

The ImagesController provides three main endpoints:

Upload Image

POST /images/:folder
src/images/images.controller.ts
@Post(':folder')
@UseInterceptors(FileInterceptor('file'))
@ApiOperation({ summary: 'Subir una imagen a una carpeta' })
@ApiConsumes('multipart/form-data')
async uploadFile(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        new MaxFileSizeValidator({ maxSize: 1024 * 1024 }),
        new FileTypeValidator({ fileType: 'image/*' }),
      ],
    }),
  )
  file: Express.Multer.File,
  @Param('folder') folder: string,
) {
  return this.imagesService.uploadFile(file, folder);
}
Validation Rules:
  • Maximum file size: 1 MB (1024 * 1024 bytes)
  • File type: image/ only*
  • Folder parameter required in URL
Request Example:
curl -X POST http://localhost:3000/images/profile-pictures \
  -H "Content-Type: multipart/form-data" \
  -F "file=@avatar.jpg"
Response:
{
  "path": "profile-pictures/5211a4b631c9dba76cf21c50c4bf3552f8c53548.jpg"
}

Modify Image

PUT /images/:folder/:path
src/images/images.controller.ts
@Put(':folder/:path')
@UseInterceptors(FileInterceptor('file'))
async modifyFile(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        new MaxFileSizeValidator({ maxSize: 10240 * 10240 }),
        new FileTypeValidator({ fileType: 'image/*' }),
      ],
    }),
  )
  file: Express.Multer.File,
  @Param('folder') folder: string,
  @Param('path') path: string,
) {
  const filepath = `${folder}/${path}`;
  return this.imagesService.modifyFile(file, filepath);
}
Validation Rules:
  • Maximum file size: 100 MB (10240 * 10240 bytes)
  • File type: image/ only*

Delete Image

DELETE /images
src/images/images.controller.ts
@Delete()
async deleteFile(@Body('path') path: string) {
  return this.imagesService.deleteFile(path);
}
Request Example:
curl -X DELETE http://localhost:3000/images \
  -H "Content-Type: application/json" \
  -d '{"path": "report-pictures/abc123.jpg"}'

Storage Directories

Images are stored in the following structure:
public/
├── profile-pictures/
│   ├── default.jpg
│   ├── 5211a4b6...f8c53548.png
│   └── a8f3c2d1...9e4b7a3c.jpg
└── report-pictures/
    ├── 0512ec54...c17335b8.jpg
    ├── 57c0a3a4...8ff743a5.jpg
    └── b9237d0f...324ea742.jpg

Static File Serving

The public/ directory is configured as a static asset directory in src/main.ts:
src/main.ts
const publicPath = path.join(__dirname, '..', 'public');
app.useStaticAssets(publicPath);
This allows uploaded images to be accessed directly:
http://localhost:3000/profile-pictures/5211a4b6...f8c53548.png
http://localhost:3000/report-pictures/abc123.jpg

File Name Generation

Files are renamed using a cryptographic hash function (randomName()) from src/util/crypto/hash.util:
  • Generates unique, collision-resistant file names
  • Prevents directory traversal attacks
  • Maintains file extension from original upload
  • Example output: 5211a4b631c9dba76cf21c50c4bf3552f8c53548.jpg

AWS S3 Integration

The project includes AWS S3 SDK dependencies:
package.json
{
  "dependencies": {
    "@aws-sdk/client-s3": "^3.891.0",
    "@aws-sdk/s3-request-presigner": "^3.893.0"
  }
}
To enable S3 uploads:
  1. Add environment variables:
    .env
    AWS_ACCESS_KEY_ID=your_access_key
    AWS_SECRET_ACCESS_KEY=your_secret_key
    AWS_REGION=us-east-1
    AWS_S3_BUCKET=your-bucket-name
    
  2. Update ImagesService:
    import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
    
    const s3Client = new S3Client({
      region: process.env.AWS_REGION,
      credentials: {
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
      },
    });
    
    async uploadToS3(file: Express.Multer.File, key: string) {
      const command = new PutObjectCommand({
        Bucket: process.env.AWS_S3_BUCKET,
        Key: key,
        Body: file.buffer,
        ContentType: file.mimetype,
      });
    
      await s3Client.send(command);
      return {
        url: `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`
      };
    }
    

Best Practices

Always validate file sizes to prevent abuse:
  • Profile pictures: 1 MB max
  • Report evidence: 10-100 MB max
  • Adjust based on your use case
Only accept image files using FileTypeValidator:
  • Prevents malicious file uploads
  • Ensures consistent file handling
  • Use image/* or specific MIME types
For production deployments:
  • Use cloud storage (S3, Google Cloud Storage)
  • Implement CDN for faster delivery
  • Set up backup strategies
  • Monitor storage usage
  • Never trust user-provided file names
  • Always generate new file names
  • Validate file contents, not just extensions
  • Implement rate limiting on upload endpoints

Error Handling

Common upload errors:
// File too large
{
  "statusCode": 400,
  "message": "File size exceeds maximum limit"
}

// Invalid file type
{
  "statusCode": 400,
  "message": "File type must be image/*"
}

// Missing file
{
  "statusCode": 400,
  "message": "File is required"
}
Test file uploads using the Swagger UI at /docs for an interactive experience.

Build docs developers (and LLMs) love