# SamaliPoli — Plateforme de Gestion Universitaire

**Projet de fin d'études — Licence**  
**Application mobile universitaire · Backend API REST (Laravel) · Frontend mobile (Flutter)**

---

## Table des matières

1. [Résumé exécutif](#1-résumé-exécutif)
2. [Contexte et problématique](#2-contexte-et-problématique)
3. [Objectifs du projet](#3-objectifs-du-projet)
4. [Acteurs et rôles](#4-acteurs-et-rôles)
5. [Architecture globale](#5-architecture-globale)
6. [Stack technique](#6-stack-technique)
7. [Installation et configuration](#7-installation-et-configuration)
8. [Base de données](#8-base-de-données)
9. [Authentification et sécurité](#9-authentification-et-sécurité)
10. [Modules fonctionnels](#10-modules-fonctionnels)
11. [Référence API complète](#11-référence-api-complète)
12. [Notifications push (Firebase FCM)](#12-notifications-push-firebase-fcm)
13. [Structure du code source](#13-structure-du-code-source)
14. [Déploiement et exploitation](#14-déploiement-et-exploitation)
15. [Erreurs fréquentes](#15-erreurs-fréquentes)
16. [Perspectives d'évolution](#16-perspectives-dévolution)

---

## 1. Résumé exécutif

**SamaliPoli** est une solution numérique destinée à moderniser la gestion de la vie académique au sein d'un établissement universitaire. Le système repose sur une **architecture client-serveur** :

- **Frontend** : application mobile développée en **Flutter** (Android / iOS)
- **Backend** : API REST développée en **Laravel 12** (PHP 8.2)
- **Base de données** : **MySQL / MariaDB**
- **Services externes** : **Firebase Cloud Messaging** (notifications push), **Jitsi Meet** (appels vidéo)

L'application couvre l'emploi du temps, les présences, les notes, les notifications, la carte du campus, la messagerie (directe et de groupe), un assistant conversationnel (chatbot), et la gestion administrative des ressources universitaires.

---

## 2. Contexte et problématique

Dans un contexte universitaire, étudiants et enseignants utilisent souvent plusieurs canaux non centralisés (groupes WhatsApp, emails, affichages papier) pour :

- Consulter l'emploi du temps et les changements de cours
- Suivre les présences et les notes
- Recevoir les annonces officielles
- Communiquer entre eux

Cette dispersion entraîne des **retards d'information**, des **erreurs de suivi** et une **mauvaise traçabilité** des données académiques.

**SamaliPoli** propose une plateforme unique, accessible depuis le smartphone, avec des droits différenciés selon le rôle (étudiant, professeur, administrateur).

---

## 3. Objectifs du projet

| Objectif | Description |
|----------|-------------|
| Centraliser l'information | Un seul point d'accès pour EDT, présences, notes, annonces |
| Automatiser le suivi | Feuille d'appel numérique, calcul de moyennes, historique |
| Faciliter la communication | Chat privé, groupes, messages multimédias, notifications push |
| Aider l'orientation | Carte du campus, chatbot pour horaires et salles |
| Sécuriser l'accès | Authentification OAuth2 (Bearer token), contrôle par rôle |

---

## 4. Acteurs et rôles

| Rôle (`roles.nom`) | `role_id` | Permissions principales |
|--------------------|-----------|-------------------------|
| **student** | 4 | EDT, présences, notes, notifications, carte, chat, chatbot |
| **prof** | 5 | Cours, feuille d'appel, saisie des notes, notifications, chat |
| **admin** | 6 | CRUD utilisateurs, facultés, groupes, salles, bureaux |

Chaque utilisateur est lié à un **groupe** (`users.groupe_id`) via sa spécialité (major) et sa faculté.

---

## 5. Architecture globale

### 5.1 Schéma d'architecture

```
┌──────────────────────────────────────────────────────────────┐
│                    APPLICATION MOBILE                         │
│                      Flutter (Dart)                           │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│  │ Profil  │ │   EDT   │ │ Présence│ │  Chat   │ │ Notes  │ │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘ └────────┘ │
└────────────────────────────┬─────────────────────────────────┘
                             │ HTTPS REST
                             │ Authorization: Bearer {token}
                             │ Content-Type: application/json
                             │ multipart/form-data (fichiers)
┌────────────────────────────▼─────────────────────────────────┐
│                    BACKEND — Laravel 12 API                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐ │
│  │   Passport   │  │   Firebase   │  │  Services métier     │ │
│  │   OAuth2     │  │   FCM Push   │  │  ChatbotService, etc.│ │
│  └──────────────┘  └──────────────┘  └──────────────────────┘ │
│  Controllers · Models · Migrations · Validation · Storage       │
└────────────────────────────┬─────────────────────────────────┘
                             │ SQL (Eloquent ORM)
┌────────────────────────────▼─────────────────────────────────┐
│              MySQL / MariaDB 10.4 — Base `laravel`              │
└──────────────────────────────────────────────────────────────┘

Services externes :
  · Firebase Cloud Messaging — notifications push
  · Jitsi Meet (meet.jit.si) — appels vidéo WebRTC
```

### 5.2 Flux de données typique

```mermaid
sequenceDiagram
    participant App as Application Flutter
    participant API as API Laravel
    participant DB as MySQL
    participant FCM as Firebase FCM

    App->>API: POST /api/login
    API->>DB: Vérifier credentials
    API-->>App: token + user

    App->>API: POST /api/store-token (FCM)
    API->>DB: Enregistrer device_token

    App->>API: GET /api/emploi-du-temps/etudiant
    API->>DB: Requête EDT + présences
    API-->>App: JSON emploi_du_temps

    App->>API: POST /api/chat/send
    API->>DB: Insérer message
    API->>FCM: Notification au destinataire
    API-->>App: 201 message créé
```

### 5.3 Pattern architectural backend

Le backend suit le pattern **MVC** de Laravel, enrichi d'une couche **Service** pour la logique métier complexe :

| Couche | Rôle | Exemple |
|--------|------|---------|
| **Routes** (`routes/api.php`) | Définition des endpoints | `Route::post('/presence', ...)` |
| **Controllers** | Validation, orchestration, réponse JSON | `PresenceController` |
| **Models** | Relations Eloquent, accesseurs | `User`, `EmploiDuTemps` |
| **Services** | Logique réutilisable | `ChatbotService`, `FirebaseNotificationService` |
| **Migrations** | Schéma de base de données versionné | `create_presences_table` |

---

## 6. Stack technique

| Composant | Technologie | Version / Remarque |
|-----------|-------------|-------------------|
| Backend | Laravel | 12.x |
| Langage serveur | PHP | 8.2+ |
| Base de données | MySQL / MariaDB | 10.4 |
| Authentification API | Laravel Passport | OAuth2 Bearer Token |
| Notifications | Firebase FCM | `kreait/laravel-firebase` ^6 |
| ORM | Eloquent | Intégré Laravel |
| Frontend mobile | Flutter | Dart |
| Appels vidéo | Jitsi Meet | URLs `https://meet.jit.si/{room_id}` |
| Serveur local | XAMPP | Apache + MySQL |
| Stockage fichiers | Laravel Storage | `storage/app/public` |

---

## 7. Installation et configuration

### 7.1 Prérequis

- PHP 8.2 ou supérieur (extensions : `pdo_mysql`, `mbstring`, `openssl`, `fileinfo`)
- Composer 2.x
- MySQL / MariaDB (XAMPP recommandé sous Windows)
- Node.js (optionnel, pour les assets Vite)

### 7.2 Installation pas à pas

```bash
# 1. Aller dans le dossier du projet
cd your-project-name

# 2. Installer les dépendances PHP
composer install

# 3. Copier et configurer l'environnement
cp .env.example .env
php artisan key:generate

# 4. Configurer la base de données dans .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

# 5. Restaurer la base depuis le dump SQL
#    Sous Windows : double-cliquer sur restore-db.bat
#    Ou en ligne de commande :
C:\xampp\mysql\bin\mysql.exe -u root laravel < laravel.sql

# 6. Appliquer les migrations supplémentaires
php artisan migrate --force

# 7. Lien symbolique pour les fichiers publics (images, audio, etc.)
php artisan storage:link

# 8. Installer les clés OAuth2 (Passport)
php artisan passport:install

# 9. Vider les caches
php artisan config:clear
php artisan cache:clear
php artisan route:clear

# 10. Démarrer le serveur (accessible sur le réseau local)
php artisan serve --host=0.0.0.0 --port=8000
```

### 7.3 Configuration Firebase

Placer le fichier de credentials Firebase Admin SDK dans :

```
storage/firebase/google-services.json
```

Variables `.env` associées :

```env
FIREBASE_CREDENTIALS_PATH=google-services.json
APP_URL=http://192.168.X.X:8000
```

> **Important pour le mobile** : `APP_URL` doit correspondre à l'adresse IP locale de la machine qui héberge Laravel (visible via `ipconfig`), et non `localhost`.

### 7.4 Script de restauration de la base

Le fichier `restore-db.bat` automatise :

1. Suppression de la base `laravel` existante
2. Création d'une base vide (utf8mb4)
3. Import du fichier `laravel.sql`

Adapter le chemin MySQL dans le script si nécessaire :

```bat
set MYSQL=C:\xampp\mysql\bin\mysql.exe
```

---

## 8. Base de données

### 8.1 Modèle conceptuel (relations principales)

```
faculte
  └── majors (faculty_id)
        └── groupes (major_id)
              └── users (groupe_id, role_id)
                    └── notes (etudiant_id)
                    └── presences (etudiant_id)
                    └── device_tokens (user_id)
              └── emploi_du_temps (groupe_id)
                    └── professeur_cours (user_id ↔ emploi_du_temps_id)
                    └── presences (emploi_du_temps_id)
                    └── notes (emploi_du_temps_id)
  └── salle (faculte_id)
        └── emploi_du_temps (salle_id)

users
  └── messages (sender_id / receiver_id)
  └── chatbot_messages
  └── chat_group_members (groupes de discussion)
  └── video_calls (initiator / receiver)
```

### 8.2 Tables principales

| Table | Description | Champs clés |
|-------|-------------|-------------|
| `users` | Utilisateurs | `name`, `email`, `password`, `role_id`, `groupe_id`, `photo_profil`, `last_seen_at` |
| `roles` | Rôles | `nom` : `student`, `prof`, `admin` |
| `faculte` | Facultés | `name` |
| `majors` | Spécialités | `name`, `faculty_id` |
| `groupes` | Groupes d'étudiants | `noms`, `major_id` |
| `salle` | Salles | `nom`, `faculte_id`, `latitude`, `longitude` |
| `emploi_du_temps` | Cours / séances | `matiere`, `groupe_id`, `salle_id`, `debut`, `fin`, `jour_semaine` |
| `professeur_cours` | Affectation prof ↔ cours | `user_id`, `emploi_du_temps_id`, `est_principal` |
| `presences` | Feuille d'appel | `etudiant_id`, `emploi_du_temps_id`, `date`, `statut`, `marque_par` |
| `notes` | Notes académiques | `etudiant_id`, `emploi_du_temps_id`, `professeur_id`, `valeur`, `type`, `semestre` |
| `notifications` | Annonces | `titre`, `message`, `groupe_id`, `major_id`, `est_lue` |
| `messages` | Chat privé | `sender_id`, `receiver_id`, `content`, `type`, `file_path`, `reply_to_id` |
| `chat_groups` | Groupes de discussion | `name`, `description`, `created_by` |
| `chat_group_members` | Membres d'un groupe | `group_id`, `user_id`, `role` |
| `group_messages` | Messages de groupe | `group_id`, `sender_id`, `content`, `type`, `reply_to_id` |
| `video_calls` | Appels vidéo | `room_id`, `initiator_id`, `receiver_id`, `group_id`, `status` |
| `chatbot_messages` | Historique chatbot | `user_id`, `role`, `content`, `intent` |
| `device_tokens` | Tokens FCM | `user_id`, `token` |

### 8.3 Table `notes` (système de notation)

| Colonne | Type | Description |
|---------|------|-------------|
| `etudiant_id` | FK → users | Étudiant noté |
| `emploi_du_temps_id` | FK → emploi_du_temps | Cours concerné |
| `professeur_id` | FK → users | Professeur ayant saisi la note |
| `valeur` | decimal(5,2) | Note sur 20 |
| `type` | enum | `controle`, `examen`, `tp`, `projet`, `oral` |
| `semestre` | enum | `1` ou `2` |
| `commentaire` | text | Optionnel |

Contrainte d'unicité : un étudiant ne peut avoir qu'**une note par cours / type / semestre**.

### 8.4 Table `presences`

| Statut en base | Label affiché | Signification |
|----------------|---------------|---------------|
| `present` | **P** | Présent |
| `absent` | **A** | Absent |
| `null` | **-** | Non marqué |

---

## 9. Authentification et sécurité

### 9.1 Mécanisme

L'API utilise **Laravel Passport** (OAuth2). Chaque requête protégée doit inclure :

```http
Authorization: Bearer {access_token}
Accept: application/json
```

### 9.2 Endpoints publics (sans token)

| Méthode | Route | Description |
|---------|-------|-------------|
| `POST` | `/api/register` | Inscription (rôle `student` par défaut) |
| `POST` | `/api/login` | Connexion → retourne `token` + `user` |
| `POST` | `/api/forgot-password` | Demande de réinitialisation |
| `POST` | `/api/verify-reset-token` | Vérification du code |
| `POST` | `/api/reset-password` | Nouveau mot de passe |

### 9.3 Exemple de connexion

**Requête :**

```json
POST /api/login
{
  "email": "mohamed@samali.com",
  "password": "motdepasse"
}
```

**Réponse :**

```json
{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "user": {
    "id": 5,
    "name": "Mohamed",
    "email": "mohamed@samali.com",
    "role_id": 4,
    "groupe_id": 1
  }
}
```

### 9.4 Contrôle d'accès

- Les contrôleurs vérifient le rôle via `$user->role->nom` ou des méthodes `isProfesseur()`, `isAdmin()`
- Les professeurs ne peuvent noter que les cours qu'ils enseignent (`professeur_cours`)
- Un utilisateur ne peut supprimer que **ses propres messages** dans le chat

---

## 10. Modules fonctionnels

### 10.1 Emploi du temps

| Utilisateur | Endpoint | Contenu retourné |
|-------------|----------|------------------|
| Étudiant | `GET /api/emploi-du-temps/etudiant` | Cours du groupe + tableau `presences` par séance |
| Professeur | `GET /api/emploi-du-temps/professeur` | Cours assignés au prof |
| Professeur | `GET /api/emploi-du-temps/groupe/{id}` | EDT d'un groupe spécifique |

### 10.2 Gestion des présences

**Flux professeur :**

1. Ouvrir la feuille d'appel : `GET /api/presence/cours/{id}?date=2026-05-12`
2. Marquer un étudiant : `PUT /api/presence/update` avec `statut: "P"` ou `"A"`
3. Sauvegarder en masse : `POST /api/presence` avec un tableau JSON

**Flux étudiant :**

- Les présences apparaissent dans `GET /api/emploi-du-temps/etudiant` sous chaque cours :

```json
"presences": [
  { "date": "2026-05-05", "label": "P" },
  { "date": "2026-05-12", "label": "A" }
]
```

### 10.3 Système de notation

**Professeur — saisie en masse :**

```json
POST /api/notes
[
  {
    "etudiant_id": 5,
    "emploi_du_temps_id": 8,
    "valeur": 14.5,
    "type": "controle",
    "semestre": "1",
    "commentaire": "Bon travail"
  }
]
```

**Étudiant — consultation :**

```json
GET /api/notes/mes-notes?semestre=1

{
  "par_matiere": [
    {
      "matiere": "Algorithmique",
      "moyenne": 15.25,
      "notes": [
        { "valeur": 14.5, "type": "controle", "semestre": "1" },
        { "valeur": 16.0, "type": "examen", "semestre": "1" }
      ]
    }
  ],
  "moyenne_generale": 15.25
}
```

### 10.4 Notifications académiques

- Le professeur crée une notification ciblant un **groupe** ou une **spécialité (major)**
- Les étudiants concernés la reçoivent via `GET /api/notifications/recues`
- Marquage comme lue : `PUT /api/notifications/{id}/read`

### 10.5 Carte du campus

- Affichage des salles avec coordonnées GPS
- Heatmap de fréquentation par faculté ou spécialité
- Mise à jour des coordonnées par l'administrateur

### 10.6 Chatbot assistant

Assistant **basé sur des règles** (détection de mots-clés en français), sans API IA externe.

| Intention utilisateur | Intent | Exemple de réponse |
|----------------------|--------|-------------------|
| "mes cours aujourd'hui" | `schedule_today` | Liste des cours du jour |
| "ma présence" | `presence_global` | Taux P/A avec indicateur |
| "salles disponibles" | `rooms_available` | Salles libres actuellement |
| "mes informations" | `profile` | Profil complet |
| "aide" | `help` | Menu des commandes |

Le champ `response` est au format **Markdown** (affichage avec `flutter_markdown` côté mobile).

### 10.7 Messagerie instantanée

#### Chat privé (1 à 1)

Types de messages supportés : `text`, `audio`, `image`, `file`

| Fonctionnalité | Endpoint |
|----------------|----------|
| Liste des utilisateurs | `GET /api/chat/users` |
| Conversations | `GET /api/chat/conversations` |
| Messages d'une conversation | `GET /api/chat/{user_id}` |
| Envoyer un message | `POST /api/chat/send` |
| Supprimer son message | `DELETE /api/chat/messages/{id}` |
| Répondre à un message | Champ `reply_to_id` dans le body |
| Indicateur "en train d'écrire" | `POST/GET /api/chat/{user_id}/typing` |
| Statut en ligne | `PATCH /api/me/online` + `GET /api/users/{id}/status` |

#### Chat de groupe

| Fonctionnalité | Endpoint |
|----------------|----------|
| Liste des groupes | `GET /api/groups` |
| Créer un groupe | `POST /api/groups` |
| Messages du groupe | `GET /api/groups/{id}/messages` |
| Envoyer un message | `POST /api/groups/{id}/messages` |
| Supprimer son message | `DELETE /api/groups/{id}/messages/{msg_id}` |
| Gérer les membres | `POST/DELETE /api/groups/{id}/members` |

#### Format d'un message (réponse API)

```json
{
  "id": 42,
  "sender_id": 5,
  "type": "image",
  "content": "photo.jpg",
  "file_url": "http://192.168.1.141:8000/storage/chat/images/img_abc.jpg",
  "duration": null,
  "reply_to": {
    "id": 38,
    "sender_id": 3,
    "type": "text",
    "content": "Tu es disponible ?"
  },
  "is_read": false,
  "created_at": "2026-05-12T14:00:00.000000Z"
}
```

### 10.8 Appels vidéo (Jitsi Meet)

| Endpoint | Description |
|----------|-------------|
| `POST /api/calls/start` | Démarrer un appel (direct ou groupe) |
| `POST /api/calls/{room_id}/join` | Rejoindre un appel |
| `PUT /api/calls/{room_id}/end` | Terminer l'appel |
| `GET /api/calls/history` | Historique des appels |

**Réponse au démarrage :**

```json
{
  "call_id": 1,
  "room_id": "samali-xKj9mQrT1abc",
  "jitsi_url": "https://meet.jit.si/samali-xKj9mQrT1abc",
  "call_type": "direct",
  "status": "pending"
}
```

> **Note Flutter** : utiliser le package `jitsi_meet_flutter_sdk` (SDK natif), pas un WebView, pour éviter les erreurs de connexion WebRTC.

### 10.9 Administration

L'administrateur gère via `/api/admin/` :

- Utilisateurs (CRUD)
- Occupations de bureaux
- Rôles disponibles

---

## 11. Référence API complète

> Préfixe commun : `/api/`  
> Authentification : `Authorization: Bearer {token}` (sauf routes publiques)

### Authentification et profil

| Méthode | Route | Description |
|---------|-------|-------------|
| `POST` | `/register` | Inscription |
| `POST` | `/login` | Connexion |
| `POST` | `/forgot-password` | Mot de passe oublié |
| `POST` | `/verify-reset-token` | Vérifier le code |
| `POST` | `/reset-password` | Réinitialiser |
| `GET` | `/student/profile` | Profil étudiant (groupe, major, faculté) |
| `PUT` | `/profile` | Mettre à jour le profil |
| `PATCH` | `/me/online` | Mettre à jour `last_seen_at` |
| `GET` | `/users/{id}/status` | Statut en ligne d'un utilisateur |

### Ressources académiques (CRUD)

| Ressource | Routes |
|-----------|--------|
| Facultés | `GET/POST /facultes` · `GET/PUT/DELETE /facultes/{id}` |
| Majors | `GET/POST /majors` · `GET/PUT/DELETE /majors/{id}` |
| Groupes | `GET/POST /groupes` · `GET/PUT/DELETE /groupes/{id}` |
| Salles | `GET/POST /salles` · `GET/PUT/DELETE /salles/{id}` |
| Major-Salle | `GET /major-salle` · `POST /major-salle` · `DELETE /major-salle/{id}` |

### Emploi du temps

| Méthode | Route |
|---------|-------|
| `GET` | `/emploi-du-temps/etudiant` |
| `GET` | `/emploi-du-temps/professeur` |
| `GET` | `/emploi-du-temps/groupe/{id}` |
| `POST` | `/emploi-du-temps` |
| `PUT` | `/emploi-du-temps/{id}` |
| `DELETE` | `/emploi-du-temps/{id}` |

### Présences

| Méthode | Route |
|---------|-------|
| `POST` | `/presence` |
| `PUT` | `/presence/update` |
| `GET` | `/presence/cours/{id}?date=YYYY-MM-DD` |
| `GET` | `/presence/etudiant/{id}` |

### Notes

| Méthode | Route | Qui |
|---------|-------|-----|
| `GET` | `/notes/mes-notes?semestre=1` | Étudiant |
| `GET` | `/notes/cours/{id}?type=controle&semestre=1` | Professeur |
| `POST` | `/notes` | Professeur (bulk) |
| `PUT` | `/notes/{id}` | Professeur |
| `DELETE` | `/notes/{id}` | Professeur |
| `GET` | `/notes/etudiant/{id}` | Professeur |

### Notifications

| Méthode | Route |
|---------|-------|
| `POST` | `/store-token` |
| `POST` | `/notifications` |
| `GET` | `/notifications/recues` |
| `GET` | `/notifications/envoyees` |
| `PUT` | `/notifications/{id}/read` |
| `DELETE` | `/notifications/{id}` |

### Chat

| Méthode | Route |
|---------|-------|
| `GET` | `/chat/users` |
| `GET` | `/chat/conversations` |
| `GET` | `/chat/{user_id}` |
| `POST` | `/chat/send` |
| `DELETE` | `/chat/messages/{id}` |
| `PUT` | `/chat/{user_id}/read` |
| `POST/GET` | `/chat/{user_id}/typing` |

### Groupes de discussion

| Méthode | Route |
|---------|-------|
| `GET` | `/groups` |
| `POST` | `/groups` |
| `GET` | `/groups/{id}` |
| `GET` | `/groups/{id}/messages` |
| `POST` | `/groups/{id}/messages` |
| `DELETE` | `/groups/{id}/messages/{msg_id}` |
| `POST/GET` | `/groups/{id}/typing` |

### Appels vidéo

| Méthode | Route |
|---------|-------|
| `POST` | `/calls/start` |
| `POST` | `/calls/{room_id}/join` |
| `PUT` | `/calls/{room_id}/end` |
| `GET` | `/calls/history` |

### Chatbot

| Méthode | Route |
|---------|-------|
| `POST` | `/chatbot/message` |
| `GET` | `/chatbot/history` |
| `DELETE` | `/chatbot/history` |

### Carte

| Méthode | Route |
|---------|-------|
| `GET` | `/map/salles` |
| `GET` | `/map/salle/{id}` |
| `GET` | `/map/student` |
| `GET` | `/map/heatmap/faculte/{id}` |
| `PUT` | `/map/salle/{id}/coordinates` |

### Administration

| Méthode | Route |
|---------|-------|
| `GET/POST` | `/admin/users` |
| `PUT/DELETE` | `/admin/users/{id}` |
| `GET` | `/admin/roles` |
| `GET/POST/PUT/DELETE` | `/admin/occupations-bureaux` |

---

## 12. Notifications push (Firebase FCM)

### 12.1 Enregistrement du token

```json
POST /api/store-token
{ "token": "fcm_device_token_ici" }
```

Le backend effectue un **upsert** : un seul token actif par utilisateur.

### 12.2 Envoi automatique

Une notification FCM est envoyée automatiquement lors de :

| Événement | Titre | Corps |
|-----------|-------|-------|
| Nouveau message privé | Nom de l'expéditeur | Contenu ou 🎤/📷/📎 |
| Nouveau message de groupe | Nom du groupe | `{expéditeur} : {contenu}` |
| Appel vidéo entrant | 📞 Appel vidéo de {nom} | Appuyez pour rejoindre |

### 12.3 Payload `data` (navigation Flutter)

```json
{
  "type": "chat",
  "sender_id": "5",
  "sender_name": "Mohamed",
  "message_id": "42",
  "chat_type": "direct"
}
```

Pour un groupe : `"chat_type": "group"`, `"group_id": "9"`.

---

## 13. Structure du code source

```
your-project-name/
├── app/
│   ├── Http/Controllers/Api/
│   │   ├── AuthController.php           # Auth, profil, groupes prof
│   │   ├── EmploiDuTempsController.php  # Emploi du temps
│   │   ├── PresenceController.php       # Présences P/A/-
│   │   ├── NoteController.php           # Notes académiques
│   │   ├── NotificationController.php   # Notifications académiques
│   │   ├── NotificationsController.php  # FCM store-token
│   │   ├── ChatController.php           # Chat privé
│   │   ├── GroupChatController.php      # Chat de groupe
│   │   ├── VideoCallController.php      # Appels Jitsi
│   │   ├── ChatbotController.php        # Chatbot
│   │   ├── UserStatusController.php     # Online + typing
│   │   ├── MapController.php            # Carte campus
│   │   ├── AdminController.php          # Administration
│   │   ├── FaculteController.php
│   │   ├── GroupeController.php
│   │   ├── MajorController.php
│   │   └── SalleController.php
│   │
│   ├── Models/
│   │   ├── User.php, Role.php
│   │   ├── Faculte.php, Major.php, Groupe.php, Salle.php
│   │   ├── EmploiDuTemps.php, ProfesseurCours.php
│   │   ├── Presence.php, Note.php
│   │   ├── Notification.php, NotificationDocument.php
│   │   ├── Message.php, GroupMessage.php
│   │   ├── ChatGroup.php, VideoCall.php
│   │   ├── ChatbotMessage.php, DeviceToken.php
│   │   └── OccupationBureau.php
│   │
│   └── Services/
│       ├── FirebaseNotificationService.php
│       └── ChatbotService.php
│
├── database/migrations/          # Schéma versionné
├── routes/api.php               # Toutes les routes API
├── storage/
│   ├── app/public/              # Fichiers uploadés (chat, audio)
│   └── firebase/                # Credentials Firebase
├── public/storage/              # Lien symbolique → storage/app/public
├── laravel.sql                  # Dump base de données initiale
├── restore-db.bat               # Script restauration Windows
├── .env                         # Configuration environnement
└── README.md                    # Ce document
```

---

## 14. Déploiement et exploitation

### 14.1 Démarrage quotidien

```bash
# 1. Démarrer XAMPP (Apache + MySQL)
# 2. Lancer l'API
php artisan serve --host=0.0.0.0 --port=8000

# 3. Dans Flutter, configurer l'URL de base :
# http://192.168.X.X:8000/api/
```

### 14.2 Commandes utiles

| Commande | Usage |
|----------|-------|
| `php artisan migrate --force` | Appliquer les migrations |
| `php artisan migrate:status` | État des migrations |
| `php artisan route:list` | Lister toutes les routes |
| `php artisan config:clear` | Vider le cache config |
| `php artisan storage:link` | Recréer le lien fichiers publics |
| `php artisan passport:install` | Réinstaller les clés OAuth2 |

### 14.3 Stockage des fichiers multimédias

| Type | Dossier | Exemple URL |
|------|---------|-------------|
| Audio | `storage/app/public/voice_messages/` | `/storage/voice_messages/voice_xxx.m4a` |
| Images chat | `storage/app/public/chat/images/` | `/storage/chat/images/img_xxx.jpg` |
| Fichiers chat | `storage/app/public/chat/files/` | `/storage/chat/files/file_xxx.pdf` |

Les URLs sont construites dynamiquement avec l'hôte de la requête pour être accessibles depuis le mobile sur le réseau local.

---

## 15. Erreurs fréquentes

| Erreur | Cause probable | Solution |
|--------|----------------|----------|
| `401 Unauthorized` | Token expiré ou absent | Se reconnecter, renvoyer le header Bearer |
| `500` sur `/api/chatbot/message` | Table `chatbot_messages` absente | `php artisan migrate --force` |
| `Column 'content' cannot be null` | Message audio sans contenu | Migration `make_messages_content_nullable` |
| `Data truncated for column 'type'` | ENUM sans valeur `audio`/`image` | Exécuter les migrations de correction ENUM |
| `file_url` en `localhost` | `APP_URL` incorrect | Mettre l'IP locale dans `.env` + `config:clear` |
| Fichiers 404 | Lien `public/storage` manquant | `php artisan storage:link` ou `mklink /J` sous Windows |
| `mysql` non reconnu | MySQL absent du PATH | Utiliser le chemin complet dans `restore-db.bat` |
| Liste étudiants vide (notes) | Mauvais filtre `groupe_id` / `role_id` | Vérifier `users.groupe_id` et `roles.nom = student` |
| Crash Flutter `String is not subtype of int` | `sender` en String au lieu d'objet | Backend corrigé : `sender` est un objet `{id, name}` |
| Appel vidéo déconnecté | WebView au lieu du SDK Jitsi | Utiliser `jitsi_meet_flutter_sdk` |

---

## 16. Perspectives d'évolution

| Amélioration | Description |
|--------------|-------------|
| WebSocket temps réel | Remplacer le polling typing par Laravel Reverb |
| Export PDF | Bulletins de notes et relevés de présence |
| Tableau de bord admin | Statistiques globales (taux d'absentéisme, moyennes) |
| Chatbot IA | Intégration OpenAI / Gemini pour des réponses plus naturelles |
| Déploiement cloud | Hébergement sur VPS avec HTTPS (Let's Encrypt) |
| Tests automatisés | PHPUnit pour les endpoints critiques |

---

## Annexe — Convention des réponses API

### Codes HTTP utilisés

| Code | Signification |
|------|---------------|
| `200` | Succès (lecture, mise à jour) |
| `201` | Ressource créée |
| `204` | Succès sans contenu |
| `401` | Non authentifié |
| `403` | Non autorisé (rôle ou propriétaire) |
| `404` | Ressource introuvable |
| `422` | Données invalides (validation Laravel) |
| `500` | Erreur serveur |

### Format des listes

Les listes sont toujours retournées dans un objet avec une clé explicite :

```json
{ "groups": [...] }
{ "messages": [...] }
{ "etudiants": [...] }
{ "conversations": [...] }
```

Les identifiants (`id`, `sender_id`, `groupe_id`, etc.) sont des **entiers** (`int`), jamais des chaînes.

---

*Document généré pour le projet SamaliPoli — Licence · Backend Laravel 12 · Frontend Flutter · MySQL · Firebase FCM*
