veza/docs/archive/root-md/FILE_UPLOAD_FORMAT.md
senke 43af35fd93 chore(audit 2.2, 2.3): nettoyer .md et .json à la racine
- 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
2026-02-15 14:35:08 +01:00

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 content
  • artist (string, optional): Artist name (for audio files)
  • album (string, optional): Album name (for audio files)
  • genre (string, optional): Genre
  • year (integer, optional): Year
  • description (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 track
  • is_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 ID
  • track_id (UUID, optional): Track ID (if applicable)
  • file_name (string): Original filename
  • file_size (int64): File size in bytes
  • file_type (string): File type ("audio", "image", "video")
  • mime_type (string): MIME type
  • checksum (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 far
  • url (string): Public URL (if available)
  • thumbnail_url (string, optional): Thumbnail URL (if applicable)
  • storage_path (string): Storage path
  • storage_provider (string): Storage provider ("s3", "local", etc.)
  • is_processed (bool): Whether file has been processed
  • processed_at (string, optional): Processing completion time (ISO 8601)
  • processing_error (string, optional): Processing error (if any)
  • virus_scanned (bool): Whether file was scanned
  • virus_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/mpeg
    • audio/mp3
    • audio/wav
    • audio/flac
    • audio/aac
    • audio/ogg
    • audio/m4a
  • Allowed Extensions: .mp3, .wav, .flac, .aac, .ogg, .m4a

Image Files

  • Max Size: 10MB (10,485,760 bytes)
  • Allowed MIME Types:
    • image/jpeg
    • image/png
    • image/gif
    • image/webp
    • image/svg+xml
  • Allowed Extensions: .jpg, .jpeg, .png, .gif, .webp, .svg

Video Files

  • Max Size: 500MB (524,288,000 bytes)
  • Allowed MIME Types:
    • video/mp4
    • video/webm
    • video/ogg
    • video/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

  1. Always Use Standard Types

    var req upload.StandardUploadRequest
    
  2. Validate File Size and Type

    if fileSize > maxSize {
        return upload.ErrorCodeFileTooLarge
    }
    
  3. Use Standard Error Codes

    errorCode := upload.ErrorCodeFileTooLarge
    
  4. Return Standard Response Format

    response := &upload.StandardUploadResponse{...}
    RespondSuccess(c, http.StatusCreated, response)
    

For Frontend Developers

  1. Use Standard Field Names

    formData.append('file', file);
    formData.append('title', title);
    
  2. Handle Progress Updates

    onUploadProgress: (progressEvent) => {
      const progress = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total,
      );
      onProgress(progress);
    }
    
  3. Handle Errors Properly

    if (error.response?.data?.error?.code === 'FILE_TOO_LARGE') {
      showError('File is too large');
    }
    
  4. 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 specification
  • ERROR_RESPONSE_STANDARD.md - Error format specification
  • REQUEST_RESPONSE_VALIDATION_GUIDE.md - Validation guide
  • veza-backend-api/internal/upload/types.go - Backend types
  • apps/web/src/types/upload.ts - Frontend types (to be created)

Last Updated: 2025-12-25
Maintained By: Veza Backend Team