# Guide de déploiement — ACPortail

Ce projet fonctionne avec **deux composants** qui doivent être déployés séparément :

| Composant | Rôle | Où l'héberger |
|---|---|---|
| **acportail** (ce dépôt, Laravel) | Le portail utilisé par les techniciens / le bureau | Hébergement web (OVH VPS recommandé), sur le sous-domaine **logiciel.acportail.fr** |
| **api** (dépôt séparé, Slim/PHP) | Passerelle vers **Gestan** (ODBC/HFSQL) + base MySQL partagée | **Sur/à côté de la machine Gestan** (accès ODBC local) |

> ⚠️ L'`api` **ne peut pas** être sur l'hébergement distant : elle a besoin d'un accès **ODBC local à Gestan**. C'est acportail (distant) qui appelle l'api (locale) en HTTPS.

---

## 1. Architecture

```
Techniciens ─HTTPS─> acportail (Laravel, hébergé OVH)   logiciel.acportail.fr
                          │
                          ├─HTTPS+token─> api (Slim, près de Gestan)
                          │                   ├─ ODBC ─> Gestan (HFSQL)
                          │                   └─ MySQL ─> base partagée (plannings, users…)
                          │
                          ├─HTTPS─> API Évaluations (absences)   evaluations.acportail.fr
                          └─HTTPS─> API jours fériés (gouv)       calendrier.api.gouv.fr

Site vitrine WordPress   acportail.fr
        └─HTTPS+token─> acportail /api/wordpress/modeles  (catalogue des modèles, voir API_WORDPRESS.md)
                              └─ images servies par modeles.acportail.fr

Base MySQL acportail (locale au portail) : rapports, dates_bloques, permissions, cache, sessions…
```

Points clés :
- Les **lectures** (plannings, interventions) passent par l'`api`. En cas de coupure, acportail sert la **dernière copie en cache** (bandeau « données en cache »).
- Les **rapports d'intervention** sont enregistrés localement puis injectés dans Gestan via l'`api` ; si la liaison est coupée, ils sont **rejoués automatiquement** (voir §6 le cron).

---

## 2. Prérequis

**acportail (Laravel 12)**
- PHP **8.2+** (8.3 recommandé) avec extensions : `pdo_mysql`, `mbstring`, `curl`, `openssl`, `intl`, `bcmath`, `fileinfo`, `xml`.
- Composer 2
- Node.js **20+** (build des assets Vite 6)
- MySQL/MariaDB
- Accès **cron** (indispensable pour la reprise des rapports)

**api (Slim/PHP)**
- PHP **8.1+** avec extensions : `pdo_odbc` (ODBC), `pdo_mysql`, `curl`, `mbstring`
- Un **DSN ODBC** configuré vers Gestan (HFSQL)
- Composer 2

---

## 3. Déploiement de l'`api` (à faire en premier, près de Gestan)

```bash
git clone <repo-api> api && cd api
composer install --no-dev --optimize-autoloader
cp .env.example .env   # puis remplir (voir §7)
```

1. Configurer le **DSN ODBC** `ODBC_DSN_GESTAN` (côté OS) pointant vers la base HFSQL Gestan.
2. Renseigner le `.env` (DB MySQL partagée, tokens, TTL de cache, CORS — voir §7).
3. S'assurer que le dossier de cache (`API_FILE_CACHE_DIR`) est **inscriptible**.
4. Servir le dossier **`public/`** via Apache/Nginx en **HTTPS**, sur une URL joignable par acportail.
5. Vérifier : `curl -H "Authorization: Bearer <API_TOKEN_ACPORTAIL>" https://<api>/acportail/plannings/week` doit répondre du JSON.

> À chaque évolution de l'`api` (ex. routes `plannings/batch`, `plannings/day`, `reference`), **redéployer l'api** puis, si besoin, purger son cache fichier : `DELETE https://<api>/acportail/cache/plannings`.

---

