Simpl-Resultat/spec-simpl-resultat-web.md
le king fu 4912ae39b0 docs: add WIP specs for OAuth keychain, monetisation, reports, and web
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:41:00 -04:00

27 KiB

Spec — Simpl-Resultat Web

Date: 2026-03-30 Projet: simpl-resultat Statut: Draft Dependance: Logto IdP (spec-compte-maximus dans la-compagnie-maximus)

Contexte

Simpl-Resultat est actuellement une app desktop Tauri v2 (Windows/Linux) avec stockage SQLite local et protection par PIN Argon2. L'objectif est de rendre l'application disponible via le web, permettant aux utilisateurs connectes avec un Compte Maximus d'acceder a leurs donnees financieres depuis un navigateur.

L'app desktop continue de fonctionner en offline sans compte. Le compte et le web sont optionnels.

Decision securite : Les donnees financieres sont stockees en clair cote serveur (Option D — chiffrement au repos + isolation forte). L'E2EE zero-knowledge a ete evalue et rejete pour la v1 car incompatible avec les fonctionnalites web (recherche serveur, rapports, import CSV). L'hebergement local au Quebec (VPS OVH Beauharnois, Loi 25) est le differenciateur securite, pas le zero-knowledge. L'E2EE reste une option premium future ("mode Coffre-fort").

Objectif

Permettre aux utilisateurs de gerer leur budget et transactions depuis un navigateur web a resultat.lacompagniemaximus.com, avec possibilite de synchronisation avec l'app desktop.

Scope

IN

  • API REST backend pour toutes les operations CRUD (transactions, categories, budgets, ajustements, imports, fournisseurs, keywords)
  • Schema PostgreSQL (migration des 13 tables SQLite, schema sr_)
  • Frontend web porte depuis le code React/Vite/Tailwind existant (bonne reutilisabilite)
  • Import CSV cote serveur (upload → parsing → auto-categorisation → preview → confirmation)
  • Auth via le service auth dedie (Compte Maximus)
  • Multi-profil par utilisateur (comme l'app desktop)
  • Hebergement sur Coolify (resultat.lacompagniemaximus.com)
  • i18n FR/EN
  • Dark mode
  • Securite Option D (chiffrement au repos + isolation)

OUT

  • E2EE zero-knowledge (decision documentee, option premium future)
  • Connexion bancaire directe (Plaid/Flinks — future phase)
  • App mobile simpl-resultat
  • Partage de budget entre utilisateurs (ex: conjoint — future phase)
  • Notifications/alertes serveur sur depassements budgetaires (future phase)
  • Export PDF de rapports (future phase)

Design

Schema PostgreSQL (schema sr_)

Migration des 13 tables SQLite existantes vers PostgreSQL. Changements principaux :

  • Ajout de user_id et profile_id pour isoler les donnees par utilisateur et par profil
  • Ajout d'une table sr_profiles pour gerer les profils multiples par utilisateur (remplace le systeme de fichiers de profils dans Tauri)
  • UUID comme cles primaires au lieu de INTEGER AUTOINCREMENT
  • TIMESTAMPTZ au lieu de TEXT/DATETIME pour les dates
  • NUMERIC(12,2) au lieu de REAL pour les montants (precision financiere)
  • Conservation de la structure hierarchique des categories (parent_id)
  • Conservation du systeme d'auto-categorisation par mots-cles
  • Seed des 54 categories par defaut et 60+ keywords par profil
CREATE SCHEMA IF NOT EXISTS sr_;

-- Profils utilisateur (remplace le systeme fichiers Tauri)
CREATE TABLE sr_.profiles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL,  -- FK vers le service auth (Compte Maximus)
    name TEXT NOT NULL,
    pin_hash TEXT,           -- Argon2 hash, optionnel sur le web
    currency TEXT NOT NULL DEFAULT 'CAD',
    date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(user_id, name)
);

-- Sources d'import (configurations de parsing)
CREATE TABLE sr_.import_sources (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    description TEXT,
    date_format TEXT NOT NULL DEFAULT '%d/%m/%Y',
    delimiter TEXT NOT NULL DEFAULT ';',
    encoding TEXT NOT NULL DEFAULT 'utf-8',
    column_mapping JSONB NOT NULL,
    skip_lines INTEGER NOT NULL DEFAULT 0,
    has_header BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(profile_id, name)
);

