# Cocolight API Client

Ce module fournit un client JavaScript prêt à l'emploi pour interagir avec l'API Cocolight. Il encapsule la gestion des appels HTTP, l'authentification, la validation des schémas, la gestion des erreurs, ainsi qu'un ensemble de normalisations des données pour simplifier l'intégration côté client.

## 📦 Installation

```bash
npm install @communecter/cocolight-api-client
```

## 🧭 Objectif

Ce client facilite l'intégration avec l'API Cocolight en offrant :

- Des appels simplifiés avec `axios`
- Un système de validation des entrées/sorties avec AJV
- La gestion automatique du token d'accès et du token de rafraîchissement
- Un circuit breaker configurable
- Un logger intégré avec Pino
- Des outils de transformation de données compatibles MongoDB/EJSON
- La gestion des appels API à partir de métadonnées JSON (endpoints)

## 🧱 Architecture

Le projet est structuré autour des fichiers suivants :

```
src/
  ApiClient.js         # Client principal : logique d'appel, authentification, validation, transformation
  EJSONType.js         # Support pour les ObjectID MongoDB avec EJSON
  endpoints.module.js  # Fichier généré avec les définitions d'endpoints (voir ci-dessous)
  error.js             # Erreurs personnalisées (ApiClientError, CircuitBreakerError)
package.json           # Dépendances, scripts, métadonnées
```

## imports

### ESM

```js
import Cocolight from "@communecter/cocolight-api-client";

const { Api, ApiClient, tokenStorageStrategy } = Cocolight;
```
### CommonJS

```js
const { ApiClient, Api, tokenStorageStrategy } = require("@communecter/cocolight-api-client").default;
```

### 🔗 Intégration via CDN

Vous pouvez inclure le client directement dans une page HTML :

```html
<script src="https://cdn.jsdelivr.net/npm/@communecter/cocolight-api-client@latest/dist/cocolight-api-client.browser.js"></script>
<script>
  const client = new CocolightApiClient.ApiClient({
    baseURL: "https://www.communecter.org"
  });

client.callEndpoint("AUTHENTICATE_URL", {
        email: "EMAIL",
        password: "PASSWORD"
      }).then(res => {
          const { accessToken, refreshToken, user } = res.data;
          console.log("✅ Authentifié :", user.username);
        })
        .then(() => {
          return client.callEndpoint("ME_INFO_URL");
        })
        .then(res => {
          console.log("👤 Profil utilisateur :", res.data);
        })
        .then(() => {
          console.log("userId :", client.userId);
        })
      .catch(err => console.error("Erreur :", err));
</script>
```

## 🔧 Utilisation de base

```js
import { ApiClient } from "@communecter/cocolight-api-client";

const client = new ApiClient({
  baseURL: "https://mon-api.com"
});

try {
    const authResponse = await client.callEndpoint("AUTHENTICATE_URL", {
      email: "EMAIL",
      password: "PASSWORD"
    });

    const { accessToken, refreshToken, user } = authResponse.data;

    console.log("✅ Authentifié :", user.username);

    const meResponse = await client.callEndpoint("ME_INFO_URL");

    console.log("👤 Profil utilisateur :", meResponse.data);

    console.log("userId :", client.userId);
  } catch (error) {
    console.error("❌ Erreur durant la session :", error.message, error.details);
  }


```

## 📜 API détaillée

### `new ApiClient(options)`

| Option                     | Type       | Description |
|---------------------------|------------|-------------|
| `baseURL` (requis)        | `string`   | URL de base de l'API |
| `accessToken`             | `string`   | Token d'accès initial |
| `refreshToken`            | `string`   | Token de rafraîchissement |
| `refreshUrl`              | `string`   | URL pour rafraîchir le token (défaut : `/api/cocolight/refreshtoken`) |
| `endpoints`               | `Array`    | Liste des endpoints (par défaut : `endpoints.module.js`) |
| `timeout`                 | `number`   | Timeout en ms (défaut : `30000`) |
| `debug`                   | `boolean`  | Active le logger debug (défaut : `false`) |
| `maxRetries`              | `number`   | Nombre de tentatives max (défaut : `0`) |
| `circuitBreakerThreshold` | `number`   | Seuil d'erreurs avant blocage (défaut : `5`) |
| `circuitBreakerResetTime` | `number`   | Délai de réactivation (en ms, défaut : `60000`) |
| `fromJSONValue`           | `boolean`  | Applique `EJSON.fromJSONValue` sur les réponses (défaut : `true`) |

### Méthodes principales

#### `callEndpoint(constant, data, transform, validate)`
Effectue un appel à un endpoint défini via `endpoints.module.js`.

- `constant`: nom unique du endpoint
- `data`: objet avec `pathParams` et données du corps
- `transform`: `true` (par défaut), `false`, ou fonction de transformation
- `validate`: `true` (par défaut), permet de valider la réponse via AJV

