# CaseOrion — Analyse & améliorations

**Date :** 2026-07-25 · **MAJ :** 2026-07-26  
**Périmètre :** analyse complète (server + client + Docker), **sans modification de code**.  
**Stack :** Express / Prisma / SQLite / Socket.IO · React (Vite) · ~16k LOC client+server.

---

## Synthèse

Le projet est une API jeu cohérente en **mono-process**, avec un effort sérieux sur l’économie haute (crédits en **strings + BigInt** via `cents.js`).  
Les risques les plus graves restants : **paiements mock en prod**, **`db push --accept-data-loss` au boot**, et **état in-memory** (sessions, timers battles, présence).

| Zone | État |
|------|------|
| Cases / wear / keys | Solide (roll serveur, BigInt value) |
| Auto-sell | Logique OK (`value < seuil`) ; UX/confusion catalogue vs instance |
| Battle (jackpot only) | Un seul mode ; 1vX / coinflip / duel **retirés** |
| Vault / prestige | Fonctionnel ; settle concurrent risqué |
| Shop | Mock OK en dev ; dangereux si prod |
| Docker | Marche en single-node ; pas scale-ready |

---

## P0 — Critique (corriger en premier)

### ~~1. Odds des duels faussées (multi-items)~~ — **résolu par suppression**
Le mode **1vX Case** (rooms custom / `duel.js`) a été **retiré** (2026-07-25). Il ne reste que le Battle MultiPlayers (`jackpot.js`). Les anciennes routes `/battle/1vx` et `/battle/coinflip` redirigent vers `/battle`.

### 1. Paiements mock utilisables en production — **en cours (B Stripe)**
**Fichiers :** `server/src/payments.js`, `routes/shop.js`, `index.js` (webhook raw body), `.env.prod.example`

Stripe Checkout + webhook `checkout.session.completed` branchés. En prod Docker : `PAYMENTS_PROVIDER=stripe` + `STRIPE_*` keys + `STRIPE_WEBHOOK_SECRET` + URL publique HTTPS vers `/api/shop/osu/webhook`.

Reste : désactiver mock en prod (A) si quelqu’un oublie `PAYMENTS_PROVIDER` ; bascule live keys quand le compte est vérifié.

---

### 3. `prisma db push --accept-data-loss` à chaque démarrage Docker
**Fichiers :** `Dockerfile`, `docker-compose.yml`

Peut **détruire / tronquer** des colonnes au restart. Inadapté à une vraie prod.

| Proposition | A (recommandé) : Prisma Migrate (`migrate deploy`) ; push manuel hors boot · B : push **sans** `--accept-data-loss` + fail loud · C : job one-shot de migration séparé du `CMD` app |

---

### ~~4. `db.js` coerce tous les BigInt → `Number`~~ — **corrigé**
`reviveMoney` **supprimé**. Choix retenu : argent en **strings** + `toCentsBigInt` (pas de plafond `Number`), BigInt Prisma éventuels laissés intacts jusqu’au JSON (`toString` dans `index.js`). Pas de conversion globale silencieuse.

---

### ~~5. Uploads SVG → XSS stocké~~ — **corrigé**
Allowlist **PNG / JPEG / WebP / GIF** (MIME + extension + vérif `sharp` du format réel). SVG et autres `image/*` refusés (admin + avatar).

---

## P1 — Économie & précision (haute priorité)

### ~~6. Finir la migration argent BigInt-safe (bout en bout)~~ — **corrigé (A)**
Helpers client dans `api.js` : `toCentsBigInt`, `compareCents`, `centsJson` (miroir serveur). Branchés sur ItemTile, CasePage, Dashboard, Battle, Prestige shortfall, Admin sorts/inputs. Affichage via les mêmes helpers.

---

### ~~7. Courses sur le solde (TOCTOU)~~ — **corrigé (A)**
`spendUserBalance` / `creditUserBalance` / `adjustUserBalance` dans `cents.js` : update SQL atomique `balance = balance ± x WHERE balance >= cost` (INTEGER SQLite) ou CAS string pour montants > Int64. Branché open case, prestige, sell/auto-sell, vault, shop, jackpot.

