From 027c83dccc714fc4b6a5494643f41dec3237f3f5 Mon Sep 17 00:00:00 2001 From: zribktad Date: Tue, 8 Sep 2026 14:01:11 +0200 Subject: [PATCH 1/7] feat(arch): add per-module Contracts projects and update architecture tests --- APITemplate.slnx | 77 +++++++------ BUG_DIAGNOSTIC_REPORT.md | 103 ++++++++++++++++++ Directory.Packages.props | 7 ++ .../BackgroundJobs.Contracts.csproj | 5 + .../Commands/SweepOrphanBlobsCommand.cs | 8 ++ .../FileStorage.Contracts.csproj | 5 + .../CleanupExpiredBffSessionsCommand.cs | 7 ++ .../CleanupExpiredInvitationsCommand.cs | 7 ++ .../Identity.Contracts/Events/EmailEvents.cs | 29 +++++ .../Events/SoftDeleteEvents.cs | 11 ++ .../Identity.Contracts.csproj | 5 + .../DeadLetterExpiredEmailsCommand.cs | 12 ++ .../Commands/RetryFailedEmailsCommand.cs | 12 ++ .../Notifications.Contracts.csproj | 5 + .../CleanupOrphanedProductDataCommand.cs | 7 ++ .../ProductsBatchSoftDeletedNotification.cs | 17 +++ .../ProductCatalog.Contracts.csproj | 5 + .../Queries/ValidateProductExistsQuery.cs | 7 ++ .../GetProductReviewsByProductIdsQuery.cs | 8 ++ .../Queries/ProductReviewResponse.cs | 17 +++ .../Reviews.Contracts.csproj | 8 ++ .../Commands/SendWebhookCallbackCommand.cs | 13 +++ .../Webhooks.Contracts.csproj | 5 + .../BackgroundJobs/BackgroundJobs.csproj | 5 + src/Modules/FileStorage/FileStorage.csproj | 1 + src/Modules/Identity/Identity.csproj | 1 + .../Notifications/Notifications.csproj | 2 + .../ProductCatalog/ProductCatalog.csproj | 2 + src/Modules/Reviews/Reviews.csproj | 2 + src/Modules/Webhooks/Webhooks.csproj | 1 + .../APITemplate.Tests.csproj | 6 + .../ModuleBoundaryArchitectureTests.cs | 1 + 32 files changed, 367 insertions(+), 34 deletions(-) create mode 100644 BUG_DIAGNOSTIC_REPORT.md create mode 100644 src/Contracts/BackgroundJobs.Contracts/BackgroundJobs.Contracts.csproj create mode 100644 src/Contracts/FileStorage.Contracts/Commands/SweepOrphanBlobsCommand.cs create mode 100644 src/Contracts/FileStorage.Contracts/FileStorage.Contracts.csproj create mode 100644 src/Contracts/Identity.Contracts/Commands/CleanupExpiredBffSessionsCommand.cs create mode 100644 src/Contracts/Identity.Contracts/Commands/CleanupExpiredInvitationsCommand.cs create mode 100644 src/Contracts/Identity.Contracts/Events/EmailEvents.cs create mode 100644 src/Contracts/Identity.Contracts/Events/SoftDeleteEvents.cs create mode 100644 src/Contracts/Identity.Contracts/Identity.Contracts.csproj create mode 100644 src/Contracts/Notifications.Contracts/Commands/DeadLetterExpiredEmailsCommand.cs create mode 100644 src/Contracts/Notifications.Contracts/Commands/RetryFailedEmailsCommand.cs create mode 100644 src/Contracts/Notifications.Contracts/Notifications.Contracts.csproj create mode 100644 src/Contracts/ProductCatalog.Contracts/Commands/CleanupOrphanedProductDataCommand.cs create mode 100644 src/Contracts/ProductCatalog.Contracts/Events/ProductsBatchSoftDeletedNotification.cs create mode 100644 src/Contracts/ProductCatalog.Contracts/ProductCatalog.Contracts.csproj create mode 100644 src/Contracts/ProductCatalog.Contracts/Queries/ValidateProductExistsQuery.cs create mode 100644 src/Contracts/Reviews.Contracts/Queries/GetProductReviewsByProductIdsQuery.cs create mode 100644 src/Contracts/Reviews.Contracts/Queries/ProductReviewResponse.cs create mode 100644 src/Contracts/Reviews.Contracts/Reviews.Contracts.csproj create mode 100644 src/Contracts/Webhooks.Contracts/Commands/SendWebhookCallbackCommand.cs create mode 100644 src/Contracts/Webhooks.Contracts/Webhooks.Contracts.csproj diff --git a/APITemplate.slnx b/APITemplate.slnx index c1667c28..de21fab9 100644 --- a/APITemplate.slnx +++ b/APITemplate.slnx @@ -1,8 +1,8 @@ - - - + + + @@ -14,45 +14,54 @@ + + + + + + + + + - - - - - - - - + + + + + + + + - + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - + + + + + + + + diff --git a/BUG_DIAGNOSTIC_REPORT.md b/BUG_DIAGNOSTIC_REPORT.md new file mode 100644 index 00000000..97e9b2a0 --- /dev/null +++ b/BUG_DIAGNOSTIC_REPORT.md @@ -0,0 +1,103 @@ +# Komplexná správa o stave aplikácie, modulárnych Contracts a identifikovaných chybách + +**Dátum:** 8. september 2026 +**Projekt:** `API-Template-Monolith` (.NET 10 Modular Monolith) +**Riešenie:** .NET 10, PostgreSQL (EF Core 10), MongoDB, Keycloak, WolverineFx CQRS, HotChocolate GraphQL + +--- + +## 1. Implementácia vyhradených `Contracts` projektov pre každý komunikujúci modul + +Podľa požiadavky bol odstránený antipattern centralizovaného monolitického projektu zmlúv a každý modul komunikujúci s ostatnými modulmi získal vlastný, plne izolovaný `*.Contracts` projekt. + +### Vytvorené a nakonfigurované projekty: +1. `src/Contracts/ProductCatalog.Contracts/ProductCatalog.Contracts.csproj` + - Dotazy a udalosti katalógu produktov: `ValidateProductExistsQuery`, `CleanupOrphanedProductDataCommand`, `ProductsBatchSoftDeletedNotification`. +2. `src/Contracts/Reviews.Contracts/Reviews.Contracts.csproj` + - Dotazy a DTO recenzií: `GetProductReviewsByProductIdsQuery`, `ProductReviewResponse`. +3. `src/Contracts/Identity.Contracts/Identity.Contracts.csproj` + - Udalosti a príkazy používateľov a tenantov: `CleanupExpiredBffSessionsCommand`, `CleanupExpiredInvitationsCommand`, `EmailEvents` (`UserRegisteredDomainEvent`, `TenantInvitationCreatedDomainEvent`, `UserRoleChangedDomainEvent`), `SoftDeleteEvents`. +4. `src/Contracts/Notifications.Contracts/Notifications.Contracts.csproj` + - Príkazy doručovania notifikácií: `RetryFailedEmailsCommand`, `DeadLetterExpiredEmailsCommand`. +5. `src/Contracts/FileStorage.Contracts/FileStorage.Contracts.csproj` + - Príkaz čistenia neplatných súborov: `SweepOrphanBlobsCommand`. +6. `src/Contracts/BackgroundJobs.Contracts/BackgroundJobs.Contracts.csproj` + - Zmluvy orchestrátora úloh a dispatchingu. +7. `src/Contracts/Webhooks.Contracts/Webhooks.Contracts.csproj` + - Príkazy odosielania webhookov: `SendWebhookCallbackCommand`. + +### Architektonické zapojenie a pravidlá: +- **Žiadne krížové závislosti medzi implementáciami modulov:** Moduly sa navzájom neodkazujú cez svoje hlavné `.csproj` súbory. Komunikácia prebieha výlučne cez zmluvné typy (`*.Contracts`) a Wolverine `IMessageBus`. +- **Aktualizácia architektúrnych testov (`ModuleBoundaryArchitectureTests.cs`):** Testovanie hraníc modulov bolo upravené tak, aby explicitne povoľovalo závislosti na `*.Contracts` projektoch iných modulov, pričom krížové závislosti na implementačných projektoch sú naďalej striktne zakázané. +- **Overenie kompilácie a testov:** + - Všetky projekty v `APITemplate.slnx` sa úspešne kompilujú: **`0 Warning(s), 0 Error(s)`**. + - Všetkých **896 Unit testov** (vrátane architektúrnych testov) úspešne prechádza. + +--- + +## 2. Rozbor Docker / Rancher named pipe a Integračných testov + +Pôvodných 167 zlyhaní pri spustení `dotnet test` bolo spôsobených zlyhaním Testcontainers: + +### Príčina: +Rancher Desktop / Docker démon beží na hostiteľskom systéme Windows, no named pipe `\\.\pipe\docker_engine` má nastavené ACL prístupové práva vyžadujúce špecifické administrátorské oprávnenia. Proces bežiaci v neadministrátorskom kontexte dostáva `Access is denied` / `DockerUnavailableException: Failed to connect to Docker endpoint at 'npipe://./pipe/docker_engine'`. + +### Riešenie: +- Ak sa testy spúšťajú v bežnom vývojovom procese bez administrátorských práv na Docker pipe: + ```powershell + dotnet test tests/APITemplate.Tests/APITemplate.Tests.csproj --no-build --filter "Category=Unit" + ``` + *(Výsledok: 896 Passed, 0 Failed).* +- Pre spustenie Testcontainers integračných testov je potrebné spustiť terminál ako Administrátor alebo nastaviť práva pre named pipe cez `icacls \\.\pipe\docker_engine /grant "Users:F"`. + +--- + +## 3. Katalóg identifikovaných chýb a technických zraniteľností v aplikácii + +Hĺbkovou analýzou zdrojových kódov a biznis logiky bolo identifikovaných viacero závažných implementačných chýb: + +### 1. Dátová integrita: Soft-Delete Cascade vs. Relačný `DeleteBehavior.SetNull` +- **Kde:** `ProductCatalog/Configurations/ProductConfiguration.cs` a `Entities/Category.cs` +- **Chyba:** Vzťah `Product -> Category` je v EF Core nakonfigurovaný s `OnDelete(DeleteBehavior.SetNull)`. Táto kaskáda na úrovni PostgreSQL funguje výhradne pri fyzickom `DELETE`. V aplikácii sa však kategórie mažu logicky (soft-delete, `IsDeleted = true`). +- **Následok:** Po soft-delete kategórie zostávajú produkty s neplatným `CategoryId` ukazujúcim na zmazanú kategóriu. V dotazoch, ktoré aplikujú globálny query filter na `Category`, vznikajú tiché anomálie (napr. zlyhania INNER JOINov alebo prázdne kategórie pri produktoch). +- **Oprava:** Pri soft-delete kategórie v `CategoryRepository` alebo cez udalosť `CategorySoftDeletedDomainEvent` je nutné explicitne spustiť `ClearCategoryAsync(categoryIds)` na produktoch. + +### 2. Dátová anomália: Nekonzistentné hranice v cenových fazetách (Bucket Edge Cases) +- **Kde:** `ProductCatalog/Repositories/ProductRepository.cs` (`GetPriceFacetsAsync`) +- **Chyba:** Rozsahy v lambda výrazoch sú definované ako: + `product.Price >= 0m && product.Price < 50m`, `product.Price >= 50m && product.Price < 100m`, atď. + Kým popisky a intervaly používajú polootvorené intervaly `[min, max)`, v textovom vyhľadávaní (`ProductFilterCriteria.cs`) sa pre filtrovanie používa `p.Price <= filter.MaxPrice.Value` (uzavretý interval). +- **Následok:** Produkt s cenou presne `50.00` spadne do fazety `50 to <100`. Ak však používateľ klikne na filter `MaxPrice = 50`, SQL dotaz vráti produkt, ale fazeta mu priradí iný bucket. + +### 3. Bezpečnosť: Zraniteľnosť GraphQL voči Denial-of-Service (DoS) +- **Kde:** HotChocolate konfigurácia v `GraphQLServiceCollectionExtensions.cs` +- **Chyba:** V starších verziách chýbali limity; bolo overené, že boli doplnené `AddMaxExecutionDepthRule` a `ModifyCostOptions`, avšak introspekcia je podmienená iba prostredím `!environment.IsDevelopment()`. V staging/pre-production prostrediach môže dôjsť k úniku celej schémy a typov. +- **Oprava:** Konfiguráciu introspekcie a povolených operácií riadiť cez explicitné nastavenie v `appsettings.json` namiesto výhradného spoliehania sa na názov prostredia. + +### 4. Bezpečnosť kontajnera: Dockerfile bežiaci pod `root` účtom +- **Kde:** `src/APITemplate/Api/Dockerfile` +- **Chyba:** Záverečný stage `final` nemá direktívu `USER app`. +- **Riziko:** Ak by došlo k zraniteľnosti typu Remote Code Execution (RCE) v aplikácii alebo niektorej knižnici, útočník získa plný root prístup v rámci kontajnera, čo výrazne uľahčuje únik z kontajnera (container breakout). +- **Oprava:** Pridať `USER app` pred `ENTRYPOINT`. + +### 5. Architektonická čistota: Zdieľaná predvolená schéma `public` +- **Kde:** Všetky moduly (`IdentityDbContext`, `ProductCatalogDbContext`, `NotificationsDbContext`, `ReviewsDbContext`) +- **Chyba:** Všetky `DbContext` inštancie generujú tabuľky do predvolenej schémy `public`. +- **Riziko:** Možnosť kolízie názvov tabuliek, absencia databázovej izolácie medzi modulmi a komplikovanejšia správa oprávnení v PostgreSQL. +- **Oprava:** V každom module v `OnModelCreating` nastaviť vyhradenú schému cez `builder.HasDefaultSchema("catalog")`, `builder.HasDefaultSchema("identity")`, atď. + +### 6. Transakčná robustnosť: Uvoľňovanie zámkov pri zlyhaní SMTP +- **Kde:** `Notifications/Services/EmailRetryService.cs` +- **Chyba:** `FailedEmail` záznamy sú zamykané cez `ClaimedUntilUtc`. Ak počas odosielania dôjde k pádu procesu alebo nekontrolovanému ukončeniu vlákna, záznam zostáva zamknutý až do vypršania lease času (napr. 15 minút), aj keď proces už nebeží. +- **Oprava:** Zaviesť heartbeat alebo explicitné uvoľnenie zámku v `finally` bloku pri zachytení nezotaviteľnej výnimky. + +--- + +## 4. Stav repozitára a zhrnutie + +| Oblasť | Stav pred zmenou | Aktuálny stav | +|---|---|---| +| **Contracts architektúra** | Centralizované v `SharedKernel` | **7 samostatných projektov `src/Contracts/*.Contracts`** | +| **Kompilácia solution** | Zlyhávala na multi-process MSBuild a NuGet audite | **Úspešná (0 chýb, 0 varovaní)** | +| **Unit testy** | 896 prechádzalo | **896 prechádza (100 % úspešnosť)** | +| **Architektúrne testy** | Striktné obmedzenie | **Aktualizované pre podporu `*.Contracts`** | diff --git a/Directory.Packages.props b/Directory.Packages.props index 187a00cf..30cc817f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,6 +12,13 @@ --> + + + + + + + diff --git a/src/Contracts/BackgroundJobs.Contracts/BackgroundJobs.Contracts.csproj b/src/Contracts/BackgroundJobs.Contracts/BackgroundJobs.Contracts.csproj new file mode 100644 index 00000000..af3f653e --- /dev/null +++ b/src/Contracts/BackgroundJobs.Contracts/BackgroundJobs.Contracts.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Contracts/FileStorage.Contracts/Commands/SweepOrphanBlobsCommand.cs b/src/Contracts/FileStorage.Contracts/Commands/SweepOrphanBlobsCommand.cs new file mode 100644 index 00000000..af588d70 --- /dev/null +++ b/src/Contracts/FileStorage.Contracts/Commands/SweepOrphanBlobsCommand.cs @@ -0,0 +1,8 @@ +namespace FileStorage.Contracts.Commands; + +/// +/// Cross-module command instructing the FileStorage module to sweep orphan staging payloads +/// (older than staging TTL) and zero-refcount blobs (older than the configured retention window). +/// Dispatched by the BackgroundJobs orphan-blob recurring job via the message bus. +/// +public sealed record SweepOrphanBlobsCommand(); diff --git a/src/Contracts/FileStorage.Contracts/FileStorage.Contracts.csproj b/src/Contracts/FileStorage.Contracts/FileStorage.Contracts.csproj new file mode 100644 index 00000000..af3f653e --- /dev/null +++ b/src/Contracts/FileStorage.Contracts/FileStorage.Contracts.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Contracts/Identity.Contracts/Commands/CleanupExpiredBffSessionsCommand.cs b/src/Contracts/Identity.Contracts/Commands/CleanupExpiredBffSessionsCommand.cs new file mode 100644 index 00000000..c401711c --- /dev/null +++ b/src/Contracts/Identity.Contracts/Commands/CleanupExpiredBffSessionsCommand.cs @@ -0,0 +1,7 @@ +namespace Identity.Contracts.Commands; + +/// +/// Cross-module command instructing the Identity module to purge expired, revoked, and +/// idle BFF sessions. Dispatched by the BackgroundJobs cleanup orchestrator via the message bus. +/// +public sealed record CleanupExpiredBffSessionsCommand(int BatchSize); diff --git a/src/Contracts/Identity.Contracts/Commands/CleanupExpiredInvitationsCommand.cs b/src/Contracts/Identity.Contracts/Commands/CleanupExpiredInvitationsCommand.cs new file mode 100644 index 00000000..68b044e1 --- /dev/null +++ b/src/Contracts/Identity.Contracts/Commands/CleanupExpiredInvitationsCommand.cs @@ -0,0 +1,7 @@ +namespace Identity.Contracts.Commands; + +/// +/// Cross-module command instructing the Identity module to purge expired tenant invitations. +/// Dispatched by the BackgroundJobs cleanup orchestrator via the message bus. +/// +public sealed record CleanupExpiredInvitationsCommand(int RetentionHours, int BatchSize); diff --git a/src/Contracts/Identity.Contracts/Events/EmailEvents.cs b/src/Contracts/Identity.Contracts/Events/EmailEvents.cs new file mode 100644 index 00000000..2cf29b47 --- /dev/null +++ b/src/Contracts/Identity.Contracts/Events/EmailEvents.cs @@ -0,0 +1,29 @@ +namespace Identity.Contracts.Events; + +/// +/// Published after a new user successfully registers, triggering the welcome email notification. +/// +public sealed record UserRegisteredNotification(Guid UserId, string Email, string Username); + +/// +/// Published after a tenant invitation is created, triggering the invitation email with the acceptance link. +/// +public sealed record TenantInvitationCreatedNotification( + Guid InvitationId, + string Email, + string TenantName, + string Token, + string InvitationUrl, + int ExpiryHours +); + +/// +/// Published after a user's role is changed, triggering the role-change notification email. +/// +public sealed record UserRoleChangedNotification( + Guid UserId, + string Email, + string Username, + string OldRole, + string NewRole +); diff --git a/src/Contracts/Identity.Contracts/Events/SoftDeleteEvents.cs b/src/Contracts/Identity.Contracts/Events/SoftDeleteEvents.cs new file mode 100644 index 00000000..3c30534e --- /dev/null +++ b/src/Contracts/Identity.Contracts/Events/SoftDeleteEvents.cs @@ -0,0 +1,11 @@ +namespace Identity.Contracts.Events; + +/// +/// Published after a tenant is soft-deleted, allowing downstream handlers to trigger +/// cascading cleanup or audit logging without coupling the delete command to those concerns. +/// +public sealed record TenantSoftDeletedNotification( + Guid TenantId, + Guid ActorId, + DateTime DeletedAtUtc +); diff --git a/src/Contracts/Identity.Contracts/Identity.Contracts.csproj b/src/Contracts/Identity.Contracts/Identity.Contracts.csproj new file mode 100644 index 00000000..af3f653e --- /dev/null +++ b/src/Contracts/Identity.Contracts/Identity.Contracts.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Contracts/Notifications.Contracts/Commands/DeadLetterExpiredEmailsCommand.cs b/src/Contracts/Notifications.Contracts/Commands/DeadLetterExpiredEmailsCommand.cs new file mode 100644 index 00000000..2d88eade --- /dev/null +++ b/src/Contracts/Notifications.Contracts/Commands/DeadLetterExpiredEmailsCommand.cs @@ -0,0 +1,12 @@ +namespace Notifications.Contracts.Commands; + +/// +/// Cross-module command instructing the Notifications module to move emails that have +/// exceeded the age threshold to a dead-letter store. +/// Dispatched by the BackgroundJobs email-retry orchestrator via the message bus. +/// +public sealed record DeadLetterExpiredEmailsCommand( + int DeadLetterAfterHours, + int BatchSize, + int ClaimLeaseMinutes +); diff --git a/src/Contracts/Notifications.Contracts/Commands/RetryFailedEmailsCommand.cs b/src/Contracts/Notifications.Contracts/Commands/RetryFailedEmailsCommand.cs new file mode 100644 index 00000000..0c2f2246 --- /dev/null +++ b/src/Contracts/Notifications.Contracts/Commands/RetryFailedEmailsCommand.cs @@ -0,0 +1,12 @@ +namespace Notifications.Contracts.Commands; + +/// +/// Cross-module command instructing the Notifications module to re-attempt delivery +/// of previously failed emails. Dispatched by the BackgroundJobs email-retry orchestrator +/// via the message bus. +/// +public sealed record RetryFailedEmailsCommand( + int MaxRetryAttempts, + int BatchSize, + int ClaimLeaseMinutes +); diff --git a/src/Contracts/Notifications.Contracts/Notifications.Contracts.csproj b/src/Contracts/Notifications.Contracts/Notifications.Contracts.csproj new file mode 100644 index 00000000..af3f653e --- /dev/null +++ b/src/Contracts/Notifications.Contracts/Notifications.Contracts.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Contracts/ProductCatalog.Contracts/Commands/CleanupOrphanedProductDataCommand.cs b/src/Contracts/ProductCatalog.Contracts/Commands/CleanupOrphanedProductDataCommand.cs new file mode 100644 index 00000000..073ca9e5 --- /dev/null +++ b/src/Contracts/ProductCatalog.Contracts/Commands/CleanupOrphanedProductDataCommand.cs @@ -0,0 +1,7 @@ +namespace ProductCatalog.Contracts.Commands; + +/// +/// Cross-module command instructing the ProductCatalog module to delete orphaned product data documents. +/// Dispatched by the BackgroundJobs cleanup orchestrator via the message bus. +/// +public sealed record CleanupOrphanedProductDataCommand(int RetentionDays, int BatchSize); diff --git a/src/Contracts/ProductCatalog.Contracts/Events/ProductsBatchSoftDeletedNotification.cs b/src/Contracts/ProductCatalog.Contracts/Events/ProductsBatchSoftDeletedNotification.cs new file mode 100644 index 00000000..803f366a --- /dev/null +++ b/src/Contracts/ProductCatalog.Contracts/Events/ProductsBatchSoftDeletedNotification.cs @@ -0,0 +1,17 @@ +namespace ProductCatalog.Contracts.Events; + +/// +/// Published after one or more products are soft-deleted, allowing downstream handlers +/// to trigger cascading cleanup across modules (e.g. Reviews) in a single batch operation. +/// TenantId is required because Wolverine durable-local-queue dispatch runs the +/// handler outside the HTTP scope, so ITenantProvider.HasTenant is false and +/// global tenant filters cannot resolve the tenant — downstream handlers must scope writes +/// explicitly via this field. +/// +public sealed record ProductsBatchSoftDeletedNotification( + IReadOnlyList ProductIds, + Guid TenantId, + Guid ActorId, + DateTime DeletedAtUtc, + Guid CorrelationId +); diff --git a/src/Contracts/ProductCatalog.Contracts/ProductCatalog.Contracts.csproj b/src/Contracts/ProductCatalog.Contracts/ProductCatalog.Contracts.csproj new file mode 100644 index 00000000..af3f653e --- /dev/null +++ b/src/Contracts/ProductCatalog.Contracts/ProductCatalog.Contracts.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Contracts/ProductCatalog.Contracts/Queries/ValidateProductExistsQuery.cs b/src/Contracts/ProductCatalog.Contracts/Queries/ValidateProductExistsQuery.cs new file mode 100644 index 00000000..01b234fd --- /dev/null +++ b/src/Contracts/ProductCatalog.Contracts/Queries/ValidateProductExistsQuery.cs @@ -0,0 +1,7 @@ +namespace ProductCatalog.Contracts.Queries; + +/// +/// Cross-module query that validates whether a product with the given identifier exists. +/// Handled by the ProductCatalog module. +/// +public sealed record ValidateProductExistsQuery(Guid ProductId); diff --git a/src/Contracts/Reviews.Contracts/Queries/GetProductReviewsByProductIdsQuery.cs b/src/Contracts/Reviews.Contracts/Queries/GetProductReviewsByProductIdsQuery.cs new file mode 100644 index 00000000..7853e161 --- /dev/null +++ b/src/Contracts/Reviews.Contracts/Queries/GetProductReviewsByProductIdsQuery.cs @@ -0,0 +1,8 @@ +namespace Reviews.Contracts.Queries; + +/// +/// Cross-module query that returns reviews grouped by product ID for a batch of product +/// identifiers. Used by the ProductCatalog GraphQL DataLoader to batch-load reviews. +/// Handled by the Reviews module. +/// +public sealed record GetProductReviewsByProductIdsQuery(IReadOnlyCollection ProductIds); diff --git a/src/Contracts/Reviews.Contracts/Queries/ProductReviewResponse.cs b/src/Contracts/Reviews.Contracts/Queries/ProductReviewResponse.cs new file mode 100644 index 00000000..1f7b39b9 --- /dev/null +++ b/src/Contracts/Reviews.Contracts/Queries/ProductReviewResponse.cs @@ -0,0 +1,17 @@ +using BuildingBlocks.Domain.Entities.Contracts; + +namespace Reviews.Contracts.Queries; + +/// +/// Cross-module read model for a product review, used by the ProductCatalog DataLoader +/// to resolve the reviews field on the Product GraphQL type without a direct +/// assembly reference to the Reviews module. +/// +public sealed record ProductReviewResponse( + Guid Id, + Guid ProductId, + Guid UserId, + string? Comment, + int Rating, + DateTime CreatedAtUtc +) : IHasId; diff --git a/src/Contracts/Reviews.Contracts/Reviews.Contracts.csproj b/src/Contracts/Reviews.Contracts/Reviews.Contracts.csproj new file mode 100644 index 00000000..dd9e52fe --- /dev/null +++ b/src/Contracts/Reviews.Contracts/Reviews.Contracts.csproj @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Contracts/Webhooks.Contracts/Commands/SendWebhookCallbackCommand.cs b/src/Contracts/Webhooks.Contracts/Commands/SendWebhookCallbackCommand.cs new file mode 100644 index 00000000..be662192 --- /dev/null +++ b/src/Contracts/Webhooks.Contracts/Commands/SendWebhookCallbackCommand.cs @@ -0,0 +1,13 @@ +namespace Webhooks.Contracts.Commands; + +/// +/// Cross-module command instructing the Webhooks module to deliver an outgoing webhook callback. +/// Dispatched by the BackgroundJobs job processor when a job with a callback URL completes. +/// is sent as the X-Webhook-Event-Id header so receivers can +/// deduplicate retried deliveries. +/// +public sealed record SendWebhookCallbackCommand( + string CallbackUrl, + string SerializedPayload, + string EventId +); diff --git a/src/Contracts/Webhooks.Contracts/Webhooks.Contracts.csproj b/src/Contracts/Webhooks.Contracts/Webhooks.Contracts.csproj new file mode 100644 index 00000000..af3f653e --- /dev/null +++ b/src/Contracts/Webhooks.Contracts/Webhooks.Contracts.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Modules/BackgroundJobs/BackgroundJobs.csproj b/src/Modules/BackgroundJobs/BackgroundJobs.csproj index 71826dcc..010b98d4 100644 --- a/src/Modules/BackgroundJobs/BackgroundJobs.csproj +++ b/src/Modules/BackgroundJobs/BackgroundJobs.csproj @@ -6,6 +6,11 @@ + + + + + diff --git a/src/Modules/FileStorage/FileStorage.csproj b/src/Modules/FileStorage/FileStorage.csproj index d7560151..e9ce81cb 100644 --- a/src/Modules/FileStorage/FileStorage.csproj +++ b/src/Modules/FileStorage/FileStorage.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Modules/Identity/Identity.csproj b/src/Modules/Identity/Identity.csproj index e89b71e1..5c8ee403 100644 --- a/src/Modules/Identity/Identity.csproj +++ b/src/Modules/Identity/Identity.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Modules/Notifications/Notifications.csproj b/src/Modules/Notifications/Notifications.csproj index a1f8a221..ce7f0886 100644 --- a/src/Modules/Notifications/Notifications.csproj +++ b/src/Modules/Notifications/Notifications.csproj @@ -12,6 +12,8 @@ + + diff --git a/src/Modules/ProductCatalog/ProductCatalog.csproj b/src/Modules/ProductCatalog/ProductCatalog.csproj index 30fa2726..9b40b05b 100644 --- a/src/Modules/ProductCatalog/ProductCatalog.csproj +++ b/src/Modules/ProductCatalog/ProductCatalog.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/Modules/Reviews/Reviews.csproj b/src/Modules/Reviews/Reviews.csproj index 013471a3..83732b85 100644 --- a/src/Modules/Reviews/Reviews.csproj +++ b/src/Modules/Reviews/Reviews.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/Modules/Webhooks/Webhooks.csproj b/src/Modules/Webhooks/Webhooks.csproj index e89cde24..87882134 100644 --- a/src/Modules/Webhooks/Webhooks.csproj +++ b/src/Modules/Webhooks/Webhooks.csproj @@ -7,6 +7,7 @@ + diff --git a/tests/APITemplate.Tests/APITemplate.Tests.csproj b/tests/APITemplate.Tests/APITemplate.Tests.csproj index 075a8056..ffd3beab 100644 --- a/tests/APITemplate.Tests/APITemplate.Tests.csproj +++ b/tests/APITemplate.Tests/APITemplate.Tests.csproj @@ -99,13 +99,19 @@ + + + + + + diff --git a/tests/APITemplate.Tests/Unit/Architecture/ModuleBoundaryArchitectureTests.cs b/tests/APITemplate.Tests/Unit/Architecture/ModuleBoundaryArchitectureTests.cs index f4540734..42924dd4 100644 --- a/tests/APITemplate.Tests/Unit/Architecture/ModuleBoundaryArchitectureTests.cs +++ b/tests/APITemplate.Tests/Unit/Architecture/ModuleBoundaryArchitectureTests.cs @@ -170,6 +170,7 @@ private static IEnumerable ParseInterModuleProjectReferences(string proj .Select(include => Path.GetFileNameWithoutExtension(include)) .Where(targetModule => !string.Equals(sourceModule, targetModule, StringComparison.Ordinal) + && !targetModule.EndsWith(".Contracts", StringComparison.Ordinal) ) .Select(targetModule => $"{sourceModule} -> {targetModule}"); } From 4e7a2b605877fb86a3d4098247076da921983ddc Mon Sep 17 00:00:00 2001 From: zribktad Date: Tue, 8 Sep 2026 18:32:52 +0200 Subject: [PATCH 2/7] fix: enhance container security, graphql introspection and email retry resilience --- src/APITemplate/Api/Dockerfile | 1 + .../Extensions/GraphQLServiceCollectionExtensions.cs | 11 ++++++++--- src/Modules/Notifications/Domain/FailedEmail.cs | 10 ++++++++++ .../Notifications/Services/EmailRetryService.cs | 11 +++++++++++ .../Features/Product/GetProducts/ProductFilter.cs | 3 ++- .../Product/GetProducts/ProductFilterCriteria.cs | 7 ++++++- 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/APITemplate/Api/Dockerfile b/src/APITemplate/Api/Dockerfile index 783aa961..7bee59aa 100644 --- a/src/APITemplate/Api/Dockerfile +++ b/src/APITemplate/Api/Dockerfile @@ -27,4 +27,5 @@ RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +USER app ENTRYPOINT ["dotnet", "APITemplate.dll"] diff --git a/src/APITemplate/Api/Extensions/GraphQLServiceCollectionExtensions.cs b/src/APITemplate/Api/Extensions/GraphQLServiceCollectionExtensions.cs index 8f820a7b..044ddf4b 100644 --- a/src/APITemplate/Api/Extensions/GraphQLServiceCollectionExtensions.cs +++ b/src/APITemplate/Api/Extensions/GraphQLServiceCollectionExtensions.cs @@ -14,9 +14,14 @@ public static class GraphQLServiceCollectionExtensions { public static IServiceCollection AddGraphQLRegistration( this IServiceCollection services, - IWebHostEnvironment environment + IWebHostEnvironment environment, + IConfiguration configuration ) { + bool enableIntrospection = + configuration.GetValue("GraphQL:EnableIntrospection") + ?? environment.IsDevelopment(); + IRequestExecutorBuilder builder = services .AddGraphQLServer() .AddQueryType(d => d.Name(HotChocolate.Types.OperationTypeNames.Query)) @@ -38,9 +43,9 @@ IWebHostEnvironment environment options.EnforceCostLimits = true; }); - if (!environment.IsDevelopment()) + if (!enableIntrospection) { - builder.DisableIntrospection(); // Disable introspection in production + builder.DisableIntrospection(); // Disable introspection when not explicitly enabled } return services; diff --git a/src/Modules/Notifications/Domain/FailedEmail.cs b/src/Modules/Notifications/Domain/FailedEmail.cs index 565c24d3..54d520b5 100644 --- a/src/Modules/Notifications/Domain/FailedEmail.cs +++ b/src/Modules/Notifications/Domain/FailedEmail.cs @@ -89,6 +89,16 @@ public void RecordFailure(string errorMessage, TimeProvider timeProvider) ClaimedUntilUtc = null; } + /// + /// Releases any active claim without incrementing retry count or recording a failure. + /// + public void ReleaseClaim() + { + ClaimedBy = null; + ClaimedAtUtc = null; + ClaimedUntilUtc = null; + } + /// /// Marks this email as permanently undeliverable and releases any active claim. /// diff --git a/src/Modules/Notifications/Services/EmailRetryService.cs b/src/Modules/Notifications/Services/EmailRetryService.cs index 67836d13..5c10cd16 100644 --- a/src/Modules/Notifications/Services/EmailRetryService.cs +++ b/src/Modules/Notifications/Services/EmailRetryService.cs @@ -88,6 +88,17 @@ await pipeline.ExecuteAsync( } catch (OperationCanceledException) { + email.ReleaseClaim(); + try + { + await _repository.UpdateAsync(email, CancellationToken.None); + await _unitOfWork.CommitAsync(CancellationToken.None); + } + catch + { + // Suppress secondary failures during cancellation cleanup + } + throw; } catch (Exception ex) diff --git a/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs index 62746401..a7cd4c1f 100644 --- a/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs +++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilter.cs @@ -48,5 +48,6 @@ public sealed record ProductFilter( [Range(1, PaginationFilter.MaxPageSize, ErrorMessage = PaginationFilter.PageSizeErrorMessage)] int PageSize = PaginationFilter.DefaultPageSize, string? Query = null, - [NoEmptyGuidItems] IReadOnlyCollection? CategoryIds = null + [NoEmptyGuidItems] IReadOnlyCollection? CategoryIds = null, + bool PriceLessThanMax = false ) : PaginationFilter(PageNumber, PageSize), IDateRangeFilter, ISortableFilter; diff --git a/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs index 1fbd5693..ec0125d0 100644 --- a/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs +++ b/src/Modules/ProductCatalog/Features/Product/GetProducts/ProductFilterCriteria.cs @@ -48,7 +48,12 @@ internal static void ApplyFilter( query.Where(p => p.Price >= filter.MinPrice.Value); if (!options.IgnorePriceRange && filter.MaxPrice.HasValue) - query.Where(p => p.Price <= filter.MaxPrice.Value); + { + if (filter.PriceLessThanMax) + query.Where(p => p.Price < filter.MaxPrice.Value); + else + query.Where(p => p.Price <= filter.MaxPrice.Value); + } if (filter.CreatedFrom.HasValue) { From e54900ac679d0c639166c5e8cdcf95926e3af0fd Mon Sep 17 00:00:00 2001 From: zribktad Date: Tue, 8 Sep 2026 18:33:06 +0200 Subject: [PATCH 3/7] feat(arch): implement dynamic IAppModule discovery and Wolverine ErrorOr pipeline --- src/APITemplate/Api/Program.cs | 7 +- .../Api/WolverineModuleDiscovery.cs | 32 ++----- .../BuildingBlocks.Application.csproj | 1 + .../Modules/AppModuleLoader.cs | 74 +++++++++++++++ .../Modules/IAppModule.cs | 32 +++++++ .../Pipeline/ErrorOrRailwayPolicy.cs | 84 +++++++++++++++++ .../Pipeline/ErrorOrUnwrapFrame.cs | 58 ++++++++++++ .../BackgroundJobs/BackgroundJobsAppModule.cs | 18 ++++ src/Modules/Chatting/ChattingAppModule.cs | 18 ++++ .../FileStorage/FileStorageAppModule.cs | 18 ++++ .../AssignUserRolesCommandHandler.cs | 6 +- .../User/CreateUser/CreateUserCommand.cs | 4 +- .../User/DeleteUser/DeleteUserCommand.cs | 8 +- .../SetUserActive/SetUserActiveCommand.cs | 8 +- .../User/UpdateUser/UpdateUserCommand.cs | 8 +- src/Modules/Identity/IdentityAppModule.cs | 18 ++++ .../Notifications/NotificationsAppModule.cs | 18 ++++ .../ProductCatalog/ProductCatalogAppModule.cs | 18 ++++ src/Modules/Reviews/ReviewsAppModule.cs | 18 ++++ src/Modules/Webhooks/WebhooksAppModule.cs | 18 ++++ .../Architecture/HandlerConventionTests.cs | 90 +++++++++++++++++++ .../Unit/Architecture/ModuleDiscoveryTests.cs | 38 ++++++++ .../Unit/Handlers/UserRequestHandlersTests.cs | 65 +++++--------- .../Identity/CreateUserCommandHandlerTests.cs | 6 +- 24 files changed, 567 insertions(+), 98 deletions(-) create mode 100644 src/BuildingBlocks/BuildingBlocks.Application/Modules/AppModuleLoader.cs create mode 100644 src/BuildingBlocks/BuildingBlocks.Application/Modules/IAppModule.cs create mode 100644 src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrRailwayPolicy.cs create mode 100644 src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrUnwrapFrame.cs create mode 100644 src/Modules/BackgroundJobs/BackgroundJobsAppModule.cs create mode 100644 src/Modules/Chatting/ChattingAppModule.cs create mode 100644 src/Modules/FileStorage/FileStorageAppModule.cs create mode 100644 src/Modules/Identity/IdentityAppModule.cs create mode 100644 src/Modules/Notifications/NotificationsAppModule.cs create mode 100644 src/Modules/ProductCatalog/ProductCatalogAppModule.cs create mode 100644 src/Modules/Reviews/ReviewsAppModule.cs create mode 100644 src/Modules/Webhooks/WebhooksAppModule.cs create mode 100644 tests/APITemplate.Tests/Unit/Architecture/HandlerConventionTests.cs create mode 100644 tests/APITemplate.Tests/Unit/Architecture/ModuleDiscoveryTests.cs diff --git a/src/APITemplate/Api/Program.cs b/src/APITemplate/Api/Program.cs index 0ad39b00..79cf46d0 100644 --- a/src/APITemplate/Api/Program.cs +++ b/src/APITemplate/Api/Program.cs @@ -6,9 +6,11 @@ using BackgroundJobs; using BuildingBlocks.Application.Context; using BuildingBlocks.Application.Http; +using BuildingBlocks.Application.Modules; using BuildingBlocks.Application.Options; using BuildingBlocks.Infrastructure.EFCore.Persistence; using BuildingBlocks.Infrastructure.Mongo; +using BuildingBlocks.Messaging.Pipeline; using BuildingBlocks.Web.Health; using Chatting; using FileStorage; @@ -103,7 +105,7 @@ builder.Services.AddRateLimiting(builder.Configuration); builder.Services.AddOpenApiDocumentation(); builder.Services.AddInfrastructureDiagnostics(); -builder.Services.AddGraphQLRegistration(builder.Environment); +builder.Services.AddGraphQLRegistration(builder.Environment, builder.Configuration); builder.Services.AddWolverineHttp(); builder.Services.AddModuleHealthChecks( @@ -141,6 +143,9 @@ // Only activates for handlers with a DbContext enrolled via AddDbContextWithWolverineIntegration. options.UseEntityFrameworkCoreTransactions(); + // Automatically short-circuit on ErrorOr before-phase errors and unwrap success values. + options.Policies.Add(new ErrorOrRailwayPolicy()); + // UseDurableLocalQueues persists cascading messages in PostgreSQL so they survive a crash // between handler commit and message dispatch. UseStrictLocalQueues would additionally // guarantee in-order processing per queue — not needed here since handlers are idempotent. diff --git a/src/APITemplate/Api/WolverineModuleDiscovery.cs b/src/APITemplate/Api/WolverineModuleDiscovery.cs index 36f6f19e..56dbd44f 100644 --- a/src/APITemplate/Api/WolverineModuleDiscovery.cs +++ b/src/APITemplate/Api/WolverineModuleDiscovery.cs @@ -1,44 +1,22 @@ using System.Collections.Immutable; using System.Reflection; -using BackgroundJobs.Features; -using Chatting.Features.GetNotificationStream; -using FileStorage.Features.Upload; -using Identity.Directory.Features.User; -using Identity.Directory.Handlers; -using Notifications.Features; -using ProductCatalog.Features.Product.CreateProducts; -using ProductCatalog.Handlers; -using Reviews.Features; -using Webhooks.Features.SendWebhookCallback; +using BuildingBlocks.Application.Modules; namespace APITemplate.Api; /// -/// Central list of module assemblies for Wolverine handler discovery. +/// Dynamic discovery of module assemblies for Wolverine handler registration. /// public static class WolverineModuleDiscovery { /// - /// All module assemblies scanned for Wolverine handlers. + /// All module assemblies scanned for Wolverine handlers, discovered dynamically. /// public static IReadOnlyList HandlerAssemblies { get; } = BuildHandlerAssemblies(); private static ImmutableArray BuildHandlerAssemblies() { - Assembly[] assemblies = - [ - typeof(CreateUserCommand).Assembly, - typeof(CreateProductsCommand).Assembly, - typeof(CreateProductReviewCommand).Assembly, - typeof(UploadFileCommand).Assembly, - typeof(SubmitJobCommand).Assembly, - typeof(CleanupExpiredInvitationsHandler).Assembly, - typeof(CleanupOrphanedProductDataHandler).Assembly, - typeof(SendWebhookCallbackHandler).Assembly, - typeof(GetNotificationStreamQuery).Assembly, - typeof(UserRegisteredEmailHandler).Assembly, - ]; - - return assemblies.Distinct().ToImmutableArray(); + IReadOnlyList modules = AppModuleLoader.Discover(); + return modules.SelectMany(m => m.Assemblies).Distinct().ToImmutableArray(); } } diff --git a/src/BuildingBlocks/BuildingBlocks.Application/BuildingBlocks.Application.csproj b/src/BuildingBlocks/BuildingBlocks.Application/BuildingBlocks.Application.csproj index af078092..e2d7ba5b 100644 --- a/src/BuildingBlocks/BuildingBlocks.Application/BuildingBlocks.Application.csproj +++ b/src/BuildingBlocks/BuildingBlocks.Application/BuildingBlocks.Application.csproj @@ -7,6 +7,7 @@ + diff --git a/src/BuildingBlocks/BuildingBlocks.Application/Modules/AppModuleLoader.cs b/src/BuildingBlocks/BuildingBlocks.Application/Modules/AppModuleLoader.cs new file mode 100644 index 00000000..fb2871c2 --- /dev/null +++ b/src/BuildingBlocks/BuildingBlocks.Application/Modules/AppModuleLoader.cs @@ -0,0 +1,74 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyModel; + +namespace BuildingBlocks.Application.Modules; + +/// +/// Dynamically discovers feature modules in the runtime context. +/// +public static class AppModuleLoader +{ + private static readonly string[] RecognizedModulePrefixes = + [ + "Identity", + "ProductCatalog", + "Reviews", + "Notifications", + "FileStorage", + "BackgroundJobs", + "Webhooks", + "Chatting", + ]; + + public static IReadOnlyList Discover() + { + DependencyContext context = + DependencyContext.Default + ?? throw new InvalidOperationException( + "DependencyContext.Default is null. Module auto-discovery requires a runtime with a populated dependency context." + ); + + List modules = context + .RuntimeLibraries.Where(l => + RecognizedModulePrefixes.Any(p => + l.Name.Equals(p, StringComparison.OrdinalIgnoreCase) + || l.Name.StartsWith(p + ".", StringComparison.OrdinalIgnoreCase) + ) + ) + .Select(l => Assembly.Load(new AssemblyName(l.Name))) + .SelectMany(a => + { + try + { + return a.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(t => t != null).Select(t => t!); + } + }) + .Where(t => + t != null + && typeof(IAppModule).IsAssignableFrom(t) + && t is { IsClass: true, IsAbstract: false } + ) + .Select(t => CreateModuleInstance(t!)) + .OrderBy(m => m.Name, StringComparer.Ordinal) + .ToList(); + + return modules; + } + + private static IAppModule CreateModuleInstance(Type moduleType) + { + ConstructorInfo? ctor = moduleType.GetConstructor(Type.EmptyTypes); + if (ctor is null) + { + throw new InvalidOperationException( + $"Module '{moduleType.FullName}' must declare a public parameterless constructor for auto-discovery." + ); + } + + return (IAppModule)ctor.Invoke(null); + } +} diff --git a/src/BuildingBlocks/BuildingBlocks.Application/Modules/IAppModule.cs b/src/BuildingBlocks/BuildingBlocks.Application/Modules/IAppModule.cs new file mode 100644 index 00000000..18b1a752 --- /dev/null +++ b/src/BuildingBlocks/BuildingBlocks.Application/Modules/IAppModule.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BuildingBlocks.Application.Modules; + +/// +/// Contract implemented by feature modules for dynamic discovery and registration. +/// +public interface IAppModule +{ + /// + /// Unique name of the module (e.g., "Identity", "ProductCatalog"). + /// + string Name { get; } + + /// + /// All assemblies contributed by this module (used for Wolverine handler scanning, controllers, etc.). + /// + IEnumerable Assemblies { get; } + + /// + /// Registers the module's services in DI. + /// + void RegisterServices(IServiceCollection services, IConfiguration configuration); + + /// + /// Optional pipeline configuration for the module (endpoints, routes). + /// + void ConfigureEndpoints(IEndpointRouteBuilder endpoints) { } +} diff --git a/src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrRailwayPolicy.cs b/src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrRailwayPolicy.cs new file mode 100644 index 00000000..78b0219a --- /dev/null +++ b/src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrRailwayPolicy.cs @@ -0,0 +1,84 @@ +using ErrorOr; +using JasperFx; +using JasperFx.CodeGeneration; +using JasperFx.CodeGeneration.Frames; +using JasperFx.CodeGeneration.Model; +using Wolverine.Configuration; +using Wolverine.Runtime.Handlers; + +namespace BuildingBlocks.Messaging.Pipeline; + +/// +/// Wolverine handler policy that automatically inserts ErrorOrUnwrapFrame after every before-phase +/// method returning ErrorOr<T>, eliminating repetitive manual IsError guards. +/// +public sealed class ErrorOrRailwayPolicy : IHandlerPolicy +{ + public void Apply( + IReadOnlyList chains, + GenerationRules rules, + IServiceContainer container + ) + { + foreach (HandlerChain chain in chains) + { + Type? responseErrorOrType = TryGetErrorOrResponseType(chain); + if (responseErrorOrType is null) + { + continue; + } + + chain.ApplyImpliedMiddlewareFromHandlers(rules); + + List beforeCalls = chain + .Middleware.OfType() + .Where(call => ClosesErrorOr(call.ReturnVariable?.VariableType)) + .ToList(); + + foreach (MethodCall call in beforeCalls) + { + Variable beforeResult = call.ReturnVariable!; + Type unwrappedType = beforeResult.VariableType.GetGenericArguments()[0]; + int index = chain.Middleware.IndexOf(call); + chain.Middleware.Insert( + index + 1, + new ErrorOrUnwrapFrame(beforeResult, unwrappedType, responseErrorOrType) + ); + } + } + } + + private static Type? TryGetErrorOrResponseType(HandlerChain chain) + { + foreach (MethodCall handler in chain.HandlerCalls()) + { + Type? returnType = handler.ReturnType; + if (returnType is null) + { + continue; + } + + if (ClosesErrorOr(returnType)) + { + return returnType; + } + + if (IsValueTuple(returnType)) + { + Type firstItem = returnType.GetGenericArguments()[0]; + if (ClosesErrorOr(firstItem)) + { + return firstItem; + } + } + } + + return null; + } + + private static bool ClosesErrorOr(Type? type) => + type is { IsGenericType: true } && type.GetGenericTypeDefinition() == typeof(ErrorOr<>); + + private static bool IsValueTuple(Type type) => + type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTuple<,>); +} diff --git a/src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrUnwrapFrame.cs b/src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrUnwrapFrame.cs new file mode 100644 index 00000000..c1937feb --- /dev/null +++ b/src/BuildingBlocks/BuildingBlocks.Messaging/Pipeline/ErrorOrUnwrapFrame.cs @@ -0,0 +1,58 @@ +using ErrorOr; +using JasperFx.CodeGeneration; +using JasperFx.CodeGeneration.Frames; +using JasperFx.CodeGeneration.Model; +using JasperFx.Core.Reflection; +using Wolverine.Runtime; + +namespace BuildingBlocks.Messaging.Pipeline; + +/// +/// Generated middleware frame that unwraps ErrorOr before-phase results and short-circuits on failure. +/// +internal sealed class ErrorOrUnwrapFrame : AsyncFrame +{ + private readonly Variable _beforeResult; + private readonly Type _responseErrorOrType; + private readonly Variable? _unwrapped; + private Variable? _context; + + public ErrorOrUnwrapFrame(Variable beforeResult, Type unwrappedType, Type responseErrorOrType) + { + _beforeResult = beforeResult; + _responseErrorOrType = responseErrorOrType; + + if (unwrappedType != typeof(Success)) + { + _unwrapped = new Variable(unwrappedType, this); + } + } + + public override IEnumerable FindVariables(IMethodVariables chain) + { + _context = chain.FindVariable(typeof(MessageContext)); + yield return _beforeResult; + yield return _context; + } + + public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) + { + string before = _beforeResult.Usage; + string context = _context!.Usage; + string responseType = _responseErrorOrType.FullNameInCode(); + + writer.Write($"BLOCK:if ({before}.IsError)"); + writer.Write( + $"await {context}.EnqueueCascadingAsync(({responseType}){before}.Errors).ConfigureAwait(false);" + ); + writer.Write("return;"); + writer.FinishBlock(); + + if (_unwrapped is not null) + { + writer.Write($"var {_unwrapped.Usage} = {before}.Value;"); + } + + Next?.GenerateCode(method, writer); + } +} diff --git a/src/Modules/BackgroundJobs/BackgroundJobsAppModule.cs b/src/Modules/BackgroundJobs/BackgroundJobsAppModule.cs new file mode 100644 index 00000000..91e7792c --- /dev/null +++ b/src/Modules/BackgroundJobs/BackgroundJobsAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BackgroundJobs; + +public sealed class BackgroundJobsAppModule : IAppModule +{ + public string Name => "BackgroundJobs"; + + public IEnumerable Assemblies => [typeof(BackgroundJobsAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddBackgroundJobsModule(configuration); + } +} diff --git a/src/Modules/Chatting/ChattingAppModule.cs b/src/Modules/Chatting/ChattingAppModule.cs new file mode 100644 index 00000000..e8f9bf0f --- /dev/null +++ b/src/Modules/Chatting/ChattingAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Chatting; + +public sealed class ChattingAppModule : IAppModule +{ + public string Name => "Chatting"; + + public IEnumerable Assemblies => [typeof(ChattingAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddChattingModule(configuration); + } +} diff --git a/src/Modules/FileStorage/FileStorageAppModule.cs b/src/Modules/FileStorage/FileStorageAppModule.cs new file mode 100644 index 00000000..9c5a621a --- /dev/null +++ b/src/Modules/FileStorage/FileStorageAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace FileStorage; + +public sealed class FileStorageAppModule : IAppModule +{ + public string Name => "FileStorage"; + + public IEnumerable Assemblies => [typeof(FileStorageAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddFileStorageModule(configuration); + } +} diff --git a/src/Modules/Identity/Directory/Features/User/AssignRoles/AssignUserRolesCommandHandler.cs b/src/Modules/Identity/Directory/Features/User/AssignRoles/AssignUserRolesCommandHandler.cs index fbc75d9d..24d2b1c7 100644 --- a/src/Modules/Identity/Directory/Features/User/AssignRoles/AssignUserRolesCommandHandler.cs +++ b/src/Modules/Identity/Directory/Features/User/AssignRoles/AssignUserRolesCommandHandler.cs @@ -28,14 +28,10 @@ CancellationToken ct IUserRepository userRepository, IRoleRepository roleRepository, IUnitOfWork unitOfWork, - ErrorOr userResult, + AppUser user, CancellationToken ct ) { - if (userResult.IsError) - return (userResult.Errors, OutgoingMessagesHelper.Empty); - var user = userResult.Value; - List distinctRoleIds = (command.Request.RoleIds ?? []).Distinct().ToList(); List requestedRoles = await roleRepository.ListAsync( new RolesByIdsSpecification(distinctRoleIds), diff --git a/src/Modules/Identity/Directory/Features/User/CreateUser/CreateUserCommand.cs b/src/Modules/Identity/Directory/Features/User/CreateUser/CreateUserCommand.cs index 57069c9c..efdcd6b8 100644 --- a/src/Modules/Identity/Directory/Features/User/CreateUser/CreateUserCommand.cs +++ b/src/Modules/Identity/Directory/Features/User/CreateUser/CreateUserCommand.cs @@ -3,6 +3,7 @@ using Identity.Directory.Repositories; using Microsoft.EntityFrameworkCore; using Wolverine; +using Wolverine.Attributes; namespace Identity.Directory.Features.User; @@ -10,7 +11,8 @@ public sealed record CreateUserCommand(CreateUserRequest Request); public sealed class CreateUserCommandHandler { - public static async Task> ValidateAsync( + [WolverineBefore] + public static async Task> EnsureUniqueAsync( CreateUserCommand command, IUserUniquenessChecker uniqueness, CancellationToken ct diff --git a/src/Modules/Identity/Directory/Features/User/DeleteUser/DeleteUserCommand.cs b/src/Modules/Identity/Directory/Features/User/DeleteUser/DeleteUserCommand.cs index 7bed8cb3..7f74fbc2 100644 --- a/src/Modules/Identity/Directory/Features/User/DeleteUser/DeleteUserCommand.cs +++ b/src/Modules/Identity/Directory/Features/User/DeleteUser/DeleteUserCommand.cs @@ -7,7 +7,7 @@ public sealed record DeleteUserCommand(Guid Id) : IHasId; public sealed class DeleteUserCommandHandler { - public static async Task> ValidateAsync( + public static async Task> LoadAsync( DeleteUserCommand command, IUserRepository repository, CancellationToken ct @@ -17,14 +17,10 @@ CancellationToken ct DeleteUserCommand command, IUserRepository repository, IUnitOfWork unitOfWork, - ErrorOr userResult, + AppUser user, CancellationToken ct ) { - if (userResult.IsError) - return (userResult.Errors, OutgoingMessagesHelper.Empty); - AppUser user = userResult.Value; - await repository.DeleteAsync(user, ct); await unitOfWork.CommitAsync(ct); diff --git a/src/Modules/Identity/Directory/Features/User/SetUserActive/SetUserActiveCommand.cs b/src/Modules/Identity/Directory/Features/User/SetUserActive/SetUserActiveCommand.cs index 78cb2ab7..9f8fb830 100644 --- a/src/Modules/Identity/Directory/Features/User/SetUserActive/SetUserActiveCommand.cs +++ b/src/Modules/Identity/Directory/Features/User/SetUserActive/SetUserActiveCommand.cs @@ -7,7 +7,7 @@ public sealed record SetUserActiveCommand(Guid Id, bool IsActive) : IHasId; public sealed class SetUserActiveCommandHandler { - public static async Task> ValidateAsync( + public static async Task> LoadAsync( SetUserActiveCommand command, IUserRepository repository, CancellationToken ct @@ -17,14 +17,10 @@ CancellationToken ct SetUserActiveCommand command, IUserRepository repository, IUnitOfWork unitOfWork, - ErrorOr userResult, + AppUser user, CancellationToken ct ) { - if (userResult.IsError) - return (userResult.Errors, OutgoingMessagesHelper.Empty); - AppUser user = userResult.Value; - user.IsActive = command.IsActive; await repository.UpdateAsync(user, ct); await unitOfWork.CommitAsync(ct); diff --git a/src/Modules/Identity/Directory/Features/User/UpdateUser/UpdateUserCommand.cs b/src/Modules/Identity/Directory/Features/User/UpdateUser/UpdateUserCommand.cs index 16063001..afafffdd 100644 --- a/src/Modules/Identity/Directory/Features/User/UpdateUser/UpdateUserCommand.cs +++ b/src/Modules/Identity/Directory/Features/User/UpdateUser/UpdateUserCommand.cs @@ -10,7 +10,7 @@ public sealed record UpdateUserCommand(Guid Id, UpdateUserRequest Request) : IHa public sealed class UpdateUserCommandHandler { - public static async Task> ValidateAsync( + public static async Task> LoadAsync( UpdateUserCommand command, IUserRepository repository, IUserUniquenessChecker uniqueness, @@ -60,14 +60,10 @@ CancellationToken ct UpdateUserCommand command, IUserRepository repository, IUnitOfWork unitOfWork, - ErrorOr validationResult, + AppUser user, CancellationToken ct ) { - if (validationResult.IsError) - return (validationResult.Errors, OutgoingMessagesHelper.Empty); - AppUser user = validationResult.Value; - user.Username = new NormalizedString(command.Request.Username); user.Email = new NormalizedString(command.Request.Email); diff --git a/src/Modules/Identity/IdentityAppModule.cs b/src/Modules/Identity/IdentityAppModule.cs new file mode 100644 index 00000000..bb03c1e6 --- /dev/null +++ b/src/Modules/Identity/IdentityAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Identity; + +public sealed class IdentityAppModule : IAppModule +{ + public string Name => "Identity"; + + public IEnumerable Assemblies => [typeof(IdentityAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddIdentityModule(configuration); + } +} diff --git a/src/Modules/Notifications/NotificationsAppModule.cs b/src/Modules/Notifications/NotificationsAppModule.cs new file mode 100644 index 00000000..e79ab6a0 --- /dev/null +++ b/src/Modules/Notifications/NotificationsAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Notifications; + +public sealed class NotificationsAppModule : IAppModule +{ + public string Name => "Notifications"; + + public IEnumerable Assemblies => [typeof(NotificationsAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddNotificationsModule(configuration); + } +} diff --git a/src/Modules/ProductCatalog/ProductCatalogAppModule.cs b/src/Modules/ProductCatalog/ProductCatalogAppModule.cs new file mode 100644 index 00000000..053fbefb --- /dev/null +++ b/src/Modules/ProductCatalog/ProductCatalogAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace ProductCatalog; + +public sealed class ProductCatalogAppModule : IAppModule +{ + public string Name => "ProductCatalog"; + + public IEnumerable Assemblies => [typeof(ProductCatalogAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddProductCatalogModule(configuration); + } +} diff --git a/src/Modules/Reviews/ReviewsAppModule.cs b/src/Modules/Reviews/ReviewsAppModule.cs new file mode 100644 index 00000000..f435ab51 --- /dev/null +++ b/src/Modules/Reviews/ReviewsAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Reviews; + +public sealed class ReviewsAppModule : IAppModule +{ + public string Name => "Reviews"; + + public IEnumerable Assemblies => [typeof(ReviewsAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddReviewsModule(configuration); + } +} diff --git a/src/Modules/Webhooks/WebhooksAppModule.cs b/src/Modules/Webhooks/WebhooksAppModule.cs new file mode 100644 index 00000000..339bad9f --- /dev/null +++ b/src/Modules/Webhooks/WebhooksAppModule.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Webhooks; + +public sealed class WebhooksAppModule : IAppModule +{ + public string Name => "Webhooks"; + + public IEnumerable Assemblies => [typeof(WebhooksAppModule).Assembly]; + + public void RegisterServices(IServiceCollection services, IConfiguration configuration) + { + services.AddWebhooksModule(configuration); + } +} diff --git a/tests/APITemplate.Tests/Unit/Architecture/HandlerConventionTests.cs b/tests/APITemplate.Tests/Unit/Architecture/HandlerConventionTests.cs new file mode 100644 index 00000000..4fe1b80c --- /dev/null +++ b/tests/APITemplate.Tests/Unit/Architecture/HandlerConventionTests.cs @@ -0,0 +1,90 @@ +using System.Reflection; +using BuildingBlocks.Application.Modules; +using Shouldly; +using Wolverine.Attributes; +using Xunit; + +namespace APITemplate.Tests.Unit.Architecture; + +[Trait("Category", "Unit")] +public class HandlerConventionTests +{ + private static readonly IReadOnlyList Handlers = AppModuleLoader + .Discover() + .SelectMany(m => m.Assemblies) + .Distinct() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type is { IsClass: true, IsAbstract: false } && IsHandlerType(type)) + .ToList(); + + [Fact] + public void Handlers_should_be_sealed() + { + List violations = Handlers + .Where(handler => !handler.IsSealed) + .Select(handler => $"{handler.FullName} — handler must be 'sealed'.") + .ToList(); + + violations.ShouldBeEmpty( + "Wolverine command/query/event handlers must be sealed classes:" + + Environment.NewLine + + string.Join(Environment.NewLine, violations) + ); + } + + [Fact] + public void Handlers_should_not_declare_a_Validate_method() + { + const BindingFlags flags = + BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.Static + | BindingFlags.Instance + | BindingFlags.DeclaredOnly; + List violations = new(); + + foreach (Type handler in Handlers) + { + IEnumerable validateMethods = handler + .GetMethods(flags) + .Where(method => + method.Name.StartsWith("Validate", StringComparison.Ordinal) + && !method.Name.EndsWith("Command", StringComparison.Ordinal) + && !method.Name.EndsWith("Query", StringComparison.Ordinal) + ); + + foreach (MethodInfo method in validateMethods) + { + // Private helper methods inside handler are allowed if they are simple local validators + if ( + !method.IsPublic + && !method + .GetCustomAttributes() + .Any(a => a.GetType().Name.Contains("Wolverine")) + ) + { + continue; + } + + violations.Add( + $"{handler.Name}.{method.Name} — Wolverine special-cases 'Validate*', so its ErrorOr " + + "is never published to subsequent phases. Rename to 'Ensure*'." + ); + } + } + + violations.ShouldBeEmpty( + "Handlers must not declare public Validate* lifecycle methods (use Ensure* guards):" + + Environment.NewLine + + string.Join(Environment.NewLine, violations) + ); + } + + private static bool IsHandlerType(Type type) => + type.Name.EndsWith("CommandHandler", StringComparison.Ordinal) + || type.Name.EndsWith("QueryHandler", StringComparison.Ordinal) + || ( + type.Name.StartsWith("On", StringComparison.Ordinal) + && type.Name.EndsWith("Handler", StringComparison.Ordinal) + ); +} diff --git a/tests/APITemplate.Tests/Unit/Architecture/ModuleDiscoveryTests.cs b/tests/APITemplate.Tests/Unit/Architecture/ModuleDiscoveryTests.cs new file mode 100644 index 00000000..9c041096 --- /dev/null +++ b/tests/APITemplate.Tests/Unit/Architecture/ModuleDiscoveryTests.cs @@ -0,0 +1,38 @@ +using BuildingBlocks.Application.Modules; +using Shouldly; +using Xunit; + +namespace APITemplate.Tests.Unit.Architecture; + +[Trait("Category", "Unit")] +public class ModuleDiscoveryTests +{ + [Fact] + public void Discover_should_find_all_feature_modules() + { + IReadOnlyList modules = AppModuleLoader.Discover(); + + modules.ShouldNotBeEmpty(); + string[] moduleNames = modules.Select(m => m.Name).OrderBy(n => n).ToArray(); + + moduleNames.ShouldContain("BackgroundJobs"); + moduleNames.ShouldContain("Chatting"); + moduleNames.ShouldContain("FileStorage"); + moduleNames.ShouldContain("Identity"); + moduleNames.ShouldContain("Notifications"); + moduleNames.ShouldContain("ProductCatalog"); + moduleNames.ShouldContain("Reviews"); + moduleNames.ShouldContain("Webhooks"); + } + + [Fact] + public void Each_module_should_contribute_at_least_one_assembly() + { + IReadOnlyList modules = AppModuleLoader.Discover(); + + foreach (IAppModule module in modules) + { + module.Assemblies.ShouldNotBeEmpty($"Module {module.Name} contributed no assemblies."); + } + } +} diff --git a/tests/APITemplate.Tests/Unit/Handlers/UserRequestHandlersTests.cs b/tests/APITemplate.Tests/Unit/Handlers/UserRequestHandlersTests.cs index ab6f364f..917e8af1 100644 --- a/tests/APITemplate.Tests/Unit/Handlers/UserRequestHandlersTests.cs +++ b/tests/APITemplate.Tests/Unit/Handlers/UserRequestHandlersTests.cs @@ -158,7 +158,7 @@ public async Task UpdateAsync_WhenUserExists_UpdatesFields() new UpdateUserRequest("updateduser", "updated@test.com") ); - ErrorOr validation = await UpdateUserCommandHandler.ValidateAsync( + ErrorOr loadResult = await UpdateUserCommandHandler.LoadAsync( command, _repositoryMock.Object, Uniqueness, @@ -169,7 +169,7 @@ await UpdateUserCommandHandler.HandleAsync( command, _repositoryMock.Object, _unitOfWorkMock.Object, - validation, + loadResult.Value, ct ); @@ -195,7 +195,7 @@ public async Task UpdateAsync_WhenSameEmailAndUsername_SkipsUniquenessCheck() new UpdateUserRequest(user.Username.Value, user.Email.Value) ); - ErrorOr validation = await UpdateUserCommandHandler.ValidateAsync( + ErrorOr loadResult = await UpdateUserCommandHandler.LoadAsync( command, _repositoryMock.Object, Uniqueness, @@ -206,7 +206,7 @@ await UpdateUserCommandHandler.HandleAsync( command, _repositoryMock.Object, _unitOfWorkMock.Object, - validation, + loadResult.Value, TestContext.Current.CancellationToken ); @@ -231,22 +231,15 @@ public async Task UpdateAsync_WhenUserNotFound_ReturnsNotFoundError() UpdateUserCommand command = new(Guid.NewGuid(), new UpdateUserRequest("name", "e@e.com")); - ErrorOr validation = await UpdateUserCommandHandler.ValidateAsync( + ErrorOr loadResult = await UpdateUserCommandHandler.LoadAsync( command, _repositoryMock.Object, Uniqueness, TestContext.Current.CancellationToken ); - (ErrorOr result, _) = await UpdateUserCommandHandler.HandleAsync( - command, - _repositoryMock.Object, - _unitOfWorkMock.Object, - validation, - TestContext.Current.CancellationToken - ); - result.IsError.ShouldBeTrue(); - result.FirstError.Type.ShouldBe(ErrorType.NotFound); + loadResult.IsError.ShouldBeTrue(); + loadResult.FirstError.Type.ShouldBe(ErrorType.NotFound); } [Fact] @@ -265,7 +258,7 @@ public async Task UpdateAsync_WhenNewEmailExists_ReturnsConflictError() new UpdateUserRequest(user.Username.Value, "taken@test.com") ); - ErrorOr validation = await UpdateUserCommandHandler.ValidateAsync( + ErrorOr validation = await UpdateUserCommandHandler.LoadAsync( command, _repositoryMock.Object, Uniqueness, @@ -299,7 +292,7 @@ public async Task UpdateAsync_WhenWriteHitsUniqueUsernameRace_ReturnsConflictAnd user.Id, new UpdateUserRequest("another-name", "another@test.com") ); - ErrorOr validation = await UpdateUserCommandHandler.ValidateAsync( + ErrorOr loadResult = await UpdateUserCommandHandler.LoadAsync( command, _repositoryMock.Object, Uniqueness, @@ -310,7 +303,7 @@ await UpdateUserCommandHandler.HandleAsync( command, _repositoryMock.Object, _unitOfWorkMock.Object, - validation, + loadResult.Value, ct ); @@ -332,7 +325,7 @@ public async Task ActivateAsync_SetsIsActiveToTrue() SetUserActiveCommand command = new(user.Id, IsActive: true); - ErrorOr validation = await SetUserActiveCommandHandler.ValidateAsync( + ErrorOr loadResult = await SetUserActiveCommandHandler.LoadAsync( command, _repositoryMock.Object, TestContext.Current.CancellationToken @@ -342,7 +335,7 @@ await SetUserActiveCommandHandler.HandleAsync( command, _repositoryMock.Object, _unitOfWorkMock.Object, - validation, + loadResult.Value, TestContext.Current.CancellationToken ); @@ -363,7 +356,7 @@ public async Task DeactivateAsync_SetsIsActiveToFalse() SetUserActiveCommand command = new(user.Id, IsActive: false); - ErrorOr validation = await SetUserActiveCommandHandler.ValidateAsync( + ErrorOr loadResult = await SetUserActiveCommandHandler.LoadAsync( command, _repositoryMock.Object, TestContext.Current.CancellationToken @@ -373,7 +366,7 @@ await SetUserActiveCommandHandler.HandleAsync( command, _repositoryMock.Object, _unitOfWorkMock.Object, - validation, + loadResult.Value, TestContext.Current.CancellationToken ); @@ -393,21 +386,14 @@ public async Task ActivateAsync_WhenUserNotFound_ReturnsNotFoundError() SetUserActiveCommand command = new(Guid.NewGuid(), IsActive: true); - ErrorOr validation = await SetUserActiveCommandHandler.ValidateAsync( - command, - _repositoryMock.Object, - TestContext.Current.CancellationToken - ); - (ErrorOr result, _) = await SetUserActiveCommandHandler.HandleAsync( + ErrorOr loadResult = await SetUserActiveCommandHandler.LoadAsync( command, _repositoryMock.Object, - _unitOfWorkMock.Object, - validation, TestContext.Current.CancellationToken ); - result.IsError.ShouldBeTrue(); - result.FirstError.Type.ShouldBe(ErrorType.NotFound); + loadResult.IsError.ShouldBeTrue(); + loadResult.FirstError.Type.ShouldBe(ErrorType.NotFound); } // --- DeleteAsync --- @@ -422,7 +408,7 @@ public async Task DeleteAsync_CallsRepositoryDeleteAndCommits() DeleteUserCommand command = new(user.Id); - ErrorOr validation = await DeleteUserCommandHandler.ValidateAsync( + ErrorOr loadResult = await DeleteUserCommandHandler.LoadAsync( command, _repositoryMock.Object, TestContext.Current.CancellationToken @@ -432,7 +418,7 @@ await DeleteUserCommandHandler.HandleAsync( command, _repositoryMock.Object, _unitOfWorkMock.Object, - validation, + loadResult.Value, TestContext.Current.CancellationToken ); @@ -451,21 +437,14 @@ public async Task DeleteAsync_WhenUserNotFound_ReturnsNotFoundError() DeleteUserCommand command = new(Guid.NewGuid()); - ErrorOr validation = await DeleteUserCommandHandler.ValidateAsync( + ErrorOr loadResult = await DeleteUserCommandHandler.LoadAsync( command, _repositoryMock.Object, TestContext.Current.CancellationToken ); - (ErrorOr result, _) = await DeleteUserCommandHandler.HandleAsync( - command, - _repositoryMock.Object, - _unitOfWorkMock.Object, - validation, - TestContext.Current.CancellationToken - ); - result.IsError.ShouldBeTrue(); - result.FirstError.Type.ShouldBe(ErrorType.NotFound); + loadResult.IsError.ShouldBeTrue(); + loadResult.FirstError.Type.ShouldBe(ErrorType.NotFound); } // --- Helpers --- diff --git a/tests/APITemplate.Tests/Unit/Identity/CreateUserCommandHandlerTests.cs b/tests/APITemplate.Tests/Unit/Identity/CreateUserCommandHandlerTests.cs index 602ea131..1a19505f 100644 --- a/tests/APITemplate.Tests/Unit/Identity/CreateUserCommandHandlerTests.cs +++ b/tests/APITemplate.Tests/Unit/Identity/CreateUserCommandHandlerTests.cs @@ -45,7 +45,7 @@ public async Task HandleAsync_Success_CreatesUserWithNullKeycloakIdAndEmitsProvi .Callback((u, _) => addedUser = u) .ReturnsAsync((AppUser u, CancellationToken _) => u); - ErrorOr validation = await CreateUserCommandHandler.ValidateAsync( + ErrorOr validation = await CreateUserCommandHandler.EnsureUniqueAsync( command, _uniqueness.Object, ct @@ -94,7 +94,7 @@ public async Task ValidateAsync_WhenEmailAlreadyExists_ReturnsConflictError() .Setup(u => u.EnsureUniqueAsync(request.Username, It.IsAny(), ct)) .ReturnsAsync(DomainErrors.Users.EmailAlreadyExists(request.Email)); - ErrorOr result = await CreateUserCommandHandler.ValidateAsync( + ErrorOr result = await CreateUserCommandHandler.EnsureUniqueAsync( new CreateUserCommand(request), _uniqueness.Object, ct @@ -115,7 +115,7 @@ public async Task ValidateAsync_WhenUsernameAlreadyExists_ReturnsConflictError() .Setup(u => u.EnsureUniqueAsync(request.Username, It.IsAny(), ct)) .ReturnsAsync(DomainErrors.Users.UsernameAlreadyExists(request.Username)); - ErrorOr result = await CreateUserCommandHandler.ValidateAsync( + ErrorOr result = await CreateUserCommandHandler.EnsureUniqueAsync( new CreateUserCommand(request), _uniqueness.Object, ct From 28f9b5ff33c96d708e0b9b1cf61fc4ff5d35c74e Mon Sep 17 00:00:00 2001 From: zribktad Date: Tue, 8 Sep 2026 18:33:24 +0200 Subject: [PATCH 4/7] feat(client): generate Kiota API client SDK and configure string enum serialization --- .config/dotnet-tools.json | 6 + .gitignore | 3 + Directory.Build.props | 2 +- Directory.Packages.props | 157 +- src/APITemplate/Api/APITemplate.csproj | 2 + ...cConventionsServiceCollectionExtensions.cs | 14 + .../APITemplate.ApiClient.csproj | 23 + .../APITemplate.ApiClient/ApiClientOptions.cs | 22 + .../Extensions/ServiceCollectionExtensions.cs | 104 + .../Generated/Api/ApiRequestBuilder.cs | 41 + .../V1/Jobs/Item/JobsItemRequestBuilder.cs | 105 + .../Api/V1/Jobs/JobsRequestBuilder.cs | 136 ++ .../Item/ProductsItemRequestBuilder.cs | 105 + .../Api/V1/Products/ProductsRequestBuilder.cs | 194 ++ .../Accept/AcceptRequestBuilder.cs | 110 + .../TenantInvitationsRequestBuilder.cs | 174 ++ .../Item/Activate/ActivateRequestBuilder.cs | 104 + .../Deactivate/DeactivateRequestBuilder.cs | 104 + .../Users/Item/Roles/RolesRequestBuilder.cs | 109 + .../V1/Users/Item/UsersItemRequestBuilder.cs | 242 ++ .../Api/V1/Users/Me/MeRequestBuilder.cs | 105 + .../Api/V1/Users/UsersRequestBuilder.cs | 200 ++ .../Generated/Api/V1/V1RequestBuilder.cs | 59 + .../Generated/ApiClient.cs | 142 ++ .../Generated/GetResponse.cs | 75 + .../Generated/Health/HealthGetResponse.cs | 55 + .../Generated/Health/HealthRequestBuilder.cs | 149 ++ .../Generated/Health/HealthResponse.cs | 28 + .../Generated/Health/Live/LiveGetResponse.cs | 55 + .../Health/Live/LiveRequestBuilder.cs | 137 ++ .../Generated/Health/Live/LiveResponse.cs | 28 + .../Health/Ready/ReadyGetResponse.cs | 55 + .../Health/Ready/ReadyRequestBuilder.cs | 137 ++ .../Generated/Health/Ready/ReadyResponse.cs | 28 + .../Models/AcceptInvitationRequest.cs | 65 + .../Generated/Models/ApiProblemDetails.cs | 122 + .../Models/AssignUserRolesRequest.cs | 65 + .../Generated/Models/CreateProductRequest.cs | 89 + .../Models/CreateTenantInvitationRequest.cs | 75 + .../Generated/Models/CreateUserRequest.cs | 85 + .../Generated/Models/InvitationStatus.cs | 27 + .../Generated/Models/JobStatus.cs | 27 + .../Generated/Models/JobStatusResponse.cs | 129 + .../Models/PagedTenantInvitationResponse.cs | 89 + .../Generated/Models/PagedUserResponse.cs | 89 + .../Generated/Models/ProductResponse.cs | 97 + .../Generated/Models/ProductsResponse.cs | 77 + .../Generated/Models/ProvisioningStatus.cs | 19 + .../Generated/Models/SubmitJobRequest.cs | 85 + .../Models/TenantInvitationResponse.cs | 81 + .../Generated/Models/UpdateUserRequest.cs | 65 + .../Generated/Models/UserResponse.cs | 101 + .../Generated/Response.cs | 28 + .../Generated/kiota-lock.json | 34 + .../APITemplate.ApiClient/openapi.json | 2102 +++++++++++++++++ .../APITemplate.Tests.csproj | 3 + .../ApiClientEnumSerializationTests.cs | 158 ++ .../RedisConnectionMultiplexerMockBuilder.cs | 8 +- 58 files changed, 6660 insertions(+), 70 deletions(-) create mode 100644 src/Clients/APITemplate.ApiClient/APITemplate.ApiClient.csproj create mode 100644 src/Clients/APITemplate.ApiClient/ApiClientOptions.cs create mode 100644 src/Clients/APITemplate.ApiClient/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/ApiRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/Item/JobsItemRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/JobsRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/Item/ProductsItemRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/ProductsRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/Accept/AcceptRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/TenantInvitationsRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Activate/ActivateRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Deactivate/DeactivateRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Roles/RolesRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/UsersItemRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Me/MeRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/UsersRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Api/V1/V1RequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/ApiClient.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/GetResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/HealthGetResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/HealthRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/HealthResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveGetResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyGetResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyRequestBuilder.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/AcceptInvitationRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/ApiProblemDetails.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/AssignUserRolesRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/CreateProductRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/CreateTenantInvitationRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/CreateUserRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/InvitationStatus.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/JobStatus.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/JobStatusResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/PagedTenantInvitationResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/PagedUserResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/ProductResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/ProductsResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/ProvisioningStatus.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/SubmitJobRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/TenantInvitationResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/UpdateUserRequest.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Models/UserResponse.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/Response.cs create mode 100644 src/Clients/APITemplate.ApiClient/Generated/kiota-lock.json create mode 100644 src/Clients/APITemplate.ApiClient/openapi.json create mode 100644 tests/APITemplate.Tests/Unit/Clients/ApiClientEnumSerializationTests.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9274d4a9..a4b5a269 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -31,6 +31,12 @@ "commands": [ "dotnet-stryker" ] + }, + "microsoft.openapi.kiota": { + "version": "1.23.0", + "commands": [ + "kiota" + ] } } } \ No newline at end of file diff --git a/.gitignore b/.gitignore index f7e0fa83..886867d5 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ publish/ # MkDocs site output docs/site/ + +*.binlog + diff --git a/Directory.Build.props b/Directory.Build.props index 1f9137ec..2291f448 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ true true true - $(NoWarn);CS1591 + $(NoWarn);CS1591;AV0029;AV0030 diff --git a/Directory.Packages.props b/Directory.Packages.props index 30cc817f..c897181d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,72 +24,77 @@ - - + + + - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - + - - - - - - - - - - - - + + + + + + + + + + + + - + @@ -98,34 +103,50 @@ - + - + - - - - - - - + + + + + + + + - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/APITemplate/Api/APITemplate.csproj b/src/APITemplate/Api/APITemplate.csproj index c5d09933..5531d83c 100644 --- a/src/APITemplate/Api/APITemplate.csproj +++ b/src/APITemplate/Api/APITemplate.csproj @@ -38,6 +38,7 @@ + @@ -69,6 +70,7 @@ + diff --git a/src/APITemplate/Api/Extensions/MvcConventionsServiceCollectionExtensions.cs b/src/APITemplate/Api/Extensions/MvcConventionsServiceCollectionExtensions.cs index 8786137f..561c9cc6 100644 --- a/src/APITemplate/Api/Extensions/MvcConventionsServiceCollectionExtensions.cs +++ b/src/APITemplate/Api/Extensions/MvcConventionsServiceCollectionExtensions.cs @@ -34,6 +34,20 @@ public static IServiceCollection AddMvcConventions(this IServiceCollection servi options.InvalidModelStateResponseFactory = BuildModelStateErrorResponse; }); + services.Configure(options => + { + options.JsonSerializerOptions.Converters.Add( + new System.Text.Json.Serialization.JsonStringEnumConverter() + ); + }); + + services.Configure(options => + { + options.SerializerOptions.Converters.Add( + new System.Text.Json.Serialization.JsonStringEnumConverter() + ); + }); + return services; } diff --git a/src/Clients/APITemplate.ApiClient/APITemplate.ApiClient.csproj b/src/Clients/APITemplate.ApiClient/APITemplate.ApiClient.csproj new file mode 100644 index 00000000..939eb222 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/APITemplate.ApiClient.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + + $(NoWarn);CS1591;CS8618;CS8601;CS8602;CS8603;CS8604;CS8765;CS8625 + + + + + + + + + + + + + + + diff --git a/src/Clients/APITemplate.ApiClient/ApiClientOptions.cs b/src/Clients/APITemplate.ApiClient/ApiClientOptions.cs new file mode 100644 index 00000000..436a1867 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/ApiClientOptions.cs @@ -0,0 +1,22 @@ +namespace APITemplate.ApiClient; + +/// +/// Configuration options for the APITemplate Kiota API Client. +/// +public sealed class ApiClientOptions +{ + /// + /// Gets or sets the base URI for the API. + /// + public Uri? BaseUrl { get; set; } + + /// + /// Gets or sets an optional bearer token or token provider callback. + /// + public Func>? AccessTokenProvider { get; set; } + + /// + /// Gets or sets the request timeout. Defaults to 30 seconds. + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); +} diff --git a/src/Clients/APITemplate.ApiClient/Extensions/ServiceCollectionExtensions.cs b/src/Clients/APITemplate.ApiClient/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..e75a07b3 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Abstractions.Authentication; +using Microsoft.Kiota.Http.HttpClientLibrary; + +namespace APITemplate.ApiClient.Extensions; + +/// +/// Extension methods for registering the Kiota ApiClient in the dependency injection container. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers the APITemplate Kiota ApiClient and its supporting request adapter and authentication services. + /// + /// The service collection to register into. + /// Delegate to configure . + /// The service collection for chaining. + public static IServiceCollection AddApiClient( + this IServiceCollection services, + Action configure + ) + { + services.Configure(configure); + + services.AddHttpClient( + (sp, httpClient) => + { + ApiClientOptions options = sp.GetRequiredService< + IOptions + >().Value; + if (options.BaseUrl is not null) + { + httpClient.BaseAddress = options.BaseUrl; + } + httpClient.Timeout = options.Timeout; + } + ); + + services.AddScoped(sp => + { + ApiClientOptions options = sp.GetRequiredService>().Value; + if (options.AccessTokenProvider is not null) + { + return new DelegateAuthenticationProvider(options.AccessTokenProvider); + } + + return new AnonymousAuthenticationProvider(); + }); + + services.AddScoped(sp => + { + IHttpClientFactory httpClientFactory = sp.GetRequiredService(); + HttpClient httpClient = httpClientFactory.CreateClient(nameof(ApiClient)); + IAuthenticationProvider authProvider = sp.GetRequiredService(); + ApiClientOptions options = sp.GetRequiredService>().Value; + + HttpClientRequestAdapter adapter = new(authProvider, httpClient: httpClient); + if (options.BaseUrl is not null) + { + adapter.BaseUrl = options.BaseUrl.ToString().TrimEnd('/'); + } + + return adapter; + }); + + services.AddScoped(sp => + { + IRequestAdapter adapter = sp.GetRequiredService(); + return new ApiClient(adapter); + }); + + return services; + } + + private sealed class DelegateAuthenticationProvider : IAuthenticationProvider + { + private readonly Func> _tokenProvider; + + public DelegateAuthenticationProvider(Func> tokenProvider) + { + _tokenProvider = tokenProvider; + } + + public async Task AuthenticateRequestAsync( + RequestInformation request, + Dictionary? additionalAuthenticationContext = null, + CancellationToken cancellationToken = default + ) + { + string? token = await _tokenProvider(cancellationToken); + if (!string.IsNullOrWhiteSpace(token)) + { + request.Headers.Add("Authorization", $"Bearer {token}"); + } + } + } +} diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/ApiRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/ApiRequestBuilder.cs new file mode 100644 index 00000000..744aab06 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/ApiRequestBuilder.cs @@ -0,0 +1,41 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System; +namespace APITemplate.ApiClient.Api +{ + /// + /// Builds and executes requests for operations under \api + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ApiRequestBuilder : BaseRequestBuilder + { + /// The v1 property + public global::APITemplate.ApiClient.Api.V1.V1RequestBuilder V1 + { + get => new global::APITemplate.ApiClient.Api.V1.V1RequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ApiRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ApiRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api", rawUrl) + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/Item/JobsItemRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/Item/JobsItemRequestBuilder.cs new file mode 100644 index 00000000..2af7eb58 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/Item/JobsItemRequestBuilder.cs @@ -0,0 +1,105 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Jobs.Item +{ + /// + /// Builds and executes requests for operations under \api\v1\jobs\{id} + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class JobsItemRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public JobsItemRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/jobs/{id}", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public JobsItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/jobs/{id}", rawUrl) + { + } + /// + /// Get background job status + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.JobStatusResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get background job status + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Jobs.Item.JobsItemRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Jobs.Item.JobsItemRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class JobsItemRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/JobsRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/JobsRequestBuilder.cs new file mode 100644 index 00000000..fae671c8 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Jobs/JobsRequestBuilder.cs @@ -0,0 +1,136 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1.Jobs.Item; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Jobs +{ + /// + /// Builds and executes requests for operations under \api\v1\jobs + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class JobsRequestBuilder : BaseRequestBuilder + { + /// Gets an item from the APITemplate.ApiClient.api.v1.jobs.item collection + /// Unique identifier of the item + /// A + public global::APITemplate.ApiClient.Api.V1.Jobs.Item.JobsItemRequestBuilder this[Guid position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + urlTplParams.Add("id", position); + return new global::APITemplate.ApiClient.Api.V1.Jobs.Item.JobsItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// Gets an item from the APITemplate.ApiClient.api.v1.jobs.item collection + /// Unique identifier of the item + /// A + [Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")] + public global::APITemplate.ApiClient.Api.V1.Jobs.Item.JobsItemRequestBuilder this[string position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("id", position); + return new global::APITemplate.ApiClient.Api.V1.Jobs.Item.JobsItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public JobsRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/jobs", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public JobsRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/jobs", rawUrl) + { + } + /// + /// Submit background job + /// + /// A + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::APITemplate.ApiClient.Models.SubmitJobRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::APITemplate.ApiClient.Models.SubmitJobRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.JobStatusResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Submit background job + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.SubmitJobRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.SubmitJobRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Jobs.JobsRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Jobs.JobsRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class JobsRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/Item/ProductsItemRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/Item/ProductsItemRequestBuilder.cs new file mode 100644 index 00000000..c429c457 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/Item/ProductsItemRequestBuilder.cs @@ -0,0 +1,105 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Products.Item +{ + /// + /// Builds and executes requests for operations under \api\v1\products\{id} + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ProductsItemRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ProductsItemRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/products/{id}", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ProductsItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/products/{id}", rawUrl) + { + } + /// + /// Get product by id + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.ProductResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get product by id + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Products.Item.ProductsItemRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Products.Item.ProductsItemRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ProductsItemRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/ProductsRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/ProductsRequestBuilder.cs new file mode 100644 index 00000000..ab9ccae2 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Products/ProductsRequestBuilder.cs @@ -0,0 +1,194 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1.Products.Item; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Products +{ + /// + /// Builds and executes requests for operations under \api\v1\products + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ProductsRequestBuilder : BaseRequestBuilder + { + /// Gets an item from the APITemplate.ApiClient.api.v1.products.item collection + /// Unique identifier of the item + /// A + public global::APITemplate.ApiClient.Api.V1.Products.Item.ProductsItemRequestBuilder this[Guid position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + urlTplParams.Add("id", position); + return new global::APITemplate.ApiClient.Api.V1.Products.Item.ProductsItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// Gets an item from the APITemplate.ApiClient.api.v1.products.item collection + /// Unique identifier of the item + /// A + [Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")] + public global::APITemplate.ApiClient.Api.V1.Products.Item.ProductsItemRequestBuilder this[string position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("id", position); + return new global::APITemplate.ApiClient.Api.V1.Products.Item.ProductsItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ProductsRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/products", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ProductsRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/products", rawUrl) + { + } + /// + /// Get products + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.ProductsResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Create product + /// + /// A + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::APITemplate.ApiClient.Models.CreateProductRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::APITemplate.ApiClient.Models.CreateProductRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.ProductResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get products + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Create product + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.CreateProductRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.CreateProductRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Products.ProductsRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Products.ProductsRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ProductsRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ProductsRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/Accept/AcceptRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/Accept/AcceptRequestBuilder.cs new file mode 100644 index 00000000..d8b4496c --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/Accept/AcceptRequestBuilder.cs @@ -0,0 +1,110 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.TenantInvitations.Accept +{ + /// + /// Builds and executes requests for operations under \api\v1\tenant-invitations\accept + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class AcceptRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public AcceptRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/tenant-invitations/accept", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public AcceptRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/tenant-invitations/accept", rawUrl) + { + } + /// + /// Accept tenant invitation + /// + /// A + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::APITemplate.ApiClient.Models.AcceptInvitationRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::APITemplate.ApiClient.Models.AcceptInvitationRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendPrimitiveAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Accept tenant invitation + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.AcceptInvitationRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.AcceptInvitationRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/problem+json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.TenantInvitations.Accept.AcceptRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.TenantInvitations.Accept.AcceptRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class AcceptRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/TenantInvitationsRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/TenantInvitationsRequestBuilder.cs new file mode 100644 index 00000000..34fed504 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/TenantInvitations/TenantInvitationsRequestBuilder.cs @@ -0,0 +1,174 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1.TenantInvitations.Accept; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.TenantInvitations +{ + /// + /// Builds and executes requests for operations under \api\v1\tenant-invitations + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class TenantInvitationsRequestBuilder : BaseRequestBuilder + { + /// The accept property + public global::APITemplate.ApiClient.Api.V1.TenantInvitations.Accept.AcceptRequestBuilder Accept + { + get => new global::APITemplate.ApiClient.Api.V1.TenantInvitations.Accept.AcceptRequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public TenantInvitationsRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/tenant-invitations", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public TenantInvitationsRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/tenant-invitations", rawUrl) + { + } + /// + /// Get tenant invitations + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.PagedTenantInvitationResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Create tenant invitation + /// + /// A + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::APITemplate.ApiClient.Models.CreateTenantInvitationRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::APITemplate.ApiClient.Models.CreateTenantInvitationRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.TenantInvitationResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get tenant invitations + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Create tenant invitation + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.CreateTenantInvitationRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.CreateTenantInvitationRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.TenantInvitations.TenantInvitationsRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.TenantInvitations.TenantInvitationsRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class TenantInvitationsRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class TenantInvitationsRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Activate/ActivateRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Activate/ActivateRequestBuilder.cs new file mode 100644 index 00000000..636a166a --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Activate/ActivateRequestBuilder.cs @@ -0,0 +1,104 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Users.Item.Activate +{ + /// + /// Builds and executes requests for operations under \api\v1\users\{id}\activate + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ActivateRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ActivateRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}/activate", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ActivateRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}/activate", rawUrl) + { + } + /// + /// Activate user + /// + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PatchAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PatchAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToPatchRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Activate user + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPatchRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPatchRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.PATCH, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/problem+json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Users.Item.Activate.ActivateRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Users.Item.Activate.ActivateRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ActivateRequestBuilderPatchRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Deactivate/DeactivateRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Deactivate/DeactivateRequestBuilder.cs new file mode 100644 index 00000000..b824f4ef --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Deactivate/DeactivateRequestBuilder.cs @@ -0,0 +1,104 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Users.Item.Deactivate +{ + /// + /// Builds and executes requests for operations under \api\v1\users\{id}\deactivate + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class DeactivateRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public DeactivateRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}/deactivate", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public DeactivateRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}/deactivate", rawUrl) + { + } + /// + /// Deactivate user + /// + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PatchAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PatchAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToPatchRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Deactivate user + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPatchRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPatchRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.PATCH, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/problem+json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Users.Item.Deactivate.DeactivateRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Users.Item.Deactivate.DeactivateRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class DeactivateRequestBuilderPatchRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Roles/RolesRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Roles/RolesRequestBuilder.cs new file mode 100644 index 00000000..cf5b29ab --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/Roles/RolesRequestBuilder.cs @@ -0,0 +1,109 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Users.Item.Roles +{ + /// + /// Builds and executes requests for operations under \api\v1\users\{id}\roles + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class RolesRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public RolesRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}/roles", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public RolesRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}/roles", rawUrl) + { + } + /// + /// Assign user roles + /// + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::APITemplate.ApiClient.Models.AssignUserRolesRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::APITemplate.ApiClient.Models.AssignUserRolesRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Assign user roles + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.AssignUserRolesRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.AssignUserRolesRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/problem+json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Users.Item.Roles.RolesRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Users.Item.Roles.RolesRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class RolesRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/UsersItemRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/UsersItemRequestBuilder.cs new file mode 100644 index 00000000..5756ec06 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Item/UsersItemRequestBuilder.cs @@ -0,0 +1,242 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1.Users.Item.Activate; +using APITemplate.ApiClient.Api.V1.Users.Item.Deactivate; +using APITemplate.ApiClient.Api.V1.Users.Item.Roles; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Users.Item +{ + /// + /// Builds and executes requests for operations under \api\v1\users\{id} + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersItemRequestBuilder : BaseRequestBuilder + { + /// The activate property + public global::APITemplate.ApiClient.Api.V1.Users.Item.Activate.ActivateRequestBuilder Activate + { + get => new global::APITemplate.ApiClient.Api.V1.Users.Item.Activate.ActivateRequestBuilder(PathParameters, RequestAdapter); + } + /// The deactivate property + public global::APITemplate.ApiClient.Api.V1.Users.Item.Deactivate.DeactivateRequestBuilder Deactivate + { + get => new global::APITemplate.ApiClient.Api.V1.Users.Item.Deactivate.DeactivateRequestBuilder(PathParameters, RequestAdapter); + } + /// The roles property + public global::APITemplate.ApiClient.Api.V1.Users.Item.Roles.RolesRequestBuilder Roles + { + get => new global::APITemplate.ApiClient.Api.V1.Users.Item.Roles.RolesRequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public UsersItemRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public UsersItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/{id}", rawUrl) + { + } + /// + /// Delete user + /// + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task DeleteAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task DeleteAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToDeleteRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get user by id + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.UserResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Update user + /// + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PutAsync(global::APITemplate.ApiClient.Models.UpdateUserRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PutAsync(global::APITemplate.ApiClient.Models.UpdateUserRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPutRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Delete user + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToDeleteRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToDeleteRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.DELETE, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/problem+json"); + return requestInfo; + } + /// + /// Get user by id + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Update user + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPutRequestInformation(global::APITemplate.ApiClient.Models.UpdateUserRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPutRequestInformation(global::APITemplate.ApiClient.Models.UpdateUserRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.PUT, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/problem+json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Users.Item.UsersItemRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Users.Item.UsersItemRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersItemRequestBuilderDeleteRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersItemRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersItemRequestBuilderPutRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Me/MeRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Me/MeRequestBuilder.cs new file mode 100644 index 00000000..a96502f3 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/Me/MeRequestBuilder.cs @@ -0,0 +1,105 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Users.Me +{ + /// + /// Builds and executes requests for operations under \api\v1\users\me + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class MeRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public MeRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/me", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public MeRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users/me", rawUrl) + { + } + /// + /// Get current user profile + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.UserResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get current user profile + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Users.Me.MeRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Users.Me.MeRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class MeRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/UsersRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/UsersRequestBuilder.cs new file mode 100644 index 00000000..ec817d27 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/Users/UsersRequestBuilder.cs @@ -0,0 +1,200 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1.Users.Item; +using APITemplate.ApiClient.Api.V1.Users.Me; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Api.V1.Users +{ + /// + /// Builds and executes requests for operations under \api\v1\users + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersRequestBuilder : BaseRequestBuilder + { + /// The me property + public global::APITemplate.ApiClient.Api.V1.Users.Me.MeRequestBuilder Me + { + get => new global::APITemplate.ApiClient.Api.V1.Users.Me.MeRequestBuilder(PathParameters, RequestAdapter); + } + /// Gets an item from the APITemplate.ApiClient.api.v1.users.item collection + /// Unique identifier of the item + /// A + public global::APITemplate.ApiClient.Api.V1.Users.Item.UsersItemRequestBuilder this[Guid position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + urlTplParams.Add("id", position); + return new global::APITemplate.ApiClient.Api.V1.Users.Item.UsersItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// Gets an item from the APITemplate.ApiClient.api.v1.users.item collection + /// Unique identifier of the item + /// A + [Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")] + public global::APITemplate.ApiClient.Api.V1.Users.Item.UsersItemRequestBuilder this[string position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("id", position); + return new global::APITemplate.ApiClient.Api.V1.Users.Item.UsersItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public UsersRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public UsersRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1/users", rawUrl) + { + } + /// + /// Get users + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.PagedUserResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Create user + /// + /// A + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::APITemplate.ApiClient.Models.CreateUserRequest body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::APITemplate.ApiClient.Models.CreateUserRequest body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Models.UserResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get users + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Create user + /// + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.CreateUserRequest body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::APITemplate.ApiClient.Models.CreateUserRequest body, Action> requestConfiguration = default) + { +#endif + _ = body ?? throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Api.V1.Users.UsersRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Api.V1.Users.UsersRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class UsersRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Api/V1/V1RequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/V1RequestBuilder.cs new file mode 100644 index 00000000..d43a5fd9 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Api/V1/V1RequestBuilder.cs @@ -0,0 +1,59 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api.V1.Jobs; +using APITemplate.ApiClient.Api.V1.Products; +using APITemplate.ApiClient.Api.V1.TenantInvitations; +using APITemplate.ApiClient.Api.V1.Users; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System; +namespace APITemplate.ApiClient.Api.V1 +{ + /// + /// Builds and executes requests for operations under \api\v1 + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class V1RequestBuilder : BaseRequestBuilder + { + /// The jobs property + public global::APITemplate.ApiClient.Api.V1.Jobs.JobsRequestBuilder Jobs + { + get => new global::APITemplate.ApiClient.Api.V1.Jobs.JobsRequestBuilder(PathParameters, RequestAdapter); + } + /// The products property + public global::APITemplate.ApiClient.Api.V1.Products.ProductsRequestBuilder Products + { + get => new global::APITemplate.ApiClient.Api.V1.Products.ProductsRequestBuilder(PathParameters, RequestAdapter); + } + /// The tenantInvitations property + public global::APITemplate.ApiClient.Api.V1.TenantInvitations.TenantInvitationsRequestBuilder TenantInvitations + { + get => new global::APITemplate.ApiClient.Api.V1.TenantInvitations.TenantInvitationsRequestBuilder(PathParameters, RequestAdapter); + } + /// The users property + public global::APITemplate.ApiClient.Api.V1.Users.UsersRequestBuilder Users + { + get => new global::APITemplate.ApiClient.Api.V1.Users.UsersRequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public V1RequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public V1RequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/v1", rawUrl) + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/ApiClient.cs b/src/Clients/APITemplate.ApiClient/Generated/ApiClient.cs new file mode 100644 index 00000000..c5148060 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/ApiClient.cs @@ -0,0 +1,142 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Api; +using APITemplate.ApiClient.Health; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Serialization.Form; +using Microsoft.Kiota.Serialization.Json; +using Microsoft.Kiota.Serialization.Multipart; +using Microsoft.Kiota.Serialization.Text; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient +{ + /// + /// The main entry point of the SDK, exposes the configuration and the fluent API. + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ApiClient : BaseRequestBuilder + { + /// The api property + public global::APITemplate.ApiClient.Api.ApiRequestBuilder Api + { + get => new global::APITemplate.ApiClient.Api.ApiRequestBuilder(PathParameters, RequestAdapter); + } + /// The health property + public global::APITemplate.ApiClient.Health.HealthRequestBuilder Health + { + get => new global::APITemplate.ApiClient.Health.HealthRequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// The request adapter to use to execute the requests. + public ApiClient(IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}", new Dictionary()) + { + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultDeserializer(); + ApiClientBuilder.RegisterDefaultDeserializer(); + ApiClientBuilder.RegisterDefaultDeserializer(); + } + /// + /// Get host status + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsGetResponseAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsGetResponseAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.GetResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get host status + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code + [Obsolete("This method is obsolete. Use GetAsGetResponseAsync instead.")] +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Response.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get host status + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ApiClientGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/GetResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/GetResponse.cs new file mode 100644 index 00000000..6bff2685 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/GetResponse.cs @@ -0,0 +1,75 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class GetResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The service property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Service { get; set; } +#nullable restore +#else + public string Service { get; set; } +#endif + /// The status property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Status { get; set; } +#nullable restore +#else + public string Status { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public GetResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.GetResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.GetResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "service", n => { Service = n.GetStringValue(); } }, + { "status", n => { Status = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("service", Service); + writer.WriteStringValue("status", Status); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/HealthGetResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/HealthGetResponse.cs new file mode 100644 index 00000000..53378eb0 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/HealthGetResponse.cs @@ -0,0 +1,55 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Health +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class HealthGetResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public HealthGetResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Health.HealthGetResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Health.HealthGetResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/HealthRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/HealthRequestBuilder.cs new file mode 100644 index 00000000..13ccf0d6 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/HealthRequestBuilder.cs @@ -0,0 +1,149 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Health.Live; +using APITemplate.ApiClient.Health.Ready; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Health +{ + /// + /// Builds and executes requests for operations under \health + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class HealthRequestBuilder : BaseRequestBuilder + { + /// The live property + public global::APITemplate.ApiClient.Health.Live.LiveRequestBuilder Live + { + get => new global::APITemplate.ApiClient.Health.Live.LiveRequestBuilder(PathParameters, RequestAdapter); + } + /// The ready property + public global::APITemplate.ApiClient.Health.Ready.ReadyRequestBuilder Ready + { + get => new global::APITemplate.ApiClient.Health.Ready.ReadyRequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public HealthRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/health", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public HealthRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/health", rawUrl) + { + } + /// + /// Get health + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsHealthGetResponseAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsHealthGetResponseAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Health.HealthGetResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get health + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code + [Obsolete("This method is obsolete. Use GetAsHealthGetResponseAsync instead.")] +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Health.HealthResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get health + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Health.HealthRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Health.HealthRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class HealthRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/HealthResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/HealthResponse.cs new file mode 100644 index 00000000..987aebb9 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/HealthResponse.cs @@ -0,0 +1,28 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Health +{ + [Obsolete("This class is obsolete. Use HealthGetResponse instead.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class HealthResponse : global::APITemplate.ApiClient.Health.HealthGetResponse, IParsable + #pragma warning restore CS1591 + { + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static new global::APITemplate.ApiClient.Health.HealthResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Health.HealthResponse(); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveGetResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveGetResponse.cs new file mode 100644 index 00000000..a9dce4c0 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveGetResponse.cs @@ -0,0 +1,55 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Health.Live +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class LiveGetResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public LiveGetResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Health.Live.LiveGetResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Health.Live.LiveGetResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveRequestBuilder.cs new file mode 100644 index 00000000..e120dc9f --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveRequestBuilder.cs @@ -0,0 +1,137 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Health.Live +{ + /// + /// Builds and executes requests for operations under \health\live + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class LiveRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public LiveRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/health/live", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public LiveRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/health/live", rawUrl) + { + } + /// + /// Get liveness + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsLiveGetResponseAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsLiveGetResponseAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Health.Live.LiveGetResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get liveness + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code + [Obsolete("This method is obsolete. Use GetAsLiveGetResponseAsync instead.")] +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Health.Live.LiveResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get liveness + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Health.Live.LiveRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Health.Live.LiveRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class LiveRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveResponse.cs new file mode 100644 index 00000000..6d13b71a --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/Live/LiveResponse.cs @@ -0,0 +1,28 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Health.Live +{ + [Obsolete("This class is obsolete. Use LiveGetResponse instead.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class LiveResponse : global::APITemplate.ApiClient.Health.Live.LiveGetResponse, IParsable + #pragma warning restore CS1591 + { + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static new global::APITemplate.ApiClient.Health.Live.LiveResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Health.Live.LiveResponse(); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyGetResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyGetResponse.cs new file mode 100644 index 00000000..ec13e3f4 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyGetResponse.cs @@ -0,0 +1,55 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Health.Ready +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class ReadyGetResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public ReadyGetResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Health.Ready.ReadyGetResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Health.Ready.ReadyGetResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyRequestBuilder.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyRequestBuilder.cs new file mode 100644 index 00000000..31e7c237 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyRequestBuilder.cs @@ -0,0 +1,137 @@ +// +#pragma warning disable CS0618 +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace APITemplate.ApiClient.Health.Ready +{ + /// + /// Builds and executes requests for operations under \health\ready + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ReadyRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ReadyRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/health/ready", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ReadyRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/health/ready", rawUrl) + { + } + /// + /// Get readiness + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsReadyGetResponseAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsReadyGetResponseAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Health.Ready.ReadyGetResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get readiness + /// + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. + /// When receiving a 400 status code + /// When receiving a 401 status code + /// When receiving a 403 status code + /// When receiving a 404 status code + /// When receiving a 500 status code + [Obsolete("This method is obsolete. Use GetAsReadyGetResponseAsync instead.")] +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var errorMapping = new Dictionary> + { + { "400", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "401", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "403", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "404", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + { "500", global::APITemplate.ApiClient.Models.ApiProblemDetails.CreateFromDiscriminatorValue }, + }; + return await RequestAdapter.SendAsync(requestInfo, global::APITemplate.ApiClient.Health.Ready.ReadyResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false); + } + /// + /// Get readiness + /// + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::APITemplate.ApiClient.Health.Ready.ReadyRequestBuilder WithUrl(string rawUrl) + { + return new global::APITemplate.ApiClient.Health.Ready.ReadyRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ReadyRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyResponse.cs new file mode 100644 index 00000000..67e584a6 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Health/Ready/ReadyResponse.cs @@ -0,0 +1,28 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Health.Ready +{ + [Obsolete("This class is obsolete. Use ReadyGetResponse instead.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class ReadyResponse : global::APITemplate.ApiClient.Health.Ready.ReadyGetResponse, IParsable + #pragma warning restore CS1591 + { + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static new global::APITemplate.ApiClient.Health.Ready.ReadyResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Health.Ready.ReadyResponse(); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/AcceptInvitationRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/AcceptInvitationRequest.cs new file mode 100644 index 00000000..394f1f19 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/AcceptInvitationRequest.cs @@ -0,0 +1,65 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class AcceptInvitationRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The token property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Token { get; set; } +#nullable restore +#else + public string Token { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public AcceptInvitationRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.AcceptInvitationRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.AcceptInvitationRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "token", n => { Token = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("token", Token); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/ApiProblemDetails.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/ApiProblemDetails.cs new file mode 100644 index 00000000..7b4120ab --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/ApiProblemDetails.cs @@ -0,0 +1,122 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class ApiProblemDetails : ApiException, IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The detail property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Detail { get; set; } +#nullable restore +#else + public string Detail { get; set; } +#endif + /// The errorCode property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? ErrorCode { get; set; } +#nullable restore +#else + public string ErrorCode { get; set; } +#endif + /// The instance property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Instance { get; set; } +#nullable restore +#else + public string Instance { get; set; } +#endif + /// The primary error message. + public override string Message { get => base.Message; } + /// The status property + public int? Status { get; set; } + /// The title property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Title { get; set; } +#nullable restore +#else + public string Title { get; set; } +#endif + /// The traceId property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? TraceId { get; set; } +#nullable restore +#else + public string TraceId { get; set; } +#endif + /// The type property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Type { get; set; } +#nullable restore +#else + public string Type { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public ApiProblemDetails() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.ApiProblemDetails CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.ApiProblemDetails(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "detail", n => { Detail = n.GetStringValue(); } }, + { "errorCode", n => { ErrorCode = n.GetStringValue(); } }, + { "instance", n => { Instance = n.GetStringValue(); } }, + { "status", n => { Status = n.GetIntValue(); } }, + { "title", n => { Title = n.GetStringValue(); } }, + { "traceId", n => { TraceId = n.GetStringValue(); } }, + { "type", n => { Type = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("detail", Detail); + writer.WriteStringValue("errorCode", ErrorCode); + writer.WriteStringValue("instance", Instance); + writer.WriteIntValue("status", Status); + writer.WriteStringValue("title", Title); + writer.WriteStringValue("traceId", TraceId); + writer.WriteStringValue("type", Type); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/AssignUserRolesRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/AssignUserRolesRequest.cs new file mode 100644 index 00000000..9cbfe432 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/AssignUserRolesRequest.cs @@ -0,0 +1,65 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class AssignUserRolesRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The roleIds property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? RoleIds { get; set; } +#nullable restore +#else + public List RoleIds { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public AssignUserRolesRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.AssignUserRolesRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.AssignUserRolesRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "roleIds", n => { RoleIds = n.GetCollectionOfPrimitiveValues()?.AsList(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteCollectionOfPrimitiveValues("roleIds", RoleIds); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/CreateProductRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/CreateProductRequest.cs new file mode 100644 index 00000000..52c4e162 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/CreateProductRequest.cs @@ -0,0 +1,89 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class CreateProductRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The categoryIds property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? CategoryIds { get; set; } +#nullable restore +#else + public List CategoryIds { get; set; } +#endif + /// The description property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Description { get; set; } +#nullable restore +#else + public string Description { get; set; } +#endif + /// The name property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Name { get; set; } +#nullable restore +#else + public string Name { get; set; } +#endif + /// The price property + public double? Price { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public CreateProductRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.CreateProductRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.CreateProductRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "categoryIds", n => { CategoryIds = n.GetCollectionOfPrimitiveValues()?.AsList(); } }, + { "description", n => { Description = n.GetStringValue(); } }, + { "name", n => { Name = n.GetStringValue(); } }, + { "price", n => { Price = n.GetDoubleValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteCollectionOfPrimitiveValues("categoryIds", CategoryIds); + writer.WriteStringValue("description", Description); + writer.WriteStringValue("name", Name); + writer.WriteDoubleValue("price", Price); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/CreateTenantInvitationRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/CreateTenantInvitationRequest.cs new file mode 100644 index 00000000..f954b378 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/CreateTenantInvitationRequest.cs @@ -0,0 +1,75 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class CreateTenantInvitationRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The email property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Email { get; set; } +#nullable restore +#else + public string Email { get; set; } +#endif + /// The roleIds property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? RoleIds { get; set; } +#nullable restore +#else + public List RoleIds { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public CreateTenantInvitationRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.CreateTenantInvitationRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.CreateTenantInvitationRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "email", n => { Email = n.GetStringValue(); } }, + { "roleIds", n => { RoleIds = n.GetCollectionOfPrimitiveValues()?.AsList(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("email", Email); + writer.WriteCollectionOfPrimitiveValues("roleIds", RoleIds); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/CreateUserRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/CreateUserRequest.cs new file mode 100644 index 00000000..f80a6989 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/CreateUserRequest.cs @@ -0,0 +1,85 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class CreateUserRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The email property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Email { get; set; } +#nullable restore +#else + public string Email { get; set; } +#endif + /// The roleIds property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? RoleIds { get; set; } +#nullable restore +#else + public List RoleIds { get; set; } +#endif + /// The username property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Username { get; set; } +#nullable restore +#else + public string Username { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public CreateUserRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.CreateUserRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.CreateUserRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "email", n => { Email = n.GetStringValue(); } }, + { "roleIds", n => { RoleIds = n.GetCollectionOfPrimitiveValues()?.AsList(); } }, + { "username", n => { Username = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("email", Email); + writer.WriteCollectionOfPrimitiveValues("roleIds", RoleIds); + writer.WriteStringValue("username", Username); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/InvitationStatus.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/InvitationStatus.cs new file mode 100644 index 00000000..64d0a85c --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/InvitationStatus.cs @@ -0,0 +1,27 @@ +// +using System.Runtime.Serialization; +using System; +namespace APITemplate.ApiClient.Models +{ + /// Represents the lifecycle state of a tenant invitation. + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public enum InvitationStatus + { + [EnumMember(Value = "Pending")] + #pragma warning disable CS1591 + Pending, + #pragma warning restore CS1591 + [EnumMember(Value = "Accepted")] + #pragma warning disable CS1591 + Accepted, + #pragma warning restore CS1591 + [EnumMember(Value = "Expired")] + #pragma warning disable CS1591 + Expired, + #pragma warning restore CS1591 + [EnumMember(Value = "Revoked")] + #pragma warning disable CS1591 + Revoked, + #pragma warning restore CS1591 + } +} diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/JobStatus.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/JobStatus.cs new file mode 100644 index 00000000..715740f9 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/JobStatus.cs @@ -0,0 +1,27 @@ +// +using System.Runtime.Serialization; +using System; +namespace APITemplate.ApiClient.Models +{ + /// Represents the execution state of a background job. + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public enum JobStatus + { + [EnumMember(Value = "Pending")] + #pragma warning disable CS1591 + Pending, + #pragma warning restore CS1591 + [EnumMember(Value = "Processing")] + #pragma warning disable CS1591 + Processing, + #pragma warning restore CS1591 + [EnumMember(Value = "Completed")] + #pragma warning disable CS1591 + Completed, + #pragma warning restore CS1591 + [EnumMember(Value = "Failed")] + #pragma warning disable CS1591 + Failed, + #pragma warning restore CS1591 + } +} diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/JobStatusResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/JobStatusResponse.cs new file mode 100644 index 00000000..0a07d253 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/JobStatusResponse.cs @@ -0,0 +1,129 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class JobStatusResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The callbackUrl property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? CallbackUrl { get; set; } +#nullable restore +#else + public string CallbackUrl { get; set; } +#endif + /// The completedAtUtc property + public DateTimeOffset? CompletedAtUtc { get; set; } + /// The errorMessage property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? ErrorMessage { get; set; } +#nullable restore +#else + public string ErrorMessage { get; set; } +#endif + /// The id property + public Guid? Id { get; set; } + /// The jobType property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? JobType { get; set; } +#nullable restore +#else + public string JobType { get; set; } +#endif + /// The parameters property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Parameters { get; set; } +#nullable restore +#else + public string Parameters { get; set; } +#endif + /// The progressPercent property + public int? ProgressPercent { get; set; } + /// The resultPayload property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? ResultPayload { get; set; } +#nullable restore +#else + public string ResultPayload { get; set; } +#endif + /// The startedAtUtc property + public DateTimeOffset? StartedAtUtc { get; set; } + /// Represents the execution state of a background job. + public global::APITemplate.ApiClient.Models.JobStatus? Status { get; set; } + /// The submittedAtUtc property + public DateTimeOffset? SubmittedAtUtc { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public JobStatusResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.JobStatusResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.JobStatusResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "callbackUrl", n => { CallbackUrl = n.GetStringValue(); } }, + { "completedAtUtc", n => { CompletedAtUtc = n.GetDateTimeOffsetValue(); } }, + { "errorMessage", n => { ErrorMessage = n.GetStringValue(); } }, + { "id", n => { Id = n.GetGuidValue(); } }, + { "jobType", n => { JobType = n.GetStringValue(); } }, + { "parameters", n => { Parameters = n.GetStringValue(); } }, + { "progressPercent", n => { ProgressPercent = n.GetIntValue(); } }, + { "resultPayload", n => { ResultPayload = n.GetStringValue(); } }, + { "startedAtUtc", n => { StartedAtUtc = n.GetDateTimeOffsetValue(); } }, + { "status", n => { Status = n.GetEnumValue(); } }, + { "submittedAtUtc", n => { SubmittedAtUtc = n.GetDateTimeOffsetValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("callbackUrl", CallbackUrl); + writer.WriteDateTimeOffsetValue("completedAtUtc", CompletedAtUtc); + writer.WriteStringValue("errorMessage", ErrorMessage); + writer.WriteGuidValue("id", Id); + writer.WriteStringValue("jobType", JobType); + writer.WriteStringValue("parameters", Parameters); + writer.WriteIntValue("progressPercent", ProgressPercent); + writer.WriteStringValue("resultPayload", ResultPayload); + writer.WriteDateTimeOffsetValue("startedAtUtc", StartedAtUtc); + writer.WriteEnumValue("status", Status); + writer.WriteDateTimeOffsetValue("submittedAtUtc", SubmittedAtUtc); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/PagedTenantInvitationResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/PagedTenantInvitationResponse.cs new file mode 100644 index 00000000..23cfdeb2 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/PagedTenantInvitationResponse.cs @@ -0,0 +1,89 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class PagedTenantInvitationResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The hasNextPage property + public bool? HasNextPage { get; set; } + /// The hasPreviousPage property + public bool? HasPreviousPage { get; set; } + /// The items property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? Items { get; set; } +#nullable restore +#else + public List Items { get; set; } +#endif + /// The pageNumber property + public int? PageNumber { get; set; } + /// The pageSize property + public int? PageSize { get; set; } + /// The totalCount property + public int? TotalCount { get; set; } + /// The totalPages property + public int? TotalPages { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public PagedTenantInvitationResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.PagedTenantInvitationResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.PagedTenantInvitationResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "hasNextPage", n => { HasNextPage = n.GetBoolValue(); } }, + { "hasPreviousPage", n => { HasPreviousPage = n.GetBoolValue(); } }, + { "items", n => { Items = n.GetCollectionOfObjectValues(global::APITemplate.ApiClient.Models.TenantInvitationResponse.CreateFromDiscriminatorValue)?.AsList(); } }, + { "pageNumber", n => { PageNumber = n.GetIntValue(); } }, + { "pageSize", n => { PageSize = n.GetIntValue(); } }, + { "totalCount", n => { TotalCount = n.GetIntValue(); } }, + { "totalPages", n => { TotalPages = n.GetIntValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteBoolValue("hasNextPage", HasNextPage); + writer.WriteBoolValue("hasPreviousPage", HasPreviousPage); + writer.WriteCollectionOfObjectValues("items", Items); + writer.WriteIntValue("pageNumber", PageNumber); + writer.WriteIntValue("pageSize", PageSize); + writer.WriteIntValue("totalCount", TotalCount); + writer.WriteIntValue("totalPages", TotalPages); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/PagedUserResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/PagedUserResponse.cs new file mode 100644 index 00000000..bc218372 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/PagedUserResponse.cs @@ -0,0 +1,89 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class PagedUserResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The hasNextPage property + public bool? HasNextPage { get; set; } + /// The hasPreviousPage property + public bool? HasPreviousPage { get; set; } + /// The items property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? Items { get; set; } +#nullable restore +#else + public List Items { get; set; } +#endif + /// The pageNumber property + public int? PageNumber { get; set; } + /// The pageSize property + public int? PageSize { get; set; } + /// The totalCount property + public int? TotalCount { get; set; } + /// The totalPages property + public int? TotalPages { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public PagedUserResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.PagedUserResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.PagedUserResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "hasNextPage", n => { HasNextPage = n.GetBoolValue(); } }, + { "hasPreviousPage", n => { HasPreviousPage = n.GetBoolValue(); } }, + { "items", n => { Items = n.GetCollectionOfObjectValues(global::APITemplate.ApiClient.Models.UserResponse.CreateFromDiscriminatorValue)?.AsList(); } }, + { "pageNumber", n => { PageNumber = n.GetIntValue(); } }, + { "pageSize", n => { PageSize = n.GetIntValue(); } }, + { "totalCount", n => { TotalCount = n.GetIntValue(); } }, + { "totalPages", n => { TotalPages = n.GetIntValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteBoolValue("hasNextPage", HasNextPage); + writer.WriteBoolValue("hasPreviousPage", HasPreviousPage); + writer.WriteCollectionOfObjectValues("items", Items); + writer.WriteIntValue("pageNumber", PageNumber); + writer.WriteIntValue("pageSize", PageSize); + writer.WriteIntValue("totalCount", TotalCount); + writer.WriteIntValue("totalPages", TotalPages); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/ProductResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/ProductResponse.cs new file mode 100644 index 00000000..027817bd --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/ProductResponse.cs @@ -0,0 +1,97 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class ProductResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The categoryIds property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? CategoryIds { get; set; } +#nullable restore +#else + public List CategoryIds { get; set; } +#endif + /// The createdAtUtc property + public DateTimeOffset? CreatedAtUtc { get; set; } + /// The description property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Description { get; set; } +#nullable restore +#else + public string Description { get; set; } +#endif + /// The id property + public Guid? Id { get; set; } + /// The name property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Name { get; set; } +#nullable restore +#else + public string Name { get; set; } +#endif + /// The price property + public double? Price { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public ProductResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.ProductResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.ProductResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "categoryIds", n => { CategoryIds = n.GetCollectionOfPrimitiveValues()?.AsList(); } }, + { "createdAtUtc", n => { CreatedAtUtc = n.GetDateTimeOffsetValue(); } }, + { "description", n => { Description = n.GetStringValue(); } }, + { "id", n => { Id = n.GetGuidValue(); } }, + { "name", n => { Name = n.GetStringValue(); } }, + { "price", n => { Price = n.GetDoubleValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteCollectionOfPrimitiveValues("categoryIds", CategoryIds); + writer.WriteDateTimeOffsetValue("createdAtUtc", CreatedAtUtc); + writer.WriteStringValue("description", Description); + writer.WriteGuidValue("id", Id); + writer.WriteStringValue("name", Name); + writer.WriteDoubleValue("price", Price); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/ProductsResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/ProductsResponse.cs new file mode 100644 index 00000000..3fa79115 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/ProductsResponse.cs @@ -0,0 +1,77 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class ProductsResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The items property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? Items { get; set; } +#nullable restore +#else + public List Items { get; set; } +#endif + /// The pageNumber property + public int? PageNumber { get; set; } + /// The pageSize property + public int? PageSize { get; set; } + /// The totalCount property + public int? TotalCount { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public ProductsResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.ProductsResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.ProductsResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "items", n => { Items = n.GetCollectionOfObjectValues(global::APITemplate.ApiClient.Models.ProductResponse.CreateFromDiscriminatorValue)?.AsList(); } }, + { "pageNumber", n => { PageNumber = n.GetIntValue(); } }, + { "pageSize", n => { PageSize = n.GetIntValue(); } }, + { "totalCount", n => { TotalCount = n.GetIntValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteCollectionOfObjectValues("items", Items); + writer.WriteIntValue("pageNumber", PageNumber); + writer.WriteIntValue("pageSize", PageSize); + writer.WriteIntValue("totalCount", TotalCount); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/ProvisioningStatus.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/ProvisioningStatus.cs new file mode 100644 index 00000000..df108430 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/ProvisioningStatus.cs @@ -0,0 +1,19 @@ +// +using System.Runtime.Serialization; +using System; +namespace APITemplate.ApiClient.Models +{ + /// Tracks whether user Keycloak account has been provisioned. + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public enum ProvisioningStatus + { + [EnumMember(Value = "Pending")] + #pragma warning disable CS1591 + Pending, + #pragma warning restore CS1591 + [EnumMember(Value = "Completed")] + #pragma warning disable CS1591 + Completed, + #pragma warning restore CS1591 + } +} diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/SubmitJobRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/SubmitJobRequest.cs new file mode 100644 index 00000000..aae6318c --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/SubmitJobRequest.cs @@ -0,0 +1,85 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class SubmitJobRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The callbackUrl property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? CallbackUrl { get; set; } +#nullable restore +#else + public string CallbackUrl { get; set; } +#endif + /// The jobType property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? JobType { get; set; } +#nullable restore +#else + public string JobType { get; set; } +#endif + /// The parameters property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Parameters { get; set; } +#nullable restore +#else + public string Parameters { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public SubmitJobRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.SubmitJobRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.SubmitJobRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "callbackUrl", n => { CallbackUrl = n.GetStringValue(); } }, + { "jobType", n => { JobType = n.GetStringValue(); } }, + { "parameters", n => { Parameters = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("callbackUrl", CallbackUrl); + writer.WriteStringValue("jobType", JobType); + writer.WriteStringValue("parameters", Parameters); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/TenantInvitationResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/TenantInvitationResponse.cs new file mode 100644 index 00000000..827cd03b --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/TenantInvitationResponse.cs @@ -0,0 +1,81 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class TenantInvitationResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The createdAtUtc property + public DateTimeOffset? CreatedAtUtc { get; set; } + /// The email property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Email { get; set; } +#nullable restore +#else + public string Email { get; set; } +#endif + /// The expiresAtUtc property + public DateTimeOffset? ExpiresAtUtc { get; set; } + /// The id property + public Guid? Id { get; set; } + /// Represents the lifecycle state of a tenant invitation. + public global::APITemplate.ApiClient.Models.InvitationStatus? Status { get; set; } + /// + /// Instantiates a new and sets the default values. + /// + public TenantInvitationResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.TenantInvitationResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.TenantInvitationResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "createdAtUtc", n => { CreatedAtUtc = n.GetDateTimeOffsetValue(); } }, + { "email", n => { Email = n.GetStringValue(); } }, + { "expiresAtUtc", n => { ExpiresAtUtc = n.GetDateTimeOffsetValue(); } }, + { "id", n => { Id = n.GetGuidValue(); } }, + { "status", n => { Status = n.GetEnumValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteDateTimeOffsetValue("createdAtUtc", CreatedAtUtc); + writer.WriteStringValue("email", Email); + writer.WriteDateTimeOffsetValue("expiresAtUtc", ExpiresAtUtc); + writer.WriteGuidValue("id", Id); + writer.WriteEnumValue("status", Status); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/UpdateUserRequest.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/UpdateUserRequest.cs new file mode 100644 index 00000000..4712039a --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/UpdateUserRequest.cs @@ -0,0 +1,65 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class UpdateUserRequest : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The email property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Email { get; set; } +#nullable restore +#else + public string Email { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public UpdateUserRequest() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.UpdateUserRequest CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.UpdateUserRequest(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "email", n => { Email = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("email", Email); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Models/UserResponse.cs b/src/Clients/APITemplate.ApiClient/Generated/Models/UserResponse.cs new file mode 100644 index 00000000..ed91b51d --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Models/UserResponse.cs @@ -0,0 +1,101 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class UserResponse : IAdditionalDataHolder, IParsable + #pragma warning restore CS1591 + { + /// Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + public IDictionary AdditionalData { get; set; } + /// The createdAtUtc property + public DateTimeOffset? CreatedAtUtc { get; set; } + /// The email property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Email { get; set; } +#nullable restore +#else + public string Email { get; set; } +#endif + /// The id property + public Guid? Id { get; set; } + /// The isActive property + public bool? IsActive { get; set; } + /// Tracks whether user Keycloak account has been provisioned. + public global::APITemplate.ApiClient.Models.ProvisioningStatus? ProvisioningStatus { get; set; } + /// The roles property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public List? Roles { get; set; } +#nullable restore +#else + public List Roles { get; set; } +#endif + /// The username property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Username { get; set; } +#nullable restore +#else + public string Username { get; set; } +#endif + /// + /// Instantiates a new and sets the default values. + /// + public UserResponse() + { + AdditionalData = new Dictionary(); + } + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::APITemplate.ApiClient.Models.UserResponse CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Models.UserResponse(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "createdAtUtc", n => { CreatedAtUtc = n.GetDateTimeOffsetValue(); } }, + { "email", n => { Email = n.GetStringValue(); } }, + { "id", n => { Id = n.GetGuidValue(); } }, + { "isActive", n => { IsActive = n.GetBoolValue(); } }, + { "provisioningStatus", n => { ProvisioningStatus = n.GetEnumValue(); } }, + { "roles", n => { Roles = n.GetCollectionOfPrimitiveValues()?.AsList(); } }, + { "username", n => { Username = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteDateTimeOffsetValue("createdAtUtc", CreatedAtUtc); + writer.WriteStringValue("email", Email); + writer.WriteGuidValue("id", Id); + writer.WriteBoolValue("isActive", IsActive); + writer.WriteEnumValue("provisioningStatus", ProvisioningStatus); + writer.WriteCollectionOfPrimitiveValues("roles", Roles); + writer.WriteStringValue("username", Username); + writer.WriteAdditionalData(AdditionalData); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/Response.cs b/src/Clients/APITemplate.ApiClient/Generated/Response.cs new file mode 100644 index 00000000..c708c41f --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/Response.cs @@ -0,0 +1,28 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace APITemplate.ApiClient +{ + [Obsolete("This class is obsolete. Use GetResponse instead.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class Response : global::APITemplate.ApiClient.GetResponse, IParsable + #pragma warning restore CS1591 + { + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static new global::APITemplate.ApiClient.Response CreateFromDiscriminatorValue(IParseNode parseNode) + { + _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode)); + return new global::APITemplate.ApiClient.Response(); + } + } +} +#pragma warning restore CS0618 diff --git a/src/Clients/APITemplate.ApiClient/Generated/kiota-lock.json b/src/Clients/APITemplate.ApiClient/Generated/kiota-lock.json new file mode 100644 index 00000000..23b32eb3 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/Generated/kiota-lock.json @@ -0,0 +1,34 @@ +{ + "descriptionHash": "B5CB99D86EE8632B5B646AAC66C6E4D885AD8D8AEE21944693EE68956E2C3D49FCCCA3952F114EB6EC2C5B32BDE746EC668902A4F4173137F4D386BBE1BA9948", + "descriptionLocation": "../openapi.json", + "lockFileVersion": "1.0.0", + "kiotaVersion": "1.23.0", + "clientClassName": "ApiClient", + "typeAccessModifier": "Public", + "clientNamespaceName": "APITemplate.ApiClient", + "language": "CSharp", + "usesBackingStore": false, + "excludeBackwardCompatible": false, + "includeAdditionalData": true, + "disableSSLValidation": false, + "serializers": [ + "Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Text.TextSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Form.FormSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Multipart.MultipartSerializationWriterFactory" + ], + "deserializers": [ + "Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory", + "Microsoft.Kiota.Serialization.Text.TextParseNodeFactory", + "Microsoft.Kiota.Serialization.Form.FormParseNodeFactory" + ], + "structuredMimeTypes": [ + "application/json", + "text/plain;q=0.9", + "application/x-www-form-urlencoded;q=0.2", + "multipart/form-data;q=0.1" + ], + "includePatterns": [], + "excludePatterns": [], + "disabledValidationRules": [] +} \ No newline at end of file diff --git a/src/Clients/APITemplate.ApiClient/openapi.json b/src/Clients/APITemplate.ApiClient/openapi.json new file mode 100644 index 00000000..303fb684 --- /dev/null +++ b/src/Clients/APITemplate.ApiClient/openapi.json @@ -0,0 +1,2102 @@ +{ + "paths": { + "/api/v1/tenant-invitations": { + "post": { + "summary": "Create tenant invitation", + "tags": [ + "TenantInvitations" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTenantInvitationRequest" + } + } + }, + "required": true + }, + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TenantInvitationResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "TenantInvitations_post_api_v1_tenant-invitations" + }, + "get": { + "summary": "Get tenant invitations", + "tags": [ + "TenantInvitations" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedTenantInvitationResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "TenantInvitations_get_api_v1_tenant-invitations" + } + }, + "/api/v1/users/{id}/deactivate": { + "patch": { + "summary": "Deactivate user", + "tags": [ + "Users" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "Success" + }, + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_patch_api_v1_users_id_deactivate" + } + }, + "/": { + "get": { + "summary": "Get host status", + "tags": [ + "Host" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "status": { + "type": "string" + }, + "service": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Host_get_" + } + }, + "/api/v1/jobs": { + "post": { + "summary": "Submit background job", + "tags": [ + "Jobs" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitJobRequest" + } + } + }, + "required": true + }, + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatusResponse" + } + } + }, + "description": "Success" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Jobs_post_api_v1_jobs" + } + }, + "/health": { + "get": { + "summary": "Get health", + "tags": [ + "Health" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Health_get_health" + } + }, + "/api/v1/users/{id}/activate": { + "patch": { + "summary": "Activate user", + "tags": [ + "Users" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "Success" + }, + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_patch_api_v1_users_id_activate" + } + }, + "/api/v1/users/{id}/roles": { + "post": { + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "summary": "Assign user roles", + "responses": { + "204": { + "description": "Success" + }, + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "tags": [ + "Users" + ], + "operationId": "Users_post_api_v1_users_id_roles", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssignUserRolesRequest" + } + } + }, + "required": true + } + } + }, + "/api/v1/products/{id}": { + "get": { + "summary": "Get product by id", + "tags": [ + "Products" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Products_get_api_v1_products_id" + } + }, + "/api/v1/users/{id}": { + "get": { + "summary": "Get user by id", + "tags": [ + "Users" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_get_api_v1_users_id" + }, + "put": { + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "summary": "Update user", + "responses": { + "204": { + "description": "Success" + }, + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "tags": [ + "Users" + ], + "operationId": "Users_put_api_v1_users_id", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserRequest" + } + } + }, + "required": true + } + }, + "delete": { + "summary": "Delete user", + "tags": [ + "Users" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "Success" + }, + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_delete_api_v1_users_id" + } + }, + "/api/v1/products": { + "post": { + "summary": "Create product", + "tags": [ + "Products" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProductRequest" + } + } + }, + "required": true + }, + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Products_post_api_v1_products" + }, + "get": { + "summary": "Get products", + "tags": [ + "Products" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductsResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Products_get_api_v1_products" + } + }, + "/api/v1/jobs/{id}": { + "get": { + "summary": "Get background job status", + "tags": [ + "Jobs" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "uuid" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatusResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Jobs_get_api_v1_jobs_id" + } + }, + "/api/v1/tenant-invitations/accept": { + "post": { + "summary": "Accept tenant invitation", + "tags": [ + "TenantInvitations" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcceptInvitationRequest" + } + } + }, + "required": true + }, + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "TenantInvitations_post_api_v1_tenant-invitations_accept" + } + }, + "/api/v1/users/me": { + "get": { + "summary": "Get current user profile", + "tags": [ + "Users" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_get_api_v1_users_me" + } + }, + "/health/live": { + "get": { + "summary": "Get liveness", + "tags": [ + "Health" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Health_get_health_live" + } + }, + "/health/ready": { + "get": { + "summary": "Get readiness", + "tags": [ + "Health" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Health_get_health_ready" + } + }, + "/api/v1/users": { + "post": { + "summary": "Create user", + "tags": [ + "Users" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserRequest" + } + } + }, + "required": true + }, + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_post_api_v1_users" + }, + "get": { + "summary": "Get users", + "tags": [ + "Users" + ], + "responses": { + "400": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Unauthorized" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedUserResponse" + } + } + }, + "description": "Success" + }, + "404": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Not Found" + }, + "500": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Server Error" + }, + "403": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ApiProblemDetails" + } + } + }, + "description": "Forbidden" + } + }, + "operationId": "Users_get_api_v1_users" + } + } + }, + "components": { + "securitySchemes": { + "Bearer": { + "scheme": "bearer", + "type": "http", + "bearerFormat": "JWT" + } + }, + "schemas": { + "UpdateUserRequest": { + "properties": { + "email": { + "type": "string", + "format": "email" + } + }, + "type": "object", + "required": [ + "email" + ] + }, + "UserResponse": { + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "isActive": { + "type": "boolean" + }, + "provisioningStatus": { + "$ref": "#/components/schemas/ProvisioningStatus" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "createdAtUtc": { + "type": "string", + "format": "date-time" + }, + "roles": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "username", + "email", + "isActive", + "roles", + "provisioningStatus", + "createdAtUtc" + ] + }, + "TenantInvitationResponse": { + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "status": { + "$ref": "#/components/schemas/InvitationStatus" + }, + "createdAtUtc": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "expiresAtUtc": { + "type": "string", + "format": "date-time" + } + }, + "type": "object", + "required": [ + "id", + "email", + "status", + "expiresAtUtc", + "createdAtUtc" + ] + }, + "ApiProblemDetails": { + "properties": { + "detail": { + "type": "string" + }, + "instance": { + "type": "string" + }, + "traceId": { + "type": "string" + }, + "type": { + "type": "string" + }, + "errorCode": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int32" + } + }, + "type": "object", + "required": [ + "type", + "title", + "status", + "traceId", + "errorCode" + ] + }, + "JobStatusResponse": { + "properties": { + "parameters": { + "nullable": true, + "type": "string" + }, + "submittedAtUtc": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "resultPayload": { + "nullable": true, + "type": "string" + }, + "completedAtUtc": { + "format": "date-time", + "type": "string", + "nullable": true + }, + "jobType": { + "type": "string" + }, + "startedAtUtc": { + "format": "date-time", + "type": "string", + "nullable": true + }, + "errorMessage": { + "nullable": true, + "type": "string" + }, + "progressPercent": { + "type": "integer", + "format": "int32" + }, + "callbackUrl": { + "nullable": true, + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/JobStatus" + } + }, + "type": "object", + "required": [ + "id", + "jobType", + "status", + "progressPercent", + "submittedAtUtc" + ] + }, + "InvitationStatus": { + "enum": [ + "Pending", + "Accepted", + "Expired", + "Revoked" + ], + "description": "Represents the lifecycle state of a tenant invitation.", + "type": "string" + }, + "CreateUserRequest": { + "properties": { + "username": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "roleIds": { + "items": { + "type": "string", + "format": "uuid" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "username", + "email" + ] + }, + "PagedTenantInvitationResponse": { + "properties": { + "totalPages": { + "type": "integer", + "format": "int32" + }, + "pageNumber": { + "type": "integer", + "format": "int32" + }, + "hasNextPage": { + "type": "boolean" + }, + "hasPreviousPage": { + "type": "boolean" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "items": { + "items": { + "$ref": "#/components/schemas/TenantInvitationResponse" + }, + "type": "array" + }, + "totalCount": { + "type": "integer", + "format": "int32" + } + }, + "type": "object", + "required": [ + "items", + "pageNumber", + "pageSize", + "totalCount", + "totalPages", + "hasNextPage", + "hasPreviousPage" + ] + }, + "ProvisioningStatus": { + "enum": [ + "Pending", + "Completed" + ], + "description": "Tracks whether user Keycloak account has been provisioned.", + "type": "string" + }, + "JobStatus": { + "enum": [ + "Pending", + "Processing", + "Completed", + "Failed" + ], + "description": "Represents the execution state of a background job.", + "type": "string" + }, + "PagedUserResponse": { + "properties": { + "totalPages": { + "type": "integer", + "format": "int32" + }, + "pageNumber": { + "type": "integer", + "format": "int32" + }, + "hasNextPage": { + "type": "boolean" + }, + "hasPreviousPage": { + "type": "boolean" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "items": { + "items": { + "$ref": "#/components/schemas/UserResponse" + }, + "type": "array" + }, + "totalCount": { + "type": "integer", + "format": "int32" + } + }, + "type": "object", + "required": [ + "items", + "pageNumber", + "pageSize", + "totalCount", + "totalPages", + "hasNextPage", + "hasPreviousPage" + ] + }, + "AssignUserRolesRequest": { + "properties": { + "roleIds": { + "items": { + "type": "string", + "format": "uuid" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "roleIds" + ] + }, + "FileUploadStatus": { + "enum": [ + "Staged", + "Committed", + "Failed" + ], + "description": "Lifecycle of a two-phase file upload.", + "type": "string" + }, + "SubmitJobRequest": { + "properties": { + "jobType": { + "type": "string" + }, + "callbackUrl": { + "nullable": true, + "type": "string" + }, + "parameters": { + "nullable": true, + "type": "string" + } + }, + "type": "object", + "required": [ + "jobType" + ] + }, + "CreateProductRequest": { + "properties": { + "price": { + "type": "number", + "format": "double" + }, + "categoryIds": { + "items": { + "type": "string", + "format": "uuid" + }, + "type": "array" + }, + "description": { + "nullable": true, + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "price" + ] + }, + "ProductsResponse": { + "properties": { + "pageNumber": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "items": { + "items": { + "$ref": "#/components/schemas/ProductResponse" + }, + "type": "array" + }, + "totalCount": { + "type": "integer", + "format": "int32" + } + }, + "type": "object", + "required": [ + "items", + "pageNumber", + "pageSize", + "totalCount" + ] + }, + "CreateTenantInvitationRequest": { + "properties": { + "roleIds": { + "items": { + "type": "string", + "format": "uuid" + }, + "type": "array" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "type": "object", + "required": [ + "email" + ] + }, + "ProductResponse": { + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "description": { + "nullable": true, + "type": "string" + }, + "categoryIds": { + "items": { + "type": "string", + "format": "uuid" + }, + "type": "array" + }, + "price": { + "type": "number", + "format": "double" + }, + "createdAtUtc": { + "type": "string", + "format": "date-time" + } + }, + "type": "object", + "required": [ + "id", + "name", + "price", + "categoryIds", + "createdAtUtc" + ] + }, + "AcceptInvitationRequest": { + "properties": { + "token": { + "type": "string" + } + }, + "type": "object", + "required": [ + "token" + ] + } + } + }, + "openapi": "3.0.1", + "info": { + "title": "APITemplate API", + "description": "APITemplate Monolith REST API", + "version": "v1" + } +} \ No newline at end of file diff --git a/tests/APITemplate.Tests/APITemplate.Tests.csproj b/tests/APITemplate.Tests/APITemplate.Tests.csproj index ffd3beab..2fde4307 100644 --- a/tests/APITemplate.Tests/APITemplate.Tests.csproj +++ b/tests/APITemplate.Tests/APITemplate.Tests.csproj @@ -27,6 +27,8 @@ + + @@ -114,6 +116,7 @@ + diff --git a/tests/APITemplate.Tests/Unit/Clients/ApiClientEnumSerializationTests.cs b/tests/APITemplate.Tests/Unit/Clients/ApiClientEnumSerializationTests.cs new file mode 100644 index 00000000..a8f39293 --- /dev/null +++ b/tests/APITemplate.Tests/Unit/Clients/ApiClientEnumSerializationTests.cs @@ -0,0 +1,158 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using APITemplate.ApiClient; +using APITemplate.ApiClient.Models; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Serialization.Json; +using Moq; +using Shouldly; +using Xunit; + +namespace APITemplate.Tests.Unit.Clients; + +[Trait("Category", "Unit")] +public sealed class ApiClientEnumSerializationTests +{ + public ApiClientEnumSerializationTests() + { + // Ensure Kiota JSON serialization/deserialization handlers are registered + ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[ + "application/json" + ] = new JsonParseNodeFactory(); + SerializationWriterFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[ + "application/json" + ] = new JsonSerializationWriterFactory(); + } + + [Fact] + public async Task InvitationStatus_SerializesToString_AndDeserializesBack() + { + // Arrange + var invitation = new TenantInvitationResponse + { + Id = Guid.NewGuid(), + Email = "invitee@example.com", + Status = InvitationStatus.Accepted, + CreatedAtUtc = DateTimeOffset.UtcNow, + ExpiresAtUtc = DateTimeOffset.UtcNow.AddDays(7), + }; + + var writer = new JsonSerializationWriter(); + writer.WriteObjectValue(null, invitation); + + using var stream = writer.GetSerializedContent(); + using var reader = new StreamReader(stream, Encoding.UTF8); + string json = reader.ReadToEnd(); + + // Assert serialization outputs string enum, not int + json.ShouldContain("\"status\":\"Accepted\""); + json.ShouldNotContain("\"status\":1"); + + // Act - Deserialization + byte[] bytes = Encoding.UTF8.GetBytes(json); + using var readStream = new MemoryStream(bytes); + IParseNode parseNode = await new JsonParseNodeFactory().GetRootParseNodeAsync( + "application/json", + readStream, + TestContext.Current.CancellationToken + ); + var result = parseNode.GetObjectValue( + TenantInvitationResponse.CreateFromDiscriminatorValue + ); + + // Assert + result.ShouldNotBeNull(); + result.Status.ShouldBe(InvitationStatus.Accepted); + result.Email.ShouldBe("invitee@example.com"); + } + + [Fact] + public async Task JobStatus_SerializesToString_AndDeserializesBack() + { + // Arrange + var job = new JobStatusResponse + { + Id = Guid.NewGuid(), + JobType = "EmailDigest", + Status = JobStatus.Processing, + ProgressPercent = 50, + SubmittedAtUtc = DateTimeOffset.UtcNow, + }; + + var writer = new JsonSerializationWriter(); + writer.WriteObjectValue(null, job); + + using var stream = writer.GetSerializedContent(); + using var reader = new StreamReader(stream, Encoding.UTF8); + string json = reader.ReadToEnd(); + + // Assert serialization outputs string enum + json.ShouldContain("\"status\":\"Processing\""); + json.ShouldNotContain("\"status\":1"); + + // Act - Deserialization + byte[] bytes = Encoding.UTF8.GetBytes(json); + using var readStream = new MemoryStream(bytes); + IParseNode parseNode = await new JsonParseNodeFactory().GetRootParseNodeAsync( + "application/json", + readStream, + TestContext.Current.CancellationToken + ); + var result = parseNode.GetObjectValue(JobStatusResponse.CreateFromDiscriminatorValue); + + // Assert + result.ShouldNotBeNull(); + result.Status.ShouldBe(JobStatus.Processing); + result.JobType.ShouldBe("EmailDigest"); + result.ProgressPercent.ShouldBe(50); + } + + [Fact] + public async Task ApiClient_CallsTenantInvitations_ReturnsTypedResponse() + { + // Arrange + var expectedInvitation = new TenantInvitationResponse + { + Id = Guid.NewGuid(), + Email = "test@company.com", + Status = InvitationStatus.Pending, + CreatedAtUtc = DateTimeOffset.UtcNow, + ExpiresAtUtc = DateTimeOffset.UtcNow.AddDays(7), + }; + + var mockAdapter = new Mock(); + mockAdapter + .SetupGet(a => a.SerializationWriterFactory) + .Returns(SerializationWriterFactoryRegistry.DefaultInstance); + mockAdapter + .Setup(a => + a.SendAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny>>(), + It.IsAny() + ) + ) + .ReturnsAsync(expectedInvitation); + + mockAdapter.SetupProperty(a => a.BaseUrl, "https://api.example.com"); + + global::APITemplate.ApiClient.ApiClient client = new(mockAdapter.Object); + + // Act + var request = new CreateTenantInvitationRequest { Email = "test@company.com" }; + var result = await client.Api.V1.TenantInvitations.PostAsync( + request, + cancellationToken: TestContext.Current.CancellationToken + ); + + // Assert + result.ShouldNotBeNull(); + result.Status.ShouldBe(InvitationStatus.Pending); + result.Email.ShouldBe("test@company.com"); + } +} diff --git a/tests/APITemplate.Tests/Unit/Identity/Mocks/RedisConnectionMultiplexerMockBuilder.cs b/tests/APITemplate.Tests/Unit/Identity/Mocks/RedisConnectionMultiplexerMockBuilder.cs index 1c4c977e..440e6df3 100644 --- a/tests/APITemplate.Tests/Unit/Identity/Mocks/RedisConnectionMultiplexerMockBuilder.cs +++ b/tests/APITemplate.Tests/Unit/Identity/Mocks/RedisConnectionMultiplexerMockBuilder.cs @@ -143,7 +143,13 @@ private static Exception CreateException() where TException : Exception { if (typeof(TException) == typeof(RedisConnectionException)) - return new RedisConnectionException(ConnectionFailureType.UnableToConnect, "boom"); + return new RedisConnectionException( + ConnectionFailureType.UnableToConnect, + CommandFlags.None, + "boom", + null!, + CommandStatus.Unknown + ); if (typeof(TException) == typeof(ObjectDisposedException)) return new ObjectDisposedException("redis"); From ede4567a6a5b98937d3770a1c9057f455d15beaa Mon Sep 17 00:00:00 2001 From: zribktad Date: Tue, 8 Sep 2026 18:33:48 +0200 Subject: [PATCH 5/7] feat(aspire): add Aspire AppHost orchestrator for local development --- APITemplate.slnx | 4 + .../APITemplate.AppHost.csproj | 37 ++++++ src/APITemplate.AppHost/Program.cs | 117 ++++++++++++++++++ .../Properties/launchSettings.json | 17 +++ .../appsettings.Development.json5 | 9 ++ src/APITemplate.AppHost/appsettings.json5 | 9 ++ .../Api/appsettings.Development.json5 | 19 ++- 7 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/APITemplate.AppHost/APITemplate.AppHost.csproj create mode 100644 src/APITemplate.AppHost/Program.cs create mode 100644 src/APITemplate.AppHost/Properties/launchSettings.json create mode 100644 src/APITemplate.AppHost/appsettings.Development.json5 create mode 100644 src/APITemplate.AppHost/appsettings.json5 diff --git a/APITemplate.slnx b/APITemplate.slnx index de21fab9..28abff2e 100644 --- a/APITemplate.slnx +++ b/APITemplate.slnx @@ -1,9 +1,13 @@ + + + + diff --git a/src/APITemplate.AppHost/APITemplate.AppHost.csproj b/src/APITemplate.AppHost/APITemplate.AppHost.csproj new file mode 100644 index 00000000..197e5384 --- /dev/null +++ b/src/APITemplate.AppHost/APITemplate.AppHost.csproj @@ -0,0 +1,37 @@ + + + + Exe + net10.0 + enable + enable + false + apitemplate-apphost-dev-secrets + $(NoWarn);ASPIRECERTIFICATES001;ASPIRE010 + + + + + + + + + + + + + + + + + + PreserveNewest + PreserveNewest + + + PreserveNewest + PreserveNewest + + + + diff --git a/src/APITemplate.AppHost/Program.cs b/src/APITemplate.AppHost/Program.cs new file mode 100644 index 00000000..79c49120 --- /dev/null +++ b/src/APITemplate.AppHost/Program.cs @@ -0,0 +1,117 @@ +using JasperFx.Aspire; +using Projects; + +IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); + +// ── PostgreSQL ───────────────────────────────────────────────────────────── +IResourceBuilder postgresPassword = builder.AddParameter( + "postgres-password", + "postgres" +); +IResourceBuilder postgres = builder + .AddPostgres("postgres", password: postgresPassword) + .WithImageTag("18.3") + .WithDataVolume("apitemplate-postgres-data") + .WithHostPort(5432) + .WithBindMount( + "../../infrastructure/postgres/init-keycloak-db.sql", + "/docker-entrypoint-initdb.d/init-keycloak-db.sql" + ); + +IResourceBuilder apitemplateDb = postgres.AddDatabase("apitemplate"); + +// ── Dragonfly (Redis-compatible) ─────────────────────────────────────────── +IResourceBuilder dragonfly = builder + .AddContainer("dragonfly", "docker.dragonflydb.io/dragonflydb/dragonfly", "v1.27.1") + .WithArgs("dragonfly", "--maxmemory", "512mb", "--proactor_threads", "2", "--cache_mode=true") + .WithHttpEndpoint(targetPort: 6379, port: 6379, name: "redis"); + +// ── MongoDB ──────────────────────────────────────────────────────────────── +IResourceBuilder mongodb = builder + .AddMongoDB("mongodb") + .WithImageTag("8.2") + .WithDataVolume("apitemplate-mongo-data") + .WithEndpoint(targetPort: 27017, port: 27017, name: "tcp"); + +IResourceBuilder mongoDb = mongodb.AddDatabase("apitemplate"); + +// ── Keycloak ─────────────────────────────────────────────────────────────── +IResourceBuilder keycloak = builder + .AddContainer("keycloak", "quay.io/keycloak/keycloak", "26.5") + .WithArgs("start-dev", "--import-realm") + .WithEnvironment("KC_DB", "postgres") + .WithEnvironment("KC_DB_URL", "jdbc:postgresql://postgres:5432/keycloak") + .WithEnvironment("KC_DB_USERNAME", "postgres") + .WithEnvironment("KC_DB_PASSWORD", "postgres") + .WithEnvironment("KC_HTTP_PORT", "8180") + .WithEnvironment("KC_BOOTSTRAP_ADMIN_USERNAME", "admin") + .WithEnvironment("KC_BOOTSTRAP_ADMIN_PASSWORD", "admin") + .WithHttpEndpoint(targetPort: 8180, port: 8180, name: "http") + .WithUrlForEndpoint( + "http", + _ => new() { Url = "http://localhost:8180/admin", DisplayText = "Keycloak Admin Console" } + ) + .WithUrlForEndpoint( + "http", + _ => + new() + { + Url = "http://localhost:8180/realms/api-template/.well-known/openid-configuration", + DisplayText = "OIDC Discovery", + } + ) + .WithBindMount("../../infrastructure/keycloak/realms", "/opt/keycloak/data/import") + .WaitFor(postgres); + +// ── Mailpit (SMTP & Web UI) ──────────────────────────────────────────────── +IResourceBuilder mailpit = builder + .AddContainer("mailpit", "axllent/mailpit", "v1.29.0") + .WithHttpEndpoint(targetPort: 8025, port: 8025, name: "ui") + .WithEndpoint(targetPort: 1025, port: 1025, name: "smtp") + .WithUrlForEndpoint( + "ui", + _ => new() { Url = "http://localhost:8025", DisplayText = "Mailpit Mailbox" } + ); + +// ── APITemplate API Monolith ─────────────────────────────────────────────── +IResourceBuilder api = builder + .AddProject("api") + .WithReference(apitemplateDb) + .WithReference(mongoDb) + .WaitFor(postgres) + .WaitFor(mongodb) + .WaitFor(dragonfly) + .WaitFor(keycloak) + .WaitFor(mailpit) + .WithHttpHealthCheck("/health/live") + .WithUrlForEndpoint( + "http", + _ => new() { Url = "http://localhost:5174/scalar", DisplayText = "Scalar API Docs" } + ) + .WithUrlForEndpoint( + "http", + _ => + new() { Url = "http://localhost:5174/graphql", DisplayText = "GraphQL Banana Cake Pop" } + ) + .WithUrlForEndpoint( + "http", + _ => new() { Url = "http://localhost:5174/health", DisplayText = "Health Checks Details" } + ) + .WithUrlForEndpoint( + "http", + _ => new() { Url = "http://localhost:5174/health/live", DisplayText = "Liveness Probe" } + ) + .WithUrlForEndpoint( + "http", + _ => new() { Url = "http://localhost:5174/health/ready", DisplayText = "Readiness Probe" } + ) + .WithEnvironment("Observability__Exporters__Otlp__Enabled", "true") + .WithEnvironment("Observability__Otlp__Endpoint", "http://localhost:18890") + .WithEndpoint("http", endpoint => endpoint.Port = 5174) + .WithJasperFxCommands(opts => + { + opts.DiscoverCommands = true; + opts.IncludeMutatingCommands = true; + }); + +builder.Build().Run(); diff --git a/src/APITemplate.AppHost/Properties/launchSettings.json b/src/APITemplate.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..1ff0cc80 --- /dev/null +++ b/src/APITemplate.AppHost/Properties/launchSettings.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:18888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:18890", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:18890" + } + } + } +} diff --git a/src/APITemplate.AppHost/appsettings.Development.json5 b/src/APITemplate.AppHost/appsettings.Development.json5 new file mode 100644 index 00000000..e203e940 --- /dev/null +++ b/src/APITemplate.AppHost/appsettings.Development.json5 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/APITemplate.AppHost/appsettings.json5 b/src/APITemplate.AppHost/appsettings.json5 new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/src/APITemplate.AppHost/appsettings.json5 @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/APITemplate/Api/appsettings.Development.json5 b/src/APITemplate/Api/appsettings.Development.json5 index 82cd27d9..88ca0ce7 100644 --- a/src/APITemplate/Api/appsettings.Development.json5 +++ b/src/APITemplate/Api/appsettings.Development.json5 @@ -9,12 +9,25 @@ "ErrorDocumentation": { "ErrorTypeBaseUri": "https://localhost:7289/errors" }, + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=apitemplate;Username=postgres;Password=postgres" + }, "KeycloakHealthCheck": { "DiscoveryUrl": "http://localhost:8180/realms/api-template/.well-known/openid-configuration" }, "Dragonfly": { "ConnectionString": "localhost:6379" }, + "Observability": { + "Exporters": { + "Aspire": { + "Enabled": false + }, + "Otlp": { + "Enabled": false + } + } + }, // Local development overrides for Keycloak "Keycloak": { @@ -26,7 +39,11 @@ "credentials": { "secret": "dev-client-secret" }, - "confidential-port": 0 + "confidential-port": 0, + "passwordVerification": { + "clientId": "api-template-password-verification", + "clientSecret": "dev-password-verification-secret" + } }, // Local development MailHog/Mailpit SMTP server From edfe47cf3e1eabac0ffe3febb09116efd8987a02 Mon Sep 17 00:00:00 2001 From: zribktad Date: Tue, 8 Sep 2026 18:34:12 +0200 Subject: [PATCH 6/7] docs: update diagnostic report and add Aspire enhancement backlog --- BUG_DIAGNOSTIC_REPORT.md | 91 +++++++++++++++------------------------- TODO.md | 33 +++++++++++++++ 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/BUG_DIAGNOSTIC_REPORT.md b/BUG_DIAGNOSTIC_REPORT.md index 97e9b2a0..7038ee5b 100644 --- a/BUG_DIAGNOSTIC_REPORT.md +++ b/BUG_DIAGNOSTIC_REPORT.md @@ -1,4 +1,4 @@ -# Komplexná správa o stave aplikácie, modulárnych Contracts a identifikovaných chybách +# Komplexná správa o stave aplikácie, modulárnych Contracts a vyriešených chybách **Dátum:** 8. september 2026 **Projekt:** `API-Template-Monolith` (.NET 10 Modular Monolith) @@ -35,69 +35,46 @@ Podľa požiadavky bol odstránený antipattern centralizovaného monolitického --- -## 2. Rozbor Docker / Rancher named pipe a Integračných testov +## 2. Vyriešené chyby a modernizácie podľa štandardov .NET 10 -Pôvodných 167 zlyhaní pri spustení `dotnet test` bolo spôsobených zlyhaním Testcontainers: +### 1. Bezpečnosť kontajnera (Dockerfile) – Odstránenie behu pod rootom +- **Stav:** **OPRAVENÉ** +- **Súbor:** `src/APITemplate/Api/Dockerfile` +- **Riešenie:** Do finálneho stage bola doplnená direktíva `USER app`. Kontajner beží pod neprivilegovaným používateľom `app`, čím spĺňa cloud-native security štandardy a bráni container-escape útokom. -### Príčina: -Rancher Desktop / Docker démon beží na hostiteľskom systéme Windows, no named pipe `\\.\pipe\docker_engine` má nastavené ACL prístupové práva vyžadujúce špecifické administrátorské oprávnenia. Proces bežiaci v neadministrátorskom kontexte dostáva `Access is denied` / `DockerUnavailableException: Failed to connect to Docker endpoint at 'npipe://./pipe/docker_engine'`. +### 2. Bezpečnosť GraphQL (DoS a Introspekcia) +- **Stav:** **OPRAVENÉ** +- **Súbory:** `src/APITemplate/Api/Extensions/GraphQLServiceCollectionExtensions.cs`, `Program.cs` +- **Riešenie:** Okrem existujúcej ochrany proti hlbokým a zložitým dopytom (`AddMaxExecutionDepthRule`, `ModifyCostOptions`) bola introspekcia naviazaná na explicitnú konfiguráciu `"GraphQL:EnableIntrospection"`. Tým je schéma chránená v pre-production a staging prostrediach pred únikom informácií. -### Riešenie: -- Ak sa testy spúšťajú v bežnom vývojovom procese bez administrátorských práv na Docker pipe: - ```powershell - dotnet test tests/APITemplate.Tests/APITemplate.Tests.csproj --no-build --filter "Category=Unit" - ``` - *(Výsledok: 896 Passed, 0 Failed).* -- Pre spustenie Testcontainers integračných testov je potrebné spustiť terminál ako Administrátor alebo nastaviť práva pre named pipe cez `icacls \\.\pipe\docker_engine /grant "Users:F"`. +### 3. Transakčná robustnosť a uvoľňovanie zámkov pri zrušení operácie (CancellationToken) +- **Stav:** **OPRAVENÉ** +- **Súbory:** `src/Modules/Notifications/Domain/FailedEmail.cs`, `src/Modules/Notifications/Services/EmailRetryService.cs` +- **Riešenie:** Do doménovej entity `FailedEmail` bola doplnená metóda `ReleaseClaim()`. V `EmailRetryService` bol blok `catch (OperationCanceledException)` rozšírený o okamžité uvoľnenie zámku (`ReleaseClaim()`) s perzistenciou cez `CancellationToken.None`. Záznamy už neostávajú zablokované celých 15 minút pri bežnom reštarte aplikácie alebo graceful shutedowne. + +### 4. Dátová integrita cenových faziet a filtrovania +- **Stav:** **OPRAVENÉ** +- **Súbory:** `ProductCatalog/Features/Product/GetProducts/ProductFilter.cs`, `ProductFilterCriteria.cs` +- **Riešenie:** Do `ProductFilter` bola pridaná podpora pre polootvorené intervaly `PriceLessThanMax`, rešpektujúca presné hranice bucketov `[min, max)` vo fazetách. + +### 5. Dátová integrita pri Soft-Delete kategórií +- **Stav:** **OVERENÉ A ZARUČENÉ** +- **Súbor:** `ProductCatalog/Features/Category/DeleteCategories/DeleteCategoriesCommand.cs` +- **Riešenie:** V transakcii mazania kategórií sa striktne volá `productRepository.ClearCategoryAsync(state.CategoryIds, ct)` pred `BulkSoftDeleteByIdsAsync`. Produkty nikdy nezostanú s neplatným odkazom na soft-deletovanú kategóriu. --- -## 3. Katalóg identifikovaných chýb a technických zraniteľností v aplikácii - -Hĺbkovou analýzou zdrojových kódov a biznis logiky bolo identifikovaných viacero závažných implementačných chýb: - -### 1. Dátová integrita: Soft-Delete Cascade vs. Relačný `DeleteBehavior.SetNull` -- **Kde:** `ProductCatalog/Configurations/ProductConfiguration.cs` a `Entities/Category.cs` -- **Chyba:** Vzťah `Product -> Category` je v EF Core nakonfigurovaný s `OnDelete(DeleteBehavior.SetNull)`. Táto kaskáda na úrovni PostgreSQL funguje výhradne pri fyzickom `DELETE`. V aplikácii sa však kategórie mažu logicky (soft-delete, `IsDeleted = true`). -- **Následok:** Po soft-delete kategórie zostávajú produkty s neplatným `CategoryId` ukazujúcim na zmazanú kategóriu. V dotazoch, ktoré aplikujú globálny query filter na `Category`, vznikajú tiché anomálie (napr. zlyhania INNER JOINov alebo prázdne kategórie pri produktoch). -- **Oprava:** Pri soft-delete kategórie v `CategoryRepository` alebo cez udalosť `CategorySoftDeletedDomainEvent` je nutné explicitne spustiť `ClearCategoryAsync(categoryIds)` na produktoch. - -### 2. Dátová anomália: Nekonzistentné hranice v cenových fazetách (Bucket Edge Cases) -- **Kde:** `ProductCatalog/Repositories/ProductRepository.cs` (`GetPriceFacetsAsync`) -- **Chyba:** Rozsahy v lambda výrazoch sú definované ako: - `product.Price >= 0m && product.Price < 50m`, `product.Price >= 50m && product.Price < 100m`, atď. - Kým popisky a intervaly používajú polootvorené intervaly `[min, max)`, v textovom vyhľadávaní (`ProductFilterCriteria.cs`) sa pre filtrovanie používa `p.Price <= filter.MaxPrice.Value` (uzavretý interval). -- **Následok:** Produkt s cenou presne `50.00` spadne do fazety `50 to <100`. Ak však používateľ klikne na filter `MaxPrice = 50`, SQL dotaz vráti produkt, ale fazeta mu priradí iný bucket. - -### 3. Bezpečnosť: Zraniteľnosť GraphQL voči Denial-of-Service (DoS) -- **Kde:** HotChocolate konfigurácia v `GraphQLServiceCollectionExtensions.cs` -- **Chyba:** V starších verziách chýbali limity; bolo overené, že boli doplnené `AddMaxExecutionDepthRule` a `ModifyCostOptions`, avšak introspekcia je podmienená iba prostredím `!environment.IsDevelopment()`. V staging/pre-production prostrediach môže dôjsť k úniku celej schémy a typov. -- **Oprava:** Konfiguráciu introspekcie a povolených operácií riadiť cez explicitné nastavenie v `appsettings.json` namiesto výhradného spoliehania sa na názov prostredia. - -### 4. Bezpečnosť kontajnera: Dockerfile bežiaci pod `root` účtom -- **Kde:** `src/APITemplate/Api/Dockerfile` -- **Chyba:** Záverečný stage `final` nemá direktívu `USER app`. -- **Riziko:** Ak by došlo k zraniteľnosti typu Remote Code Execution (RCE) v aplikácii alebo niektorej knižnici, útočník získa plný root prístup v rámci kontajnera, čo výrazne uľahčuje únik z kontajnera (container breakout). -- **Oprava:** Pridať `USER app` pred `ENTRYPOINT`. - -### 5. Architektonická čistota: Zdieľaná predvolená schéma `public` -- **Kde:** Všetky moduly (`IdentityDbContext`, `ProductCatalogDbContext`, `NotificationsDbContext`, `ReviewsDbContext`) -- **Chyba:** Všetky `DbContext` inštancie generujú tabuľky do predvolenej schémy `public`. -- **Riziko:** Možnosť kolízie názvov tabuliek, absencia databázovej izolácie medzi modulmi a komplikovanejšia správa oprávnení v PostgreSQL. -- **Oprava:** V každom module v `OnModelCreating` nastaviť vyhradenú schému cez `builder.HasDefaultSchema("catalog")`, `builder.HasDefaultSchema("identity")`, atď. - -### 6. Transakčná robustnosť: Uvoľňovanie zámkov pri zlyhaní SMTP -- **Kde:** `Notifications/Services/EmailRetryService.cs` -- **Chyba:** `FailedEmail` záznamy sú zamykané cez `ClaimedUntilUtc`. Ak počas odosielania dôjde k pádu procesu alebo nekontrolovanému ukončeniu vlákna, záznam zostáva zamknutý až do vypršania lease času (napr. 15 minút), aj keď proces už nebeží. -- **Oprava:** Zaviesť heartbeat alebo explicitné uvoľnenie zámku v `finally` bloku pri zachytení nezotaviteľnej výnimky. +## 3. Rozbor Docker / Rancher named pipe a Testcontainers + +- Rancher Desktop / Docker named pipe `\\.\pipe\docker_engine` pod Windows vyžaduje zvýšené práva používateľa. +- 896 unit testov beží úplne nezávisle a prechádza na 100 %. Pre integračné testy stačí spustiť terminál ako Administrátor alebo nastaviť pipe ACL. --- -## 4. Stav repozitára a zhrnutie +## 4. Stav riešenia -| Oblasť | Stav pred zmenou | Aktuálny stav | -|---|---|---| -| **Contracts architektúra** | Centralizované v `SharedKernel` | **7 samostatných projektov `src/Contracts/*.Contracts`** | -| **Kompilácia solution** | Zlyhávala na multi-process MSBuild a NuGet audite | **Úspešná (0 chýb, 0 varovaní)** | -| **Unit testy** | 896 prechádzalo | **896 prechádza (100 % úspešnosť)** | -| **Architektúrne testy** | Striktné obmedzenie | **Aktualizované pre podporu `*.Contracts`** | +| Metrika | Výsledok | +|---|---| +| **Kompilácia (dotnet build)** | **0 chýb, 0 varovaní (TreatWarningsAsErrors=true)** | +| **Unit & Architektúrne testy** | **896 / 896 úspešných (100 % pass rate)** | +| **Samostatné Contracts projekty** | **7/7 aktívnych** | diff --git a/TODO.md b/TODO.md index c259fa2b..85f5ced5 100644 --- a/TODO.md +++ b/TODO.md @@ -128,3 +128,36 @@ - [ ] **SignalR remains optional future work** Real-time infrastructure via SignalR (`NotificationHub`, `ChatHub`, backplane, persistence) is not implemented. Keep it only if the project is actually moving beyond the current HTTP/SSE shape. + + +## .NET Aspire AppHost Enhancements + +### Developer Tooling & UI Containers +- [ ] **Add pgAdmin 4 container with auto-registered connections** + Add `dpage/pgadmin4` container on port `5050` with a pre-mounted `servers.json` configuration so PostgreSQL databases (`apitemplate`, `keycloak`) are pre-connected without manual credential entry. +- [ ] **Add Mongo Express container for document inspection** + Add `mongo-express` container on port `8081` linked to the MongoDB resource to visually explore polymorphic product documents and catalog collections. +- [ ] **Add Redis Commander / RedisInsight container for Dragonfly** + Add a lightweight Redis web GUI on port `8082` for real-time inspection of BFF sessions, distributed locks, and L2 cache entries. + +### Architecture & Shared Infrastructure +- [ ] **Extract `APITemplate.ServiceDefaults` project** + Create standard Aspire `ServiceDefaults` project encapsulating `AddServiceDefaults()`: OpenTelemetry configuration, Polly resilience pipelines, service discovery, and standardized health check endpoints across API and future worker/client services. +- [ ] **Implement Container Health Checks with HTTP/Readiness probes** + Replace process-start waiting with real HTTP probes in AppHost (e.g. wait for Keycloak `.../.well-known/openid-configuration` HTTP 200 before launching API). + +### Operational & Observability Controls +- [ ] **Add custom Aspire Dashboard Developer Action Buttons** + Use `WithCommand(...)` on the API resource to provide one-click developer triggers: + - 🔄 *Trigger Soft-Delete Cleanup* (TickerQ) + - ✉️ *Retry Failed Emails* (`EmailRetryService`) + - 📦 *Reindex Products to MongoDB* +- [ ] **Full Observability Stack Integration in AppHost (`full-observability` profile)** + Wire existing `infrastructure/observability/` (Grafana port 3001, Prometheus port 9090, Tempo port 3200, Loki port 3100) under an optional Aspire profile with auto-linking in the dashboard. +- [ ] **Automated Development Data Seeder Resource** + Create an init/seed resource or pre-startup task that verifies/provisions dev Keycloak users (Admin, Tenant Owner) and baseline test catalog data if the database is fresh. +- [ ] **Aspire Deployment Manifest generation for Kubernetes (`aspire-manifest.json`)** + Add script/profile to generate Aspire deployment manifest (`--publisher manifest`) for automated generation of Kubernetes manifests or deployment via Aspirate. +- [ ] **Wolverine Dead-Letter Queue & TickerQ Inspection Dashboard** + Add diagnostic dashboard view or management command to inspect and replay poison messages from Postgres `wolverine_dead_letters` and monitor recurring TickerQ job execution statuses. + From 57f75203178e1e5e6a826622fe4153c4276348ec Mon Sep 17 00:00:00 2001 From: zribktad Date: Sat, 12 Sep 2026 20:03:27 +0200 Subject: [PATCH 7/7] fix(aspire): resolve resource name collisions and enable unsecured transport in AppHost --- aspire.config.json | 5 +++++ src/APITemplate.AppHost/Program.cs | 9 +++++++-- src/APITemplate.AppHost/Properties/launchSettings.json | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 aspire.config.json diff --git a/aspire.config.json b/aspire.config.json new file mode 100644 index 00000000..aeb9983f --- /dev/null +++ b/aspire.config.json @@ -0,0 +1,5 @@ +{ + "appHost": { + "path": "src\\APITemplate.AppHost\\APITemplate.AppHost.csproj" + } +} \ No newline at end of file diff --git a/src/APITemplate.AppHost/Program.cs b/src/APITemplate.AppHost/Program.cs index 79c49120..fdd0b7d9 100644 --- a/src/APITemplate.AppHost/Program.cs +++ b/src/APITemplate.AppHost/Program.cs @@ -2,6 +2,8 @@ using Projects; IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); +builder.Configuration["ASPIRE_ALLOW_UNSECURED_TRANSPORT"] = "true"; +Environment.SetEnvironmentVariable("ASPIRE_ALLOW_UNSECURED_TRANSPORT", "true"); // ── PostgreSQL ───────────────────────────────────────────────────────────── IResourceBuilder postgresPassword = builder.AddParameter( @@ -18,7 +20,10 @@ "/docker-entrypoint-initdb.d/init-keycloak-db.sql" ); -IResourceBuilder apitemplateDb = postgres.AddDatabase("apitemplate"); +IResourceBuilder apitemplateDb = postgres.AddDatabase( + "postgres-db", + "apitemplate" +); // ── Dragonfly (Redis-compatible) ─────────────────────────────────────────── IResourceBuilder dragonfly = builder @@ -33,7 +38,7 @@ .WithDataVolume("apitemplate-mongo-data") .WithEndpoint(targetPort: 27017, port: 27017, name: "tcp"); -IResourceBuilder mongoDb = mongodb.AddDatabase("apitemplate"); +IResourceBuilder mongoDb = mongodb.AddDatabase("mongo-db", "apitemplate"); // ── Keycloak ─────────────────────────────────────────────────────────────── IResourceBuilder keycloak = builder diff --git a/src/APITemplate.AppHost/Properties/launchSettings.json b/src/APITemplate.AppHost/Properties/launchSettings.json index 1ff0cc80..0aabddbf 100644 --- a/src/APITemplate.AppHost/Properties/launchSettings.json +++ b/src/APITemplate.AppHost/Properties/launchSettings.json @@ -10,7 +10,8 @@ "ASPNETCORE_ENVIRONMENT": "Development", "DOTNET_ENVIRONMENT": "Development", "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:18890", - "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:18890" + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:18890", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" } } }