fix: corrections SW cache, nginx proxy, sw.js no-cache — 2026-05-01 05:48

This commit is contained in:
Claude Deploy
2026-05-01 05:48:38 +00:00
parent e3f3bb0ea5
commit 50758fe232
4 changed files with 721 additions and 18 deletions
+577
View File
@@ -0,0 +1,577 @@
# EquiTask — Documentation technique complète
> Application PWA de mesure et de répartition des tâches domestiques
> Version actuelle : **1.0.0** — Live sur **https://equitask.domench.fr**
> Dernière mise à jour : avril 2026
---
## Table des matières
1. [Vision et concept](#1-vision-et-concept)
2. [Architecture globale](#2-architecture-globale)
3. [Stack technique](#3-stack-technique)
4. [Structure des fichiers](#4-structure-des-fichiers)
5. [Base de données](#5-base-de-données)
6. [API REST — référence complète](#6-api-rest--référence-complète)
7. [Frontend — pages et composants](#7-frontend--pages-et-composants)
8. [Système de score](#8-système-de-score)
9. [Mode offline / PWA](#9-mode-offline--pwa)
10. [Déploiement et infrastructure](#10-déploiement-et-infrastructure)
11. [Variables d'environnement](#11-variables-denvironnement)
12. [Seed automatique](#12-seed-automatique)
13. [Limites connues de la V1](#13-limites-connues-de-la-v1)
---
## 1. Vision et concept
EquiTask permet à un foyer de **mesurer objectivement qui fait quoi** dans les tâches du quotidien. Chaque tâche est quantifiée par un **score = durée × coefficient de pénibilité**, ce qui permet de comparer l'investissement réel de chaque membre plutôt que le simple nombre de tâches.
**Flux utilisateur principal :**
1. Première visite → wizard Setup (nom du foyer + membres)
2. Sélection du profil (qui effectue la tâche ?)
3. Page Saisie → clic sur une tâche → modal de confirmation → score enregistré
4. Dashboard → visualisation de la répartition + indicateur d'équilibre
**Concept clé : le score**
```
score_final = durée_minutes × coefficient_pénibilité (15)
```
Exemples : Nettoyage WC (10 min × 4) = 40 pts ; Préparation repas (45 min × 3) = 135 pts
---
## 2. Architecture globale
```
┌─────────────────────────────────────────────────────────────┐
│ Navigateur │
│ React PWA (Vite + Tailwind) │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ Setup │ │ Saisie │ │ Dashboard │ │ Paramètres │ │
│ └──────────┘ └──────────┘ └───────────┘ └──────────────┘ │
│ IndexedDB (queue offline) Service Worker (cache) │
└──────────────────────┬──────────────────────────────────────┘
│ HTTP /api/*
┌──────────────────────▼──────────────────────────────────────┐
│ Express.js (Node 20) │
│ /api/foyer /api/membres /api/categories │
│ /api/taches /api/saisies /api/saisies/stats │
│ Fichiers statiques frontend servis depuis /public │
└──────────────────────┬──────────────────────────────────────┘
│ better-sqlite3
┌──────────────────────▼──────────────────────────────────────┐
│ SQLite — /data/equitask.db │
│ foyer | membres | categories | taches_recurrentes | saisies│
└─────────────────────────────────────────────────────────────┘
```
Tout tourne dans **un seul conteneur Docker** : l'Express sert à la fois l'API et les fichiers statiques buildés du frontend. La base SQLite est persistée dans un **volume Docker** (`equitask-data:/data`).
---
## 3. Stack technique
### Frontend
| Technologie | Version | Rôle |
|---|---|---|
| React | 18.2 | UI |
| Vite | 5.2 | Build + dev server |
| TypeScript | 5.2 | Typage |
| Tailwind CSS | 3.4 | Styles utilitaires |
| React Router | 6.22 | Navigation SPA |
| TanStack Query | 5.28 | Fetching + cache serveur |
| Recharts | 2.12 | Graphiques (bar, area, pie) |
| date-fns | 3.6 | Manipulation de dates |
| idb | 8.0 | IndexedDB (queue offline) |
| vite-plugin-pwa | 0.19 | Service Worker + manifest |
### Backend
| Technologie | Version | Rôle |
|---|---|---|
| Node.js | 20 (Alpine) | Runtime |
| Express | 4.18 | HTTP server |
| TypeScript | 5.4 | Typage |
| Drizzle ORM | 0.30 | ORM SQLite |
| better-sqlite3 | 9.4 | Driver SQLite synchrone |
| helmet | 7.1 | Headers sécurité |
| cors | 2.8 | CORS (dev uniquement) |
| morgan | 1.10 | Logs HTTP |
### Infrastructure
| Composant | Détail |
|---|---|
| Hébergement | VPS OVH — 57.131.33.182 |
| Orchestration | Coolify 4.0.0-beta.474 |
| Reverse proxy | Traefik v3.6 |
| DNS | OVH — wildcard `*.domench.fr` → IP VPS |
| Source code | Gitea — https://git.domench.fr/gael/equitask |
| HTTPS | Let's Encrypt via Traefik |
| Registry | Pas de registry externe — build local sur le VPS |
---
## 4. Structure des fichiers
```
equitask/
├── Dockerfile # Multi-stage build (3 stages)
├── docker-compose.yml # Pour dev local
├── .dockerignore
├── .gitignore
├── backend/
│ ├── package.json
│ ├── tsconfig.json
│ └── src/
│ ├── index.ts # Point d'entrée Express + seed auto
│ ├── db/
│ │ ├── index.ts # Connexion SQLite + initDb() + CREATE TABLE
│ │ ├── schema.ts # Schéma Drizzle (tables + types TS)
│ │ ├── seed.ts # Seed async (non utilisé en prod)
│ │ └── seed-sync.ts # Seed synchrone
│ └── routes/
│ ├── foyer.ts # CRUD foyer
│ ├── membres.ts # CRUD membres
│ ├── categories.ts # CRUD catégories
│ ├── taches.ts # CRUD tâches récurrentes
│ └── saisies.ts # CRUD saisies + stats + export
└── frontend/
├── package.json
├── tsconfig.json # "types": ["vite/client"] requis !
├── tsconfig.node.json
├── vite.config.ts # PWA + proxy /api → :3001
├── tailwind.config.js
├── postcss.config.js
├── index.html
├── public/
│ ├── favicon.svg
│ └── icons/
│ ├── icon-192.png
│ └── icon-512.png
└── src/
├── main.tsx
├── App.tsx # Router + QueryClient + AppProvider
├── index.css
├── types/
│ └── index.ts # Tous les types TS de l'app
├── api/
│ ├── client.ts # fetch wrapper typé
│ └── index.ts # foyerApi, membresApi, tachesApi, saisiesApi, dashboardApi
├── context/
│ └── AppContext.tsx # foyer, membreActif, isOnline, queueCount
├── hooks/
│ ├── useOfflineSync.ts # Sync auto IndexedDB → API au retour en ligne
│ └── useToast.ts
├── offline/
│ └── queue.ts # IndexedDB : enqueue / dequeue / getQueue / countQueue
├── pages/
│ ├── Setup.tsx # Wizard première config (foyer + membres)
│ ├── SelectionProfil.tsx # Écran de sélection du membre
│ ├── Saisie.tsx # Grille tâches + modals (récurrente + one-shot)
│ ├── Dashboard.tsx # Graphiques + indicateur équilibre + historique
│ └── Parametres.tsx # Gestion membres / catégories / tâches
└── components/
└── ui/
├── Layout.tsx # Shell avec nav bas mobile
├── Modal.tsx # Modale générique
├── PenibiliteSelector.tsx # 5 boutons 15
├── ScoreBadge.tsx # Badge score coloré
└── Toast.tsx # Notifications
```
---
## 5. Base de données
### Schéma SQLite
```sql
-- Un seul enregistrement — configuration du foyer
CREATE TABLE foyer (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
cree_le TEXT DEFAULT (datetime('now', 'localtime'))
);
-- Membres du foyer (max 7 recommandé)
CREATE TABLE membres (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('adulte', 'enfant')),
couleur TEXT NOT NULL, -- ex: #ef4444
actif INTEGER DEFAULT 1, -- soft delete
ordre INTEGER DEFAULT 0,
cree_le TEXT DEFAULT (datetime('now', 'localtime'))
);
-- Catégories de tâches (éditables)
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
icone TEXT NOT NULL, -- emoji
couleur TEXT NOT NULL,
ordre INTEGER DEFAULT 0,
actif INTEGER DEFAULT 1
);
-- Catalogue des tâches habituelles
CREATE TABLE taches_recurrentes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nom TEXT NOT NULL,
categorie_id INTEGER NOT NULL REFERENCES categories(id),
duree_moyenne_min INTEGER NOT NULL,
coefficient_penibilite INTEGER NOT NULL CHECK(coefficient_penibilite BETWEEN 1 AND 5),
actif INTEGER DEFAULT 1 -- soft delete
);
-- Historique de toutes les saisies
CREATE TABLE saisies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tache_recurrente_id INTEGER REFERENCES taches_recurrentes(id), -- nullable
nom_tache_oneshot TEXT, -- nullable (tâche ponctuelle)
categorie_id INTEGER NOT NULL REFERENCES categories(id),
membre_id INTEGER NOT NULL REFERENCES membres(id),
date_heure TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
duree_reelle_min INTEGER, -- override de la durée par défaut
coefficient_penibilite INTEGER NOT NULL CHECK(coefficient_penibilite BETWEEN 1 AND 5),
score_final INTEGER NOT NULL, -- snapshot = duree * coef
notes TEXT,
synced INTEGER DEFAULT 1,
cree_le TEXT DEFAULT (datetime('now', 'localtime')),
CHECK (tache_recurrente_id IS NOT NULL OR nom_tache_oneshot IS NOT NULL)
);
-- Index de performance
CREATE INDEX idx_saisies_membre ON saisies(membre_id);
CREATE INDEX idx_saisies_date ON saisies(date_heure);
CREATE INDEX idx_saisies_categorie ON saisies(categorie_id);
```
### Règles importantes
- **Soft delete** partout : `actif = 0` pour membres, catégories et tâches (jamais de DELETE réel)
- **score_final est un snapshot** : stocké au moment de la saisie, non recalculé en cas de modification ultérieure de la tâche
- Une saisie doit avoir soit `tache_recurrente_id` soit `nom_tache_oneshot` (contrainte CHECK)
- `duree_reelle_min` est nullable : si null, la durée d'affichage est celle de la tâche référencée
---
## 6. API REST — référence complète
Base URL : `/api`
Content-Type : `application/json`
### Foyer
| Méthode | Route | Description |
|---|---|---|
| GET | `/foyer` | Récupère le foyer (`{ foyer: Foyer \| null }`) |
| POST | `/foyer` | Crée le foyer (`{ nom }`) |
| PUT | `/foyer/:id` | Modifie le nom du foyer |
### Membres
| Méthode | Route | Description |
|---|---|---|
| GET | `/membres` | Liste les membres actifs |
| POST | `/membres` | Crée un membre (`{ nom, role, couleur, ordre? }`) |
| PUT | `/membres/:id` | Modifie un membre |
| DELETE | `/membres/:id` | Soft delete (actif = 0) |
### Catégories
| Méthode | Route | Description |
|---|---|---|
| GET | `/categories` | Liste les catégories actives, triées par `ordre` |
| POST | `/categories` | Crée (`{ nom, icone, couleur, ordre? }`) |
| PUT | `/categories/:id` | Modifie |
| DELETE | `/categories/:id` | Soft delete |
### Tâches récurrentes
| Méthode | Route | Description |
|---|---|---|
| GET | `/taches` | Liste + `score_calcule` joint, triées catégorie/nom |
| POST | `/taches` | Crée (`{ nom, categorie_id, duree_moyenne_min, coefficient_penibilite }`) |
| PUT | `/taches/:id` | Modifie |
| DELETE | `/taches/:id` | Soft delete |
### Saisies
| Méthode | Route | Body / Query | Description |
|---|---|---|---|
| GET | `/saisies` | `?debut&fin&membre_id&categorie_id&type&page&limit` | Liste paginée (max 200/page) avec jointures |
| POST | `/saisies` | `{ tache_recurrente_id?, nom_tache_oneshot?, categorie_id, membre_id, date_heure?, duree_reelle_min?, coefficient_penibilite, score_final, notes? }` | Crée une saisie |
| POST | `/saisies/batch` | `{ saisiesData: [...] }` | Sync offline (ignore les erreurs individuelles) |
| PUT | `/saisies/:id` | champs modifiables | Modifie une saisie |
| DELETE | `/saisies/:id` | — | Suppression réelle (pas de soft delete) |
| GET | `/saisies/stats` | `?debut&fin&inclure_enfants&categorie_id` | Stats dashboard (voir ci-dessous) |
| GET | `/saisies/export` | `?format=json\|csv` | Télécharge toutes les saisies |
### Réponse `/saisies/stats`
```json
{
"scores_par_membre": [
{ "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "role": "adulte",
"score_total": 3420, "pourcentage": 62, "nb_saisies": 28 }
],
"scores_par_categorie": [
{ "categorie_id": 1, "nom": "Cuisine", "icone": "🍳", "couleur": "#ef4444",
"scores_membres": [
{ "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "score": 1200 }
]
}
],
"evolution_temporelle": [
{ "date": "2026-04-20",
"scores": [{ "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "score": 315 }] }
],
"indicateur_equilibre": {
"adulte1": { "membre_id": 1, "nom": "Alice", "couleur": "#ef4444", "score": 3420, "pourcentage": 62 },
"adulte2": { "membre_id": 2, "nom": "Bob", "couleur": "#3b82f6", "score": 2100, "pourcentage": 38 },
"ecart_pct": 24,
"statut": "orange" // "vert" ≤10%, "orange" ≤25%, "rouge" >25%
}
}
```
### Santé
```
GET /api/health → { "ok": true, "version": "1.0.0", "timestamp": "..." }
```
---
## 7. Frontend — pages et composants
### Flux de navigation
```
/ (SelectionProfil)
├── [pas de foyer] → /setup (Setup)
└── [foyer OK] → sélection membre → mémorisation dans sessionStorage
├── /saisie (Layout > Saisie)
├── /dashboard (Layout > Dashboard)
└── /parametres (Layout > Parametres)
```
### Pages
**Setup** — Wizard en 2 étapes : nom du foyer, puis ajout des membres (min 1 adulte). Initialise le foyer via POST `/api/foyer` puis POST `/api/membres`.
**SelectionProfil** — Grille de cartes membres. Le clic stocke le membre dans `sessionStorage` (via `AppContext.setMembreActif`) et redirige vers `/saisie`.
**Saisie** — Grille de cartes tâches groupées par catégorie (tabs horizontaux). Deux modals :
- `ModalConfirmTache` : confirme une tâche récurrente, permet d'override durée/pénibilité
- `ModalOneShot` : saisie libre avec option "ajouter au catalogue"
**Dashboard** — 4 blocs de visualisation :
1. Scores cumulés par membre (barres de progression)
2. Indicateur d'équilibre couple (barre bicolore + statut vert/orange/rouge)
3. Répartition par catégorie (BarChart Recharts, grouped)
4. Évolution temporelle (AreaChart Recharts)
Plus un tableau historique paginé avec suppression.
Filtres : période (semaine/mois/7j/30j/personnalisé), catégorie, toggle enfants. Export CSV/JSON.
**Parametres** — Trois onglets :
- Membres : CRUD + couleur + rôle
- Catégories : CRUD + icône emoji + couleur
- Tâches : CRUD filtré par catégorie + durée + pénibilité
### Composants UI
| Composant | Rôle |
|---|---|
| `Layout` | Shell : contenu + barre de navigation bas (mobile-first) avec icônes + badge offline |
| `Modal` | Modale générique avec backdrop, animation, gestion focus |
| `PenibiliteSelector` | 5 boutons numérotés 15 avec couleurs graduées |
| `ScoreBadge` | Badge score avec couleur selon valeur (vert → rouge) |
| `Toast` | Notification éphémère (succès/erreur/info) avec animation |
### AppContext
```typescript
interface AppContextType {
foyer: Foyer | null;
setFoyer: (f: Foyer | null) => void;
membreActif: Membre | null; // persisté dans sessionStorage
setMembreActif: (m: Membre | null) => void;
isOnline: boolean; // window online/offline events
queueCount: number; // nb saisies en attente de sync
setQueueCount: (n: number) => void;
}
```
---
## 8. Système de score
Le score est la métrique centrale de l'app. Il cherche à rendre comparables des tâches de nature très différente.
```
score = durée_minutes × coefficient_pénibilité
```
**Coefficient de pénibilité (15) :**
- 1 = tâche agréable ou automatique
- 2 = tâche neutre
- 3 = tâche un peu contraignante
- 4 = tâche pénible (WC, impôts...)
- 5 = réservé aux tâches exceptionnellement pénibles
**Exemples issus du seed :**
| Tâche | Durée | Coef | Score |
|---|---|---|---|
| Nettoyage WC | 10 min | 4 | 40 pts |
| Préparation repas | 45 min | 3 | 135 pts |
| Batch cooking | 90 min | 3 | 270 pts |
| Déclarations impôts | 60 min | 4 | 240 pts |
**Indicateur d'équilibre :**
- Calculé uniquement sur les adultes (les enfants sont exclus par défaut)
- `écart_pct = |score_adulte1 - score_adulte2| / score_total × 100`
- Vert ≤ 10% — Orange ≤ 25% — Rouge > 25%
---
## 9. Mode offline / PWA
### Service Worker (Workbox)
Configuré dans `vite.config.ts` via `vite-plugin-pwa` :
- **Assets statiques** : précachés au build (JS, CSS, HTML, PNG, SVG)
- **Appels API** (`/api/*`) : stratégie `NetworkFirst` avec fallback cache, TTL 24h, max 100 entrées
### Queue offline (IndexedDB)
Quand `isOnline === false`, les saisies ne sont pas envoyées au serveur mais stockées dans IndexedDB via `src/offline/queue.ts` (base `equitask-offline`, store `saisies-queue`).
Au retour en ligne (`useOfflineSync.ts`), toutes les saisies en queue sont envoyées une par une via `POST /api/saisies`. En cas d'erreur réseau, la synchronisation s'arrête (les saisies restent en queue).
### Manifest PWA
```json
{
"name": "EquiTask - Répartition tâches",
"short_name": "EquiTask",
"display": "standalone",
"orientation": "portrait-primary",
"theme_color": "#1e1b4b",
"background_color": "#0f172a"
}
```
Icônes : 192×192 et 512×512 (maskable).
---
## 10. Déploiement et infrastructure
### Dockerfile (multi-stage)
```
Stage 1 — frontend-builder (node:20-alpine)
npm install --include=dev ← OBLIGATOIRE (sinon vite/tsc pas installés)
npm run build → dist/
Stage 2 — backend-builder (node:20-alpine)
apk add python3 make g++ ← pour compiler better-sqlite3 (module natif)
npm install --include=dev ← OBLIGATOIRE (sinon tsc pas installé)
npm run build → dist/
Stage 3 — runtime (node:20-alpine)
apk add libstdc++ ← runtime better-sqlite3
Copie node_modules du builder (incluant better-sqlite3 natif)
Copie dist/ backend
Copie dist/ frontend → public/
ENV NODE_ENV=production PORT=3001 DATABASE_PATH=/data/equitask.db
VOLUME ["/data"]
CMD ["node", "dist/index.js"]
```
**Points critiques Docker :**
- Utiliser `npm install --include=dev` et non `npm ci` (pas de lockfile commité)
- `NODE_ENV=production` supprime les devDependencies → toujours passer `--include=dev` dans les stages builder
- `better-sqlite3` compile un module natif → outils de compilation nécessaires au build, `libstdc++` au runtime
### Infrastructure Coolify
```
Coolify URL : http://100.94.204.91:8000
App UUID : bunmdt8jmb2i594zuhzvdhfy
Gitea repo : https://git.domench.fr/gael/equitask.git
Branche : main
Build pack : dockerfile
Port exposé : 3001
Domaine : https://equitask.domench.fr
Volume : equitask-data → /data
```
### Script de déploiement
`deploy.py` (à la racine de `Applications VPS/`) automatise la création d'une nouvelle app :
```bash
# Depuis Windows cmd (pas PowerShell)
py -X utf8 deploy.py equitask ./equitask
```
Lit `config-deploy.json`, crée le repo Gitea, pousse le code, crée l'app Coolify, configure le volume, déclenche le build.
**Particularités Coolify API v1 découvertes :**
- `fqdn` en lecture seule → utiliser `domains` dans PATCH
- `POST /applications/public` tronque le hostname du `git_repository` → re-PATCH après création
- Storage API : utiliser `custom_docker_run_options: "--mount type=volume,..."` (pas l'API `/storages`)
- Déploiement : `GET /api/v1/deploy?uuid={app_uuid}&force=false` (pas POST)
---
## 11. Variables d'environnement
| Variable | Défaut | Description |
|---|---|---|
| `NODE_ENV` | `production` | Désactive CORS en production |
| `PORT` | `3001` | Port d'écoute Express |
| `DATABASE_PATH` | `/data/equitask.db` | Chemin absolu de la base SQLite |
En développement local, créer un `.env` à la racine de `backend/` :
```env
NODE_ENV=development
PORT=3001
DATABASE_PATH=./data/equitask.db
```
---
## 12. Seed automatique
Au démarrage, `backend/src/index.ts` vérifie si la table `categories` est vide. Si oui, il insère automatiquement :
**7 catégories :** Cuisine 🍳, Ménage 🧹, Courses 🛒, Enfants 👶, Administratif 📋, Entretien maison 🔧, Charge mentale 🧠
**29 tâches récurrentes** couvrant les activités domestiques courantes, avec durées et coefficients préréglés.
Ce seed ne s'exécute qu'une seule fois (première instance vierge). Les données sont ensuite modifiables depuis la page Paramètres.
---
## 13. Limites connues de la V1
**Fonctionnelles :**
- Pas d'authentification — une seule instance par déploiement (un seul foyer)
- Pas de gestion multi-foyers
- Pas de notifications push (rappels, suggestions)
- Pas d'objectifs ou de gamification
- Les statistiques ne couvrent pas les tendances long terme (ex: évolution mensuelle sur 6 mois)
- Pas de vue "récapitulatif hebdomadaire" envoyé par email/notification
- Le tableau historique n'a pas de tri par colonne
- Pas de possibilité de modifier une saisie existante depuis l'historique (seulement supprimer)
- Pas de saisie rapide ("j'ai fait X aujourd'hui à 8h30" sans passer par la grille)
**Techniques :**
- Pas de migrations Drizzle — schéma recréé via `CREATE TABLE IF NOT EXISTS` (fragile pour les évolutions)
- Pas de tests automatisés (ni backend ni frontend)
- Pas de CI/CD : le déploiement est déclenché manuellement via `deploy.py`
- `score_final` est un snapshot immuable — pas de recalcul si on modifie le coefficient d'une tâche
- Pas de rate limiting sur l'API
- Pas d'authentification API
- `sessionStorage` pour le membre actif → perdu si on ferme l'onglet
- Offline sync séquentielle (une saisie à la fois) → lente si beaucoup de saisies en queue
+127
View File
@@ -0,0 +1,127 @@
# Prompt de reprise — EquiTask V2
> À coller tel quel en début de nouvelle session pour démarrer le développement de la V2.
---
## Contexte du projet
Tu vas m'aider à développer **EquiTask V2**, l'évolution d'une application PWA de mesure de répartition des tâches domestiques.
**L'application V1 est déjà en production** à l'adresse `https://equitask.domench.fr`. Le code source se trouve dans mon dossier de travail `equitask/` (dossier sélectionné dans Cowork).
Avant de commencer, lis les deux fichiers de référence :
1. `equitask/DOCUMENTATION.md` — documentation technique complète de la V1 (architecture, stack, BDD, API, composants, limites)
2. `equitask/PROMPT_REPRISE_V2.md` — ce fichier (contexte V2)
---
## Ce qui existe en V1 (à ne pas recoder)
- **Backend** : Express + TypeScript + SQLite (Drizzle ORM) + better-sqlite3
- Routes : `/api/foyer`, `/api/membres`, `/api/categories`, `/api/taches`, `/api/saisies`
- Stats dashboard : `GET /api/saisies/stats?debut&fin&inclure_enfants&categorie_id`
- Export CSV/JSON : `GET /api/saisies/export?format=json|csv`
- **Frontend** : React 18 + Vite + Tailwind + TanStack Query + Recharts
- Pages : Setup (wizard), SelectionProfil, Saisie (grille tâches), Dashboard (4 graphiques), Paramètres (3 onglets)
- Offline : queue IndexedDB + sync automatique au retour en ligne
- PWA : Service Worker Workbox, manifest, icônes
- **Infra** : Docker multi-stage → Coolify → https://equitask.domench.fr
- Déploiement via `deploy.py` depuis le dossier parent
- Volume SQLite persisté : `equitask-data:/data`
---
## Système de score (inchangé en V2)
```
score_final = durée_minutes × coefficient_pénibilité (15)
```
L'indicateur d'équilibre mesure l'écart en % entre les deux adultes du foyer.
Seuils : vert ≤ 10%, orange ≤ 25%, rouge > 25%.
---
## Objectifs de la V2
> **À compléter par Gaël avant de démarrer** — noter ici les fonctionnalités souhaitées.
> Exemples de pistes identifiées lors du développement de la V1 :
### Fonctionnalités prioritaires (à valider)
- [ ] **Authentification simple** — code PIN par membre ou mot de passe foyer pour éviter que n'importe qui modifie les données
- [ ] **Notifications / rappels** — push notification ou email hebdomadaire avec le récap de répartition
- [ ] **Vue récapitulatif hebdomadaire** — résumé automatique chaque dimanche soir
- [ ] **Objectifs** — définir un objectif d'équilibre cible et suivre la progression
- [ ] **Modification d'une saisie** — pouvoir éditer une saisie depuis l'historique (actuellement suppression seulement)
- [ ] **Saisie rapide** — ajouter une tâche passée sans passer par la grille (ex: "j'ai fait X hier")
- [ ] **Tri du tableau historique** — tri par colonne (date, membre, score...)
- [ ] **Tendances long terme** — graphique mensuel sur 6/12 mois
- [ ] **Badges/gamification** — récompenses pour les périodes bien équilibrées
### Améliorations techniques (à valider)
- [ ] **Migrations Drizzle** — remplacer les `CREATE TABLE IF NOT EXISTS` par un vrai système de migrations
- [ ] **Tests** — au moins les routes API critiques (saisies, stats)
- [ ] **CI/CD automatique** — déclencher le déploiement sur push Gitea (webhook → Coolify)
- [ ] **Rate limiting** — protéger l'API des abus
- [ ] **Recalcul des scores** — option pour recalculer les scores historiques si les paramètres d'une tâche changent
---
## Contraintes techniques à respecter
1. **Rester dans la même stack** : Express/Node 20, React 18, SQLite, TypeScript, Tailwind
2. **Compatibilité Docker** : le Dockerfile multi-stage doit continuer à fonctionner
- Toujours `npm install --include=dev` (pas `npm ci`, pas de lockfile)
- `NODE_ENV=production` coupe les devDependencies — le `--include=dev` est obligatoire dans les builder stages
3. **Volume SQLite** : les données sont dans `/data/equitask.db` — les migrations doivent être non-destructives
4. **Frontend `tsconfig.json`** : doit avoir `"types": ["vite/client"]` pour `import.meta.env`
5. **API** : rester rétrocompatible avec V1 (mêmes routes, mêmes structures de réponse)
6. **Design** : dark theme slate-950/slate-900, accents indigo-600, cards rounded-2xl — maintenir la cohérence visuelle
---
## Infra de déploiement
```
VPS OVH : 57.131.33.182
Coolify : http://100.94.204.91:8000
Gitea : https://git.domench.fr/gael/equitask
Branche : main
App Coolify : bunmdt8jmb2i594zuhzvdhfy
URL prod : https://equitask.domench.fr
```
Pour redéployer après modifications :
```bash
# Depuis Windows — cmd (pas PowerShell)
cd C:\Users\GALWAL~1\Documents\Claude\Projects\APPLIC~1
py -X utf8 deploy.py equitask ./equitask
```
Ou : pusher sur `main` et déclencher manuellement dans Coolify.
---
## Points de vigilance hérités de la V1
Ces bugs/pièges ont déjà été rencontrés — à ne pas reproduire :
- **`npm ci` échoue** → pas de `package-lock.json` commité, toujours utiliser `npm install`
- **`tsc: not found` pendant le build Docker** → `NODE_ENV=production` coupe les devDependencies, toujours `--include=dev`
- **TypeScript `import.meta.env` introuvable** → vérifier `"types": ["vite/client"]` dans `frontend/tsconfig.json`
- **Type `role: string` trop large** → dans les mutations TanStack Query, typer explicitement `role: 'adulte' | 'enfant'`
- **Coolify API `fqdn` read-only** → utiliser le champ `domains` dans les PATCH
- **Coolify API git_repository tronqué** → re-PATCH après création pour rétablir l'URL complète
- **`py` vs `python`** → sur ce Windows, `py` fonctionne en cmd mais pas `python3`
- **Chemin avec `ë`** → utiliser le chemin court Windows `GALWAL~1` dans cmd si nécessaire
---
## Pour démarrer la session
1. Lis `equitask/DOCUMENTATION.md` (documentation technique complète)
2. Explore le code existant si besoin : `equitask/backend/src/` et `equitask/frontend/src/`
3. Demande-moi quelles fonctionnalités V2 je veux prioriser
4. Propose un plan de développement incrémental (backend d'abord, puis frontend)
5. Travaille fichier par fichier en préservant la compatibilité avec l'existant
+14 -13
View File
@@ -108,19 +108,20 @@ app.get('/api/health', (_req, res) => {
// ─── Frontend statique (en production) ──────────────────────────────────────
const frontendPath = path.join(__dirname, '..', 'public');
if (fs.existsSync(frontendPath)) {
app.use(express.static(frontendPath));
// SPA fallback - toutes les routes non-API servent index.html
app.get('*', (req, res) => {
if (!req.path.startsWith('/api')) {
res.sendFile(path.join(frontendPath, 'index.html'));
}
// Service worker — jamais mis en cache (sinon l'ancien SW continue après déploiement)
app.get('/sw.js', (_req, res) => {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.sendFile(path.join(frontendPath, 'sw.js'));
});
}
// ─── Démarrage ───────────────────────────────────────────────────────────────
app.listen(PORT, () => {
console.log(`🚀 EquiTask API démarrée sur le port ${PORT}`);
console.log(` Mode : ${process.env.NODE_ENV || 'development'}`);
});
// Manifeste PWA — pas de cache long
app.get(/\.webmanifest$/, (_req, res, next) => {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Expires', '0');
next();
});
export default app;
app.use(express.static(frontendPath));
// SPA fallback - toutes les routes non-API ser
+3 -5
View File
@@ -25,12 +25,13 @@ export default defineConfig({
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
cleanupOutdatedCaches: true,
runtimeCaching: [
{
urlPattern: /^\/api\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
cacheName: 'equitask-api-cache',
expiration: { maxEntries: 100, maxAgeSeconds: 86400 },
networkTimeoutSeconds: 10,
},
@@ -45,7 +46,4 @@ export default defineConfig({
server: {
port: 5173,
proxy: {
'/api': { target: 'http://localhost:3001', changeOrigin: true },
},
},
});
'/api': { target: 'http://localhost