◂ Retour à [README](./README.md)

# InWink APIs — Assets (guide d'utilisation de l'API)

> **But.** Décrire comment **créer**, **rechercher**, **modifier**, **supprimer**, **classer**
> et **référencer** des assets via les APIs d'administration InWink (EventAPI / CommunityAPI /
> CustomerAPI). Tout est décrit au niveau HTTP : verbe + route + corps JSON + réponse JSON.
>
> Auth : jeton porteur (Bearer) avec le claim `{scope}asset` (valeur `read` en lecture,
> `edit` en écriture). Le flux d'upload de fichier réutilise celui décrit dans
> la section `file-fields` — s'y référer pour le détail du lien d'upload signé.
>
> Convention JSON : corps de requête acceptés en casse insensible ; réponses en `camelCase`.

---

## 1. Principe (à lire en premier)

Un **asset** est une entrée de bibliothèque médias, scopée à un événement, une communauté ou
un tenant. Contrairement à un simple champ fichier, un asset a une identité propre, un titre,
un type, une miniature, peut être rangé dans un dossier et **référencé par d'autres contenus**
via le schéma `inwinkasset://`.

**Forme d'un asset (en lecture)** :

```json
{
  "id": "3fa85f64-....",
  "title": "Logo principal",          // localisable (peut être un objet multi-langues)
  "assetType": 0,                       // voir table ci-dessous
  "file": { "blobName": "...", "container": "...", "url": "...", "mimeType": "...", "size": 123 },
  "thumbnail": { ... },                 // objet fichier ou null
  "folderId": "....",                   // ou null
  "createdAt": "2026-07-01T10:00:00Z",
  "applicationScope": "...",
  "applicationScopeId": "..."
}
```

**Types d'asset (`assetType`)** : `0`=Image, `1`=Video, `2`=Document, `3`=Audio, `4`=Font, `5`=Model3D.

> ⚠️ **Le type d'asset n'est jamais dans le corps de requête** : il est déterminé par la
> **route typée** sur laquelle on POST (`image-asset` → Image, `video-asset` → Video, …).

Les champs `file` et `thumbnail` sont alimentés par le **même flux d'upload temporaire** que
les champs fichier (voir la section `file-fields`) : on passe le **nom du blob temporaire** à la
création, et l'API le transfère puis le sérialise en objet fichier.

---

## 2. Routes de base par API

| API | Base CRUD typée | Base recherche (tous types) |
|-----|-----------------|-----------------------------|
| EventAPI | `{eventId}/{type}-asset` | `{eventId}/asset` |
| CommunityAPI | `{communityId}/{type}-asset` | `{communityId}/asset` |
| CustomerAPI | `{customerId}/audience/{tenantId}/{type}-asset` | `.../asset` |

`{type}` ∈ `image`, `video`, `audio`, `document`, `font`, `model3d` (selon l'API — EventAPI
expose les 6, CommunityAPI 4, CustomerAPI 3).

Dans la suite, `{base}` = la base CRUD typée (ex. `{eventId}/image-asset`).

---

## 3. Créer un asset (upload + création)

### Étape 1 — Téléverser le fichier (flux temporaire)

Les endpoints d'upload sont disponibles sous la base de l'asset :

```
POST {base}/file               { "type": "image/png", "name": "logo.png" }   → objet d'accès temporaire
   (puis PUT des octets sur url+container+blobName+sas — voir section file-fields §2)
POST {base}/file/renew         { "blobName": "...", "sas": "..." }            → renouvellement
POST {base}/file/try-renew     { "blobName": "...", "sas": "..." }            → renouvellement si expiré
POST {base}/file/finalize-tmp  { "blobName": "...", "sas": "..." }            → 200
```

Répéter pour la miniature si on en fournit une.

### Étape 2 — Créer l'asset en référençant les blobs temporaires

```
POST {base}
Content-Type: application/json

{
  "title": "Logo principal",
  "file": "<blobName_du_fichier>",        // nom du blob temporaire (obligatoire)
  "thumbnail": "<blobName_miniature>",     // optionnel
  "folderId": "3fa85f64-....",             // optionnel
  "applicationScope": "...",
  "applicationScopeId": "..."
}
```

Réponse `200` — l'asset créé (voir forme §1). L'API transfère le blob temporaire vers son
emplacement définitif et remplit `file` / `thumbnail` avec l'objet fichier résultant.

---

## 4. Rechercher / compter

```
POST {base}/query
{
  "applicationScope": "...",
  "applicationScopeId": "...",     // optionnel
  "folderId": "3fa85f64-....",     // optionnel — la présence de la clé active le filtre par dossier
  "id": "....",                    // optionnel — un asset précis
  "search": "logo",                // optionnel — recherche dans le titre
  "fileUrl": "...",                // optionnel — recherche par URL de fichier
  "page": { "index": 0, "size": 20 },   // ou { "skip": 0, "take": 20 }
  "orders": [ { "desc": true, "value": { "createdAt": {} } } ]
}
→ 200  liste paginée d'assets
```