## 4. Mise en ligne d'acportail sur `logiciel.acportail.fr`

Procédure complète, dans l'ordre. Les étapes 4.4 et 4.5 sont celles qu'on oublie :
**ni la base ni les images ne sont dans le dépôt**.

### 4.1 DNS

Dans la zone DNS d'`acportail.fr` (espace client OVH → *Domaines* → *Zone DNS*), ajouter :

| Sous-domaine | Type | Cible |
|---|---|---|
| `logiciel` | A | l'IPv4 du serveur |
| `logiciel` | AAAA | l'IPv6 du serveur, s'il en a une |

Attendre la propagation, puis vérifier avant toute demande de certificat :

```bash
dig +short logiciel.acportail.fr
```

Tant que cette commande ne renvoie pas l'IP du serveur, Let's Encrypt échouera.

### 4.2 Récupérer le code

> ⚠️ **La branche à déployer est `acportail`, pas `main`.** `main` n'a reçu que le commit
> initial, tout le projet vit sur `acportail`.

```bash
sudo mkdir -p /var/www && cd /var/www
sudo git clone -b acportail git@gitlab.com:cyril28/acportail.git acportail
cd acportail
```

Le dépôt est privé : prévoir soit une **clé de déploiement** SSH (GitLab → *Settings* →
*Repository* → *Deploy keys*), soit un clone HTTPS avec un jeton d'accès personnel.

```bash
composer install --no-dev --optimize-autoloader
npm ci && npm run build
```

Le `npm run build` n'est pas optionnel : les pages de connexion et d'inscription passent par
`@vite` (`resources/views/layouts/guest.blade.php`). Sans le build, `public/build/manifest.json`
n'existe pas et **la page de login renvoie une erreur 500**. Les pages d'administration, elles,
utilisent les assets statiques de `public/assets/`, déjà dans le dépôt.

### 4.3 Le fichier `.env`

```bash
cp .env.example .env
php artisan key:generate
nano .env
```

À renseigner impérativement (détail complet en §7) :

```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://logiciel.acportail.fr

DB_DATABASE=acportail
DB_USERNAME=...
DB_PASSWORD=...

ACPORTAIL_API_URL=https://<url-publique-de-l-api>/acportail
GESTAN_API_URL=https://<url-publique-de-l-api>/gestan
WORDPRESS_API_TOKEN=...
MODELES_IMAGE_BASE=https://logiciel.acportail.fr/storage
```

`APP_DEBUG=false` n'est pas cosmétique : en cas d'erreur, Laravel afficherait sinon la trace
complète, avec le contenu du `.env`, à n'importe quel visiteur.

`MODELES_IMAGE_BASE` détermine les URLs d'images renvoyées par l'API vitrine. Le mettre sur
`https://logiciel.acportail.fr/storage` si les images sont servies par le portail, ou sur
`https://modeles.acportail.fr` si elles restent hébergées à part (voir `API_WORDPRESS.md`).

### 4.4 La base de données

Le dépôt contient les migrations et les seeders, mais **pas les données** : ni les utilisateurs,
ni les plannings locaux, ni le catalogue. Deux chemins possibles.

**Reprendre la base existante (recommandé)** — environ 10 Mo. Depuis le poste de
développement, dans Git Bash :

```bash
"/c/wamp64/bin/mysql/mysql8.4.7/bin/mysqldump" -u laravel -p acportail > acportail.sql
scp acportail.sql user@logiciel.acportail.fr:~/
```

Puis sur le serveur, la création de la base et de l'utilisateur avec un compte qui a les
droits (`root` ou l'administrateur fourni par l'hébergeur) :

```bash
mysql -u root -p -e "CREATE DATABASE acportail CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p -e "CREATE USER 'laravel'@'localhost' IDENTIFIED BY '<mot-de-passe>'; GRANT ALL ON acportail.* TO 'laravel'@'localhost';"
mysql -u laravel -p acportail < ~/acportail.sql
cd /var/www/acportail && php artisan migrate --force
```

