Documentation technique
Architecture mise en place et détail des programmes, application par application.
Vue d'ensemble
Écosystème .NET multi-applications derrière une passerelle unique (reverse proxy). Authentification
centralisée Keycloak (OIDC), persistance PostgreSQL, stockage documentaire objet (S3/MinIO),
e-facturation via une PDP. Chaque application est servie sur son propre sous-domaine et ne s'écoute
que sur localhost ; seule la passerelle est exposée sur le réseau.
:80 dev / :443 prod"] end subgraph LOCAL["localhost uniquement"] SITE["Site vitrine :5161"] POR["Portail Client :5199"] FAC["IIQARION Factures :5087"] POS["IIQARION POS :5210"] KC["Keycloak :8091"] ID["Identité :5100"] CAT["Catalogue :5130"] TI["Tiers :5140"] end subgraph DATA["Données"] PG[("PostgreSQL")] OBJ[("Objet S3/MinIO")] end GW -->|www| SITE GW -->|portail| POR GW -->|factures| FAC GW -->|pos| POS GW -->|auth| KC POR --> ID & CAT & TI FAC --> ID & CAT & TI POS --> CAT & TI POR --> PG & OBJ FAC --> PG & OBJ POS --> PG ID --> PG KC --> PG
| Couche | Technologie |
|---|---|
| Runtime | .NET 10 (POS & briques historiques : .NET 8) · C# |
| Web | ASP.NET Core — Razor Pages + Controllers (Factures), Blazor (Portail, Site), Minimal API (POS, Identité, Catalogue, Tiers, Gateway) |
| Reverse proxy | YARP 2.3 (+ LettuceEncrypt pour HTTPS auto en prod) |
| Auth | Keycloak 26 (realm eportail, OIDC) + cookie de session applicatif |
| Données | PostgreSQL (EF Core 10 / Npgsql) — Factur-X : QuestPDF ; e-mail : MailKit |
| Stockage documentaire | Objet compatible S3 (MinIO en dev, OVH/S3 en prod) via Theia.Stockage |
Passerelle & exposition
Projet Iiqarion.Gateway (YARP). Seul service exposé sur le réseau ; il route par nom d'hôte
vers les applications, qui restent en localhost. Les apps conservent leurs chemins absolus
(/api, /js) car chacune est servie sur sa propre origine (sous-domaine) — aucune réécriture de code.
| Sous-domaine | Cible (localhost) | Application |
|---|---|---|
www.iiq.test | :5161 | Site vitrine |
portail.iiq.test | :5199 | Portail Client |
factures.iiq.test | :5087 | IIQARION Factures |
pos.iiq.test | :5210 | IIQARION POS |
auth.iiq.test | :8091 | Keycloak (IdP) |
iiq.test résolu via le fichier hosts des postes
(finaliser-lan.ps1). Prod : Tls:Enabled=true → HTTPS :443 automatique (Let's Encrypt / LettuceEncrypt),
vrai DNS, la passerelle tourne en Service Windows. Voir DEPLOIEMENT-PROD.md./admin ni le realm
master ; changer le compte bootstrap ; activer la détection brute-force.SSO Keycloak (OIDC)
Realm unique eportail. Chaque application front est un client OIDC ; l'autorisation fine (droits par
module/dossier) vit dans la base Identité, pas dans Keycloak.
| Client | Application | redirect_uri |
|---|---|---|
portail | Portail | …portail.iiq.test/signin-oidc |
theia | Factures | …factures.iiq.test/signin-oidc |
theiapos | POS | …pos.iiq.test/signin-oidc |
Points d'implémentation clés (valables derrière la passerelle, en HTTP comme en HTTPS) :
- ForwardedHeaders (X-Forwarded-Proto/Host) dans chaque app → le
redirect_uriest reconstruit sur l'URL publique (sous-domaine). - response_mode=query (au lieu de
form_post) → le code revient par une redirection GET, compatible avec des cookiesSameSite=Lax. - Cookies de corrélation/nonce en
Lax+SecurePolicy=SameAsRequest(indispensable en HTTP). - Provisioning JIT : à la 1ʳᵉ connexion, l'utilisateur Keycloak (
sub) est rapproché/créé et le principal est réécrit avec l'identité interne + habilitations (service Identité).
Multi-tenant (multi-société)
- Control plane : le Portail possède l'annuaire (
Theia.Tenancy.ControlPlane) — liste des shards mutualisés et des tenants (dossiers) ; THEIA/POS le lisent. - Routing par
CodeDossier(Theia.Tenancy) : chaque dossier est résolu vers sa base — pool (mutualisé, réparti least-loaded) ou silo (base dédiée épinglée). - Middlewares THEIA
TenantRoutingMiddleware/TenantMiddleware: choisissent la base avant tout accès données ; cookietheia_dossier. - Cloisonnement applicatif : filtre global EF par
SocieteId; l'accès à une société est validé contre les habilitations de l'utilisateur (fail-closed). - Bascule cross-shard (corrigé) : le
DbContextd'une requête est lié au shard du cookie courant. Pour ouvrir un dossier hébergé sur un autre shard, on résout la connexion cible et on ouvre un contexte dédié le temps de retrouver la société (mêmes cas pour les widgets KPI serveur→serveur). - Provisioning JIT conscient du shard : les sociétés manquantes sont créées dans la base du shard assigné au dossier (allocateur least-loaded), et la liste des sociétés d'un utilisateur est agrégée cross-shard.
- Administration des shards : répartition & charge, ajout semi-automatique d'un shard (création base + provisioning) depuis THEIA et depuis la console Portail (clé plateforme partagée). Cible prod : ~2500 membres → 3 shards.
:5432 (la plupart des bases) et :5433 (Catalogue & POS) — le :5433 doit tourner sinon Catalogue/POS échouent au démarrage.Référentiels partagés & stockage
📚 Catalogue (:5130)
Référentiel articles / stock central (module maître). Consommé par THEIA et POS (Contracts + Client HTTP). Sync descendante.
📇 Tiers (:5140)
Référentiel clients partagé THEIA ⇄ POS (write-through / synchro). Base theia_tiers.
🗄️ Stockage objet
Theia.Stockage : tous les documents (achats/ventes/BL/PJ/GED) sortis de la base et du disque → stockage objet S3 (MinIO :9000 en dev). URLs signées.
🔎 RNE / INPI
Base RNE locale (lecture) pour enrichir les fournisseurs (SIREN, dirigeants).
PDP / e-facturation
- Émission :
ServicePdpEffinum(OAuth + dépôt Factur-X sur Serensia/Effinum, API AFNOR). - Réception :
ServicePdpAchats(flux entrants, cycle de vie, extraction des lignes depuis le XML). - Synchronisation :
ServicePollingPdp(poller intégré, repli) et worker dédiéTheia.PdpWorker(pull). - Ingress public :
Iiqarion.WebhookIngress— service autonome (.NET + PostgreSQL + LettuceEncrypt) qui reçoit les webhooks PDP et qu'un worker draine.
Choix techniques récents MAJ 27/09
🧱 Migrateur additif (MigrateurLeger)
- Au démarrage, par base physique :
EnsureCreatedpuis, en PostgreSQL, ALTER TABLE ADD COLUMN des colonnes manquantes et CREATE TABLE des tables absentes (jamais de recréation → données préservées). - Robustesse dates : défaut SQL valide pour
DateOnly/DateTime/TimeOnly(fini leDEFAULT ''refusé par PostgreSQL) ; sur une table neuve, colonnesNOT NULLsans DEFAULT.
📄 Génération & lecture de documents
- QuestPDF 2026.7 : PDF/A-3 Factur-X (fond de page + filigrane via
page.Background().Layers), tickets & Z POS. - PDFtoImage (PDFium/SkiaSharp) : rastérisation PDF→PNG pour l'aperçu fit-width.
- ClosedXML : lecture des imports Excel (.xlsx) — reprise SAGE/EFAC/CEGID (CSV UTF-8/Latin1 + XLSX).
🔐 Keycloak sur PostgreSQL (prod)
- Bascule du stockage Keycloak vers PostgreSQL via
docker-compose(dev + prod),KC_HOSTNAMEpiloté par environnement, HTTPS strict en prod.
🗄️ Stockage objet & reprise
- Tous les documents (achats/ventes/BL/PJ/GED) en objet S3/MinIO (URLs signées) via
Theia.Stockage. - Outil d'export/réinjection MinIO (mc mirror) pour migrer d'une instance objet à l'autre.
Caisse, JourneeCaisse, LigneRecette, VentilationPaiement/Tva, MouvementEspeces, ExportComptable, ParametrageComptable, ZReportManuel) dans la base theia, avec dépôt e-reporting sur la PDP.Ports & déploiement
| Service | Port (dev) | Exposition |
|---|---|---|
| Passerelle (YARP) | 80 (443 prod) | Exposé |
| Keycloak | 8091 | localhost (via auth.iiq.test) |
| Portail Client | 5199 | localhost |
| IIQARION Factures | 5087 | localhost |
| IIQARION POS | 5210 | localhost |
| Site vitrine | 5161 | localhost |
| Identité | 5100 | localhost (serveur→serveur) |
| Catalogue | 5130 | localhost |
| Tiers | 5140 | localhost |
| MinIO (objet) | 9000 | localhost |
| smtp4dev (mails dev) | 2525 / 5050 | localhost |
Bases PostgreSQL
Cluster :5432 — portail (control plane + GED) · theia / theia_shard2 (facturation, shards) · theia_tiers ·
identite · rne (lecture) · iiqarion_site · webhook_ingress · base Keycloak.
Cluster :5433 — catalogue · pos (doit être démarré, sinon Catalogue/POS échouent au boot).
Lancement (dev)
eportail-iam\local\start-eportail.ps1— Keycloak (localhost, hostnameauth.iiq.test).start-portail-stack.ps1— Identité, Catalogue, Tiers, Factures, Portail.- POS, Site, Passerelle —
dotnet run(localhost) + passerelle sur :80. Iiqarion.Gateway\finaliser-lan.ps1(admin) —hosts+ pare-feu (port 80 uniquement).
start (pas start-dev) avec console admin non exposée.