```
POST {base}/count      (même corps)   → 200  <nombre entier>
```

Le filtrage inclut toujours le scope de l'application et le type de la route.

> Pour lister **tous types confondus**, utiliser la base recherche `{scopeId}/asset/query`.

---

## 5. Modifier / supprimer / déplacer

```
POST {base}/{assetId}          (édition partielle)
```
- Champs absents = inchangés. `file`/`thumbnail` optionnels (omettre = conserver).
- `thumbnail` **présent mais vide** = supprime la miniature.
- `folderId`/`title` mis à jour seulement si présents dans le corps.

```
POST {base}/delete/{assetId}                             → 200
POST {base}/delete       { "assetIds": ["...", "..."] }  → 200  (suppression en masse)
POST {base}/movetofolder { "assetIds": ["..."], "folderId": "..." }   → 200
```

La suppression retire aussi les fichiers (principal + miniature + cache).

---

## 6. Dossiers d'assets

Un dossier a la forme `{ name, parentFolderId?, assetType }` et est hiérarchique.

- **Le CRUD des dossiers passe par les endpoints d'entité génériques** (pas par les routes
  d'assets) : création / recherche / suppression standard sur `{scopeId}/{folderEntity}`
  (ex. `{eventId}/eventassetfolder`, `{communityId}/communityassetfolder`,
  `{customerId}/audience/{tenantId}/authtenantassetfolder`).
- **Route spécifique** — supprimer un dossier en **conservant** son contenu (sous-dossiers/assets) :
  ```
  POST {scopeId}/{folderEntity}/delete/{folderId}/keepSubContent
  ```
- **Contrainte** : le dossier ciblé lors d'une création/déplacement doit exister et avoir le
  **même type** que l'asset, sinon `400` (« Folder type does not match asset type »).

---

## 7. Référencer un asset depuis un contenu (`inwinkasset://`)

Pour lier un asset à un autre contenu (typiquement le contenu d'un ContentTemplate / page
builder), on **stocke une chaîne de référence** dans le champ concerné :

```
inwinkasset://{assetId}                  // asset du scope courant
inwinkasset://{assetId}/{shardId}        // asset d'un autre tenant (cross-tenant)
```

**Ne pas** y écrire un objet fichier ni une URL directe : juste la chaîne `inwinkasset://{id}`.

À la lecture du contenu, l'API remplace automatiquement chaque référence par **l'objet asset
complet** (`{ id, tenantId, title, assetType, thumbnail, file, applicationScope, validFrom }`).
Les références **non résolvables sont retirées** de la sortie. Lors d'une copie d'événement,
les références sont remappées vers les nouveaux identifiants.

---

## 8. Livraison publique (lecture anonyme)

Le binaire d'un asset est servi et redimensionné en accès public :

```
GET event/{eventId}/asset/{assetId}/file[/{language}/{fileName}]
GET event/{eventId}/asset/{assetId}/thumbnail[/{language}/{fileName}]
   (préfixes équivalents : tenant/{tenantId}/..., community/{communityId}/...)
```

Paramètres de requête (optionnels) : `mw` (largeur max), `mh` (hauteur max), `q` (qualité),
`b` (flou), `o` (format de sortie), `nocache`. Les images sont redimensionnées à la volée
(cache 30 jours + ETag). Les fichiers non-image sont streamés ou **redirigés (302)** vers le CDN.

---

## 9. Exemples

- **Recherche** :
  ```
  POST {eventId}/image-asset/query
  { "orders": [ { "desc": true, "value": { "createdAt": {} } } ] }
  → 200  liste d'assets
  ```
- **Recherche par dossier** : ajouter `"folderId": "<guid>"` au corps.
- **Création** (après upload du fichier temporaire) :
  ```
  POST {eventId}/image-asset
  { "title": "Bannière", "file": "<blobName>", "applicationScope": "...", "applicationScopeId": "..." }
  → 200  asset créé
  ```
- **Téléchargement public** : `GET event/{eventId}/asset/{assetId}/file` → `200` (image) ou
  `302` (redirection, ex. vidéo).
- **Suppression de dossier en conservant le contenu** :
  `POST {eventId}/eventassetfolder/delete/{folderId}/keepSubContent`.

---

## 10. Points d'attention

- **Pas de contrôle du MIME par type d'asset** à la création : l'API vérifie seulement que le
  contenu correspond à son MIME déclaré (parmi les MIME supportés), pas qu'il correspond au
  type de la route. Le type d'asset ne change que l'emplacement de stockage et le rendu public
  (seules les images sont redimensionnées ; les autres sont streamées/redirigées).
- **Busting de cache** : à chaque changement de fichier, un nouvel emplacement est généré,
  donc l'URL du fichier change — ne pas mettre en cache un objet fichier d'asset au-delà d'une édition.
- **`title`** peut être localisé (objet multi-langues).
- **`file` obligatoire à la création**, `thumbnail` optionnel ; à l'édition les deux sont optionnels.

---

*Voir aussi : `file-fields` (détail du flux d'upload temporaire réutilisé ici), `mutations`, `entities`.*