#### `setToken(token)` / `getToken()`
Gère le token d'accès.

#### `setRefreshToken(rt)` / `getRefreshToken()`
Gère le token de rafraîchissement.

#### `resetSession()`
Réinitialise la session utilisateur.

### Événements

- `refreshSuccess` — déclenché après un rafraîchissement réussi
- `validationError` — déclenché si AJV détecte des erreurs
- `sessionReset` — déclenché par `resetSession()`

## 🛠️ Scripts disponibles

Depuis `package.json` :

- `npm run test` — Lance les tests via Jest
- `npm run exemple` — Lance un exemple
- `npm run lint` / `lint:fix` — Vérifie et corrige le code avec ESLint
- `npm run build` — Génère les builds navigateur + Node via Webpack
- `npm run generate:*` — Génère les modules, documentation ou données de test depuis les fichiers JSON d’API

## 🧩 Dépendances principales

- `axios` & `axios-retry` — pour les appels HTTP robustes
- `ajv` & `ajv-formats` — pour la validation JSON Schema
- `ejson` — pour la compatibilité MongoDB (ObjectID, dates...)
- `pino` — logger performant, compatible navigateur
- `events` — pour la gestion des événements custom côté client

## 🗃️ Format des endpoints

Chaque endpoint dans `endpoints.module.js` suit le schéma :
```js
{
  constant: "GET_USER_INFO",
  method: "GET",
  path: "/api/users/{userId}",
  auth: "bearer",
  pathParams: { type: "object", properties: { userId: { type: "string" } }, required: ["userId"] },
  request: { ...schemaAjv },
  responses: { "200": { ...schemaAjv } },
  postActions: [
    { type: "setToken", path: "accessToken" },
    { type: "setUserId", path: "user._id" }
  ]
}
```

## 🔒 Sécurité et circuit breaker

Un circuit breaker est intégré :
- Blocage temporaire après trop d’échecs (`circuitBreakerThreshold`)
- Réactivation après un délai (`circuitBreakerResetTime`)
- Journalisation complète avec `pino`

## 🧪 Validation AJV

Chaque appel est validé avant (requête) et après (réponse) via AJV. En cas d’erreur, l’événement `validationError` est déclenché avec les détails.

## 🔄 Rafraîchissement automatique

Si une réponse retourne 401, le client tente automatiquement un appel POST vers `refreshUrl` avec le `refreshToken`, et réessaie l’appel initial si réussi.

## 🧼 Transformation des données

Les réponses sont normalisées :
- Champs dates, images, IDs MongoDB
- Structures communes comme `results`, `news`, `notif`, `replies`, etc.
- Conversion EJSON via `EJSON.fromJSONValue()`

## 🧙 Valeurs dynamiques

Dans les données `pathParams`, il est possible d’utiliser des alias dynamiques :

```js
pathParams: {
  userId: "@userId",
  accessToken: "@accessToken"
}
```

## 🎩 API de façade : `Api`

La classe `Api` fournit une interface unifiée pour gérer les utilisateurs, organisations, projets et actualités. Elle encapsule les appels à `ApiClient` et permet de manipuler les entités comme des objets métier avec des méthodes pratiques.

### 🧪 Authentification

#### `Api.userLogin(email, password, options)`
Crée une instance d’`Api` authentifiée.

```js
const api = await Api.userLogin("john@doe.com", "password123", { baseURL: "https://api.monapp.com" });
const user = await api.me();
```

#### `Api.userApi(options)`
Retourne une instance `UserApi` si tu veux gérer manuellement l’authentification :

```js
const userApi = Api.userApi({ baseURL: "..." });
const user = await userApi.login("email", "mdp");
```

---

### 🔐 Session et utilisateur courant

#### `api.me()`
Retourne l'utilisateur actuellement connecté (`User`).

```js
const user = await api.me();
console.log(user.data.email);
```

---

### 👤 Utilisateurs

#### `api.user(userData)`
Crée une instance `User` (autre que le connecté).

```js
const otherUser = await api.user({ slug: "caroline" });
console.log(otherUser.data.description);
```

---

### 🏛️ Organisations

#### `api.organization({ id | slug })`
Retourne une instance d’`Organization`. Récupère automatiquement le profil public.

```js
const orga = await api.organization({ slug: "asso-verte" });
console.log(orga.data.name);
```

---

### 🏗️ Projets

#### `api.project({ id | slug })`
Retourne une instance `Project`. Récupère automatiquement le profil public.

```js
const projet = await api.project({ id: "647..." });
```

---

## 📦 Entités métiers

Les entités suivantes partagent une API commune grâce aux mixins : `User`, `Organization`, `Project`, `News`.

### Propriétés

| Propriété         | Description |
|------------------|-------------|
| `data`           | Proxy combiné `serverData + draftData` |
| `draftData`      | Données modifiables avant `save()` |
| `initialDraftData` | Snapshot initial pour détection des changements |
| `serverData`     | Dernières données serveur |
| `isConnected`    | Vrai si `ApiClient` a un token |
| `userId`         | ID de l'utilisateur connecté |
| `isMe`           | Vrai si l'entité correspond à l'utilisateur courant |

