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

243 lines
6.2 KiB
Markdown

# 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
```typescript
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
```typescript
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
```bash
POST /api/reports/upload
Authorization: Bearer {clerk_token}
Content-Type: multipart/form-data
photo: {file}
```
#### Response
```json
{
"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:
```typescript
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.
```typescript
// 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):
```typescript
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
```typescript
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
```json
{
"error": {
"code": "FILE_TOO_LARGE",
"message": "File size exceeds the maximum limit of 5MB",
"timestamp": "2024-11-16T12:00:00.000Z"
}
}
```
### Invalid File Type
```json
{
"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
```json
{
"error": {
"code": "NO_FILE",
"message": "No file uploaded",
"timestamp": "2024-11-16T12:00:00.000Z"
}
}
```
## Environment Variables
```env
# 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:
```bash
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