Files
ocean-cleanup/server/FILE_UPLOAD_IMPLEMENTATION.md
Paul Bailey 18e1e03bfc feat: improve file upload authentication and error handling
- Add better error messages for authentication failures
- Display warning in ReportForm when user is not signed in
- Update documentation with authentication requirements
- Improve error handling in reportService.uploadPhoto()

This helps users understand they need to be signed in to upload photos.
2025-11-16 19:21:47 +00:00

6.2 KiB

File Upload and EXIF Processing Implementation

Overview

This document describes the implementation of file upload and EXIF metadata extraction for the Beach Litter Mapping Platform backend.

Components

1. Multer Middleware (src/middleware/upload.ts)

Handles file uploads to the local filesystem with the following features:

  • Storage Configuration: Files are stored in the ./uploads directory with unique filenames
  • File Naming: {fieldname}-{timestamp}-{random}.{ext} format to prevent collisions
  • File Type Validation: Only accepts JPEG, PNG, and WebP images
  • Size Limits: Maximum file size of 5MB (configurable via MAX_FILE_SIZE env var)
  • Auto-directory Creation: Creates upload directory if it doesn't exist

Configuration

const storage = multer.diskStorage({
  destination: './uploads',
  filename: 'photo-{timestamp}-{random}.{ext}'
});

const fileFilter = (file) => {
  allowedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']
};

const limits = {
  fileSize: 5242880 // 5MB
};

2. EXIF Extraction Utility (src/utils/exif.ts)

Extracts metadata from uploaded photos using the exifr library:

  • GPS Coordinates: Latitude and longitude from EXIF GPS data
  • Timestamp: Original photo capture time (DateTimeOriginal)
  • Camera Info: Make and model of the camera/device
  • Error Handling: Returns empty object if EXIF data is unavailable

Extracted Data

interface ExifData {
  latitude?: number;
  longitude?: number;
  timestamp?: Date;
  make?: string;
  model?: string;
}

3. Upload Endpoint (POST /api/reports/upload)

Protected endpoint for uploading individual photos:

  • Authentication: Requires valid Clerk session token
  • Single File Upload: Accepts one photo at a time via photo field
  • EXIF Processing: Automatically extracts metadata from uploaded photo
  • Response: Returns file URL and extracted EXIF data

Request

POST /api/reports/upload
Authorization: Bearer {clerk_token}
Content-Type: multipart/form-data

photo: {file}

Response

{
  "url": "/uploads/photo-1234567890-123456789.jpg",
  "exifData": {
    "latitude": 53.3498,
    "longitude": -6.2603,
    "timestamp": "2024-11-16T12:30:00.000Z"
  }
}

4. Static File Serving

Uploaded files are served via Express static middleware:

app.use('/uploads', express.static(path.join(__dirname, '../uploads')));

Files are accessible at: http://localhost:3005/uploads/{filename}

Usage Flow

1. Upload Photo

Important: The user must be signed in with Clerk to upload photos. The frontend automatically includes the Clerk session token in the Authorization header.

// Frontend usage (with Clerk authentication)
const formData = new FormData();
formData.append('photo', file);

// The reportService automatically includes the auth token
const { url, exifData } = await reportService.uploadPhoto(file);

Manual API call (for testing):

const formData = new FormData();
formData.append('photo', file);

// Get Clerk token (in a Clerk-authenticated component)
const { getToken } = useAuth();
const token = await getToken();

const response = await fetch('/api/reports/upload', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`
  },
  body: formData
});

const { url, exifData } = await response.json();

2. Create Report with Photo URLs

const report = await fetch('/api/reports', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${clerkToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    latitude: exifData.latitude || manualLat,
    longitude: exifData.longitude || manualLng,
    locationSource: exifData.latitude ? 'exif' : 'manual',
    photoUrls: [url],
    photoTimestamp: exifData.timestamp,
    litterType: 'plastic',
    quantity: 'moderate',
    description: 'Beach litter report'
  })
});

Error Handling

File Too Large

{
  "error": {
    "code": "FILE_TOO_LARGE",
    "message": "File size exceeds the maximum limit of 5MB",
    "timestamp": "2024-11-16T12:00:00.000Z"
  }
}

Invalid File Type

{
  "error": {
    "code": "INVALID_FILE_TYPE",
    "message": "Invalid file type. Only JPEG, PNG, and WebP images are allowed.",
    "timestamp": "2024-11-16T12:00:00.000Z"
  }
}

No File Uploaded

{
  "error": {
    "code": "NO_FILE",
    "message": "No file uploaded",
    "timestamp": "2024-11-16T12:00:00.000Z"
  }
}

Environment Variables

# File Upload Configuration
UPLOAD_DIR=./uploads              # Directory for uploaded files
MAX_FILE_SIZE=5242880             # Maximum file size in bytes (5MB)

Security Considerations

  1. File Type Validation: Only image MIME types are accepted
  2. Size Limits: Prevents large file uploads that could exhaust storage
  3. Authentication Required: All uploads require valid Clerk session
  4. Unique Filenames: Prevents file overwrites and collisions
  5. EXIF Stripping: Consider stripping sensitive EXIF data before public display (future enhancement)

Testing

Run the test script to verify the implementation:

npm run test:upload
# or
npx tsx src/scripts/testFileUpload.ts

The test verifies:

  • ✅ Uploads directory exists
  • ✅ EXIF extraction works correctly
  • ✅ Configuration is properly set
  • ✅ Multer middleware loads successfully

Requirements Satisfied

This implementation satisfies the following requirements:

  • 1.2: Extract GPS coordinates from EXIF metadata
  • 1.3: Prompt for location when EXIF GPS data is unavailable
  • 1.4: Accept at least one photograph with minimum resolution
  • 1.5: Extract and display original photo capture time from EXIF

Future Enhancements

  1. Cloud Storage: Migrate from local filesystem to AWS S3 or Cloudinary
  2. Image Optimization: Resize and compress images on upload
  3. Multiple File Upload: Support batch photo uploads
  4. EXIF Privacy: Strip sensitive EXIF data (location, device info) before public display
  5. Virus Scanning: Integrate antivirus scanning for uploaded files
  6. CDN Integration: Serve images via CDN for better performance