-- Fichiers importes (deduplication)
CREATE TABLE sr_.imported_files (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    source_id UUID NOT NULL REFERENCES sr_.import_sources(id) ON DELETE CASCADE,
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    filename TEXT NOT NULL,
    file_hash TEXT NOT NULL,
    import_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    row_count INTEGER NOT NULL DEFAULT 0,
    status TEXT NOT NULL DEFAULT 'completed',
    notes TEXT,
    UNIQUE(source_id, filename)
);

-- Categories hierarchiques (parent_id, pre-seeded)
CREATE TABLE sr_.categories (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    parent_id UUID REFERENCES sr_.categories(id) ON DELETE SET NULL,
    color TEXT,
    icon TEXT,
    type TEXT NOT NULL DEFAULT 'expense',  -- 'expense' | 'income'
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    is_inputable BOOLEAN NOT NULL DEFAULT TRUE,
    sort_order INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Fournisseurs (normalises pour matching)
CREATE TABLE sr_.suppliers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    normalized_name TEXT NOT NULL,
    category_id UUID REFERENCES sr_.categories(id) ON DELETE SET NULL,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(profile_id, normalized_name)
);

-- Mots-cles pour auto-categorisation (pre-seeded)
CREATE TABLE sr_.keywords (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    keyword TEXT NOT NULL,
    category_id UUID NOT NULL REFERENCES sr_.categories(id) ON DELETE CASCADE,
    supplier_id UUID REFERENCES sr_.suppliers(id) ON DELETE SET NULL,
    priority INTEGER NOT NULL DEFAULT 0,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    UNIQUE(profile_id, keyword, category_id)
);

-- Transactions (avec support split via parent_transaction_id)
CREATE TABLE sr_.transactions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    date DATE NOT NULL,
    description TEXT NOT NULL,
    amount NUMERIC(12,2) NOT NULL,
    category_id UUID REFERENCES sr_.categories(id) ON DELETE SET NULL,
    supplier_id UUID REFERENCES sr_.suppliers(id) ON DELETE SET NULL,
    source_id UUID REFERENCES sr_.import_sources(id) ON DELETE SET NULL,
    file_id UUID REFERENCES sr_.imported_files(id) ON DELETE SET NULL,
    original_description TEXT,
    notes TEXT,
    is_manually_categorized BOOLEAN NOT NULL DEFAULT FALSE,
    is_split BOOLEAN NOT NULL DEFAULT FALSE,
    parent_transaction_id UUID REFERENCES sr_.transactions(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Ajustements (ponctuels ou recurrents)
CREATE TABLE sr_.adjustments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    description TEXT,
    date DATE NOT NULL,
    is_recurring BOOLEAN NOT NULL DEFAULT FALSE,
    recurrence_rule TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Entrees d'ajustement (montant par categorie)
CREATE TABLE sr_.adjustment_entries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    adjustment_id UUID NOT NULL REFERENCES sr_.adjustments(id) ON DELETE CASCADE,
    category_id UUID NOT NULL REFERENCES sr_.categories(id) ON DELETE CASCADE,
    amount NUMERIC(12,2) NOT NULL,
    description TEXT
);

-- Entrees budgetaires (grille 12 mois par categorie)
CREATE TABLE sr_.budget_entries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    category_id UUID NOT NULL REFERENCES sr_.categories(id) ON DELETE CASCADE,
    year INTEGER NOT NULL,
    month INTEGER NOT NULL,  -- 1-12
    amount NUMERIC(12,2) NOT NULL,
    notes TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(profile_id, category_id, year, month)
);

-- Templates de budget (reutilisables)
CREATE TABLE sr_.budget_templates (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    description TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(profile_id, name)
);

-- Entrees de template de budget
CREATE TABLE sr_.budget_template_entries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    template_id UUID NOT NULL REFERENCES sr_.budget_templates(id) ON DELETE CASCADE,
    category_id UUID NOT NULL REFERENCES sr_.categories(id) ON DELETE CASCADE,
    amount NUMERIC(12,2) NOT NULL,
    UNIQUE(template_id, category_id)
);

-- Templates de configuration d'import
CREATE TABLE sr_.import_config_templates (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    delimiter TEXT NOT NULL DEFAULT ';',
    encoding TEXT NOT NULL DEFAULT 'utf-8',
    date_format TEXT NOT NULL DEFAULT 'DD/MM/YYYY',
    skip_lines INTEGER NOT NULL DEFAULT 0,
    has_header BOOLEAN NOT NULL DEFAULT TRUE,
    column_mapping JSONB NOT NULL,
    amount_mode TEXT NOT NULL DEFAULT 'single',      -- 'single' | 'dual'
    sign_convention TEXT NOT NULL DEFAULT 'negative_expense',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(profile_id, name)
);

