diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml
index ea58d2fe..86694c89 100644
--- a/.forgejo/workflows/buildAndTest.yml
+++ b/.forgejo/workflows/buildAndTest.yml
@@ -44,8 +44,4 @@ jobs:
- name: Test
run: |
echo "🚀 Lancement des tests..."
- cd /src/_src && dotnet test \
- --verbosity normal \
- --filter="Category!=Platform-Android" \
- --logger "xunit;LogFileName=test-results.xml" \
- && echo "✅ Success !" || echo "❌ Fail ($?)!"
+ cd /src/_src && dotnet test --verbosity normal && echo "✅ Success !"
diff --git a/.github/workflows/docker-publish-backend.yml b/.github/workflows/docker-publish-backend.yml
index d8466bdb..6c2431ae 100644
--- a/.github/workflows/docker-publish-backend.yml
+++ b/.github/workflows/docker-publish-backend.yml
@@ -26,7 +26,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Test
- run: dotnet test --no-build --verbosity normal --filter="Category!=Platform-Android"
+ run: dotnet test --no-build --verbosity normal
# 4. Build et Push de l'image de production finale
- name: Build and push production image
uses: docker/build-push-action@v7
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index a30c9c06..3faedae0 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -7,13 +7,12 @@
"fileLocation": ["relative", "${workspaceFolder}"],
"source": "dotnet",
"pattern": {
- "regexp": "^\\s*(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.*)$",
+ "regexp": "^\\s+(.*)\\((\\d+):(\\d+)\\):\\s+(error|warning)\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
- "code": 5,
- "message": 6
+ "message": 5
}
}
],
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bef40e3..7210a227 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,105 +1,21 @@
# Changelog
-## [1.0.8-rc12] - unstable
+Toutes les modifications notables de PostIt et de la plateforme Yavsc
+sont documentées dans ce fichier.
-### Added
+Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
+et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-* [PostIt] Nouveau helper d'image `ImageHelper` pour charger des bitmaps depuis les ressources et depuis le web.
-* [PostIt] Affichage de l'avatar XS dans la liste des performers d'activites, avec fallback visuel (initiale utilisateur).
-* [PostIt.Tests] Nouveaux tests autour des URLs avatar et de la source d'autorite.
-* [contrib] Ajout d'un `README.md` utilitaire pour les symboles/icones.
+À noter : la **parité du numéro de patch** porte une signification de canal :
-### Changed
+- **patch pair** (ex. `1.0.0`, `1.0.2`) → **stable**
+- **patch impair** (ex. `1.0.1`, `1.0.3`) → **preview**
+- **suffixe** (ex. `1.0.0-rc1`, `1.0.0-alpha`) → **instable**
-* [PostIt] Les avatars ne sont plus relies en string sur `Image.Source`: ils sont telecharges et lies en `Bitmap`.
-* [Yavsc.Api.Client] `ActivityApiClient` accepte une base d'avatar dediee et construit les URLs avatar depuis l'autorite d'identification.
-* [PostIt] Le header de `MainPage` n'utilise plus `ScrollViewer`; remplacement par une barre de commandes basee sur `WrapPanel`.
-* [PostIt] Alignement de la navigation blogs: renommage `PushMainPageAsync` -> `PushBlogsPageAsync` et ajustement de `HomePageViewModel`.
+Cette convention est partagée avec le dépôt
+[`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian)
+pour la production des paquets `.deb`.
-### Fixed
-
-néant
-
-## [1.0.8-rc11] - unstable
-
-### Added
-
-nothing
-
-### Changed
-
-* [Yavsc.Api.Test] Mise a jour de `Microsoft.EntityFrameworkCore.Sqlite` vers `10.0.11` afin de supprimer l'alerte NU1903 liee a `SQLitePCLRaw.lib.e_sqlite3` 2.1.11.
-* [Yavsc.Org] Nettoyage de la configuration NuGet pour le restore: suppression du fichier local `Directory.Packages.props` au profit du fichier racine centralise.
-* [Yavsc.Org] Suppression de references de packages redondantes dans le projet, sans impact fonctionnel attendu.
-
-### Fixed
-
-* [Yavsc.Api.Test] Le restore n'emet plus le warning de vulnerabilite `NU1903` sur `SQLitePCLRaw.lib.e_sqlite3`.
-* [Yavsc.Org] Suppression d'une vulnerabilite de severite elevee sur AutoMapper apres publication et consommation de la nouvelle version candidate de `HigginsSoft.IdentityServer8`.
-
-## [1.0.8-rc10] - unstable
-
-### Added
-
-* [PostIt] Une page d'historique des commandes billing permet maintenant d'ouvrir une commande existante.
-* [PostIt] Une vue "Demandes en cours" en lecture seule est disponible pour le performer, filtrée sur les statuts actifs (Inserted, Accepted, InProgress).
-* [Yavsc.Org] Nouvelles entités `Country` et `PerformerCodeInputValidation` pour piloter la validation du code entreprise performer par pays.
-
-### Changed
-
-* [PostIt] La page détail billing se préremplit depuis une commande existante (Rdv, Brush, MBrush) et passe en mode mise à jour.
-* [Yavsc.Org] Le formulaire `Manage/SetActivity` inclut désormais le pays d'exercice (`fr`, `en`, `pt`) et applique la regex associée au champ `SIREN`.
-* [Yavsc.Org] La vérification externe du numéro d'entreprise est conservée uniquement pour le pays `fr`.
-
-### Fixed
-
-* [PostIt] Le flux historique n'est plus limité à une simple liste: l'action d'ouverture charge la commande cible puis navigue vers la page détail.
-* [Yavsc.Org] Le champ `SIREN` n'est plus validé avec une règle unique indépendante du pays d'exercice.
-
-## [1.0.8-rc9] - unstable
-
-### Added
-
-nothing
-
-### Changed
-
-masquage non-owner côté backend de l'ACL du billet
-
-### Fixed
-
-On a maintenant le comportement attendu bout en bout:
-
-ACL chargée depuis le BlogPostDto
-noms de cercles affichés dans le dialogue ACL côté PostIt
-
-## [1.0.8-rc8] - unstable
-
-### Added
-
-nothing
-
-### Changed
-
-nothing
-
-### Fixed
-
-The PostIt publish toggle button
-
-## [1.0.8-rc7] - unstable
-
-### Added
-
-* [PostIt] The search pattern now persists
-
-### Changed
-
-* The blog spot path is now `/api/v1/blogspot` (yet in last release)
-
-### Fixed
-
-* [Yavsc.Org] (Ticket #45) La forme de l'email de l'utilisateur est maintenant validée avant l'envoi du formulaire d'enregistrement
## [1.0.8-rc6] - unstable
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8aa0567d..b9969ea5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,39 +49,6 @@ Les tests sont répartis en :
item « Tests d'intégration smoke par BC ».
- `src/PostIt.Tests/` — tests unitaires du client desktop PostIt.
-## Onboarding assiste par agents IA
-
-Pour accelerer la prise en main du depot avec Copilot/Plan/Explore :
-
-- Parcours pas-a-pas : [doc/onboarding-agents.md](./doc/onboarding-agents.md)
-- Playbook d'usage des agents : [doc/agent-playbook.md](./doc/agent-playbook.md)
-- Matrice intentions -> agent -> preuves : [doc/agent-intent-matrix.md](./doc/agent-intent-matrix.md)
-
-Regle minimale en contribution assistee par agent :
-- expliciter l'impact architecture,
-- justifier le niveau de tests execute,
-- documenter les risques residuels.
-
-## Le CHANGELOG.md
-
-Le `CHANGELOG.md` est un document de changement de version
-
-Toutes les modifications notables de PostIt et de la plateforme Yavsc
-sont documentées dans ce fichier.
-
-Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
-et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-À noter : la **parité du numéro de patch** porte une signification de canal :
-
-- **patch pair** (ex. `1.0.0`, `1.0.2`) → **preview**
-- **patch impair** (ex. `1.0.1`, `1.0.3`) → **stable**
-- **suffixe** (ex. `1.0.0-rc1`, `1.0.0-alpha`) → **instable**
-
-Cette convention est partagée avec le dépôt
-[`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian)
-pour la production des paquets `.deb`.
-
## Navigation (PostIt)
La navigation est centralisée dans
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f1be93f9..7505c851 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,31 +1,15 @@
true
- 8.1.0-pazofrc007
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -33,24 +17,14 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 08f0461d..fa9d4ecf 100644
--- a/Makefile
+++ b/Makefile
@@ -77,6 +77,13 @@ release:
echo " V : version semver (ex. 1.0.7-rc1) — sert à nommer la branche."; \
exit 1; \
fi
+ @CURRENT=$$(git branch --show-current); \
+ if [ "$$CURRENT" != "main" ]; then \
+ echo "Refus : la cible doit être lancée depuis main."; \
+ echo " Branche courante : $$CURRENT"; \
+ echo " Fais : git checkout main && git pull --ff-only origin main"; \
+ exit 1; \
+ fi
@if [ -n "$$(git status --porcelain)" ]; then \
echo "Working tree sale, refus de créer une branche release."; \
git status --short; \
diff --git a/README.md b/README.md
index 549348f4..33dc4c77 100644
--- a/README.md
+++ b/README.md
@@ -16,11 +16,9 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml
# Statut actuel des actions GitHub
-* [](https://github.com/pazof/yavsc/actions/workflows/codeql.yml)
-
-* [](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml)
-
+-* [](https://github.com/pazof/yavsc/actions/workflows/codeql.yml)
+* [](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml)
# Documentation
L'architecture, la roadmap et les détails métier sont documentés
@@ -28,10 +26,6 @@ sous [`doc/`](./doc/). Voir l'[index de la documentation](./doc/README.md)
pour le sommaire complet. La racine de l'architecture est
[Architecture.md](./doc/Architecture.md).
-Pour une prise en main guidee avec agents IA:
-- parcours onboarding: [doc/onboarding-agents.md](./doc/onboarding-agents.md)
-- playbook d'usage: [doc/agent-playbook.md](./doc/agent-playbook.md)
-
# Construction et déploiement
diff --git a/ROADMAP.md b/ROADMAP.md
index 364b98bd..4c00e629 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -68,7 +68,7 @@ Trois principes non négociables traversent tous les jalons :
>
> Chaque jalon a un **critère de sortie** vérifiable.
-### Jalon 0 — Fondations techniques
+### Jalon 0 — Fondations techniques *(en cours)*
> Cible : pouvoir parler du domaine sans se battre avec le runtime.
@@ -81,7 +81,7 @@ Trois principes non négociables traversent tous les jalons :
---
-### Jalon 1 — Prestation signée de bout en bout *(en cours)*
+### Jalon 1 — Prestation signée de bout en bout
> Cible : un projet client/fournisseur aboutit à un **devis signé par les deux parties**, traçable, avec notifications.
diff --git a/contrib/.env-sample b/contrib/.env-sample
deleted file mode 100644
index fb01ede4..00000000
--- a/contrib/.env-sample
+++ /dev/null
@@ -1,24 +0,0 @@
-# parametres de déploiement au Makefile
-
-POSTGRES_HOST=localhost
-POSTGRES_PORT=5432
-POSTGRES_DB=yavsc
-POSTGRES_USER=yavsc
-POSTGRES_PASSWORD=
-
-HTTP_HOST=localhost
-
-Org_PORT=83
-Blogs_PORT=85
-Api_PORT=87
-
-PostIt_CLIENT_ID=postit
-
-ASPNETCORE_Smtp__Host="mercure.pschneider.fr"
-ASPNETCORE_Smtp__Port=465
-ASPNETCORE_Smtp__SenderName="Paul Schneider"
-ASPNETCORE_Smtp__SenderEmail="paul@pschneider.fr"
-ASPNETCORE_Smtp__UserName="paul"
-ASPNETCORE_Smtp__Password=""
-
-DESTDIR=/srv/www/yavsc
diff --git a/contrib/Makefile b/contrib/Makefile
index 79145668..151045db 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -1,4 +1,4 @@
-APP_PROJECT_NAMES=Org Blogs Api
+APP_PROJECT_NAMES=Org Blogs
SLNDIR=..
include $(SLNDIR)/.env
@@ -9,11 +9,9 @@ generated/:
generated/yavscOrg.service:
generated/yavscBlogs.service:
-generated/yavscApi.service:
generated/yavsc%.service: generated/ template.service $(SLNDIR)/.env
@cat template.service | APP_NAME="$*" \
- DESTDIR="$(DESTDIR)" \
HTTP_HOST="$(HTTP_HOST)" \
HTTP_PORT="$*_$(HTTP_PORT)" \
BASEAPPDIR="$(BASEAPPDIR)" \
@@ -35,12 +33,11 @@ generated/yavsc%.service: generated/ template.service $(SLNDIR)/.env
@echo Created service file: $@
-copy-services: copy-service-Org copy-service-Blogs copy-service-Api
+copy-services: copy-service-Org copy-service-Blogs
copy-service-Org: /etc/systemd/system/yavscOrg.service
copy-service-Blogs: /etc/systemd/system/yavscBlogs.service
-copy-service-Api: /etc/systemd/system/yavscApi.service
-copy-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-services
+copy-binaries: build_publish_Org build_publish_Blogs stop-services
@for project in $(APP_PROJECT_NAMES); \
do LCAPI=$$(echo $${project}|tr [:upper:] [:lower:]) ; \
echo "$${project} -> $${LCAPI}" ; \
@@ -63,8 +60,6 @@ copy-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-serv
build_publish_%: clean_publish_dir_%
@ASPNETCORE_ENV=$(CONFIGURATION) dotnet publish $(SLNDIR)/src/Yavsc.$*/Yavsc.$*.csproj
-build_publish: build_publish_Org build_publish_Blogs build_publish_Api
-
clean_publish_dir_%:
@rm -rf $(SLNDIR)/src/Yavsc.$*/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish
@@ -89,7 +84,6 @@ stop-services:
$(SLNDIR)/src/Yavsc.Org/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish
$(SLNDIR)/src/Yavsc.Blogs/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish
-$(SLNDIR)/src/Yavsc.Api/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish
showConfig:
@echo CONFIGURATION: $(CONFIGURATION)
@@ -98,3 +92,4 @@ showConfig:
clean:
@rm -rf generated
+.PHONY: build_publish mep showConfig copy-service-Org copy-service-Blogs reinstall clean
diff --git a/contrib/README.md b/contrib/README.md
deleted file mode 100644
index 01156156..00000000
--- a/contrib/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Read me
-
-## Note aux icones
-
-㝉®🅬⛒⛑🩎🩺🞫🞮🞕🞖🞆🔴🔵🔲🖂🔧🔩🔐🔌💾💼💬💭👿👾🏷🎯🏹🌍🎎💩
diff --git a/contrib/tmp/yavscBlogs.service b/contrib/tmp/yavscBlogs.service
deleted file mode 100644
index 718534cd..00000000
--- a/contrib/tmp/yavscBlogs.service
+++ /dev/null
@@ -1,37 +0,0 @@
-[Unit]
-Description=yavsc-Blogs
-After=syslog.target
-After=network.target
-Wants=postgresql.service
-After=postgresql.service
-
-[Service]
-RestartSec=5s
-Type=simple
-User=yavsc
-Group=yavsc
-WorkingDirectory=/srv/www/yavsc
-ExecStart=/srv/www/yavsc/Yavsc.Blogs
-Restart=always
-Environment="HOME="
-Environment="ANTHROPIC_API_KEY=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"
-Environment="ANTHROPIC_MAX_TOKENS=255"
-Environment="ASPNETCORE_Environment="
-Environment="ASPNETCORE_Kestrel__Endpoints__Http=http://localhost:Blogs_"
-Environment="ASPNETCORE_ConnectionStrings__YavscConnection=Server=localhost;Port=5432;Database=yavsc;Username=yavsc;Password=4T/X+fOnE;"
-
-Environment="ASPNETCORE_Smtp__Host=\"mercure.pschneider.f\""
-Environment="ASPNETCORE_Smtp__Port=465"
-Environment="ASPNETCORE_Smtp__SenderName=\"Paul Schneider\""
-Environment="ASPNETCORE_Smtp__SenderEmail=\"paul@pschneider.fr\""
-Environment="ASPNETCORE_Smtp__UserName=\"paul\""
-Environment="ASPNETCORE_Smtp__Password=\"j\0Dsn5=t\""
-
-CapabilityBoundingSet=CAP_NET_BIND_SERVICE
-AmbientCapabilities=CAP_NET_BIND_SERVICE
-StandardOutput=syslog
-StandardError=syslog
-SyslogIdentifier=yavscBlogs
-
-[Install]
-WantedBy=multi-user.target
diff --git a/doc/README.md b/doc/README.md
index fb9bea94..912a2e56 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -18,9 +18,6 @@ La racine de l'architecture est [Architecture.md](Architecture.md).
| [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding |
| [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md) | Découpage des projets .NET (Abstract, Server, Org, Api, Blogs, Web, Org.Tests) |
| [testing.md](testing.md) | Stratégie de test : conventions des dossiers, EF Core in-memory, auth stubs, scaffold partagé |
-| [onboarding-agents.md](onboarding-agents.md) | Parcours pas-à-pas pour prise en main agents IA + architecture + tests |
-| [agent-playbook.md](agent-playbook.md) | Playbook d'usage de Copilot, Plan, Explore avec scénarios et anti-patterns |
-| [agent-intent-matrix.md](agent-intent-matrix.md) | Matrice intentions développeur -> agent -> preuves attendues |
## Roadmap & design exploration
diff --git a/doc/agent-intent-matrix.md b/doc/agent-intent-matrix.md
deleted file mode 100644
index 975cd7b7..00000000
--- a/doc/agent-intent-matrix.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Matrice intentions -> agent -> preuves
-
-Cette matrice aide a choisir rapidement l'agent adapte et a exiger
-une sortie verifiable.
-
-| Intention developpeur | Agent principal | Entrees minimales | Sortie minimale attendue | Verification |
-|---|---|---|---|---|
-| Comprendre un BC avant changement | Explore | BC cible, profondeur, contrainte de perimetre | Composants, points d'entree, tests relies, risques | Lire les fichiers cites + confirmer tests proposes |
-| Decomposer une tache transverse | Plan | Objectif, contraintes, definition of done | Etapes ordonnees, dependances, criteres de verif | Verifier que chaque etape a une preuve observable |
-| Implementer une modif locale | Copilot | Fichier cible, comportement attendu, conventions | Patch minimal, justification courte | Build/test du projet impacte |
-| Ajouter un test smoke | Copilot (+Explore) | Route/endpoint, projet de test cible | Test + commande cible | Execution test cible |
-| Corriger une regression | Plan + Copilot | Symptome, zone suspecte, test attendu | Fix + test NonRegression | Test rouge avant, vert apres |
-| Diagnostiquer flux PostIt/OIDC | Explore + Plan | Flux, symptome, plateforme | Carte du flux + hypotheses testables | Verification manuelle + tests existants |
-
-## Regles d'arbitrage
-
-- Si l'intention est "comprendre": commencer par Explore.
-- Si l'intention est "orchestrer": commencer par Plan.
-- Si l'intention est "produire": utiliser Copilot apres cadrage.
-- Si une sortie n'inclut pas de preuve, elle est incomplete.
diff --git a/doc/agent-playbook.md b/doc/agent-playbook.md
deleted file mode 100644
index ecc14f1d..00000000
--- a/doc/agent-playbook.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# Playbook d'usage des agents IA (Yavsc)
-
-Ce playbook normalise l'usage de Copilot, Plan et Explore dans le depot.
-Il privilegie des sorties verifiables: fichiers, commandes tests, risques.
-
-## Quand utiliser quel agent
-
-- Plan: quand la tache est ambigue, transverse ou risquee.
-- Explore: quand il faut cartographier rapidement des zones du code.
-- Copilot: quand les specifications sont claires et localisees.
-
-## Prompt type (base)
-
-Utiliser ce squelette avant toute tache non triviale:
-
-```text
-Contexte:
-Objectif:
-Contraintes:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt/Views/ActivitiesPage.axaml.cs b/src/PostIt/PostIt/Views/ActivitiesPage.axaml.cs
deleted file mode 100644
index 95517007..00000000
--- a/src/PostIt/PostIt/Views/ActivitiesPage.axaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-
-namespace PostIt.Views;
-
-public partial class ActivitiesPage : ContentPage
-{
- public ActivitiesPage()
- {
- InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-}
diff --git a/src/PostIt/PostIt/Views/BillingQueriesPage.axaml b/src/PostIt/PostIt/Views/BillingQueriesPage.axaml
deleted file mode 100644
index c4b333e4..00000000
--- a/src/PostIt/PostIt/Views/BillingQueriesPage.axaml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/PostIt/PostIt/Views/BillingQueriesPage.axaml.cs b/src/PostIt/PostIt/Views/BillingQueriesPage.axaml.cs
deleted file mode 100644
index 1ca28f04..00000000
--- a/src/PostIt/PostIt/Views/BillingQueriesPage.axaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-
-namespace PostIt.Views;
-
-public partial class BillingQueriesPage : ContentPage
-{
- public BillingQueriesPage()
- {
- InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-}
\ No newline at end of file
diff --git a/src/PostIt/PostIt/Views/CommandFormsPage.axaml b/src/PostIt/PostIt/Views/CommandFormsPage.axaml
deleted file mode 100644
index 8fad010e..00000000
--- a/src/PostIt/PostIt/Views/CommandFormsPage.axaml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt/Views/CommandFormsPage.axaml.cs b/src/PostIt/PostIt/Views/CommandFormsPage.axaml.cs
deleted file mode 100644
index 1d19715b..00000000
--- a/src/PostIt/PostIt/Views/CommandFormsPage.axaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-
-namespace PostIt.Views;
-
-public partial class CommandFormsPage : ContentPage
-{
- public CommandFormsPage()
- {
- InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-}
\ No newline at end of file
diff --git a/src/PostIt/PostIt/Views/Commands/BrushPage.axaml b/src/PostIt/PostIt/Views/Commands/BrushPage.axaml
deleted file mode 100644
index 9c33cecb..00000000
--- a/src/PostIt/PostIt/Views/Commands/BrushPage.axaml
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt/Views/Commands/BrushPage.axaml.cs b/src/PostIt/PostIt/Views/Commands/BrushPage.axaml.cs
deleted file mode 100644
index c028472f..00000000
--- a/src/PostIt/PostIt/Views/Commands/BrushPage.axaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-
-namespace PostIt.Views.Commands;
-
-public partial class BrushPage : ContentPage
-{
- public BrushPage()
- {
- InitializeComponent();
- }
-}
diff --git a/src/PostIt/PostIt/Views/Commands/RdvPage.axaml b/src/PostIt/PostIt/Views/Commands/RdvPage.axaml
deleted file mode 100644
index 46280602..00000000
--- a/src/PostIt/PostIt/Views/Commands/RdvPage.axaml
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt/Views/Commands/RdvPage.axaml.cs b/src/PostIt/PostIt/Views/Commands/RdvPage.axaml.cs
deleted file mode 100644
index 6a0e1d05..00000000
--- a/src/PostIt/PostIt/Views/Commands/RdvPage.axaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-
-namespace PostIt.Views.Commands;
-
-public partial class RdvPage : ContentPage
-{
- public RdvPage()
- {
- InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-}
diff --git a/src/PostIt/PostIt/Views/HomePage.axaml b/src/PostIt/PostIt/Views/HomePage.axaml
index 7f6527e3..16e66cc0 100644
--- a/src/PostIt/PostIt/Views/HomePage.axaml
+++ b/src/PostIt/PostIt/Views/HomePage.axaml
@@ -18,9 +18,5 @@
Command="{Binding OpenBlogs}"
HorizontalAlignment="Center"
IsEnabled="{Binding SessionStatus.IsLoggedIn}"/>
-
diff --git a/src/PostIt/PostIt/Views/MainPage.axaml b/src/PostIt/PostIt/Views/MainPage.axaml
index 41ed7ece..e44f9f99 100644
--- a/src/PostIt/PostIt/Views/MainPage.axaml
+++ b/src/PostIt/PostIt/Views/MainPage.axaml
@@ -24,20 +24,20 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
@@ -31,10 +32,10 @@
-
+
-
-
+
+ Text="{Binding BusinessApiUrl, Mode=TwoWay}"/>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/src/PostIt/PostIt/Views/SettingsPage.axaml.cs b/src/PostIt/PostIt/Views/SettingsPage.axaml.cs
index 593dcfee..14916bb3 100644
--- a/src/PostIt/PostIt/Views/SettingsPage.axaml.cs
+++ b/src/PostIt/PostIt/Views/SettingsPage.axaml.cs
@@ -1,116 +1,12 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using Avalonia.Controls;
-using Avalonia.Media;
-using Avalonia.Platform.Storage;
-using Microsoft.Extensions.DependencyInjection;
-using PostIt.Services;
namespace PostIt.Views;
public partial class SettingsPage: ContentPage
{
- private const int MaxAvatarSizeMegabytes = 2;
- private const string AcceptedAvatarFormats = "PNG, JPG/JPEG, WEBP, GIF";
-
- private int _avatarStatusVersion;
-
public SettingsPage()
{
InitializeComponent();
}
-
- private async void ChooseAvatar_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
- {
- var statusVersion = Interlocked.Increment(ref _avatarStatusVersion);
- ChooseAvatarButton.IsEnabled = false;
- SetAvatarStatus("Sélection d'un fichier avatar...", Brushes.Gray);
-
- var topLevel = TopLevel.GetTopLevel(this);
- if (topLevel is null)
- {
- SetAvatarStatus("Impossible d'accéder à la fenêtre active.", Brushes.IndianRed);
- ChooseAvatarButton.IsEnabled = true;
- return;
- }
-
- var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
- {
- Title = "Choisir un avatar",
- AllowMultiple = false,
- FileTypeFilter = new[]
- {
- new FilePickerFileType("Images")
- {
- Patterns = new[] { "*.png", "*.jpg", "*.jpeg", "*.webp", "*.gif" }
- }
- }
- });
-
- var file = files.FirstOrDefault();
- if (file is null)
- {
- SetAvatarStatus("Upload annulé.", Brushes.Gray);
- ChooseAvatarButton.IsEnabled = true;
- return;
- }
-
- try
- {
- SetAvatarStatus("Upload avatar en cours...", Brushes.Gray);
-
- await using var stream = await file.OpenReadAsync();
- var api = ((App)App.Current!).ServiceProvider!.GetRequiredService();
- var message = await api.SetAvatarAsync(stream, file.Name, GetMimeType(file.Name));
- SetAvatarStatus(string.IsNullOrWhiteSpace(message)
- ? "Avatar mis à jour."
- : message, Brushes.ForestGreen);
-
- _ = ClearSuccessStatusLaterAsync(statusVersion);
- }
- catch (Exception ex)
- {
- SetAvatarStatus($"Échec upload avatar: {ex.Message}", Brushes.IndianRed);
- Console.Error.WriteLine($"🩎 Avatar upload failed: {ex.Message}");
- }
- finally
- {
- ChooseAvatarButton.IsEnabled = true;
- }
- }
-
- private async Task ClearSuccessStatusLaterAsync(int statusVersion)
- {
- await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(true);
- if (statusVersion != _avatarStatusVersion)
- return;
-
- if (AvatarUploadStatusText.Foreground == Brushes.ForestGreen)
- {
- SetAvatarStatus($"Avatar prêt. Formats supportés: {AcceptedAvatarFormats}. Taille max: {MaxAvatarSizeMegabytes} MB.", Brushes.Gray);
- }
- }
-
- private void SetAvatarStatus(string message, IBrush color)
- {
- AvatarUploadStatusText.Foreground = color;
- AvatarUploadStatusText.Text = message;
- }
-
- private static string GetMimeType(string fileName)
- {
- var ext = Path.GetExtension(fileName)?.ToLowerInvariant();
- return ext switch
- {
- ".png" => "image/png",
- ".jpg" or ".jpeg" => "image/jpeg",
- ".webp" => "image/webp",
- ".gif" => "image/gif",
- _ => "application/octet-stream"
- };
- }
-}
+}
\ No newline at end of file
diff --git a/src/PostIt/PostIt/Views/SignaturePage.axaml.cs b/src/PostIt/PostIt/Views/SignaturePage.axaml.cs
index b6e9bc12..da6b8b7b 100644
--- a/src/PostIt/PostIt/Views/SignaturePage.axaml.cs
+++ b/src/PostIt/PostIt/Views/SignaturePage.axaml.cs
@@ -62,16 +62,9 @@ public partial class SignaturePage : ContentPage
var h = PadFrame.Bounds.Height;
if (w <= 0 || h <= 0) return;
- var pending = _control.PendingStroke;
var strokes = _control.Strokes;
- int sealedCount = strokes.Count - pending.Count;
- if (sealedCount < 0)
- {
- sealedCount = 0;
- }
-
int i = 0;
- while (i < sealedCount)
+ while (i < strokes.Count)
{
int k = strokes[i];
if (k <= 0) break;
@@ -94,27 +87,5 @@ public partial class SignaturePage : ContentPage
poly.Points = pts;
InkLayer.Children.Add(poly);
}
-
- if (pending.Count > 0)
- {
- var poly = new Polyline
- {
- Stroke = StrokeBrush,
- StrokeThickness = StrokeThickness,
- StrokeLineCap = PenLineCap.Round,
- StrokeJoin = PenLineJoin.Round,
- };
-
- var pts = new List(pending.Count / 2);
- for (int p = 0; p < pending.Count; p += 2)
- {
- int nx = pending[p];
- int ny = pending[p + 1];
- pts.Add(new Point(nx / CoordinateMax * w, ny / CoordinateMax * h));
- }
-
- poly.Points = pts;
- InkLayer.Children.Add(poly);
- }
}
}
diff --git a/src/Yavsc.Abstract/Authentication/RegisterModel.cs b/src/Yavsc.Abstract/Authentication/RegisterModel.cs
index 3bf9876b..2ebb3297 100644
--- a/src/Yavsc.Abstract/Authentication/RegisterModel.cs
+++ b/src/Yavsc.Abstract/Authentication/RegisterModel.cs
@@ -12,8 +12,8 @@ namespace Yavsc.ViewModels.Account
public string UserName { get; set; }
[Required()]
- [StringLength(maximumLength: 102, MinimumLength = 5)]
- [EmailAddress(ErrorMessage = "L'adresse e-mail n'est pas valide.")]
+ [StringLength( maximumLength:102, MinimumLength = 5)]
+ // [EmailAddress]
[Display(Name = "Email", Description = "E-Mail")]
public string Email { get; set; }
diff --git a/src/Yavsc.Abstract/Blogspot/BlogPostDto.cs b/src/Yavsc.Abstract/Blogspot/BlogPostDto.cs
index 9cb5b143..0da052ae 100644
--- a/src/Yavsc.Abstract/Blogspot/BlogPostDto.cs
+++ b/src/Yavsc.Abstract/Blogspot/BlogPostDto.cs
@@ -1,5 +1,4 @@
using Yavsc.Abstract.Identity.Security;
-using System.Text.Json.Serialization;
namespace Yavsc.Blogspot;
@@ -36,26 +35,11 @@ public class BlogPostDto : IBlogPost
return true;
}
- public ICollection ACL = new List();
-
- ///
- /// Wire-only ACL bridge for System.Text.Json: accepts the
- /// acl/ACL payload from GET detail responses,
- /// but is never emitted on POST/PUT from the client.
- ///
- [JsonPropertyName("acl")]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
- public List? WireAcl
- {
- get => null;
- set => ACL = value ?? new List();
- }
+ private List ACL { get; set; } = new List();
public string[] Tags { get; set; }
- ICollection ICircleAuthorized.ACL => this.ACL;
-
public string[] GetTags() => Tags;
- public CircleAuthorization[] GetACL() => ACL.ToArray();
+ public ICircleAuthorization[] GetACL() => ACL.ToArray();
}
diff --git a/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs b/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs
index a42e5428..c58fca48 100644
--- a/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs
+++ b/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs
@@ -3,7 +3,8 @@ using Yavsc.Abstract.Identity.Security;
namespace Yavsc.Abstract.BlogSpot;
-public class PostAccessControlRulePayload : CircleAuthorization
+public class PostAccessControlRulePayload : ICircleAuthorization
{
+ public long CircleId { get; set; }
public long BlogPostId { get; set; }
}
diff --git a/src/Yavsc.Abstract/HairCut/HairPrestationDto.cs b/src/Yavsc.Abstract/HairCut/HairPrestationDto.cs
deleted file mode 100644
index 0c4b80e2..00000000
--- a/src/Yavsc.Abstract/HairCut/HairPrestationDto.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Yavsc.Models.Haircut;
-
-///
-/// Lightweight hair-prestation description exposed to API clients.
-///
-public sealed class HairPrestationDto
-{
- public long Id { get; set; }
- public string Title { get; set; } = string.Empty;
- public string Details { get; set; } = string.Empty;
-}
\ No newline at end of file
diff --git a/src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs b/src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs
index 96392c7b..d8b08c05 100644
--- a/src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs
+++ b/src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs
@@ -11,7 +11,7 @@ namespace Yavsc.Abstract.Identity.Security;
/// UI already has the post, and the circles are looked up by id
/// against the list returned by GET /api/circle.
///
-public class CircleAuthorization
+public sealed class CircleAuthorization : ICircleAuthorization
{
public long CircleId { get; set; }
}
diff --git a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorization.cs b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorization.cs
new file mode 100644
index 00000000..9c16bd3b
--- /dev/null
+++ b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorization.cs
@@ -0,0 +1,8 @@
+namespace Yavsc.Abstract.Identity.Security
+{
+
+ public interface ICircleAuthorization
+ {
+ long CircleId { get; set; }
+ }
+}
diff --git a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs
index 6b593f3c..25c21961 100644
--- a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs
+++ b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs
@@ -9,7 +9,7 @@ namespace Yavsc.Abstract.Identity.Security
bool AuthorizeCircle(long circleId);
- ICollection ACL { get; } //ICircleAuthorization [] GetACL();
+ ICircleAuthorization [] GetACL();
}
}
diff --git a/src/Yavsc.Abstract/Workflow/ActivityInfo.cs b/src/Yavsc.Abstract/Workflow/ActivityInfo.cs
deleted file mode 100644
index c0f543f4..00000000
--- a/src/Yavsc.Abstract/Workflow/ActivityInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Yavsc.Abstract.Workflow;
-
-///
-/// Activity node returned by the browsing API.
-///
-public sealed class ActivityInfo
-{
- public string Code { get; set; } = string.Empty;
- public string Name { get; set; } = string.Empty;
- public string ParentCode { get; set; } = string.Empty;
- public string Description { get; set; } = string.Empty;
- public string Photo { get; set; } = string.Empty;
- public int Rate { get; set; }
- public int PerformerCount { get; set; }
- public List Forms { get; set; } = new();
- public List Children { get; set; } = new();
-}
diff --git a/src/Yavsc.Abstract/Workflow/CommandFormSummary.cs b/src/Yavsc.Abstract/Workflow/CommandFormSummary.cs
deleted file mode 100644
index 865690e0..00000000
--- a/src/Yavsc.Abstract/Workflow/CommandFormSummary.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Yavsc.Abstract.Workflow;
-
-///
-/// Lightweight command-form description exposed to API clients.
-///
-public sealed class CommandFormSummary
-{
- public long Id { get; set; }
- public string ActionName { get; set; } = string.Empty;
- public string Title { get; set; } = string.Empty;
-}
diff --git a/src/Yavsc.Abstract/Workflow/Country.cs b/src/Yavsc.Abstract/Workflow/Country.cs
deleted file mode 100644
index d782d0aa..00000000
--- a/src/Yavsc.Abstract/Workflow/Country.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace Yavsc.Models.Workflow
-{
- ///
- /// Supported country of exercise for performer profile validations.
- ///
- public class Country
- {
- [Key]
- [MaxLength(2)]
- [MinLength(2)]
- public string Code { get; set; } = string.Empty;
-
- [Required]
- [MaxLength(64)]
- public string DisplayName { get; set; } = string.Empty;
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs b/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs
index 4314dd37..e53583a5 100644
--- a/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs
+++ b/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs
@@ -3,7 +3,6 @@ namespace Yavsc.Workflow
public interface IPerformerProfile
{
string PerformerId { get; set; }
- string ExerciseCountryCode { get; set; }
string SIREN { get; set; }
bool AcceptNotifications { get; set; }
long OrganizationAddressId { get; set; }
diff --git a/src/Yavsc.Abstract/Workflow/PerformerActivity.cs b/src/Yavsc.Abstract/Workflow/PerformerActivity.cs
deleted file mode 100644
index a5ee3b25..00000000
--- a/src/Yavsc.Abstract/Workflow/PerformerActivity.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Yavsc.Abstract.Workflow;
-
-///
-/// Lightweight performer description returned for one activity.
-///
-public sealed class PerformerActivity
-{
- public string PerformerId { get; set; } = string.Empty;
- public bool HasPerformerProfile { get; set; }
- public string UserName { get; set; } = string.Empty;
- public bool Active { get; set; }
- public bool AcceptNotifications { get; set; }
- public bool AcceptPublicContact { get; set; }
- public string WebSite { get; set; } = string.Empty;
- public string ActivityCode { get; set; } = string.Empty;
- public string ActivityName { get; set; } = string.Empty;
- public string SettingsClassName { get; set; } = string.Empty;
- public int ExtraActivityCount { get; set; }
-}
diff --git a/src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs b/src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs
deleted file mode 100644
index 84e9c1cc..00000000
--- a/src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Yavsc.Models.Workflow
-{
- ///
- /// Validation rule for performer business code input by country.
- ///
- public class PerformerCodeInputValidation
- {
- [Key]
- public long Id { get; set; }
-
- [Required]
- [MaxLength(2)]
- [MinLength(2)]
- public string CountryCode { get; set; } = string.Empty;
-
- [Required]
- [MaxLength(256)]
- public string RegularExpression { get; set; } = string.Empty;
-
- [Required]
- [MaxLength(128)]
- public string ErrorMessage { get; set; } = string.Empty;
-
- [ForeignKey(nameof(CountryCode))]
- public Country Country { get; set; }
- }
-
- public static class PerformerCodeInputValidationCatalog
- {
- public static readonly IReadOnlyList Countries = new List
- {
- new() { Code = "fr", DisplayName = "France" },
- new() { Code = "en", DisplayName = "England" },
- new() { Code = "pt", DisplayName = "Portugal" },
- };
-
- public static readonly IReadOnlyList Rules = new List
- {
- new()
- {
- Id = 1,
- CountryCode = "fr",
- RegularExpression = "^[0-9]{9,14}$",
- ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres."
- },
- new()
- {
- Id = 2,
- CountryCode = "en",
- RegularExpression = "^[A-Za-z0-9]{8,14}$",
- ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques."
- },
- new()
- {
- Id = 3,
- CountryCode = "pt",
- RegularExpression = "^[0-9]{9}$",
- ErrorMessage = "Le code PT doit contenir exactement 9 chiffres."
- },
- };
-
- public static string NormalizeCountryCode(string? countryCode)
- {
- return (countryCode ?? string.Empty).Trim().ToLowerInvariant();
- }
-
- public static PerformerCodeInputValidation? GetRule(string? countryCode)
- {
- var normalized = NormalizeCountryCode(countryCode);
- foreach (var rule in Rules)
- {
- if (string.Equals(rule.CountryCode, normalized, StringComparison.Ordinal))
- {
- return rule;
- }
- }
-
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj
index 78ffe531..4a9c75bc 100644
--- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj
+++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj
@@ -11,7 +11,7 @@
latest
1.1.0.0
1.1.0.0
- 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070
+ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0
1.1.0-beta.1
\ No newline at end of file
diff --git a/src/Yavsc.Api.Client/ActivityApiClient.cs b/src/Yavsc.Api.Client/ActivityApiClient.cs
deleted file mode 100644
index 52fad1a4..00000000
--- a/src/Yavsc.Api.Client/ActivityApiClient.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using Yavsc.Abstract.Workflow;
-
-namespace Yavsc.Api.Client;
-
-///
-/// HTTP client for browsing business activities and their performers.
-/// Uses absolute URLs so it can coexist with other Yavsc clients that
-/// target a different API host on the same shared transport. The same
-/// activity payload also carries the eligible billing forms for a
-/// selected performer/activity pair.
-///
-public sealed class ActivityApiClient
-{
- private const string PathPrefix = "activity";
-
- private readonly IYavscApiClient _api;
- private readonly Uri _baseAddress;
- private readonly Uri _avatarBaseAddress;
-
- public ActivityApiClient(IYavscApiClient api, string businessBaseAddress, string? avatarBaseAddress = null)
- {
- _api = api ?? throw new ArgumentNullException(nameof(api));
- if (string.IsNullOrEmpty(businessBaseAddress))
- throw new ArgumentException("Base address is required.", nameof(businessBaseAddress));
-
- _baseAddress = new Uri(businessBaseAddress, UriKind.Absolute);
- _avatarBaseAddress = string.IsNullOrWhiteSpace(avatarBaseAddress)
- ? _baseAddress
- : new Uri(avatarBaseAddress, UriKind.Absolute);
- }
-
- public Task> GetCatalogAsync(
- string? parentCode = null,
- CancellationToken ct = default)
- {
- var path = string.IsNullOrWhiteSpace(parentCode)
- ? $"{PathPrefix}/catalog"
- : $"{PathPrefix}/catalog?parentCode={Uri.EscapeDataString(parentCode)}";
-
- return _api.CallAsync>(HttpMethod.Get, Absolute(path), ct: ct);
- }
-
- public Task> GetUsersAsync(
- string activityCode,
- CancellationToken ct = default)
- {
- if (string.IsNullOrWhiteSpace(activityCode))
- throw new ArgumentException("Activity code is required.", nameof(activityCode));
-
- return _api.CallAsync>(
- HttpMethod.Get,
- Absolute($"{PathPrefix}/{Uri.EscapeDataString(activityCode)}/users"),
- ct: ct);
- }
-
- public Task> GetPerformersAsync(
- string activityCode,
- CancellationToken ct = default)
- => GetUsersAsync(activityCode, ct);
-
- public string BuildAvatarXsUrl(string? userName)
- {
- var siteRoot = new Uri(_avatarBaseAddress, "/");
-
- if (string.IsNullOrWhiteSpace(userName))
- {
- return new Uri(siteRoot, "images/Users/icon_user.xs.png").ToString();
- }
-
- return new Uri(siteRoot, $"avatars/{Uri.EscapeDataString(userName)}.xs.png").ToString();
- }
-
- private string Absolute(string relativePath) => new Uri(_baseAddress, relativePath).ToString();
-}
diff --git a/src/Yavsc.Api.Client/BillingApiClient.cs b/src/Yavsc.Api.Client/BillingApiClient.cs
deleted file mode 100644
index 35a6722d..00000000
--- a/src/Yavsc.Api.Client/BillingApiClient.cs
+++ /dev/null
@@ -1,364 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using Yavsc.Models.Billing;
-using Yavsc.Models.Haircut;
-using Yavsc;
-
-namespace Yavsc.Api.Client;
-
-///
-/// HTTP client for posting commands to the business billing routes.
-/// Uses absolute URLs so it can coexist with blog-targeting clients on
-/// the same shared transport.
-///
-public sealed class BillingApiClient
-{
- private const string PathPrefix = "billing";
-
- private readonly IYavscApiClient _api;
- private readonly Uri _baseAddress;
-
- public BillingApiClient(IYavscApiClient api, string businessBaseAddress)
- {
- _api = api ?? throw new ArgumentNullException(nameof(api));
- if (string.IsNullOrWhiteSpace(businessBaseAddress))
- throw new ArgumentException("Base address is required.", nameof(businessBaseAddress));
-
- _baseAddress = new Uri(businessBaseAddress, UriKind.Absolute);
- }
-
- public Task CreateAsync(string billingCode, object payload, CancellationToken ct = default)
- {
- if (string.IsNullOrWhiteSpace(billingCode))
- throw new ArgumentException("Billing code is required.", nameof(billingCode));
-
- return _api.CallAsync(
- HttpMethod.Post,
- Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}"),
- body: payload,
- ct: ct);
- }
-
- public Task> GetHairPrestationsAsync(string billingCode, CancellationToken ct = default)
- {
- if (string.IsNullOrWhiteSpace(billingCode))
- throw new ArgumentException("Billing code is required.", nameof(billingCode));
-
- return _api.CallAsync>(
- HttpMethod.Get,
- Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}/prestations"),
- ct: ct);
- }
-
- public async Task> GetQuerySummariesAsync(string billingCode, CancellationToken ct = default)
- {
- if (string.IsNullOrWhiteSpace(billingCode))
- throw new ArgumentException("Billing code is required.", nameof(billingCode));
-
- var items = await _api.CallAsync>(
- HttpMethod.Get,
- Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}"),
- ct: ct) ?? new List();
-
- foreach (var item in items)
- {
- item.BillingCode = billingCode;
- }
-
- return items;
- }
-
- public async Task GetQueryAsync(string billingCode, long queryId, CancellationToken ct = default)
- {
- if (string.IsNullOrWhiteSpace(billingCode))
- throw new ArgumentException("Billing code is required.", nameof(billingCode));
- if (queryId <= 0)
- throw new ArgumentOutOfRangeException(nameof(queryId));
-
- var code = billingCode.Trim();
- var path = Absolute($"{PathPrefix}/{Uri.EscapeDataString(code)}/{queryId}");
-
- if (string.Equals(code, BillingCodes.Rdv, StringComparison.Ordinal))
- {
- var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false);
- return MapRdv(dto, code);
- }
-
- if (string.Equals(code, BillingCodes.Brush, StringComparison.Ordinal))
- {
- var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false);
- return MapBrush(dto, code);
- }
-
- if (string.Equals(code, BillingCodes.MBrush, StringComparison.Ordinal))
- {
- var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false);
- return MapMBrush(dto, code);
- }
-
- throw new NotSupportedException($"Billing code '{code}' is not supported.");
- }
-
- public Task UpdateAsync(string billingCode, long queryId, BillingQueryDetailsDto payload, CancellationToken ct = default)
- {
- if (string.IsNullOrWhiteSpace(billingCode))
- throw new ArgumentException("Billing code is required.", nameof(billingCode));
- if (queryId <= 0)
- throw new ArgumentOutOfRangeException(nameof(queryId));
- if (payload is null)
- throw new ArgumentNullException(nameof(payload));
-
- var code = billingCode.Trim();
-
- return _api.CallAsync(
- HttpMethod.Put,
- Absolute($"{PathPrefix}/{Uri.EscapeDataString(code)}/{queryId}"),
- body: BuildUpdatePayload(code, queryId, payload),
- ct: ct);
- }
-
- private string Absolute(string relativePath) => new Uri(_baseAddress, relativePath).ToString();
-
- private static BillingQueryDetailsDto MapRdv(RdvQueryResponse dto, string billingCode)
- {
- return new BillingQueryDetailsDto
- {
- Id = dto.Id,
- BillingCode = billingCode,
- ActivityCode = dto.ActivityCode ?? string.Empty,
- PerformerId = dto.PerformerId ?? string.Empty,
- ClientId = dto.ClientId ?? string.Empty,
- Description = dto.Description ?? string.Empty,
- Consent = dto.Consent,
- EventDate = dto.EventDate,
- Status = dto.Status,
- Reason = dto.Reason ?? string.Empty,
- Provisional = dto.Provisional,
- Location = dto.Location is null
- ? null
- : new BillingLocationDto
- {
- Address = dto.Location.Address ?? string.Empty,
- Latitude = dto.Location.Latitude,
- Longitude = dto.Location.Longitude,
- }
- };
- }
-
- private static BillingQueryDetailsDto MapBrush(HairCutQueryResponse dto, string billingCode)
- {
- return new BillingQueryDetailsDto
- {
- Id = dto.Id,
- BillingCode = billingCode,
- ActivityCode = dto.ActivityCode ?? string.Empty,
- PerformerId = dto.PerformerId ?? string.Empty,
- ClientId = dto.ClientId ?? string.Empty,
- Description = dto.Description ?? string.Empty,
- Consent = dto.Consent,
- EventDate = dto.EventDate,
- Status = dto.Status,
- AdditionalInfo = dto.AdditionalInfo ?? string.Empty,
- Provisional = dto.Provisional,
- PrestationId = dto.PrestationId,
- Location = dto.Location is null
- ? null
- : new BillingLocationDto
- {
- Address = dto.Location.Address ?? string.Empty,
- Latitude = dto.Location.Latitude,
- Longitude = dto.Location.Longitude,
- }
- };
- }
-
- private static BillingQueryDetailsDto MapMBrush(HairMultiCutQueryResponse dto, string billingCode)
- {
- return new BillingQueryDetailsDto
- {
- Id = dto.Id,
- BillingCode = billingCode,
- ActivityCode = dto.ActivityCode ?? string.Empty,
- PerformerId = dto.PerformerId ?? string.Empty,
- ClientId = dto.ClientId ?? string.Empty,
- Description = dto.Description ?? string.Empty,
- Consent = dto.Consent,
- EventDate = dto.EventDate,
- Status = dto.Status,
- Provisional = dto.Provisional,
- PrestationIds = (dto.Prestations ?? new List())
- .Select(p => p.PrestationId)
- .Where(id => id > 0)
- .ToList(),
- Location = dto.Location is null
- ? null
- : new BillingLocationDto
- {
- Address = dto.Location.Address ?? string.Empty,
- Latitude = dto.Location.Latitude,
- Longitude = dto.Location.Longitude,
- }
- };
- }
-
- private static object BuildUpdatePayload(string billingCode, long queryId, BillingQueryDetailsDto payload)
- {
- if (string.Equals(billingCode, BillingCodes.Rdv, StringComparison.Ordinal))
- {
- if (payload.EventDate is null)
- throw new ArgumentException("EventDate is required for Rdv.", nameof(payload));
-
- return new
- {
- Id = queryId,
- ActivityCode = payload.ActivityCode,
- PerformerId = payload.PerformerId,
- ClientId = payload.ClientId,
- Consent = payload.Consent,
- EventDate = payload.EventDate.Value,
- Location = ToLocationPayload(payload.Location),
- Reason = payload.Reason,
- Status = payload.Status,
- Provisional = payload.Provisional,
- Description = payload.Description,
- };
- }
-
- if (string.Equals(billingCode, BillingCodes.Brush, StringComparison.Ordinal))
- {
- if (payload.PrestationId is null || payload.PrestationId <= 0)
- throw new ArgumentException("PrestationId is required for Brush.", nameof(payload));
-
- return new
- {
- Id = queryId,
- ActivityCode = payload.ActivityCode,
- PerformerId = payload.PerformerId,
- ClientId = payload.ClientId,
- Consent = payload.Consent,
- EventDate = payload.EventDate,
- Location = ToLocationPayload(payload.Location),
- PrestationId = payload.PrestationId.Value,
- AdditionalInfo = payload.AdditionalInfo,
- Status = payload.Status,
- Provisional = payload.Provisional,
- Description = payload.Description,
- };
- }
-
- if (string.Equals(billingCode, BillingCodes.MBrush, StringComparison.Ordinal))
- {
- if (payload.EventDate is null)
- throw new ArgumentException("EventDate is required for MBrush.", nameof(payload));
- if (payload.PrestationIds is null || payload.PrestationIds.Count == 0)
- throw new ArgumentException("At least one prestation is required for MBrush.", nameof(payload));
-
- return new
- {
- Id = queryId,
- ActivityCode = payload.ActivityCode,
- PerformerId = payload.PerformerId,
- ClientId = payload.ClientId,
- Consent = payload.Consent,
- EventDate = payload.EventDate.Value,
- Location = ToLocationPayload(payload.Location),
- Prestations = payload.PrestationIds
- .Where(id => id > 0)
- .Select(id => new { PrestationId = id })
- .ToList(),
- Status = payload.Status,
- Provisional = payload.Provisional,
- Description = payload.Description,
- };
- }
-
- throw new NotSupportedException($"Billing code '{billingCode}' is not supported.");
- }
-
- private static object? ToLocationPayload(BillingLocationDto? location)
- {
- if (location is null)
- {
- return null;
- }
-
- var payload = new Dictionary
- {
- ["Address"] = location.Address,
- };
-
- if (location.Latitude.HasValue)
- {
- payload["Latitude"] = location.Latitude.Value;
- }
-
- if (location.Longitude.HasValue)
- {
- payload["Longitude"] = location.Longitude.Value;
- }
-
- return payload;
- }
-
- private sealed class BillingLocationResponse
- {
- public string? Address { get; set; }
- public double Latitude { get; set; }
- public double Longitude { get; set; }
- }
-
- private sealed class RdvQueryResponse
- {
- public long Id { get; set; }
- public string? ActivityCode { get; set; }
- public string? PerformerId { get; set; }
- public string? ClientId { get; set; }
- public string? Description { get; set; }
- public bool Consent { get; set; }
- public DateTime EventDate { get; set; }
- public QueryStatus Status { get; set; }
- public string? Reason { get; set; }
- public decimal? Provisional { get; set; }
- public BillingLocationResponse? Location { get; set; }
- }
-
- private sealed class HairCutQueryResponse
- {
- public long Id { get; set; }
- public string? ActivityCode { get; set; }
- public string? PerformerId { get; set; }
- public string? ClientId { get; set; }
- public string? Description { get; set; }
- public bool Consent { get; set; }
- public DateTime? EventDate { get; set; }
- public QueryStatus Status { get; set; }
- public decimal? Provisional { get; set; }
- public long PrestationId { get; set; }
- public string? AdditionalInfo { get; set; }
- public BillingLocationResponse? Location { get; set; }
- }
-
- private sealed class HairMultiCutQueryResponse
- {
- public long Id { get; set; }
- public string? ActivityCode { get; set; }
- public string? PerformerId { get; set; }
- public string? ClientId { get; set; }
- public string? Description { get; set; }
- public bool Consent { get; set; }
- public DateTime EventDate { get; set; }
- public QueryStatus Status { get; set; }
- public decimal? Provisional { get; set; }
- public BillingLocationResponse? Location { get; set; }
- public List? Prestations { get; set; }
- }
-
- private sealed class HairPrestationCollectionItemResponse
- {
- public long PrestationId { get; set; }
- }
-}
diff --git a/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs b/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs
deleted file mode 100644
index 6d658ce4..00000000
--- a/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Yavsc;
-
-namespace Yavsc.Api.Client;
-
-///
-/// Normalized billing-query shape used by PostIt when opening an existing
-/// command from history.
-///
-public sealed class BillingQueryDetailsDto
-{
- public long Id { get; set; }
- public string BillingCode { get; set; } = string.Empty;
- public string ActivityCode { get; set; } = string.Empty;
- public string PerformerId { get; set; } = string.Empty;
- public string ClientId { get; set; } = string.Empty;
- public string Description { get; set; } = string.Empty;
- public bool Consent { get; set; } = true;
- public DateTime? EventDate { get; set; }
- public QueryStatus Status { get; set; } = QueryStatus.Inserted;
- public string Reason { get; set; } = string.Empty;
- public string AdditionalInfo { get; set; } = string.Empty;
- public decimal? Provisional { get; set; }
- public BillingLocationDto? Location { get; set; }
- public long? PrestationId { get; set; }
- public List PrestationIds { get; set; } = new();
-}
-
-public sealed class BillingLocationDto
-{
- public string Address { get; set; } = string.Empty;
- public double? Latitude { get; set; }
- public double? Longitude { get; set; }
-}
diff --git a/src/Yavsc.Api.Client/Dtos/BillingQuerySummaryDto.cs b/src/Yavsc.Api.Client/Dtos/BillingQuerySummaryDto.cs
deleted file mode 100644
index 0102b691..00000000
--- a/src/Yavsc.Api.Client/Dtos/BillingQuerySummaryDto.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using Yavsc;
-
-namespace Yavsc.Api.Client;
-
-///
-/// Lightweight billing-query projection consumed by PostIt list views.
-/// Extra JSON fields from concrete query types are ignored.
-///
-public sealed class BillingQuerySummaryDto
-{
- public long Id { get; set; }
- public string BillingCode { get; set; } = string.Empty;
- public string ActivityCode { get; set; } = string.Empty;
- public string PerformerId { get; set; } = string.Empty;
- public string ClientId { get; set; } = string.Empty;
- public QueryStatus Status { get; set; }
- public string Description { get; set; } = string.Empty;
- public DateTime? EventDate { get; set; }
- public string Reason { get; set; } = string.Empty;
- public string AdditionalInfo { get; set; } = string.Empty;
- public decimal? Provisional { get; set; }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj
index 6ceaab11..44e4eb27 100644
--- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj
+++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj
@@ -17,7 +17,7 @@
true
1.1.0.0
1.1.0.0
- 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070
+ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0
1.1.0-beta.1
diff --git a/src/Yavsc.Api.Test/ActivityApiControllerTests.cs b/src/Yavsc.Api.Test/ActivityApiControllerTests.cs
deleted file mode 100644
index 0525e349..00000000
--- a/src/Yavsc.Api.Test/ActivityApiControllerTests.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using Microsoft.Extensions.DependencyInjection;
-using Yavsc.Abstract.Workflow;
-using Yavsc.Api.Test.Fixtures;
-using Yavsc.Tests.Shared;
-
-namespace Yavsc.Api.Test;
-
-[Collection("Yavsc Api")]
-public sealed class ActivityApiControllerTests : IClassFixture
-{
- private readonly ApiWebServerFixture _fixture;
-
- public ActivityApiControllerTests(ApiWebServerFixture fixture)
- {
- _fixture = fixture;
- }
-
- private HttpClient NewClient(string subject = "alice", string scope = "api")
- {
- var handler = new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = (_, _, _, _) => true
- };
- var http = new HttpClient(handler)
- {
- BaseAddress = new Uri(_fixture.BaseAddress)
- };
- http.DefaultRequestHeaders.Authorization =
- new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope));
- return http;
- }
-
- [Fact]
- public async Task GetUsers_returns_declared_user_for_exact_activity_code()
- {
- _fixture.ResetAndSeedActivityGraph();
- using var http = NewClient();
-
- var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(payload);
- Assert.Single(payload!);
- Assert.Equal("alice", payload[0].PerformerId);
- Assert.Equal("alice", payload[0].UserName);
- Assert.True(payload[0].HasPerformerProfile);
- Assert.True(payload[0].Active);
- Assert.Equal("dev", payload[0].ActivityCode);
- }
-
- [Fact]
- public async Task GetUsers_returns_user_even_when_performer_inactive()
- {
- _fixture.ResetAndSeedActivityGraph();
- using (var scope = _fixture.Services.CreateScope())
- {
- var db = scope.ServiceProvider.GetRequiredService();
- var performer = db.Performers.Single(p => p.PerformerId == "alice");
- performer.Active = false;
- db.SaveChanges();
- }
-
- using var http = NewClient();
- var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(payload);
- Assert.Single(payload!);
- Assert.Equal("alice", payload[0].PerformerId);
- Assert.False(payload[0].Active);
- }
-
- [Fact]
- public async Task Catalog_and_Performers_are_consistent_for_dev_activity()
- {
- _fixture.ResetAndSeedActivityGraph();
- using var http = NewClient();
-
- var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
-
- var catalog = await catalogResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(catalog);
-
- var dev = catalog!.Single(a => a.Code == "dev");
- Assert.True(dev.PerformerCount > 0);
-
- var performersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode);
-
- var performers = await performersResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(performers);
- Assert.Equal(dev.PerformerCount, performers!.Count);
- Assert.Contains(performers, p => p.PerformerId == "alice");
- }
-
- [Fact]
- public async Task Catalog_does_not_list_activity_without_declaration()
- {
- _fixture.ResetAndSeedActivityGraph();
- using var http = NewClient();
-
- var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
-
- var catalog = await catalogResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(catalog);
- Assert.DoesNotContain(catalog!, a => a.Code == "ghost");
- Assert.Contains(catalog!, a => a.Code == "dev");
- }
-
- [Fact]
- public async Task Catalog_lists_declared_activity_even_when_performer_inactive()
- {
- _fixture.ResetAndSeedActivityGraph();
- using var http = NewClient();
-
- var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
-
- var catalog = await catalogResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(catalog);
- Assert.Contains(catalog!, a => a.Code == "declared-only");
-
- var response = await http.GetAsync("/api/v1/activity/declared-only/users", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(payload);
- Assert.Single(payload!);
- Assert.Equal("bob", payload[0].PerformerId);
- Assert.Equal("bob", payload[0].UserName);
- Assert.True(payload[0].HasPerformerProfile);
- Assert.False(payload[0].Active);
- }
-
- [Fact]
- public async Task Performers_alias_returns_same_payload_as_users_endpoint()
- {
- _fixture.ResetAndSeedActivityGraph();
- using var http = NewClient();
-
- var usersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
- var performersResponse = await http.GetAsync("/api/v1/activity/dev/performers", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, usersResponse.StatusCode);
- Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode);
-
- var usersPayload = await usersResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- var performersPayload = await performersResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
-
- Assert.NotNull(usersPayload);
- Assert.NotNull(performersPayload);
- Assert.Equal(usersPayload!.Count, performersPayload!.Count);
- Assert.Equal(usersPayload[0].PerformerId, performersPayload[0].PerformerId);
- Assert.Equal(usersPayload[0].UserName, performersPayload[0].UserName);
- }
-}
diff --git a/src/Yavsc.Api.Test/ApiCollection.cs b/src/Yavsc.Api.Test/ApiCollection.cs
deleted file mode 100644
index 2c670522..00000000
--- a/src/Yavsc.Api.Test/ApiCollection.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Yavsc.Api.Test;
-
-[CollectionDefinition("Yavsc Api")]
-public sealed class ApiCollection
-{
-}
diff --git a/src/Yavsc.Api.Test/Directory.Packages.props b/src/Yavsc.Api.Test/Directory.Packages.props
deleted file mode 100644
index c2edf9ea..00000000
--- a/src/Yavsc.Api.Test/Directory.Packages.props
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs b/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs
deleted file mode 100644
index ca4ae70f..00000000
--- a/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs
+++ /dev/null
@@ -1,343 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.Data.Sqlite;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.IdentityModel.Tokens;
-using Yavsc.Controllers;
-using Yavsc.Models;
-using Yavsc.Models.Haircut;
-using Yavsc.Models.Relationship;
-using Yavsc.Models.Workflow;
-using Yavsc.Tests.Shared;
-
-namespace Yavsc.Api.Test.Fixtures;
-
-public sealed class ApiWebServerFixture : WebHostFixture
-{
- protected override int HttpsPort => 5104;
-
- private static SqliteConnection? _sharedSqliteConnection;
- private static readonly object _sqliteLock = new();
-
- protected override WebApplication BuildApp(WebApplicationBuilder builder)
- {
- SqliteConnection sharedConnection;
- lock (_sqliteLock)
- {
- if (_sharedSqliteConnection is null)
- {
- _sharedSqliteConnection = new SqliteConnection(
- "Data Source=YavscApiTests;Mode=Memory;Cache=Shared");
- _sharedSqliteConnection.Open();
- }
- sharedConnection = _sharedSqliteConnection;
- }
-
- builder.Services.AddDbContext(opt =>
- opt.UseSqlite(sharedConnection));
-
- builder.Services.AddControllers()
- .AddApplicationPart(typeof(ActivityApiController).Assembly);
-
- builder.Services.AddAuthorization();
-
- builder.Services.AddAuthentication("Bearer")
- .AddJwtBearer("Bearer", options =>
- {
- options.IncludeErrorDetails = true;
- options.MapInboundClaims = false;
- options.TokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuer = true,
- ValidIssuer = TestTokenIssuer.Issuer,
- ValidateAudience = false,
- ValidateLifetime = true,
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = TestTokenIssuer.SigningKey,
- NameClaimType = "sub",
- RoleClaimType = Yavsc.Constants.RoleClaimType,
- };
- });
-
- return builder.Build();
- }
-
- protected override async Task ConfigurePipelineAsync(WebApplication app)
- {
- app.UseDeveloperExceptionPage();
- app.UseRouting();
- app.UseAuthentication();
- app.UseAuthorization();
- app.MapControllers();
-
- using (var scope = app.Services.CreateScope())
- {
- var db = scope.ServiceProvider.GetRequiredService();
- db.Database.EnsureCreated();
- }
-
- await Task.CompletedTask;
- return app;
- }
-
- public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal));
-
- public void ResetAndSeedActivityGraph()
- {
- using var scope = Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService();
-
- db.Database.EnsureDeleted();
- db.Database.EnsureCreated();
-
- var user = new ApplicationUser
- {
- Id = "alice",
- UserName = "alice",
- Email = "alice@example.test",
- EmailConfirmed = true,
- FullName = "Alice",
- };
- db.Users.Add(user);
-
- var location = new Location
- {
- Address = "1 rue du Test",
- Latitude = 48.8566,
- Longitude = 2.3522,
- };
- db.Add(location);
- db.SaveChanges();
-
- var activity = new Activity
- {
- Code = "dev",
- Name = "Dev",
- Hidden = false,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- };
- db.Activities.Add(activity);
-
- db.Activities.Add(new Activity
- {
- Code = "ghost",
- Name = "Ghost",
- Hidden = false,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- });
-
- var performer = new PerformerProfile
- {
- PerformerId = "alice",
- SIREN = "123456789",
- OrganizationAddressId = location.Id,
- AcceptNotifications = true,
- AcceptPublicContact = true,
- Active = true,
- Rate = 5,
- WebSite = "https://alice.dev",
- };
- db.Performers.Add(performer);
-
- db.UserActivities.Add(new UserActivity
- {
- UserId = "alice",
- DoesCode = "dev",
- Weight = 100,
- });
-
- db.Users.Add(new ApplicationUser
- {
- Id = "bob",
- UserName = "bob",
- Email = "bob@example.test",
- EmailConfirmed = true,
- FullName = "Bob",
- });
-
- db.Activities.Add(new Activity
- {
- Code = "declared-only",
- Name = "Declared Only",
- Hidden = false,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- });
-
- db.Performers.Add(new PerformerProfile
- {
- PerformerId = "bob",
- SIREN = "987654321",
- OrganizationAddressId = location.Id,
- AcceptNotifications = false,
- AcceptPublicContact = false,
- Active = false,
- Rate = 0,
- WebSite = "",
- });
-
- db.UserActivities.Add(new UserActivity
- {
- UserId = "bob",
- DoesCode = "declared-only",
- Weight = 10,
- });
-
- db.SaveChanges();
- }
-
- public void ResetAndSeedRdvQueryGraph()
- {
- ResetAndSeedActivityGraph();
-
- using var scope = Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService();
-
- var location = db.Locations.Single(l => l.Address == "1 rue du Test");
-
- db.RdvQueries.Add(new RdvQuery
- {
- ActivityCode = "dev",
- ClientId = "alice",
- PerformerId = "alice",
- Consent = true,
- EventDate = DateTime.UtcNow.AddDays(1),
- Location = location,
- Reason = "Initial rendez-vous",
- Status = Yavsc.QueryStatus.Inserted,
- });
-
- db.SaveChanges();
- }
-
- public void ResetAndSeedHaircutGraph()
- {
- ResetAndSeedActivityGraph();
-
- using var scope = Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService();
-
- var location = db.Locations.Single(l => l.Address == "1 rue du Test");
-
- if (!db.Activities.Any(a => a.Code == "brush"))
- {
- db.Activities.Add(new Activity
- {
- Code = "brush",
- Name = "Brush",
- Hidden = false,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- });
- }
-
- if (!db.Activities.Any(a => a.Code == "mbrush"))
- {
- db.Activities.Add(new Activity
- {
- Code = "mbrush",
- Name = "MBrush",
- Hidden = false,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- });
- }
-
- if (!db.BrusherProfile.Any(p => p.UserId == "alice"))
- {
- db.BrusherProfile.Add(new BrusherProfile
- {
- UserId = "alice",
- ActionDistance = 25,
- WomenLongCutPrice = 50m,
- WomenHalfCutPrice = 40m,
- WomenShortCutPrice = 30m,
- ManCutPrice = 20m,
- KidCutPrice = 15m,
- ShampooPrice = 5m,
- });
- }
-
- var prestation1 = new HairPrestation
- {
- Gender = HairCutGenders.Women,
- Length = HairLength.HalfLong,
- Cut = true,
- Shampoo = true,
- Dressing = HairDressings.Brushing,
- Tech = HairTechnos.NoTech,
- Cares = false,
- Taints = new List(),
- };
- var prestation2 = new HairPrestation
- {
- Gender = HairCutGenders.Man,
- Length = HairLength.Short,
- Cut = true,
- Shampoo = false,
- Dressing = HairDressings.Brushing,
- Tech = HairTechnos.NoTech,
- Cares = false,
- Taints = new List(),
- };
-
- db.HairPrestation.AddRange(prestation1, prestation2);
- db.SaveChanges();
-
- db.HairCutQueries.Add(new HairCutQuery
- {
- ActivityCode = "brush",
- ClientId = "alice",
- PerformerId = "alice",
- Consent = true,
- EventDate = DateTime.UtcNow.AddDays(3),
- Location = location,
- PrestationId = prestation1.Id,
- Prestation = prestation1,
- AdditionalInfo = "Coupe test",
- Status = Yavsc.QueryStatus.Inserted,
- Description = "Haircut seed",
- });
-
- db.HairMultiCutQueries.Add(new HairMultiCutQuery
- {
- ActivityCode = "mbrush",
- ClientId = "alice",
- PerformerId = "alice",
- Consent = true,
- EventDate = DateTime.UtcNow.AddDays(4),
- Location = location,
- Prestations = new List
- {
- new() { PrestationId = prestation1.Id, Prestation = prestation1 },
- new() { PrestationId = prestation2.Id, Prestation = prestation2 },
- },
- Status = Yavsc.QueryStatus.Inserted,
- });
-
- db.SaveChanges();
- }
-
- public override void Dispose()
- {
- try
- {
- base.Dispose();
- }
- finally
- {
- lock (_sqliteLock)
- {
- if (_sharedSqliteConnection is not null)
- {
- _sharedSqliteConnection.Close();
- _sharedSqliteConnection.Dispose();
- _sharedSqliteConnection = null;
- }
- }
- }
- }
-}
diff --git a/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs b/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs
deleted file mode 100644
index b4b7e3e4..00000000
--- a/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.EntityFrameworkCore;
-using Yavsc.Api.Test.Fixtures;
-using Yavsc.Models;
-using Yavsc.Models.Haircut;
-using Yavsc.Models.Relationship;
-using Yavsc.Tests.Shared;
-
-namespace Yavsc.Api.Test;
-
-[Collection("Yavsc Api")]
-public sealed class HairCutQueryApiControllerTests : IClassFixture
-{
- private readonly ApiWebServerFixture _fixture;
-
- public HairCutQueryApiControllerTests(ApiWebServerFixture fixture)
- {
- _fixture = fixture;
- }
-
- private HttpClient NewClient(string subject = "alice", string scope = "api")
- {
- var handler = new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = (_, _, _, _) => true
- };
-
- var http = new HttpClient(handler)
- {
- BaseAddress = new Uri(_fixture.BaseAddress)
- };
- http.DefaultRequestHeaders.Authorization =
- new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope));
- return http;
- }
-
- [Fact]
- public async Task Billing_brush_route_supports_crud()
- {
- _fixture.ResetAndSeedHaircutGraph();
- using var http = NewClient();
-
- var prestationId = await GetPrestationIdAsync();
-
- var createPayload = new HairCutQuery
- {
- ActivityCode = "brush",
- PerformerId = "alice",
- Consent = true,
- EventDate = DateTime.UtcNow.AddDays(5),
- Location = new Location
- {
- Address = "2 rue de la Coupe",
- Latitude = 48.8570,
- Longitude = 2.3525,
- },
- PrestationId = prestationId,
- AdditionalInfo = "Brushing test",
- Status = QueryStatus.Inserted,
- Description = "Haircut create",
- };
-
- var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Brush", createPayload, TestContext.Current.CancellationToken);
- if (createResponse.StatusCode != HttpStatusCode.Created)
- {
- var body = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
- Assert.Fail($"Unexpected status {createResponse.StatusCode}: {body}");
- }
-
- var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
- Assert.NotNull(created);
- Assert.NotEqual(0, created!.Id);
- Assert.Equal("alice", created.ClientId);
-
- var getResponse = await http.GetAsync($"/api/v1/billing/Brush/{created.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
-
- var fetched = await getResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
- Assert.NotNull(fetched);
- Assert.Equal(created.Id, fetched!.Id);
- Assert.Equal("Brushing test", fetched.AdditionalInfo);
-
- fetched.AdditionalInfo = "Brushing modifié";
- var putResponse = await http.PutAsJsonAsync($"/api/v1/billing/Brush/{fetched.Id}", fetched, TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode);
-
- var deleteResponse = await http.DeleteAsync($"/api/v1/billing/Brush/{fetched.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode);
-
- var missingResponse = await http.GetAsync($"/api/v1/billing/Brush/{fetched.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode);
- }
-
- [Fact]
- public async Task Billing_brush_route_exposes_prestation_catalog()
- {
- _fixture.ResetAndSeedHaircutGraph();
- using var http = NewClient();
-
- var response = await http.GetAsync("/api/v1/billing/Brush/prestations", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var catalog = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(catalog);
- Assert.NotEmpty(catalog!);
- Assert.All(catalog!, item => Assert.False(string.IsNullOrWhiteSpace(item.Title)));
- Assert.Contains(catalog!, item => item.Title == "Femme · Cheveux mi-longs"
- && item.Details == "Coupe · Brushing · Aucune technique spécifique · Shampoing · Sans soins");
- }
-
- private async Task GetPrestationIdAsync()
- {
- using var scope = _fixture.Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService();
- return await db.HairPrestation.Select(p => p.Id).FirstAsync(TestContext.Current.CancellationToken);
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs b/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs
deleted file mode 100644
index 8d940b8b..00000000
--- a/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Yavsc.Api.Test.Fixtures;
-using Yavsc.Models;
-using Yavsc.Models.Haircut;
-using Yavsc.Models.Relationship;
-using Yavsc.Tests.Shared;
-
-namespace Yavsc.Api.Test;
-
-[Collection("Yavsc Api")]
-public sealed class HairMultiCutQueryApiControllerTests : IClassFixture
-{
- private readonly ApiWebServerFixture _fixture;
-
- public HairMultiCutQueryApiControllerTests(ApiWebServerFixture fixture)
- {
- _fixture = fixture;
- }
-
- private HttpClient NewClient(string subject = "alice", string scope = "api")
- {
- var handler = new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = (_, _, _, _) => true
- };
-
- var http = new HttpClient(handler)
- {
- BaseAddress = new Uri(_fixture.BaseAddress)
- };
- http.DefaultRequestHeaders.Authorization =
- new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope));
- return http;
- }
-
- [Fact]
- public async Task Billing_mbrush_route_supports_crud()
- {
- _fixture.ResetAndSeedHaircutGraph();
- using var http = NewClient();
-
- var prestationIds = await GetPrestationIdsAsync();
-
- var createPayload = new HairMultiCutQuery
- {
- ActivityCode = "mbrush",
- PerformerId = "alice",
- Consent = true,
- EventDate = DateTime.UtcNow.AddDays(6),
- Location = new Location
- {
- Address = "3 rue du Groupe",
- Latitude = 48.8580,
- Longitude = 2.3530,
- },
- Prestations = prestationIds.Select(id => new HairPrestationCollectionItem { PrestationId = id }).ToList(),
- Status = QueryStatus.Inserted,
- };
-
- var createResponse = await http.PostAsJsonAsync("/api/v1/billing/MBrush", createPayload, TestContext.Current.CancellationToken);
- if (createResponse.StatusCode != HttpStatusCode.Created)
- {
- var body = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
- Assert.Fail($"Unexpected status {createResponse.StatusCode}: {body}");
- }
-
- var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
- Assert.NotNull(created);
- Assert.NotEqual(0, created!.Id);
- Assert.Equal("alice", created.ClientId);
- Assert.Equal(2, created.Prestations.Count);
-
- var getResponse = await http.GetAsync($"/api/v1/billing/MBrush/{created.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
-
- var fetched = await getResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
- Assert.NotNull(fetched);
- Assert.Equal(created.Id, fetched!.Id);
- Assert.Equal(2, fetched.Prestations.Count);
-
- fetched.Status = QueryStatus.Accepted;
- var putResponse = await http.PutAsJsonAsync($"/api/v1/billing/MBrush/{fetched.Id}", fetched, TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode);
-
- var deleteResponse = await http.DeleteAsync($"/api/v1/billing/MBrush/{fetched.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode);
-
- var missingResponse = await http.GetAsync($"/api/v1/billing/MBrush/{fetched.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode);
- }
-
- [Fact]
- public async Task Billing_mbrush_route_exposes_prestation_catalog()
- {
- _fixture.ResetAndSeedHaircutGraph();
- using var http = NewClient();
-
- var response = await http.GetAsync("/api/v1/billing/MBrush/prestations", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var catalog = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken);
- Assert.NotNull(catalog);
- Assert.NotEmpty(catalog!);
- Assert.All(catalog!, item => Assert.False(string.IsNullOrWhiteSpace(item.Details)));
- Assert.Contains(catalog!, item => item.Title == "Femme · Cheveux mi-longs"
- && item.Details == "Coupe · Brushing · Aucune technique spécifique · Shampoing · Sans soins");
- }
-
- private async Task> GetPrestationIdsAsync()
- {
- using var scope = _fixture.Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService();
- return await db.HairPrestation
- .OrderBy(p => p.Id)
- .Select(p => p.Id)
- .Take(2)
- .ToListAsync(TestContext.Current.CancellationToken);
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Api.Test/HomeControllerTests.cs b/src/Yavsc.Api.Test/HomeControllerTests.cs
deleted file mode 100644
index a5706dc5..00000000
--- a/src/Yavsc.Api.Test/HomeControllerTests.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Security.Claims;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Extensions.Options;
-using Yavsc.Api.Test.Fixtures;
-using Yavsc.Controllers;
-using Yavsc.Models.Workflow;
-
-namespace Yavsc.Api.Test;
-
-[Collection("Yavsc Api")]
-public sealed class HomeControllerTests : IClassFixture
-{
- private readonly ApiWebServerFixture _fixture;
-
- public HomeControllerTests(ApiWebServerFixture fixture)
- {
- _fixture = fixture;
- }
-
- [Fact]
- public async Task Index_does_not_list_activity_without_declaration()
- {
- _fixture.ResetAndSeedActivityGraph();
-
- using var scope = _fixture.Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService();
-
- var controller = new HomeController(
- NullLogger.Instance,
- localizer: null!,
- context: db,
- settingsOptions: Options.Create(new SiteSettings()),
- env: new TestEnvironment());
-
- controller.ControllerContext = new ControllerContext
- {
- HttpContext = new DefaultHttpContext
- {
- User = new ClaimsPrincipal(new ClaimsIdentity(new[]
- {
- new Claim("sub", "alice"),
- new Claim(ClaimTypes.NameIdentifier, "alice")
- }, "Bearer"))
- }
- };
-
- var result = await controller.Index(id: null);
- var view = Assert.IsType(result);
- var model = Assert.IsAssignableFrom>(view.Model);
-
- Assert.Contains(model, a => a.Code == "dev");
- Assert.DoesNotContain(model, a => a.Code == "ghost");
- }
-
- private sealed class TestEnvironment : IWebHostEnvironment
- {
- public string ApplicationName { get; set; } = "Yavsc.Api.Test";
- public IFileProvider WebRootFileProvider { get; set; } = null!;
- public string WebRootPath { get; set; } = string.Empty;
- public string EnvironmentName { get; set; } = "Development";
- public string ContentRootPath { get; set; } = string.Empty;
- public IFileProvider ContentRootFileProvider { get; set; } = null!;
- }
-}
diff --git a/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs b/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs
deleted file mode 100644
index 8fe7e959..00000000
--- a/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using Yavsc;
-using Yavsc.Api.Test.Fixtures;
-using Yavsc.Models.Workflow;
-using Yavsc.Tests.Shared;
-
-namespace Yavsc.Api.Test;
-
-[Collection("Yavsc Api")]
-public sealed class RdvQueryApiControllerTests : IClassFixture
-{
- private readonly ApiWebServerFixture _fixture;
-
- public RdvQueryApiControllerTests(ApiWebServerFixture fixture)
- {
- _fixture = fixture;
- }
-
- private HttpClient NewClient(string subject = "alice", string scope = "api")
- {
- var handler = new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = (_, _, _, _) => true
- };
-
- var http = new HttpClient(handler)
- {
- BaseAddress = new Uri(_fixture.BaseAddress)
- };
- http.DefaultRequestHeaders.Authorization =
- new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope));
- return http;
- }
-
- [Fact]
- public async Task Billing_rdv_route_supports_crud()
- {
- _fixture.ResetAndSeedRdvQueryGraph();
- using var http = NewClient();
-
- var createPayload = new RdvQuery
- {
- ActivityCode = "dev",
- PerformerId = "alice",
- Consent = true,
- EventDate = DateTime.UtcNow.AddDays(2),
- Location = new Yavsc.Models.Relationship.Location
- {
- Address = "1 rue du Test",
- Latitude = 48.8566,
- Longitude = 2.3522,
- },
- Reason = "Second rendez-vous",
- Status = QueryStatus.Inserted,
- };
-
- var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode);
-
- var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
- Assert.NotNull(created);
- Assert.NotEqual(0, created!.Id);
- Assert.Equal("alice", created.ClientId);
-
- var getResponse = await http.GetAsync($"/api/v1/billing/Rdv/{created.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
-
- var fetched = await getResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken);
- Assert.NotNull(fetched);
- Assert.Equal(created.Id, fetched!.Id);
- Assert.Equal("Second rendez-vous", fetched.Reason);
-
- fetched.Reason = "Rendez-vous modifié";
- var putResponse = await http.PutAsJsonAsync($"/api/v1/billing/Rdv/{fetched.Id}", fetched, TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode);
-
- var deleteResponse = await http.DeleteAsync($"/api/v1/billing/Rdv/{fetched.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode);
-
- var missingResponse = await http.GetAsync($"/api/v1/billing/Rdv/{fetched.Id}", TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode);
- }
-}
diff --git a/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj b/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj
deleted file mode 100644
index 760f5ce8..00000000
--- a/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
- net10.0
- enable
- enable
- false
- Yavsc.Api.Test
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs
index a8d28806..6d91ba82 100644
--- a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
-using Yavsc.Abstract.Workflow;
using Yavsc.Server.Helpers;
using Yavsc.Models;
using Yavsc.Models.Workflow;
@@ -9,7 +8,6 @@ using Yavsc.Models.Workflow;
namespace Yavsc.Controllers
{
- [Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/activity")]
public class ActivityApiController : Controller
@@ -28,119 +26,6 @@ namespace Yavsc.Controllers
return _context.Activities.Include(a=>a.Forms).Where( a => !a.Hidden );
}
- [HttpGet("catalog")]
- public async Task>> GetCatalog(
- CancellationToken cancellationToken,
- [FromQuery] string parentCode = null)
- {
- var activities = await _context.Activities
- .AsNoTracking()
- .Include(a => a.Forms)
- .Include(a => a.Children)
- .ThenInclude(c => c.Forms)
- .Where(a => !a.Hidden && a.ParentCode == parentCode)
- .OrderByDescending(a => a.Rate)
- .ToListAsync(cancellationToken);
-
- var codes = activities
- .Select(a => a.Code)
- .Concat(activities.SelectMany(a => (a.Children ?? new List())
- .Where(c => !c.Hidden)
- .Select(c => c.Code)))
- .Where(c => !string.IsNullOrWhiteSpace(c))
- .Distinct()
- .ToArray();
-
- var performerCounts = await (
- from ua in _context.UserActivities.AsNoTracking()
- where !string.IsNullOrWhiteSpace(ua.DoesCode) && codes.Contains(ua.DoesCode)
- group ua by ua.DoesCode into g
- select new
- {
- Code = g.Key,
- Count = g.Select(x => x.UserId).Distinct().Count()
- })
- .ToDictionaryAsync(x => x.Code, x => x.Count, cancellationToken);
-
- var filteredActivities = activities
- .Where(a =>
- (performerCounts.TryGetValue(a.Code, out var ownCount) && ownCount > 0)
- || (a.Children ?? new List())
- .Where(c => !c.Hidden)
- .Any(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0))
- .ToList();
-
- return Ok(filteredActivities.Select(a => ToBrowseItem(a, performerCounts)).ToList());
- }
-
- [HttpGet("{id}/users")]
- public async Task>> GetUsers(
- [FromRoute] string id,
- CancellationToken cancellationToken)
- {
- if (string.IsNullOrWhiteSpace(id))
- {
- return BadRequest("Activity code is required.");
- }
-
- var activity = await _context.Activities
- .AsNoTracking()
- .SingleOrDefaultAsync(a => a.Code == id, cancellationToken);
- if (activity is null)
- {
- return NotFound();
- }
-
- var users = await QueryDeclaredUsersAsync(id, activity.Name, cancellationToken);
-
- return Ok(users);
- }
-
- [HttpGet("{id}/performers")]
- public Task>> GetPerformers(
- [FromRoute] string id,
- CancellationToken cancellationToken)
- {
- // Backward-compatible alias kept for existing clients.
- return GetUsers(id, cancellationToken);
- }
-
- private Task> QueryDeclaredUsersAsync(
- string activityCode,
- string activityName,
- CancellationToken cancellationToken)
- {
- return (
- from ua in _context.UserActivities.AsNoTracking()
- join u in _context.ApplicationUser.AsNoTracking() on ua.UserId equals u.Id into users
- from user in users.DefaultIfEmpty()
- join p in _context.Performers.AsNoTracking() on ua.UserId equals p.PerformerId into performerProfiles
- from performer in performerProfiles.DefaultIfEmpty()
- where ua.DoesCode == activityCode
- orderby user != null ? user.UserName : ua.UserId
- select new PerformerActivity
- {
- PerformerId = ua.UserId,
- HasPerformerProfile = performer != null,
- UserName = user != null ? (user.UserName ?? string.Empty) : string.Empty,
- Active = performer != null && performer.Active,
- AcceptNotifications = performer != null && performer.AcceptNotifications,
- AcceptPublicContact = performer != null && performer.AcceptPublicContact,
- WebSite = performer != null ? (performer.WebSite ?? string.Empty) : string.Empty,
- ActivityCode = activityCode,
- ActivityName = activityName,
- SettingsClassName = _context.Activities
- .Where(a => a.Code == activityCode)
- .Select(a => a.SettingsClassName)
- .FirstOrDefault() ?? string.Empty,
- ExtraActivityCount = _context.UserActivities
- .Where(x => x.UserId == ua.UserId && x.DoesCode != activityCode)
- .Count()
- })
- .Distinct()
- .ToListAsync(cancellationToken);
- }
-
// GET: api/ActivityApi/5
[HttpGet("{id}", Name = "GetActivity")]
public async Task GetActivity([FromRoute] string id)
@@ -259,52 +144,5 @@ namespace Yavsc.Controllers
{
return _context.Activities.Count(e => e.Code == id) > 0;
}
-
- private static ActivityInfo ToBrowseItem(
- Activity activity,
- IReadOnlyDictionary performerCounts)
- {
- return new ActivityInfo
- {
- Code = activity.Code,
- Name = activity.Name,
- ParentCode = activity.ParentCode,
- Description = activity.Description,
- Photo = activity.Photo,
- Rate = activity.Rate,
- PerformerCount = performerCounts.TryGetValue(activity.Code, out var count) ? count : 0,
- Forms = (activity.Forms ?? Enumerable.Empty())
- .Select(f => new CommandFormSummary
- {
- Id = f.Id,
- ActionName = f.ActionName,
- Title = f.Title,
- })
- .ToList(),
- Children = (activity.Children ?? Enumerable.Empty())
- .Where(c => !c.Hidden)
- .Where(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0)
- .OrderByDescending(c => c.Rate)
- .Select(c => new ActivityInfo
- {
- Code = c.Code,
- Name = c.Name,
- ParentCode = c.ParentCode,
- Description = c.Description,
- Photo = c.Photo,
- Rate = c.Rate,
- PerformerCount = performerCounts.TryGetValue(c.Code, out var childCount) ? childCount : 0,
- Forms = (c.Forms ?? Enumerable.Empty())
- .Select(f => new CommandFormSummary
- {
- Id = f.Id,
- ActionName = f.ActionName,
- Title = f.Title,
- })
- .ToList(),
- })
- .ToList(),
- };
- }
}
}
diff --git a/src/Yavsc.Api/Controllers/Business/BillingController.cs b/src/Yavsc.Api/Controllers/Business/BillingController.cs
index 197e06b1..72180fc1 100644
--- a/src/Yavsc.Api/Controllers/Business/BillingController.cs
+++ b/src/Yavsc.Api/Controllers/Business/BillingController.cs
@@ -19,7 +19,6 @@ namespace Yavsc.ApiControllers
using Yavsc.ViewModels.Auth;
using Yavsc.Server.Helpers;
- [Authorize]
[Route(Constants.APIPrefix + "/bill"), Authorize]
public class BillingController : Controller
{
diff --git a/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs
index 293cca9a..0d7112ec 100644
--- a/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs
@@ -12,7 +12,6 @@ namespace Yavsc.Controllers
using Microsoft.EntityFrameworkCore;
using Yavsc.Server.Helpers;
- [Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/bookquery"), Authorize("Performer")]
public class BookQueryApiController : Controller
diff --git a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs
index b01819f2..891909f0 100644
--- a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs
@@ -9,7 +9,6 @@ using Yavsc.Server.Helpers;
namespace Yavsc.Controllers
{
- [Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/estimate"), Authorize]
public class EstimateApiController : Controller
diff --git a/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs b/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs
index 748f60cc..3fc91365 100644
--- a/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs
@@ -1,5 +1,4 @@
using System.Security.Claims;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc.Models;
@@ -8,7 +7,6 @@ using Yavsc.Server.Helpers;
namespace Yavsc.Controllers
{
- [Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/EstimateTemplatesApi")]
public class EstimateTemplatesApiController : Controller
diff --git a/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs b/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs
index a4ecbf2f..91d57d9f 100644
--- a/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs
@@ -1,4 +1,3 @@
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Yavsc.Helpers;
using Yavsc.Models;
@@ -7,7 +6,6 @@ using Yavsc.ViewModels.FrontOffice;
namespace Yavsc.ApiControllers
{
- [Authorize]
[Route(Constants.APIPrefix + "/front")]
public class FrontOfficeApiController : Controller
{
diff --git a/src/Yavsc.Api/Controllers/Business/HairCutQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/HairCutQueryApiController.cs
deleted file mode 100644
index 6c7c1ba6..00000000
--- a/src/Yavsc.Api/Controllers/Business/HairCutQueryApiController.cs
+++ /dev/null
@@ -1,234 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using Yavsc.Models;
-using Yavsc.Models.Billing;
-using Yavsc.Models.Haircut;
-using Yavsc.Models.Relationship;
-using Yavsc.Server.Helpers;
-
-namespace Yavsc.Controllers;
-
-[Authorize]
-[Produces("application/json")]
-[Route(Constants.APIPrefix + "/billing/" + BillingCodes.Brush)]
-public class HairCutQueryApiController : Controller
-{
- private readonly ApplicationDbContext _context;
-
- public HairCutQueryApiController(ApplicationDbContext context)
- {
- _context = context;
- }
-
- [HttpGet]
- public async Task GetQueries(CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var queries = await _context.HairCutQueries
- .AsNoTracking()
- .Include(q => q.Prestation)
- .Include(q => q.Location)
- .Include(q => q.Client)
- .Include(q => q.PerformerProfile)
- .Where(q => q.ClientId == uid || q.PerformerId == uid)
- .OrderByDescending(q => q.Id)
- .ToListAsync(cancellationToken);
-
- return Ok(queries);
- }
-
- [HttpGet("prestations")]
- public async Task GetPrestations(CancellationToken cancellationToken)
- {
- var prestations = await _context.HairPrestation
- .AsNoTracking()
- .OrderBy(p => p.Gender)
- .ThenBy(p => p.Length)
- .ThenBy(p => p.Tech)
- .Select(p => ToDto(p))
- .ToListAsync(cancellationToken);
-
- return Ok(prestations);
- }
-
- [HttpGet("{id}", Name = "GetBillingHairCutQuery")]
- public async Task GetQuery([FromRoute] long id, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var query = await _context.HairCutQueries
- .Include(q => q.Prestation)
- .Include(q => q.Location)
- .Include(q => q.Client)
- .Include(q => q.PerformerProfile)
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (query is null)
- {
- return NotFound();
- }
-
- if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- return Ok(query);
- }
-
- [HttpPost]
- public async Task PostQuery([FromBody] HairCutQuery query, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
- if (string.IsNullOrWhiteSpace(query.ClientId))
- {
- query.ClientId = uid;
- }
-
- ModelState.Remove("ClientId");
- ModelState.Remove("Prestation");
-
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- ModelState.AddModelError("ClientId", "You can only create your own HairCutQuery");
- return BadRequest(ModelState);
- }
-
- query.Prestation = await _context.HairPrestation
- .SingleOrDefaultAsync(p => p.Id == query.PrestationId, cancellationToken);
- if (query.Prestation is null)
- {
- ModelState.AddModelError("PrestationId", "Unknown hair prestation.");
- return BadRequest(ModelState);
- }
-
- if (!ModelState.IsValid)
- {
- return BadRequest(ModelState);
- }
-
- query.Location = await ResolveLocationAsync(query.Location, cancellationToken);
-
- _context.HairCutQueries.Add(query);
-
- try
- {
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
- }
- catch (DbUpdateException)
- {
- if (QueryExists(query.Id))
- {
- return Conflict();
- }
-
- throw;
- }
-
- return CreatedAtRoute("GetBillingHairCutQuery", new { id = query.Id }, query);
- }
-
- [HttpPut("{id}")]
- public async Task PutQuery([FromRoute] long id, [FromBody] HairCutQuery query, CancellationToken cancellationToken)
- {
- var existing = await _context.HairCutQueries
- .Include(q => q.Location)
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (existing is null)
- {
- return NotFound();
- }
-
- var uid = User.GetUserId();
- if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- var prestation = await _context.HairPrestation
- .SingleOrDefaultAsync(p => p.Id == query.PrestationId, cancellationToken);
- if (prestation is null)
- {
- ModelState.AddModelError("PrestationId", "Unknown hair prestation.");
- return BadRequest(ModelState);
- }
-
- existing.ActivityCode = query.ActivityCode;
- existing.PerformerId = query.PerformerId;
- existing.Consent = query.Consent;
- existing.EventDate = query.EventDate;
- existing.AdditionalInfo = query.AdditionalInfo;
- existing.Status = query.Status;
- existing.Provisional = query.Provisional;
- existing.PrestationId = prestation.Id;
- existing.Prestation = prestation;
- existing.Location = await ResolveLocationAsync(query.Location, cancellationToken);
-
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
- return NoContent();
- }
-
- [HttpDelete("{id}")]
- public async Task DeleteQuery([FromRoute] long id, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var query = await _context.HairCutQueries
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (query is null)
- {
- return NotFound();
- }
-
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- _context.HairCutQueries.Remove(query);
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
-
- return Ok(query);
- }
-
- private async Task ResolveLocationAsync(Location candidate, CancellationToken cancellationToken)
- {
- if (candidate is null)
- {
- return null;
- }
-
- var existingLocation = await _context.Locations.FirstOrDefaultAsync(
- x => x.Address == candidate.Address
- && x.Longitude == candidate.Longitude
- && x.Latitude == candidate.Latitude,
- cancellationToken);
-
- if (existingLocation is not null)
- {
- return existingLocation;
- }
-
- _context.Attach(candidate);
- return candidate;
- }
-
- private bool QueryExists(long id)
- {
- return _context.HairCutQueries.Any(e => e.Id == id);
- }
-
- private static HairPrestationDto ToDto(HairPrestation prestation)
- {
- return new HairPrestationDto
- {
- Id = prestation.Id,
- Title = prestation.GetDisplayTitle(),
- Details = prestation.GetDisplayDetails(),
- };
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Api/Controllers/Business/HairMultiCutQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/HairMultiCutQueryApiController.cs
deleted file mode 100644
index f850b3f4..00000000
--- a/src/Yavsc.Api/Controllers/Business/HairMultiCutQueryApiController.cs
+++ /dev/null
@@ -1,286 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using Yavsc.Models;
-using Yavsc.Models.Billing;
-using Yavsc.Models.Haircut;
-using Yavsc.Models.Relationship;
-using Yavsc.Server.Helpers;
-
-namespace Yavsc.Controllers;
-
-[Authorize]
-[Produces("application/json")]
-[Route(Constants.APIPrefix + "/billing/" + BillingCodes.MBrush)]
-public class HairMultiCutQueryApiController : Controller
-{
- private readonly ApplicationDbContext _context;
-
- public HairMultiCutQueryApiController(ApplicationDbContext context)
- {
- _context = context;
- }
-
- [HttpGet]
- public async Task GetQueries(CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var queries = await _context.HairMultiCutQueries
- .AsNoTracking()
- .Include(q => q.Prestations)
- .ThenInclude(p => p.Prestation)
- .Include(q => q.Location)
- .Include(q => q.Client)
- .Include(q => q.PerformerProfile)
- .Where(q => q.ClientId == uid || q.PerformerId == uid)
- .OrderByDescending(q => q.Id)
- .ToListAsync(cancellationToken);
-
- return Ok(queries.Select(SanitizeForResponse).ToList());
- }
-
- [HttpGet("prestations")]
- public async Task GetPrestations(CancellationToken cancellationToken)
- {
- var prestations = await _context.HairPrestation
- .AsNoTracking()
- .OrderBy(p => p.Gender)
- .ThenBy(p => p.Length)
- .ThenBy(p => p.Tech)
- .Select(p => new HairPrestationDto
- {
- Id = p.Id,
- Title = p.GetDisplayTitle(),
- Details = p.GetDisplayDetails()
- })
- .ToListAsync(cancellationToken);
-
- return Ok(prestations);
- }
-
- [HttpGet("{id}", Name = "GetBillingHairMultiCutQuery")]
- public async Task GetQuery([FromRoute] long id, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var query = await _context.HairMultiCutQueries
- .Include(q => q.Prestations)
- .ThenInclude(p => p.Prestation)
- .Include(q => q.Location)
- .Include(q => q.Client)
- .Include(q => q.PerformerProfile)
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (query is null)
- {
- return NotFound();
- }
-
- if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- return Ok(SanitizeForResponse(query));
- }
-
- [HttpPost]
- public async Task PostQuery([FromBody] HairMultiCutQuery query, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
- if (string.IsNullOrWhiteSpace(query.ClientId))
- {
- query.ClientId = uid;
- }
-
- ModelState.Remove("ClientId");
-
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- ModelState.AddModelError("ClientId", "You can only create your own HairMultiCutQuery");
- return BadRequest(ModelState);
- }
-
- if (query.Prestations is null || query.Prestations.Count == 0)
- {
- ModelState.AddModelError("Prestations", "At least one hair prestation is required.");
- return BadRequest(ModelState);
- }
-
- var prestationItems = await ResolvePrestationsAsync(query.Prestations, cancellationToken);
- if (prestationItems is null)
- {
- ModelState.AddModelError("Prestations", "One or more hair prestations are unknown.");
- return BadRequest(ModelState);
- }
-
- if (!ModelState.IsValid)
- {
- return BadRequest(ModelState);
- }
-
- query.Prestations = prestationItems;
- query.Location = await ResolveLocationAsync(query.Location, cancellationToken);
- _context.HairMultiCutQueries.Add(query);
-
- try
- {
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
- }
- catch (DbUpdateException)
- {
- if (QueryExists(query.Id))
- {
- return Conflict();
- }
-
- throw;
- }
-
- return CreatedAtRoute("GetBillingHairMultiCutQuery", new { id = query.Id }, SanitizeForResponse(query));
- }
-
- [HttpPut("{id}")]
- public async Task PutQuery([FromRoute] long id, [FromBody] HairMultiCutQuery query, CancellationToken cancellationToken)
- {
- var existing = await _context.HairMultiCutQueries
- .Include(q => q.Prestations)
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (existing is null)
- {
- return NotFound();
- }
-
- var uid = User.GetUserId();
- if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- if (query.Prestations is null || query.Prestations.Count == 0)
- {
- ModelState.AddModelError("Prestations", "At least one hair prestation is required.");
- return BadRequest(ModelState);
- }
-
- var prestationItems = await ResolvePrestationsAsync(query.Prestations, cancellationToken);
- if (prestationItems is null)
- {
- ModelState.AddModelError("Prestations", "One or more hair prestations are unknown.");
- return BadRequest(ModelState);
- }
-
- _context.RemoveRange(existing.Prestations);
- existing.ActivityCode = query.ActivityCode;
- existing.PerformerId = query.PerformerId;
- existing.Consent = query.Consent;
- existing.EventDate = query.EventDate;
- existing.Status = query.Status;
- existing.Provisional = query.Provisional;
- existing.Prestations = prestationItems;
- existing.Location = await ResolveLocationAsync(query.Location, cancellationToken);
-
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
- return NoContent();
- }
-
- [HttpDelete("{id}")]
- public async Task DeleteQuery([FromRoute] long id, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var query = await _context.HairMultiCutQueries
- .Include(q => q.Prestations)
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (query is null)
- {
- return NotFound();
- }
-
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- _context.RemoveRange(query.Prestations);
- _context.HairMultiCutQueries.Remove(query);
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
-
- return Ok(SanitizeForResponse(query));
- }
-
- private async Task> ResolvePrestationsAsync(
- IEnumerable requestedItems,
- CancellationToken cancellationToken)
- {
- var ids = requestedItems
- .Select(x => x.PrestationId)
- .Where(x => x > 0)
- .ToArray();
-
- if (ids.Length == 0)
- {
- return null;
- }
-
- var prestations = await _context.HairPrestation
- .Where(p => ids.Contains(p.Id))
- .ToDictionaryAsync(p => p.Id, cancellationToken);
-
- if (prestations.Count != ids.Distinct().Count())
- {
- return null;
- }
-
- return requestedItems
- .Select(item => new HairPrestationCollectionItem
- {
- PrestationId = item.PrestationId,
- Prestation = prestations[item.PrestationId],
- })
- .ToList();
- }
-
- private async Task ResolveLocationAsync(Location candidate, CancellationToken cancellationToken)
- {
- if (candidate is null)
- {
- return null;
- }
-
- var existingLocation = await _context.Locations.FirstOrDefaultAsync(
- x => x.Address == candidate.Address
- && x.Longitude == candidate.Longitude
- && x.Latitude == candidate.Latitude,
- cancellationToken);
-
- if (existingLocation is not null)
- {
- return existingLocation;
- }
-
- _context.Attach(candidate);
- return candidate;
- }
-
- private bool QueryExists(long id)
- {
- return _context.HairMultiCutQueries.Any(e => e.Id == id);
- }
-
- private static HairMultiCutQuery SanitizeForResponse(HairMultiCutQuery query)
- {
- if (query.Prestations is not null)
- {
- foreach (var item in query.Prestations)
- {
- item.Query = null;
- }
- }
-
- return query;
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs b/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs
index 930eea91..5f769e4e 100644
--- a/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs
@@ -1,4 +1,3 @@
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
@@ -7,7 +6,6 @@ using Yavsc.Models;
namespace Yavsc.ApiControllers
{
- [Authorize]
[Route(Constants.APIPrefix + "/payment")]
public class PaymentApiController : Controller
{
diff --git a/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs b/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs
index 36586adb..2ad1ded5 100644
--- a/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs
@@ -10,7 +10,6 @@ namespace Yavsc.Controllers
using Yavsc.Helpers;
using Yavsc.Services;
- [Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/performers")]
public class PerformersApiController : Controller
diff --git a/src/Yavsc.Api/Controllers/Business/ProductApiController.cs b/src/Yavsc.Api/Controllers/Business/ProductApiController.cs
index ae3820dc..d9792839 100644
--- a/src/Yavsc.Api/Controllers/Business/ProductApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/ProductApiController.cs
@@ -7,7 +7,6 @@ using Yavsc.Server.Helpers;
namespace Yavsc.Controllers
{
- [Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/ProductApi")]
public class ProductApiController : Controller
diff --git a/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs
deleted file mode 100644
index fcfac2b2..00000000
--- a/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using Yavsc.Models;
-using Yavsc.Models.Billing;
-using Yavsc.Models.Workflow;
-using Yavsc.Server.Helpers;
-
-namespace Yavsc.Controllers;
-
-[Authorize]
-[Produces("application/json")]
-[Route(Constants.APIPrefix + "/billing/" + BillingCodes.Rdv)]
-public class RdvQueryApiController : Controller
-{
- private readonly ApplicationDbContext _context;
-
- public RdvQueryApiController(ApplicationDbContext context)
- {
- _context = context;
- }
-
- [HttpGet]
- public async Task GetQueries(CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var queries = await _context.RdvQueries
- .AsNoTracking()
- .Include(q => q.Location)
- .Include(q => q.Client)
- .Include(q => q.PerformerProfile)
- .Where(q => q.ClientId == uid || q.PerformerId == uid)
- .OrderByDescending(q => q.Id)
- .ToListAsync(cancellationToken);
-
- return Ok(queries);
- }
-
- [HttpGet("{id}", Name = "GetRdvQuery")]
- public async Task GetQuery([FromRoute] long id, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var query = await _context.RdvQueries
- .Include(q => q.Location)
- .Include(q => q.Client)
- .Include(q => q.PerformerProfile)
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (query is null)
- {
- return NotFound();
- }
-
- if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- return Ok(query);
- }
-
- [HttpPost]
- public async Task PostQuery([FromBody] RdvQuery query, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
- if (string.IsNullOrWhiteSpace(query.ClientId))
- {
- query.ClientId = uid;
- }
-
- ModelState.Remove("ClientId");
-
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- ModelState.AddModelError("ClientId", "You can only create your own RdvQuery");
- return BadRequest(ModelState);
- }
-
- if (!ModelState.IsValid)
- {
- return BadRequest(ModelState);
- }
-
- if (query.Location is not null)
- {
- var existingLocation = await _context.Locations.FirstOrDefaultAsync(
- x => x.Address == query.Location.Address
- && x.Longitude == query.Location.Longitude
- && x.Latitude == query.Location.Latitude,
- cancellationToken);
-
- if (existingLocation is not null)
- {
- query.Location = existingLocation;
- }
- else
- {
- _context.Attach(query.Location);
- }
- }
-
- _context.RdvQueries.Add(query);
-
- try
- {
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
- }
- catch (DbUpdateException)
- {
- if (QueryExists(query.Id))
- {
- return Conflict();
- }
-
- throw;
- }
-
- return CreatedAtRoute("GetRdvQuery", new { id = query.Id }, query);
- }
-
- [HttpPut("{id}")]
- public async Task PutQuery([FromRoute] long id, [FromBody] RdvQuery query, CancellationToken cancellationToken)
- {
- if (!ModelState.IsValid)
- {
- return BadRequest(ModelState);
- }
-
- if (id != query.Id)
- {
- return BadRequest();
- }
-
- var uid = User.GetUserId();
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- _context.Entry(query).State = EntityState.Modified;
-
- try
- {
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
- }
- catch (DbUpdateConcurrencyException)
- {
- if (!QueryExists(id))
- {
- return NotFound();
- }
-
- throw;
- }
-
- return NoContent();
- }
-
- [HttpDelete("{id}")]
- public async Task DeleteQuery([FromRoute] long id, CancellationToken cancellationToken)
- {
- var uid = User.GetUserId();
-
- var query = await _context.RdvQueries
- .SingleOrDefaultAsync(q => q.Id == id, cancellationToken);
-
- if (query is null)
- {
- return NotFound();
- }
-
- if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
- {
- return Forbid();
- }
-
- _context.RdvQueries.Remove(query);
- await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
-
- return Ok(query);
- }
-
- private bool QueryExists(long id)
- {
- return _context.RdvQueries.Any(e => e.Id == id);
- }
-}
diff --git a/src/Yavsc.Api/Controllers/accounting/AccountController.cs b/src/Yavsc.Api/Controllers/accounting/AccountController.cs
index 155274cd..aff71013 100644
--- a/src/Yavsc.Api/Controllers/accounting/AccountController.cs
+++ b/src/Yavsc.Api/Controllers/accounting/AccountController.cs
@@ -2,8 +2,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
-using Microsoft.AspNetCore.Http;
-using ImageMagick;
using Yavsc.Models;
using Yavsc.Api.Helpers;
@@ -12,21 +10,10 @@ using System.Diagnostics;
namespace Yavsc.WebApi.Controllers
{
- [Route( Constants.APIPrefix + "/account")]
+ [Route("~/api/account")]
[Authorize("ApiScope")]
public class ApiAccountController : Controller
{
- private const long MaxAvatarSizeBytes = 2 * 1024 * 1024;
- private static readonly string[] AcceptedAvatarMimeTypes =
- [
- "image/png",
- "image/jpeg",
- "image/webp",
- "image/gif",
- "image/bmp",
- "image/tiff",
- ];
-
readonly ApplicationDbContext _dbContext;
private readonly ILogger _logger;
@@ -74,102 +61,23 @@ namespace Yavsc.WebApi.Controllers
return Ok(new { host = Request.ForwardedFor() });
}
-
+
///
/// Updates the avatar
///
///
- [HttpPost("set-avatar")]
+ [HttpPost("~/api/set-avatar")]
public async Task SetAvatar()
{
var user = await GetUserData(User.GetUserId());
- if (!Request.HasFormContentType)
- {
- return BadRequest(new
- {
- status = "invalid_request",
- message = "A multipart/form-data request is required.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- });
- }
+ if (Request.Form.Files.Count!=1)
+ return new BadRequestResult();
+ if (!Request.Form.Files[0].ContentType.StartsWith("image/png"))
+ return new BadRequestResult();
- if (Request.Form.Files.Count != 1)
- {
- return BadRequest(new
- {
- status = "invalid_file_count",
- message = "Exactly one file is required.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- });
- }
-
- var avatarFile = Request.Form.Files[0];
- if (avatarFile.Length <= 0)
- {
- return BadRequest(new
- {
- status = "empty_file",
- message = "The uploaded file is empty.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- });
- }
-
- if (avatarFile.Length > MaxAvatarSizeBytes)
- {
- return BadRequest(new
- {
- status = "file_too_large",
- message = "Avatar is too large.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- });
- }
-
- if (!AcceptedAvatarMimeTypes.Any(m => string.Equals(m, avatarFile.ContentType, StringComparison.OrdinalIgnoreCase)))
- {
- return StatusCode(StatusCodes.Status415UnsupportedMediaType, new
- {
- status = "unsupported_media_type",
- message = "Unsupported image format.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- });
- }
-
- try
- {
- var info = user.ReceiveAvatar(avatarFile);
- await _dbContext.SaveChangesAsync();
- return Ok(new
- {
- status = "uploaded",
- message = "Avatar uploaded successfully.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- avatar = info,
- });
- }
- catch (MagickException ex)
- {
- _logger.LogWarning(ex, "Avatar upload failed: invalid image data for user {UserId}", user.Id);
- return BadRequest(new
- {
- status = "invalid_image_data",
- message = "Image content could not be decoded.",
- acceptedMimeTypes = AcceptedAvatarMimeTypes,
- maxFileSizeBytes = MaxAvatarSizeBytes,
- outputFormat = "image/png",
- });
- }
+ var info = user.ReceiveAvatar(Request.Form.Files[0]);
+ await _dbContext.SaveChangesAsync();
+ return Ok(info);
}
[HttpGet("identity")]
diff --git a/src/Yavsc.Api/Program.cs b/src/Yavsc.Api/Program.cs
index 5269eef1..3ed20e8d 100644
--- a/src/Yavsc.Api/Program.cs
+++ b/src/Yavsc.Api/Program.cs
@@ -20,7 +20,6 @@ using Yavsc.Helpers;
using Yavsc.Interface;
using Yavsc.Interfaces;
using Yavsc.Models;
-using Yavsc;
using Yavsc.Server.Helpers;
using Yavsc.Services;
@@ -33,7 +32,6 @@ internal class Program
var builder = WebApplication.CreateBuilder(args);
builder.AddConfiguration("api");
- Config.SiteSetup = builder.Configuration.GetSection("Site").Get() ?? new SiteSettings();
var services = builder.Services;
@@ -74,10 +72,9 @@ internal class Program
services.AddAuthentication("Bearer")
.AddYavscJwtBearer(builder.Configuration);
- // DbContextBuilder
services.AddDbContext(options =>
- options.UseNpgsql(builder.Configuration.GetConnectionString(
- Yavsc.Constants.YavscConnectionStringName)));
+
+ options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
services.AddLocalization(options =>
{
diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj
index c8c30904..20bc2eb6 100644
--- a/src/Yavsc.Api/Yavsc.Api.csproj
+++ b/src/Yavsc.Api/Yavsc.Api.csproj
@@ -7,7 +7,7 @@
true
1.1.0.0
1.1.0.0
- 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070
+ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0
1.1.0-beta.1
diff --git a/src/Yavsc.Api/appsettings-api.json b/src/Yavsc.Api/appsettings-api.json
index d8b295de..ac626c0d 100644
--- a/src/Yavsc.Api/appsettings-api.json
+++ b/src/Yavsc.Api/appsettings-api.json
@@ -2,8 +2,7 @@
"Site": {
"Authority": "https://localhost:5001",
"CorsAllowedOrigins": [
- "https://localhost:5003",
- "https://yavsc.pschneider.fr"
+ "https://localhost:5003"
]
},
"Logging": {
diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs
index ab5ebcc6..4aef805f 100644
--- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs
+++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs
@@ -283,8 +283,8 @@ public sealed class BlogAclApiTests : IClassFixture
TestContext.Current.CancellationToken
));
Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind);
- Assert.True(doc.RootElement.GetArrayLength() >= 1);
- Assert.Contains(doc.RootElement.EnumerateArray(), p => p.GetProperty("id").GetInt64() == created.Id);
+ Assert.Equal(2, doc.RootElement.GetArrayLength());
+ Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64());
// detail should return the same post, with ACL and tags.
var detailResponse = await http.GetAsync(
@@ -296,86 +296,7 @@ public sealed class BlogAclApiTests : IClassFixture
));
Assert.Equal(JsonValueKind.Object, detailDoc.RootElement.ValueKind);
Assert.Equal(created.Id, detailDoc.RootElement.GetProperty("id").GetInt64());
- Assert.True(detailDoc.RootElement.TryGetProperty("acl", out var acl));
- Assert.False(detailDoc.RootElement.TryGetProperty("ACL", out _));
- Assert.Equal(JsonValueKind.Array, acl.ValueKind);
- Assert.Equal(1, acl.GetArrayLength());
-
- var aclEntry = acl[0];
- Assert.Equal(JsonValueKind.Object, aclEntry.ValueKind);
- Assert.True(aclEntry.TryGetProperty("circleId", out var circleId));
- Assert.Equal(_fixture.CircleId, circleId.GetInt64());
- }
-
- [Fact]
- public async Task Non_owner_can_read_restricted_post_but_receives_empty_acl_in_list_and_detail()
- {
- CleanupAcl();
- _fixture.SeedUser(_fixture.DefaultUserLogin);
- _fixture.SeedUser("tester");
- _fixture.SeedCircle(_fixture.DefaultUserLogin, "test", false,
- new[] { _fixture.DefaultUserLogin, "tester" });
-
- using var ownerHttp = NewClient(_fixture.DefaultUserLogin);
- using var readerHttp = NewClient("tester");
-
- var draft = new BlogPost
- {
- Id = 0,
- Title = "ACL scrub test",
- Article = "Visible to circle member",
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow
- };
-
- var postResponse = await ownerHttp.PostAsJsonAsync(
- BlogUrl(),
- draft,
- TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode);
-
- var created = await postResponse.Content.ReadFromJsonAsync(
- TestContext.Current.CancellationToken);
- Assert.NotNull(created);
- Assert.NotEqual(0, created!.Id);
-
- var grantResponse = await ownerHttp.PostAsJsonAsync(
- BlogAclUrl(),
- new PostAccessControlRulePayload
- {
- CircleId = _fixture.CircleId,
- BlogPostId = created.Id
- },
- TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.Created, grantResponse.StatusCode);
-
- var listResponse = await readerHttp.GetAsync(
- BlogUrl(),
- TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
-
- using var listDoc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync(
- TestContext.Current.CancellationToken));
- Assert.Equal(JsonValueKind.Array, listDoc.RootElement.ValueKind);
- foreach (var listed in listDoc.RootElement.EnumerateArray())
- {
- var authorId = listed.GetProperty("authorId").GetString();
- if (string.Equals(authorId, "tester", StringComparison.Ordinal))
- continue;
-
- Assert.True(listed.TryGetProperty("acl", out var listedAcl));
- Assert.Equal(0, listedAcl.GetArrayLength());
- }
-
- var detailResponse = await readerHttp.GetAsync(
- $"{BlogUrl()}/{created.Id}",
- TestContext.Current.CancellationToken);
- Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode);
-
- using var detailDoc = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync(
- TestContext.Current.CancellationToken));
- Assert.True(detailDoc.RootElement.TryGetProperty("acl", out var detailAcl));
- Assert.Equal(0, detailAcl.GetArrayLength());
+ Assert.Equal(1, detailDoc.RootElement.GetProperty("acl").GetArrayLength());
}
}
diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs
index 00e2ea61..f6b46ba9 100644
--- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs
+++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs
@@ -14,10 +14,15 @@ namespace Yavsc.Blogs.Tests;
[Collection("JwtClaimMapping")]
public sealed class BlogApiMappedClaimsTests :
-IClassFixture
+IClassFixture,
+IBackendFixture
{
private readonly MappedClaimsBlogsWebServerFixture _fixture;
+ public IReadOnlyList Addresses => throw new NotImplementedException();
+
+ public IServiceProvider Services => throw new NotImplementedException();
+
public BlogApiMappedClaimsTests(MappedClaimsBlogsWebServerFixture fixture)
{
_fixture = fixture;
diff --git a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs
index 7311ce04..2546de0a 100644
--- a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs
+++ b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs
@@ -25,7 +25,7 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix
{
private readonly InMemoryDatabaseRoot _inMemoryRoot = new();
private readonly Dictionary _savedInboundMap;
- private WebApplication? _app = null;
+ private readonly WebApplication _app;
public MappedClaimsBlogsWebServerFixture()
{
@@ -86,7 +86,6 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix
public void Dispose()
{
- if (_app is null) return;
_app.StopAsync().GetAwaiter().GetResult();
_app.DisposeAsync().AsTask().GetAwaiter().GetResult();
@@ -97,7 +96,6 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix
}
}
-
private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager
{
public FileAccessRight GetFilePathAccess(System.Security.Claims.ClaimsPrincipal user, string fileRelativePath)
diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj
index 88764806..7763cf07 100644
--- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj
+++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj
@@ -9,7 +9,7 @@
true
1.1.0.0
1.1.0.0
- 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070
+ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0
1.1.0-beta.1
diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj
index 25521e8a..834bc57f 100644
--- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj
+++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj
@@ -8,7 +8,7 @@
true
1.1.0.0
1.1.0.0
- 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070
+ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0
1.1.0-beta.1
diff --git a/src/Yavsc.Org.Tests/Directory.Packages.props b/src/Yavsc.Org.Tests/Directory.Packages.props
index 5927d2b5..b267eafe 100644
--- a/src/Yavsc.Org.Tests/Directory.Packages.props
+++ b/src/Yavsc.Org.Tests/Directory.Packages.props
@@ -7,5 +7,7 @@
+
+
diff --git a/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs b/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs
deleted file mode 100644
index 200f37fa..00000000
--- a/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace Yavsc.Org.Tests.NonRegression;
-
-///
-/// Non-regression: avatar requests under /avatars must never return 404
-/// for missing files. The pipeline falls back to static defaults under
-/// /images/Users/icon_user*.png.
-///
-public class AvatarFallbackTests : IClassFixture
-{
- private readonly TestWebApplicationFactory _factory;
-
- public AvatarFallbackTests(TestWebApplicationFactory factory)
- {
- _factory = factory;
- }
-
- [Theory]
- [InlineData("/avatars/user-does-not-exist.png")]
- [InlineData("/avatars/user-does-not-exist.s.png")]
- [InlineData("/avatars/user-does-not-exist.xs.png")]
- public async Task Missing_avatar_file_returns_default_image_instead_of_404(string path)
- {
- using var client = _factory.CreateClient();
- var ct = TestContext.Current.CancellationToken;
-
- var response = await client.GetAsync(path, ct);
-
- Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("image/png", response.Content.Headers.ContentType?.MediaType);
-
- var payload = await response.Content.ReadAsByteArrayAsync(ct);
- Assert.True(payload.Length > 0, $"Expected a non-empty fallback image for {path}.");
- }
-}
diff --git a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs
index fd1fe501..743dbbc1 100644
--- a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs
+++ b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs
@@ -1,19 +1,8 @@
-using System.ComponentModel.DataAnnotations;
-using System.Globalization;
-using MailKit.Net.Smtp;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Extensions.Options;
-using MimeKit;
using Yavsc.Interface;
using Yavsc.Interfaces;
-using Yavsc.Models.Relationship;
using Yavsc.Org.Tests.Fakes;
-using Yavsc.Services;
-using Yavsc.Settings;
-using Yavsc.ViewModels.Account;
namespace Yavsc.Org.Tests
{
@@ -66,97 +55,5 @@ namespace Yavsc.Org.Tests
client.Calls.Select(c => c.Kind).ToArray());
Assert.Equal(_serverFixture.SiteSettings.Owner.EMail, client.LastSentMessage?.To.Mailboxes.First().Address);
}
-
- [Fact]
- public void RegisterModel_rejects_invalid_email_format()
- {
- var model = new RegisterModel
- {
- UserName = "alice",
- Email = "this is not an email",
- Password = "Password123!",
- ConfirmPassword = "Password123!"
- };
-
- var results = new List();
- var valid = Validator.TryValidateObject(
- model,
- new ValidationContext(model),
- results,
- validateAllProperties: true);
-
- Assert.False(valid);
- Assert.Contains(results, r => r.MemberNames.Contains(nameof(RegisterModel.Email)));
- }
-
- [Fact]
- public async Task SendEmailAsync_ignores_smtp_recipient_rejection()
- {
- var sender = new MailSender(
- Options.Create(new SiteSettings
- {
- Title = "Test",
- Authority = "example.com",
- Owner = new StaticContact { Name = "Test Owner", EMail = "owner@example.com" }
- }),
- Options.Create(new SmtpSettings
- {
- Host = "smtp.test.local",
- Port = 465,
- UserName = "test-user",
- Password = "secret"
- }),
- NullLoggerFactory.Instance,
- new TestStringLocalizer(),
- new RejectingSmtpClientFactory());
-
- var result = await sender.SendEmailAsync(
- "Alice",
- "contact@pschneider.fr",
- "Welcome",
- "hello");
-
- Assert.Equal(string.Empty, result);
- }
-
- private sealed class RejectingSmtpClientFactory : ISmtpClientFactory
- {
- public Yavsc.Interfaces.ISmtpClient CreateClient() => new RejectingSmtpClient();
- }
-
- private sealed class RejectingSmtpClient : Yavsc.Interfaces.ISmtpClient
- {
- public int Timeout { get; set; }
- public void Connect(string host, int port, MailKit.Security.SecureSocketOptions options) { }
- public void Authenticate(string userName, string password) { }
- public Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default)
- {
- throw new SmtpCommandException(
- SmtpErrorCode.RecipientNotAccepted,
- SmtpStatusCode.MailboxUnavailable,
- "Recipient address rejected: User unknown in local recipient table");
- }
- public void Disconnect(bool quit) { }
- public void Dispose() { }
- }
-
- private sealed class TestStringLocalizer : IStringLocalizer
- {
- public LocalizedString this[string name] => new(name, name);
- public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments));
-
- public IEnumerable GetAllStrings(bool includeParentCultures)
- => Enumerable.Empty();
-
- public LocalizedString GetString(string name)
- => new(name, name);
-
- public LocalizedString GetString(string name, params object[] arguments)
- => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments));
-
- public IStringLocalizer WithCulture(CultureInfo culture)
- => this;
- }
-
}
}
diff --git a/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs b/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs
deleted file mode 100644
index c47bdff5..00000000
--- a/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using Yavsc.Models.Relationship;
-using Yavsc.Models.Workflow;
-
-namespace Yavsc.Tests.NonRegression;
-
-public class PerformerCodeInputValidationTests
-{
- [Fact]
- public void Validate_rejects_unknown_country_code()
- {
- var profile = CreateBaseProfile();
- profile.ExerciseCountryCode = "de";
- profile.SIREN = "123456789";
-
- var results = Validate(profile);
-
- Assert.Contains(results, r => r.MemberNames.Contains(nameof(PerformerProfile.ExerciseCountryCode)));
- }
-
- [Fact]
- public void Validate_rejects_code_not_matching_country_rule()
- {
- var profile = CreateBaseProfile();
- profile.ExerciseCountryCode = "pt";
- profile.SIREN = "ABC123";
-
- var results = Validate(profile);
-
- Assert.Contains(results, r => r.MemberNames.Contains(nameof(PerformerProfile.SIREN)));
- }
-
- [Fact]
- public void Validate_accepts_country_specific_valid_codes()
- {
- var fr = CreateBaseProfile();
- fr.ExerciseCountryCode = "fr";
- fr.SIREN = "123456789";
-
- var en = CreateBaseProfile();
- en.ExerciseCountryCode = "en";
- en.SIREN = "AB12CD34";
-
- var pt = CreateBaseProfile();
- pt.ExerciseCountryCode = "pt";
- pt.SIREN = "501964843";
-
- Assert.Empty(Validate(fr));
- Assert.Empty(Validate(en));
- Assert.Empty(Validate(pt));
- }
-
- private static PerformerProfile CreateBaseProfile()
- {
- return new PerformerProfile
- {
- PerformerId = "perf-1",
- SIREN = "123456789",
- ExerciseCountryCode = "fr",
- OrganizationAddress = new Location
- {
- Address = "1 rue du Test",
- Latitude = 48.8566,
- Longitude = 2.3522,
- },
- };
- }
-
- private static List Validate(PerformerProfile profile)
- {
- var ctx = new ValidationContext(profile);
- var results = new List();
- Validator.TryValidateObject(profile, ctx, results, validateAllProperties: true);
- return results;
- }
-}
\ No newline at end of file
diff --git a/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs b/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs
deleted file mode 100644
index 23ace458..00000000
--- a/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-namespace Yavsc.Org.Tests.NonRegression;
-
-///
-/// Guard rails for the SetActivity performer settings page:
-/// - countries are provided to the ComboBox via ViewBag.Countries
-/// - client-side SIREN validation is wired to country-specific regex rules
-/// - controller exposes the validation catalog to the view
-///
-public class SetActivityCountryValidationViewTests
-{
- [Fact]
- public void SetActivity_cshtml_binds_country_combo_to_ViewBag_Countries()
- {
- var content = File.ReadAllText(ResolveSetActivityViewPath());
-
- Assert.Contains("asp-for=\"ExerciseCountryCode\"", content);
- Assert.Contains("asp-items=\"ViewBag.Countries\"", content);
- }
-
- [Fact]
- public void SetActivity_cshtml_contains_country_aware_siren_javascript_validation()
- {
- var content = File.ReadAllText(ResolveSetActivityViewPath());
-
- Assert.Contains("$.validator.addMethod(\"sirenByCountry\"", content);
- Assert.Contains("new RegExp(selectedRule.regex)", content);
- Assert.Contains("const countryInput = $(\"#ExerciseCountryCode\")", content);
- Assert.Contains("const sirenInput = $(\"#SIREN\")", content);
- }
-
- [Fact]
- public void ManageController_exposes_countries_and_country_validation_rules_to_view()
- {
- var content = File.ReadAllText(ResolveManageControllerPath());
-
- Assert.Contains("ViewBag.Countries = countries;", content);
- Assert.Contains("ViewBag.CountryCodeValidationRules = PerformerCodeInputValidationCatalog.Rules;", content);
- Assert.Contains("ModelState.Remove(nameof(PerformerProfile.ExerciseCountryCode));", content);
- }
-
- private static string ResolveSetActivityViewPath()
- {
- return ResolveFromWorkspaceRoot(
- "src", "Yavsc.Org", "Views", "Manage", "SetActivity.cshtml");
- }
-
- private static string ResolveManageControllerPath()
- {
- return ResolveFromWorkspaceRoot(
- "src", "Yavsc.Org", "Controllers", "Accounting", "ManageController.cs");
- }
-
- private static string ResolveFromWorkspaceRoot(params string[] relative)
- {
- var dir = AppContext.BaseDirectory;
- for (var i = 0; i < 10 && dir is not null; i++)
- {
- var candidate = Path.Combine(new[] { dir }.Concat(relative).ToArray());
- if (File.Exists(candidate))
- {
- return candidate;
- }
-
- dir = Path.GetDirectoryName(dir);
- }
-
- throw new FileNotFoundException(
- "Could not locate test target from " + AppContext.BaseDirectory);
- }
-}
diff --git a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs
index 327c2db4..c77a20ab 100644
--- a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs
+++ b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs
@@ -1,6 +1,3 @@
-using IdentityServer8.Stores;
-using Microsoft.Extensions.DependencyInjection;
-
namespace Yavsc.Org.Tests.Smoke;
///
@@ -33,27 +30,4 @@ public class AccountSmokeTests : SmokeTestBase, IClassFixture();
-
- var exception = await Record.ExceptionAsync(resourceStore.GetAllResourcesAsync);
-
- Assert.Null(exception);
- }
}
diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj
index 10607920..747498d4 100644
--- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj
+++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj
@@ -11,7 +11,7 @@
$(MSBuildProjectDirectory)\test.runsettings
1.1.0.0
1.1.0.0
- 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070
+ 1.1.0-beta.1+148.Branch.release-1.0.8-rc4.Sha.6b161b0fb509fec0ce467aa94f1a450b202af2f0
1.1.0-beta.1
diff --git a/src/Yavsc.Org/Contants.cs b/src/Yavsc.Org/Contants.cs
index 246bc3ac..c4a5e8b1 100644
--- a/src/Yavsc.Org/Contants.cs
+++ b/src/Yavsc.Org/Contants.cs
@@ -14,7 +14,7 @@ public static class Constants
// 'offline_access' is handled by IdentityServer8 itself and never
// needs an explicit ApiScope row.
public static readonly string[] BuildInApiScopes = {
- "admin", "moderation", "performer", "client", "api" };
+ "admin", "moderation", "performer", "client" };
// One ApiResource per application scope. Each scope is exposed by
// exactly one resource, named after the scope ("admin" -> "admin"
@@ -29,7 +29,6 @@ public static class Constants
new ApiResourceScopeSpecification { ScopeName = "moderation", Description = "Moderation access", ResourceName = "moderation", ResourceDisplayName = "Moderation API" },
new ApiResourceScopeSpecification { ScopeName = "performer", Description = "Performer access", ResourceName = "performer", ResourceDisplayName = "Performer API" },
new ApiResourceScopeSpecification { ScopeName = "client", Description = "Client access", ResourceName = "client", ResourceDisplayName = "Client API" },
- new ApiResourceScopeSpecification { ScopeName = "api", Description = "Core API access", ResourceName = "api", ResourceDisplayName = "Yavsc Core API" },
new ApiResourceScopeSpecification { ScopeName = "blogs", Description = "Blogs access", ResourceName = "blogs", ResourceDisplayName = "Yavsc Blogs API" }
};
}
diff --git a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs
index 77e0e32a..c562736d 100644
--- a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs
+++ b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs
@@ -94,6 +94,107 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
}
+ public async Task SignIn(SignInModel model, [FromForm] string button)
+ {
+ if (Request.Method == "POST") // "hGbkk9B94NAae#aG"
+
+ {
+ if (model.Provider == null || model.Provider == "LOCAL")
+ {
+ if (ModelState.IsValid)
+ {
+ var user = await _userManager.FindByNameAsync(model.UserName);
+ var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
+ if (user != null)
+ {
+
+
+ var signin = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true);
+
+ // validate username/password against in-memory store
+ if (signin.Succeeded)
+ {
+ await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
+
+ // only set explicit expiration here if user chooses "remember me".
+ // otherwise we rely upon expiration configured in cookie middleware.
+ await HttpContext.SignInAsync(user, _roleManager, model.RememberMe, _dbContext);
+ var authResult = await HttpContext.AuthenticateAsync();
+ if (!authResult.Succeeded)
+ {
+ return this.Unauthorized();
+ }
+ String bearer = await HttpContext.GetTokenAsync("Bearer", "Bearer");
+ HttpContext.Response.Cookies.Append("Bearer", bearer);
+
+ if (context != null)
+ {
+ if (context.IsNativeClient())
+ {
+ // The client is native, so this change in how to
+ // return the response is for better UX for the end user.
+ return this.LoadingPage("Redirect", model.ReturnUrl);
+ }
+
+ // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
+ return Redirect(model.ReturnUrl);
+ }
+
+ // request for a local page
+ if (Url.IsLocalUrl(model.ReturnUrl))
+ {
+ return Redirect(model.ReturnUrl);
+ }
+ else if (string.IsNullOrEmpty(model.ReturnUrl))
+ {
+ return Redirect("~/");
+ }
+ else
+ {
+ // user might have clicked on a malicious link - should be logged
+ throw new Exception("invalid return URL");
+ }
+ }
+ }
+
+ await _events.RaiseAsync(new UserLoginFailureEvent(model.UserName, "invalid credentials", clientId: context?.Client.ClientId));
+ ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
+ }
+ }
+ else
+ {
+
+ // Note: the "provider" parameter corresponds to the external
+ // authentication provider choosen by the user agent.
+ if (string.IsNullOrEmpty(model.Provider))
+ {
+ _logger.LogWarning("Provider not specified");
+ return BadRequest();
+ }
+
+ // Instruct the middleware corresponding to the requested external identity
+ // provider to redirect the user agent to its own authorization endpoint.
+ // Note: the authenticationScheme parameter must match the value configured in Startup.cs
+
+ // Note: the "returnUrl" parameter corresponds to the endpoint the user agent
+ // will be redirected to after a successful authentication and not
+ // the redirect_uri of the requesting client application.
+ if (string.IsNullOrEmpty(model.ReturnUrl))
+ {
+ _logger.LogWarning("ReturnUrl not specified");
+ return BadRequest();
+ }
+ // Note: this still is not the redirect uri given to the third party provider, at building the challenge.
+ var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { model.ReturnUrl }, protocol: "https", host: Config.Authority);
+ var properties = _signInManager.ConfigureExternalAuthenticationProperties(model.Provider, redirectUrl);
+ // var properties = new AuthenticationProperties{RedirectUri=ReturnUrl};
+ return new ChallengeResult(model.Provider, properties);
+
+ }
+ }
+ return View(model);
+ }
+
///
/// Entry point into the login workflow
///
@@ -463,8 +564,6 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
[ValidateAntiForgeryToken]
public async Task Register(RegisterModel model)
{
- model.Email = model.Email?.Trim();
-
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
diff --git a/src/Yavsc.Org/Controllers/Accounting/ManageController.cs b/src/Yavsc.Org/Controllers/Accounting/ManageController.cs
index e09bbb5a..43e11cd2 100644
--- a/src/Yavsc.Org/Controllers/Accounting/ManageController.cs
+++ b/src/Yavsc.Org/Controllers/Accounting/ManageController.cs
@@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
-using Microsoft.AspNetCore.Mvc.Rendering;
using Yavsc.Models.Workflow;
using Yavsc.Helpers;
using Yavsc.Models.Relationship;
@@ -16,7 +15,6 @@ using Yavsc.Services;
using Yavsc.ViewModels.Manage;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Authorization;
-using IdentityServer8;
using Yavsc.Server.Helpers;
namespace Yavsc.Controllers
@@ -525,45 +523,8 @@ namespace Yavsc.Controllers
}
[HttpGet]
- public async Task SetAvatar(
- [FromServices] IdentityServerTools identityServerTools)
+ public IActionResult SetAvatar()
{
- var currentUser = await GetCurrentUserAsync();
- if (currentUser == null)
- {
- return Challenge();
- }
-
- var claims = new List
- {
- new("sub", currentUser.Id),
- new("name", currentUser.UserName ?? currentUser.Email ?? currentUser.Id),
- new("scope", "api"),
- new("aud", "api")
- };
-
- // Short-lived token limited to avatar upload from this page.
- string avatarAccessToken = string.Empty;
- try
- {
- avatarAccessToken = await identityServerTools.IssueClientJwtAsync(
- "postit",
- 300,
- new[] { "api" },
- new[] { "api" },
- claims);
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "IssueClientJwtAsync failed for SetAvatar, trying direct IssueJwtAsync fallback.");
- }
-
- if (string.IsNullOrWhiteSpace(avatarAccessToken))
- {
- avatarAccessToken = await identityServerTools.IssueJwtAsync(300, claims);
- }
-
- ViewData["AvatarAccessToken"] = avatarAccessToken;
return View();
}
@@ -583,19 +544,15 @@ namespace Yavsc.Controllers
{
var currentProfile = _dbContext.Performers.Include(x => x.OrganizationAddress)
.First(x => x.PerformerId == uid);
- currentProfile.ExerciseCountryCode = NormalizeCountryCodeOrDefault(currentProfile.ExerciseCountryCode);
ViewBag.Activities = _dbContext.ActivityItems(existing.Activity);
- SetExerciseCountries(currentProfile.ExerciseCountryCode);
return View(currentProfile);
}
ViewBag.Activities = _dbContext.ActivityItems(new List());
- SetExerciseCountries("fr");
return View(new PerformerProfile
{
PerformerId = user.Id,
Performer = user,
- ExerciseCountryCode = "fr",
OrganizationAddress = new Location()
});
}
@@ -606,42 +563,28 @@ namespace Yavsc.Controllers
{
var user = GetCurrentUserAsync().Result;
var uid = user.Id;
- var postedCountryCode = model.ExerciseCountryCode;
- model.ExerciseCountryCode = NormalizeCountryCodeOrDefault(model.ExerciseCountryCode);
-
- // Model binding validated the raw posted payload before entering
- // the action. If country was empty, we fallback to "fr" above,
- // so remove the stale min-length error attached to the empty value.
- if (string.IsNullOrWhiteSpace(postedCountryCode))
- {
- ModelState.Remove(nameof(PerformerProfile.ExerciseCountryCode));
- }
-
try
{
if (ModelState.IsValid)
{
- var isFrenchPerformerCode = string.Equals(model.ExerciseCountryCode, "fr", StringComparison.Ordinal);
- if (isFrenchPerformerCode)
+
+ var exSiren = await _dbContext.ExceptionsSIREN.FirstOrDefaultAsync(
+ ex => ex.SIREN == model.SIREN
+ );
+ if (exSiren != null)
{
- var exSiren = await _dbContext.ExceptionsSIREN.FirstOrDefaultAsync(
- ex => ex.SIREN == model.SIREN
- );
- if (exSiren != null)
+ _logger.LogInformation("Exception SIREN:" + exSiren);
+ }
+ else
+ {
+ var taskCheck = await _cchecker.CheckAsync(model.SIREN);
+ if (!taskCheck.success)
{
- _logger.LogInformation("Exception SIREN:" + exSiren);
- }
- else
- {
- var taskCheck = await _cchecker.CheckAsync(model.SIREN);
- if (!taskCheck.success)
- {
- ModelState.AddModelError(
- "SIREN",
- _SR["Invalid company number"] + " (" + taskCheck.errorCode + ")"
- );
- _logger.LogInformation($"Invalid company number: {model.SIREN}/{taskCheck.errorType}/{taskCheck.errorCode}/{taskCheck.errorMessage}" );
- }
+ ModelState.AddModelError(
+ "SIREN",
+ _SR["Invalid company number"] + " (" + taskCheck.errorCode + ")"
+ );
+ _logger.LogInformation($"Invalid company number: {model.SIREN}/{taskCheck.errorType}/{taskCheck.errorCode}/{taskCheck.errorMessage}" );
}
}
}
@@ -679,35 +622,10 @@ namespace Yavsc.Controllers
}
ViewBag.Activities = _dbContext.ActivityItems(new List());
ViewBag.GoogleSettings = _googleSettings;
- SetExerciseCountries(model.ExerciseCountryCode);
model.Performer = _dbContext.Users.Single(u=>u.Id == model.PerformerId);
return View(model);
}
- private static string NormalizeCountryCodeOrDefault(string code)
- {
- var normalized = PerformerCodeInputValidationCatalog.NormalizeCountryCode(code);
- if (string.IsNullOrWhiteSpace(normalized))
- {
- return "fr";
- }
-
- return normalized;
- }
-
- private void SetExerciseCountries(string selectedCountryCode)
- {
- var selected = NormalizeCountryCodeOrDefault(selectedCountryCode);
- var countries = new SelectList(
- PerformerCodeInputValidationCatalog.Countries,
- nameof(Country.Code),
- nameof(Country.DisplayName),
- selected);
- ViewBag.ExerciseCountries = countries;
- ViewBag.Countries = countries;
- ViewBag.CountryCodeValidationRules = PerformerCodeInputValidationCatalog.Rules;
- }
-
[HttpPost]
public async Task UnsetActivity()
{
diff --git a/src/Yavsc.Org/Controllers/Contracting/FormsController.cs b/src/Yavsc.Org/Controllers/Contracting/FormsController.cs
new file mode 100644
index 00000000..423cf2f4
--- /dev/null
+++ b/src/Yavsc.Org/Controllers/Contracting/FormsController.cs
@@ -0,0 +1,120 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Yavsc.Models;
+using Yavsc.Models.Forms;
+using Yavsc.Server.Helpers;
+
+namespace Yavsc.Controllers
+{
+ public class FormsController : Controller
+ {
+ private readonly ApplicationDbContext _context;
+
+ public FormsController(ApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ // GET: Forms
+ public async Task Index()
+ {
+ return View(await _context.Form.ToListAsync());
+ }
+
+ // GET: Forms/Details/5
+ public async Task Details(string id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Form form = await _context.Form.SingleAsync(m => m.Id == id);
+ if (form == null)
+ {
+ return NotFound();
+ }
+
+ return View(form);
+ }
+
+ // GET: Forms/Create
+ public IActionResult Create()
+ {
+ return View();
+ }
+
+ // POST: Forms/Create
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Create(Form form)
+ {
+ if (ModelState.IsValid)
+ {
+ _context.Form.Add(form);
+ await _context.SaveChangesAsync(User.GetUserId());
+ return RedirectToAction("Index");
+ }
+ return View(form);
+ }
+
+ // GET: Forms/Edit/5
+ public async Task Edit(string id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Form form = await _context.Form.SingleAsync(m => m.Id == id);
+ if (form == null)
+ {
+ return NotFound();
+ }
+ return View(form);
+ }
+
+ // POST: Forms/Edit/5
+ [HttpPost]
+ [ValidateAntiForgeryToken]
+ public async Task Edit(Form form)
+ {
+ if (ModelState.IsValid)
+ {
+ _context.Update(form);
+ await _context.SaveChangesAsync(User.GetUserId());
+ return RedirectToAction("Index");
+ }
+ return View(form);
+ }
+
+ // GET: Forms/Delete/5
+ [ActionName("Delete")]
+ public async Task Delete(string id)
+ {
+ if (id == null)
+ {
+ return NotFound();
+ }
+
+ Form form = await _context.Form.SingleAsync(m => m.Id == id);
+ if (form == null)
+ {
+ return NotFound();
+ }
+
+ return View(form);
+ }
+
+ // POST: Forms/Delete/5
+ [HttpPost, ActionName("Delete")]
+ [ValidateAntiForgeryToken]
+ public async Task DeleteConfirmed(string id)
+ {
+ Form form = await _context.Form.SingleAsync(m => m.Id == id);
+ _context.Form.Remove(form);
+ await _context.SaveChangesAsync(User.GetUserId());
+ return RedirectToAction("Index");
+ }
+ }
+}
diff --git a/src/Yavsc.Org/Controllers/Contracting/SIRENExceptionsController.cs b/src/Yavsc.Org/Controllers/Contracting/SIRENExceptionsController.cs
index ed4ee5b5..bf08484e 100644
--- a/src/Yavsc.Org/Controllers/Contracting/SIRENExceptionsController.cs
+++ b/src/Yavsc.Org/Controllers/Contracting/SIRENExceptionsController.cs
@@ -3,8 +3,6 @@ using Microsoft.AspNetCore.Mvc;
using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.Server.Helpers;
-using Microsoft.EntityFrameworkCore;
-using System.Linq;
namespace Yavsc.Controllers
{
@@ -15,7 +13,7 @@ namespace Yavsc.Controllers
public SIRENExceptionsController(ApplicationDbContext context)
{
- _context = context;
+ _context = context;
}
// GET: SIRENExceptions
@@ -52,41 +50,15 @@ namespace Yavsc.Controllers
[ValidateAntiForgeryToken]
public IActionResult Create(ExceptionSIREN exceptionSIREN)
{
- exceptionSIREN ??= new ExceptionSIREN();
- exceptionSIREN.SIREN = NormalizeSiren(exceptionSIREN.SIREN);
-
- if (string.IsNullOrWhiteSpace(exceptionSIREN.SIREN) || exceptionSIREN.SIREN.Length != 9 || !exceptionSIREN.SIREN.All(char.IsDigit))
- {
- ModelState.AddModelError(nameof(ExceptionSIREN.SIREN), "Le SIREN doit contenir exactement 9 chiffres.");
- }
-
- if (_context.ExceptionsSIREN.Any(e => e.SIREN == exceptionSIREN.SIREN))
- {
- ModelState.AddModelError(nameof(ExceptionSIREN.SIREN), "Ce SIREN est deja dans la liste des exceptions.");
- }
-
if (ModelState.IsValid)
{
_context.ExceptionsSIREN.Add(exceptionSIREN);
- try
- {
- _context.SaveChanges(User.GetUserId());
- return RedirectToAction("Index");
- }
- catch (DbUpdateException)
- {
- ModelState.AddModelError(string.Empty, "Impossible d'enregistrer cette exception SIREN.");
- }
+ _context.SaveChanges(User.GetUserId());
+ return RedirectToAction("Index");
}
return View(exceptionSIREN);
}
- private static string NormalizeSiren(string? siren)
- {
- if (string.IsNullOrWhiteSpace(siren)) return string.Empty;
- return new string(siren.Where(char.IsDigit).ToArray());
- }
-
// GET: SIRENExceptions/Edit/5
public IActionResult Edit(string id)
{
diff --git a/src/Yavsc.Org/Controllers/HomeController.cs b/src/Yavsc.Org/Controllers/HomeController.cs
index 6132b093..c7aa131f 100644
--- a/src/Yavsc.Org/Controllers/HomeController.cs
+++ b/src/Yavsc.Org/Controllers/HomeController.cs
@@ -63,35 +63,9 @@ namespace Yavsc.Controllers
.Where(a => a.ParentCode == id)
.OrderByDescending(a => a.Rate).ToList();
- var candidateCodes = toShow
- .Select(a => a.Code)
- .Concat(toShow.SelectMany(a => (a.Children ?? new List())
- .Where(c => !c.Hidden)
- .Select(c => c.Code)))
- .Where(c => !string.IsNullOrWhiteSpace(c))
- .Distinct()
- .ToArray();
-
- var performerCounts = _dbContext.UserActivities
- .Where(ua => candidateCodes.Contains(ua.DoesCode))
- .GroupBy(ua => ua.DoesCode)
- .Select(g => new { Code = g.Key, Count = g.Select(x => x.UserId).Distinct().Count() })
- .ToDictionary(x => x.Code, x => x.Count);
-
- toShow = toShow
- .Where(a =>
- (performerCounts.TryGetValue(a.Code, out var ownCount) && ownCount > 0)
- || (a.Children ?? new List())
- .Where(c => !c.Hidden)
- .Any(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0))
- .ToList();
-
foreach (var a in toShow)
{
- a.Children = (a.Children ?? new List())
- .Where(c => !c.Hidden)
- .Where(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0)
- .ToList();
+ a.Children = a.Children.Where(c => !c.Hidden).ToList();
}
return View(toShow);
}
diff --git a/src/Yavsc.Org/Directory.Packages.props b/src/Yavsc.Org/Directory.Packages.props
new file mode 100644
index 00000000..fd16374b
--- /dev/null
+++ b/src/Yavsc.Org/Directory.Packages.props
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs
index e986620f..6528b9c6 100644
--- a/src/Yavsc.Org/Extensions/HostingExtensions.cs
+++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs
@@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
-using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -804,7 +803,6 @@ public static class HostingExtensions
// silent refresh path to work; without it IdentityServer
// refuses to issue a refresh_token.
"blogs",
- "api",
IdentityServer8.IdentityServerConstants.StandardScopes.OpenId,
IdentityServer8.IdentityServerConstants.StandardScopes.Profile,
IdentityServer8.IdentityServerConstants.StandardScopes.OfflineAccess,
@@ -1283,52 +1281,10 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;");
app.UseFileServer(Config.AvatarsOptions);
- app.Use(async (context, next) =>
- {
- await next();
-
- if (context.Response.StatusCode != StatusCodes.Status404NotFound
- || context.Response.HasStarted
- || !HttpMethods.IsGet(context.Request.Method)
- || !context.Request.Path.StartsWithSegments(Constants.AvatarsPath, out var avatarFile))
- {
- return;
- }
-
- var webHostEnvironment = app.ApplicationServices.GetRequiredService();
- var fallbackAsset = ResolveAvatarFallbackAsset(avatarFile);
- var fallbackFile = webHostEnvironment.WebRootFileProvider.GetFileInfo(fallbackAsset.TrimStart('/'));
- if (!fallbackFile.Exists)
- {
- return;
- }
-
- context.Response.StatusCode = StatusCodes.Status200OK;
- context.Response.ContentType = "image/png";
- await context.Response.SendFileAsync(fallbackFile);
- });
-
app.UseFileServer(Config.GitOptions);
app.UseStaticFiles();
return app;
}
- private static string ResolveAvatarFallbackAsset(PathString avatarFile)
- {
- var fileName = avatarFile.Value ?? string.Empty;
-
- if (fileName.EndsWith(".xs.png", StringComparison.OrdinalIgnoreCase))
- {
- return "/images/Users/icon_user.xs.png";
- }
-
- if (fileName.EndsWith(".s.png", StringComparison.OrdinalIgnoreCase))
- {
- return "/images/Users/icon_user.s.png";
- }
-
- return Constants.DefaultAvatar;
- }
-
}
diff --git a/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.Designer.cs b/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.Designer.cs
deleted file mode 100644
index 3348b9c4..00000000
--- a/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.Designer.cs
+++ /dev/null
@@ -1,4734 +0,0 @@
-//
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-using Yavsc.Models;
-
-#nullable disable
-
-namespace Yavsc.Migrations
-{
- [DbContext(typeof(ApplicationDbContext))]
- [Migration("20260831040104_AddPerformerCountryValidation")]
- partial class AddPerformerCountryValidation
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "10.0.9")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
-
- b.Property("AllowedAccessTokenSigningAlgorithms")
- .HasColumnType("text");
-
- b.Property("Created")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Description")
- .HasColumnType("text");
-
- b.Property("DisplayName")
- .HasColumnType("text");
-
- b.Property("Enabled")
- .HasColumnType("boolean");
-
- b.Property("LastAccessed")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Name")
- .HasColumnType("text");
-
- b.Property("NonEditable")
- .HasColumnType("boolean");
-
- b.Property("ShowInDiscoveryDocument")
- .HasColumnType("boolean");
-
- b.Property("Updated")
- .HasColumnType("timestamp with time zone");
-
- b.HasKey("Id");
-
- b.ToTable("ApiResources");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ApiResourceId")
- .HasColumnType("integer");
-
- b.Property("ApiResourceId1")
- .HasColumnType("integer");
-
- b.Property("Type")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.HasIndex("ApiResourceId");
-
- b.HasIndex("ApiResourceId1");
-
- b.ToTable("ApiResourceClaims");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ApiResourceId")
- .HasColumnType("integer");
-
- b.Property("ApiResourceId1")
- .HasColumnType("integer");
-
- b.Property("Key")
- .HasColumnType("text");
-
- b.Property("Value")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.HasIndex("ApiResourceId");
-
- b.HasIndex("ApiResourceId1");
-
- b.ToTable("ApiResourceProperties");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ApiResourceId")
- .HasColumnType("integer");
-
- b.Property("ApiResourceId1")
- .HasColumnType("integer");
-
- b.Property("Scope")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.HasIndex("ApiResourceId");
-
- b.HasIndex("ApiResourceId1");
-
- b.ToTable("ApiResourceScopes");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ApiResourceId")
- .HasColumnType("integer");
-
- b.Property("ApiResourceId1")
- .HasColumnType("integer");
-
- b.Property("Created")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Description")
- .HasColumnType("text");
-
- b.Property("Expiration")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Type")
- .HasColumnType("text");
-
- b.Property("Value")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.HasIndex("ApiResourceId");
-
- b.HasIndex("ApiResourceId1");
-
- b.ToTable("ApiResourceSecrets");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
-
- b.Property("Description")
- .HasColumnType("text");
-
- b.Property("DisplayName")
- .HasColumnType("text");
-
- b.Property("Emphasize")
- .HasColumnType("boolean");
-
- b.Property("Enabled")
- .HasColumnType("boolean");
-
- b.Property("Name")
- .HasColumnType("text");
-
- b.Property("Required")
- .HasColumnType("boolean");
-
- b.Property("ShowInDiscoveryDocument")
- .HasColumnType("boolean");
-
- b.HasKey("Id");
-
- b.ToTable("ApiScopes");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("ScopeId")
- .HasColumnType("integer");
-
- b.Property("ScopeId1")
- .HasColumnType("integer");
-
- b.Property("Type")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.HasIndex("ScopeId");
-
- b.HasIndex("ScopeId1");
-
- b.ToTable("ApiScopeClaims");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("Key")
- .HasColumnType("text");
-
- b.Property("ScopeId")
- .HasColumnType("integer");
-
- b.Property("ScopeId1")
- .HasColumnType("integer");
-
- b.Property("Value")
- .HasColumnType("text");
-
- b.HasKey("Id");
-
- b.HasIndex("ScopeId");
-
- b.HasIndex("ScopeId1");
-
- b.ToTable("ApiScopeProperties");
- });
-
- modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
-
- b.Property("AbsoluteRefreshTokenLifetime")
- .HasColumnType("integer");
-
- b.Property("AccessTokenLifetime")
- .HasColumnType("integer");
-
- b.Property("AccessTokenType")
- .HasColumnType("integer");
-
- b.Property("AllowAccessTokensViaBrowser")
- .HasColumnType("boolean");
-
- b.Property("AllowOfflineAccess")
- .HasColumnType("boolean");
-
- b.Property("AllowPlainTextPkce")
- .HasColumnType("boolean");
-
- b.Property("AllowRememberConsent")
- .HasColumnType("boolean");
-
- b.Property("AllowedIdentityTokenSigningAlgorithms")
- .HasColumnType("text");
-
- b.Property("AlwaysIncludeUserClaimsInIdToken")
- .HasColumnType("boolean");
-
- b.Property("AlwaysSendClientClaims")
- .HasColumnType("boolean");
-
- b.Property("AuthorizationCodeLifetime")
- .HasColumnType("integer");
-
- b.Property("BackChannelLogoutSessionRequired")
- .HasColumnType("boolean");
-
- b.Property("BackChannelLogoutUri")
- .HasColumnType("text");
-
- b.Property("ClientClaimsPrefix")
- .HasColumnType("text");
-
- b.Property("ClientId")
- .HasColumnType("text");
-
- b.Property("ClientName")
- .HasColumnType("text");
-
- b.Property("ClientUri")
- .HasColumnType("text");
-
- b.Property("ConsentLifetime")
- .HasColumnType("integer");
-
- b.Property("Created")
- .HasColumnType("timestamp with time zone");
-
- b.Property("Description")
- .HasColumnType("text");
-
- b.Property("DeviceCodeLifetime")
- .HasColumnType("integer");
-
- b.Property("EnableLocalLogin")
- .HasColumnType("boolean");
-
- b.Property("Enabled")
- .HasColumnType("boolean");
-
- b.Property("FrontChannelLogoutSessionRequired")
- .HasColumnType("boolean");
-
- b.Property("FrontChannelLogoutUri")
- .HasColumnType("text");
-
- b.Property("IdentityTokenLifetime")
- .HasColumnType("integer");
-
- b.Property("IncludeJwtId")
- .HasColumnType("boolean");
-
- b.Property("LastAccessed")
- .HasColumnType("timestamp with time zone");
-
- b.Property("LogoUri")
- .HasColumnType("text");
-
- b.Property("NonEditable")
- .HasColumnType("boolean");
-
- b.Property("PairWiseSubjectSalt")
- .HasColumnType("text");
-
- b.Property("ProtocolType")
- .HasColumnType("text");
-
- b.Property("RefreshTokenExpiration")
- .HasColumnType("integer");
-
- b.Property("RefreshTokenUsage")
- .HasColumnType("integer");
-
- b.Property("RequireClientSecret")
- .HasColumnType("boolean");
-
- b.Property("RequireConsent")
- .HasColumnType("boolean");
-
- b.Property("RequirePkce")
- .HasColumnType("boolean");
-
- b.Property