# djuju02.fr

Site personnel et overlays Twitch. Hébergement OVH mutualisé **PERSO**
(cluster100, PHP 8.2, **pas de SSH shell** — SFTP uniquement).

---

## Architecture

Le principe qui gouverne tout : **ce qui n'a pas besoin d'être servi en HTTP
vit hors de `www/`**. Sur un mutualisé, c'est la protection la plus solide,
parce qu'elle ne dépend pas d'un `.htaccess` qui pourrait être mal interprété.

```
/home/djujufm/            ← racine du compte, JAMAIS servie
├── _secrets/             🔒 identifiants (chmod 600, jamais versionné)
├── _app/                 🔒 PHP non exposé : bootstrap, lib, partials
├── _storage/             🔒 jetons chiffrés, sessions, logs, cache
└── www/                  🌐 SEUL dossier exposé
```

`_secrets/twitch.php` n'est **pas** dans le dépôt et ne doit jamais y entrer.
Le modèle `_secrets/twitch.php.example` documente les clés attendues.

---

## Règles non négociables

### Sécurité

- **Aucun secret dans le dépôt.** Pas de jeton, pas de clé, pas de mot de
  passe, même en commentaire ou en exemple « temporaire ».
- **Pas de `<script>` ni de `style="..."` en ligne.** La CSP du site les
  bloque, et c'est ce qui rend une injection XSS inoffensive. Tout JS va dans
  `www/assets/js/`, tout CSS dans `www/assets/css/`.
- **`textContent`, jamais `innerHTML`** pour insérer une valeur venant de
  Twitch, d'un formulaire ou d'une URL. `innerHTML = ""` pour vider est la
  seule exception admise.
- **Toute sortie PHP passe par `e()`** (échappement HTML de `_app/bootstrap.php`).
- **Tout formulaire porte un jeton CSRF** (`csrf_token()` / `csrf_check()`).
- Une URL construite à partir d'une entrée externe est validée par expression
  régulière avant usage (voir `overlays/assets/js/direct.js`, identifiants
  d'emote).

### Structure

- La navbar existe à **un seul endroit** : `_app/partials/nav.php` pour le
  HTML, `www/assets/css/shared-nav.css` pour le style. Ne jamais la dupliquer
  dans une page.
- Une nouvelle page suit ce gabarit :

```php
<?php
require __DIR__ . '/../../_app/bootstrap.php';
$page = [
    'title'       => 'Titre',
    'description' => 'Une phrase pour Google et les aperçus Discord.',
    'active'      => 'creations',
    'canonical'   => '/chemin',
    'css'         => ['pages/ma-page.css'],
];
require APP_PATH . '/partials/head.php';
?>
<main id="contenu">…</main>
<?php require APP_PATH . '/partials/footer.php'; ?>
```

Puis l'ajouter à `www/sitemap.xml`.

### CSP par répertoire

Trois politiques, de la plus stricte à la plus permissive :

| Chemin | Politique |
|---|---|
| `/overlays/` | `default-src 'none'` + CDN Twitch + WebSocket IRC |
| Site | `default-src 'self'`, sans `unsafe-inline` |
| `/jeux/` | `unsafe-eval` + `unsafe-inline` — exception documentée pour Babel/Tailwind |

L'exception de `/jeux/` est confinée à son `.htaccess`. Ne jamais l'étendre
au site entier ; la procédure pour la supprimer est en commentaire dans ce
fichier.

---

## Twitch

| Donnée | Mécanisme |
|---|---|
| Chat | WebSocket IRC anonyme, direct navigateur (compte `justinfan`) |
| Compteurs | `/twitch/api/donnees.php`, cache 60 s |
| Alertes | Webhook EventSub → file d'attente → polling 3-5 s |
| Badges | `/twitch/api/badges.php`, cache 24 h |

**Un overlay est une page publique.** Son code source est lisible par
quiconque connaît l'URL : aucun jeton ne doit y transiter. Les données qui
exigent une authentification passent par un endpoint PHP qui garde le jeton
côté serveur.

`eventsub.php` est ouvert sur Internet. Trois vérifications avant qu'un
événement n'atteigne l'écran : signature HMAC-SHA256 en temps constant,
fenêtre temporelle de 10 minutes, déduplication par identifiant de message.
Ne jamais les affaiblir.

---

## Conventions

- **Français** pour les commentaires, les noms de variables et de fonctions
  dans le code métier. Les API (`textContent`, `addEventListener`) restent
  évidemment en anglais.
- Commentaires **explicatifs** : dire *pourquoi*, pas *quoi*. Un commentaire
  qui paraphrase la ligne suivante est du bruit.
- Le hook `data-js="nom"` relie le HTML au JS des overlays. Les noms varient
  d'un overlay à l'autre — vérifier dans le fichier HTML avant de câbler.

---

## Vérifications avant de livrer

```bash
# Syntaxe JS
node --check www/overlays/assets/js/*.js

# Syntaxe PHP (si php est installé localement)
find www _app -name '*.php' -exec php -l {} \;

# Aucun inline qui casserait la CSP
grep -rn 'style="' www --include=*.php --include=*.html
grep -rn '<script>' www --include=*.html

# Aucun secret sur le point d'être commité
git diff --cached | grep -iE 'client_secret|token_key|eventsub_secret|admin_key'
```

Après déploiement :

```powershell
.\verifier-site.ps1     # compare le serveur à INVENTAIRE.txt
```

---

## Déploiement

`git push` sur `main` déclenche `.github/workflows/deploy.yml`, qui envoie
`www/` et `_app/` en SFTP. `_secrets/` et `_storage/` ne sont **jamais**
touchés par le déploiement — ils vivent uniquement sur le serveur.

Voir `GITHUB.md` pour la mise en place.

---

## Pièges rencontrés

Ces erreurs ont déjà coûté du temps. Les connaître évite de les refaire.

- **`http.firewall=security` dans `.ovhconfig`** renvoyait 403 sur tout le
  site, en amont d'Apache, donc invisible depuis le `.htaccess`. Laisser sur
  `none`.
- **`DirectorySlash Off`** est nécessaire pour que `/creations` ne redirige
  pas vers `/creations/`.
- **Le tag `emotes` de Twitch** donne des positions en points de code, pas en
  UTF-16. Utiliser `Array.from()`, jamais `substring()`.
- **`badges.php` renvoie `{ok, badges:{clé:{url, titre}}}`**, pas un
  dictionnaire plat. Le JS normalise les deux formats.
- **Retirer puis remettre une classe d'animation** détruit et recrée la couche
  GPU, ce qui produit un micro-saut visible. Modifier les variables CSS à la
  place.
- **OBS met le CSS en cache agressivement.** Après modification : clic droit
  sur la source → Propriétés → Actualiser le cache.
