Bug fix - notifications audience
* Performers had their notifications publicaly published. * refacto admin group name
This commit is contained in:
parent
d192705f29
commit
d476c08046
36 changed files with 421 additions and 74 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -1,5 +1,16 @@
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.0.8-rc32] - unstable
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Performer notification audience
|
||||||
|
|
||||||
## [1.0.8-rc31] - unstable
|
## [1.0.8-rc31] - unstable
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -16,7 +27,6 @@
|
||||||
* avoid NRE in MailSender on rdv query notifications
|
* avoid NRE in MailSender on rdv query notifications
|
||||||
* Android emulator smoke test skipped in CI because Calabash instrumentation signature mismatches the target APK on hosted runners
|
* Android emulator smoke test skipped in CI because Calabash instrumentation signature mismatches the target APK on hosted runners
|
||||||
|
|
||||||
|
|
||||||
## [1.0.8-rc30] - unstable
|
## [1.0.8-rc30] - unstable
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ La racine de l'architecture est [Architecture.md](Architecture.md).
|
||||||
| [architecture/offres-frontmatter.md](architecture/offres-frontmatter.md) | Offre fournisseur, ClasseFormulaire, ClasseDevis, parsing frontmatter |
|
| [architecture/offres-frontmatter.md](architecture/offres-frontmatter.md) | Offre fournisseur, ClasseFormulaire, ClasseDevis, parsing frontmatter |
|
||||||
| [architecture/postit-oidc.md](architecture/postit-oidc.md) | Client desktop PostIt, custom URI scheme, silent refresh |
|
| [architecture/postit-oidc.md](architecture/postit-oidc.md) | Client desktop PostIt, custom URI scheme, silent refresh |
|
||||||
| [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding |
|
| [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding |
|
||||||
|
| [architecture/notifications.md](architecture/notifications.md) | Notifications persistées, messages Web et événements temps réel côté serveur, Web et PostIt |
|
||||||
| [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md) | Découpage des projets .NET (Abstract, Server, Org, Api, Blogs, Web, Org.Tests) |
|
| [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é |
|
| [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 |
|
| [onboarding-agents.md](onboarding-agents.md) | Parcours pas-à-pas pour prise en main agents IA + architecture + tests |
|
||||||
|
|
|
||||||
229
doc/architecture/notifications.md
Normal file
229
doc/architecture/notifications.md
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
# Notifications Yavsc : serveur, Web et PostIt
|
||||||
|
|
||||||
|
Ce document décrit les mécanismes de notification actuellement présents
|
||||||
|
dans le dépôt. Le terme « notification » recouvre plusieurs flux distincts :
|
||||||
|
|
||||||
|
1. les **annonces persistées** en base, affichées dans le site Web et sur
|
||||||
|
l'accueil de PostIt ;
|
||||||
|
2. les **messages temporaires de l'interface Web**, attachés à une réponse
|
||||||
|
MVC par `ViewBag.Notify` ;
|
||||||
|
3. les **événements temps réel** du chat et les événements métier diffusés par
|
||||||
|
SignalR.
|
||||||
|
|
||||||
|
Ces flux n'ont ni le même stockage, ni le même cycle de vie, ni les mêmes
|
||||||
|
clients.
|
||||||
|
|
||||||
|
## Vue d'ensemble
|
||||||
|
|
||||||
|
| Flux | Serveur / transport | Web | PostIt |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Annonce persistée | Entité `Notification` en base, API HTTP | Affichée dans le layout et masquable | Liste sur `HomePage`, chargée par HTTP et masquable |
|
||||||
|
| Message d'interface | Liste de portée requête dans `ViewBag.Notify` | Affichée comme alerte dans le layout | Non consommé |
|
||||||
|
| Événement de chat | Hub SignalR authentifié à `/chathub` | Le site Web n'en consomme pas dans ses vues MVC | `notifyUser` et `notifyRoom` alimentent les journaux du chat |
|
||||||
|
| Événement métier `push` | SignalR vers les connexions actives, plus e-mail | Pas de consommateur identifié dans les vues Web | Pas de gestionnaire `push` dans le client PostIt actuel |
|
||||||
|
|
||||||
|
Les notifications persistées sont donc différentes d'une notification système
|
||||||
|
native (notification du navigateur ou du système d'exploitation). Le client
|
||||||
|
PostIt affiche une liste dans sa page d'accueil ; il ne demande pas de
|
||||||
|
permission de notification native.
|
||||||
|
|
||||||
|
## 1. Annonces persistées
|
||||||
|
|
||||||
|
### Modèle et stockage
|
||||||
|
|
||||||
|
L'entité partagée [`Notification`](../../src/Yavsc.Abstract/Messaging/Notification.cs)
|
||||||
|
est enregistrée dans la table `Notification` via `ApplicationDbContext`.
|
||||||
|
Elle contient :
|
||||||
|
|
||||||
|
- `Id`, identifiant généré ;
|
||||||
|
- `title` et `body`, obligatoires, limités respectivement à 1 024 et 512
|
||||||
|
caractères ;
|
||||||
|
- `icon`, `sound`, `tag`, `color`, propriétés optionnelles ;
|
||||||
|
- `click_action`, obligatoire, utilisé comme libellé d'action dans les
|
||||||
|
interfaces ;
|
||||||
|
- `Target`, chaîne de ciblage. Les valeurs documentées par le modèle sont
|
||||||
|
`null` (tout le monde), `user/{UserId}`, `pub/{topic}` et
|
||||||
|
`administration`.
|
||||||
|
|
||||||
|
Le masquage d'une annonce pour un utilisateur connecté est stocké dans
|
||||||
|
`DismissClicked`, avec une clé composée de l'identifiant utilisateur et de
|
||||||
|
l'identifiant de notification. Le même avis peut ainsi être masqué
|
||||||
|
indépendamment par chaque utilisateur.
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
[`NotificationsApiController`](../../src/Yavsc.Api/Controllers/Administration/NotificationsApiController.cs)
|
||||||
|
expose les deux routes GET suivantes, sous le préfixe `/api/v1` :
|
||||||
|
|
||||||
|
| Route | Accès | Résultat |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET /api/v1/notifications` | Anonyme ou authentifié | Notifications visibles à l'appelant, non masquées |
|
||||||
|
| `GET /api/v1/notifications/me` | Anonyme ou authentifié | Même action et mêmes règles que la route sans suffixe |
|
||||||
|
|
||||||
|
La réponse est une liste JSON triée par `Id` décroissant. L'API exclut les
|
||||||
|
notifications masquées par l'utilisateur authentifié, puis applique les
|
||||||
|
règles d'audience : cible vide, cible `pub/…`, cible correspondant à
|
||||||
|
`user/{identifiant courant}`, ou cible `administration` pour un administrateur.
|
||||||
|
Une requête anonyme n'a ni identifiant utilisateur ni rôle administrateur.
|
||||||
|
|
||||||
|
[`DismissClicksApiController`](../../src/Yavsc.Api/Controllers/Administration/DismissClicksApiController.cs)
|
||||||
|
est réservé aux utilisateurs authentifiés :
|
||||||
|
|
||||||
|
| Route | Effet |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/v1/dismiss` | Liste les identifiants masqués par l'utilisateur |
|
||||||
|
| `POST /api/v1/dismiss/click/{notificationId}` | Masque l'avis pour l'utilisateur ; l'opération est idempotente |
|
||||||
|
| `DELETE /api/v1/dismiss/click/{notificationId}` | Rétablit l'avis s'il était masqué ; renvoie `404` sinon |
|
||||||
|
|
||||||
|
Le POST renvoie `404` si l'identifiant de notification n'existe pas. Le modèle
|
||||||
|
de réponse est [`DismissedNotification`](../../src/Yavsc.Abstract/Messaging/DismissedNotification.cs).
|
||||||
|
|
||||||
|
### Création et notifications métier
|
||||||
|
|
||||||
|
Le contrôleur MVC
|
||||||
|
[`NotificationsController`](../../src/Yavsc.Org/Controllers/Communicating/NotificationsController.cs)
|
||||||
|
permet la gestion CRUD des enregistrements de notification.
|
||||||
|
|
||||||
|
Le service [`YavscMessageSender`](../../src/Yavsc.Server/Services/YavscMessageSender.cs)
|
||||||
|
crée aussi une notification persistée, ciblée sur `user/{id}`, pour les
|
||||||
|
événements `RdvQueryEvent` et `ClientEstimateSignedEvent`. L'enregistrement
|
||||||
|
est effectué avant l'envoi d'e-mail. Le même service tente ensuite d'envoyer
|
||||||
|
un e-mail si l'adresse est disponible et confirmée, puis diffuse l'événement
|
||||||
|
`push` aux connexions SignalR actives de l'utilisateur.
|
||||||
|
|
||||||
|
Cette diffusion `push` est conditionnée par la présence de connexions actives ;
|
||||||
|
l'avis stocké en base reste, lui, disponible lors d'une récupération ultérieure
|
||||||
|
par l'API.
|
||||||
|
|
||||||
|
## 2. Site Web
|
||||||
|
|
||||||
|
### Annonces persistées sur l'accueil
|
||||||
|
|
||||||
|
[`HomeController.Index`](../../src/Yavsc.Org/Controllers/HomeController.cs)
|
||||||
|
charge les notifications non masquées et les place dans `ViewBag.Notify`.
|
||||||
|
[`_Layout.cshtml`](../../src/Yavsc.Org/Views/Shared/_Layout.cshtml) les rend
|
||||||
|
comme des alertes Bootstrap avec icône, titre, corps et action. Le titre et le
|
||||||
|
corps passent par les composants Markdown du site.
|
||||||
|
|
||||||
|
Le clic sur l'action appelle `POST /Home/DismissNotification/{id}` via
|
||||||
|
[`site.js`](../../src/Yavsc.Org/wwwroot/js/site.js) :
|
||||||
|
|
||||||
|
- pour un utilisateur connecté, le masquage est enregistré dans
|
||||||
|
`DismissClicked` ;
|
||||||
|
- pour un visiteur anonyme, les identifiants sont conservés dans la session
|
||||||
|
Web (`clicked`).
|
||||||
|
|
||||||
|
La route renvoie `404` si l'avis n'existe pas et `204` après le masquage.
|
||||||
|
Comme l'API, l'accueil Web filtre `Target` : les cibles `user/{id}` ne sont
|
||||||
|
visibles qu'à l'utilisateur correspondant, et la cible `administration`
|
||||||
|
uniquement aux administrateurs. Un visiteur anonyme ne reçoit que les
|
||||||
|
notifications publiques (`Target` vide ou préfixe `pub/`).
|
||||||
|
|
||||||
|
### Messages temporaires par requête
|
||||||
|
|
||||||
|
Les helpers [`NotifyInfo`, `NotifyWarning` et `Notify`](../../src/Yavsc.Org/Helpers/ControllerHelpers.cs)
|
||||||
|
ajoutent des objets à `ViewBag.Notify`. Ce mécanisme n'écrit pas en base :
|
||||||
|
il ne vit que dans la réponse MVC qui rend le layout. Les méthodes
|
||||||
|
`NotifyInfo` et `NotifyWarning` partagent actuellement le même rendu générique
|
||||||
|
`alert-info`.
|
||||||
|
|
||||||
|
## 3. PostIt
|
||||||
|
|
||||||
|
[`NotificationsApiClient`](../../src/Yavsc.Api.Client/NotificationsApiClient.cs)
|
||||||
|
construit les routes à partir de la base d'API de l'hôte métier configuré.
|
||||||
|
Il fournit les méthodes de lecture publique et authentifiée, ainsi que les
|
||||||
|
opérations de lecture, masquage et restauration.
|
||||||
|
|
||||||
|
Sur [`HomePage`](../../src/PostIt/PostIt/Views/Layout/HomePage.axaml),
|
||||||
|
[`HomePageViewModel`](../../src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs)
|
||||||
|
charge la liste :
|
||||||
|
|
||||||
|
- sans session valide, il appelle la route publique
|
||||||
|
`GET /api/v1/notifications` ;
|
||||||
|
- avec une session valide, il appelle
|
||||||
|
`GET /api/v1/notifications/me` avec le client HTTP authentifié.
|
||||||
|
|
||||||
|
Le chargement est lancé à l'ouverture de la page et relancé lorsque l'état de
|
||||||
|
connexion change. Un compteur de version ignore les réponses obsolètes si un
|
||||||
|
chargement plus récent a déjà commencé. L'interface distingue l'état de
|
||||||
|
chargement, la liste vide et l'erreur de chargement ; l'utilisateur peut aussi
|
||||||
|
actualiser manuellement.
|
||||||
|
|
||||||
|
Le bouton d'une notification utilise `click_action` comme libellé et masque
|
||||||
|
l'élément. En session authentifiée, PostIt appelle
|
||||||
|
`POST /api/v1/dismiss/click/{id}` avant de retirer l'élément de la liste. Si
|
||||||
|
l'appel échoue, l'élément reste affiché et une erreur est présentée. Sans
|
||||||
|
session, l'élément est retiré de la liste locale, sans persistance du
|
||||||
|
masquage côté serveur.
|
||||||
|
|
||||||
|
## 4. Événements temps réel
|
||||||
|
|
||||||
|
### Hub du chat
|
||||||
|
|
||||||
|
Le hub [`ChatHub`](../../src/Yavsc.Server/Hubs/ChatHub.cs) est publié par
|
||||||
|
Yavsc.Api à la racine sous `/chathub` (et non sous `/api/v1`) et porte
|
||||||
|
`[Authorize]`. Il s'appuie sur SignalR et sur `IConnexionManager` pour suivre
|
||||||
|
les utilisateurs et les salons.
|
||||||
|
|
||||||
|
Les événements de notification émis par le hub comprennent notamment :
|
||||||
|
|
||||||
|
| Événement SignalR | Données | Utilisation PostIt |
|
||||||
|
|---|---|---|
|
||||||
|
| `notifyUser` | `type`, identifiant ou nom cible, message | Ajout au journal système |
|
||||||
|
| `notifyRoom` | `type`, salon, utilisateur ou détail | Ajout au journal du salon ; met à jour la liste d'utilisateurs sur `UserJoin` |
|
||||||
|
| `joint` | `ChatRoomInfo` | Initialise la vue du salon rejoint |
|
||||||
|
| `ReceiveMessage` | message avec nom, salon et corps | Ajoute le message à l'historique du salon |
|
||||||
|
| `notifyUserInRoom` | `type`, salon, message | Pas de gestionnaire correspondant dans `ChatViewModel` actuellement |
|
||||||
|
|
||||||
|
Les constantes de types du chat sont déclarées dans
|
||||||
|
[`NotificationTypes`](../../src/Yavsc.Abstract/Messaging/NotificationTypes.cs).
|
||||||
|
Les messages `notifyUser` et `notifyRoom` ne sont pas enregistrés en base :
|
||||||
|
ils décrivent l'état temps réel de connexion, de salon ou les erreurs de
|
||||||
|
conversation.
|
||||||
|
|
||||||
|
Le [`ChatViewModel`](../../src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs)
|
||||||
|
ouvre le hub avec le jeton d'accès PostIt, tente un renouvellement silencieux
|
||||||
|
du jeton, et active la reconnexion automatique. Après reconnexion, il rejoint
|
||||||
|
à nouveau le salon sélectionné, car la connexion SignalR précédente et ses
|
||||||
|
groupes ne sont plus actifs. Les callbacks mettent à jour les collections sur
|
||||||
|
le thread UI d'Avalonia.
|
||||||
|
|
||||||
|
### Événement métier `push`
|
||||||
|
|
||||||
|
En plus des événements du chat, `YavscMessageSender` utilise le même contexte
|
||||||
|
SignalR pour émettre `push` avec le topic et une charge utile sérialisée. Ce
|
||||||
|
nom d'événement est distinct de `notifyUser` et `notifyRoom`. Aucun
|
||||||
|
gestionnaire `push` n'est présent dans le client PostIt actuel : les annonces
|
||||||
|
métier persistées sont visibles dans PostIt lors du prochain chargement ou
|
||||||
|
rafraîchissement HTTP.
|
||||||
|
|
||||||
|
## 5. Points d'attention pour les évolutions
|
||||||
|
|
||||||
|
- Conserver les règles d'audience alignées entre l'API et le site Web ; en
|
||||||
|
particulier, appliquer un filtrage `Target` côté Web avant d'exposer les
|
||||||
|
avis dans le layout.
|
||||||
|
- Ne pas confondre masquage utilisateur (`DismissClicked`) et suppression
|
||||||
|
globale de la notification : le masquage API est par utilisateur.
|
||||||
|
- Les sessions anonymes du site Web et PostIt n'ont pas le même comportement :
|
||||||
|
le site Web conserve le masquage en session, tandis que PostIt ne le garde
|
||||||
|
que dans la liste affichée jusqu'au prochain rechargement.
|
||||||
|
- Ajouter un nouveau type de cible ou un nouvel événement temps réel implique
|
||||||
|
d'examiner séparément son filtrage, son transport et les clients qui
|
||||||
|
l'écoutent.
|
||||||
|
- Les références à GCM et le nom historique de `YavscMessageSender` ne
|
||||||
|
signifient pas que PostIt reçoit une notification native hors ligne : le
|
||||||
|
chemin observé ici est l'e-mail, l'événement SignalR temps réel et la
|
||||||
|
notification persistée récupérée par HTTP.
|
||||||
|
|
||||||
|
## Références d'implémentation
|
||||||
|
|
||||||
|
- [`Notification`](../../src/Yavsc.Abstract/Messaging/Notification.cs)
|
||||||
|
- [`NotificationsApiController`](../../src/Yavsc.Api/Controllers/Administration/NotificationsApiController.cs)
|
||||||
|
- [`DismissClicksApiController`](../../src/Yavsc.Api/Controllers/Administration/DismissClicksApiController.cs)
|
||||||
|
- [`NotificationsController`](../../src/Yavsc.Org/Controllers/Communicating/NotificationsController.cs)
|
||||||
|
- [`HomeController`](../../src/Yavsc.Org/Controllers/HomeController.cs)
|
||||||
|
- [`YavscMessageSender`](../../src/Yavsc.Server/Services/YavscMessageSender.cs)
|
||||||
|
- [`ChatHub`](../../src/Yavsc.Server/Hubs/ChatHub.cs)
|
||||||
|
- [`NotificationsApiClient`](../../src/Yavsc.Api.Client/NotificationsApiClient.cs)
|
||||||
|
- [`HomePageViewModel`](../../src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs)
|
||||||
|
- [`ChatViewModel`](../../src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs)
|
||||||
|
|
@ -103,7 +103,7 @@ public partial class SessionStatusViewModel : ViewModelBase
|
||||||
if (profile is null)
|
if (profile is null)
|
||||||
throw new InvalidOperationException("Le serveur a renvoyé un profil vide.");
|
throw new InvalidOperationException("Le serveur a renvoyé un profil vide.");
|
||||||
if (version == _accessVersion)
|
if (version == _accessVersion)
|
||||||
IsAdministrator = profile.Roles.Contains(Yavsc.Constants.AdminGroupName, StringComparer.Ordinal);
|
IsAdministrator = profile.Roles.Contains(Yavsc.Constants.AdminName, StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace Yavsc
|
||||||
public const string CirclePath = "circle";
|
public const string CirclePath = "circle";
|
||||||
public const string CommentsPath = "blogcomments";
|
public const string CommentsPath = "blogcomments";
|
||||||
public const string ChatHubPath = "chathub";
|
public const string ChatHubPath = "chathub";
|
||||||
|
public const string Administration = "administration";
|
||||||
|
|
||||||
public static readonly Scope[] SiteScopes = {
|
public static readonly Scope[] SiteScopes = {
|
||||||
new Scope { Id = "profile", Description = "Your profile informations" },
|
new Scope { Id = "profile", Description = "Your profile informations" },
|
||||||
|
|
@ -52,7 +53,7 @@ namespace Yavsc
|
||||||
public const string DefaultFactor = "Default";
|
public const string DefaultFactor = "Default";
|
||||||
public const string MobileAppFactor = "Mobile Application";
|
public const string MobileAppFactor = "Mobile Application";
|
||||||
public const string SMSFactor = "SMS";
|
public const string SMSFactor = "SMS";
|
||||||
public const string AdminGroupName = "Administrator";
|
public const string AdminName = "Administrator";
|
||||||
public const string PerformerGroupName = "Performer";
|
public const string PerformerGroupName = "Performer";
|
||||||
public const string StarGroupName = "Star";
|
public const string StarGroupName = "Star";
|
||||||
public const string StarHunterGroupName = "StarHunter";
|
public const string StarHunterGroupName = "StarHunter";
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public sealed class NotificationsApiController : Controller
|
||||||
public async Task<ActionResult<List<Notification>>> GetCurrentUserNotifications()
|
public async Task<ActionResult<List<Notification>>> GetCurrentUserNotifications()
|
||||||
{
|
{
|
||||||
var currentUserId = User.GetUserId();
|
var currentUserId = User.GetUserId();
|
||||||
var isAdmin = User.IsInRole(Constants.AdminGroupName);
|
var isAdmin = User.IsInRole(Constants.AdminName);
|
||||||
|
|
||||||
var dismissedIds = string.IsNullOrWhiteSpace(currentUserId)
|
var dismissedIds = string.IsNullOrWhiteSpace(currentUserId)
|
||||||
? new HashSet<long>()
|
? new HashSet<long>()
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ public sealed class ResourceUsageApiController : Controller
|
||||||
[HttpGet(Constants.ResourceUsageAdminRoute)]
|
[HttpGet(Constants.ResourceUsageAdminRoute)]
|
||||||
public async Task<ActionResult<List<ResourceUsageSummary>>> GetAdminOverview()
|
public async Task<ActionResult<List<ResourceUsageSummary>>> GetAdminOverview()
|
||||||
{
|
{
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using Yavsc.Api.Services;
|
||||||
namespace Yavsc.ApiControllers.Administration;
|
namespace Yavsc.ApiControllers.Administration;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[ApiExplorerSettings(GroupName = "administration")]
|
[ApiExplorerSettings(GroupName = Constants.Administration)]
|
||||||
[Authorize("AdministratorOnly")]
|
[Authorize("AdministratorOnly")]
|
||||||
[Route(Constants.APIPrefix + "/administration")]
|
[Route(Constants.APIPrefix + "/administration")]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ namespace Yavsc.ApiControllers
|
||||||
logger.LogWarning("Got bill ack:"+bill.GetIsAcquitted().ToString());
|
logger.LogWarning("Got bill ack:"+bill.GetIsAcquitted().ToString());
|
||||||
if (bill is not NominativeServiceCommand { InvoiceEstimateId: not null } invoice)
|
if (bill is not NominativeServiceCommand { InvoiceEstimateId: not null } invoice)
|
||||||
return Conflict(new { Error = "Create the invoice from a signed estimate first." });
|
return Conflict(new { Error = "Create the invoice from a signed estimate first." });
|
||||||
if (User.GetUserId() != invoice.InvoicePerformerId && !User.IsInRole(Constants.AdminGroupName))
|
if (User.GetUserId() != invoice.InvoicePerformerId && !User.IsInRole(Constants.AdminName))
|
||||||
return Forbid();
|
return Forbid();
|
||||||
return ViewComponent("Bill",new object[] { billingCode, bill, OutputFormat.Pdf, true } );
|
return ViewComponent("Bill",new object[] { billingCode, bill, OutputFormat.Pdf, true } );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
bool UserIsAdminOrThis(string uid)
|
bool UserIsAdminOrThis(string uid)
|
||||||
{
|
{
|
||||||
if (User.IsInRole(Constants.AdminGroupName)) return true;
|
if (User.IsInRole(Constants.AdminName)) return true;
|
||||||
return uid == User.GetUserId();
|
return uid == User.GetUserId();
|
||||||
}
|
}
|
||||||
bool UserIsAdminOrInThese(string oid, string uid)
|
bool UserIsAdminOrInThese(string oid, string uid)
|
||||||
{
|
{
|
||||||
if (User.IsInRole(Constants.AdminGroupName)) return true;
|
if (User.IsInRole(Constants.AdminName)) return true;
|
||||||
var cuid = User.GetUserId();
|
var cuid = User.GetUserId();
|
||||||
return cuid == uid || cuid == oid;
|
return cuid == uid || cuid == oid;
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ namespace Yavsc.Controllers
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
if (uid != estimate.OwnerId)
|
if (uid != estimate.OwnerId)
|
||||||
{
|
{
|
||||||
|
|
@ -115,7 +115,7 @@ namespace Yavsc.Controllers
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (estimate.OwnerId == null) estimate.OwnerId = uid;
|
if (estimate.OwnerId == null) estimate.OwnerId = uid;
|
||||||
|
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
if (uid != estimate.OwnerId)
|
if (uid != estimate.OwnerId)
|
||||||
{
|
{
|
||||||
|
|
@ -185,7 +185,7 @@ namespace Yavsc.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
if (uid != estimate.OwnerId)
|
if (uid != estimate.OwnerId)
|
||||||
{
|
{
|
||||||
|
|
@ -285,7 +285,7 @@ namespace Yavsc.Controllers
|
||||||
if (estimate == null) return NotFound();
|
if (estimate == null) return NotFound();
|
||||||
|
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (!User.IsInRole(Constants.AdminGroupName) && uid != estimate.OwnerId)
|
if (!User.IsInRole(Constants.AdminName) && uid != estimate.OwnerId)
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
if (body == null || body.FileId <= 0)
|
if (body == null || body.FileId <= 0)
|
||||||
|
|
@ -319,7 +319,7 @@ namespace Yavsc.Controllers
|
||||||
if (estimate == null) return NotFound();
|
if (estimate == null) return NotFound();
|
||||||
|
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (!User.IsInRole(Constants.AdminGroupName) && uid != estimate.OwnerId)
|
if (!User.IsInRole(Constants.AdminName) && uid != estimate.OwnerId)
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
var link = _context.EstimateAttachedFiles
|
var link = _context.EstimateAttachedFiles
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (estimateTemplate.OwnerId!=uid)
|
if (estimateTemplate.OwnerId!=uid)
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
_context.Entry(estimateTemplate).State = EntityState.Modified;
|
_context.Entry(estimateTemplate).State = EntityState.Modified;
|
||||||
|
|
@ -133,7 +133,7 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (estimateTemplate.OwnerId!=uid)
|
if (estimateTemplate.OwnerId!=uid)
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
_context.EstimateTemplates.Remove(estimateTemplate);
|
_context.EstimateTemplates.Remove(estimateTemplate);
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ namespace Yavsc.ApiControllers
|
||||||
if (estimate is null) return NotFound(new { Error = "no estimate linked to this query" });
|
if (estimate is null) return NotFound(new { Error = "no estimate linked to this query" });
|
||||||
|
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (!User.IsInRole(Constants.AdminGroupName)
|
if (!User.IsInRole(Constants.AdminName)
|
||||||
&& uid != estimate.ClientId && uid != estimate.OwnerId)
|
&& uid != estimate.ClientId && uid != estimate.OwnerId)
|
||||||
return Forbid();
|
return Forbid();
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Yavsc.ApiControllers
|
||||||
if (estimate is null) return NotFound(new { Error = "no estimate linked to this query" });
|
if (estimate is null) return NotFound(new { Error = "no estimate linked to this query" });
|
||||||
|
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (!User.IsInRole(Constants.AdminGroupName)
|
if (!User.IsInRole(Constants.AdminName)
|
||||||
&& uid != estimate.ClientId && uid != estimate.OwnerId)
|
&& uid != estimate.ClientId && uid != estimate.OwnerId)
|
||||||
return Forbid();
|
return Forbid();
|
||||||
|
|
||||||
|
|
@ -403,7 +403,7 @@ namespace Yavsc.ApiControllers
|
||||||
if (isProvider) return (query, ActorRole.Provider, null);
|
if (isProvider) return (query, ActorRole.Provider, null);
|
||||||
if (isClient) return (query, ActorRole.Client, null);
|
if (isClient) return (query, ActorRole.Client, null);
|
||||||
|
|
||||||
if (User.IsInRole(Constants.AdminGroupName))
|
if (User.IsInRole(Constants.AdminName))
|
||||||
return (query, ActorRole.Admin, null);
|
return (query, ActorRole.Admin, null);
|
||||||
|
|
||||||
return (null, ActorRole.None, Forbid());
|
return (null, ActorRole.None, Forbid());
|
||||||
|
|
@ -453,7 +453,7 @@ namespace Yavsc.ApiControllers
|
||||||
if (error is not null) return error;
|
if (error is not null) return error;
|
||||||
if (query is null) return BadRequest(new { Error = "query not found" });
|
if (query is null) return BadRequest(new { Error = "query not found" });
|
||||||
|
|
||||||
if (role != ActorRole.Client && !User.IsInRole(Constants.AdminGroupName))
|
if (role != ActorRole.Client && !User.IsInRole(Constants.AdminName))
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
if (body == null || body.FileId <= 0)
|
if (body == null || body.FileId <= 0)
|
||||||
|
|
@ -463,7 +463,7 @@ namespace Yavsc.ApiControllers
|
||||||
var file = await dbContext.UploadedFiles
|
var file = await dbContext.UploadedFiles
|
||||||
.FirstOrDefaultAsync(f => f.Id == body.FileId, token);
|
.FirstOrDefaultAsync(f => f.Id == body.FileId, token);
|
||||||
if (file == null) return BadRequest(new { error = "file not found" });
|
if (file == null) return BadRequest(new { error = "file not found" });
|
||||||
if (file.OwnerId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (file.OwnerId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
var existing = await dbContext.QueryAttachedFiles
|
var existing = await dbContext.QueryAttachedFiles
|
||||||
|
|
@ -490,7 +490,7 @@ namespace Yavsc.ApiControllers
|
||||||
if (error is not null) return error;
|
if (error is not null) return error;
|
||||||
if (query is null) return BadRequest(new { Error = "query not found" });
|
if (query is null) return BadRequest(new { Error = "query not found" });
|
||||||
|
|
||||||
if (role != ActorRole.Client && !User.IsInRole(Constants.AdminGroupName))
|
if (role != ActorRole.Client && !User.IsInRole(Constants.AdminName))
|
||||||
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
return new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
var link = await dbContext.QueryAttachedFiles
|
var link = await dbContext.QueryAttachedFiles
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public class HairCutQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +141,7 @@ public class HairCutQueryApiController : Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (existing.ClientId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +185,7 @@ public class HairCutQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (query.ClientId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -234,4 +234,4 @@ public class HairCutQueryApiController : Controller
|
||||||
Details = prestation.GetDisplayDetails(),
|
Details = prestation.GetDisplayDetails(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ public class HairMultiCutQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +152,7 @@ public class HairMultiCutQueryApiController : Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (existing.ClientId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +200,7 @@ public class HairMultiCutQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (query.ClientId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -285,4 +285,4 @@ public class HairMultiCutQueryApiController : Controller
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public sealed class InvoicesController(IInvoicingService invoices, ILogger<Invoi
|
||||||
public Task<ActionResult<InvoiceResponse>> VerifyPayPal(long commandId, VerifyInvoicePayPalRequest request, CancellationToken ct)
|
public Task<ActionResult<InvoiceResponse>> VerifyPayPal(long commandId, VerifyInvoicePayPalRequest request, CancellationToken ct)
|
||||||
=> ExecuteAsync(() => invoices.VerifyPayPalAsync(commandId, request.TransactionId, User.GetUserId(), IsAdmin, ct));
|
=> ExecuteAsync(() => invoices.VerifyPayPalAsync(commandId, request.TransactionId, User.GetUserId(), IsAdmin, ct));
|
||||||
|
|
||||||
private bool IsAdmin => User.IsInRole(Constants.AdminGroupName);
|
private bool IsAdmin => User.IsInRole(Constants.AdminName);
|
||||||
|
|
||||||
private async Task<ActionResult<InvoiceResponse>> ExecuteAsync(Func<Task<InvoiceResponse>> action, bool created = false)
|
private async Task<ActionResult<InvoiceResponse>> ExecuteAsync(Func<Task<InvoiceResponse>> action, bool created = false)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ public class RdvQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +157,7 @@ public class RdvQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (existing.ClientId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +222,7 @@ public class RdvQueryApiController : Controller
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName))
|
if (query.ClientId != uid && !User.IsInRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (blogpost.AuthorId!=uid)
|
if (blogpost.AuthorId!=uid)
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
|
|
||||||
_context.SaveChanges(User.GetUserId());
|
_context.SaveChanges(User.GetUserId());
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (uid != blackListed.OwnerId)
|
if (uid != blackListed.OwnerId)
|
||||||
if (!User.IsInRole(Constants.AdminGroupName))
|
if (!User.IsInRole(Constants.AdminName))
|
||||||
if (!User.IsInRole(Constants.FrontOfficeGroupName))
|
if (!User.IsInRole(Constants.FrontOfficeGroupName))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (uid != chatRoomAccess.UserId && uid != chatRoomAccess.Room.OwnerId
|
if (uid != chatRoomAccess.UserId && uid != chatRoomAccess.Room.OwnerId
|
||||||
&& ! User.IsInMsRole(Constants.AdminGroupName))
|
&& ! User.IsInMsRole(Constants.AdminName))
|
||||||
|
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("UserId","get refused");
|
ModelState.AddModelError("UserId","get refused");
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName );
|
var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName );
|
||||||
|
|
||||||
if (uid != room.OwnerId && ! User.IsInMsRole(Constants.AdminGroupName))
|
if (uid != room.OwnerId && ! User.IsInMsRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("ChannelName", "access put refused");
|
ModelState.AddModelError("ChannelName", "access put refused");
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
|
|
@ -110,7 +110,7 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName );
|
var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName );
|
||||||
if (room == null || (uid != room.OwnerId && ! User.IsInMsRole(Constants.AdminGroupName)))
|
if (room == null || (uid != room.OwnerId && ! User.IsInMsRole(Constants.AdminName)))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("ChannelName", "access post refused");
|
ModelState.AddModelError("ChannelName", "access post refused");
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
|
|
@ -154,7 +154,7 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName );
|
var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName );
|
||||||
if (room == null || (uid != room.OwnerId && chatRoomAccess.UserId != uid && ! User.IsInMsRole(Constants.AdminGroupName)))
|
if (room == null || (uid != room.OwnerId && chatRoomAccess.UserId != uid && ! User.IsInMsRole(Constants.AdminName)))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("UserId", "access drop refused");
|
ModelState.AddModelError("UserId", "access drop refused");
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ namespace Yavsc.Controllers
|
||||||
|
|
||||||
if (User.GetUserId() != chatRoom.OwnerId )
|
if (User.GetUserId() != chatRoom.OwnerId )
|
||||||
{
|
{
|
||||||
if (!User.IsInMsRole(Constants.AdminGroupName))
|
if (!User.IsInMsRole(Constants.AdminName))
|
||||||
return BadRequest(new {error = "OwnerId"});
|
return BadRequest(new {error = "OwnerId"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ internal class Program
|
||||||
{
|
{
|
||||||
policy
|
policy
|
||||||
.RequireAuthenticatedUser()
|
.RequireAuthenticatedUser()
|
||||||
.RequireClaim(Constants.RoleClaimType, Constants.AdminGroupName);
|
.RequireClaim(Constants.RoleClaimType, Constants.AdminName);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.AddYavscCors(builder.Configuration)
|
.AddYavscCors(builder.Configuration)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ public sealed partial class AdministrationService(
|
||||||
|
|
||||||
public async Task<AdministrationSummary> GetSummaryAsync(CancellationToken ct)
|
public async Task<AdministrationSummary> GetSummaryAsync(CancellationToken ct)
|
||||||
=> new(await db.Users.CountAsync(ct),
|
=> new(await db.Users.CountAsync(ct),
|
||||||
(await users.GetUsersInRoleAsync(Constants.AdminGroupName)).Count,
|
(await users.GetUsersInRoleAsync(Constants.AdminName)).Count,
|
||||||
typeof(AdministrationService).Assembly.GetName().ToString(), System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
|
typeof(AdministrationService).Assembly.GetName().ToString(), System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
|
||||||
|
|
||||||
public async Task<IReadOnlyList<AdministrationRole>> GetRolesAsync(CancellationToken ct)
|
public async Task<IReadOnlyList<AdministrationRole>> GetRolesAsync(CancellationToken ct)
|
||||||
|
|
@ -46,7 +46,7 @@ public sealed partial class AdministrationService(
|
||||||
|
|
||||||
public async Task EnsureRolesAsync(CancellationToken ct)
|
public async Task EnsureRolesAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
foreach (var name in new[] { Constants.AdminGroupName, Constants.StarGroupName, Constants.PerformerGroupName,
|
foreach (var name in new[] { Constants.AdminName, Constants.StarGroupName, Constants.PerformerGroupName,
|
||||||
Constants.FrontOfficeGroupName, Constants.StarHunterGroupName, Constants.BlogModeratorGroupName })
|
Constants.FrontOfficeGroupName, Constants.StarHunterGroupName, Constants.BlogModeratorGroupName })
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
@ -73,7 +73,7 @@ public sealed partial class AdministrationService(
|
||||||
CheckIdentity(await users.AddToRoleAsync(user, role.Name!));
|
CheckIdentity(await users.AddToRoleAsync(user, role.Name!));
|
||||||
else if (!member && alreadyMember)
|
else if (!member && alreadyMember)
|
||||||
{
|
{
|
||||||
if (role.Name == Constants.AdminGroupName &&
|
if (role.Name == Constants.AdminName &&
|
||||||
(await users.GetUsersInRoleAsync(role.Name)).Count <= 1)
|
(await users.GetUsersInRoleAsync(role.Name)).Count <= 1)
|
||||||
throw Conflict("Impossible de retirer le dernier administrateur.");
|
throw Conflict("Impossible de retirer le dernier administrateur.");
|
||||||
CheckIdentity(await users.RemoveFromRoleAsync(user, role.Name!));
|
CheckIdentity(await users.RemoveFromRoleAsync(user, role.Name!));
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ public static class AdministrationServiceCollectionExtensions
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
services.AddScoped<IAdministrationService, AdministrationService>();
|
services.AddScoped<IAdministrationService, AdministrationService>();
|
||||||
services.AddScoped<AdministrationExceptionFilter>();
|
services.AddScoped<AdministrationExceptionFilter>();
|
||||||
services.AddOpenApi("administration", options =>
|
services.AddOpenApi(Constants.Administration, options =>
|
||||||
options.ShouldInclude = description => description.GroupName == "administration");
|
options.ShouldInclude = description => description.GroupName == Constants.Administration);
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
// autorisés comme pour le reste du workflow.
|
// autorisés comme pour le reste du workflow.
|
||||||
bool CanReadFile(UploadedFile file, string uid)
|
bool CanReadFile(UploadedFile file, string uid)
|
||||||
{
|
{
|
||||||
if (User.IsInRole(Constants.AdminGroupName)) return true;
|
if (User.IsInRole(Constants.AdminName)) return true;
|
||||||
if (file.OwnerId == uid) return true;
|
if (file.OwnerId == uid) return true;
|
||||||
|
|
||||||
if (dbContext.EstimateAttachedFiles.Any(a =>
|
if (dbContext.EstimateAttachedFiles.Any(a =>
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
// ensure all roles existence
|
// ensure all roles existence
|
||||||
foreach (string roleName in new string[] {
|
foreach (string roleName in new string[] {
|
||||||
Constants.AdminGroupName,
|
Constants.AdminName,
|
||||||
Constants.StarGroupName,
|
Constants.StarGroupName,
|
||||||
Constants.PerformerGroupName,
|
Constants.PerformerGroupName,
|
||||||
Constants.FrontOfficeGroupName,
|
Constants.FrontOfficeGroupName,
|
||||||
|
|
@ -86,13 +86,12 @@ namespace Yavsc.Controllers
|
||||||
public async Task<IActionResult> Take()
|
public async Task<IActionResult> Take()
|
||||||
{
|
{
|
||||||
// If some amdin already exists, make this method disapear
|
// If some amdin already exists, make this method disapear
|
||||||
var admins = await _userManager.GetUsersInRoleAsync(Constants.AdminGroupName);
|
var admins = await _userManager.GetUsersInRoleAsync(Constants.AdminName);
|
||||||
if (admins != null && admins.Count > 0)
|
if (admins != null && admins.Count > 0)
|
||||||
{
|
{
|
||||||
// All is ok, nothing to do here.
|
// All is ok, nothing to do here.
|
||||||
if (User.IsInMsRole(Constants.AdminGroupName))
|
if (User.IsInMsRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
|
|
||||||
return Ok(new { message = "you already got it." });
|
return Ok(new { message = "you already got it." });
|
||||||
}
|
}
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -116,7 +115,7 @@ namespace Yavsc.Controllers
|
||||||
return new BadRequestObjectResult(ModelState);
|
return new BadRequestObjectResult(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
var addToRoleResult = await _userManager.AddToRoleAsync(user, Constants.AdminGroupName);
|
var addToRoleResult = await _userManager.AddToRoleAsync(user, Constants.AdminName);
|
||||||
if (!addToRoleResult.Succeeded)
|
if (!addToRoleResult.Succeeded)
|
||||||
{
|
{
|
||||||
AddErrors(addToRoleResult);
|
AddErrors(addToRoleResult);
|
||||||
|
|
@ -130,11 +129,11 @@ namespace Yavsc.Controllers
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
var adminCount = await _userManager.GetUsersInRoleAsync(
|
var adminCount = await _userManager.GetUsersInRoleAsync(
|
||||||
Constants.AdminGroupName);
|
Constants.AdminName);
|
||||||
var userCount = await _dbContext.Users.CountAsync();
|
var userCount = await _dbContext.Users.CountAsync();
|
||||||
var youAreAdmin = await _userManager.IsInRoleAsync(
|
var youAreAdmin = await _userManager.IsInRoleAsync(
|
||||||
await _userManager.FindByIdAsync(User.GetUserId()),
|
await _userManager.FindByIdAsync(User.GetUserId()),
|
||||||
Constants.AdminGroupName);
|
Constants.AdminName);
|
||||||
|
|
||||||
var roles = await _roleManager.Roles.Select(x => new RoleInfo
|
var roles = await _roleManager.Roles.Select(x => new RoleInfo
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
private async Task SetupView(Announce announce)
|
private async Task SetupView(Announce announce)
|
||||||
{
|
{
|
||||||
ViewBag.IsAdmin = User.IsInMsRole(Constants.AdminGroupName);
|
ViewBag.IsAdmin = User.IsInMsRole(Constants.AdminName);
|
||||||
ViewBag.IsPerformer = User.IsInMsRole(Constants.PerformerGroupName);
|
ViewBag.IsPerformer = User.IsInMsRole(Constants.PerformerGroupName);
|
||||||
ViewBag.AllowEdit = announce==null || announce.Id<=0 || !_authorizationService.AuthorizeAsync(User,announce,new EditPermission()).IsFaulted;
|
ViewBag.AllowEdit = announce==null || announce.Id<=0 || !_authorizationService.AuthorizeAsync(User,announce,new EditPermission()).IsFaulted;
|
||||||
List<SelectListItem> dl = new List<SelectListItem>();
|
List<SelectListItem> dl = new List<SelectListItem>();
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Yavsc.Controllers
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
// Only allow admin to create corporate annonces
|
// Only allow admin to create corporate annonces
|
||||||
if (announce.For == Reason.Corporate && ! User.IsInMsRole(Constants.AdminGroupName))
|
if (announce.For == Reason.Corporate && ! User.IsInMsRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError("For", _localizer["YourNotAdmin"]);
|
ModelState.AddModelError("For", _localizer["YourNotAdmin"]);
|
||||||
return View(announce);
|
return View(announce);
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,14 @@ namespace Yavsc.Controllers
|
||||||
if (clicked == null) clicked = new long[0];
|
if (clicked == null) clicked = new long[0];
|
||||||
}
|
}
|
||||||
else clicked = _dbContext.DismissClicked.Where(d => d.UserId == uid).Select(d => d.NotificationId).ToArray();
|
else clicked = _dbContext.DismissClicked.Where(d => d.UserId == uid).Select(d => d.NotificationId).ToArray();
|
||||||
|
var isAdmin = User.IsInRole(Constants.AdminName);
|
||||||
var notes = _dbContext.Notification.Where(
|
var notes = _dbContext.Notification.Where(
|
||||||
n => !clicked.Contains(n.Id)
|
n => !clicked.Contains(n.Id)
|
||||||
);
|
).Where(n =>
|
||||||
|
string.IsNullOrWhiteSpace(n.Target)
|
||||||
|
|| (n.Target != null && n.Target.StartsWith("pub/"))
|
||||||
|
|| (!string.IsNullOrWhiteSpace(uid) && n.Target == $"user/{uid}")
|
||||||
|
|| (isAdmin && n.Target == Constants.Administration));
|
||||||
if (notes.Any()) this.Notify(notes);
|
if (notes.Any()) this.Notify(notes);
|
||||||
|
|
||||||
var toShow = _dbContext.Activities
|
var toShow = _dbContext.Activities
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ namespace Yavsc.Controllers
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
if (model.UserId != uid) if (!User.IsInMsRole(Constants.AdminGroupName))
|
if (model.UserId != uid) if (!User.IsInMsRole(Constants.AdminName))
|
||||||
return new ChallengeResult();
|
return new ChallengeResult();
|
||||||
|
|
||||||
_context.Instrumentation.Add(model);
|
_context.Instrumentation.Add(model);
|
||||||
|
|
@ -81,7 +81,7 @@ namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
if (id != uid) if (!User.IsInMsRole(Constants.AdminGroupName))
|
if (id != uid) if (!User.IsInMsRole(Constants.AdminName))
|
||||||
return new ChallengeResult();
|
return new ChallengeResult();
|
||||||
Instrumentation musicianSettings = await _context.Instrumentation.SingleAsync(m => m.UserId == id);
|
Instrumentation musicianSettings = await _context.Instrumentation.SingleAsync(m => m.UserId == id);
|
||||||
if (musicianSettings == null)
|
if (musicianSettings == null)
|
||||||
|
|
@ -97,7 +97,7 @@ namespace Yavsc.Controllers
|
||||||
public async Task<IActionResult> Edit(Instrumentation musicianSettings)
|
public async Task<IActionResult> Edit(Instrumentation musicianSettings)
|
||||||
{
|
{
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (musicianSettings.UserId != uid) if (!User.IsInMsRole(Constants.AdminGroupName))
|
if (musicianSettings.UserId != uid) if (!User.IsInMsRole(Constants.AdminName))
|
||||||
return new ChallengeResult();
|
return new ChallengeResult();
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
|
|
@ -123,7 +123,7 @@ namespace Yavsc.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (musicianSettings.UserId != uid) if (!User.IsInMsRole(Constants.AdminGroupName))
|
if (musicianSettings.UserId != uid) if (!User.IsInMsRole(Constants.AdminName))
|
||||||
return new ChallengeResult();
|
return new ChallengeResult();
|
||||||
return View(musicianSettings);
|
return View(musicianSettings);
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +136,7 @@ namespace Yavsc.Controllers
|
||||||
Instrumentation musicianSettings = await _context.Instrumentation.SingleAsync(m => m.UserId == id);
|
Instrumentation musicianSettings = await _context.Instrumentation.SingleAsync(m => m.UserId == id);
|
||||||
|
|
||||||
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
if (musicianSettings.UserId != uid) if (!User.IsInMsRole(Constants.AdminGroupName))
|
if (musicianSettings.UserId != uid) if (!User.IsInMsRole(Constants.AdminName))
|
||||||
return new ChallengeResult();
|
return new ChallengeResult();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,14 +263,14 @@ public static class HostingExtensions
|
||||||
policy
|
policy
|
||||||
.RequireAuthenticatedUser()
|
.RequireAuthenticatedUser()
|
||||||
.RequireClaim(Constants.RoleClaimType,
|
.RequireClaim(Constants.RoleClaimType,
|
||||||
new string[] { Constants.PerformerGroupName, Constants.AdminGroupName })
|
new string[] { Constants.PerformerGroupName, Constants.AdminName })
|
||||||
;
|
;
|
||||||
});
|
});
|
||||||
options.AddPolicy("AdministratorOnly", policy =>
|
options.AddPolicy("AdministratorOnly", policy =>
|
||||||
{
|
{
|
||||||
_ = policy
|
_ = policy
|
||||||
.RequireAuthenticatedUser()
|
.RequireAuthenticatedUser()
|
||||||
.RequireClaim(Constants.RoleClaimType, Constants.AdminGroupName);
|
.RequireClaim(Constants.RoleClaimType, Constants.AdminName);
|
||||||
});
|
});
|
||||||
|
|
||||||
options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName));
|
options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName));
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<li><a class="dropdown-item @PageHelpers.ActivePage(ViewContext, "Feature")" asp-controller="Feature" asp-action="Index">Features</a></li>
|
<li><a class="dropdown-item @PageHelpers.ActivePage(ViewContext, "Feature")" asp-controller="Feature" asp-action="Index">Features</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@if (User.IsInMsRole(Constants.AdminGroupName)) {
|
@if (User.IsInMsRole(Constants.AdminName)) {
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle @PageHelpers.ActivePageAny(ViewContext, administrationControllers)" href="#" id="dropdown05" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle @PageHelpers.ActivePageAny(ViewContext, administrationControllers)" href="#" id="dropdown05" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Administration
|
Administration
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Yavsc.Server.Hubs
|
||||||
var userId = _dbContext.Users.First(u => u.UserName == userName).Id;
|
var userId = _dbContext.Users.First(u => u.UserName == userName).Id;
|
||||||
|
|
||||||
await Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).SendAsync("notifyUser", NotificationTypes.Connected, userName, null);
|
await Clients.Group(ChatHubConstants.HubGroupFollowingPrefix + userId).SendAsync("notifyUser", NotificationTypes.Connected, userName, null);
|
||||||
isCop = Context.User.IsInMsRole(Constants.AdminGroupName);
|
isCop = Context.User.IsInMsRole(Constants.AdminName);
|
||||||
if (isCop)
|
if (isCop)
|
||||||
{
|
{
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupCops);
|
await Groups.AddToGroupAsync(Context.ConnectionId, ChatHubConstants.HubGroupCops);
|
||||||
|
|
@ -232,7 +232,7 @@ namespace Yavsc.Server.Hubs
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Constants.AdminGroupName)]
|
[Authorize(Constants.AdminName)]
|
||||||
public void GLine(string userName, string reason)
|
public void GLine(string userName, string reason)
|
||||||
{
|
{
|
||||||
if (!InputValidator.ValidateUserName(userName)) return;
|
if (!InputValidator.ValidateUserName(userName)) return;
|
||||||
|
|
@ -330,7 +330,7 @@ namespace Yavsc.Server.Hubs
|
||||||
var identityUserName = Context.User.GetUserName();
|
var identityUserName = Context.User.GetUserName();
|
||||||
|
|
||||||
if (userName[0] != '?' && Context.User != null)
|
if (userName[0] != '?' && Context.User != null)
|
||||||
if (!Context.User.IsInMsRole(Constants.AdminGroupName))
|
if (!Context.User.IsInMsRole(Constants.AdminName))
|
||||||
{
|
{
|
||||||
|
|
||||||
var bl = _dbContext.BlackListed
|
var bl = _dbContext.BlackListed
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ public sealed class ApiWebServerFixture : WebHostFixture
|
||||||
options.AddPolicy("AdministratorOnly", policy =>
|
options.AddPolicy("AdministratorOnly", policy =>
|
||||||
{
|
{
|
||||||
policy.RequireAuthenticatedUser();
|
policy.RequireAuthenticatedUser();
|
||||||
policy.RequireClaim(Yavsc.Constants.RoleClaimType, Yavsc.Constants.AdminGroupName);
|
policy.RequireClaim(Yavsc.Constants.RoleClaimType, Yavsc.Constants.AdminName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,10 @@ using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Yavsc.Abstract.Models.Messaging;
|
||||||
using Yavsc.Api.Test.Fixtures;
|
using Yavsc.Api.Test.Fixtures;
|
||||||
using Yavsc.Controllers;
|
using Yavsc.Controllers;
|
||||||
|
using Yavsc.Models;
|
||||||
using Yavsc.Models.Workflow;
|
using Yavsc.Models.Workflow;
|
||||||
|
|
||||||
namespace Yavsc.Api.Test;
|
namespace Yavsc.Api.Test;
|
||||||
|
|
@ -57,6 +59,106 @@ public sealed class HomeControllerTests : IClassFixture<ApiWebServerFixture>
|
||||||
Assert.DoesNotContain(model, a => a.Code == "ghost");
|
Assert.DoesNotContain(model, a => a.Code == "ghost");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Index_does_not_show_performer_notification_to_anonymous_visitors()
|
||||||
|
{
|
||||||
|
_fixture.ResetAndSeedActivityGraph();
|
||||||
|
using var scope = _fixture.Services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
||||||
|
db.Notification.AddRange(
|
||||||
|
Note("Rendez-vous professionnel", "user/alice"),
|
||||||
|
Note("Annonce publique", "pub/all"));
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var controller = CreateHomeController(db, new ClaimsPrincipal(new ClaimsIdentity()));
|
||||||
|
controller.ControllerContext.HttpContext.Session = new TestSession();
|
||||||
|
|
||||||
|
await controller.Index(id: null);
|
||||||
|
|
||||||
|
object notifyValue = controller.ViewBag.Notify;
|
||||||
|
var notifications = Assert.IsAssignableFrom<IEnumerable<Notification>>(notifyValue);
|
||||||
|
Assert.Equal("Annonce publique", Assert.Single(notifications).title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Index_shows_performer_notification_only_to_its_target_user()
|
||||||
|
{
|
||||||
|
_fixture.ResetAndSeedActivityGraph();
|
||||||
|
using var scope = _fixture.Services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
||||||
|
db.Notification.AddRange(
|
||||||
|
Note("Rendez-vous professionnel", "user/alice"),
|
||||||
|
Note("Autre rendez-vous", "user/bob"),
|
||||||
|
Note("Annonce publique", "pub/all"));
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var controller = CreateHomeController(db, UserPrincipal("bob"));
|
||||||
|
await controller.Index(id: null);
|
||||||
|
|
||||||
|
object notifyValue = controller.ViewBag.Notify;
|
||||||
|
var notifications = Assert.IsAssignableFrom<IEnumerable<Notification>>(notifyValue);
|
||||||
|
Assert.Equal(new[] { "Autre rendez-vous", "Annonce publique" },
|
||||||
|
notifications.Select(notification => notification.title));
|
||||||
|
|
||||||
|
var performerController = CreateHomeController(db, UserPrincipal("alice"));
|
||||||
|
await performerController.Index(id: null);
|
||||||
|
|
||||||
|
object performerNotifyValue = performerController.ViewBag.Notify;
|
||||||
|
var performerNotifications = Assert.IsAssignableFrom<IEnumerable<Notification>>(performerNotifyValue);
|
||||||
|
Assert.Equal(new[] { "Rendez-vous professionnel", "Annonce publique" },
|
||||||
|
performerNotifications.Select(notification => notification.title));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HomeController CreateHomeController(
|
||||||
|
Yavsc.Models.ApplicationDbContext db,
|
||||||
|
ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
var controller = new HomeController(
|
||||||
|
NullLogger<HomeController>.Instance,
|
||||||
|
localizer: null!,
|
||||||
|
context: db,
|
||||||
|
settingsOptions: Options.Create(new SiteSettings()),
|
||||||
|
env: new TestEnvironment());
|
||||||
|
|
||||||
|
controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = user }
|
||||||
|
};
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClaimsPrincipal UserPrincipal(string userId)
|
||||||
|
=> new(new ClaimsIdentity(new[]
|
||||||
|
{
|
||||||
|
new Claim("sub", userId),
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId)
|
||||||
|
}, "Bearer"));
|
||||||
|
|
||||||
|
private static Notification Note(string title, string target)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
title = title,
|
||||||
|
body = "Corps",
|
||||||
|
click_action = "Fermer",
|
||||||
|
Target = target
|
||||||
|
};
|
||||||
|
|
||||||
|
private sealed class TestSession : ISession
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, byte[]> _values = new();
|
||||||
|
|
||||||
|
public bool IsAvailable => true;
|
||||||
|
public string Id => "home-controller-test-session";
|
||||||
|
public IEnumerable<string> Keys => _values.Keys;
|
||||||
|
|
||||||
|
public void Clear() => _values.Clear();
|
||||||
|
public Task CommitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
|
public Task LoadAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
|
public void Remove(string key) => _values.Remove(key);
|
||||||
|
public void Set(string key, byte[] value) => _values[key] = value;
|
||||||
|
public bool TryGetValue(string key, out byte[] value) => _values.TryGetValue(key, out value!);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class TestEnvironment : IWebHostEnvironment
|
private sealed class TestEnvironment : IWebHostEnvironment
|
||||||
{
|
{
|
||||||
public string ApplicationName { get; set; } = "Yavsc.Api.Test";
|
public string ApplicationName { get; set; } = "Yavsc.Api.Test";
|
||||||
|
|
|
||||||
|
|
@ -414,7 +414,7 @@ public sealed class InvoiceApiTests(ApiWebServerFixture fixture) : IClassFixture
|
||||||
using var admin = Client();
|
using var admin = Client();
|
||||||
admin.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
|
admin.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
|
||||||
TestTokenIssuer.Issue("alice", "api", extraClaims:
|
TestTokenIssuer.Issue("alice", "api", extraClaims:
|
||||||
[new System.Security.Claims.Claim(Constants.RoleClaimType, Constants.AdminGroupName)]));
|
[new System.Security.Claims.Claim(Constants.RoleClaimType, Constants.AdminName)]));
|
||||||
var response = await admin.GetAsync("/openapi/invoicing.json", Ct);
|
var response = await admin.GetAsync("/openapi/invoicing.json", Ct);
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
var json = await response.Content.ReadAsStringAsync(Ct);
|
var json = await response.Content.ReadAsStringAsync(Ct);
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ public sealed class PodcastControllerTests(ApiWebServerFixture fixture) : IClass
|
||||||
using var admin = Client("podcast-admin");
|
using var admin = Client("podcast-admin");
|
||||||
admin.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
|
admin.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
|
||||||
TestTokenIssuer.Issue("podcast-admin", "api",
|
TestTokenIssuer.Issue("podcast-admin", "api",
|
||||||
extraClaims: new[] { new Claim(Constants.RoleClaimType, Constants.AdminGroupName) }));
|
extraClaims: new[] { new Claim(Constants.RoleClaimType, Constants.AdminName) }));
|
||||||
using var response = await admin.GetAsync("/openapi/podcasts.json", _ct);
|
using var response = await admin.GetAsync("/openapi/podcasts.json", _ct);
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync(_ct));
|
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync(_ct));
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ public sealed class ResourceUsageApiControllerTests : IClassFixture<ApiWebServer
|
||||||
title = "Admin",
|
title = "Admin",
|
||||||
body = "Restricted to administrators",
|
body = "Restricted to administrators",
|
||||||
click_action = "/admin",
|
click_action = "/admin",
|
||||||
Target = "administration",
|
Target = Constants.Administration,
|
||||||
});
|
});
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ public sealed class ResourceUsageApiControllerTests : IClassFixture<ApiWebServer
|
||||||
"api",
|
"api",
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new Claim(Yavsc.Constants.RoleClaimType, Yavsc.Constants.AdminGroupName)
|
new Claim(Yavsc.Constants.RoleClaimType, Yavsc.Constants.AdminName)
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = await adminHttp.GetAsync("/api/v1/resource-usage/admin", TestContext.Current.CancellationToken);
|
var response = await adminHttp.GetAsync("/api/v1/resource-usage/admin", TestContext.Current.CancellationToken);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue