427 lines
27 KiB
Markdown
427 lines
27 KiB
Markdown
# 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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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.
|