# Upload I/O Asynchrone — Documentation

**Date**: 2025-01-27  
**Status**: ✅ **IMPLEMENTED** - MOD-P2-008

---

## Vue d'ensemble

L'upload de fichiers audio utilise maintenant une **sémantique asynchrone** avec réponse `202 Accepted`. La copie fichier (`io.Copy`) se fait en arrière-plan dans une goroutine suivie, permettant au handler HTTP de répondre immédiatement.

---

## Sémantique HTTP

### Endpoint: `POST /api/v1/tracks`

**Réponse**: `202 Accepted`

**Headers**:
```
Location: /api/v1/tracks/{track_id}/status
```

**Body**:
```json
{
  "success": true,
  "data": {
    "track_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "uploading",
    "status_url": "/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status",
    "message": "Upload initiated, file is being saved in background"
  }
}
```

---

## Flux d'Exécution

### 1. Handler (`UploadTrack`)

1. Validation du fichier (ClamAV, format, quota)
2. Création du Track en DB avec `Status=Uploading` **immédiatement**
3. Lancement de la copie fichier en **goroutine** (`copyFileAsync`)
4. Réponse **202 Accepted** avec `track_id`

### 2. Goroutine Asynchrone (`copyFileAsync`)

1. Création d'un contexte avec timeout (5 minutes)
2. Ouverture du fichier source (`fileHeader.Open()`)
3. Création du fichier destination (`os.Create`)
4. Copie avec `io.Copy`
5. Mise à jour du Status:
   - `Processing` si succès
   - `Failed` si erreur
6. Nettoyage automatique en cas d'échec (`os.Remove`)

---

## Suivi de Progression

### Endpoint: `GET /api/v1/tracks/{id}/status`

**Réponse**: `200 OK`

**Body**:
```json
{
  "success": true,
  "data": {
    "track_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "processing",
    "progress": 100,
    "message": "File uploaded, processing..."
  }
}
```

**Status possibles**:
- `uploading`: Fichier en cours de copie
- `processing`: Fichier copié, traitement en cours
- `completed`: Track prêt
- `failed`: Échec (upload ou traitement)

---

## Gestion d'Erreurs

### Erreurs Synchrones (avant goroutine)

- **Validation échouée**: `400 Bad Request`
- **Quota dépassé**: `403 Forbidden`
- **ClamAV unavailable**: `503 Service Unavailable`
- **Virus détecté**: `422 Unprocessable Entity`

### Erreurs Asynchrones (dans goroutine)

- **Erreur de copie**: Status → `Failed`, fichier nettoyé
- **Timeout (5 min)**: Status → `Failed`, fichier nettoyé
- **Contexte annulé**: Status → `Failed`, fichier nettoyé

**Nettoyage automatique**: Le fichier est supprimé (`os.Remove`) en cas d'échec.

---

## Traçabilité

### Logs

Tous les logs incluent:
- `track_id`: UUID du track
- `user_id`: UUID de l'utilisateur
- `request_id`: ID de requête (si disponible via context)

**Exemples**:
```
INFO Track upload initiated (async) track_id=... user_id=... filename=...
INFO Track status updated track_id=... status=processing message=...
INFO Track file copied successfully (async) track_id=... bytes_written=...
```

### Request ID

Le `request_id` est propagé via le contexte:
```go
ctx := c.Request.Context() // Contient request_id du middleware
track, err := service.UploadTrack(ctx, userID, fileHeader)
```

---

## Exemples cURL

### 1. Upload d'un fichier

```bash
curl -X POST http://localhost:8080/api/v1/tracks \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "file=@audio.mp3" \
  -v
```

**Réponse**:
```
HTTP/1.1 202 Accepted
Location: /api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status

{
  "success": true,
  "data": {
    "track_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "uploading",
    "status_url": "/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status",
    "message": "Upload initiated, file is being saved in background"
  }
}
```

### 2. Vérifier le statut

