Files
schotzimmo/.kiro/specs/socle-plateforme/design.md
T

27 KiB

Design Document

Spec : Socle Plateforme SchotzimmO

Document de conception technique rédigé en français ; les titres de sections suivent le gabarit standard. Le besoin fonctionnel métier est décrit dans requirements.md ; les exigences techniques dérivées dans design-requirements.md. La présente conception met en œuvre ces exigences.


Overview

SchotzimmO (« l'ami de l'immobilier » ; Schotzi signifie « ami » en alsacien, immO pour l'immobilier) est une plateforme web SaaS multi-utilisateur, multi-réseaux et multi-module destinée aux mandataires immobiliers. Ce document décrit la conception technique du socle : les capacités transverses communes à tous les Modules (authentification, comptes, Réseaux, parrainage/hiérarchie, visibilité descendante, annuaire, abonnements/Packs, activation des Modules, administration, tableau de bord, cadre applicatif, isolation des données).

Le socle fournit l'ossature sur laquelle se branchent les Modules métier (Carnet d'adresses, KMS, CRM, Agenda, Facturation…), chacun décrit dans sa propre spec. Il définit les contrats transverses que ces Modules implémentent (contribution au Tableau_De_Bord, partage hiérarchique, contrôle d'accès par Abonnement).

Décision structurante : architecture à deux niveaux de base

L'isolation des réseaux est physique, par une architecture à deux niveaux de base de données :

  • Base Socle (centrale, unique) : authentification, référentiel des Réseaux et leurs informations de connexion, abonnements, catalogue des Modules/Packs, administration, KPI d'usage remontés par réseau. Elle ne contient aucune donnée métier produite par les mandataires.
  • Base Réseau (une par Réseau) : toutes les données métier cloisonnées — profil mandataire, parrainage/hiérarchie, et données des Modules.

À la connexion, le socle authentifie l'utilisateur sur la Base Socle, lit son Réseau, en déduit la chaîne de connexion de sa Base Réseau, et toute l'activité métier de la session s'exécute sur cette Base Réseau. L'identité (account_id) est générée côté socle et réutilisée comme clé du profil côté réseau (identité partagée).

Périmètre fonctionnel (socle)

# Capacité Description
1 Inscription Création de Compte avec Reseau, Identifiant_Reseau, Statut_Juridique, SIREN optionnel ; via Lien_Parrainage ou directe ; Starter par défaut
2 Authentification & session Login e-mail/mot de passe (Base Socle), JWT, « se souvenir de moi », reset par e-mail
3 Réseaux Référentiel multi-réseaux, base par réseau, onboarding réseau par l'Administrateur, personnalisation (thème/paramétrage)
4 Parrainage & hiérarchie Déclaration parrain/filleuls, invitations e-mail, états supposé/validé, rapprochement par e-mail, confirmation bilatérale, anti-cycle (Base Réseau)
5 Visibilité descendante Agrégats sur toute la Descendance, détail limité aux Filleuls_Directs (N+1)
6 Annuaire Liste et arbre des membres d'un Reseau (inscrits + déclarés), recherche
7 Abonnements & Packs Starter/Pro/Premium inclusifs, souscription, rétrogradation = conservation + consultation seule
8 Catalogue & activation Modules Référentiel Module->Pack, états accessible/consultation/verrouillé, contrôle d'accès par appel
9 Administration Rôle admin, gestion comptes/abonnements/catalogue, onboarding réseau, correction d'arbre, KPI d'usage par réseau
10 Tableau de bord Agrégation résiliente des contributions des Modules accessibles
11 Cadre applicatif Navigation dynamique, avatar/profil, mapping d'erreurs, garde d'auth, personnalisation par réseau
12 Isolation des données Isolation physique par Base Réseau + authentification centrale ; références inter-bases logiques

Contraintes techniques

  • Multi-utilisateur authentifié : chaque Mandataire possède un Compte (Base Socle) ; session par JWT signé HS256 (durée configurable, défaut 720 min / 12 h) ; tous les endpoints de données sont protégés par authentification Bearer.
  • Isolation physique par réseau : une Base Réseau par Réseau ; la Base Socle ne porte aucune donnée métier. Les références account_id entre Base Socle et Base Réseau sont logiques (mêmes valeurs d'identifiant), jamais des FK SQL ni des jointures inter-bases.
  • Résolution de connexion par réseau : la Base Socle stocke, par Réseau, une clé logique (db_connection_key) qui résout la chaîne de connexion de la Base Réseau via la configuration (jamais la chaîne en base). Un service de résolution fournit le DbContext réseau de la requête selon le claim network du JWT.
  • Frontend : Angular (TypeScript), SPA, routage protégé par un garde d'authentification, navigation construite dynamiquement depuis le catalogue des Modules, thème/libellés personnalisables par Réseau.
  • Backend : ASP.NET Core Web API (C#), architecture en couches (Controllers -> Services -> Repositories/DbContext) avec deux DbContext : SocleDbContext et ReseauDbContext.
  • Base de données : MariaDB / MySQL, via le provider EF Core Pomelo.EntityFrameworkCore.MySql. Chaînes de connexion fournies par variable d'environnement : la Base Socle et chaque Base Réseau peuvent être conteneurisées ou externes (cas de l'environnement de PROD de l'exploitant). Migrations EF Core appliquées de façon idempotente : schéma socle sur la Base Socle, schéma réseau sur chaque Base Réseau.
  • E-mails : service SMTP configurable (invitations, réinitialisation). Si Smtp:Host est vide, repli de journalisation du lien.
  • Hébergement : Docker Compose — un conteneur frontend (Angular servi par nginx) publié sur un port hôte, un conteneur backend (ASP.NET Core) non exposé directement, et des bases MariaDB conteneurisées (services avec volumes) ou externes. Terminaison HTTPS par un reverse-proxy externe (ex. Caddy).
  • Deux réseaux au lancement : iad (production) et demo (démonstration en ligne + tests de développement).
  • Politique de visibilité : 403 Forbidden pour tout accès hors Portee_Hierarchique ; 404 pour les ressources réellement inexistantes dans le périmètre autorisé.

Charte graphique

Reprise de la palette SchotzimmO : variables CSS globales — Orange #F28C28, Bleu #0074B8 (primaire), Anthracite #343A40, Gris clair #E7E9EA. Le bleu est conservé comme clin d'oeil au réseau IAD (réseau partenaire au lancement). La charte de base est personnalisable par Réseau (thème, libellés) via le paramétrage porté par la Base Socle. Convention d'interaction : les boutons d'action passent à l'orange plein au survol ; les boutons « Annuler » restent neutres, les suppressions rouges, les états actifs en bleu plein. Les assets (logo, favicon) seront fournis ultérieurement.


Architecture

Architecture globale

graph TB
    subgraph "Navigateur (SPA)"
        FE[Angular App<br/>TypeScript]
    end

    subgraph "Backend (ASP.NET Core)"
        API[API REST<br/>Controllers]
        AUTHZ[Autorisation<br/>Auth + Abonnement + Hierarchie]
        RES[NetworkConnectionResolver<br/>resout la base reseau]
        SVC[Services socle + modules]
        MOD[Contrats Modules<br/>IModuleAccess<br/>IDashboardContributor<br/>IHierarchySharing]
        MAIL[EmailService<br/>SMTP / repli log]
        SOCLE[(SocleDbContext)]
        RESEAU[(ReseauDbContext)]
    end

    subgraph "Stockage"
        DBS[(Base Socle<br/>MariaDB)]
        DBR1[(Base Reseau IAD)]
        DBR2[(Base Reseau Demo)]
    end

    SMTP[Serveur SMTP]

    FE -- "HTTP/JSON + Bearer JWT" --> API
    API --> AUTHZ
    AUTHZ --> RES
    AUTHZ --> SVC
    SVC --> MOD
    SVC --> MAIL
    SVC --> SOCLE
    SVC --> RESEAU
    SOCLE --> DBS
    RES -. "chaine de connexion selon claim network" .-> RESEAU
    RESEAU --> DBR1
    RESEAU --> DBR2
    MAIL -- "SMTP" --> SMTP

Flux de connexion et résolution de base réseau

sequenceDiagram
    participant U as Utilisateur
    participant FE as Angular Frontend
    participant API as AuthController
    participant SOCLE as SocleDbContext (Base Socle)
    participant RES as NetworkConnectionResolver
    participant RESEAU as ReseauDbContext (Base Reseau)

    U->>FE: Saisit e-mail + mot de passe
    FE->>API: POST /api/auth/login
    API->>SOCLE: Verifie identifiants + lit network_id
    SOCLE-->>API: Compte valide (account_id, network, role)
    API-->>FE: JWT { sub, network, role }
    U->>FE: Requete metier (Bearer JWT)
    FE->>API: GET /api/... (module)
    API->>RES: Resout la connexion pour claim network
    RES-->>RESEAU: Chaine de connexion de la Base Reseau
    API->>RESEAU: Execute la requete metier (account_id)
    RESEAU-->>FE: Donnees du reseau

Flux de contrôle d'accès à un Module

sequenceDiagram
    participant FE as Frontend
    participant API as Module Controller
    participant AUTHZ as Autorisation
    participant SUB as SubscriptionService (Base Socle)
    participant SVC as Service du Module (Base Reseau)

    FE->>API: Requete Module + Bearer JWT
    API->>AUTHZ: Verifie JWT (sub, network, role)
    AUTHZ->>SUB: Droit sur ce Module ?
    SUB-->>AUTHZ: accessible | consultation | verrouille
    alt verrouille
        AUTHZ-->>FE: 403 (abonnement requis)
    else consultation + ecriture
        AUTHZ-->>FE: 403 (lecture seule)
    else autorise
        AUTHZ->>SVC: Execute sur la Base Reseau (account_id)
        SVC-->>FE: 200 / 201
    end

Data Models

Le modèle est réparti sur deux bases. Le détail physique (types MariaDB, index, contraintes, seed) est tenu dans .kiro/specs/_data-model/database.md.

Base Socle (centrale)

erDiagram
    NETWORK ||--o{ ACCOUNT : "network_id"
    NETWORK ||--o{ NETWORK_USAGE_KPI : "network_id"
    ACCOUNT ||--|| SUBSCRIPTION : "account_id"
    PACK ||--o{ SUBSCRIPTION : "pack_id"
    PACK ||--o{ MODULE_CATALOG : "required_pack_id"
    ACCOUNT ||--o{ PASSWORD_RESET : "account_id"

    NETWORK {
        int Id PK
        string Code UK
        string Name UK
        string DbConnectionKey UK
        text ThemeSettings "nullable"
        bool IsActive
        bool IsProvisioned
    }
    ACCOUNT {
        int Id PK
        int NetworkId FK
        string Email UK
        string PasswordHash
        string Role "mandataire|admin"
        bool IsActive
        datetime CreatedAt
    }
    PACK {
        int Id PK
        string PackKey UK
        int Level UK
        bool IsFree
    }
    MODULE_CATALOG {
        int Id PK
        string ModuleKey UK
        int RequiredPackId FK
        bool IsActive
    }
    SUBSCRIPTION {
        int Id PK
        int AccountId FK
        int PackId FK
        datetime StartsAt
        datetime EndsAt "nullable"
        string Status "actif|expire"
    }
    PASSWORD_RESET {
        int Id PK
        int AccountId FK
        string TokenHash
        datetime ExpiresAt
        bool Used
    }
    NETWORK_USAGE_KPI {
        int Id PK
        int NetworkId FK
        string MetricKey
        decimal MetricValue
        string Period "nullable"
    }

Base Réseau (une par réseau)

erDiagram
    MANDATAIRE_PROFILE ||--o| MANDATAIRE_PROFILE : "sponsor_account_id"
    MANDATAIRE_PROFILE ||--o{ DECLARED_MEMBER : "declared_by_account_id"
    MANDATAIRE_PROFILE ||--o{ AFFILIATION_REQUEST : "requester / target"

    MANDATAIRE_PROFILE {
        int AccountId PK "= accounts.id du socle (ref logique)"
        string FirstName
        string LastName
        string NetworkMemberId UK
        string LegalStatus "nullable"
        string Siren "nullable"
        int SponsorAccountId FK "nullable"
        datetime CreatedAt
    }
    DECLARED_MEMBER {
        int Id PK
        string Email
        string FirstName "nullable"
        string LastName "nullable"
        int DeclaredByAccountId FK
        string Relation "parrain|filleul"
        string Status "declare|invite"
    }
    AFFILIATION_REQUEST {
        int Id PK
        int RequesterAccountId FK
        int TargetAccountId FK
        string ProposedRelation "parrain|filleul"
        string Status "en_attente|approuvee|refusee"
    }

Notes de modélisation

  • Identité partagée (Option B) : ACCOUNT (Base Socle) porte le strict minimum d'authentification. MANDATAIRE_PROFILE (Base Réseau) porte le profil métier et le parrainage, sa clé primaire AccountId réutilisant accounts.id. La cohérence des deux morceaux (création, désactivation) est assurée applicativement.
  • Parrainage dans la Base Réseau : l'auto-référence sponsor_account_id est une vraie FK au sein de la base réseau -> intégrité du parrainage garantie par la base. Le cloisonnement réseau est physique (base propre au réseau), d'où l'absence de colonne network_id dans les tables réseau.
  • Références inter-bases : account_id circule entre les deux bases comme référence logique (FK*), jamais via une FK SQL ou une jointure traversant deux bases.
  • Abonnement : un seul SUBSCRIPTION actif par Compte (Base Socle). Packs inclusifs par Level. Consultation seule déduite : Module hors Pack courant mais données présentes dans la Base Réseau.
  • Réseau : networks.db_connection_key = clé logique de connexion (secret en configuration) ; is_provisioned indique que la Base Réseau est initialisée (schéma + seed) avant toute inscription.
  • KPI d'usage : network_usage_kpis reçoit des agrégats non nominatifs remontés par chaque réseau ; aucune consolidation ne traverse les bases réseau.

Components and Interfaces

Endpoints d'API (socle)

Domaine Méthode & route Rôle
Réseaux GET /api/networks Liste des Reseaux ouverts à l'inscription (public)
Inscription POST /api/auth/register Création de Compte (directe ou via referralToken)
Auth POST /api/auth/login Connexion (Base Socle), retourne le JWT
Auth POST /api/auth/forgot-password Déclenche la réinitialisation (réponse constante)
Auth POST /api/auth/reset-password Applique le nouveau mot de passe via jeton
Auth POST /api/auth/change-password Changement de mot de passe (authentifié)
Profil GET /api/profile, PUT /api/profile Consulter/modifier le Profil (Base Réseau ; Reseau non modifiable)
Parrainage GET /api/referrals/validate Valide un Lien_Parrainage et retourne le pré-remplissage
Parrainage POST /api/referrals/declare Déclarer parrain / filleuls
Parrainage POST /api/referrals/{id}/invite Envoyer une Invitation par e-mail
Parrainage POST /api/referrals/confirmations Proposer une Confirmation_Affiliation
Parrainage POST /api/referrals/confirmations/{id}/approve|reject Approuver / refuser
Annuaire GET /api/directory Membres du Reseau (inscrits + déclarés), paginé, recherche
Annuaire GET /api/directory/tree Arbre de la Descendance de l'appelant
Abonnement GET /api/subscription, POST /api/subscription Abonnement courant / changement de Pack
Modules GET /api/modules Catalogue + état par Module (accessible/consultation/verrouille)
Tableau de bord GET /api/dashboard Agrégation des contributions des Modules accessibles
Admin GET /api/admin/accounts, PUT /api/admin/accounts/{id}/subscription Gestion des comptes/abonnements
Admin POST /api/admin/networks, PUT /api/admin/networks/{id} Onboarding et paramétrage d'un Reseau
Admin PUT /api/admin/modules Rattachement Module -> Pack
Admin GET /api/admin/stats KPI d'usage remontés par réseau (non nominatifs)
Admin POST /api/admin/referrals/reassign Correction de l'arbre de parrainage
Santé GET /health Sonde de disponibilité (healthcheck conteneur)

Composants backend structurants

  • SocleDbContext — accès à la Base Socle (networks, accounts, packs, module_catalog, subscriptions, password_resets, network_usage_kpis).
  • ReseauDbContext — accès à une Base Réseau (mandataire_profiles, declared_members, affiliation_requests, + tables des Modules). Sa chaîne de connexion est fournie à la requête.
  • NetworkConnectionResolver — à partir du claim network du JWT, résout la db_connection_key du Réseau vers la chaîne de connexion (configuration) et fournit le ReseauDbContext de la requête.
  • AuthService / AccountService — authentification sur la Base Socle, création de Compte (socle) + profil (réseau) dans une opération coordonnée.
  • ReferralService — déclarations, invitations, rapprochement par e-mail, confirmations (Base Réseau).
  • SubscriptionService — abonnement et calcul des Modules accessibles / consultation seule.
  • NetworkProvisioningService — onboarding d'un Réseau (création/initialisation de la Base Réseau : schéma + seed), réservé à l'Administrateur.

Contrats transverses pour les Modules

Approche « plugins applicatifs » par injection de dépendances (composition à la compilation) :

  • IModuleAccess — déclare le ModuleKey et le RequiredPack d'un Module ; permet au socle de calculer l'état (accessible / consultation / verrouille) et de garder les routes.
  • IDashboardContributor — fournit les éléments d'un Module pour le Tableau_De_Bord, appelé de façon résiliente.
  • IHierarchySharing — fournit, pour un périmètre hiérarchique, les agrégats (toute la Descendance) et le détail N+1 (Filleuls_Directs) que le Module accepte de partager.

Ajouter un Module = enregistrer son implémentation (injection de dépendances), son entrée de Catalogue_Modules, et ses tables dans le schéma de la Base Réseau.


Error Handling

  • Mapping des Codes_HTTP (centralisé côté Frontend, messages en français) : 401 (session expirée -> reconnexion), 403 (accès refusé / abonnement requis / lecture seule), 404 (ressource inexistante dans le périmètre autorisé), 409 (conflit : e-mail déjà utilisé, cycle de parrainage), 422 (erreurs de validation), 5xx (indisponibilité).
  • Résolution de base réseau : si la Base Réseau d'un Réseau est indisponible ou non provisionnée, le Backend répond 503 (service indisponible) sans exposer de détail d'infrastructure ; la connexion n'est jamais tentée pour un Réseau is_provisioned = 0.
  • Validation d'entrée : DTO validés côté Backend ; erreurs de champ en 422, sans divulgation sensible.
  • Anti-énumération : forgot-password répond toujours 200 ; register renvoie un message générique si l'e-mail est déjà pris.
  • Isolation en cas d'erreur d'accès : accès hors Portee_Hierarchique -> 403 ; 404 possible pour les ressources dont l'existence serait sensible.
  • Cohérence inter-bases : la création d'un Compte (socle) et de son profil (réseau) est coordonnée ; en cas d'échec de l'une des étapes, l'opération est annulée pour ne pas laisser un Compte sans profil (ou l'inverse).
  • Transactions : toute écriture multi-entités au sein d'une base est atomique (rollback complet en cas d'échec). Les opérations traversant les deux bases sont séquencées avec compensation applicative (pas de transaction distribuée).
  • Secrets : aucun secret (mot de passe, token, chaîne de connexion) n'est journalisé ni renvoyé dans un message d'erreur.
  • Résilience du Tableau de bord : l'échec de la contribution d'un Module est isolé et n'empêche pas le rendu des autres sections.

Testing Strategy

  • Tests unitaires (Backend) : services socle (authentification, création compte+profil coordonnée, calcul des Modules accessibles, portée hiérarchique), résolution de connexion réseau (mock de configuration), avec mocks (SMTP, horloge).
  • Tests d'intégration (Backend) : endpoints d'API sur une Base Socle + une Base Réseau de test (conteneurs éphémères), vérifiant les Codes_HTTP, l'isolation par base réseau et la cohérence inter-bases.
  • Tests de propriétés / invariants : non-existence de cycle dans l'Arbre_Parrainage, au plus un abonnement actif par compte, visibilité strictement descendante, aucune jointure/accès traversant deux bases réseau.
  • Tests Frontend : composants Angular clés (formulaires d'inscription/connexion, navigation dynamique, personnalisation par réseau, mapping d'erreurs).
  • Tests de sécurité ciblés : routes protégées refusant l'accès sans JWT valide, routes admin refusant un rôle mandataire, impossibilité d'accéder à une Base Réseau autre que celle du claim network.
  • Non-régression : suite exécutée au build ; aucune livraison sans build vert.

Correctness Properties

Propriétés que le système doit garantir en permanence (invariants vérifiables) :

Property 1: Isolation physique par réseau

Validates: Requirements 3.3, 3.4, 12.2, 12.5

Toute donnée métier d'un Réseau réside dans sa Base Réseau ; aucune requête ne lit les données d'un autre Réseau, et aucune jointure ne traverse deux bases.

Property 2: Authentification centrale cohérente

Validates: Requirements 1.3, 2.2, 12.1

Un Compte existe dans la Base Socle avec un e-mail unique sur toute la plateforme ; tout Compte actif possède un profil correspondant dans sa Base Réseau (account_id partagé), et réciproquement.

Property 3: Acyclicité du parrainage

Validates: Requirements 4.5, 4.6

L'Arbre_Parrainage ne contient aucun cycle ; un mandataire a au plus un sponsor_account_id validé.

Property 4: Visibilité descendante stricte

Validates: Requirements 5.1, 5.2, 5.3, 5.4

Un mandataire n'accède jamais aux données d'un ancêtre ou d'une autre branche ; le détail est limité au niveau N+1, l'agrégat couvre toute la Descendance.

Property 5: Unicité de l'abonnement actif

Validates: Requirements 7.7

Au plus un abonnement au statut actif par Compte à un instant donné (Base Socle).

Property 6: Inclusivité des Packs

Validates: Requirements 7.2, 8.1

Un Compte accède à tout Module dont RequiredPack.Level <= Level du Pack courant.

Property 7: Conservation à la rétrogradation

Validates: Requirements 7.5, 7.6, 7.8

Aucune donnée n'est supprimée lors d'une Retrogradation ; les Modules hors Pack mais pourvus de données (Base Réseau) restent accessibles en consultation seule.

Property 8: Confidentialité des secrets

Validates: Requirements 2.7, 2.8

Les mots de passe, jetons et chaînes de connexion ne sont jamais stockés en clair ni journalisés ; la Base Socle ne stocke qu'une clé logique de connexion.

Property 9: Atomicité et cohérence inter-bases

Validates: Requirements 12.4

Toute écriture multi-entités au sein d'une base est atomique ; les opérations traversant les deux bases sont compensées pour ne jamais laisser un Compte sans profil (ou l'inverse).

Property 10: Réseau provisionné avant inscription

Validates: Requirements 3.1, 3.2

Aucune inscription n'est possible sur un Réseau non actif ou non provisionné (is_active = 0 ou is_provisioned = 0).


Design Decisions

  • Deux niveaux de base (Base Socle + Base Réseau par réseau) (retenu) vs base unique mutualisée avec cloisonnement applicatif (rejeté) : l'isolation physique par réseau garantit le cloisonnement par construction (pas de fuite inter-réseaux par bug), facilite la personnalisation par réseau et une éventuelle exigence contractuelle de séparation. Coût assumé : migrations à appliquer sur N bases réseau, provisioning d'une base à chaque nouveau réseau, et références inter-bases logiques (pas de FK SQL). Alternative mutualisée écartée car le projet valorise l'isolation forte et la personnalisation par réseau.
  • Identité partagée, Option B (retenu) vs compte entièrement au socle (rejeté) : l'authentification minimale reste centrale (login unique), mais le profil et le parrainage vivent dans la Base Réseau pour que les FK métier (parrainage, propriété des données) restent intra-base et garanties par MariaDB. Le compte entièrement au socle aurait dispersé les FK métier entre deux bases, sans intégrité référentielle.
  • Clé logique de connexion + secret en configuration (retenu) vs chaîne de connexion stockée en base (rejeté) : conforme à la règle « aucun secret en base/log » ; la Base Socle ne contient qu'une clé, le secret restant dans la configuration d'environnement.
  • Pas de consolidation inter-réseaux ; KPI remontés par réseau (retenu) : l'administration consomme des agrégats non nominatifs poussés par chaque réseau dans network_usage_kpis, sans requête traversant les bases réseau. Évite toute jointure inter-bases et respecte le cloisonnement.
  • MariaDB/MySQL via Pomelo (retenu) vs SQLite (rejeté) : multi-utilisateur concurrent + serveur MariaDB déjà présent en PROD. Connexions en configuration (conteneurisées ou externes) sans changement de code.
  • JWT HS256 (retenu) : simple à exploiter ; clé en configuration, démarrage refusé si absente. Passage à RS256 possible plus tard.
  • Contrats de Modules (plugins applicatifs) (retenu) vs socle monolithique (rejeté) : permet d'empiler les Modules sans modifier le socle.
  • Packs inclusifs par niveau (retenu) vs composition module par module (rejeté au MVP) : trois Packs ordonnés simples à comprendre et facturer ; rattachement Module->Pack paramétrable.
  • Rétrogradation = conservation + consultation seule (retenu) vs suppression/archivage (rejeté) : aucune perte de donnée ; consultation seule déduite de la présence de données.
  • Onboarding réseau par l'Administrateur (retenu) : un Réseau est créé, provisionné et paramétré (thème) avant toute inscription de mandataire ; détail à affiner après l'expérience IAD.

Deployment

  • docker-compose : services backend (ASP.NET Core, non exposé), frontend (nginx servant la SPA Angular, publié sur un port hôte), et optionnellement des services db MariaDB (Base Socle et bases réseau) avec volumes, pour les environnements sans serveur externe.
  • Bases externes : en PROD, les services db sont omis ; les chaînes de connexion (socle + réseaux) pointent vers le serveur MariaDB existant de l'exploitant.
  • Provisioning d'un réseau : création de la Base Réseau (schéma + seed), enregistrement du Réseau dans la Base Socle (db_connection_key, theme_settings), passage is_provisioned = 1. Au lancement : réseaux iad et demo.
  • Migrations : schéma socle appliqué à la Base Socle ; schéma réseau appliqué à chaque Base Réseau, de façon idempotente.
  • Secrets par environnement : clé JWT, chaînes de connexion (socle + réseaux) et paramètres SMTP via variables d'environnement (.env), jamais dans l'image. Démarrage refusé si la clé JWT est absente.
  • Reverse-proxy HTTPS externe (Caddy) relaie vers le frontend ; le backend ne gère pas la terminaison TLS.
  • Healthcheck : GET /health sur le backend.