-- Preferences utilisateur (cle-valeur par profil)
CREATE TABLE sr_.user_preferences (
    profile_id UUID NOT NULL REFERENCES sr_.profiles(id) ON DELETE CASCADE,
    key TEXT NOT NULL,
    value TEXT NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (profile_id, key)
);

-- Journal d'audit (securite)
CREATE TABLE sr_.audit_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL,
    profile_id UUID,
    action TEXT NOT NULL,       -- 'login', 'export', 'delete', 'import', 'bulk_delete'
    entity_type TEXT,          -- 'transaction', 'profile', 'category', etc.
    entity_id UUID,
    metadata JSONB,            -- details supplementaires (ex: nombre de lignes importees)
    ip_address INET,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Index
CREATE INDEX idx_sr_profiles_user ON sr_.profiles(user_id);
CREATE INDEX idx_sr_transactions_profile_date ON sr_.transactions(profile_id, date);
CREATE INDEX idx_sr_transactions_category ON sr_.transactions(category_id);
CREATE INDEX idx_sr_transactions_supplier ON sr_.transactions(supplier_id);
CREATE INDEX idx_sr_transactions_source ON sr_.transactions(source_id);
CREATE INDEX idx_sr_transactions_file ON sr_.transactions(file_id);
CREATE INDEX idx_sr_transactions_parent ON sr_.transactions(parent_transaction_id);
CREATE INDEX idx_sr_categories_profile_parent ON sr_.categories(profile_id, parent_id);
CREATE INDEX idx_sr_categories_type ON sr_.categories(profile_id, type);
CREATE INDEX idx_sr_suppliers_profile_category ON sr_.suppliers(profile_id, category_id);
CREATE INDEX idx_sr_suppliers_normalized ON sr_.suppliers(profile_id, normalized_name);
CREATE INDEX idx_sr_keywords_profile_category ON sr_.keywords(profile_id, category_id);
CREATE INDEX idx_sr_keywords_keyword ON sr_.keywords(profile_id, keyword);
CREATE INDEX idx_sr_budget_entries_period ON sr_.budget_entries(profile_id, year, month);
CREATE INDEX idx_sr_adjustment_entries_adjustment ON sr_.adjustment_entries(adjustment_id);
CREATE INDEX idx_sr_imported_files_source ON sr_.imported_files(source_id);
CREATE INDEX idx_sr_audit_log_user ON sr_.audit_log(user_id, created_at);
CREATE INDEX idx_sr_audit_log_profile ON sr_.audit_log(profile_id, created_at);