```bash
curl -X GET http://localhost:8080/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Réponse** (pendant upload):
```json
{
  "success": true,
  "data": {
    "track_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "uploading",
    "progress": 0,
    "message": "Upload started"
  }
}
```

**Réponse** (après copie):
```json
{
  "success": true,
  "data": {
    "track_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "processing",
    "progress": 100,
    "message": "File uploaded, processing..."
  }
}
```

**Réponse** (échec):
```json
{
  "success": true,
  "data": {
    "track_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "failed",
    "progress": 0,
    "message": "Failed to save file: ..."
  }
}
```

---

## Polling Recommandé

### Stratégie Simple

```javascript
async function uploadAndWait(trackId) {
  const maxAttempts = 60; // 5 minutes max (5s * 60)
  const interval = 5000; // 5 secondes

  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(`/api/v1/tracks/${trackId}/status`);
    const data = await response.json();

    if (data.data.status === 'completed') {
      return data.data;
    }
    if (data.data.status === 'failed') {
      throw new Error(data.data.message);
    }

    await sleep(interval);
  }

  throw new Error('Upload timeout');
}
```

### Stratégie avec Exponential Backoff

```javascript
async function uploadAndWaitWithBackoff(trackId) {
  let interval = 1000; // 1 seconde initial
  const maxInterval = 30000; // 30 secondes max
  const maxAttempts = 120; // ~10 minutes max

  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(`/api/v1/tracks/${trackId}/status`);
    const data = await response.json();

    if (data.data.status === 'completed') {
      return data.data;
    }
    if (data.data.status === 'failed') {
      throw new Error(data.data.message);
    }

    await sleep(interval);
    interval = Math.min(interval * 1.5, maxInterval); // Exponential backoff
  }

  throw new Error('Upload timeout');
}
```

---

## Configuration

### Timeout de Copie

**Valeur par défaut**: 5 minutes

**Modification**: `internal/core/track/service.go`
```go
copyCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
```

### Répertoire d'Upload

**Variable d'environnement**: `UPLOAD_DIR` (optionnel)

**Valeur par défaut**: `uploads/tracks`

---

## Tests

### Tests Unitaires

```bash
go test ./internal/core/track -v -run TestUploadTrack_Async
```

**Tests inclus**:
- `TestUploadTrack_Async_Success`: Upload réussi, vérification Status
- `TestUploadTrack_Async_Interruption`: Gestion interruption (contexte)
- `TestUploadTrack_Async_ErrorHandling`: Gestion erreurs
- `TestCopyFileAsync_ContextCancellation`: Annulation directe

---

## Limitations et Notes

### Limitations Actuelles

1. **Pas de progression détaillée**: Le `progress` dans `GetUploadStatus` n'est pas mis à jour pendant la copie (reste à 0 jusqu'à 100)
2. **Timeout fixe**: 5 minutes (non configurable via env)
3. **Pas de retry automatique**: Si la copie échoue, le Track reste en `Failed`

### Améliorations Futures (Optionnel)

1. **Progression détaillée**: Utiliser `io.TeeReader` pour suivre les bytes copiés
2. **Retry automatique**: Relancer la copie en cas d'erreur réseau
3. **Webhooks**: Notifier le client quand l'upload est terminé
4. **Chunked upload**: Pour très gros fichiers (>100MB)

---

## Cohérence avec l'Architecture

### Avantages

- ✅ **Cohérent** avec `GetUploadStatus` existant
- ✅ **Cohérent** avec `Track.Status` (Uploading, Processing, Completed, Failed)
- ✅ **Traçabilité complète** (logs + request_id)
- ✅ **Nettoyage automatique** en cas d'échec
- ✅ **Support cancellation** (context)

### Intégration

- ✅ Utilise le système de Status existant
- ✅ Compatible avec le traitement asynchrone (streaming, metadata)
- ✅ Pas de changement breaking pour les clients (juste nouveau status code)

---

## Dépannage

### Upload reste en "uploading"

**Cause**: Goroutine bloquée ou timeout non atteint  
**Solution**: Vérifier les logs, attendre 5 minutes max

### Fichier créé mais Status=Failed

**Cause**: Erreur après copie (validation, DB, etc.)  
**Solution**: Vérifier les logs pour le message d'erreur

### Status=Failed immédiatement

**Cause**: Erreur lors de l'ouverture du fichier source  
**Solution**: Vérifier que le fichier est valide et accessible

---

## Références

- [HTTP 202 Accepted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202)
- [Async Processing Pattern](https://restfulapi.net/asynchronous-operations-in-rest/)
- [Context Package](https://pkg.go.dev/context)

---

**Dernière mise à jour**: 2025-01-27  
**Maintenu par**: Veza Backend Team
