> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wethehivers.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Uploads médias

> Logos, bannières, photos staff, photos profil, covers blog

# Uploads médias

Tous les uploads transitent par le backend (scan AV + validation) avant d'atterrir sur **Cloudflare R2**.

## Matrice des médias

| Média                 | Bucket    | Rôle      | Formats   | Taille max | Ratio |
| --------------------- | --------- | --------- | --------- | :--------: | :---: |
| Logo entreprise       | `public`  | OWNER     | PNG, JPG  |    2 Mo    | carré |
| Bannière entreprise   | `public`  | OWNER     | JPG       |    5 Mo    |  16:9 |
| Photo staff           | `public`  | OWNER     | JPG       |    3 Mo    |  4:3  |
| Photo profil candidat | `public`  | CANDIDAT  | PNG, JPG  |    1 Mo    | carré |
| CV candidat           | `private` | CANDIDAT  | PDF       |    5 Mo    |   —   |
| Cover blog            | `public`  | ADMIN     | JPG, WebP |    5 Mo    |  16:9 |
| KYC                   | `private` | RECRUTEUR | PDF, JPG  |    10 Mo   |   —   |

## Pipeline général

```mermaid theme={null}
flowchart LR
    CLI[Client] --> UP[Upload multipart]
    UP --> VAL{Valider MIME + taille}
    VAL -->|échec| R413[413]
    VAL -->|OK| AV[Scan ClamAV]
    AV -->|infecté| QUAR[Quarantaine + 422]
    AV -->|propre| IMG[Traitement image]
    IMG --> R2[PUT R2]
    R2 --> DB[files_metadata + parent entity]
    DB --> CDN[CDN Cloudflare warmup]
    CDN --> OK[200 + URL]
```

## 1. Logo entreprise

```http theme={null}
POST /v1/api/recruiters/me/entreprise/logo HTTP/1.1
Authorization: Bearer <OWNER_TOKEN>
Content-Type: multipart/form-data

file=@logo.png
```

**Traitement image** :

```mermaid theme={null}
flowchart TD
    UP[Upload PNG/JPG] --> RESIZE[Resize 512x512 + 256x256 + 128x128]
    RESIZE --> WEBP[Convertir en WebP]
    WEBP --> R2[PUT 3 variants]
    R2 --> JSON[Retour URLs responsive]
```

**Réponse** :

```json theme={null}
{
  "logoUrl": "https://cdn.wethehivers.com/public/entreprises/18/logo.webp",
  "variants": {
    "512": "https://cdn.wethehivers.com/public/entreprises/18/logo-512.webp",
    "256": "https://cdn.wethehivers.com/public/entreprises/18/logo-256.webp",
    "128": "https://cdn.wethehivers.com/public/entreprises/18/logo-128.webp"
  }
}
```

## 2. Bannière entreprise

Ratio imposé **16:9** (erreur 422 sinon).

```http theme={null}
POST /v1/api/recruiters/me/entreprise/banner HTTP/1.1
file=@banner-1920x1080.jpg
```

```mermaid theme={null}
flowchart LR
    IN[JPG 1920x1080+] --> CHK{ratio 16:9 ± 2%}
    CHK -->|non| E422[422]
    CHK -->|oui| VAR[Générer 1920, 1200, 768]
    VAR --> R2[PUT R2]
```

## 3. Photos staff

Jusqu'à **10 photos** par entreprise.

```http theme={null}
POST /v1/api/recruiters/me/entreprise/staff-photos HTTP/1.1
Content-Type: multipart/form-data

files=@photo1.jpg
files=@photo2.jpg
caption=Équipe dev Douala
```

```mermaid theme={null}
sequenceDiagram
    participant R as Recruteur
    participant API as Backend
    participant DB as DB

    R->>API: Upload 3 photos
    API->>API: Pour chaque: valider + scan + R2 PUT
    API->>DB: INSERT staff_photos (3 lignes)
    API-->>R: 200 [url, url, url]
```

### Réorganiser

```http theme={null}
PUT /v1/api/recruiters/me/entreprise/staff-photos/order
{ "order": [15, 12, 18, 14] }
```

### Supprimer

```http theme={null}
DELETE /v1/api/recruiters/me/entreprise/staff-photos/12
```

## 4. Photo profil candidat

