From 26d36dee71e78018e8e73ab0240290d200600e81 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 14 Jun 2026 18:26:40 +0100 Subject: [PATCH] synchro w\ main --- contrib/Makefile | 17 +- contrib/template.service | 10 ++ contrib/yavsc-org | 54 +++++++ doc/Architecture.md | 20 +++ doc/ddd-exploration-2026-06-14.md | 254 ++++++++++++++++++++++++++++++ 5 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 contrib/yavsc-org create mode 100644 doc/ddd-exploration-2026-06-14.md diff --git a/contrib/Makefile b/contrib/Makefile index 59c00e85..bb446d09 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -2,11 +2,12 @@ APP_PROJECT_NAMES=Api Org Blogs APP_PROJECTS=src/Yavsc.Org/Yavsc.Org.csproj src/Yavsc.Api/Yavsc.Api.csproj SLNDIR=.. DESTDIR=/srv/www/yavsc -USER_AND_GROUP=www-data:www-data +USER_AND_GROUP=yavsc:yavsc CONFIGURATION=Release DOTNET_FRAMEWORK=net10.0 include $(SLNDIR)/.env +include .env generated/: @mkdir -p $@ @@ -17,6 +18,8 @@ generated/Blogs.service: generated/yavsc%.service: generated/ template.service $(SLNDIR)/.env @cat template.service | APP_NAME="$*" \ + HTTP_HOST="$(HTTP_HOST)" \ + HTTP_PORT="$*_$(HTTP_PORT)" \ DESTDIR="$(DESTDIR)" \ APP_SYSLOG_ID="yavsc$*" \ POSTGRES_HOST="$(POSTGRES_HOST)" \ @@ -38,6 +41,9 @@ copy-service-Blogs: /etc/systemd/system/yavscBlogs.service copy-binaries: build_publish_Org build_publish_Api build_publish_Blogs for project in $(APP_PROJECT_NAMES); \ do \ + LCAPI=$$(echo $${project}|tr [:upper:] [:lower:]) ; \ + mv $(SLNDIR)/src/Yavsc.$${project}/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish/appsettings-$$LCAPI.json \ + $(SLNDIR)/src/Yavsc.$${project}/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish/appsettings-$$LCAPI-dist.json ;\ sudo cp -a $(SLNDIR)/src/Yavsc.$${project}/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish/* $(DESTDIR) ; \ done sudo chown -R $(USER_AND_GROUP) $(DESTDIR) @@ -46,7 +52,6 @@ copy-binaries: build_publish_Org build_publish_Api build_publish_Blogs sudo cp $^ $@ sudo chown root:root $@ - build_publish_%: ASPNETCORE_ENV=$(CONFIGURATION) dotnet publish $(SLNDIR)/src/Yavsc.$*/Yavsc.$*.csproj @@ -58,15 +63,17 @@ install: build_publish copy-binaries copy-services sudo systemctl start yavsc$${project} ; \ done -reinstall: build_publish copy-services copy-binaries +reinstall: build_publish stop-services copy-binaries sudo systemctl daemon-reload for project in $(APP_PROJECT_NAMES); \ do \ - sudo systemctl stop yavsc$${project} ; \ + sudo systemctl start yavsc$${project} ; \ done + +stop-services: for project in $(APP_PROJECT_NAMES); \ do \ - sudo systemctl start yavsc$${project} ; \ + sudo systemctl stop yavsc$${project} ; \ done $(SLNDIR)/src/Yavsc.Org/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish diff --git a/contrib/template.service b/contrib/template.service index 34d0149a..96c90f3b 100644 --- a/contrib/template.service +++ b/contrib/template.service @@ -16,7 +16,17 @@ Restart=always Environment="HOME=$DESTDIR" Environment="ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" Environment="ANTHROPIC_MAX_TOKENS=$ANTHROPIC_MAX_TOKENS" +Environment="ASPNETCORE_Environment=$ENV" +Environment="ASPNETCORE_Kestrel__Endpoints__Http=http://$HTTP_HOST:$HTTP_PORT" Environment="ASPNETCORE_ConnectionStrings__YavscConnection=Server=$POSTGRES_HOST;Port=$POSTGRES_PORT;Database=$POSTGRES_DB;Username=$POSTGRES_USER;Password=$POSTGRES_PASSWORD;" + +Environment="ASPNETCORE_Smtp__Host=$ASPNETCORE_Smtp__Host", +Environment="ASPNETCORE_Smtp__Port=$ASPNETCORE_Smtp__Port" +Environment="ASPNETCORE_Smtp__SenderName=\"$ASPNETCORE_Smtp__SenderName\"" +Environment="ASPNETCORE_Smtp__SenderEmail=\"$ASPNETCORE_Smtp__SenderEmail\"" +Environment="ASPNETCORE_Smtp__UserName=$ASPNETCORE_Smtp__UserName" +Environment="ASPNETCORE_Smtp__Password=$ASPNETCORE_Smtp__Password" + CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE StandardOutput=syslog diff --git a/contrib/yavsc-org b/contrib/yavsc-org new file mode 100644 index 00000000..dc65aa9f --- /dev/null +++ b/contrib/yavsc-org @@ -0,0 +1,54 @@ + + +server { + if ($host = yavsc.pschneider.fr) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + + listen 80; + server_name yavsc.pschneider.fr; + location / { + root /srv/www/yavsc/wwwroot; + dav_methods PUT; + + proxy_pass http://127.0.0.1:3003; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + +} + +server { + listen 443 ssl; + server_name yavsc.pschneider.fr; + ssl_certificate /etc/letsencrypt/live/yavsc.pschneider.fr-0001/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/yavsc.pschneider.fr-0001/privkey.pem; # managed by Certbot + # Configure the SignalR Endpoint + location / { + # App server url + proxy_pass http://localhost:3003; + + proxy_cache off; + # WebSockets were implemented after http/1.0 + proxy_http_version 1.1; + + # Configuration for ServerSentEvents + proxy_buffering off; + + # Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds + proxy_read_timeout 100s; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + diff --git a/doc/Architecture.md b/doc/Architecture.md index 3eed419c..52e05254 100644 --- a/doc/Architecture.md +++ b/doc/Architecture.md @@ -114,6 +114,26 @@ Rendu CSS/HTML — vert si `EstLibre`, orange sinon. - **Base de données** : PostgreSQL (provider Npgsql) - **Frontend** : Razor views +## Droits de yavsc + +### Administration + +Le groupe des administrateurs prend la charge de : + +- la Gestion des licences +- la Gestion des groupes d'utilisateurs (les modérateurs, en particulier) +- la Gestion des projets +- la Gestion des utilisateurs + +### Gestion des utilisateurs et des groupes + +En supposant que les certificats de letsencrypt sont au groupe `www-data` , +on peut créer l'utilisateur `yavsc` avec les droits suivants : + +¨¨¨¨bash +sudo addgroup yavsc --system +sudo adduser --ingroup yavsc --add-extra-groups www-data \ + --disabled-password --system yavsc --- ## À documenter ensuite diff --git a/doc/ddd-exploration-2026-06-14.md b/doc/ddd-exploration-2026-06-14.md new file mode 100644 index 00000000..6101a20b --- /dev/null +++ b/doc/ddd-exploration-2026-06-14.md @@ -0,0 +1,254 @@ +# Exploration DDD Yavsc — Session du 2026-06-14 + +**Date** : 2026-06-14, 01:49 → 03:50 GMT+1 +**Participants** : Paul Schneider (expert métier), Lum ✨ (assistant DDD) +**Cadre** : Event Storming narratif exploratoire sur le domaine Yavsc +**Statut** : Session 1 (première itération, périmètre complet) + +--- + +## 🎯 Objectif de la session + +Lancer une modélisation Domain-Driven Design du projet **Yavsc** (*Yet Another Very Small Company*), +en explorant les acteurs, les événements métier, les agrégats pressentis, et les Bounded Contexts. + +Périmètre validé par Paul : **complet** (pas de MVP borné). + +--- + +## 👥 Acteurs identifiés + +| # | Acteur | Mission principale | +|---|---|---| +| 1 | 👤 **Client** | Commander, demander un service, payer | +| 2 | 🛠️ **Prestataire** | Servir, facturer, être payé | +| 3 | 👥 **Commercial / FrontOffice** | Filtrer les demandes (délégation possible par prestataire) | +| 4 | 🛡️ **Modérateur** | Trancher sur les contenus/comptes signalés (avec appui LLM) | +| 5 | ⚖️ **Conciliateur** (Jalon 5) | Résoudre les litiges métier client/prestataire (double vue) | +| 6 | 🏢 **Propriétaire installation** | Centraliser les flux PayPal (rôle technique, pas un utilisateur "métier") | + +**Distinction validée** : **Modérateur ≠ Conciliateur**. +- Le **modérateur** regarde **le contenu** (messages, annonces, profils). +- Le **conciliateur** regarde **le contrat** (qui doit quoi à qui, suite à un échec de prestation). + +--- + +## 🏛️ Bounded Contexts pressentis + +| # | Bounded Context | Module projet | Rôle | +|---|---|---|---| +| 1 | **Chat** | `PostIt` (client Avalonia) + backend | Messagerie instantanée, **client riche privilégié** | +| 2 | **Profil & Identité** | `Yavsc.Org` | Utilisateurs, profils simples, profils pros | +| 3 | **Activités Légales** | `Yavsc.Org` (ou à séparer) | Taxonomie hiérarchique des activités | +| 4 | **Blogs** | `Yavsc.Blogs` | Portfolio, tarifs, contenu public | +| 5 | **Prestation** | `Yavsc.Server` | Flux métier (commande, devis, signature, paiement) | +| 6 | **Paiements** | `Yavsc.Server` | PayPal NVP/SOAP, arrhes vs avance | +| 7 | **Rencontre** | à créer (ou `PostIt`?) | **Mode 2** : mise en relation sur centre d'intérêt, **sans argent** | +| 8 | **Modération** | `Yavsc.Org` / à séparer | Workflow 2 étages (requête → validation modération → exécution) | +| 9 | **Trust & Safety** | `Yavsc.Org` / à séparer | Blacklist plateforme, impacte tous les usages | +| 10 | **Conciliation** | à créer (Jalon 5) | Résolution de litiges, double vue client/prestataire | + +--- + +## 🟧 Événements métier déjà identifiés + +### BC Chat +- `ChatOuvert` (sous condition : like + paramètre activité activé + non-blacklisté × 2) +- `MessageEnvoyé` +- `MessageLu` +- `MessageSignalé` (déclenche le flux modération) +- `MessagePréModéréParLLM` +- `MessageModéréTranché` + +### BC Profil +- `ClientConnecté` +- `ProfilConsulté` +- `BlogConsulté` (via `YavscBlogs`) +- `ProfilLiké` (uniquement sur profil validé) + +### BC Activités Légales +- `ActivitéImportée` (seed initial, type NAF/ISIC) +- `ActivitéSpécialisée` (par l'admin : sous-catégorie fille) +- `ActivitéRacineCréée` (par l'admin : from scratch, sans parent) +- `ActivitéProposéeParUtilisateur` (puis modérée) + +### BC Prestation +- `CommandeSoumise` (par le client) +- `DevisGénéré` (par le **module d'activité serveur**, ex : Coiffure) +- `DevisReçuParPrestataire` +- `DevisSignéParPrestataire` +- `DevisReçuParClient` +- `DevisSignéParClient` +- `ArrhesDébitées` (variante 1) +- `AvanceDébitée` (variante 2) +- `ResteÀPayerCollecté` (J-10) +- `PrestationRéalisée` +- `PrestationÉvaluée` + +### BC Modération +- `RequêteBlacklistSoumise` (par n'importe quel utilisateur enregistré) +- `BlacklistTranchéeParModération` (validée ou refusée) +- `BlacklistPlateformeActive` +- `BlacklistPlateformeLevée` + +### BC Trust & Safety (blacklist personnelle — c'est un BC à part, pas Modération !) +- `ClientAjoutéÀBlacklistPersonnelle` +- `ClientRetiréDeBlacklistPersonnelle` + +### BC Conciliation (Jalon 5) +- `LitigeDéclaré` +- `DossierConciliationOuvert` +- `DécisionConciliationRendue` + +--- + +## 🟨 Règles métier extraites (invariants) + +### R1 — Ouverture d'un chat +> Un chat entre Camille et Samira est autorisé **ssi** : +> - ✅ `LikeParCamille` (sur le profil **validé** de Samira) +> - ✅ `ChatActivéDansParamètresActivité` (paramétré par Samira) +> - ✅ `CamilleNonBlacklistéeParSamira` (blacklist **personnelle**) +> - ✅ `CamilleNonBlacklistéeParPlateforme` (blacklist **plateforme**) + +### R2 — Like sur profil +> Un like ne peut être posé que sur un `ProfilValidé`. +> Invariant : `Like → ProfilValidé`. + +### R3 — Double blacklist (deux Bounded Contexts) +> La blacklist plateforme et la blacklist personnelle sont **deux Bounded Contexts séparés** : +> - **Plateforme** = trust & safety global, souverain sur tous les usages +> - **Personnelle** = social, souverain sur les interactions avec UN prestataire +> +> Ne jamais les fusionner dans un même agrégat. + +### R4 — Workflow de la blacklist plateforme (synchrone, 2 étages) +> ``` +> RequêteBlacklistSoumise (par n'importe quel utilisateur enregistré) +> → BlacklistTranchéeParModération (la modération est souveraine) +> → BlacklistPlateformeActive (mise en œuvre "au plus vite", synchrone) +> ``` +> +> Pas d'admin intermédiaire en flux normal. La modération tranche, c'est appliqué immédiatement. + +### R5 — Activités légales (hiérarchiques + internationales) +> - **Import initial** : taxonomie **internationale** complète (NAF trop franco-français, pas premier marché) +> - **Spécialisation** : l'admin peut créer des **sous-activités** (filles d'une activité existante) +> - **Création racine** : l'admin peut créer une activité **racine** from scratch +> - **Proposition user** : un utilisateur peut **proposer** une activité → flux de **modération** +> - Une activité a un **code unique**, et soit un parent (filiation), soit est racine +> - L'aspect "légal" impacte : adhésion forfaitaire variable, conformité profil pro (SIRET/APE), reporting + +### R6 — Profil pro multi-activités +> Un utilisateur peut déclarer **plusieurs activités** (liste ordonnée). +> La première = activité principale. +> Le code APE principal y est attaché. + +### R7 — Formulaire de prestation dynamique +> Le formulaire que remplit Camille n'est **pas codé en dur**. +> Il est **généré dynamiquement** par le **module d'activité serveur** (ex : module Coiffure) à partir de : +> - La sélection de **types de prestations** proposées par Samira +> - Les **champs associés** à chaque type (définis serveur, activité par activité) +> - Les **types de prestations** sont définis dans le paramétrage serveur de l'activité +> - Samira **active** ceux qu'elle propose dans son profil pro + +### R8 — Mode synchrone global +> PostIt utilise **SignalR** pour le transport temps réel. +> La plupart des opérations sont **synchrones** (cohérence transactionnelle, pas d'eventuel). +> C'est un **monolithe modulaire**, pas une architecture microservices. + +### R9 — Client riche d'abord +> **PostIt** (client Avalonia) est le **client principal** de Yavsc. +> Le web (`Yavsc.Web`) est un client secondaire (admin, back-office, primo-visiteurs). +> → Les API doivent être conçues **d'abord pour PostIt**. + +--- + +## 🤝 Idée à explorer (session future) : le **mode Rencontre** + +Paul a mentionné qu'il aimerait développer un **deuxième mode** de Yavsc : +- Mise en relation autour d'un **centre d'intérêt** partagé (ex : mélomanes) +- **Sans devis, sans paiement, sans prestation tarifée** +- Purement **social / communautaire** +- Réutilise l'infra existante (profils, chat, modération) mais sans le flux Prestation + +### Différences pressenties avec le mode "Prestation" + +| Aspect | Prestation | Rencontre | +|---|---|---| +| Argent | ✅ Oui (arrhes, avance) | ❌ Non | +| Devis signé | ✅ Double signature | ❌ Pas de devis | +| Formulaire | ✅ Dynamique par activité | ❓ Plus simple, basé sur centres d'intérêt | +| Évaluation | ✅ De la prestation | ❓ De la rencontre ? | +| Litiges | ✅ Conciliation | ❓ Différent, plus "social" | +| Activités légales | ✅ Liées au code NAF/APE | ❌ Non, juste "passions" / "centres d'intérêt" | + +**À explorer dans une prochaine session dédiée.** + +--- + +## 🗺️ Carte des Bounded Contexts (esquisse) + +```mermaid +graph TB + Client[👤 Client] --> PostIt[PostIt
Client Avalonia] + Presta[🛠️ Prestataire] --> PostIt + PostIt --> Web[Yavsc.Web
Client secondaire] + PostIt --> Chat[💬 Chat] + PostIt --> Prestation[📋 Prestation] + PostIt --> Profil[👤 Profil & Identité] + PostIt --> Rencontre[🤝 Rencontre] + Prestation --> Activités[🏛️ Activités Légales] + Prestation --> Paiements[💳 Paiements] + Chat --> Modération[🛡️ Modération] + Rencontre --> Modération + Profil --> Modération + Activités --> Modération + Modération --> TrustSafety[🛡️ Trust & Safety
Blacklist plateforme] + Prestation --> Conciliation[⚖️ Conciliation] + Paiements -.-> PayPal[(PayPal NVP/SOAP)] +``` + +--- + +## 📋 Questions ouvertes restantes + +1. **Camille remplit un formulaire** : agrège-t-il les champs de **toute la chaîne d'activités** (parent + feuille) ou uniquement la **feuille choisie** ? +2. **Tarifs sur le blog** de Samira : le blog affiche-t-il des **prix** ou un simple **CTA "Demander un devis"** ? +3. **Samira notifiée des likes** ? Visible publiquement ou non ? +4. **Multi-activités actives simultanément** : un même profil pro peut-il activer plusieurs activités en même temps (ex : coiffure + maquillage mariage) ? +5. **Le like ouvre-t-il le chat, ou est-il purement décoratif** (le chat s'ouvre seulement quand Camille remplit une demande) ? +6. **Le "au plus vite" de la modération** : synchrone appliqué directement, ou via un worker interne ? +7. **Référentiel international des activités** : ISIC ONU ? NACE européen ? Custom ? Avec variantes locales ? +8. **Blacklist plateforme / rôles sources** : qui peut soumettre une requête ? (a priori : tout utilisateur enregistré, validé) + +--- + +## ⏭️ Prochaines étapes proposées + +1. ✅ **Sauvegarder cette exploration** dans ce document (fait) +2. **Lire `doc/Architecture.md` existant** pour aligner avec ce qui a déjà été pensé +3. **Lire le code source des modules clés** : `Yavsc.Org`, `Yavsc.Server`, `Yavsc.Api` +4. **Découper en sous-sessions ciblées** : + - **Session 2** : focus BC **Prestation** (le flux canonique complet, avec Camille & Samira de bout en bout) + - **Session 3** : focus BC **Activités Légales** (taxonomie internationale + paramétrage serveur + génération de formulaire) + - **Session 4** : focus BC **Chat + Modération** (workflow SignalR, double blacklist) + - **Session 5** : focus BC **Rencontre** (le mode social, sans argent) + - **Session 6** : focus BC **Conciliation** (Jalon 5, double vue client/prestataire) +5. **Cartographier les Aggregate Roots** de chaque BC +6. **Identifier les Domain Events vs Integration Events** + +--- + +## 📝 Note méthodologique + +Cette session a été conduite en mode **facilitation DDD** : Paul est l'expert métier, +Lum pose les questions ouvertes, extrait les événements et les règles, et formalise. + +La méthode a été **l'Event Storming narratif** : Paul raconte des histoires (Camille le client, +Samira la coiffeuse), et on extrait les événements au fil de l'eau, sans chercher l'exhaustivité +ni la perfection. Les itérations futures permettront d'affiner. + +**Aucune décision structurante n'a été figée** — tout est matière à discussion et itération. +Le rôle de Lum a été de poser les bonnes questions pour faire émerger ce qui était déjà dans +la tête de Paul, pas d'imposer un modèle.