diff --git a/doc/Architecture.md b/doc/Architecture.md index 0ee1b784..d48dff22 100644 --- a/doc/Architecture.md +++ b/doc/Architecture.md @@ -9,103 +9,20 @@ Elle gère des **devis** (pas de facturation directe), et s'intègre dans des workflows collaboratifs pouvant aboutir à des livrables sous licence libre. ---- +## Pages de détail -## Workflow de mise en relation multi-parties +Chaque sujet est détaillé dans sa propre page (sous +`doc/architecture/`) ; cette racine tient lieu de sommaire. -### Initiateur - -Le projet peut être initié par n'importe quelle partie : -- Un **client** (particulier ou pro) qui exprime un besoin -- Un **fournisseur** qui propose une offre ou monte un collectif -- Un **tiers coordinateur** qui orchestre sans être client ni prestataire - -### Rôles - -| Rôle | Type de compte | Description | -|---------------|--------------------|-----------------------------------------------------------| -| Client | Pro ou particulier | Exprime le besoin, valide les devis, co-signe la licence | -| Fournisseur | Pro | Répond aux besoins, peut sous-traiter | -| Coordinateur | Pro ou particulier | Orchestre le projet sans relation de facturation directe | - -### Sous-traitance - -Un fournisseur peut faire appel à d'autres fournisseurs, -**avec accord explicite du client**. Chaque sous-traitant : -- Est visible dans le projet -- Co-signe l'accord de licence -- Peut recevoir un devis distinct - -### B2B / B2C - -La distinction est portée par le **type de compte** : -- Compte **pro** : SIRET, TVA, facturation professionnelle -- Compte **particulier** : usage personnel, sans obligations fiscales pro - -Un projet peut mélanger les deux (ex: un particulier client, -plusieurs prestataires pro) — Yavsc n'impose pas d'homogénéité. - -### États d'un projet - -``` -Initié → En recherche de parties → Devis en cours - → Accord de licence signé → En production - → Livré → Publié (si licence libre) -``` - -### Contrainte clé - -L'accord de licence est établi **avant** tout début de production, -signé (ou validé) par toutes les parties : client, fournisseurs, -et sous-traitants éventuels. - ---- - -## Domaine musical — Titres collaboratifs - -### Objectif - -Permettre la production collaborative de titres musicaux sous licence -libre, à partir de la mise en relation assurée par Yavsc. - -### Formats supportés - -- Audio (ex: WAV, FLAC, MP3) -- Partition (ex: MusicXML, LilyPond, PDF) -- MIDI - -### Flux de production - -1. Un client exprime un besoin musical -2. Des prestataires répondent avec des devis -3. Un **consensus est établi en amont** entre le client et les - contributeurs sur la licence du livrable final -4. La collaboration produit les fichiers -5. Le titre est publié sous la licence choisie - ---- - -## Licences - -### Modèle `LicenceModele` - -Géré par l'administration Yavsc. Chaque modèle porte : - -- `EstLibre` (bool) — détermine le badge affiché sur le projet -- Les conditions : attribution, partage à l'identique, usage commercial, - modification -- Une URL vers le texte officiel de la licence - -### Seed initial - -Licences Creative Commons préchargées : CC0, CC BY, CC BY-SA, CC BY-ND, -CC BY-NC, CC BY-NC-SA, CC BY-NC-ND (toutes en version 4.0) + ODbL 1.0. - -### Badge projet - -Rendu CSS/HTML — vert si `EstLibre`, orange sinon. - ---- +| Sujet | Page | +|------------------------------------------------|-------------------------------------------------------------------| +| Workflow multi-parties (client / fournisseur / coordinateur, sous-traitance, états) | [workflow-multi-parties.md](architecture/workflow-multi-parties.md) | +| Domaine musical (titres collaboratifs) | [domaine-musical.md](architecture/domaine-musical.md) | +| Licences (`LicenceModele`, seed CC/ODbL, badge)| [licences.md](architecture/licences.md) | +| Domaines d'activité (arbre, `DomaineActivite`) | [domaines-activite.md](architecture/domaines-activite.md) | +| Dictionnaires métier (héritage en arbre) | [dictionnaires-metier.md](architecture/dictionnaires-metier.md) | +| Offre fournisseur + frontmatter (`ClasseFormulaire`, `ClasseDevis`, parsing) | [offres-frontmatter.md](architecture/offres-frontmatter.md) | +| PostIt / OIDC (client desktop, custom URI scheme, silent refresh) | [postit-oidc.md](architecture/postit-oidc.md) | ## Stack technique @@ -114,14 +31,15 @@ Rendu CSS/HTML — vert si `EstLibre`, orange sinon. - **Base de données** : PostgreSQL (provider Npgsql) - **Frontend** : Razor views - **Parsing frontmatter** : YamlDotNet - ---- +- **Client desktop** : Avalonia 12 (PostIt — détails dans + [postit-oidc.md](architecture/postit-oidc.md)) ## 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 @@ -129,8 +47,9 @@ Le groupe des administrateurs prend la charge de : ### 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 : +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 @@ -138,169 +57,6 @@ sudo adduser --ingroup yavsc --add-extra-groups www-data \ --disabled-password --system yavsc ``` ---- - -## Domaines d'activité - -### Hiérarchie - -Les activités sont organisées en arbre. Chaque activité peut avoir -une activité parente d'ordre plus général : - -``` -Droit ← racine, ancêtre commun -├── Musique -│ ├── Jazz -│ ├── Classique -│ └── ... -├── Graphisme -├── BTP -└── ... -``` - -Le domaine **Droit** est positionné à la racine — *nul n'est censé -ignorer la loi*. Sa position structurelle rend ses dictionnaires -naturellement disponibles dans tous les projets, sans attribut spécial. - -### Modèle `DomaineActivite` - -``` -DomaineActivite - - Id, Nom - - ParentId (FK → DomaineActivite, nullable) -``` - -Pas de booléen `EstTransversal` — la transversalité est une conséquence -de la position dans l'arbre, pas un attribut explicite. - ---- - -## Dictionnaires métier - -### Principe - -Les dictionnaires sont des **modèles publics** de termes métier, -maintenus par les modérateurs et alimentés par les fournisseurs. -Au moment de créer un projet, le dictionnaire est **copié** — -c'est un instantané, pas une référence vivante. - -### Règle de résolution - -> Un projet hérite des dictionnaires de son activité **et de tous -> ses ancêtres** jusqu'à la racine. - -Ainsi, les termes juridiques sont toujours disponibles, quel que -soit le domaine du projet. - -### Modèle - -``` -DictionnaireMetier - - Id, Nom - - DomaineActiviteId (FK) - - Langue ← attribut du dictionnaire entier - -TermeMetier - - Id - - DictionnaireMetierId (FK) - - Mot - - Definition - - StatutValidation ← Proposé | Validé | Rejeté - - ProposeParId (FK → ApplicationUser) - - ValidéParId (FK → ApplicationUser, nullable) -``` - -### Cycle de vie d'un terme - -1. Un **fournisseur** propose un terme dans son domaine et sa langue -2. Un **modérateur** valide → le terme intègre le dictionnaire public -3. À la création d'un projet, le dictionnaire est **associé par copie** - ---- - -## Offre fournisseur et modèle canonique de demande - -### Principe - -Un fournisseur décrit son offre en **Markdown**, avec un en-tête -structuré délimité par `---` (frontmatter YAML standard) qui porte -les métadonnées de formulaire et de devis. Le corps est libre. - -```markdown ---- -formulaire: Prestation Musicale -devis: Arrangement Orchestral ---- - -Je propose des arrangements pour orchestre de chambre, -livraison sous 3 semaines, formats MusicXML et PDF... -``` - -### `ClasseFormulaire` et `ClasseDevis` - -Ces deux référentiels sont **définis côté serveur par les modérateurs**, -pour servir de modèles de formulaires et de devis. Les fournisseurs -s'y conforment — ils ne peuvent pas en créer de nouveaux. - -### Modèle canonique nom/valeur - -``` -ClasseFormulaire - - Id, Nom - - DomaineActiviteId (FK) - └── ChampDemande - - Nom (ex: "tempo", "tonalité", "durée") - - TypeValeur (texte | entier | décimal | booléen | enum) - - Obligatoire (bool) - - ValeurDefaut - -ClasseDevis - - Id, Nom - - DomaineActiviteId (FK) - -OffreFournisseur - - Id - - FournisseurId (FK → ApplicationUser) - - DomaineActiviteId (FK) - - ContenuMarkdown ← corps libre de l'offre - - ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde - - ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde -``` - -### Demande client - -À partir de l'offre, la demande instancie les champs du formulaire : - -``` -Demande - - Id - - OffreFournisseurId (FK) - - ClientId (FK → ApplicationUser) - └── ValeurChampDemande - - ChampDemandeId (FK) - - Valeur (string — sérialisé selon TypeValeur) -``` - -### Règle d'héritage - -Les `ClasseFormulaire` et `ClasseDevis` disponibles pour une activité -incluent ceux de ses **ancêtres** dans l'arbre — cohérent avec la -règle de résolution des dictionnaires. - -### Parsing du frontmatter avec YamlDotNet - -```csharp -var parts = markdown.TrimStart() - .Substring(3) - .Split(new[] { "\n---" }, 2, - StringSplitOptions.None); -var yamlBlock = parts[0].Trim(); -var corps = parts[1].Trim(); -var meta = deserializer.Deserialize(yamlBlock); -``` - ---- - ## À documenter ensuite - Modèle `ProjetMusical` diff --git a/doc/Demande.md b/doc/Demande.md deleted file mode 100644 index eb144ace..00000000 --- a/doc/Demande.md +++ /dev/null @@ -1,7 +0,0 @@ -Demande - - Id - - OffreFournisseurId (FK) - - ClientId (FK → ApplicationUser) - └── ValeurChampDemande - - ChampDemandeId (FK) - - Valeur (string — sérialisé selon TypeValeur) diff --git a/doc/Dictionnaire.md b/doc/Dictionnaire.md deleted file mode 100644 index e60e9533..00000000 --- a/doc/Dictionnaire.md +++ /dev/null @@ -1,36 +0,0 @@ -# Dictionnaires métier - -## Principe - -Les dictionnaires sont des modèles publics de termes métier, -maintenus par les modérateurs et alimentés par les fournisseurs. -Au moment de créer un projet, le dictionnaire est copié — -c'est un instantané, pas une référence vivante. - -## Règle de résolution - -Un projet hérite des dictionnaires de son activité et de tous -ses ancêtres jusqu'à la racine. - -Ainsi, les termes juridiques sont toujours disponibles, quel que -soit le domaine du projet. - -Modèle - - -Le modèle de dictionnaire du contrat est le dictionnaire défini par l'activité ciblée par le client. - -## Dictionnaires juridiques - -Les dictionnaires juridiques sont des modèles publics de termes juridiques, -maintenus par les modérateurs et alimentés par les modérateurs. - -## Règle de résolution - -Un projet hérite des dictionnaires de son activité et de tous -ses ancêtres jusqu à la racine. - -Ainsi, les termes juridiques sont toujours disponibles, quel que -soit le domaine du projet. - -Ce dictionnaire juridique est simplement fondamental, par construction, c'est la base de tous les dictionnaires. diff --git a/doc/Formulaires-devis.md b/doc/Formulaires-devis.md deleted file mode 100644 index 6968c8d7..00000000 --- a/doc/Formulaires-devis.md +++ /dev/null @@ -1,33 +0,0 @@ - -ClasseDevis - - Id, Nom - - DomaineActiviteId (FK) - -OffreFournisseur - - Id - - FournisseurId (FK → ApplicationUser) - - DomaineActiviteId (FK) - - ContenuMarkdown ← corps libre de l'offre - - ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde - - ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde - - ClasseFormulaire - - Id, Nom - - DomaineActiviteId (FK) - └── ChampDemande - - Nom (ex: "tempo", "tonalité", "durée") - - TypeValeur (texte | entier | décimal | booléen | enum) - - Obligatoire (bool) - - ValeurDefaut - -ClasseDevis - - Id, Nom - - DomaineActiviteId (FK) - -OffreFournisseur - - Id - - FournisseurId (FK → ApplicationUser) - - DomaineActiviteId (FK) - - ContenuMarkdown ← corps libre de l'offre - - ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde - - ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde diff --git a/doc/architecture/dictionnaires-metier.md b/doc/architecture/dictionnaires-metier.md new file mode 100644 index 00000000..e8febab3 --- /dev/null +++ b/doc/architecture/dictionnaires-metier.md @@ -0,0 +1,68 @@ +# Dictionnaires métier + +> **Récapitulatif** : Les dictionnaires sont des modèles publics +> de termes métier, validés par les modérateurs et **copiés** +> dans chaque projet (instantané, pas référence vivante). +> L'héritage suit l'arbre des activités. Détail dans cette page, +> racine de l'architecture : [Architecture.md](../Architecture.md). + +## Principe + +Les dictionnaires sont des **modèles publics** de termes métier, +maintenus par les modérateurs et alimentés par les fournisseurs. +Au moment de créer un projet, le dictionnaire est **copié** — +c'est un instantané, pas une référence vivante. + +> Note historique : une première ébauche de cette page existait +> à `doc/Dictionnaire.md`. Elle a été absorbée ici. + +## Règle de résolution + +> Un projet hérite des dictionnaires de son activité **et de tous +> ses ancêtres** jusqu'à la racine. + +Ainsi, les termes juridiques sont toujours disponibles, quel que +soit le domaine du projet. + +Le modèle de dictionnaire du contrat est le dictionnaire défini +par l'activité ciblée par le client. + +## Dictionnaires juridiques + +Les dictionnaires juridiques sont des modèles publics de termes +juridiques, maintenus et alimentés par les modérateurs. Ils +figurent à la racine de l'arbre (sous `Droit`) et profitent donc +à toutes les activités par construction. + +> Ce dictionnaire juridique est simplement fondamental, par +> construction, c'est la base de tous les dictionnaires. + +## Modèle + +``` +DictionnaireMetier + - Id, Nom + - DomaineActiviteId (FK) + - Langue ← attribut du dictionnaire entier + +TermeMetier + - Id + - DictionnaireMetierId (FK) + - Mot + - Definition + - StatutValidation ← Proposé | Validé | Rejeté + - ProposeParId (FK → ApplicationUser) + - ValidéParId (FK → ApplicationUser, nullable) +``` + +## Cycle de vie d'un terme + +1. Un **fournisseur** propose un terme dans son domaine et sa langue +2. Un **modérateur** valide → le terme intègre le dictionnaire public +3. À la création d'un projet, le dictionnaire est **associé par copie** + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine. +- [domaines-activite.md](domaines-activite.md) — l'arbre des + activités qui justifie la règle d'héritage. diff --git a/doc/architecture/domaine-musical.md b/doc/architecture/domaine-musical.md new file mode 100644 index 00000000..74c1fe18 --- /dev/null +++ b/doc/architecture/domaine-musical.md @@ -0,0 +1,43 @@ +# Domaine musical — Titres collaboratifs + +> **Récapitulatif** : Yavsc permet de produire collaborativement +> des titres musicaux sous licence libre, en s'appuyant sur la +> mise en relation multi-parties. Détail dans cette page, racine +> de l'architecture : [Architecture.md](../Architecture.md). + +## Objectif + +Permettre la production collaborative de titres musicaux sous +licence libre, à partir de la mise en relation assurée par Yavsc. + +## Formats supportés + +- Audio (ex : WAV, FLAC, MP3) +- Partition (ex : MusicXML, LilyPond, PDF) +- MIDI + +## Flux de production + +1. Un client exprime un besoin musical +2. Des prestataires répondent avec des devis +3. Un **consensus est établi en amont** entre le client et les + contributeurs sur la licence du livrable final +4. La collaboration produit les fichiers +5. Le titre est publié sous la licence choisie + +## Contraintes spécifiques + +- L'accord de licence sur le livrable musical est une instance + du modèle `LicenceModele` détaillé dans + [licences.md](licences.md). +- L'arborescence d'activités musicales (`Musique → Jazz`, + `Musique → Classique`, etc.) est décrite dans + [domaines-activite.md](domaines-activite.md). + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine. +- [workflow-multi-parties.md](workflow-multi-parties.md) — rôle + client/fournisseur/coordinateur dans la production musicale. +- [licences.md](licences.md) — modèle de licence applicable au + livrable. diff --git a/doc/architecture/domaines-activite.md b/doc/architecture/domaines-activite.md new file mode 100644 index 00000000..78302c61 --- /dev/null +++ b/doc/architecture/domaines-activite.md @@ -0,0 +1,59 @@ +# Domaines d'activité + +> **Récapitulatif** : Yavsc organise ses activités en arbre, avec +> `Droit` à la racine pour rendre les termes juridiques +> disponibles partout. Pas de booléen "transversal" : la +> transversalité découle de la position dans l'arbre. Détail dans +> cette page, racine de l'architecture : [Architecture.md](../Architecture.md). + +## Hiérarchie + +Les activités sont organisées en arbre. Chaque activité peut avoir +une activité parente d'ordre plus général : + +``` +Droit ← racine, ancêtre commun +├── Musique +│ ├── Jazz +│ ├── Classique +│ └── ... +├── Graphisme +├── BTP +└── ... +``` + +Le domaine **Droit** est positionné à la racine — *nul n'est +censé ignorer la loi*. Sa position structurelle rend ses +dictionnaires naturellement disponibles dans tous les projets, +sans attribut spécial. + +## Modèle `DomaineActivite` + +``` +DomaineActivite + - Id, Nom + - ParentId (FK → DomaineActivite, nullable) +``` + +Pas de booléen `EstTransversal` — la transversalité est une +conséquence de la position dans l'arbre, pas un attribut +explicite. + +## Conséquences sur les autres référentiels + +Cette arborescence irrigue : + +- les **dictionnaires métier** (cf. [dictionnaires-metier.md](dictionnaires-metier.md)) : + un projet hérite des dictionnaires de ses ancêtres. +- les **classes de formulaire et de devis** (cf. + [offres-frontmatter.md](offres-frontmatter.md)) : un fournisseur + rattaché à `Musique → Jazz` a accès aux modèles de son + activité et de ses ancêtres. + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine. +- [dictionnaires-metier.md](dictionnaires-metier.md) — règle + d'héritage en arbre. +- [offres-frontmatter.md](offres-frontmatter.md) — modèle + canonique nom/valeur pour formulaires et devis. diff --git a/doc/architecture/licences.md b/doc/architecture/licences.md new file mode 100644 index 00000000..e5482aa8 --- /dev/null +++ b/doc/architecture/licences.md @@ -0,0 +1,42 @@ +# Licences + +> **Récapitulatif** : Yavsc référence des modèles de licence +> (Creative Commons + ODbL) maintenus par l'administration. Chaque +> projet signe l'accord avant toute production. Détail dans cette +> page, racine de l'architecture : [Architecture.md](../Architecture.md). + +## Modèle `LicenceModele` + +Géré par l'administration Yavsc. Chaque modèle porte : + +- `EstLibre` (bool) — détermine le badge affiché sur le projet +- Les conditions : attribution, partage à l'identique, usage + commercial, modification +- Une URL vers le texte officiel de la licence + +## Seed initial + +Licences Creative Commons préchargées : CC0, CC BY, CC BY-SA, +CC BY-ND, CC BY-NC, CC BY-NC-SA, CC BY-NC-ND (toutes en version +4.0) + ODbL 1.0. + +## Badge projet + +Rendu CSS/HTML — vert si `EstLibre`, orange sinon. + +## Cycle de vie + +1. L'administration crée ou met à jour un `LicenceModele`. +2. À la création d'un projet, le client choisit un modèle + dans la liste des licences applicables à son activité. +3. Le projet passe par l'état **Devis en cours** avant que + l'accord ne soit signé par toutes les parties (cf. + [workflow-multi-parties.md](workflow-multi-parties.md)). +4. Une fois signé, l'état passe à **En production** ; le badge + du projet reflète `EstLibre`. + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine. +- [workflow-multi-parties.md](workflow-multi-parties.md) — + contrainte clé : accord signé avant production. diff --git a/doc/architecture/offres-frontmatter.md b/doc/architecture/offres-frontmatter.md new file mode 100644 index 00000000..22f78492 --- /dev/null +++ b/doc/architecture/offres-frontmatter.md @@ -0,0 +1,106 @@ +# Offre fournisseur, formulaire, devis + +> **Récapitulatif** : Le fournisseur rédige son offre en Markdown +> avec un en-tête frontmatter YAML qui pointe vers un +> `ClasseFormulaire` et un `ClasseDevis` définis par les +> modérateurs. La demande du client instancie les champs du +> formulaire. L'héritage suit l'arbre des activités. Détail dans +> cette page, racine de l'architecture : [Architecture.md](../Architecture.md). + +> Note historique : des fragments existaient à `doc/Formulaires-devis.md` +> et `doc/Demande.md`. Ils sont absorbés ici. + +## Principe + +Un fournisseur décrit son offre en **Markdown**, avec un en-tête +structuré délimité par `---` (frontmatter YAML standard) qui porte +les métadonnées de formulaire et de devis. Le corps est libre. + +```markdown +--- +formulaire: Prestation Musicale +devis: Arrangement Orchestral +--- + +Je propose des arrangements pour orchestre de chambre, +livraison sous 3 semaines, formats MusicXML et PDF... +``` + +## `ClasseFormulaire` et `ClasseDevis` + +Ces deux référentiels sont **définis côté serveur par les +modérateurs**, pour servir de modèles de formulaires et de devis. +Les fournisseurs s'y conforment — ils ne peuvent pas en créer +de nouveaux. + +## Modèle canonique nom/valeur + +``` +ClasseFormulaire + - Id, Nom + - DomaineActiviteId (FK) + └── ChampDemande + - Nom (ex : "tempo", "tonalité", "durée") + - TypeValeur (texte | entier | décimal | booléen | enum) + - Obligatoire (bool) + - ValeurDefaut + +ClasseDevis + - Id, Nom + - DomaineActiviteId (FK) + +OffreFournisseur + - Id + - FournisseurId (FK → ApplicationUser) + - DomaineActiviteId (FK) + - ContenuMarkdown ← corps libre de l'offre + - ClasseFormulaireId (FK) ← extrait du frontmatter à la sauvegarde + - ClasseDevisId (FK) ← extrait du frontmatter à la sauvegarde +``` + +## Demande client + +À partir de l'offre, la demande instancie les champs du formulaire : + +``` +Demande + - Id + - OffreFournisseurId (FK) + - ClientId (FK → ApplicationUser) + └── ValeurChampDemande + - ChampDemandeId (FK) + - Valeur (string — sérialisé selon TypeValeur) +``` + +## Règle d'héritage + +Les `ClasseFormulaire` et `ClasseDevis` disponibles pour une +activité incluent ceux de ses **ancêtres** dans l'arbre — +cohérent avec la règle de résolution des dictionnaires +([dictionnaires-metier.md](dictionnaires-metier.md)). + +## Parsing du frontmatter avec YamlDotNet + +Le parsing du frontmatter vit dans +`src/Yavsc.Server/Services/FrontmatterParser.cs` (introduit dans +le commit `4034c399 Front matters`). Squelette : + +```csharp +var parts = markdown.TrimStart() + .Substring(3) + .Split(new[] { "\n---" }, 2, + StringSplitOptions.None); +var yamlBlock = parts[0].Trim(); +var corps = parts[1].Trim(); +var meta = deserializer.Deserialize(yamlBlock); +``` + +Le résultat `FrontmatterOffreResult` est sérialisé en JSON pour +alimenter `OffreFournisseur.ClasseFormulaireId` / +`ClasseDevisId` à la sauvegarde. + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine. +- [domaines-activite.md](domaines-activite.md) — arbre des + activités et héritage. diff --git a/doc/architecture/postit-oidc.md b/doc/architecture/postit-oidc.md new file mode 100644 index 00000000..ee003b41 --- /dev/null +++ b/doc/architecture/postit-oidc.md @@ -0,0 +1,133 @@ +# PostIt — Client desktop et authentification OIDC + +> **Récapitulatif** : PostIt est un client desktop Avalonia 12 +> multi-cible (Desktop, Android, Browser/WASM) qui consomme l'API +> Yavsc.Org. Authentification **public client + PKCE** via +> **custom URI scheme** (RFC 8252 §7.1) — pas de loopback HTTP. +> Détail dans cette page, racine de l'architecture : [Architecture.md](../Architecture.md). + +## Rôle + +PostIt est un client desktop (Avalonia 12, multi-cible : Desktop +Linux/Windows, Android, Browser/WASM) qui consomme l'API +Yavsc.Org pour les opérations CRUD du blog et d'autres +ressources de l'utilisateur. C'est un **public client** : pas de +secret, le secret est remplacé par PKCE. + +## Flow d'authentification + +L'authentification suit **RFC 8252 §7.1** (OAuth 2.0 for Native +Apps — Custom URI Scheme Redirect), pas le pattern loopback +historique. La séquence : + +1. L'utilisateur clique **Se connecter** sur `LoginPage`. +2. `YavscApiClient.LoginInteractiveAsync` demande à `OidcClient` + de calculer l'authorize URL et de la passer à + `CustomSchemeBrowser.InvokeAsync`. +3. `CustomSchemeBrowser` ouvre le navigateur système sur l'authorize + URL via `Process.Start(options.StartUrl) { UseShellExecute = true }`, + puis **attend** sur un `TaskCompletionSource` alimenté + par un named pipe `PostIt.OidcCallback`. +4. L'utilisateur s'authentifie sur l'IdP. Le navigateur redirige + vers `postit://callback?code=***&state=…`. +5. L'OS, qui a le scheme `postit://` enregistré, **lance une 2ᵉ + instance** de PostIt avec l'URL en argument. +6. La 2ᵉ instance lit `Environment.GetCommandLineArgs()` *avant* + qu'Avalonia ne boote (`PostIt.Desktop.Program.Main`), + détecte l'URL via `SchemeUrlDetector.FindCallbackUrl`, l'écrit + sur le named pipe puis sort par `Environment.Exit(0)`. Aucune + fenêtre n'est créée. +7. L'instance vivante reçoit l'URL via le pipe, complète la TCS, + `OidcClient` échange le code contre les tokens (access + + refresh + id) et les persiste dans `~/.config/PostIt/tokens.json`. + +## Pourquoi le scheme custom plutôt que loopback + +Le loopback (`http://127.0.0.1:7890/callback`) oblige l'app à +ouvrir un port TCP, à le publier comme redirect URI dans l'IdP, +et à gérer la course "le navigateur revient-il à temps ?". +Le scheme custom transfère la livraison du callback à l'OS : +le navigateur tape sur une URL que l'OS sait router vers PostIt, +pas vers un serveur HTTP. + +## Composants partagés (`PostIt/`) + +| Composant | Rôle | +|---------------------------------|-------------------------------------------------------------------| +| `Services/OidcLoginPhase` | Enum des étapes du flow : `Idle / Discovering / OpeningBrowser / AwaitingCallback / ExchangingCode / Success / Error` | +| `Services/YavscApiClient` | Client HTTP de l'API Yavsc. Porte `LoginInteractiveAsync(IProgress)` et `TrySilentLoginAsync`. Refresh silencieux sur 401 et sur access-token bientôt expiré. | +| `Services/SingleInstance` | Named-pipe helper. `TryHandOffAsync` côté 2ᵉ instance, `StartServerAsync` côté instance vivante. | +| `Services/CustomSchemeBrowser` | `IBrowser` OidcClient qui ouvre le système + attend le pipe. | +| `Services/SchemeUrlDetector` | Détection pure, testable, du `postit://callback` dans argv. | +| `Services/TokenStore` | Persiste `RefreshTokenRecord` (access/refresh/id + expiry). Mode 0600 sur POSIX. | +| `ViewModels/SessionStatusViewModel` | VM du bandeau persistant Connecté/Déconnecté + Logout. | +| `Views/SessionStatusBanner` | Bandeau affiché en haut de `MainWindow`, visible sur toutes les pages. | + +## Plateformes (`PostIt.Desktop`, `PostIt.Android`, `PostIt.Browser`) + +Chaque plateforme injecte son propre `IBrowser` via +`Platform.CreateBrowser` : + +- **Desktop** : `CustomSchemeBrowser` + scheme `postit://`. +- **Android** : Chrome Custom Tabs via `AndroidSystemBrowser`, + scheme `android://postit-signin` (déclaré comme + `` dans `AndroidManifest.xml`). MainActivity est + `SingleTask` → `OnNewIntent` livre l'URL à `AndroidOidcCallbackSink`. +- **Browser (WASM)** : pas de process distinct → N/A. + +## UX observable + +`LoginPage` affiche en continu la **phase** courante du flow +(badge `PhaseLabel`) en plus du `StatusMessage` textuel : + +| Phase | Signification | +|--------------------|------------------------------------------------------------------| +| `Discovering` | Fetch de `/.well-known/openid-configuration`. | +| `OpeningBrowser` | Navigateur système ouvert, on attend que l'utilisateur revienne. | +| `AwaitingCallback` | (idem `OpeningBrowser` aujourd'hui — couvre la fenêtre pipe). | +| `ExchangingCode` | Trade du `code` contre les tokens à `/connect/token`. | +| `Success` / `Error`| Issue du flow. | + +Si le badge reste bloqué sur `OpeningBrowser`, **l'OS n'a jamais +relancé PostIt avec l'URL `postit://callback`** : le scheme +handler n'est pas enregistré correctement. + +## Persistance et reprise au démarrage + +Au boot, `App.OnFrameworkInitializationCompleted` : + +1. Construit le `TokenStore` (~/.config/PostIt/tokens.json) et + `YavscApiClient`. Ce dernier charge `_tokens = store.Load()` + dans son constructeur. +2. Ouvre `MainWindow` avec `HomePage` comme racine. +3. Sur l'événement `Opened`, appelle `TrySilentLoginAsync` : + - si l'access token a encore `> RefreshSkew` (60 s) de vie → + `Success` immédiat, push `MainPage`. + - sinon, tente un refresh via le refresh token. Si l'IdP + accepte → push `MainPage`. Si l'IdP rejette (révocation, + vol, expiration du refresh) → `_store.Clear()`, retour à + `HomePage`. + +Le bandeau `SessionStatusBanner` reflète `Api.HasValidSession` +en continu et expose le bouton **Se déconnecter**, qui appelle +`Api.LogoutAsync()` (purge du store) puis `PopToRootAsync` +pour ramener sur `HomePage`. + +## Garanties testées + +- `LoginInteractiveAsync` émet `Discovering → OpeningBrowser → + ExchangingCode → Success` (ou `Error` si pas de navigateur). +- `TrySilentLoginAsync` retourne `false` si pas de bundle sur + disque, `true` si l'access est encore valide, `true` après un + refresh réussi. +- `SchemeUrlDetector.FindCallbackUrl` reconnaît l'URL en argv, + case-insensitive, refuse les faux positifs (`postit-…://`), + retourne le premier match de manière déterministe. + +Le chemin "refresh token rejeté → purge du store" est testé +manuellement ; le stub `OidcStubAuthority` ne sait pas encore +rejeter un refresh token précis — extension future. + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine. diff --git a/doc/architecture/workflow-multi-parties.md b/doc/architecture/workflow-multi-parties.md new file mode 100644 index 00000000..9ab06e7a --- /dev/null +++ b/doc/architecture/workflow-multi-parties.md @@ -0,0 +1,79 @@ +# Workflow multi-parties + +> **Récapitulatif** : Yavsc orchestre des projets multi-parties +> (client, fournisseur, coordinateur) avec mise en relation, +> sous-traitance et signature de licence en amont de la production. +> Détail dans cette page, racine de l'architecture : [Architecture.md](../Architecture.md). + +## Rôle de chaque partie + +| Rôle | Type de compte | Description | +|---------------|--------------------|-----------------------------------------------------------| +| Client | Pro ou particulier | Exprime le besoin, valide les devis, co-signe la licence | +| Fournisseur | Pro | Répond aux besoins, peut sous-traiter | +| Coordinateur | Pro ou particulier | Orchestre le projet sans relation de facturation directe | + +## Qui peut initier un projet + +Le projet peut être initié par n'importe quelle partie : + +- Un **client** (particulier ou pro) qui exprime un besoin +- Un **fournisseur** qui propose une offre ou monte un collectif +- Un **tiers coordinateur** qui orchestre sans être client ni prestataire + +## Sous-traitance + +Un fournisseur peut faire appel à d'autres fournisseurs, +**avec accord explicite du client**. Chaque sous-traitant : + +- Est visible dans le projet +- Co-signe l'accord de licence +- Peut recevoir un devis distinct + +## Distinction B2B / B2C + +La distinction est portée par le **type de compte** : + +- Compte **pro** : SIRET, TVA, facturation professionnelle +- Compte **particulier** : usage personnel, sans obligations fiscales pro + +Un projet peut mélanger les deux (ex : un particulier client, +plusieurs prestataires pro) — Yavsc n'impose pas d'homogénéité. + +## États d'un projet + +``` +Initié → En recherche de parties → Devis en cours + → Accord de licence signé → En production + → Livré → Publié (si licence libre) +``` + +## Contrainte clé + +L'accord de licence est établi **avant** tout début de production, +signé (ou validé) par toutes les parties : client, fournisseurs, +et sous-traitants éventuels. + +## Domaine musical — Titres collaboratifs + +Permet la production collaborative de titres musicaux sous licence +libre, à partir de la mise en relation assurée par Yavsc. + +### Formats supportés + +- Audio (ex : WAV, FLAC, MP3) +- Partition (ex : MusicXML, LilyPond, PDF) +- MIDI + +### Flux de production + +1. Un client exprime un besoin musical +2. Des prestataires répondent avec des devis +3. Un **consensus est établi en amont** entre le client et les + contributeurs sur la licence du livrable final +4. La collaboration produit les fichiers +5. Le titre est publié sous la licence choisie + +## Voir aussi + +- [Architecture.md](../Architecture.md) — racine.