# Contrat des API actuelles

Ce document décrit le comportement réellement exposé par les routes Next.js actuelles. Il sert de référence de compatibilité pour le futur backend PHP.

## Modèle JSON `EventRecord`

```json
{
  "id": 1,
  "slug": "mon-evenement-a1b2",
  "managementToken": "uuid-suivi-de-32-caracteres-hexadecimaux",
  "email": "contact@example.fr",
  "imageUrl": "/api/images/uuid.webp",
  "title": "Mon événement",
  "category": "Event’go",
  "date": "2026-08-22",
  "time": "19:30",
  "location": "Paris",
  "description": "Description",
  "price": "Gratuit",
  "updatedAt": "2026-07-25T12:00:00.000Z",
  "createdAt": "2026-07-25T12:00:00.000Z"
}
```

`managementToken` et `imageUrl` peuvent être `null`. Le code ne filtre actuellement pas `email` ni `managementToken` dans les réponses `event`, y compris sur `GET /api/events` et la page publique via le repository. Le backend PHP devra préserver les besoins de l'interface, mais l'exposition publique de ces champs constitue un risque à traiter explicitement avant sa livraison.

## `GET /api/events`

- Authentification : aucune.
- Paramètres de chemin : aucun.
- Paramètres de requête : aucun n'est interprété.
- Corps : aucun.

Réponse normale, HTTP `200` :

```json
{ "events": [EventRecord] }
```

Comportement PostgreSQL : 50 événements maximum, triés par `createdAt` décroissant.

Comportement Cloudflare historique : le proxy renvoie la liste fournie par le backend historique.

Comportement d'erreur actuel : toute erreur interceptée par la route devient HTTP `200` :

```json
{ "events": [] }
```

## `POST /api/events`

- Authentification : aucune.
- `Content-Type` : `application/json`.
- Paramètres de requête : aucun.

Corps :

```json
{
  "email": "contact@example.fr",
  "imageUrl": "/api/images/uuid.webp",
  "title": "Mon événement",
  "category": "Event’go",
  "date": "2026-08-22",
  "time": "19:30",
  "location": "Paris",
  "description": "Description",
  "price": "Gratuit"
}
```

Champs obligatoires, chaînes non vides après `trim()` :

- `email`
- `title`
- `category`
- `date`
- `time`
- `location`
- `description`
- `price`

`imageUrl` est facultatif ; absent, vide ou non textuel devient `null`. L'e-mail est mis en minuscules. Les autres chaînes sont nettoyées avec `trim()`.

Réponse HTTP `201` :

```json
{
  "event": EventRecord,
  "managementUrl": "https://domaine.example/manage/JETON",
  "emailSent": true
}
```

`managementUrl` utilise `EVENTGO_PUBLIC_URL` si défini, sinon l'origine de la requête. `emailSent` vaut `false` si aucun envoi n'a eu lieu ou a échoué sans exception.

Erreurs :

| HTTP | Réponse |
| ---: | --- |
| `400` | `{"error":"Tous les champs sont requis."}` |
| `500` | `{"error":"La publication est temporairement indisponible."}` |

Un JSON mal formé tombe actuellement dans le `500`, pas dans le `400`.

## `GET /api/manage/{token}`

- Authentification : possession du jeton privé dans l'URL.
- Paramètre `token` : chaîne issue du chemin, recherche exacte en base.
- Corps : aucun.

Réponse HTTP `200` :

```json
{ "event": EventRecord }
```

Erreur HTTP `404` :

```json
{ "error": "Lien invalide ou expiré." }
```

Une erreur de repository non interceptée produit actuellement une erreur serveur Next générique.

## `PATCH /api/manage/{token}`

- Authentification : possession du jeton privé.
- `Content-Type` : `application/json`.

Tous les champs sont facultatifs :

```json
{
  "title": "Nouveau titre",
  "date": "2026-08-23",
  "time": "20:00",
  "location": "Lyon",
  "description": "Nouvelle description",
  "price": "Prix libre",
  "imageUrl": "/api/images/autre-uuid.jpg"
}
```

Règles actuelles :