Même famille restante : double settle vault (login + socket + intervalle) — le crédit est atomique, mais le calcul de ticks peut encore se chevaucher.

---

### ~~8. Auto-sell — clarté & robustesse~~ — **partiel (A)**
Won banner case : **toujours** wear + valeur d’instance ; si auto-sold, ajoute aussi le payout.

Reste ouvert : B immédiat serveur · C TTL claim · D confirm prestige.

---

### ~~9. Colonnes prestige mortes / non branchées~~ — **corrigé (B)**
Supprimées du schema + reset server-prestige : `prestigeKeyOpenReduction`, `prestigeStartBonus`, `prestigeAdBonus`, `prestigeAdCooldownReduction`, `prestigeShopDiscount`, `prestigeStartOsu`, `prestigeDropValueBonus`, `prestigeCaseDiscount`.

---

## P2 — Sécurité & prod

### 10. Sessions MemoryStore
Perdues au restart ; incompatible multi-replicas.

| Proposition | A : SQLite/Redis store · B : sticky single node documenté · C : JWT (changement plus large) |

### 11. CORS trop permissif
`index.js` autorise toujours localhost + LAN HTTP, même “prod”.

| Proposition | A : en prod, **uniquement** `CLIENT_ORIGIN` + `CORS_ORIGINS` |

### 12. Rate limiting absent
Login / register / open / ad / shop confirm non limités.

| Proposition | A : `express-rate-limit` par IP+user · B : captcha register · C : cooldowns serveur déjà partiels (ad) à généraliser |

### 13. Admin via `session.role` sans re-check DB
Élévation stale possible.

| Proposition | A : `requireAdmin` recharge le user · B : claim `roleVersion` en session |

### 14. Secrets & config
`SESSION_SECRET=dev-secret` par défaut ; pas de `server/.env.example` local (seulement README + `.env.prod.example`).

| Proposition | A : fail boot si secret faible en prod · B : ajouter `.env.example` |

### 15. Mots de passe faibles
Min 6 caractères seulement.

| Proposition | A : min 8 + zxcvbn soft · B : garder simple (jeu privé) mais documenter |

---

## P3 — Qualité code / refactor

### ~~16. Duplication jackpot ↔ duel~~ — **obsolète**
`duel.js` / 1vX / coinflip **retirés** ; un seul type de battle (`services/jackpot.js`). Plus de duplication cross-mode à factoriser.

| Ancienne prop. | A : `services/battleCommon.js` · B : unifier modèle “room” — **non pertinent** tant qu’il n’y a qu’un mode |

Si un 2ᵉ mode battle revient plus tard, reprendre **A** d’abord (helpers serialize / finish / auto-sell), **B** seulement si les rooms divergent vraiment.

### 17. `serializeInv` dupliqué
`inventory.js` / `profile.js` (et variantes).

| Proposition | **A (retenu)** : `serializeInventoryItem` partagé (module commun serveur) |

### 18. Client — listeners `socket.on('connect')` jamais retirés
**Fichiers :** `Layout.jsx`, `BattleHub.jsx`, `BattleRoomPage.jsx`, `DropFeed.jsx`  
(les pages Duel* n’existent plus)

Les `useEffect` font souvent `socket.off` des events métier, mais **pas** du handler `connect` → fuite au remount / Strict Mode.

| Proposition | **A (retenu)** : nommer le handler `onConnect` et `socket.off('connect', onConnect)` dans chaque cleanup |

### ~~19. Battle hub incomplet~~ — **résolu / simplifié**
Hub = **un seul** mode Battle (jackpot MultiPlayers). 1vX et Coinflip **n’existent plus** (redirects legacy vers `/battle`). Plus de cartes stub / copy multi-modes à aligner.

---

### 20. Guards auth copiés partout
Chaque page : loading / !user / admin redirect.

| Proposition | A : `<RequireAuth>` / `<RequirePlayer>` · B : loader React Router |

