578 lines
23 KiB
Markdown
578 lines
23 KiB
Markdown
# 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é (1–5)
|
||
```
|
||
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 1–5
|
||
├── 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 1–5 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é (1–5) :**
|
||
- 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
|