- `title`, `location`, `description` : une chaîne vide ou uniquement composée d'espaces conserve l'ancienne valeur ;
- `date`, `time`, `price` : une chaîne vide conserve l'ancienne valeur ;
- `imageUrl` absent conserve l'ancienne valeur ;
- `imageUrl` chaîne non vide remplace l'ancienne valeur ;
- `imageUrl` vide, `null` ou d'un autre type devient `null` ;
- `email`, `category`, `slug`, `managementToken`, `createdAt` ne sont pas modifiables par cette route ;
- `updatedAt` est remplacé par une date ISO lors d'une mise à jour PostgreSQL.

Réponse HTTP `200` :

```json
{ "event": EventRecord }
```

Erreur HTTP `404` si le jeton n'existe pas :

```json
{ "error": "Lien invalide ou expiré." }
```

Limites du contrat actuel :

- aucune validation complète du format ou de la longueur ;
- JSON mal formé et erreur de repository non interceptés ;
- la route suppose que l'événement existe encore entre la lecture et la mise à jour ;
- l'ancienne image n'est pas supprimée lorsqu'une nouvelle URL la remplace.

## `DELETE /api/manage/{token}`

- Authentification : possession du jeton privé.
- Corps : aucun.

Réponse HTTP `200` :

```json
{ "deleted": true }
```

Erreur HTTP `404` :

```json
{ "error": "Lien invalide ou expiré." }
```

Après suppression en base, la route tente de supprimer l'image. Une erreur de suppression d'image est ignorée et ne modifie pas la réponse.

Dans le backend PHP définitif, l'image stockée sur disque est supprimée. Une
erreur de suppression reste ignorée pour conserver le contrat.

## `POST /api/uploads`

- Authentification : aucune.
- `Content-Type` : `multipart/form-data`.
- Champ fichier obligatoire : `image`.
- Formats MIME : `image/jpeg`, `image/png`, `image/webp`.
- Taille maximale : 5 × 1024 × 1024 octets.

Réponse HTTP `201` :

```json
{ "url": "/api/images/uuid.webp" }
```

Erreurs :

| HTTP | Réponse |
| ---: | --- |
| `400` | `{"error":"Aucune image reçue."}` |
| `400` | `{"error":"Utilisez une image JPG, PNG ou WebP de 5 Mo maximum."}` |
| `503` | `{"error":"Le stockage des images est indisponible."}` |

La validation actuelle repose sur le type MIME déclaré par le `File` et sa taille. La future version PHP devra également inspecter le contenu, sans modifier les formats ni la limite visible.

## `GET /api/images/{key}`

- Authentification : aucune.
- Corps de réponse : binaire, pas JSON.
- Format exact de `key` :

```text
^[a-f0-9-]+\.(jpg|png|webp)$
```

Réponse HTTP `200` :

- `Content-Type` de l'objet stocké ;
- `Cache-Control` si fourni par le stockage ;
- `ETag` si fourni par le stockage ;
- corps binaire.

Erreur : HTTP `404`, corps texte `Not found`, si la clé est invalide ou absente.

Dans le backend PHP définitif, la lecture utilise exclusivement le disque OVH.
Aucune image R2 ou S3 n'existe à reprendre.

## Routes réellement utilisées

Aucune autre route API applicative n'existe dans `app/api/`.

Les appels frontend actuels sont :

- accueil : `POST /api/uploads`, puis `POST /api/events` ;
- gestion : `GET`, `PATCH`, `DELETE /api/manage/{token}` ;
- navigateur et pages : `GET /api/images/{key}` ;
- page publique : accès au repository côté serveur, sans API HTTP dédiée ;
- `GET /api/events` existe, mais la page d'accueil affiche actuellement des exemples statiques et ne l'appelle pas.

## Authentification et sécurité du jeton

Il n'existe ni compte ni session. Le jeton de gestion est un **bearer secret dans l'URL** :

- généré par concaténation de deux UUID, le second sans tirets ;
- stocké en clair dans `management_token` ;
- unique en base ;
- sans date d'expiration ;
- transmis par e-mail et affiché après publication.

Conséquences à préserver fonctionnellement :

- toute personne possédant l'URL peut lire, modifier et supprimer l'événement ;
- le jeton ne doit jamais apparaître dans les journaux applicatifs, outils d'analyse, référents ou pages publiques ;
- les pages de gestion devront envoyer `Referrer-Policy: no-referrer` et `X-Robots-Tag: noindex, nofollow`.