```http theme={null}
POST /v1/api/candidats/me/photo HTTP/1.1
file=@profile.jpg
```

Le backend génère un **avatar circulaire** 256x256 WebP.

```mermaid theme={null}
flowchart LR
    UP[Upload] --> CROP[Crop carré centré]
    CROP --> CIR[Masque circulaire]
    CIR --> WEBP[WebP 256]
    WEBP --> R2[PUT]
```

## 5. CV candidat (privé)

Voir [URLs pré-signées](/concepts/urls-presignees) pour les règles d'accès.

```http theme={null}
POST /v1/api/candidats/me/cv HTTP/1.1
file=@cv.pdf
```

```mermaid theme={null}
sequenceDiagram
    participant C as Candidat
    participant API as Backend
    participant AV as ClamAV
    participant R2 as R2 private
    participant DB as DB

    C->>API: POST /candidats/me/cv (PDF)
    API->>API: Valider MIME = application/pdf
    API->>R2: PUT private/cv/17.pdf (status=PENDING_SCAN)
    API->>AV: Scan stream
    AV-->>API: OK
    API->>DB: UPDATE files_metadata status=ACTIVE
    API->>API: Extraire métadonnées PDF
    API-->>C: 200 {cvId, fileName, size}
```

## 6. Cover blog (admin)

```http theme={null}
POST /v1/api/admin/blog/14/cover HTTP/1.1
file=@cover.jpg
```

Conversion automatique en WebP + variants responsive :

| Variant           | Taille    | Usage              |
| ----------------- | --------- | ------------------ |
| `cover-1920.webp` | 1920×1080 | Desktop full width |
| `cover-1200.webp` | 1200×675  | Tablet             |
| `cover-600.webp`  | 600×338   | Mobile / cards     |

## 7. Gestion des erreurs

```mermaid theme={null}
flowchart TD
    ERR[Erreur upload] --> T{Type}
    T -->|MIME invalide| E415[415 Unsupported Media Type]
    T -->|Trop gros| E413[413 Payload Too Large]
    T -->|Quota dépassé| E402[402 Payment Required]
    T -->|Scan AV fail| E422[422 Unprocessable Entity]
    T -->|Ratio invalide| E422B[422 + message]
    T -->|R2 indispo| E503[503 Service Unavailable]
```

## 8. Quotas de stockage

| Rôle              | Quota total       | Retry après dépassement   |
| ----------------- | ----------------- | ------------------------- |
| CANDIDAT          | 50 Mo             | Supprimer anciens uploads |
| RECRUTEUR (OWNER) | 100 Mo entreprise | Idem ou upgrade plan      |
| ADMIN             | Illimité          | —                         |

```mermaid theme={null}
flowchart LR
    UP[Upload] --> Q{Quota check}
    Q -->|OK| CONT[Continuer]
    Q -->|dépassé| E402[402 + lien nettoyage / upgrade]
```

## 9. URLs signées (fichiers privés)

Pour les CV et KYC, utiliser :

```http theme={null}
GET /v1/api/files/cv/17  → 200 {url, expiresAt} ou 302 redirect
```

L'URL signée expire en **15 minutes**. Voir [URLs pré-signées](/concepts/urls-presignees).

## 10. Suppression

```http theme={null}
DELETE /v1/api/candidats/me/cv/17
DELETE /v1/api/recruiters/me/entreprise/logo
```

La suppression est **immédiate** en R2 + marquage `files_metadata.status=DELETED`. Pas de corbeille.

## Snippet JS — upload avec progression

```javascript theme={null}
async function uploadCV(file, onProgress) {
  const form = new FormData();
  form.append('file', file);

  const xhr = new XMLHttpRequest();
  xhr.open('POST', '/v1/api/candidats/me/cv');
  xhr.setRequestHeader('Authorization', `Bearer ${token}`);
  xhr.upload.onprogress = (e) => {
    if (e.lengthComputable) onProgress(e.loaded / e.total);
  };
  return new Promise((resolve, reject) => {
    xhr.onload = () => resolve(JSON.parse(xhr.response));
    xhr.onerror = () => reject(new Error('Upload failed'));
    xhr.send(form);
  });
}
```

## Voir aussi

* [URLs pré-signées](/concepts/urls-presignees)
* [Modèle Entreprise](/models/entreprise)