### 21. CasePage — complexité
State machine respin/auto-sell/announce via refs : difficile à maintenir, mais globalement correcte.

| Proposition | A : extraire hook `useCaseOpenFlow` · B : machine XState · C : laisser tel quel + tests d’intégration |

### 22. Admin UX
- Inputs prix en cents bruts + `Number`  
- Pas d’édition packs crédits / ads (seulement osu packs)  
- Adjust player sans confirm  
- Pas de lien “voir le site joueur”

| Proposition | A : champs crédits + confirm adjust · B : admin shop packs crédits |

### 23. Tests absents
Aucun test automatisé visible sur la logique critique (odds, cents, auto-sell, vault).

| Proposition | A : Vitest unitaire `cents` / `wear` / `autoSell` / jackpot weights · B : smoke API Playwright |

### 24. Observabilité
`console.error` local ; pas de métriques / tracing.

| Proposition | A : structured logs (pino) + request id · B : health DB ready |

---

## P4 — Produit / design (nice to have)

| # | Sujet | Proposition |
|---|--------|-------------|
| 25 | Prestige sans confirm | Dialog “tu perds inventaire + keys” |
| 26 | Catalog conservé au prestige | Documenter (déjà le cas) ou option reset |
| 27 | Favoris bettables | Clarifier UI ou bloquer en battle |
| 28 | Feed montre parfois FT | Afficher valeur d’instance |
| 29 | Lazy routes / code-split | `React.lazy` admin vs player |
| 30 | SQLite scale | Postgres si charge / multi-instance |
| 31 | Achievements / shop ads hardcodés | Config admin |
| 32 | Animation CaseReel perf | Lazy images ; réduire slots si `animReduction` |

---

## Vérification logique (résumé métier)

| Système | Verdict |
|---------|---------|
| **Drop case** | Serveur fait foi ; luck = catalog chapters + Fortune |
| **Wear → prix** | Correct (FT = ref) ; Polish/Quality biaisent vers le haut |
| **Auto-sell** | `instance < seuil` ; Yield après coup ; respin + auto-sell : keepers (≥ seuil) conservés |
| **Keys** | Progression documentée ; reset keys au prestige joueur, **totalOpens conservé** |
| **Vault** | Timer partagé ; risque double settle |
| **Battle / Jackpot** | Seul mode battle ; poids valeur OK |
| **1vX / Duel / Coinflip** | **Retirés** |
| **Prestige joueur** | Wipe bal/inv/keys ; keep skills/Pr/catalog/**opens-by-case** |
| **Server prestige** | Wipe économie globale ; keep account/achievements/stats |

---

## Roadmap proposée (ordre d’exécution)

```
Semaine 1 (P0 restant)
  ├─ Gate mock payments en prod
  └─ Plan migration Docker (stop accept-data-loss au boot)

Semaine 2 (P2–P3)
  ├─ Rate limit + CORS prod + session store
  ├─ serializeInventoryItem partagé
  ├─ socket.off('connect', onConnect) (Layout, Battle*, DropFeed)
  ├─ Tests unitaires cents / wear / autoSell / jackpot
  └─ Confirms UX (prestige, admin adjust)
```

---

## Alternatives d’architecture (si le projet grossit)

| Option | Quand | Coût |
|--------|-------|------|
| **Rester mono SQLite** | LAN / petits effectifs | Faible — documenter single-node |
| **Postgres + Redis** (sessions, presence, timers) | Multi-instances / uptime | Moyen |
| **Queue économie** (BullMQ) | Anti race balance/vault | Moyen |
| **Séparer worker** (spins, vault ticks) | Charge Socket.IO | Élevé |
| **Stripe live** | Monétisation réelle | Moyen + conformité |

---

## Hors scope de cette analyse

- Audit pentest complet / dépendances CVE détaillées  
- Refonte UI/UX visuelle  
- Réintroduction Coinflip / 1vX (non prévu)  
- Contenu (balancing drops / prix items)

---

*Document de pilotage. Prioriser P0 restant avant tout déploiement Docker “réel”.*
