- Archiver 131 .md dans docs/archive/root-md/ - Archiver 22 .json dans docs/archive/root-json/ - Conserver 7 .md utiles (README, CONTRIBUTING, CHANGELOG, etc.) - Conserver package.json, package-lock.json, turbo.json - Ajouter README d'index dans chaque archive
12 KiB
File Upload Format Standardization
INT-015: Add file upload format standardization
Date: 2025-12-25
Status: Completed
Overview
This document defines the standardized format for all file uploads in the Veza platform. It ensures consistency between backend and frontend, making upload handling predictable and maintainable.
Standard Upload Request Format
All file uploads use multipart/form-data with the following standardized field names:
Required Fields
file(file, required): The actual file being uploaded
Optional Metadata Fields
title(string, optional): Title of the contentartist(string, optional): Artist name (for audio files)album(string, optional): Album name (for audio files)genre(string, optional): Genreyear(integer, optional): Yeardescription(string, optional): Description
File Type and Context Fields
file_type(string, optional): Type of file -"audio","image", or"video"(auto-detected if not provided)track_id(UUID, optional): Track ID if updating an existing trackis_public(boolean, optional): Public visibility (default:false)metadata(string, optional): JSON string with additional metadata
Example Request
const formData = new FormData();
formData.append('file', file);
formData.append('title', 'My Track');
formData.append('artist', 'Artist Name');
formData.append('album', 'Album Name');
formData.append('genre', 'Electronic');
formData.append('year', '2025');
formData.append('is_public', 'true');
await apiClient.post('/tracks', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
Standard Upload Response Format
All upload responses follow this standardized structure:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"track_id": "660e8400-e29b-41d4-a716-446655440001",
"file_name": "track.mp3",
"file_size": 5242880,
"file_type": "audio",
"mime_type": "audio/mpeg",
"checksum": "sha256:abc123...",
"status": "completed",
"progress": 100,
"bytes_uploaded": 5242880,
"url": "https://cdn.example.com/tracks/550e8400...",
"thumbnail_url": "https://cdn.example.com/thumbnails/550e8400...",
"storage_path": "tracks/2025/12/550e8400...",
"storage_provider": "s3",
"is_processed": true,
"processed_at": "2025-12-25T10:30:00Z",
"processing_error": null,
"virus_scanned": true,
"virus_scan_result": "clean",
"virus_scanned_at": "2025-12-25T10:29:45Z",
"created_at": "2025-12-25T10:29:30Z",
"updated_at": "2025-12-25T10:30:00Z",
"expires_at": null
}
}
Response Fields
id(UUID): Upload IDtrack_id(UUID, optional): Track ID (if applicable)file_name(string): Original filenamefile_size(int64): File size in bytesfile_type(string): File type ("audio","image","video")mime_type(string): MIME typechecksum(string): File checksum (SHA-256)status(string): Upload status ("pending","uploading","processing","completed","failed","cancelled")progress(int): Progress percentage (0-100)bytes_uploaded(int64): Bytes uploaded so farurl(string): Public URL (if available)thumbnail_url(string, optional): Thumbnail URL (if applicable)storage_path(string): Storage pathstorage_provider(string): Storage provider ("s3","local", etc.)is_processed(bool): Whether file has been processedprocessed_at(string, optional): Processing completion time (ISO 8601)processing_error(string, optional): Processing error (if any)virus_scanned(bool): Whether file was scannedvirus_scan_result(string, optional): Scan result ("clean","infected","error")virus_scanned_at(string, optional): Scan timestamp (ISO 8601)created_at(string): Upload creation time (ISO 8601)updated_at(string): Last update time (ISO 8601)expires_at(string, optional): Expiration time (ISO 8601, if applicable)
Upload Status Values
| Status | Description |
|---|---|
pending |
Upload queued, not started |
uploading |
File is being uploaded |
processing |
File is being processed (transcoding, etc.) |
completed |
Upload and processing completed successfully |
failed |
Upload or processing failed |
cancelled |
Upload was cancelled |
Error Response Format
Upload errors follow the standard error response format:
{
"success": false,
"error": {
"code": "FILE_TOO_LARGE",
"message": "File size exceeds maximum allowed size of 100MB",
"details": {
"file_size": 104857600,
"max_size": 100000000,
"field": "file"
}
}
}
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
FILE_REQUIRED |
400 | No file provided |
FILE_TOO_LARGE |
413 | File size exceeds maximum |
INVALID_FILE_TYPE |
415 | File type not supported |
INVALID_FILE_FORMAT |
400 | File format is invalid |
VIRUS_DETECTED |
422 | Virus detected in file |
VIRUS_SCAN_FAILED |
503 | Virus scan failed |
VIRUS_SCAN_UNAVAILABLE |
503 | Virus scanning service unavailable |
QUOTA_EXCEEDED |
403 | Upload quota exceeded |
UPLOAD_FAILED |
500 | Upload failed |
PROCESSING_FAILED |
500 | Processing failed |
TOO_MANY_CONCURRENT_UPLOADS |
503 | Too many concurrent uploads |
INVALID_METADATA |
400 | Invalid metadata format |
File Type Limits
Audio Files
- Max Size: 100MB (104,857,600 bytes)
- Allowed MIME Types:
audio/mpegaudio/mp3audio/wavaudio/flacaudio/aacaudio/oggaudio/m4a
- Allowed Extensions:
.mp3,.wav,.flac,.aac,.ogg,.m4a
Image Files
- Max Size: 10MB (10,485,760 bytes)
- Allowed MIME Types:
image/jpegimage/pngimage/gifimage/webpimage/svg+xml
- Allowed Extensions:
.jpg,.jpeg,.png,.gif,.webp,.svg
Video Files
- Max Size: 500MB (524,288,000 bytes)
- Allowed MIME Types:
video/mp4video/webmvideo/oggvideo/avi
- Allowed Extensions:
.mp4,.webm,.ogg,.avi
Upload Progress
For long-running uploads, progress can be tracked:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "uploading",
"progress": 45,
"bytes_uploaded": 2359296,
"total_bytes": 5242880,
"estimated_time_remaining": 30,
"message": "Uploading...",
"updated_at": "2025-12-25T10:30:00Z"
}
Batch Upload
For multiple file uploads:
{
"total_files": 5,
"successful": 4,
"failed": 1,
"results": [
{
"index": 1,
"file_name": "track1.mp3",
"file_size": 5242880,
"file_type": "audio",
"status": "completed",
"upload_id": "550e8400-e29b-41d4-a716-446655440000"
},
{
"index": 2,
"file_name": "track2.mp3",
"file_size": 10485760,
"file_type": "audio",
"status": "failed",
"error": "File too large"
}
],
"errors": []
}
Backend Implementation
Using Standard Types
import "veza-backend-api/internal/upload"
// Parse upload request
var req upload.StandardUploadRequest
if err := c.ShouldBind(&req); err != nil {
RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, err.Error()))
return
}
// Create response
response := &upload.StandardUploadResponse{
ID: uploadID,
FileName: fileHeader.Filename,
FileSize: fileSize,
FileType: "audio",
Status: upload.UploadStatusCompleted,
CreatedAt: time.Now(),
}
RespondSuccess(c, http.StatusCreated, response)
Error Handling
// Return standardized error
RespondWithAppError(c, apperrors.New(
apperrors.ErrCodeValidation,
"File size exceeds maximum allowed size",
map[string]interface{}{
"code": upload.ErrorCodeFileTooLarge,
"file_size": fileSize,
"max_size": maxSize,
},
))
Frontend Implementation
Upload Request
import { apiClient } from '@/services/api/client';
interface UploadMetadata {
title?: string;
artist?: string;
album?: string;
genre?: string;
year?: number;
description?: string;
is_public?: boolean;
}
async function uploadFile(
file: File,
metadata: UploadMetadata = {},
onProgress?: (progress: number) => void,
): Promise<StandardUploadResponse> {
const formData = new FormData();
formData.append('file', file);
if (metadata.title) formData.append('title', metadata.title);
if (metadata.artist) formData.append('artist', metadata.artist);
if (metadata.album) formData.append('album', metadata.album);
if (metadata.genre) formData.append('genre', metadata.genre);
if (metadata.year) formData.append('year', metadata.year.toString());
if (metadata.description) formData.append('description', metadata.description);
if (metadata.is_public !== undefined) {
formData.append('is_public', metadata.is_public.toString());
}
const response = await apiClient.post<StandardUploadResponse>(
'/tracks',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total && onProgress) {
const progress = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress(progress);
}
},
},
);
return response.data;
}
Type Definitions
interface StandardUploadResponse {
id: string;
track_id?: string;
file_name: string;
file_size: number;
file_type: 'audio' | 'image' | 'video';
mime_type: string;
checksum: string;
status: 'pending' | 'uploading' | 'processing' | 'completed' | 'failed' | 'cancelled';
progress: number;
bytes_uploaded: number;
url: string;
thumbnail_url?: string;
storage_path: string;
storage_provider: string;
is_processed: boolean;
processed_at?: string;
processing_error?: string;
virus_scanned: boolean;
virus_scan_result?: 'clean' | 'infected' | 'error';
virus_scanned_at?: string;
created_at: string;
updated_at: string;
expires_at?: string;
}
Best Practices
For Backend Developers
-
Always Use Standard Types
var req upload.StandardUploadRequest -
Validate File Size and Type
if fileSize > maxSize { return upload.ErrorCodeFileTooLarge } -
Use Standard Error Codes
errorCode := upload.ErrorCodeFileTooLarge -
Return Standard Response Format
response := &upload.StandardUploadResponse{...} RespondSuccess(c, http.StatusCreated, response)
For Frontend Developers
-
Use Standard Field Names
formData.append('file', file); formData.append('title', title); -
Handle Progress Updates
onUploadProgress: (progressEvent) => { const progress = Math.round( (progressEvent.loaded * 100) / progressEvent.total, ); onProgress(progress); } -
Handle Errors Properly
if (error.response?.data?.error?.code === 'FILE_TOO_LARGE') { showError('File is too large'); } -
Validate Before Upload
if (file.size > maxSize) { throw new Error('File too large'); }
Migration Guide
Legacy Format (Deprecated)
// Old format
type UploadRequest struct {
TrackID uuid.UUID `form:"track_id"`
FileType string `form:"file_type"`
Title string `form:"title"`
}
Standardized Format
// New format
import "veza-backend-api/internal/upload"
var req upload.StandardUploadRequest
References
DATETIME_STANDARD.md- Date/time format specificationERROR_RESPONSE_STANDARD.md- Error format specificationREQUEST_RESPONSE_VALIDATION_GUIDE.md- Validation guideveza-backend-api/internal/upload/types.go- Backend typesapps/web/src/types/upload.ts- Frontend types (to be created)
Last Updated: 2025-12-25
Maintained By: Veza Backend Team