---

### Méthodes génériques

#### `entity.save()`
Sauvegarde les données via les blocs modifiés (`UPDATE_BLOCK_*`, `ADD_*`).

#### `entity.refresh()`
Recharge les données depuis le serveur.

#### `entity.hasChanges()`
Retourne `true` si le draft est différent du snapshot initial.

---

## ✏️ Méthodes d’édition (communes à toutes les entités)

| Méthode                | Description |
|------------------------|-------------|
| `updateDescription`    | Met à jour les champs `shortDescription`, `description`, `descMentions` |
| `updateInfo`           | Nom, email, téléphone, etc. |
| `updateSocial`         | Réseaux sociaux (Facebook, GitHub...) |
| `updateLocality`       | Adresse, géolocalisation |
| `updateSlug`           | Slug de l’URL |
| `updateImageProfil`    | Upload de l’avatar |

---

## 📰 Actualités (`News`)

Créées à partir d’un `User`, `Organization` ou `Project`.

### Création

```js
const news = await orga.news();
news.data.text = "Nouvelle actu !";
await news.save();
```

### Gestion

| Méthode        | Description |
|----------------|-------------|
| `addMention({ slug \| id })` | Ajoute une mention à la news |
| `addImage(file)`            | Ajoute une image (via validation MIME) |
| `addFile(file)`             | Ajoute un fichier (PDF, CSV...) |
| `delete()`                  | Supprime la news |
| `refresh()`                 | Recharge la news |
| `get()`                     | Récupère les données à partir de l’ID |

---

## 🔄 Utilisation des schémas de validation (AJV)

Toutes les entités sont basées sur les constantes de schéma :
- `ADD_*`
- `UPDATE_BLOCK_*`
- `PROFIL_IMAGE`

Ces constantes sont mappées automatiquement pour les appels `save()`, `updateX()`, etc.

---

## ✅ Exemple : mise à jour d’un projet

```js
const projet = await api.project({ slug: "bio-bazar" });
projet.data.description = "Nouveau descriptif de mon projet.";
if (projet.hasChanges()) {
  await projet.save();
}
```

## 👤 API : Utilisateur (`User`)

Une instance `User` offre un ensemble de méthodes métier pour interagir avec les projets, organisations et actualités de l’utilisateur.

---

### 🔍 Chargement

```js
const user = await api.user({ slug: "caroline" });
await user.get();
```

---

### 🏭 Création d'entités depuis `User`

Un utilisateur peut **créer** :

#### ➕ Organisation

```js
const orga = await user.organization({ name: "Ma nouvelle asso" });
await orga.save(); // appelle ADD_ORGANIZATION puis rafraîchit les données
```

#### ➕ Projet

```js
const projet = await user.project({ name: "Mon projet citoyen" });
await projet.save(); // appelle ADD_PROJECT
```

#### ➕ Actualité

```js
const news = await user.news({ text: "Hello world 🌍" });
await news.save(); // appelle ADD_NEWS
```

---

### 📦 Récupération des projets de l'utilisateur

#### `user.getProjects()`

Retourne la liste des projets créés ou co-administrés par l’utilisateur.

```js
const { results: projets } = await user.getProjects();
console.log("Projets liés à l'utilisateur :", projets.map(p => p.data.name));
```

---

### 🧑‍🤝‍🧑 Récupération des organisations de l'utilisateur

#### `user.getOrganizations()`

Retourne les organisations où il est membre (admin ou non).

```js
const { results: orgs } = await user.getOrganizations();
console.log("Organisations :", orgs.map(o => o.data.name));
```

---

### 📰 Actualités associées

#### `user.getNews()`

Retourne toutes les actualités liées à l’utilisateur.

```js
const newsList = await user.getNews({ indexStep: 5 });
```

#### `user.news(newsData)`

Crée une nouvelle actualité, ou charge une existante si `id` est fourni.

```js
const actu = await user.news({ text: "Ceci est une nouvelle actu" });
await actu.save();
```

---

### ⚙️ Mise à jour du profil utilisateur

Les méthodes `updateX` disponibles sont :

- `updateDescription(data)`
- `updateInfo(data)`
- `updateSocial(data)`
- `updateLocality(data)`
- `updateSlug({ slug })`
- `updateImageProfil({ profil_avatar })`
- `updateSettings({ type, value })`

---

### 🔑 Sécurité et gestion de compte

#### Modifier mot de passe

```js
await user.changePassword({
  oldPassword: "secret1",
  newPassword: "secret2",
  newPassword2: "secret2"
});
```

#### Supprimer son compte

```js
await user.delete({ reason: "Je souhaite quitter la plateforme" });
```

## Licence

MIT

## 👤 Auteur

Thomas Craipeau