Le `migrate --force` après l'import applique les migrations qui manqueraient au dump.

> ⚠️ **`php artisan db:seed` ne tourne pas sur le serveur hébergé.** Le seeder racine appelle
> `TempUsersSeeder` et les seeders de plannings, qui lisent la connexion `hfsql_acportail`,
> c'est-à-dire un **DSN ODBC vers Gestan** — disponible uniquement sur la machine près de
> Gestan. Sur OVH, la reprise du dump est donc le seul chemin praticable.

Seuls les seeders du catalogue se suffisent à eux-mêmes, car ils lisent les CSV du dépôt.
Utile pour rejouer le catalogue sans toucher au reste :

```bash
php artisan db:seed --class="Database\Seeders\Modeles\ModelesDatabaseSeeder" --force
```

### 4.5 Les images des modèles — 103 Mo, absentes du dépôt

`storage/` est ignoré par git : après un `clone`, `storage/app/public/images` est **vide**.
Sans cette étape, la calculatrice, les fiches modèles, les fiches types et l'API vitrine
renvoient toutes des images cassées.

Depuis le poste de développement :

```bash
scp -r storage/app/public/images user@logiciel.acportail.fr:/var/www/acportail/storage/app/public/
```

(ou WinSCP / FileZilla en glisser-déposer, c'est 103 Mo). Puis, sur le serveur :

```bash
php artisan storage:link
```

`storage:link` crée `public/storage` → `storage/app/public`. Sans lui, les images sont bien
sur le disque mais aucune URL ne les atteint.

### 4.6 Droits et optimisations

```bash
sudo chown -R www-data:www-data /var/www/acportail
sudo find /var/www/acportail/storage /var/www/acportail/bootstrap/cache -type d -exec chmod 775 {} \;

php artisan config:cache
php artisan route:cache
php artisan view:cache
```

Seuls `storage/` et `bootstrap/cache/` ont besoin d'être inscriptibles par le serveur web.

> Après chaque modification du `.env` en production, **refaire `php artisan config:cache`** :
> tant que le cache de configuration n'est pas régénéré, les anciennes valeurs restent actives.

### 4.7 Serveur web + HTTPS

**La racine web est `public/`, jamais la racine du projet.** Si le docroot pointe une
arborescence plus haut, `.env`, `storage/` et le code source deviennent téléchargeables.

Nginx :

```nginx
server {
    listen 80;
    server_name logiciel.acportail.fr;
    root /var/www/acportail/public;
    index index.php;

    location / { try_files $uri $uri/ /index.php?$query_string; }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    location ~ /\.(?!well-known).* { deny all; }

    client_max_body_size 20M;
}
```

Apache :

```apache
<VirtualHost *:80>
    ServerName logiciel.acportail.fr
    DocumentRoot /var/www/acportail/public

    <Directory /var/www/acportail/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
```

`AllowOverride All` est nécessaire : c'est le `.htaccess` livré par Laravel dans `public/`
qui réécrit les URLs. Activer aussi `sudo a2enmod rewrite`.

Puis le certificat, une fois le DNS propagé :

```bash
sudo certbot --nginx -d logiciel.acportail.fr
```

Certbot ajoute l'écoute en 443, le certificat et la redirection HTTP → HTTPS, et installe son
propre renouvellement automatique. Vérifier : `sudo certbot renew --dry-run`.

`client_max_body_size` (Nginx) ou `upload_max_filesize` / `post_max_size` (PHP) doivent rester
au-dessus du poids des photos envoyées depuis les rapports d'intervention.

**Sur hébergement mutualisé OVH plutôt que VPS**, il n'y a ni vhost ni certbot à écrire :
dans l'espace client, *Hébergements* → *Multisite*, ajouter `logiciel.acportail.fr` en
pointant le **dossier racine** sur `acportail/public`, et cocher le certificat SSL gratuit.
Attention alors au §6 : la fréquence « chaque minute » du cron n'est pas garantie en mutualisé,
et `storage:link` suppose que les liens symboliques soient autorisés.

## 5. La liaison acportail ↔ api (le point sensible)

acportail est **hébergé** mais l'`api` est **locale** (près de Gestan). Il faut donc rendre l'`api` joignable **de façon sécurisée** :

- Exposer l'`api` en **HTTPS** derrière un reverse-proxy, **OU** via **VPN**, **OU** un tunnel (Cloudflare Tunnel, etc.).
- **Ne jamais** l'exposer sans protection : l'accès est déjà protégé par **token** (`Authorization: Bearer`), garde-le + restreins par **firewall/IP** + **CORS** (`CORS_ORIGINS_ACPORTAIL`).
- Dans acportail, `ACPORTAIL_API_URL` et `GESTAN_API_URL` doivent pointer vers cette URL publique de l'`api`, avec les tokens correspondants.

> Si l'`api` est injoignable, acportail **ne plante pas** : lectures servies depuis le cache (bandeau), rapports mis en file d'attente. Mais aucune donnée fraîche ne remontera tant que la liaison n'est pas rétablie.

---

## 6. Cron — reprise automatique des rapports (IMPORTANT)

Les rapports non injectés dans Gestan sont rejoués par la commande `interventions:sync-rapports`, planifiée **chaque minute** (`routes/console.php`). Pour que ça tourne, le **planificateur Laravel doit être déclenché chaque minute** par le cron du serveur.

**Sur VPS OVH (Linux)** — `crontab -e` :

```bash
* * * * * cd /var/www/acportail && php artisan schedule:run >> /dev/null 2>&1
```

- Une seule ligne, réglée **une fois**. Le démon cron l'exécute automatiquement chaque minute.
- `schedule:run` est un **one-shot** : il regarde ce qui est dû et le lance, puis s'arrête. C'est le cron qui le rappelle chaque minute.

**Mutualisé OVH** : via *Espace client → Hébergements → Tâches planifiées*. ⚠️ la fréquence « chaque minute » n'est pas garantie sur mutualisé — si c'est trop espacé, prévoir le repli « middleware terminable » (sync opportuniste au fil du trafic) ou passer sur VPS.

**En local (dev)** : `php artisan schedule:work` (reste allumé, appelle `schedule:run` chaque minute ; Ctrl+C pour arrêter). À ne PAS utiliser en prod.

Test manuel : `php artisan interventions:sync-rapports`.

---

## 7. Variables d'environnement

### acportail (`.env`)

| Clé | Description |
|---|---|
| `APP_NAME`, `APP_ENV=production`, `APP_DEBUG=false`, `APP_URL` | App Laravel |
| `APP_KEY` | Généré via `php artisan key:generate` |
| `DB_CONNECTION=mysql`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD` | Base MySQL du portail |
| `CACHE_STORE=database`, `SESSION_DRIVER=database`, `QUEUE_CONNECTION=database` | Stockage en base (garder tel quel) |
| `ACPORTAIL_API_URL`, `ACPORTAIL_API_TOKEN` | URL + token de l'`api` (plannings) |
| `ACPORTAIL_API_TTL_LIST/WEEK/MONTH/SEARCH/FIND` | TTL du cache plannings (secondes) |
| `GESTAN_API_URL`, `GESTAN_API_TOKEN` | URL + token de l'`api` (interventions / clients Gestan) |
| `API_URL_EVALUATIONS`, `API_TOKEN_EVALUATIONS` | API Évaluations (absences) |
| `WORDPRESS_API_TOKEN` | Token attendu sur `/api/wordpress/*` (catalogue du site vitrine, voir `API_WORDPRESS.md`) |
| `MODELES_IMAGE_BASE` | Base des URLs d'images renvoyées par cette API |
| `MAIL_*` | Envoi d'e-mails |

### api (`.env`)

| Clé | Description |
|---|---|
| `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD` | MySQL partagée (plannings, users, pivots…) |
| `ODBC_DSN_GESTAN`, `ODBC_USERNAME`, `ODBC_PASSWORD` | Connexion ODBC à Gestan (HFSQL) |
| `API_TOKEN_ACPORTAIL`, `API_TOKEN_GESTAN`, `API_TOKEN_EVALUATIONS`, … | Tokens attendus par domaine (doivent matcher côté acportail) |
| `CORS_ORIGINS_ACPORTAIL`, … | Origines autorisées (mettre l'URL du portail) |
| `API_FILE_CACHE_DIR` | Dossier de cache fichier (inscriptible) |
| `*_GESTAN_CACHE_TTL` (`LIST`, `WEEK`, `MONTH`, `FIND`, `SEARCH`, `HISTORY`) | TTL des caches Gestan |

> 🔐 Ne **jamais** committer les `.env`. Utiliser des tokens forts et différents par environnement.

---

## 8. SSL / certificats

- En **local (WAMP)**, la vérification SSL des API HTTPS externes (Évaluations, jours fériés) est désactivée **uniquement si `APP_ENV=local`** (`withoutVerifying`).
- En **production**, la vérification SSL est **active** : le serveur doit avoir un **bundle CA valide** (c'est le cas par défaut sur un Linux à jour). Rien à désactiver.

---

## 9. Checklist post-déploiement

- [ ] `https://<portail>` s'ouvre, connexion OK.
- [ ] Le **planning** s'affiche (jour/semaine/mois) → l'`api` est bien joignable.
- [ ] La liste **interventions** s'affiche + les **stats**.
- [ ] Ouvrir une intervention, **créer un rapport** → statut « Synchronisé » (Gestan up).
- [ ] Couper l'`api` → recharger : bandeau « **données en cache** » (pas de 500), un nouveau rapport passe en « **en attente** ».
- [ ] Remettre l'`api` → attendre ~1 min (cron) : le rapport passe « Synchronisé » tout seul (ou bouton **réessayer**).
- [ ] Le **bandeau jours fériés / dates bloquées** apparaît dans le planning.
- [ ] Les **absences** (index, fiche user, colonne congé) s'affichent (API Évaluations).
- [ ] `php artisan schedule:list` montre `interventions:sync-rapports` chaque minute.
- [ ] Les **images des modèles** s'affichent (fiche type, calculatrice) → `storage/app/public/images` bien transféré et `storage:link` fait.
- [ ] `curl https://logiciel.acportail.fr/api/wordpress/modeles` renvoie **401**, et **200** avec le bon token → l'API vitrine est en place et protégée.
- [ ] `curl -I https://logiciel.acportail.fr` : redirection **301 vers HTTPS**, certificat valide.
- [ ] `https://logiciel.acportail.fr/.env` renvoie **403/404** et non le contenu du fichier → le docroot pointe bien sur `public/`.

---

## 10. Mises à jour ultérieures

```bash
cd /var/www/acportail
git pull origin acportail
composer install --no-dev --optimize-autoloader
php artisan migrate --force
npm ci && npm run build
php artisan config:cache && php artisan route:cache && php artisan view:cache
```

- `git pull origin acportail` : c'est la branche de travail, `main` n'est pas à jour.
- Les **images** ajoutées depuis le dernier déploiement ne suivent pas le `git pull`, elles
  vivent dans `storage/`. Les envoyer séparément, puis relancer le seeder si de nouveaux
  modèles sont arrivés :
  `php artisan db:seed --class="Database\Seeders\Modeles\UpdateImagesSeeder" --force`.

- Après une évolution de l'`api` (dépôt séparé), **la redéployer aussi** et purger son cache si nécessaire.
- Vider le cache applicatif si besoin : `php artisan optimize:clear`.