-- Preferences par defaut (inserees a la creation d'un profil)
-- INSERT INTO sr_.user_preferences (profile_id, key, value) VALUES
--   ($profile_id, 'language', 'fr'),
--   ($profile_id, 'theme', 'light'),
--   ($profile_id, 'currency', 'CAD'),
--   ($profile_id, 'date_format', 'DD/MM/YYYY');

API REST

Base URL: https://resultat.lacompagniemaximus.com/api Auth: Bearer token (JWT depuis Logto) Tous les endpoints scopes par user_id (extrait du JWT) + profile_id (header ou parametre)

Profils

Methode Endpoint Description
GET /profiles Lister les profils de l'utilisateur
POST /profiles Creer un profil (seed categories + keywords)
GET /profiles/:id Details d'un profil
PUT /profiles/:id Modifier un profil (nom, devise, format date)
DELETE /profiles/:id Supprimer un profil et toutes ses donnees
POST /profiles/:id/verify-pin Verifier le PIN (couche securite optionnelle)

Transactions

Methode Endpoint Description
GET /transactions Lister avec filtres (date range, category_id, supplier_id, search, min/max amount, page, limit)
POST /transactions Creer une transaction manuelle
GET /transactions/:id Detail d'une transaction
PUT /transactions/:id Modifier (description, categorie, notes, montant)
DELETE /transactions/:id Supprimer
POST /transactions/:id/split Splitter en plusieurs entrees par categorie
DELETE /transactions/bulk Suppression en lot (body: { ids: [] })

Categories

Methode Endpoint Description
GET /categories Arbre hierarchique complet
POST /categories Creer une categorie custom
PUT /categories/:id Modifier (nom, couleur, icone, parent, sort_order)
DELETE /categories/:id Supprimer (SET NULL sur les transactions liees)
PUT /categories/reorder Reorganiser l'ordre des categories

Fournisseurs & Mots-cles

Methode Endpoint Description
GET /suppliers Lister les fournisseurs
POST /suppliers Creer un fournisseur
PUT /suppliers/:id Modifier
DELETE /suppliers/:id Supprimer
GET /keywords Lister les mots-cles
POST /keywords Creer un mot-cle
PUT /keywords/:id Modifier
DELETE /keywords/:id Supprimer

Budgets

Methode Endpoint Description
GET /budgets/:year Grille budgetaire 12 mois pour une annee
PUT /budgets Mettre a jour des entrees budgetaires (batch)
GET /budgets/templates Lister les templates de budget
POST /budgets/templates Creer un template
PUT /budgets/templates/:id Modifier un template
DELETE /budgets/templates/:id Supprimer un template
POST /budgets/apply-template Appliquer un template a une annee

Ajustements

Methode Endpoint Description
GET /adjustments Lister les ajustements
POST /adjustments Creer un ajustement (avec entrees)
GET /adjustments/:id Detail d'un ajustement
PUT /adjustments/:id Modifier
DELETE /adjustments/:id Supprimer

Import CSV

Methode Endpoint Description
POST /import/upload Upload un fichier CSV (stockage temporaire)
POST /import/parse Parser le CSV avec config (delimiter, encoding, date_format, column mapping)
GET /import/preview/:upload_id Preview des transactions parsees avec auto-categorisation
POST /import/confirm Confirmer l'import (inserer les transactions)
GET /import/sources Configurations de sources d'import
POST /import/sources Sauvegarder une config de source
PUT /import/sources/:id Modifier une config
DELETE /import/sources/:id Supprimer une config
GET /import/history Historique des imports
GET /import/config-templates Templates de configuration d'import
POST /import/config-templates Creer un template de config

Rapports (rendus possibles par Option D)

Methode Endpoint Description
GET /reports/monthly-summary/:year/:month Totaux par categorie, budget vs reel
GET /reports/trends Tendances mensuelles sur une periode (query: start, end, category_ids)
GET /reports/category-breakdown Repartition des depenses par categorie (query: start, end)

Frontend Web

C'est le gros avantage : l'app Tauri existante est deja React 19 + Vite + Tailwind CSS v4 + TypeScript. Les composants UI, hooks et logique metier sont hautement reutilisables. Changements principaux :

  • Remplacer les commandes Tauri (Rust → SQLite) par des appels API (fetch → REST)
  • Remplacer @tauri-apps/plugin-sql par un client API
  • Remplacer les operations systeme de fichiers (import CSV depuis fichier local) par un upload fichier
  • Remplacer la selection de profil Tauri → auth web + selecteur de profil
  • Conserver tous les composants React : table de transactions, arbre de categories, grille budget, graphiques (Recharts), wizard import
  • Conserver react-router-dom, i18next, lucide-react, recharts, papaparse (pour preview client), @dnd-kit (drag-and-drop)
  • Retirer les dependances Tauri : @tauri-apps/api, @tauri-apps/plugin-*

Heberge sur Coolify a resultat.lacompagniemaximus.com.

Securite (Option D — detaillee)

Puisque l'app traite des donnees financieres sensibles, voici le modele de securite complet :

  1. Chiffrement au repos : Repertoire de donnees PostgreSQL sur volume chiffre (LUKS/dm-crypt sur le VPS)
  2. TLS en transit : HTTPS obligatoire, headers HSTS
  3. Isolation du datastore :
    • Schema sr_ separe des autres apps dans PostgreSQL
    • Credentials PostgreSQL dedies au service simpl-resultat (pas de superuser partage)
    • Le service n'a pas acces a Forgejo, MinIO (sauf pour import CSV temporaire), ni aux autres schemas
  4. Isolation par utilisateur : Toutes les requetes filtrees par user_id + profile_id. Pas de requetes cross-user possibles.
  5. Backups chiffres : pg_dump du schema sr_ chiffre avec une cle stockee hors du VPS (ex: sur la machine locale ou dans un secret manager)
  6. Politique d'acces : Jamais de consultation des donnees utilisateurs sans consentement explicite. Pas de dashboard admin montrant les donnees financieres des utilisateurs.
  7. Audit log : Table sr_.audit_log (user_id, action, entity, timestamp) pour tracer les acces sensibles (exports, suppressions, imports)
  8. Rate limiting : Sur tous les endpoints, particulierement import CSV (upload) et auth
  9. Input validation : Sanitization de toutes les entrees, particulierement les descriptions de transactions et les notes
  10. Session : JWT configure dans Logto (access token ~1h, refresh token ~14j), SDK gere le refresh automatiquement

E2EE future ("mode Coffre-fort") : Option premium ou le profil entier est chiffre cote client (AES-256-GCM, cle derivee du mot de passe utilisateur). Le serveur stocke un blob opaque. Incompatible avec les fonctionnalites serveur (rapports, search, import CSV serveur). L'utilisateur choisit : fonctionnalites completes OU privacy maximale.

Gestion d'acces

  • Acces a l'app web reserve aux utilisateurs avec un Compte Maximus
  • Plan gratuit : acces basique (illimite pour la v1, pas de paywall)
  • Plan premium (futur) : import CSV illimite, rapports avances, multi-profil, backup automatique
  • Verification du JWT claims apps.simpl-resultat pour confirmer l'acces
  • Profile-level access : un utilisateur peut avoir plusieurs profils (personnel, couple, etc.)
  • PIN optionnel par profil sur le web (couche de securite supplementaire, comme sur desktop)

Sync Desktop <-> Web

Deux approches, la plus simple pour la v1 :

Option A — Export/Import (v1, simple) [retenue]

  • L'app desktop a deja un export/import SREF (AES-256-GCM chiffre)
  • Ajouter un bouton "Synchroniser avec le cloud" dans l'app desktop
  • Upload le fichier SREF vers le serveur, le serveur dechiffre et importe
  • Pas de sync temps reel, mais suffisant pour un backup/migration

Option B — Sync continue (v2, complexe) [future]

  • Change tracking sur chaque table (updated_at + sync tokens)
  • Sync bidirectionnelle comme simpl-liste
  • Conflict resolution par entite
  • Plus complexe a cause du nombre de tables (13) et des relations

Recommandation : Option A pour la v1 (reutilise l'infrastructure export/import existante), Option B comme amelioration future.

Plan de travail

Issue 1 — Schema PostgreSQL et migrations [type:task]

Dependances: Logto deploye et operationnel

  • Creer le schema sr_ dans PostgreSQL (15 tables, index, contraintes)
  • Table sr_profiles (multi-profil par utilisateur)
  • Table sr_audit_log
  • Script de seed : 54 categories + 60+ keywords par profil
  • Script de migration
  • Tests du schema (contraintes, cascades, unicite)

Issue 2 — API REST backend [type:feature]

Dependances: Issue 1

  • Setup projet (Next.js ou Express sur Coolify)
  • Middleware auth (verification JWT, extraction user_id, injection profile_id)
  • Endpoints profils (CRUD + PIN)
  • Endpoints transactions (CRUD + filtres + splits + bulk delete)
  • Endpoints categories (CRUD + arbre hierarchique + reorder)
  • Endpoints fournisseurs + keywords
  • Endpoints budgets (grille mensuelle + templates + apply)
  • Endpoints ajustements (CRUD avec entrees)
  • Endpoints rapports (monthly summary, trends, category breakdown)
  • Rate limiting + input validation
  • Audit logging middleware

Issue 3 — Import CSV serveur [type:feature]

Dependances: Issue 2

  • Upload CSV vers stockage temporaire (MinIO ou /tmp)
  • Parsing multi-encoding (UTF-8, Windows-1252, ISO-8859-15) comme l'app desktop
  • Auto-detection delimiteur
  • Column mapping configurable
  • Auto-categorisation par keywords (reproduire la logique Rust existante)
  • Preview avant confirmation
  • Deduplication par filename + file_hash (comme l'app desktop)
  • Gestion des import sources et config templates

Issue 4 — Frontend web [type:feature]

Dependances: Issue 2, Issue 3

  • Setup projet (React + Vite + Tailwind CSS v4, port de la config existante)
  • Client API (wrapper fetch avec auth JWT, gestion erreurs, profile_id)
  • Auth flow (login via Logto, SDK @logto/react ou OIDC standard)
  • Selecteur de profil
  • Page transactions (table, filtres, tri, recherche, pagination)
  • Page detail transaction (edition, re-categorisation, split)
  • Saisie manuelle de transactions
  • Page categories (arbre hierarchique, CRUD, drag-and-drop)
  • Page budget (grille 12 mois, budget vs reel, templates)
  • Page ajustements (CRUD, recurrence)
  • Wizard import CSV (port des 13 etapes existantes vers upload serveur)
  • Page rapports (graphiques Recharts : tendances, repartition par categorie)
  • Page fournisseurs et keywords
  • Dark mode
  • i18n FR/EN (reutiliser les fichiers de traduction existants)
  • Responsive
  • Deployer sur Coolify (resultat.lacompagniemaximus.com)

Issue 5 — Sync desktop <-> web (v1: export/import) [type:feature]

Dependances: Issue 2

  • Ajouter bouton "Synchroniser avec le cloud" dans l'app desktop Tauri
  • Auth flow desktop : OAuth2 via Logto (OIDC standard, tauri-plugin-oauth ou WebView)
  • Export SREF → upload vers le serveur
  • Le serveur dechiffre le SREF et importe dans le schema sr_
  • Download depuis le serveur → import SREF dans l'app desktop
  • Indicateur de derniere synchronisation

Issue 6 — Securite et audit [type:task]

Dependances: Issue 2

  • Configurer le chiffrement au repos du volume PostgreSQL (LUKS)
  • Credentials PostgreSQL dedies (pas de superuser partage)
  • Configurer les backups chiffres du schema sr_
  • Implementer sr_audit_log (middleware automatique)
  • Headers de securite (HSTS, CSP, X-Frame-Options, X-Content-Type-Options)
  • Tests de securite (injection SQL, XSS, IDOR cross-user)

Ordre d'execution

Logto (prerequis externe)
    └── Issue 1 (Schema PostgreSQL)
            ├── Issue 2 (API REST)
            │       ├── Issue 3 (Import CSV)
            │       ├── Issue 5 (Sync desktop)
            │       └── Issue 6 (Securite)
            │
            └── Issue 4 (Frontend web) — depends on Issue 2 + Issue 3

Criteres d'acceptation

  • Un utilisateur connecte peut acceder a ses finances depuis resultat.lacompagniemaximus.com
  • Les operations CRUD fonctionnent pour les transactions, categories, budgets, ajustements
  • L'import CSV fonctionne cote serveur (upload, parsing, auto-categorisation, preview, confirmation)
  • Les rapports (tendances, repartition) s'affichent correctement avec des graphiques
  • Le multi-profil fonctionne (creer, switcher, supprimer des profils)
  • L'app desktop continue de fonctionner sans compte (offline-first preserve)
  • La sync export/import entre desktop et web fonctionne
  • Le site fonctionne en FR et EN, en light et dark mode
  • Le site est responsive
  • Les donnees financieres sont isolees des autres services (schema separe, credentials dedies)
  • Un audit log trace les acces sensibles

Estimation

8-12 sessions de travail (hors service auth). C'est le plus gros chantier des apps web en raison de la complexite du modele de donnees (13 tables desktop → 15 tables web) et de la logique metier (import CSV, auto-categorisation, budgets, rapports).

Reutilisabilite du code existant

Composant Reutilisable ? Notes
Composants React (53 fichiers) Oui Deja React 19 + Tailwind CSS v4, memes primitives que le web
Recharts (graphiques) Oui Librairie web native, memes graphiques
Types TypeScript Oui Interfaces, enums, types de donnees
Hooks custom (12 hooks useReducer) Partiellement Adapter les appels services → fetch API
Services metier (14 services) Partiellement Remplacer tauri-plugin-sql par client API REST
i18n (traductions FR/EN) Oui Fichiers JSON reutilisables directement
@dnd-kit (drag-and-drop) Oui Librairie web native
papaparse (parsing CSV) Oui Utile pour preview cote client
lucide-react (icones) Oui Librairie web native
react-router-dom (routing) Oui Meme routing
Commandes Rust (17 commandes Tauri) Non Remplacees par l'API REST
Crypto Rust (Argon2, AES-256-GCM) Non PIN optionnel reimplemente en JS si necessaire
Import CSV Rust (parsing) Non Reimplemente cote serveur (Node.js)
tauri-plugin-sql Non Remplace par client API
tauri-plugin-updater Non Non applicable au web