# Documentation API Blog Leifo

## Vue d'ensemble

L'API blog de Leifo permet de gérer les articles de blog de manière sécurisée. Seuls les utilisateurs authentifiés avec le rôle **admin** peuvent créer, modifier, publier ou supprimer des articles.

## Authentification

Toutes les opérations d'administration nécessitent :
- Une authentification via Manus OAuth
- Le rôle `admin` dans la base de données

Pour promouvoir un utilisateur en admin, modifiez directement le champ `role` dans la table `users` via l'interface de gestion de la base de données.

## Endpoints disponibles

### Endpoints publics

#### `blog.getAll()`
Récupère tous les articles publiés.

**Retour :**
```typescript
Array<{
  id: number;
  title: string;
  slug: string;
  excerpt: string;
  content: string;
  heroImage: string;
  category: string;
  tags: string[];
  author: string;
  readingTime: number;
  published: boolean;
  publishedAt: Date | null;
  seoTitle: string | null;
  seoDescription: string | null;
  seoKeywords: string[];
}>
```

#### `blog.getRecent({ limit?: number })`
Récupère les articles récents publiés (par défaut 3).

**Paramètres :**
- `limit` (optionnel) : Nombre d'articles à récupérer

#### `blog.getBySlug({ slug: string })`
Récupère un article spécifique par son slug.

**Paramètres :**
- `slug` : Le slug unique de l'article

### Endpoints admin (authentification requise)

#### `blog.create(data)`
Crée un nouvel article.

**Paramètres :**
```typescript
{
  title: string;              // Min 5 caractères
  slug: string;               // Min 3 caractères, doit être unique
  excerpt: string;            // Min 20 caractères
  content: string;            // Min 100 caractères (Markdown supporté)
  heroImage: string;          // URL valide de l'image
  category: string;
  tags: string[];
  author?: string;            // Par défaut "Leifo"
  readingTime: number;        // Temps de lecture en minutes
  seoTitle?: string;
  seoDescription?: string;
  seoKeywords?: string[];
  published?: boolean;        // Par défaut false
}
```

**Retour :**
```typescript
{
  success: true;
  id: number;  // ID de l'article créé
}
```

#### `blog.update({ id, ...updates })`
Met à jour un article existant.

**Paramètres :**
```typescript
{
  id: number;                 // ID de l'article à modifier
  title?: string;
  slug?: string;
  excerpt?: string;
  content?: string;
  heroImage?: string;
  category?: string;
  tags?: string[];
  author?: string;
  readingTime?: number;
  seoTitle?: string;
  seoDescription?: string;
  seoKeywords?: string[];
}
```

**Retour :**
```typescript
{
  success: true;
}
```

#### `blog.publish({ id, published })`
Publie ou dépublie un article.

**Paramètres :**
```typescript
{
  id: number;
  published: boolean;
}
```

**Retour :**
```typescript
{
  success: true;
}
```

#### `blog.delete({ id })`
Supprime définitivement un article.

**Paramètres :**
```typescript
{
  id: number;
}
```

**Retour :**
```typescript
{
  success: true;
}
```

## Interface d'administration

Une interface web complète est disponible à l'adresse `/admin/blog` pour gérer les articles via une interface graphique.

### Fonctionnalités de l'interface :
- ✅ Liste de tous les articles (publiés et brouillons)
- ✅ Création d'articles avec formulaire complet
- ✅ Édition d'articles existants
- ✅ Publication/Dépublication en un clic
- ✅ Suppression d'articles
- ✅ Support du Markdown pour le contenu
- ✅ Gestion des tags et catégories
- ✅ Optimisation SEO (titre, description, mots-clés)

## Utilisation depuis le code

### Exemple : Créer un article

```typescript
import { trpc } from "@/lib/trpc";

const createMutation = trpc.blog.create.useMutation();

const handleCreateArticle = async () => {
  try {
    const result = await createMutation.mutateAsync({
      title: "Mon nouvel article",
      slug: "mon-nouvel-article",
      excerpt: "Ceci est un extrait de mon article...",
      content: "# Titre\n\nContenu en **Markdown**...",
      heroImage: "https://example.com/image.jpg",
      category: "Web Design",
      tags: ["design", "tendances"],
      author: "Leifo",
      readingTime: 5,
      published: true,
    });
    
    console.log("Article créé avec l'ID:", result.id);
  } catch (error) {
    console.error("Erreur:", error.message);
  }
};
```

### Exemple : Mettre à jour un article

```typescript
const updateMutation = trpc.blog.update.useMutation();

await updateMutation.mutateAsync({
  id: 123,
  title: "Nouveau titre",
  excerpt: "Nouvel extrait mis à jour",
});
```

### Exemple : Publier un article

```typescript
const publishMutation = trpc.blog.publish.useMutation();

await publishMutation.mutateAsync({
  id: 123,
  published: true,
});
```

## Sécurité

- ✅ Toutes les mutations admin vérifient le rôle de l'utilisateur
- ✅ Les utilisateurs non-admin reçoivent une erreur `FORBIDDEN`
- ✅ Les utilisateurs non-authentifiés ne peuvent pas accéder aux endpoints admin
- ✅ Validation des données avec Zod avant insertion en base
- ✅ Les slugs doivent être uniques (contrainte de base de données)

## Tests

Des tests unitaires complets sont disponibles dans `server/blog-api.test.ts` :
- ✅ Création d'articles par admin
- ✅ Refus d'accès pour non-admin
- ✅ Mise à jour d'articles
- ✅ Publication/Dépublication
- ✅ Accès public aux articles publiés

Exécuter les tests :
```bash
pnpm test
```

## Notes importantes

1. **Slug unique** : Chaque article doit avoir un slug unique. Le système renvoie une erreur si vous tentez de créer un article avec un slug déjà existant.

2. **Markdown** : Le champ `content` supporte le format Markdown. Le contenu est automatiquement rendu avec le composant `Streamdown` sur les pages de détail.

3. **Images** : Les images doivent être hébergées et accessibles via URL. Vous pouvez utiliser un service d'hébergement d'images ou le système de stockage S3 intégré.

4. **SEO** : Les champs SEO sont optionnels mais recommandés pour améliorer le référencement de vos articles.

5. **Publication** : Un article créé avec `published: false` est considéré comme un brouillon et n'apparaît pas sur le site public. Utilisez `blog.publish()` pour le publier ultérieurement.
