245 lines
10 KiB
Markdown
245 lines
10 KiB
Markdown
# 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”.*
|