diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index a591825c..601f09c7 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -43,55 +43,7 @@ updates:
open-pull-requests-limit: 5
- package-ecosystem: "docker"
- directory: "/src/APITemplate.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/ProductCatalog/ProductCatalog.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/Reviews/Reviews.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/Identity/Identity.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/Notifications/Notifications.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/FileStorage/FileStorage.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/BackgroundJobs/BackgroundJobs.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Services/Webhooks/Webhooks.Api"
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 5
-
- - package-ecosystem: "docker"
- directory: "/src/Gateway/Gateway.Api"
+ directory: "/src/APITemplate"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
index 7b3d10e6..0a42cce1 100644
--- a/.github/workflows/pr-validation.yml
+++ b/.github/workflows/pr-validation.yml
@@ -8,6 +8,7 @@ on:
pull_request:
branches:
- main
+ - master
jobs:
build-and-test:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5e617018..de5c1368 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -6,7 +6,6 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "prepare: debug http",
- "postDebugTask": "docker: infra down (dev)",
"program": "${workspaceFolder}/src/APITemplate.Api/bin/Debug/net10.0/APITemplate.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/APITemplate.Api",
@@ -29,7 +28,6 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "prepare: debug https",
- "postDebugTask": "docker: infra down (dev)",
"program": "${workspaceFolder}/src/APITemplate.Api/bin/Debug/net10.0/APITemplate.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/APITemplate.Api",
@@ -125,173 +123,6 @@
"Observability__Exporters__Otlp__Enabled": "true"
},
"console": "integratedTerminal"
- },
- {
- "name": "MS: Gateway",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug gateway",
- "program": "${workspaceFolder}/src/Gateway/Gateway.Api/bin/Debug/net10.0/Gateway.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Gateway/Gateway.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:8080",
- "ReverseProxy__Clusters__product-catalog__Destinations__destination1__Address": "http://localhost:5010",
- "ReverseProxy__Clusters__reviews__Destinations__destination1__Address": "http://localhost:5020",
- "ReverseProxy__Clusters__identity__Destinations__destination1__Address": "http://localhost:5030",
- "ReverseProxy__Clusters__notifications__Destinations__destination1__Address": "http://localhost:5040",
- "ReverseProxy__Clusters__file-storage__Destinations__destination1__Address": "http://localhost:5050",
- "ReverseProxy__Clusters__background-jobs__Destinations__destination1__Address": "http://localhost:5060",
- "ReverseProxy__Clusters__webhooks__Destinations__destination1__Address": "http://localhost:5070"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: Product Catalog",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug product-catalog",
- "program": "${workspaceFolder}/src/Services/ProductCatalog/ProductCatalog.Api/bin/Debug/net10.0/ProductCatalog.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/ProductCatalog/ProductCatalog.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5010",
- "ConnectionStrings__ProductCatalogDb": "Host=localhost;Port=5432;Database=productcatalog_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "MongoDB__ConnectionString": "mongodb://localhost:27017",
- "MongoDB__DatabaseName": "productcatalog",
- "Keycloak__realm": "api-template",
- "Keycloak__auth-server-url": "http://localhost:8180/",
- "Keycloak__resource": "api-template",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: Reviews",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug reviews",
- "program": "${workspaceFolder}/src/Services/Reviews/Reviews.Api/bin/Debug/net10.0/Reviews.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/Reviews/Reviews.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5020",
- "ConnectionStrings__ReviewsDb": "Host=localhost;Port=5432;Database=reviews_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "Keycloak__realm": "api-template",
- "Keycloak__auth-server-url": "http://localhost:8180/",
- "Keycloak__resource": "api-template",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: Identity",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug identity",
- "program": "${workspaceFolder}/src/Services/Identity/Identity.Api/bin/Debug/net10.0/Identity.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/Identity/Identity.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5030",
- "ConnectionStrings__IdentityDb": "Host=localhost;Port=5432;Database=identity_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "Keycloak__realm": "api-template",
- "Keycloak__auth-server-url": "http://localhost:8180/",
- "Keycloak__resource": "api-template",
- "Keycloak__credentials__secret": "dev-client-secret",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: Notifications",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug notifications",
- "program": "${workspaceFolder}/src/Services/Notifications/Notifications.Api/bin/Debug/net10.0/Notifications.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/Notifications/Notifications.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5040",
- "ConnectionStrings__DefaultConnection": "Host=localhost;Port=5432;Database=notifications_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "Email__SmtpHost": "localhost",
- "Email__SmtpPort": "1025",
- "Email__UseSsl": "false",
- "Email__SenderEmail": "noreply@api-template.local",
- "Email__SenderName": "API Template",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: File Storage",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug file-storage",
- "program": "${workspaceFolder}/src/Services/FileStorage/FileStorage.Api/bin/Debug/net10.0/FileStorage.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/FileStorage/FileStorage.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5050",
- "ConnectionStrings__FileStorageDb": "Host=localhost;Port=5432;Database=filestorage_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "FileStorage__BasePath": "${workspaceFolder}/tmp/storage",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: Background Jobs",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug background-jobs",
- "program": "${workspaceFolder}/src/Services/BackgroundJobs/BackgroundJobs.Api/bin/Debug/net10.0/BackgroundJobs.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/BackgroundJobs/BackgroundJobs.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5060",
- "ConnectionStrings__DefaultConnection": "Host=localhost;Port=5432;Database=backgroundjobs_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "ConnectionStrings__Dragonfly": "localhost:6379",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
- },
- {
- "name": "MS: Webhooks",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "ms: prepare debug webhooks",
- "program": "${workspaceFolder}/src/Services/Webhooks/Webhooks.Api/bin/Debug/net10.0/Webhooks.Api.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/Services/Webhooks/Webhooks.Api",
- "stopAtEntry": false,
- "env": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "ASPNETCORE_URLS": "http://localhost:5070",
- "ConnectionStrings__DefaultConnection": "Host=localhost;Port=5432;Database=webhooks_db;Username=postgres;Password=postgres",
- "ConnectionStrings__RabbitMQ": "amqp://guest:guest@localhost:5672",
- "Observability__Otlp__Endpoint": "http://localhost:4317"
- },
- "console": "integratedTerminal"
}
]
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 88f898b6..1c2500c2 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -299,280 +299,6 @@
],
"dependsOrder": "sequence",
"problemMatcher": []
- },
- {
- "label": "ms: build all",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/APITemplate.Microservices.slnx"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: test all",
- "type": "shell",
- "command": "dotnet test ${workspaceFolder}/tests/SharedKernel.Tests && dotnet test ${workspaceFolder}/tests/ProductCatalog.Tests && dotnet test ${workspaceFolder}/tests/Reviews.Tests && dotnet test ${workspaceFolder}/tests/Identity.Tests && dotnet test ${workspaceFolder}/tests/Notifications.Tests && dotnet test ${workspaceFolder}/tests/FileStorage.Tests && dotnet test ${workspaceFolder}/tests/BackgroundJobs.Tests && dotnet test ${workspaceFolder}/tests/Webhooks.Tests && dotnet test ${workspaceFolder}/tests/Integration.Tests --filter \"Category=Integration.SmokeStartup\"",
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: smoke startup",
- "type": "process",
- "command": "dotnet",
- "args": [
- "test",
- "${workspaceFolder}/tests/Integration.Tests/Integration.Tests.csproj",
- "--filter",
- "Category=Integration.SmokeStartup"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build gateway",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Gateway/Gateway.Api/Gateway.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build product-catalog",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/ProductCatalog/ProductCatalog.Api/ProductCatalog.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build reviews",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/Reviews/Reviews.Api/Reviews.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build identity",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/Identity/Identity.Api/Identity.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build notifications",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/Notifications/Notifications.Api/Notifications.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build file-storage",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/FileStorage/FileStorage.Api/FileStorage.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build background-jobs",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/BackgroundJobs/BackgroundJobs.Api/BackgroundJobs.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: build webhooks",
- "type": "process",
- "command": "dotnet",
- "args": [
- "build",
- "${workspaceFolder}/src/Services/Webhooks/Webhooks.Api/Webhooks.Api.csproj"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "ms: docker infra up",
- "type": "process",
- "command": "docker",
- "args": [
- "compose",
- "-f",
- "docker-compose.microservices.yml",
- "up",
- "-d",
- "postgres",
- "mongodb",
- "dragonfly",
- "rabbitmq",
- "mailpit"
- ],
- "problemMatcher": []
- },
- {
- "label": "ms: docker infra ensure up",
- "type": "process",
- "command": "pwsh",
- "args": [
- "-NoProfile",
- "-ExecutionPolicy",
- "Bypass",
- "-Command",
- "$ErrorActionPreference = 'Stop'; function Start-Or-Skip([string]$composeFile, [string]$service) { $output = docker compose -f $composeFile up -d $service 2>&1; $exitCode = $LASTEXITCODE; if ($exitCode -eq 0) { $output | Out-Host; return }; $msg = ($output | Out-String); if ($msg -match 'port is already allocated' -or $msg -match 'Bind for 0\\.0\\.0\\.0') { Write-Host \"$service port is already allocated. Continuing.\"; return }; throw $msg }; Start-Or-Skip 'docker-compose.yml' 'keycloak-db'; Start-Or-Skip 'docker-compose.yml' 'keycloak'; Start-Or-Skip 'docker-compose.microservices.yml' 'postgres'; Start-Or-Skip 'docker-compose.microservices.yml' 'mongodb'; Start-Or-Skip 'docker-compose.microservices.yml' 'dragonfly'; Start-Or-Skip 'docker-compose.microservices.yml' 'rabbitmq'; Start-Or-Skip 'docker-compose.microservices.yml' 'mailpit'; exit 0"
- ],
- "problemMatcher": []
- },
- {
- "label": "ms: docker infra + observability up",
- "type": "process",
- "command": "docker",
- "args": [
- "compose",
- "-f",
- "docker-compose.microservices.yml",
- "up",
- "-d",
- "postgres",
- "mongodb",
- "dragonfly",
- "rabbitmq",
- "mailpit",
- "alloy",
- "prometheus",
- "loki",
- "tempo",
- "grafana"
- ],
- "problemMatcher": []
- },
- {
- "label": "ms: docker all up",
- "type": "process",
- "command": "docker",
- "args": [
- "compose",
- "-f",
- "docker-compose.microservices.yml",
- "up",
- "--build",
- "-d"
- ],
- "problemMatcher": []
- },
- {
- "label": "ms: docker all down",
- "type": "process",
- "command": "docker",
- "args": [
- "compose",
- "-f",
- "docker-compose.microservices.yml",
- "down"
- ],
- "problemMatcher": []
- },
- {
- "label": "ms: docker logs",
- "type": "process",
- "command": "docker",
- "args": [
- "compose",
- "-f",
- "docker-compose.microservices.yml",
- "logs",
- "-f",
- "--tail",
- "100"
- ],
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug gateway",
- "dependsOn": [
- "ms: build gateway"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug product-catalog",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build product-catalog"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug identity",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build identity"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug notifications",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build notifications"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug reviews",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build reviews"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug file-storage",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build file-storage"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug background-jobs",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build background-jobs"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
- },
- {
- "label": "ms: prepare debug webhooks",
- "dependsOn": [
- "ms: docker infra ensure up",
- "ms: build webhooks"
- ],
- "dependsOrder": "sequence",
- "problemMatcher": []
}
]
}
diff --git a/APITemplate.slnx b/APITemplate.slnx
index 25e85f46..7df42cb4 100644
--- a/APITemplate.slnx
+++ b/APITemplate.slnx
@@ -1,191 +1,27 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f6d36144..e5a26a58 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,7 +3,6 @@
true
-
@@ -21,9 +20,9 @@
-
-
-
+
+
+
@@ -33,10 +32,8 @@
-
-
@@ -44,30 +41,25 @@
-
-
-
-
-
-
+
+
-
-
-
-
+
+
+
-
+
@@ -81,13 +73,10 @@
-
-
-
-
+
+
-
diff --git a/README.md b/README.md
index d2c745f2..3b94c8d6 100644
--- a/README.md
+++ b/README.md
@@ -252,7 +252,7 @@ classDiagram
* **GraphQL Core:** HotChocolate `15.1`
* **Auth:** Keycloak 26 (JWT Bearer + BFF Cookie via OIDC)
* **Utilities:** `Serilog.AspNetCore`, `FluentValidation`, `Ardalis.Specification`, `Kot.MongoDB.Migrations`
-* **Test Suite:** xUnit 3, `Microsoft.AspNetCore.Mvc.Testing`, Moq, Shouldly (exclusive assertion style; see `docs/testing.md`), `FluentValidation.TestHelper`, Testcontainers.PostgreSql, Respawn
+* **Test Suite:** xUnit 3, `Microsoft.AspNetCore.Mvc.Testing`, Moq, Shouldly, `FluentValidation.TestHelper`, Testcontainers.PostgreSql, Respawn
---
diff --git a/TODO-Architecture.md b/TODO-Architecture.md
index ea7a3839..cbeb9836 100644
--- a/TODO-Architecture.md
+++ b/TODO-Architecture.md
@@ -1,200 +1,267 @@
-# Architecture: Microservices with A-Frame + Wolverine + RabbitMQ
+# Architecture Evolution: Monolith to Microservices
-## Status: IMPLEMENTED
+## Current State
-All 7 microservices extracted from the monolith and running independently.
+Well-structured **Clean Architecture monolith** with:
+- Vertical slice features (Product, Category, User, Tenant, Email, Webhooks, Jobs)
+- Wolverine as message bus (supports distributed transport)
+- Dual database (PostgreSQL + MongoDB)
+- Multi-tenancy with isolated query filters
+- Domain events for cross-feature communication
---
-## Open TODOs
+## Identified Bounded Contexts
-### Reliability
-- Replace in-memory `ChannelJobQueue` in BackgroundJobs with durable persistence/replay so queued jobs survive process restarts.
-- Replace in-memory `ChannelEmailQueue` in Notifications with durable persistence/replay so pending emails are not lost on restart between enqueue and delivery.
+| # | Service | Entities | Database |
+|---|---------|----------|----------|
+| 1 | **Product Catalog** | Product, Category, ProductData, ProductDataLink, ProductCategoryStats | PostgreSQL + MongoDB |
+| 2 | **Reviews** | ProductReview | PostgreSQL |
+| 3 | **Identity & Tenancy** | AppUser, Tenant, TenantInvitation | PostgreSQL + Keycloak |
+| 4 | **Notifications** | FailedEmail, email templates | PostgreSQL |
+| 5 | **File Storage** | StoredFile | PostgreSQL + filesystem/S3 |
+| 6 | **Background Jobs** | JobExecution | PostgreSQL (TickerQ) |
+| 7 | **Webhooks** | Incoming/Outgoing webhooks | In-memory queues |
-### File Storage Cascade
-- Implement real product-related file cleanup in FileStorage on `ProductDeletedIntegrationEvent`.
-- Introduce an explicit relation from stored files to product-owned resources, so `FilesCascadeCompleted` is emitted only after actual cleanup instead of a placeholder acknowledgment (currently publishes `FilesCascadeCompleted` with `count = 0`).
+---
-### Messaging — Idempotency
-- Add `Guid MessageId` to all integration event records and guard consumers with an idempotency check against a seen-message store (RabbitMQ redelivery can duplicate work today).
+## Recommended Strategy: Modular Monolith → Strangler Fig
-### DRY — Product validators
-- Consolidate `ProductValidationRules` and `ProductRequestValidatorBase` from `APITemplate.Application` and `ProductCatalog.Application` into a single shared location (e.g. `SharedKernel.Application`).
+### Phase 1 — Modular Monolith
-### Test Coverage
-- Add integration/runtime coverage for microservices shared auth: permission policies, BFF cookie/OIDC flow (where applicable), tenant claim enrichment, and locked-down Wolverine HTTP endpoints.
+Transform the current monolith into isolated modules with explicit boundaries while keeping everything in a single deployable unit.
-### Gateway — Demo / Example APIs
-- Expose monolith-style demo endpoints (`IdempotentController`, `PatchController`, `SseController` under `src/APITemplate.Api/`) via a dedicated Examples service or Gateway host, and register routes in YARP.
+#### Step 1: Create Module Structure
-### Optional polish
-- **Output cache:** ProductCatalog, Identity, Reviews, and FileStorage use `AddSharedOutputCaching` + `[OutputCache]` + invalidation. BackgroundJobs still runs with `useOutputCaching: false` (it has a GET on `JobsController`); enable caching there only if you want read responses cached like the other APIs.
+Create a new directory layout under `src/Modules/`:
----
+```
+src/Modules/
+ ProductCatalog/
+ ProductCatalog.Domain/
+ ProductCatalog.Application/
+ ProductCatalog.Infrastructure/
+ ProductCatalog.Api/
+ Reviews/
+ Reviews.Domain/
+ Reviews.Application/
+ Reviews.Infrastructure/
+ Reviews.Api/
+ Identity/
+ Identity.Domain/
+ Identity.Application/
+ Identity.Infrastructure/
+ Identity.Api/
+ Notifications/
+ Notifications.Domain/
+ Notifications.Application/
+ Notifications.Infrastructure/
+ Notifications.Api/
+ FileStorage/
+ FileStorage.Domain/
+ FileStorage.Application/
+ FileStorage.Infrastructure/
+ FileStorage.Api/
+ BackgroundJobs/
+ BackgroundJobs.Domain/
+ BackgroundJobs.Application/
+ BackgroundJobs.Infrastructure/
+ BackgroundJobs.Api/
+ Webhooks/
+ Webhooks.Domain/
+ Webhooks.Application/
+ Webhooks.Infrastructure/
+ Webhooks.Api/
+```
-## Completed ([x])
+#### Step 2: Extract Shared Kernel
-### [x] Saga — EF Core persistence
-`ProductDeletionSaga` and `TenantDeactivationSaga` are mapped in each service `DbContext`, with EF migrations and saga schema (`sagas`).
+Create `src/SharedKernel/` containing cross-cutting concerns shared by all modules:
-### [x] Saga — Timeout handling
-Both sagas schedule Wolverine timeouts (`ProductDeletionSagaTimeout`, `TenantDeactivationSagaTimeout`) with compensation-style handling.
+- `IAuditableTenantEntity`, `IAuditableEntity`, `ISoftDeletable`, `IHasId`
+- `AuditInfo` value object
+- `ITenantProvider`, `IActorProvider`
+- `PagedResponse`
+- `IUnitOfWork` abstraction
+- `IRepository` base interface
+- Multi-tenancy infrastructure (global query filters, tenant resolution)
+- Soft-delete base infrastructure
+- Common domain exceptions (`NotFoundException`, `ValidationException`)
+- Audit stamping logic
-### [x] Output caching (microservices read APIs)
-Tenant-aware policies, Redis/Dragonfly-backed output cache registration (`AddSharedOutputCaching`), `UseSharedOutputCaching` in the shared pipeline, `CacheInvalidationHandler`, and write-side `CacheInvalidationCascades` are implemented in `SharedKernel.Api` / `SharedKernel.Application` and wired for **ProductCatalog, Identity, Reviews, FileStorage** (including `[OutputCache]` on their GET controllers).
+#### Step 3: Split AppDbContext
-### [x] Saga-flow integration tests
-`tests/Integration.Tests/Sagas/ProductDeletionSagaIntegrationTests.cs` and `TenantDeactivationSagaIntegrationTests.cs` exercise cross-service correlation and completion messages.
+Replace the single `AppDbContext` with per-module DbContexts:
----
+- `ProductCatalogDbContext` — Products, Categories, ProductDataLinks, ProductCategoryStats
+- `ReviewsDbContext` — ProductReviews
+- `IdentityDbContext` — AppUsers, Tenants, TenantInvitations
+- `NotificationsDbContext` — FailedEmails
+- `FileStorageDbContext` — StoredFiles
+- `BackgroundJobsDbContext` — JobExecutions
-## Bounded Contexts (Implemented)
+All contexts share the same PostgreSQL database but enforce module boundaries — a module must not query another module's tables directly.
-| # | Service | Entities | Database | Transport |
-|---|---------|----------|----------|-----------|
-| 1 | **Product Catalog** | Product, Category, ProductData, ProductDataLink, ProductCategoryStats | PostgreSQL + MongoDB | RabbitMQ (product-catalog.events) |
-| 2 | **Reviews** | ProductReview, ProductProjection | PostgreSQL | RabbitMQ (reviews.events) |
-| 3 | **Identity & Tenancy** | AppUser, Tenant, TenantInvitation | PostgreSQL + Keycloak | RabbitMQ (identity.events) |
-| 4 | **Notifications** | FailedEmail | PostgreSQL | RabbitMQ (consumer only) |
-| 5 | **File Storage** | StoredFile | PostgreSQL + filesystem | RabbitMQ (consumer only) |
-| 6 | **Background Jobs** | JobExecution | PostgreSQL + TickerQ | RabbitMQ (consumer only) |
-| 7 | **Webhooks** | WebhookSubscription, DeliveryLog, EventType | PostgreSQL | RabbitMQ (consumer only) |
+#### Step 4: Define Module Contracts (Events)
----
+Create `src/Contracts/` as a shared NuGet package containing only:
+
+- Integration events (cross-module communication)
+- Shared DTOs for inter-module queries
+- No domain logic, no entities
+
+Example events:
+```
+ProductCreatedEvent { ProductId, TenantId, Name }
+ProductDeletedEvent { ProductId, TenantId }
+UserRegisteredEvent { UserId, TenantId, Email }
+TenantDeactivatedEvent { TenantId }
+```
-## Architecture Patterns
+#### Step 5: Replace Direct Cross-Module Calls with Events
-### A-Frame Architecture (per handler)
-- **Load** (Infrastructure) - fetch data, validate existence
-- **Handle** (Domain Logic) - pure business decisions
-- **Conductor** (Wolverine) - orchestrates Load -> Handle -> side effects
+Current direct dependencies to refactor:
-### Clean Architecture (per service)
-- Domain -> Application -> Infrastructure -> Api
-- SharedKernel for cross-cutting concerns (5 projects)
+| Caller | Callee | Current | Target |
+|--------|--------|---------|--------|
+| Product soft-delete | Reviews cascade | `ProductSoftDeleteCascadeRule` calls ReviewRepository directly | Publish `ProductDeletedEvent` → Reviews module handles cascade |
+| Tenant soft-delete | Users, Products cascade | `TenantSoftDeleteCascadeRule` accesses multiple repositories | Publish `TenantDeactivatedEvent` → each module handles own cleanup |
+| ProductReview creation | User validation | Queries UserRepository | Reviews module stores read-only user projection, updated via `UserUpdatedEvent` |
+| Product creation | Category validation | Queries CategoryRepository | Both in same module (Product Catalog) — no change needed |
+| Email handlers | User/Tenant data | Queries user/tenant repos | Notifications module receives all needed data in the event payload |
-### Vertical Slice (per feature)
-- Co-located command/query + handler + validator + DTOs in Application layer
+#### Step 6: Enforce Module Isolation
-### DDD
-- Aggregate roots per bounded context
-- Integration events for cross-service communication
-- Saga orchestration for distributed workflows
+- Each module exposes only its public API (controllers, events, query interfaces)
+- No module references another module's `Domain` or `Infrastructure` project
+- Communication exclusively through Wolverine events (in-process for now)
+- Add architecture tests (NetArchTest or ArchUnitNET) to enforce boundaries
----
+#### Step 7: Split GraphQL Schema
-## Solution Structure
+- Each module defines its own GraphQL types, queries, and mutations
+- Use Hot Chocolate Schema Stitching to compose the unified schema
+- Prepare for future Hot Chocolate Federation when modules become services
-```
-src/
- SharedKernel/
- SharedKernel.Domain/ # Entity contracts, value objects, exceptions
- SharedKernel.Application/ # ErrorOr middleware, batch, DTOs, queue abstractions
- SharedKernel.Infrastructure/ # TenantAuditableDbContext, UoW, RepositoryBase, queue impl
- SharedKernel.Messaging/ # Wolverine conventions, RabbitMQ topology, retry policies
- SharedKernel.Api/ # Base controller, ErrorOr mapping, auth, observability, DI helpers
- Contracts/
- Contracts.IntegrationEvents/ # Integration events + saga messages
- Services/
- {ServiceName}/
- {ServiceName}.Domain/
- {ServiceName}.Application/
- {ServiceName}.Infrastructure/
- {ServiceName}.Api/
- Gateway/
- Gateway.Api/ # YARP reverse proxy
-tests/
- {ServiceName}.Tests/ # Unit tests per service
-```
+#### Step 8: Split REST Controllers
+
+- Move controllers into their respective module's `Api` project
+- Host module still composes all endpoints in `Program.cs`
+- Each module registers its own services via `IServiceCollection` extensions
---
-## Messaging Topology (RabbitMQ)
-
-### Exchanges (fanout, durable)
-| Exchange | Publisher |
-|----------|-----------|
-| `identity.events` | Identity |
-| `product-catalog.events` | Product Catalog |
-| `reviews.events` | Reviews |
-
-### Queue Bindings
-| Queue | Exchange | Consumer |
-|-------|----------|----------|
-| `reviews.product-created` | product-catalog.events | Reviews |
-| `reviews.product-deleted` | product-catalog.events | Reviews |
-| `reviews.tenant-deactivated` | identity.events | Reviews |
-| `file-storage.product-deleted` | product-catalog.events | File Storage |
-| `notifications.user-registered` | identity.events | Notifications |
-| `notifications.user-role-changed` | identity.events | Notifications |
-| `notifications.invitation-created` | identity.events | Notifications |
-| `webhooks.product-created` | product-catalog.events | Webhooks |
-| `webhooks.product-deleted` | product-catalog.events | Webhooks |
-| `webhooks.review-created` | reviews.events | Webhooks |
-| `webhooks.category-deleted` | product-catalog.events | Webhooks |
-| `background-jobs.tenant-deactivated` | identity.events | Background Jobs |
-| `identity.tenant-deactivated` | identity.events | Identity |
-| `identity.users-cascade-completed` | — (direct) | Identity |
-| `identity.products-cascade-completed` | — (direct) | Identity |
-| `identity.categories-cascade-completed` | — (direct) | Identity |
-| `product-catalog.tenant-deactivated` | identity.events | ProductCatalog |
+### Phase 2 — Strangler Fig Extraction
----
+Extract modules into independent services when scaling demands it. Start with the least coupled modules.
+
+#### Extraction Order (least to most coupled)
-## Sagas
+1. **Notifications** — no inbound queries, only consumes events
+2. **File Storage** — simple CRUD, minimal dependencies
+3. **Webhooks** — event-driven by nature
+4. **Background Jobs** — independent scheduler
+5. **Reviews** — depends on Product (read-only projection)
+6. **Identity & Tenancy** — central but well-defined API (Keycloak handles heavy lifting)
+7. **Product Catalog** — core domain, extract last
-### ProductDeletionSaga (Product Catalog)
-- Triggered by `StartProductDeletionSaga` from DeleteProductsCommand
-- Publishes `ProductDeletedIntegrationEvent` to cascade to Reviews + FileStorage
-- Waits for `ReviewsCascadeCompleted` + `FilesCascadeCompleted`
+#### Step 1: Deploy API Gateway
-### TenantDeactivationSaga (Identity)
-- Triggered by `StartTenantDeactivationSaga` from DeleteTenantCommand
-- Publishes `TenantDeactivatedIntegrationEvent` to all dependent services
-- Waits for `UsersCascadeCompleted` + `ProductsCascadeCompleted` + `CategoriesCascadeCompleted`
+- Add YARP or Ocelot as reverse proxy
+- Route all traffic through gateway
+- Initially, gateway proxies everything to the monolith
+
+#### Step 2: Extract First Service (Notifications)
+
+1. Create standalone ASP.NET project from Notifications module
+2. Give it its own PostgreSQL database (or schema)
+3. Switch Wolverine transport from in-process to RabbitMQ:
+ ```csharp
+ // Before (in-process)
+ opts.PublishMessage().Locally();
+
+ // After (distributed)
+ opts.PublishMessage()
+ .ToRabbitQueue("notifications");
+ ```
+4. Update API Gateway to route `/api/v1/notifications/*` to new service
+5. Remove Notifications module from monolith
+
+#### Step 3: Configure Distributed Messaging
+
+- Deploy RabbitMQ (or Azure Service Bus)
+- Enable Wolverine outbox pattern for guaranteed delivery:
+ ```csharp
+ opts.UseRabbitMq(rabbit => { ... })
+ .AutoProvision()
+ .UseConventionalRouting();
+ opts.Policies.UseDurableOutboxOnAllSendingEndpoints();
+ ```
+- Each service gets its own durable inbox/outbox
+
+#### Step 4: Database-per-Service
+
+- Each extracted service gets its own PostgreSQL database
+- Migrate data from shared DB to service-owned DB
+- Remove tables from monolith's DB after migration
+- MongoDB stays with Product Catalog service
+
+#### Step 5: Repeat for Each Service
+
+Follow the same pattern for each module in extraction order:
+1. Stand up independent service from module code
+2. Point its Wolverine transport to RabbitMQ
+3. Give it its own database
+4. Update API Gateway routing
+5. Remove module from monolith
+
+#### Step 6: Handle Cross-Service Queries
+
+For queries that span multiple services:
+
+- **API Composition** — Gateway aggregates responses from multiple services
+- **CQRS Read Models** — Services maintain denormalized read projections updated via events
+- **GraphQL Federation** — Hot Chocolate Federation composes subgraphs from each service
---
-## Infrastructure (Docker Compose)
-
-| Service | Image | Port | Purpose |
-|---------|-------|------|---------|
-| RabbitMQ | rabbitmq:4-management | 5672, 15672 | Message broker |
-| PostgreSQL | postgres:17 | 5432 | 7 databases (one per service) |
-| MongoDB | mongo:8 | 27017 | Product Catalog documents |
-| Dragonfly | dragonflydb | 6379 | Cache + TickerQ coordination |
-| Mailpit | axllent/mailpit | 1025, 8025 | Dev email |
-| Alloy | grafana/alloy | 4317 | OTLP collector |
-| Prometheus | prom/prometheus | 9090 | Metrics |
-| Loki | grafana/loki | 3100 | Logs |
-| Tempo | grafana/tempo | 3200 | Traces |
-| Grafana | grafana/grafana | 3001 | Dashboards |
-| YARP Gateway | custom | 8080 | API routing |
-
-### Running
-```bash
-docker compose -f docker-compose.microservices.yml up -d
-```
+## Infrastructure Requirements
+
+### Phase 1 (Modular Monolith)
+- No new infrastructure needed
+- Same PostgreSQL, MongoDB, Redis/DragonFly, Keycloak
+
+### Phase 2 (Microservices)
+- **Message Broker:** RabbitMQ or Azure Service Bus
+- **API Gateway:** YARP or Ocelot
+- **Container Orchestration:** Docker Compose (dev) → Kubernetes (prod)
+- **Service Discovery:** Kubernetes DNS or Consul
+- **Distributed Tracing:** Already have OpenTelemetry — works across services
+- **Centralized Logging:** Already have Serilog + OTLP — works across services
+- **Per-Service Databases:** Multiple PostgreSQL instances (or schemas)
---
-## Observability
+## Risk Mitigation
-- **Tracing**: OpenTelemetry -> Alloy -> Tempo -> Grafana
-- **Metrics**: OpenTelemetry -> Alloy -> Prometheus -> Grafana
-- **Logging**: Serilog -> Console + OTLP -> Alloy -> Loki -> Grafana
-- **Instrumentation**: ASP.NET Core, HTTP Client, Npgsql, Wolverine, Runtime, Process
+| Risk | Mitigation |
+|------|------------|
+| Data inconsistency across services | Wolverine outbox pattern + idempotent handlers (already have `IIdempotencyStore`) |
+| Lost events | Durable messaging with RabbitMQ persistent queues + Wolverine dead letter queue |
+| Debugging complexity | OpenTelemetry distributed tracing (already configured) |
+| Service discovery failures | Kubernetes DNS + health checks (already have health check infrastructure) |
+| Database migration errors | Per-module DbContext in Phase 1 validates data boundaries before physical split |
+| GraphQL schema fragmentation | Hot Chocolate Federation maintains unified schema |
---
-## Key Design Decisions
+## Key Wolverine Advantages
+
+The current Wolverine setup makes this transition significantly easier:
-1. **EF Core (not Marten)** - existing codebase uses EF Core, no migration to event sourcing
-2. **REST only (no GraphQL)** - simplified API surface for microservices
-3. **Notifications = A-Frame pilot** - Wolverine HTTP Endpoints instead of ASP.NET Controllers
-4. **Other services = ASP.NET Controllers** - proven pattern, familiar to team
-5. **Database-per-service** - separate PostgreSQL databases via init script
-6. **SharedKernel DRY** - TenantAuditableDbContext base, shared service registration, common queue infrastructure
-7. **Saga for cascading deletes** - replaces synchronous SoftDeleteCascadeRules
-8. **ProductProjection read model** - Reviews stores denormalized product data, updated via events
+1. **Transport agnostic** — switch from in-process to RabbitMQ/Azure SB with config change, not code change
+2. **Built-in outbox** — guaranteed message delivery across service boundaries
+3. **Durable inbox** — idempotent message processing
+4. **Saga support** — orchestrate multi-service workflows
+5. **FluentValidation middleware** — works identically in monolith and microservices
+6. **Handler discovery** — static handlers work the same regardless of deployment topology
diff --git a/docker-compose.microservices.yml b/docker-compose.microservices.yml
deleted file mode 100644
index a47e70b4..00000000
--- a/docker-compose.microservices.yml
+++ /dev/null
@@ -1,302 +0,0 @@
-services:
- # Infrastructure
- rabbitmq:
- image: rabbitmq:4.2.5-management
- ports:
- - "5672:5672"
- - "15672:15672"
- healthcheck:
- test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- postgres:
- image: postgres:18.3
- environment:
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: postgres
- ports:
- - "5432:5432"
- volumes:
- - pgdata:/var/lib/postgresql/data
- - ./infrastructure/docker/init-microservices-databases.sql:/docker-entrypoint-initdb.d/init.sql
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U postgres"]
- interval: 5s
- timeout: 5s
- retries: 5
-
- mongodb:
- image: mongo:8.2.6-noble
- ports:
- - "27017:27017"
- volumes:
- - mongodata:/data/db
-
- dragonfly:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.37.2
- ports:
- - "6379:6379"
-
- mailpit:
- image: axllent/mailpit:v1.22
- ports:
- - "8025:8025"
- - "1025:1025"
-
- # Observability
- alloy:
- image: grafana/alloy:v1.11.0
- ports:
- - "4317:4317"
- - "4318:4318"
- - "12345:12345"
- volumes:
- - ./infrastructure/observability/alloy/config.alloy:/etc/alloy/config.alloy:ro
- command:
- - run
- - /etc/alloy/config.alloy
-
- prometheus:
- image: prom/prometheus:v2.55.1
- command:
- - --config.file=/etc/prometheus/prometheus.yml
- - --storage.tsdb.path=/prometheus
- - --web.enable-remote-write-receiver
- ports:
- - "9090:9090"
- volumes:
- - ./infrastructure/observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- - prometheusdata:/prometheus
-
- loki:
- image: grafana/loki:3.4.2
- command: ["-config.file=/etc/loki/config.yml"]
- ports:
- - "3100:3100"
- volumes:
- - ./infrastructure/observability/loki/config.yml:/etc/loki/config.yml:ro
- - lokidata:/loki
-
- tempo:
- image: grafana/tempo:2.6.1
- command: ["-config.file=/etc/tempo/config.yml"]
- ports:
- - "3200:3200"
- - "4319:4317"
- volumes:
- - ./infrastructure/observability/tempo/config.yml:/etc/tempo/config.yml:ro
- - tempodata:/var/tempo
-
- grafana:
- image: grafana/grafana:12.0.0
- environment:
- GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
- GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
- ports:
- - "3001:3000"
- depends_on:
- - prometheus
- - loki
- - tempo
- volumes:
- - ./infrastructure/observability/grafana/grafana.ini:/etc/grafana/grafana.ini:ro
- - ./infrastructure/observability/grafana/provisioning:/etc/grafana/provisioning:ro
- - ./infrastructure/observability/grafana/dashboards:/var/lib/grafana/dashboards:ro
- - grafanadata:/var/lib/grafana
-
- # Gateway
- gateway:
- build:
- context: .
- dockerfile: src/Gateway/Gateway.Api/Dockerfile
- ports:
- - "8080:8080"
- environment:
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- product-catalog:
- condition: service_healthy
- reviews:
- condition: service_healthy
- identity:
- condition: service_healthy
- notifications:
- condition: service_healthy
- file-storage:
- condition: service_healthy
- background-jobs:
- condition: service_healthy
- webhooks:
- condition: service_healthy
-
- # Services (each with own database)
- product-catalog:
- build:
- context: .
- dockerfile: src/Services/ProductCatalog/ProductCatalog.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=productcatalog_db;Username=postgres;Password=postgres"
- MongoDB__ConnectionString: "mongodb://mongodb:27017"
- MongoDB__DatabaseName: "productcatalog"
- RabbitMQ__HostName: rabbitmq
- Keycloak__AuthServerUrl: "http://keycloak:8180/"
- Keycloak__Realm: "api-template"
- Keycloak__Resource: "api-template"
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- mongodb:
- condition: service_started
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- reviews:
- build:
- context: .
- dockerfile: src/Services/Reviews/Reviews.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=reviews_db;Username=postgres;Password=postgres"
- RabbitMQ__HostName: rabbitmq
- Keycloak__AuthServerUrl: "http://keycloak:8180/"
- Keycloak__Realm: "api-template"
- Keycloak__Resource: "api-template"
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- identity:
- build:
- context: .
- dockerfile: src/Services/Identity/Identity.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=identity_db;Username=postgres;Password=postgres"
- RabbitMQ__HostName: rabbitmq
- Keycloak__AuthServerUrl: "http://keycloak:8180/"
- Keycloak__Realm: "api-template"
- Keycloak__Resource: "api-template"
- Keycloak__Credentials__Secret: "your-secret-here"
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- notifications:
- build:
- context: .
- dockerfile: src/Services/Notifications/Notifications.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=notifications_db;Username=postgres;Password=postgres"
- RabbitMQ__HostName: rabbitmq
- Email__SmtpHost: mailpit
- Email__SmtpPort: "1025"
- Email__UseSsl: "false"
- Email__SenderEmail: "noreply@api-template.local"
- Email__SenderName: "API Template"
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- mailpit:
- condition: service_started
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- file-storage:
- build:
- context: .
- dockerfile: src/Services/FileStorage/FileStorage.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=filestorage_db;Username=postgres;Password=postgres"
- RabbitMQ__HostName: rabbitmq
- FileStorage__BasePath: /app/storage
- Observability__Otlp__Endpoint: "http://alloy:4317"
- volumes:
- - filestorage:/app/storage
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- background-jobs:
- build:
- context: .
- dockerfile: src/Services/BackgroundJobs/BackgroundJobs.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=backgroundjobs_db;Username=postgres;Password=postgres"
- ConnectionStrings__Redis: "dragonfly:6379"
- RabbitMQ__HostName: rabbitmq
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- dragonfly:
- condition: service_started
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- webhooks:
- build:
- context: .
- dockerfile: src/Services/Webhooks/Webhooks.Api/Dockerfile
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=webhooks_db;Username=postgres;Password=postgres"
- RabbitMQ__HostName: rabbitmq
- Observability__Otlp__Endpoint: "http://alloy:4317"
- depends_on:
- postgres:
- condition: service_healthy
- rabbitmq:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 10s
- timeout: 5s
- retries: 5
-
-volumes:
- pgdata:
- mongodata:
- filestorage:
- prometheusdata:
- lokidata:
- tempodata:
- grafanadata:
diff --git a/docker-compose.production.yml b/docker-compose.production.yml
index cd42132a..81d8b064 100644
--- a/docker-compose.production.yml
+++ b/docker-compose.production.yml
@@ -1,6 +1,6 @@
services:
alloy:
- image: grafana/alloy:v1.11.0
+ image: grafana/alloy:v1.10.2
restart: unless-stopped
volumes:
- ./infrastructure/observability/alloy/config.alloy:/etc/alloy/config.alloy:ro
@@ -9,7 +9,7 @@ services:
- /etc/alloy/config.alloy
prometheus:
- image: prom/prometheus:v2.55.1
+ image: prom/prometheus:v3.4.2
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
@@ -21,7 +21,7 @@ services:
- prometheusdata:/prometheus
loki:
- image: grafana/loki:3.4.2
+ image: grafana/loki:3.5.5
restart: unless-stopped
command: ["-config.file=/etc/loki/config.yml"]
volumes:
@@ -29,7 +29,7 @@ services:
- lokidata:/loki
tempo:
- image: grafana/tempo:2.6.1
+ image: grafana/tempo:2.9.1
restart: unless-stopped
command: ["-config.file=/etc/tempo/config.yml"]
volumes:
@@ -37,7 +37,7 @@ services:
- tempodata:/var/tempo
grafana:
- image: grafana/grafana:12.0.0
+ image: grafana/grafana:12.3.1
restart: unless-stopped
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
@@ -54,7 +54,7 @@ services:
- grafanadata:/var/lib/grafana
postgres:
- image: postgres:18.3
+ image: postgres:17.2
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USERNAME}
@@ -69,7 +69,7 @@ services:
retries: 5
mongodb:
- image: mongo:8.2.6-noble
+ image: mongo:8.0.4
restart: unless-stopped
volumes:
- mongodata:/data/db
@@ -80,7 +80,7 @@ services:
retries: 5
keycloak-db:
- image: postgres:18.3
+ image: postgres:17.2
restart: unless-stopped
environment:
POSTGRES_USER: ${KC_DB_USERNAME}
@@ -95,7 +95,7 @@ services:
retries: 5
keycloak:
- image: quay.io/keycloak/keycloak:26.5.6
+ image: quay.io/keycloak/keycloak:26.1
restart: unless-stopped
command: start --optimized
environment:
@@ -119,7 +119,7 @@ services:
start_period: 60s
dragonfly-master:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.37.2
+ image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
restart: unless-stopped
volumes:
- dragonfly-master-data:/data
@@ -131,7 +131,7 @@ services:
retries: 5
dragonfly-replica:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.37.2
+ image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
restart: unless-stopped
volumes:
- dragonfly-replica-data:/data
@@ -146,7 +146,7 @@ services:
retries: 5
dragonfly-proxy:
- image: haproxy:3.3.6
+ image: haproxy:3.1-alpine
restart: unless-stopped
volumes:
- ./infrastructure/dragonfly/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
diff --git a/docker-compose.yml b/docker-compose.yml
index 8ccd5ce6..775ddd3b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,6 @@
services:
alloy:
- image: grafana/alloy:v1.11.0
+ image: grafana/alloy:v1.10.2
ports:
- "4317:4317"
- "4318:4318"
@@ -12,7 +12,7 @@ services:
- /etc/alloy/config.alloy
prometheus:
- image: prom/prometheus:v2.55.1
+ image: prom/prometheus:v3.4.2
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
@@ -25,7 +25,7 @@ services:
- prometheusdata:/prometheus
loki:
- image: grafana/loki:3.4.2
+ image: grafana/loki:3.5.5
command: ["-config.file=/etc/loki/config.yml"]
ports:
- "3100:3100"
@@ -34,7 +34,7 @@ services:
- lokidata:/loki
tempo:
- image: grafana/tempo:2.6.1
+ image: grafana/tempo:2.9.1
command: ["-config.file=/etc/tempo/config.yml"]
ports:
- "3200:3200"
@@ -44,7 +44,7 @@ services:
- tempodata:/var/tempo
grafana:
- image: grafana/grafana:12.0.0
+ image: grafana/grafana:12.3.1
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
@@ -62,7 +62,7 @@ services:
- grafanadata:/var/lib/grafana
aspire-dashboard:
- image: mcr.microsoft.com/dotnet/aspire-dashboard:9.1
+ image: mcr.microsoft.com/dotnet/aspire-dashboard:9.5
profiles: ["aspire"]
ports:
- "${ASPIRE_OTLP_GRPC_PORT:-4317}:18889"
@@ -89,7 +89,7 @@ services:
retries: 5
mongodb:
- image: mongo:8.2.6-noble
+ image: mongo:8.2
ports:
- "27017:27017"
volumes:
@@ -115,7 +115,7 @@ services:
retries: 5
keycloak:
- image: quay.io/keycloak/keycloak:26.5.6
+ image: quay.io/keycloak/keycloak:26.5
command: start-dev --import-realm
environment:
KC_DB: postgres
@@ -140,7 +140,7 @@ services:
start_period: 60s
dragonfly:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.37.2
+ image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
ports:
- "6379:6379"
volumes:
@@ -153,7 +153,7 @@ services:
retries: 5
mailpit:
- image: axllent/mailpit:v1.22
+ image: axllent/mailpit:v1.29.0
ports:
- "8025:8025"
- "1025:1025"
diff --git a/docs/Architecture-Microservices-Plan.md b/docs/Architecture-Microservices-Plan.md
deleted file mode 100644
index 1561b11b..00000000
--- a/docs/Architecture-Microservices-Plan.md
+++ /dev/null
@@ -1,710 +0,0 @@
-# Microservices Migration Plan: A-Frame + Wolverine + RabbitMQ + DDD
-
-## Context
-
-The API-Template project is a well-structured Clean Architecture monolith (.NET 10) with Wolverine as in-process message bus, EF Core + PostgreSQL + MongoDB, multi-tenancy, and 7 identified bounded contexts. The goal is to transform it into independent microservices using:
-
-- **A-Frame Architecture** (Jeremy D. Miller) with Wolverine as conductor
-- **Wolverine Sagas** for cross-service orchestration
-- **RabbitMQ** for inter-service messaging
-- **Clean Architecture + Vertical Slice + DDD** within each service
-- **YARP** API Gateway
-- **EF Core** persistence (no Marten)
-- **REST only** (GraphQL removed)
-- **Notifications service** as Wolverine HTTP Endpoints pilot (A-Frame)
-- Remaining services use ASP.NET Controllers
-
----
-
-## 1. Solution Structure
-
-```
-solution/
- src/
- SharedKernel/
- SharedKernel.Domain/ # Entity contracts, value objects, exceptions
- SharedKernel.Application/ # Cross-cutting: ErrorOr middleware, batch, DTOs
- SharedKernel.Infrastructure/ # Base DbContext, audit, soft-delete, UoW, repos
- SharedKernel.Messaging/ # Wolverine conventions, RabbitMQ topology, tenant propagation
- SharedKernel.Api/ # Base controller, ErrorOr mapping, auth, health checks
-
- Contracts/
- Contracts.IntegrationEvents/ # Integration event records ONLY (no domain logic)
-
- Services/
- ProductCatalog/
- ProductCatalog.Domain/
- ProductCatalog.Application/
- ProductCatalog.Infrastructure/
- ProductCatalog.Api/ # ASP.NET Controllers
- Reviews/
- Reviews.Domain/
- Reviews.Application/
- Reviews.Infrastructure/
- Reviews.Api/ # ASP.NET Controllers
- Identity/
- Identity.Domain/
- Identity.Application/
- Identity.Infrastructure/
- Identity.Api/ # ASP.NET Controllers
- Notifications/
- Notifications.Domain/
- Notifications.Application/
- Notifications.Infrastructure/
- Notifications.Api/ # Wolverine HTTP Endpoints (A-Frame pilot)
- FileStorage/
- FileStorage.Domain/
- FileStorage.Application/
- FileStorage.Infrastructure/
- FileStorage.Api/ # ASP.NET Controllers
- BackgroundJobs/
- BackgroundJobs.Domain/
- BackgroundJobs.Application/
- BackgroundJobs.Infrastructure/
- BackgroundJobs.Api/ # ASP.NET Controllers
- Webhooks/
- Webhooks.Domain/
- Webhooks.Application/
- Webhooks.Infrastructure/
- Webhooks.Api/ # ASP.NET Controllers
-
- Gateway/
- Gateway.Api/ # YARP reverse proxy
-
- tests/
- SharedKernel.Tests/
- ProductCatalog.Tests/
- Reviews.Tests/
- Identity.Tests/
- Notifications.Tests/
- FileStorage.Tests/
- BackgroundJobs.Tests/
- Webhooks.Tests/
- Integration.Tests/ # Cross-service E2E tests
-
- infrastructure/
- docker/
- observability/
-```
-
-### Project Reference Rules
-
-- **Domain** -> SharedKernel.Domain only
-- **Application** -> Domain, SharedKernel.Application, Contracts.IntegrationEvents
-- **Infrastructure** -> Application, Domain, SharedKernel.Infrastructure
-- **Api** -> Application, Infrastructure, SharedKernel.Api, SharedKernel.Messaging
-- **No service references another service's projects** - only Contracts.IntegrationEvents
-
----
-
-## 2. A-Frame Architecture Pattern (Per Service)
-
-Three functional areas in each handler:
-
-| Area | Role | Who |
-|------|------|-----|
-| **Load** (Infrastructure) | Fetch data, validate existence | Static `LoadAsync` method |
-| **Handle** (Domain Logic) | Pure business decisions | Static `Handle`/`HandleAsync` method |
-| **Conductor** | Orchestrates Load -> Handle -> side effects | Wolverine (generated) |
-
-### Example: Delete Products Handler (A-Frame)
-
-```csharp
-// ProductCatalog.Application/Features/Products/Commands/DeleteProducts/DeleteProductsHandler.cs
-public static class DeleteProductsHandler
-{
- // LOAD: Infrastructure concern
- public static async Task<(HandlerContinuation, IReadOnlyList?)> LoadAsync(
- DeleteProductsCommand command,
- IProductRepository repository,
- CancellationToken ct)
- {
- IReadOnlyList products = await repository.ListAsync(
- new ProductsByIdsSpec(command.Ids.ToHashSet()), ct);
- if (products.Count != command.Ids.Count)
- return (HandlerContinuation.Stop, null);
- return (HandlerContinuation.Continue, products);
- }
-
- // HANDLE: Pure domain logic - returns response + integration event
- public static (BatchResponse, ProductDeletedIntegrationEvent) Handle(
- DeleteProductsCommand command,
- IReadOnlyList products)
- {
- foreach (Product product in products)
- product.SoftDeleteProductDataLinks();
- return (
- new BatchResponse([], command.Ids.Count, 0),
- new ProductDeletedIntegrationEvent(products.Select(p => p.Id).ToList())
- );
- }
-}
-```
-
-### Vertical Slice within Clean Architecture
-
-Each feature is a vertical slice (co-located command, handler, validator, DTOs) inside the Application layer. Clean Architecture layers remain project boundaries enforcing dependency direction.
-
-```
-Application/Features/Products/Commands/CreateProducts/
- CreateProductsCommand.cs # Message record
- CreateProductsHandler.cs # A-Frame Load + Handle
- CreateProductsValidator.cs # FluentValidation
- CreateProductsResponse.cs # DTO
-```
-
----
-
-## 3. SharedKernel Contents
-
-### SharedKernel.Domain
-*Migrated from `APITemplate.Domain/Entities/Contracts/` and `APITemplate.Domain/Exceptions/`*
-
-- `IAuditableTenantEntity`, `IAuditableEntity`, `ISoftDeletable`, `IHasId`, `ITenantEntity`
-- `AuditInfo` value object, `AuditDefaults`
-- `PagedResponse`
-- `IRepository` base (Ardalis)
-- `IUnitOfWork`
-- Domain exceptions: `AppException`, `NotFoundException`, `ValidationException`, `ConflictException`
-
-### SharedKernel.Application
-*Migrated from `APITemplate.Application/Common/`*
-
-- `ITenantProvider`, `IActorProvider`
-- `BatchResponse`, `BatchFailureContext`, batch rules
-- `ErrorOrValidationMiddleware`
-- `ErrorCatalog` (general section)
-- Shared validation, sorting, search helpers
-
-### SharedKernel.Infrastructure
-*Migrated from `APITemplate.Infrastructure/Persistence/` and `APITemplate.Infrastructure/Repositories/`*
-
-- **`SharedDbContext`** base class with tenant filters, audit stamping, soft-delete processing
-- `AuditableEntityStateManager`
-- `ISoftDeleteCascadeRule`, `SoftDeleteProcessor`
-- `UnitOfWork`, `EfCoreTransactionProvider`, `ManagedTransactionScope`
-- `RepositoryBase`
-- Pagination helpers
-- `IIdempotencyStore`, `DistributedCacheIdempotencyStore`
-- OpenTelemetry + Serilog registration helpers
-
-### SharedKernel.Messaging
-- Wolverine convention registration (handler discovery, middleware policy)
-- RabbitMQ topology conventions (exchange/queue naming)
-- `TenantAwareEnvelopeMapper` (propagates TenantId via `x-tenant-id` header)
-- Outbox/inbox policy registration
-- Retry/error handling policy defaults
-
-### SharedKernel.Api
-- `ApiControllerBase` with ErrorOr helpers
-- `ToActionResult`, `ToBatchResult` extensions
-- Global `ProblemDetails` exception handler
-- `RequirePermissionAttribute`, Permission constants
-- Idempotency filter
-- Health check registration
-- Rate limiting, CORS, OpenAPI helpers
-
----
-
-## 4. Integration Events (Contracts.IntegrationEvents)
-
-```csharp
-// Identity events
-public sealed record UserRegisteredIntegrationEvent(Guid UserId, Guid TenantId, string Email, string Username, DateTime OccurredAtUtc);
-public sealed record UserRoleChangedIntegrationEvent(Guid UserId, Guid TenantId, string Email, string Username, string OldRole, string NewRole, DateTime OccurredAtUtc);
-public sealed record TenantDeactivatedIntegrationEvent(Guid CorrelationId, Guid TenantId, Guid ActorId, DateTime OccurredAtUtc);
-public sealed record TenantInvitationCreatedIntegrationEvent(Guid InvitationId, string Email, string TenantName, string Token, DateTime OccurredAtUtc);
-
-// Product Catalog events
-public sealed record ProductCreatedIntegrationEvent(Guid ProductId, Guid TenantId, string Name, DateTime OccurredAtUtc);
-public sealed record ProductDeletedIntegrationEvent(IReadOnlyList ProductIds, Guid TenantId, DateTime OccurredAtUtc);
-public sealed record CategoryDeletedIntegrationEvent(Guid CategoryId, Guid TenantId, DateTime OccurredAtUtc);
-
-// Reviews events
-public sealed record ReviewCreatedIntegrationEvent(Guid ReviewId, Guid ProductId, Guid UserId, Guid TenantId, int Rating, DateTime OccurredAtUtc);
-
-// Saga responses
-public sealed record ReviewsCascadeCompleted(Guid CorrelationId, int DeletedCount);
-public sealed record FilesCascadeCompleted(Guid CorrelationId, int DeletedCount);
-public sealed record ProductsCascadeCompleted(Guid CorrelationId, Guid TenantId, int DeletedCount);
-public sealed record UsersCascadeCompleted(Guid CorrelationId, Guid TenantId, int DeactivatedCount);
-public sealed record CategoriesCascadeCompleted(Guid CorrelationId, Guid TenantId, int DeletedCount);
-```
-
----
-
-## 5. RabbitMQ Messaging Topology
-
-### Exchanges (topic, durable)
-
-| Exchange | Publisher | Routing Keys |
-|----------|-----------|-------------|
-| `identity.events` | Identity | `user.registered`, `user.role-changed`, `tenant.deactivated`, `tenant.invitation.created` |
-| `product-catalog.events` | Product Catalog | `product.created`, `product.deleted`, `category.deleted` |
-| `reviews.events` | Reviews | `review.created` |
-
-### Queue Bindings
-
-| Queue | Exchange | Routing Key | Consumer |
-|-------|----------|------------|----------|
-| `reviews.product-deleted` | product-catalog.events | `product.deleted` | Reviews |
-| `reviews.tenant-deactivated` | identity.events | `tenant.deactivated` | Reviews |
-| `file-storage.product-deleted` | product-catalog.events | `product.deleted` | File Storage |
-| `notifications.user-registered` | identity.events | `user.registered` | Notifications |
-| `notifications.user-role-changed` | identity.events | `user.role-changed` | Notifications |
-| `notifications.invitation-created` | identity.events | `tenant.invitation.*` | Notifications |
-| `product-catalog.tenant-deactivated` | identity.events | `tenant.deactivated` | Product Catalog |
-| `webhooks.product-created` | product-catalog.events | `product.created` | Webhooks |
-| `background-jobs.tenant-deactivated` | identity.events | `tenant.deactivated` | Background Jobs |
-
-### Dead Letter
-
-All queues: `x-dead-letter-exchange: dlx.default` -> `dlq.default`
-
-### Wolverine RabbitMQ Config (per service)
-
-```csharp
-opts.UseRabbitMq(rabbit => { rabbit.HostName = "rabbitmq"; })
- .AutoProvision()
- .UseConventionalRouting();
-opts.PersistMessagesWithPostgresql(connectionString); // Outbox in service DB
-opts.Policies.UseDurableInboxOnAllListeners();
-opts.Policies.UseDurableOutboxOnAllSendingEndpoints();
-```
-
-### Required NuGet Packages per Service
-
-```xml
-
-
-
-
-
-```
-
----
-
-## 6. Wolverine Saga Patterns
-
-### 6.1 Product Deletion Cascade Saga (hosted in Product Catalog)
-
-Replaces `ProductSoftDeleteCascadeRule` which currently queries Reviews + ProductDataLinks in same DbContext.
-
-```csharp
-public class ProductDeletionSaga : Saga
-{
- public string? Id { get; set; }
- public IReadOnlyList ProductIds { get; set; } = [];
- public Guid TenantId { get; set; }
- public bool ReviewsCascaded { get; set; }
- public bool FilesCascaded { get; set; }
-
- public static (ProductDeletionSaga, SagaTimeout) Start(StartProductDeletionSaga command)
- {
- return (new ProductDeletionSaga
- {
- Id = command.CorrelationId.ToString(),
- ProductIds = command.ProductIds,
- TenantId = command.TenantId
- }, new SagaTimeout(command.CorrelationId));
- }
-
- public void Handle(ReviewsCascadeCompleted _) { ReviewsCascaded = true; TryComplete(); }
- public void Handle(FilesCascadeCompleted _) { FilesCascaded = true; TryComplete(); }
- public void Handle(SagaTimeout _) => MarkCompleted(); // Accept partial on timeout
-
- private void TryComplete()
- {
- if (ReviewsCascaded && FilesCascaded) MarkCompleted();
- }
-}
-```
-
-### 6.2 Tenant Deactivation Cascade Saga (hosted in Identity)
-
-Replaces `TenantSoftDeleteCascadeRule` which queries Users, Products, Categories in same DbContext.
-
-```csharp
-public class TenantDeactivationSaga : Saga
-{
- public string? Id { get; set; }
- public Guid TenantId { get; set; }
- public bool UsersCascaded { get; set; }
- public bool ProductsCascaded { get; set; }
- public bool CategoriesCascaded { get; set; }
-
- public static (TenantDeactivationSaga, SagaTimeout) Start(StartTenantDeactivationSaga command)
- => (new TenantDeactivationSaga { Id = command.CorrelationId.ToString(), TenantId = command.TenantId },
- new SagaTimeout(command.CorrelationId));
-
- public void Handle(UsersCascadeCompleted _) { UsersCascaded = true; TryComplete(); }
- public void Handle(ProductsCascadeCompleted _) { ProductsCascaded = true; TryComplete(); }
- public void Handle(CategoriesCascadeCompleted _) { CategoriesCascaded = true; TryComplete(); }
- public void Handle(SagaTimeout _) => MarkCompleted();
-
- private void TryComplete()
- {
- if (UsersCascaded && ProductsCascaded && CategoriesCascaded) MarkCompleted();
- }
-}
-```
-
-### 6.3 Choreography (no saga needed)
-
-| Workflow | Pattern | Why |
-|----------|---------|-----|
-| User registration -> welcome email | Choreography | Single consumer, fire-and-forget |
-| Tenant invitation -> invitation email | Choreography | Single consumer, fire-and-forget |
-| Role change -> notification | Choreography | Single consumer, fire-and-forget |
-| Review creation -> product validation | Local read projection | Reviews stores `ProductProjection` table updated via events |
-
-### Saga persistence
-
-Two verified options:
-- **Option A (Recommended)**: `WolverineFx.EntityFrameworkCore` - saga state mapped as entity in service's DbContext. Full EF Core integration, same transaction as business data.
-- **Option B**: `WolverineFx.Postgresql` lightweight saga storage - JSON serialized saga state in auto-created `{SagaName}_saga` tables. No DbContext mapping needed.
-
----
-
-## 7. Notifications Service (A-Frame Pilot - Wolverine HTTP Endpoints)
-
-### Domain
-
-```
-Notifications.Domain/
- Entities/FailedEmail.cs # Migrated
- Interfaces/IFailedEmailRepository.cs
-```
-
-### Application (Wolverine HTTP Endpoints)
-
-```csharp
-// GET /api/v1/notifications/failed-emails
-public static class GetFailedEmailsEndpoint
-{
- [WolverineGet("/api/v1/notifications/failed-emails")]
- public static async Task> HandleAsync(
- [FromQuery] PaginationFilter filter,
- IFailedEmailRepository repository,
- CancellationToken ct)
- {
- return await repository.GetPagedAsync(/* ... */);
- }
-}
-
-// POST /api/v1/notifications/failed-emails/{id}/retry (A-Frame Load + Handle)
-public static class RetryFailedEmailEndpoint
-{
- // LOAD
- public static async Task<(ProblemDetails?, FailedEmail?)> LoadAsync(
- [FromRoute] Guid id,
- IFailedEmailRepository repository,
- CancellationToken ct)
- {
- FailedEmail? email = await repository.GetByIdAsync(id, ct);
- return email is null
- ? (new ProblemDetails { Status = 404, Detail = $"Email {id} not found" }, null)
- : (WolverineContinue.NoProblems, email);
- }
-
- // HANDLE
- [WolverinePost("/api/v1/notifications/failed-emails/{id}/retry")]
- public static RetryEmailCommand Handle([FromRoute] Guid id, FailedEmail email)
- {
- return new RetryEmailCommand(email.Id, email.To, email.Subject, email.HtmlBody);
- }
-}
-```
-
-### Event Consumers (from RabbitMQ)
-
-```csharp
-public static class UserRegisteredNotificationHandler
-{
- public static async Task HandleAsync(
- UserRegisteredIntegrationEvent @event,
- IEmailTemplateRenderer renderer,
- IEmailQueue queue,
- CancellationToken ct)
- {
- string html = await renderer.RenderAsync("user-registration", new { @event.Username }, ct);
- await queue.EnqueueAsync(new EmailMessage(@event.Email, "Welcome!", html), ct);
- }
-}
-```
-
-### Infrastructure
-
-Migrated from monolith: `MailKit` sender, `Fluid` template renderer, email queue, failed email store, `NotificationsDbContext`.
-
-### Program.cs
-
-```csharp
-builder.Host.UseWolverine(opts =>
-{
- opts.Discovery.IncludeAssembly(typeof(UserRegisteredNotificationHandler).Assembly);
- opts.UseRabbitMq(r => { r.HostName = "rabbitmq"; }).AutoProvision().UseConventionalRouting();
- opts.PersistMessagesWithPostgresql(connectionString);
- opts.Policies.UseDurableInboxOnAllListeners();
- opts.ListenToRabbitQueue("notifications.user-registered");
- opts.ListenToRabbitQueue("notifications.user-role-changed");
- opts.ListenToRabbitQueue("notifications.invitation-created");
-});
-app.MapWolverineEndpoints(); // No MapControllers
-```
-
----
-
-## 8. YARP API Gateway
-
-### Gateway.Api/Program.cs
-
-```csharp
-builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
-app.UseRateLimiter();
-app.MapReverseProxy();
-app.MapHealthChecks("/health");
-```
-
-### Routes (appsettings.json)
-
-| Route Pattern | Cluster |
-|---------------|---------|
-| `/api/v1/products/**` | product-catalog:8080 |
-| `/api/v1/categories/**` | product-catalog:8080 |
-| `/api/v1/productreviews/**` | reviews:8080 |
-| `/api/v1/users/**`, `/api/v1/tenants/**`, `/api/v1/tenantinvitations/**` | identity:8080 |
-| `/api/v1/notifications/**` | notifications:8080 |
-| `/api/v1/files/**` | file-storage:8080 |
-| `/api/v1/jobs/**` | background-jobs:8080 |
-| `/api/v1/webhooks/**` | webhooks:8080 |
-
-Gateway handles: routing, rate limiting, CORS, correlation IDs, health aggregation. No business logic.
-
----
-
-## 9. Database-per-Service
-
-| Service | Database | Special |
-|---------|----------|---------|
-| Product Catalog | `productcatalog_db` | + MongoDB for ProductData |
-| Reviews | `reviews_db` | + `ProductProjection` read model |
-| Identity | `identity_db` | + Keycloak |
-| Notifications | `notifications_db` | |
-| File Storage | `filestorage_db` | + filesystem/S3 |
-| Background Jobs | `backgroundjobs_db` | + TickerQ tables |
-| Webhooks | `webhooks_db` | |
-
-Each DB also contains Wolverine envelope tables (outbox/inbox) via `WolverineFx.EntityFrameworkCore`.
-
-Reviews service maintains `ProductProjection(ProductId, TenantId, Name, IsActive)` updated via `ProductCreatedIntegrationEvent` / `ProductDeletedIntegrationEvent` - replaces direct FK to Products.
-
----
-
-## 10. Error Handling & Retry
-
-```csharp
-// Per-service Wolverine config
-opts.Policies.Failures
- .Handle()
- .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds())
- .Then.MoveToErrorQueue();
-
-opts.Policies.Failures
- .Handle()
- .RetryWithCooldown(100.Milliseconds(), 500.Milliseconds())
- .Then.MoveToErrorQueue();
-
-opts.Policies.Failures
- .Handle()
- .ScheduleRetry(5.Seconds(), 30.Seconds(), 2.Minutes())
- .Then.MoveToErrorQueue();
-```
-
-- **Idempotency**: Wolverine durable inbox (dedup by envelope ID) + existing `IIdempotencyStore` for non-naturally-idempotent ops
-- **Saga timeouts**: 5-minute `SagaTimeout` message, accept partial completion
-- **Dead letters**: `dlx.default` -> `dlq.default`, Wolverine `wolverine_dead_letters` table
-
----
-
-## 11. Docker Compose
-
-New services added to existing compose:
-
-```yaml
-services:
- rabbitmq:
- image: rabbitmq:4-management
- ports: ["5672:5672", "15672:15672"]
-
- postgres: # Shared instance, init.sql creates per-service databases
- volumes:
- - ./infrastructure/docker/init-databases.sql:/docker-entrypoint-initdb.d/init.sql
-
- gateway:
- build: { dockerfile: src/Gateway/Gateway.Api/Dockerfile }
- ports: ["8080:8080"]
-
- product-catalog:
- build: { dockerfile: src/Services/ProductCatalog/ProductCatalog.Api/Dockerfile }
- environment:
- ConnectionStrings__DefaultConnection: "Host=postgres;Database=productcatalog_db;..."
- RabbitMQ__HostName: rabbitmq
-
- reviews:
- build: { dockerfile: src/Services/Reviews/Reviews.Api/Dockerfile }
- identity:
- build: { dockerfile: src/Services/Identity/Identity.Api/Dockerfile }
- notifications:
- build: { dockerfile: src/Services/Notifications/Notifications.Api/Dockerfile }
- file-storage:
- build: { dockerfile: src/Services/FileStorage/FileStorage.Api/Dockerfile }
- background-jobs:
- build: { dockerfile: src/Services/BackgroundJobs/BackgroundJobs.Api/Dockerfile }
- webhooks:
- build: { dockerfile: src/Services/Webhooks/Webhooks.Api/Dockerfile }
-
- # Existing: mongodb, keycloak, dragonfly, mailpit, alloy, prometheus, loki, tempo, grafana
-```
-
----
-
-## 12. Feasibility Assessment (Verified Against Docs + Codebase)
-
-### Required New NuGet Packages
-
-| Package | Purpose | Verified |
-|---------|---------|----------|
-| `WolverineFx.RabbitMQ` | RabbitMQ transport | Yes - `UseRabbitMq()`, `AutoProvision()`, `UseConventionalRouting()` confirmed in docs |
-| `WolverineFx.Http` | Wolverine HTTP Endpoints | Yes - `[WolverineGet]`, `[WolverinePost]`, `MapWolverineEndpoints()` confirmed in docs |
-| `WolverineFx.EntityFrameworkCore` | EF Core integration + saga persistence | Yes - `UseEntityFrameworkCoreTransactions()`, saga state via DbContext mapping confirmed |
-| `Yarp.ReverseProxy` | API Gateway | Yes - Microsoft official package, stable |
-
-### Current Packages (already referenced in `Directory.Packages.props`)
-
-- `WolverineFx` v5.22.0
-- `WolverineFx.FluentValidation` v5.22.0
-
-### Component Feasibility
-
-| Component | Feasibility | Verification Source | Details |
-|-----------|------------|---------------------|---------|
-| **A-Frame with Wolverine** | **HIGH** | Codebase + Jeremy D. Miller blog | Monolith already uses static Wolverine handlers (`CreateProductsCommandHandler`, `CacheInvalidationHandler`). Load/Handle separation is a naming/structure convention on top of existing pattern. No new library needed. |
-| **Wolverine HTTP Endpoints** | **HIGH** | wolverine.netlify.app/guide/http/ | `[WolverineGet]`/`[WolverinePost]` documented with full Load/Handle pattern. ProblemDetails natively supported. `WolverineHttp` adds `AddWolverineHttp()` + `MapWolverineEndpoints()`. Pilot only (Notifications). |
-| **Wolverine Sagas with EF Core** | **HIGH** | wolverine.netlify.app/guide/durability/sagas + /efcore | Saga persistence supports EF Core, PostgreSQL, SQL Server, SQLite, MySQL, RavenDb, CosmosDB, Oracle. For EF Core: DbContext must have mapping for saga state entity. `WolverineFx.EntityFrameworkCore` package. Lightweight saga storage (JSON in DB) also available via `WolverineFx.Postgresql`. |
-| **RabbitMQ Transport** | **HIGH** | wolverine.netlify.app/guide/messaging/transports/rabbitmq | `UseRabbitMq(Uri)`, `UseRabbitMqUsingNamedConnection()`, `AutoProvision()`, `ListenToRabbitQueue()`, `PublishMessage().ToRabbitExchange()`. Channel config, control queues, partitioning all documented. |
-| **Outbox with EF Core (no Marten)** | **HIGH** | wolverine.netlify.app/guide/durability/efcore | `WolverineFx.EntityFrameworkCore` provides outbox integrated with EF Core's `SaveChangesAsync()`. Wolverine "both calls DbContext.SaveChangesAsync() and flushes persisted messages" in same transaction. Limitation: only one database registration per outbox. |
-| **Tenant Propagation via RabbitMQ** | **HIGH** | Wolverine docs (envelope mapper) | Custom `IRabbitMqEnvelopeMapper` allows header injection/extraction. TenantId via `x-tenant-id` header. |
-| **YARP Gateway** | **HIGH** | Microsoft official | Config-driven reverse proxy. Production-ready, actively maintained. |
-| **SharedKernel extraction** | **HIGH** | Codebase analysis | All shared code exists in monolith under well-defined paths. Extraction = move files + update project references. |
-| **Database-per-service** | **MEDIUM** | Architectural pattern | Fresh Code-First migrations per service. Reviews FK->Products replaced by `ProductProjection` read model (eventual consistency). Risk: data migration scripts needed for existing data. |
-| **UnitOfWork + Wolverine outbox** | **MEDIUM** | Codebase + Wolverine docs | Current `UnitOfWork` uses `DbContext.Database.BeginTransactionAsync()`. Wolverine's EF Core middleware manages its own transaction. Rule: handlers publishing events use Wolverine transaction; internal-only handlers use custom UnitOfWork. No conflict if separated. |
-| **TickerQ per-service** | **MEDIUM** | Codebase (TickerQ v10.2.2) | Each service needing schedules embeds own TickerQ instance. Background Jobs service centralizes cross-cutting jobs only. |
-| **GraphQL removal** | **HIGH** | Greenfield project | Remove HotChocolate 15.1 packages + delete `Api/GraphQL/` folder. No legacy clients to break. |
-
-### Potential Blockers
-
-| Blocker | Severity | Resolution |
-|---------|----------|------------|
-| Wolverine outbox supports only 1 DB registration | LOW | Each microservice has exactly 1 database - not an issue |
-| Saga state serialization for complex types (`IReadOnlyList`) | LOW | Wolverine uses System.Text.Json for lightweight saga storage - works with standard types |
-| `CritterStackDefaults` with `TypeLoadMode.Static` requires code generation | LOW | Pre-generate Wolverine code per service at build time (`dotnet build` generates) |
-| Current `MessageBusExtensions.PublishSafeAsync` swallows errors | LOW | Replace with outbox pattern - errors handled by retry/dead-letter, not swallowed |
-
----
-
-## 13. Risk Analysis
-
-| Risk | Impact | Mitigation |
-|------|--------|------------|
-| Data inconsistency during saga | HIGH | Wolverine durable outbox (at-least-once) + idempotent handlers + saga timeouts |
-| Operational complexity increase | HIGH | Shared observability (OpenTelemetry + Grafana already configured), Docker Compose for dev |
-| Reviews needs Product data | MEDIUM | `ProductProjection` read model updated via integration events. Eventual consistency acceptable. |
-| Two transaction managers | MEDIUM | Clear rule: outbox-publishing handlers use Wolverine transaction, internal handlers use UnitOfWork |
-| Migration effort | HIGH | Phased approach - Notifications first validates patterns before committing all services |
-| Network partitions | MEDIUM | Durable outbox persists locally, delivered when RabbitMQ recovers |
-
----
-
-## 14. Implementation Phases
-
-### Phase 1: Foundation
-1. Create solution structure with all project shells
-2. Extract SharedKernel from monolith
-3. Create Contracts.IntegrationEvents
-4. Set up Gateway.Api with YARP
-
-### Phase 2: Notifications Service (A-Frame Pilot)
-5. Create Notifications service with Wolverine HTTP Endpoints
-6. Migrate email infrastructure (MailKit, Fluid, queue, templates)
-7. Configure RabbitMQ transport + outbox
-8. E2E test: Identity -> RabbitMQ -> Notifications -> email sent
-
-### Phase 3: Identity & Tenancy
-9. Create Identity service with own DbContext
-10. Migrate Keycloak integration
-11. Implement TenantDeactivation saga
-12. Publish integration events for user/tenant lifecycle
-
-### Phase 4: File Storage + Webhooks
-13. Create File Storage service (simple CRUD)
-14. Create Webhooks service (HMAC validation, delivery)
-
-### Phase 5: Reviews
-15. Create Reviews service with own DbContext
-16. Add ProductProjection read model
-17. Handle cascade via ProductDeletedIntegrationEvent
-
-### Phase 6: Product Catalog
-18. Create Product Catalog service (core domain, last extracted)
-19. Own DbContext + MongoDB connection
-20. Implement ProductDeletion saga
-
-### Phase 7: Background Jobs
-21. Create Background Jobs service with TickerQ
-22. Centralized scheduling for cross-service jobs
-
-### Phase 8: Cleanup
-23. Remove old monolith projects
-24. Remove GraphQL (HotChocolate)
-25. E2E integration tests through YARP gateway
-26. Load testing
-
----
-
-## 15. Key Files to Modify/Migrate
-
-| Source (Monolith) | Target |
-|-------------------|--------|
-| `src/APITemplate.Domain/Entities/Contracts/` | SharedKernel.Domain |
-| `src/APITemplate.Application/Common/Middleware/ErrorOrValidationMiddleware.cs` | SharedKernel.Application |
-| `src/APITemplate.Application/Common/Events/EmailEvents.cs` | Contracts.IntegrationEvents (expanded) |
-| `src/APITemplate.Application/Common/Events/SoftDeleteEvents.cs` | Contracts.IntegrationEvents (expanded) |
-| `src/APITemplate.Infrastructure/Persistence/AppDbContext.cs` | Decomposed into per-service DbContexts inheriting SharedDbContext |
-| `src/APITemplate.Infrastructure/Persistence/SoftDelete/ProductSoftDeleteCascadeRule.cs` | Replaced by ProductDeletionSaga |
-| `src/APITemplate.Infrastructure/Persistence/SoftDelete/TenantSoftDeleteCascadeRule.cs` | Replaced by TenantDeactivationSaga |
-| `src/APITemplate.Infrastructure/Repositories/RepositoryBase.cs` | SharedKernel.Infrastructure |
-| `src/APITemplate.Infrastructure/Persistence/UnitOfWork/` | SharedKernel.Infrastructure |
-| `src/APITemplate.Infrastructure/Persistence/Auditing/` | SharedKernel.Infrastructure |
-| `src/APITemplate.Infrastructure/Email/` | Notifications.Infrastructure |
-| `src/APITemplate.Api/Api/Controllers/V1/` | Per-service Api projects |
-| `src/APITemplate.Api/Api/GraphQL/` | **DELETED** |
-| `src/APITemplate.Api/Program.cs` | Per-service Program.cs + Gateway Program.cs |
-
----
-
-## 16. Verification Plan
-
-1. **Unit tests**: A-Frame Handle methods are pure functions -> test with simple assertions
-2. **Integration tests per service**: `WebApplicationFactory` + Testcontainers (PostgreSQL + RabbitMQ)
-3. **Cross-service E2E**: Docker Compose up all services + Gateway, run Saga scenarios
-4. **Specific scenarios to verify**:
- - Product deletion -> Reviews cascade via saga -> confirmation
- - Tenant deactivation -> all services cascade -> saga completion
- - User registration -> Notifications receives event -> email sent
- - YARP routes all endpoints correctly
- - Tenant propagation via RabbitMQ headers
- - Outbox delivery after RabbitMQ recovery
- - Dead letter handling on repeated failures
-5. **Build**: `dotnet build` all projects, ensure no cross-service references
-6. **Existing tests**: Adapt to per-service structure, ensure parity
diff --git a/docs/monolith-migration-gaps.md b/docs/monolith-migration-gaps.md
deleted file mode 100644
index 6f63ba35..00000000
--- a/docs/monolith-migration-gaps.md
+++ /dev/null
@@ -1,232 +0,0 @@
-# Monolith -> Microservices Migration Gaps
-
-What the old monolith (APITemplate.*) had that microservices still need, including how each feature was implemented.
-
-**Legend:** ❌ MISSING — ⚠️ PARTIAL — ✅ IMPLEMENTED
-
-## Status Overview
-
-| # | Gap | Status |
-|---|-----|--------|
-| 1 | CORS | ❌ MISSING |
-| 2 | Rate Limiting | ❌ MISSING |
-| 3 | CSRF Validation | ⚠️ PARTIAL — constants defined, middleware absent |
-| 4 | Request Context Middleware | ❌ MISSING |
-| 5 | Serilog Request Logging Pipeline | ⚠️ PARTIAL — basic `UseSharedSerilog`, no full pipeline |
-| 6 | Keycloak Readiness Check | ❌ MISSING |
-| 7 | Distributed BFF Session Persistence | ❌ MISSING |
-| 8 | GraphQL | ❌ MISSING |
-| 9 | Observability / Telemetry | ⚠️ PARTIAL — generic OTEL only, no domain telemetry |
-| 10 | Inbound Webhook Processing | ⚠️ PARTIAL — signer only, no validator/filter/queue |
-| 11 | SSE Streaming Endpoint | ❌ MISSING |
-| 12 | JSON Patch Endpoint | ❌ MISSING |
-| 13 | Idempotent Endpoint demo | ⚠️ PARTIAL — infra in SharedKernel, no service uses `[Idempotent]` |
-| 14 | Email Retry Background Job | ⚠️ PARTIAL — `FailedEmail` entity/repo exists, no retry job/service |
-| 15 | Auth Bootstrap Seeder | ⚠️ PARTIAL — `BootstrapTenantOptions` exists, no seeder implementation |
-| 16 | Startup Task Coordinator | ❌ MISSING |
-| 17 | SoftDeleteCascadeRule Implementations | ⚠️ PARTIAL — interface in SharedKernel, no implementations |
-| 18 | Entity Normalization Service | ⚠️ PARTIAL — interface in SharedKernel, no implementation |
-| 19 | MongoDB Migration Support | ❌ MISSING |
-| 20 | MongoDB Health Check | ❌ MISSING |
-| 21 | Log Redaction / PII Classification | ❌ MISSING |
-| 22 | External Integration Sync Job | ❌ MISSING |
-| 23 | BackgroundJobs Options Validator | ⚠️ PARTIAL — `[ValidateObjectMembers]` annotations, no `IValidateOptions` impl |
-| 24 | Expired Invitation Cleanup | ❌ MISSING |
-| 25 | Cross-Service Soft-Delete Purge | ❌ MISSING |
-| 26 | Orphaned MongoDB ProductData Cleanup | ❌ MISSING |
-
----
-
-## High Impact - Core Cross-Cutting Concerns
-
-### 1. CORS — ❌ MISSING
-- No CORS config in any microservice or Gateway
-- **Recommendation:** Configure at Gateway level (YARP)
-- **How it was implemented:**
- Default CORS policy in `AuthenticationServiceCollectionExtensions.AddAuthenticationOptions()`. Options class `CorsOptions` with `string[] AllowedOrigins` bound from `Cors:AllowedOrigins`. Policy: `WithOrigins(...).AllowAnyHeader().AllowAnyMethod().AllowCredentials()`. Applied via `app.UseCors()` inside `UseSecurityPipeline()`.
-
-### 2. Rate Limiting — ❌ MISSING
-- No rate limiting in any microservice
-- **Recommendation:** Add to Gateway or per-service via SharedKernel
-- **How it was implemented:**
- Fixed-window per-client rate limiter in `ApiServiceCollectionExtensions.AddRateLimiting()`. Options class `RateLimitingOptions` (`PermitLimit=100`, `WindowMinutes=1`) bound from `RateLimiting:Fixed`. Partition key: JWT `Identity.Name` > `RemoteIpAddress` > `"anonymous"`. Used `IConfigureOptions` for deferred resolution. Recorded rejections via `ApiMetrics.RecordRateLimitRejection()`. Applied with `app.UseRateLimiter()` and `.RequireRateLimiting()` on `MapControllers()`/`MapGraphQL()`.
-
-### 3. CSRF Validation — ⚠️ PARTIAL
-- `AuthConstants.Csrf` constants (`X-CSRF` header name/value) are defined in Identity service, but `CsrfValidationMiddleware` is not implemented
-- **Recommendation:** Add middleware to Identity.Api pipeline
-- **How it was implemented:**
- Custom `CsrfValidationMiddleware` (primary constructor with `RequestDelegate` + `IProblemDetailsService`). Skipped safe methods (GET/HEAD/OPTIONS) and Bearer-authenticated requests. For cookie-authenticated mutations, required `X-CSRF: 1` header (constants in `AuthConstants.Csrf.HeaderName`/`HeaderValue`). Returned RFC 7807 ProblemDetails 403 on failure. Ran after `UseAuthentication()`, before `UseAuthorization()`.
-
-### 4. Request Context Middleware — ❌ MISSING
-- Not present in SharedKernel pipeline or any service
-- **Recommendation:** Add to SharedKernel.Api pipeline
-- **How it was implemented:**
- `RequestContextMiddleware` resolved correlation ID from `X-Correlation-Id` header (fallback: `TraceIdentifier`). Emitted `X-Correlation-Id`, `X-Trace-Id`, `X-Elapsed-Ms` response headers. Enriched Serilog via `LogContext.PushProperty` (CorrelationId, TenantId). Tagged `IHttpMetricsTagsFeature` with `api.surface` and `authenticated`. Constants in `RequestContextConstants`.
-
-### 5. Serilog Request Logging Pipeline — ⚠️ PARTIAL
-- `SharedKernel.Api/Extensions/SerilogExtensions.cs` provides basic `UseSharedSerilog()` used by all services, but the full request-level pipeline is absent
-- Missing: `UseSerilogRequestLogging()` with custom level logic, `ActivityTraceEnricher`, `AddApplicationRedaction()`
-- **Recommendation:** Enhance SharedKernel Serilog setup
-- **How it was implemented:**
- `UseRequestContextPipeline()` chained `UseMiddleware()` then `UseSerilogRequestLogging()`. Custom `GetLevel`: client-aborted = Info, 500+ = Error, 400+ = Warning, else Info. Enriched `DiagnosticContext` with `RequestHost`/`RequestScheme`. Also used `ActivityTraceEnricher` for trace/span ID enrichment and `AddApplicationRedaction()` for PII masking (HMAC for sensitive data, erasing for personal). Serilog sinks included `Serilog.Sinks.OpenTelemetry` with gRPC protocol.
-
-### 6. Keycloak Readiness Check — ❌ MISSING
-- No microservice waits for Keycloak at startup
-- **Recommendation:** Add to services that depend on Keycloak (Identity, any with JWT)
-- **How it was implemented:**
- `WaitForKeycloakAsync()` extension on `WebApplication`. Read `KeycloakOptions` (AuthServerUrl, Realm, SkipReadinessCheck). Built OIDC discovery URL via `KeycloakUrlHelper.BuildDiscoveryUrl()`. Used Polly `ResiliencePipeline` keyed `ResiliencePipelineKeys.KeycloakReadiness` for retry. HTTP GET to discovery endpoint with 5s timeout. Threw `InvalidOperationException` after `KeycloakOptions.ReadinessMaxRetries` retries. Wrapped in `StartupTelemetry.WaitForKeycloakReadinessAsync()`.
-
-### 7. Distributed BFF Session Persistence — ❌ MISSING
-- Identity has BFF cookie auth but no distributed session store — cookie tickets are in-memory only, lost on pod restart
-- **Recommendation:** Add to Identity.Api
-- **How it was implemented:**
- **ValkeyTicketStore** (was DragonflyTicketStore): implemented `ITicketStore`. Stored ASP.NET Core `AuthenticationTicket` in `IDistributedCache` (Redis/Valkey). Key prefix: `"bff:ticket:"` + GUID. Encrypted ticket bytes via `IDataProtector` (purpose: `"bff:ticket"`). TTL from `BffOptions.SessionTimeoutMinutes`. Handled `CryptographicException` gracefully on retrieve.
- **CookieSessionRefresher**: static class with `OnValidatePrincipal` callback for `CookieAuthenticationOptions.Events`. Checked token expiry against `BffOptions.TokenRefreshThresholdMinutes`. Sent refresh_token grant to Keycloak token endpoint via `IHttpClientFactory` (client: `AuthConstants.HttpClients.KeycloakToken`). Updated cookie tokens on success, rejected principal on failure. Recorded telemetry via `AuthTelemetry`.
-
----
-
-## Medium Impact - Feature Gaps
-
-### 8. GraphQL — ❌ MISSING
-- Not present in any microservice
-- **Recommendation:** If needed, add to ProductCatalog.Api or a dedicated GraphQL gateway
-- **How it was implemented:**
- HotChocolate `AddGraphQLServer()`. Schema: `AddQueryType()` with extensions `CategoryQueries`, `ProductReviewQueries`. Mutations: `AddMutationType()` with extension `ProductReviewMutations`. Custom types: `ProductType`, `ProductReviewType`. DataLoaders: `ProductReviewsByProductDataLoader`. Instrumentation: `GraphQlExecutionMetricsListener` (diagnostic event listener). Pagination: `MaxPageSize` from `PaginationFilter.MaxPageSize`, `IncludeTotalCount=true`. Security: `AddAuthorization()`. Depth limit: `AddMaxExecutionDepthRule(5)`. Mapped via `app.MapGraphQL()` with rate limiting + `MapNitroApp("/graphql/ui")`.
-
-### 9. Observability / Telemetry — ⚠️ PARTIAL
-- `SharedKernel.Api/Extensions/ObservabilityExtensions.cs` provides generic OTEL setup (tracing, metrics, OTLP export)
-- Missing: `ApiMetrics`, `AuthTelemetry`, `CacheTelemetry`, `ConflictTelemetry`, `ValidationTelemetry`, `StoredProcedureTelemetry`, `StartupTelemetry`, `HealthCheckMetricsPublisher`, `ObservabilityConventions`
-- **Recommendation:** Move shared telemetry to SharedKernel, service-specific to each service
-- **How it was implemented:**
- Core: `ObservabilityConventions` (ActivitySourceName/MeterName = `"APITemplate"`), `ApiMetrics` (static `Meter` + counters for rate-limit rejections, handled exceptions). Domain-specific: `AuthTelemetry`, `CacheTelemetry`, `ConflictTelemetry`, `GraphQlTelemetry`, `ValidationTelemetry`, `StoredProcedureTelemetry`, `StartupTelemetry` — each with static methods wrapping `ActivitySource.StartActivity()`. Support: `HttpRouteResolver` (display-friendly route names), `TelemetryApiSurfaceResolver` (REST vs GraphQL classification), `HealthCheckMetricsPublisher` (health check results as metrics). Registration in `AddObservability()`: OpenTelemetry tracing (ASP.NET Core, HttpClient, HotChocolate, Redis, Npgsql, MongoDB) + metrics (runtime, process, custom histograms). Config section: `"Observability"` with `ObservabilityOptions` (ServiceName, Exporters.Aspire/Otlp/Console). Auto-detected Aspire in dev, OTLP in containers.
-
-### 10. Inbound Webhook Processing — ⚠️ PARTIAL
-- `HmacWebhookPayloadSigner` exists in Webhooks service (outbound signing only)
-- Missing: `HmacWebhookPayloadValidator`, `WebhookSignatureResourceFilter`, `ValidateWebhookSignatureAttribute`, `IWebhookProcessingQueue`, `WebhookProcessingBackgroundService`, `LoggingWebhookEventHandler`
-- **Recommendation:** Add inbound processing to Webhooks service
-- **How it was implemented:**
- Full chain:
- - **Validator**: `HmacWebhookPayloadValidator` (`IWebhookPayloadValidator`) — HMAC-SHA256 signature + timestamp header validation.
- - **Filter**: `WebhookSignatureResourceFilter` (`IAsyncResourceFilter`) — read raw body with `EnableBuffering()`, checked for `[ValidateWebhookSignature]` attribute, threw `UnauthorizedException` on failure.
- - **Queue**: `ChannelWebhookQueue` (Channel-based `IWebhookProcessingQueue`) + `WebhookProcessingBackgroundService` consumer.
- - **Handler**: `LoggingWebhookEventHandler` (`IWebhookEventHandler`).
- - Config: `WebhookOptions` section. Headers: `WebhookConstants.SignatureHeader`, `WebhookConstants.TimestampHeader`. Controller: `[AllowAnonymous]`, 1MB request limit.
-
-### 11. SSE Streaming Endpoint — ❌ MISSING
-- Not in any microservice
-- **Recommendation:** Add to Notifications service or a dedicated Realtime service
-- **How it was implemented:**
- `SseController` with `[HttpGet("stream")]`. Set headers: `text/event-stream`, `no-cache`, `keep-alive`. Sent `GetNotificationStreamQuery` via Wolverine `IMessageBus.InvokeAsync>()`. Wrote `data: \n\n` frames via `StreamWriter` on `Response.Body`, flushing after each item. Took `SseStreamRequest` from query params. Required `Permission.Examples.Read`.
-
-### 12. JSON Patch Endpoint — ❌ MISSING
-- Not in any microservice
-- **Recommendation:** Add to ProductCatalog if needed
-- **How it was implemented:**
- `PatchController` with `[HttpPatch("products/{id:guid}")]`. Used `SystemTextJsonPatch` library (not Newtonsoft). Received `JsonPatchDocument`. Sent `PatchProductCommand(id, dto => patchDocument.ApplyTo(dto))` — command carried an `Action` apply-delegate so the application layer controlled mutation. Returned `ErrorOr` via `ToActionResult()`. Required `Permission.Examples.Update`.
-
-### 13. Idempotent Endpoint — ⚠️ PARTIAL
-- `IdempotentAttribute` and `IdempotencyActionFilter` exist in SharedKernel.Api, `DistributedCacheIdempotencyStore` and `InMemoryIdempotencyStore` exist in SharedKernel.Infrastructure
-- No controller in any service currently uses `[Idempotent]`
-- **Recommendation:** Apply `[Idempotent]` attribute to appropriate POST endpoints (e.g., payment, order creation)
-
-### 14. Email Retry Background Job — ⚠️ PARTIAL
-- `FailedEmail` entity, `IFailedEmailRepository`, `FailedEmailRepository`, `FailedEmailErrorNormalizer` exist in Notifications service
-- Missing: `EmailRetryRecurringJob`, `IEmailRetryService`, `EmailRetryService`, stored procedures (`ClaimExpiredFailedEmailsProcedure`, `ClaimRetryableFailedEmailsProcedure`), Polly `SmtpSend` pipeline
-- **Recommendation:** Add to Notifications service (TickerQ job) or BackgroundJobs service
-- **How it was implemented:**
- TickerQ recurring job: `EmailRetryRecurringJob` with `[TickerFunction(TickerQFunctionNames.EmailRetry)]`. Gated by `IDistributedJobCoordinator.ExecuteIfLeaderAsync()` for multi-node safety. Delegated to `EmailRetryService` (`IEmailRetryService`). Optimistic per-record claiming: `ClaimRetryableBatchAsync()` with owner = `"{MachineName}:{ProcessId}"` and lease timeout. Per-email commit for crash safety. Dead-lettered via `ClaimExpiredBatchAsync()`. Config: `BackgroundJobsOptions.EmailRetry` with `EmailRetryJobOptions` (Cron=`"*/15 * * * *"`, MaxRetryAttempts=5, BatchSize=50, DeadLetterAfterHours=48, ClaimLeaseMinutes=10). Used Polly `SmtpSend` pipeline for delivery retry. Stored procedures: `ClaimExpiredFailedEmailsProcedure`, `ClaimRetryableFailedEmailsProcedure`.
-
-### 15. Auth Bootstrap Seeder — ⚠️ PARTIAL
-- `BootstrapTenantOptions` class exists and is bound in Identity.Api `Program.cs`
-- Missing: `AuthBootstrapSeeder` implementation — no tenant is seeded on startup
-- **Recommendation:** Add to Identity.Api startup
-- **How it was implemented:**
- `AuthBootstrapSeeder` with deps: `AppDbContext`, `IOptions`. Seeded default tenant (hardcoded ID `00000000-0000-0000-0000-000000000001`). Config: `Bootstrap:Tenant` section with `Code` and `Name`. Used `IgnoreQueryFilters(["SoftDelete", "Tenant"])` to find existing. Restored soft-deleted/deactivated tenants. Only called `SaveChangesAsync` if changes made. Called during startup in `UseDatabaseAsync()` wrapped by `StartupTelemetry.RunAuthBootstrapSeedAsync()`.
-
-### 16. Startup Task Coordinator — ❌ MISSING
-- Prevents concurrent migrations in multi-instance deployments; not present in any service
-- **Recommendation:** Add to SharedKernel.Infrastructure
-- **How it was implemented:**
- `PostgresAdvisoryLockStartupTaskCoordinator` (`IStartupTaskCoordinator`). Used PostgreSQL `pg_advisory_lock(@lockKey)` / `pg_advisory_unlock(@lockKey)` on a dedicated `NpgsqlConnection`. `StartupTaskName` enum values as stable lock keys. Returned `IAsyncDisposable` lease (`PostgresAdvisoryLockLease`). Fell back to `NoOpAsyncDisposable` for non-Npgsql providers. Connection string from `DbContext.Database.GetConnectionString()`.
-
----
-
-## Lower Impact - Completeness
-
-### 17. SoftDeleteCascadeRule Implementations — ⚠️ PARTIAL
-- `ISoftDeleteCascadeRule` and `ISoftDeleteProcessor` / `SoftDeleteProcessor` exist in SharedKernel.Infrastructure and are wired into all DbContexts (ProductCatalog, Identity, Reviews, FileStorage)
-- Missing: concrete implementations — no `ProductSoftDeleteCascadeRule`, `TenantSoftDeleteCascadeRule` in any service
-- **Recommendation:** Implement per-service as needed (e.g., cascade-soft-delete ProductData/Reviews when Product is soft-deleted)
-
-### 18. Entity Normalization Service — ⚠️ PARTIAL
-- `IEntityNormalizationService` exists in SharedKernel.Infrastructure and is invoked by `TenantAuditableDbContext`
-- Missing: any implementation — `AppUserEntityNormalizationService` not present in Identity service
-- **Recommendation:** Add to Identity.Infrastructure
-- **How it was implemented:**
- `AppUserEntityNormalizationService` (`IEntityNormalizationService`). Single method `Normalize(IAuditableTenantEntity entity)` — type-checked for `AppUser`, then set `NormalizedUsername = AppUser.NormalizeUsername(user.Username)` and `NormalizedEmail = AppUser.NormalizeEmail(user.Email)`. Normalization methods were static on the `AppUser` domain entity.
-
-### 19. MongoDB Migration Support — ❌ MISSING
-- ProductCatalog has `MongoDbContext` but no migration runner
-- **Recommendation:** Add to ProductCatalog.Api startup
-- **How it was implemented:**
- Used `Kot.MongoDB.Migrations` package. Called `IMigrator.MigrateAsync()` at startup in `UseDatabaseAsync()`.
-
-### 20. MongoDB Health Check — ❌ MISSING
-- ProductCatalog registers generic `AddHealthChecks()` but no MongoDB-specific check
-- **Recommendation:** Add to ProductCatalog.Api
-- **How it was implemented:**
- Custom `MongoDbHealthCheck` registered via `services.AddHealthChecks().AddCheck()`.
-
-### 21. Log Redaction / PII Classification — ❌ MISSING
-- **Recommendation:** Add to SharedKernel when compliance required
-- **How it was implemented:**
- `Microsoft.Extensions.Compliance.Redaction` with `AddApplicationRedaction()` in `Program.cs`. `LogDataClassifications` defined taxonomies (Sensitive, Personal). Configured HMAC redaction for sensitive data, erasing for personal data.
-
-### 22. External Integration Sync Job — ❌ MISSING
-- `IExternalIntegrationSyncService` with recurring job not implemented
-- **Recommendation:** Add when needed
-
-### 23. BackgroundJobs Options Validator — ⚠️ PARTIAL
-- `BackgroundJobsOptions` uses `[ValidateObjectMembers]` data annotations for nested validation
-- Missing: explicit `BackgroundJobsOptionsValidator : IValidateOptions` — no startup-time validation failure with descriptive messages
-- **Recommendation:** Add to BackgroundJobs service
-- **How it was implemented:**
- `BackgroundJobsOptionsValidator` — startup validation of job config using `IValidateOptions`.
-
-### 24. Expired Invitation Cleanup — ❌ MISSING
-- `ICleanupService` in BackgroundJobs has no `CleanupExpiredInvitationsAsync` method
-- Expired pending invitations accumulate indefinitely in the Identity database
-- **Recommendation:** Add to Identity service (own cleanup job) or extend BackgroundJobs via a cross-service event/API call
-- **How it was implemented:**
- Monolith `CleanupService.CleanupExpiredInvitationsAsync(int retentionHours, int batchSize)`. Queried `TenantInvitations` with `IgnoreQueryFilters()` filtering `Status == InvitationStatus.Pending && ExpiresAtUtc < cutoff`. Used `ExecuteDeleteAsync()` in a batch loop until fewer than `batchSize` rows deleted. Config: `CleanupJobOptions.ExpiredInvitationRetentionHours`. Called by `CleanupRecurringJob` alongside soft-delete and orphan cleanup.
-
-### 25. Cross-Service Soft-Delete Purge — ❌ MISSING
-- Microservices `BackgroundJobs.CleanupService.CleanupSoftDeletedRecordsAsync` only purges its own `JobExecution` records
-- Soft-deleted entities in all other services (Products, Categories, Users, Tenants, Reviews, Files, StoredFiles) are **never physically deleted**
-- **Recommendation:** Add `ISoftDeleteCleanupStrategy` + `SoftDeleteCleanupStrategy` to each relevant service and expose a cleanup endpoint or internal job
-- **How it was implemented:**
- Monolith: `ISoftDeleteCleanupStrategy` interface (`EntityName`, `CleanupAsync(DateTime cutoff, int batchSize, CancellationToken ct)`). Generic implementation `SoftDeleteCleanupStrategy` used `EF Core ExecuteDeleteAsync` with `IgnoreQueryFilters()` on `ISoftDeletable` entities. Registered per entity type via DI (`services.AddScoped>()`). `CleanupService` received `IEnumerable` and iterated all strategies. Monolith cleaned: `Product`, `Category`, `AppUser`, `Tenant`, `TenantInvitation`, `StoredFile`, `ProductReview`.
-
-### 26. Orphaned MongoDB ProductData Cleanup — ❌ MISSING
-- No safety-net cleanup exists for MongoDB `ProductData` documents that lose their PostgreSQL `ProductDataLink`
-- Can occur after transaction failures, cascade bugs, or manual DB edits
-- **Recommendation:** Add to ProductCatalog service as a periodic maintenance job
-- **How it was implemented:**
- Monolith `CleanupService.CleanupOrphanedProductDataAsync(int retentionDays, int batchSize)`. Paginated through MongoDB `ProductData` collection (cursor via `lastSeenId`), filtered `CreatedAt < cutoff`. For each page, fetched linked IDs from `ProductDataLinks` in PostgreSQL (with `IgnoreQueryFilters()`). Computed orphan IDs as set difference. Deleted orphans via `MongoDB.DeleteManyAsync(Builders.Filter.In(...))`. Config: `CleanupJobOptions.OrphanRetentionDays`.
-
----
-
-## Testing Gaps
-
-### Missing test categories in microservices:
-- **Auth integration tests** - no service tests auth flows end-to-end
-- **Postgres-specific tests** - search, tenant isolation, cascade, transactions
-- **GraphQL tests** - follows from feature gap
-- **SSE/Patch/Idempotent/Webhook-receive tests** - follow from feature gaps
-- **Infrastructure tests** - CORS, rate limiting behavior
-
-### Testing infrastructure unique to monolith:
-- Alba-based integration fixtures (microservices use WebApplicationFactory)
-- InMemoryProductRepository for unit tests
-- WebhookTestHelper
-- TestOutputCacheStore
diff --git a/docs/testing.md b/docs/testing.md
index 05ceb3f5..73e16eb0 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -10,14 +10,6 @@ This guide explains how to add unit tests and integration tests for this project
**PostgreSQL integration:** Testcontainers.PostgreSql
**Database reset:** Respawn (resets PostgreSQL to a clean state between tests without re-creating the schema)
-### Assertions
-
-Use **Shouldly** for all test assertions (`ShouldBe`, `ShouldNotBeNull`, `ShouldContain`, and so on). Do not add **FluentAssertions** or another general-purpose assertion library unless the team explicitly agrees; mixing styles makes failures harder to read and reviews noisier.
-
-Shared JSON options and HTTP response helpers used by multiple test projects live in **`tests/Tests.Common`** (`TestJsonOptions`, `HttpResponseAssertionExtensions.ShouldHaveStatusAsync`, etc.).
-
-For APIs that accept `CancellationToken`, prefer **`TestContext.Current.CancellationToken`** in test code so cancellation is responsive (xUnit analyzer **xUnit1051**).
-
Run all tests:
```bash
diff --git a/infrastructure/docker/init-microservices-databases.sql b/infrastructure/docker/init-microservices-databases.sql
deleted file mode 100644
index 3779b591..00000000
--- a/infrastructure/docker/init-microservices-databases.sql
+++ /dev/null
@@ -1,8 +0,0 @@
--- Create per-service databases for microservices architecture
-CREATE DATABASE productcatalog_db;
-CREATE DATABASE reviews_db;
-CREATE DATABASE identity_db;
-CREATE DATABASE notifications_db;
-CREATE DATABASE filestorage_db;
-CREATE DATABASE backgroundjobs_db;
-CREATE DATABASE webhooks_db;
diff --git a/infrastructure/keycloak/realms/api-template-realm.json b/infrastructure/keycloak/realms/api-template-realm.json
index ab134770..419a9422 100644
--- a/infrastructure/keycloak/realms/api-template-realm.json
+++ b/infrastructure/keycloak/realms/api-template-realm.json
@@ -12,7 +12,7 @@
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": false,
"editUsernameAllowed": false,
- "passwordPolicy": "length(4)",
+ "passwordPolicy": "length(8) and upperCase(1) and digits(1) and forceExpiredPasswordChange(365)",
"bruteForceProtected": true,
"failureFactor": 5,
"waitIncrementSeconds": 60,
@@ -168,7 +168,7 @@
"credentials": [
{
"type": "password",
- "value": "admin",
+ "value": "Admin123",
"temporary": false
}
],
diff --git a/monolith/API-Template-mono/.dockerignore b/monolith/API-Template-mono/.dockerignore
deleted file mode 100644
index 1ed9ea04..00000000
--- a/monolith/API-Template-mono/.dockerignore
+++ /dev/null
@@ -1,7 +0,0 @@
-**/bin
-**/obj
-**/.vs
-**/node_modules
-**/.idea
-*.user
-*.suo
diff --git a/monolith/API-Template-mono/.gitignore b/monolith/API-Template-mono/.gitignore
deleted file mode 100644
index 4f9a26b4..00000000
--- a/monolith/API-Template-mono/.gitignore
+++ /dev/null
@@ -1,66 +0,0 @@
-# Build outputs
-bin/
-obj/
-out/
-
-# Visual Studio
-.vs/
-*.user
-*.suo
-*.userosscache
-*.sln.docstates
-
-# Rider
-.idea/
-*.sln.iml
-
-# NuGet
-*.nupkg
-*.snupkg
-packages/
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# .NET
-*.orig
-
-# Agent files
-.claude/**/*local*
-.cursor/**
-docs/superpowers/**
-**/obj-codex/**
-CLAUDE.md
-AGENTS.md
-
-# Logs
-*.log
-logs/
-
-# OS
-.DS_Store
-Thumbs.db
-
-
-# Environment / secrets
-.env
-.env.local
-*.env
-appsettings.Development.json
-
-# Docker
-docker-compose.override.yml
-
-# Test results
-TestResults/
-coverage/
-*.trx
-*.coveragexml
-
-# Published output
-publish/
-
-
-# Git worktrees
-.worktrees/
-
diff --git a/monolith/API-Template-mono/APITemplate.slnx b/monolith/API-Template-mono/APITemplate.slnx
deleted file mode 100644
index 7df42cb4..00000000
--- a/monolith/API-Template-mono/APITemplate.slnx
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/monolith/API-Template-mono/Directory.Build.targets b/monolith/API-Template-mono/Directory.Build.targets
deleted file mode 100644
index 5a161a05..00000000
--- a/monolith/API-Template-mono/Directory.Build.targets
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
diff --git a/monolith/API-Template-mono/Directory.Packages.props b/monolith/API-Template-mono/Directory.Packages.props
deleted file mode 100644
index e5a26a58..00000000
--- a/monolith/API-Template-mono/Directory.Packages.props
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/monolith/API-Template-mono/LICENSE b/monolith/API-Template-mono/LICENSE
deleted file mode 100644
index f288702d..00000000
--- a/monolith/API-Template-mono/LICENSE
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
diff --git a/monolith/API-Template-mono/README.md b/monolith/API-Template-mono/README.md
deleted file mode 100644
index 3b94c8d6..00000000
--- a/monolith/API-Template-mono/README.md
+++ /dev/null
@@ -1,1279 +0,0 @@
-# APITemplate
-
-[](https://github.com/zribktad/API-Template/actions/workflows/pr-validation.yml)
-
-A scalable, clean, and modern template designed to jumpstart **.NET 10** Web API and Data-Driven applications. By providing a curated set of industry-standard libraries and combining modern **REST** APIs side-by-side with a robust **GraphQL** backend, it bridges the gap between typical monolithic development speed and Clean Architecture principles within a single maintainable repository.
-
-## 📚 How-To Guides
-
-Step-by-step guides for the most common workflows in this project:
-
-| Guide | Description |
-| ---------------------------------------------------- | --------------------------------------------------------------------------- |
-| [GraphQL Endpoint](docs/graphql-endpoint.md) | Add a type, query, mutation, and optional DataLoader |
-| [REST Endpoint](docs/rest-endpoint.md) | Full workflow: entity → DTO → validator → Wolverine handler → controller |
-| [EF Core Migration](docs/ef-migration.md) | Create and apply PostgreSQL schema migrations |
-| [MongoDB Migration](docs/mongodb-migration.md) | Create index and data migrations with Kot.MongoDB.Migrations |
-| [Transactions](docs/transactions.md) | Wrap multiple operations in an atomic Unit of Work transaction |
-| [Authentication](docs/AUTHENTICATION.md) | JWT login flow, protecting endpoints, and production guidance |
-| [Stored Procedures](docs/stored-procedures.md) | Add a PostgreSQL function and call it safely from C# |
-| [MongoDB Polymorphism](docs/mongodb-polymorphism.md) | Store multiple document subtypes in one collection |
-| [Validation](docs/validation.md) | Add FluentValidation rules, cross-field rules, and shared validators |
-| [Specifications](docs/specifications.md) | Write reusable EF Core query specifications with Ardalis |
-| [Scalar & GraphQL UI](docs/scalar-and-graphql-ui.md) | Use the Scalar REST explorer and Nitro GraphQL playground |
-| [Testing](docs/testing.md) | Write unit tests (services, validators, repositories) and integration tests |
-| [Observability](docs/observability.md) | Run OpenTelemetry locally with Aspire Dashboard or Grafana LGTM |
-| [Caching](docs/CACHING.md) | Configure output caching, rate limiting, and DragonFly backing store |
-| [Result Pattern](docs/result-pattern.md) | Guidelines for introducing selective `Result` flow in phase 2 |
-| [Git Hooks](docs/GIT_HOOKS.md) | Auto-install Husky.Net hooks and format staged C# files with CSharpier |
-
----
-
-## 🚀 Key Features
-
-* **Architecture Pattern:** Clean mapping of concerns inside a monolithic solution (emulating Clean Architecture). `Domain` rules and interfaces are isolated from `Application` logic and `Infrastructure`.
-* **Dual API Modalities:**
- * **REST API:** Clean HTTP endpoints using versioned controllers (`Asp.Versioning.Mvc`).
- * **GraphQL API:** Complex query batching via `HotChocolate`, integrated Mutations and DataLoaders to eliminate the N+1 problem.
-* **Modern Interactive Documentation:** Native `.NET 10` OpenAPI integrations displayed smoothly in the browser using **Scalar** `/scalar`. Includes **Nitro UI** `/graphql/ui` for testing queries natively.
-* **Dual Database Architecture:**
- * **PostgreSQL + EF Core 10:** Relational entities (Products, Categories, Reviews, Tenants, Users) with the Repository + Unit of Work pattern.
- * **MongoDB:** Semi-structured media metadata (ProductData) with a polymorphic document model and BSON discriminators.
-* **Multi-Tenancy:** Every relational entity implements `IAuditableTenantEntity`. `AppDbContext` enforces per-tenant read isolation via global query filters (`TenantId == currentTenant && !IsDeleted`). New rows are automatically stamped with the current tenant from the request JWT.
-* **Soft Delete with Cascade:** Delete operations are converted to soft-delete updates in `AppDbContext.SaveChangesAsync`. Cascade rules (e.g. `ProductSoftDeleteCascadeRule`) propagate soft-deletes to dependent entities without relying on database-level cascades.
-* **Audit Fields:** All entities carry `AuditInfo` (owned EF type) with `CreatedAtUtc`, `CreatedBy`, `UpdatedAtUtc`, `UpdatedBy`. Fields are stamped automatically in `SaveChangesAsync`.
-* **Optimistic Concurrency:** PostgreSQL native `xmin` system column configured as a concurrency token. `DbUpdateConcurrencyException` is mapped to HTTP 409 by `ApiExceptionHandler`.
-* **Rate Limiting:** Fixed-window per-client rate limiter (`100 req/min` default). Partition key priority: JWT username → remote IP → `"anonymous"`. Returns HTTP 429 on breach. Limits are configurable via `RateLimiting:Fixed`.
-* **Output Caching:** Tenant-isolated ASP.NET Core output cache backed by **DragonFly** (Redis-compatible). Policies: `Products` (30 s), `Categories` (60 s), `Reviews` (30 s). Mutations evict affected tags. Falls back to in-memory when `Dragonfly:ConnectionString` is absent.
-* **Domain Filtering:** Seamless filtering, sorting, and paging powered by `Ardalis.Specification` to decouple query models from infrastructural EF abstractions.
-* **Enterprise-Grade Utilities:**
- * **Validation:** Pipelined model validation using `FluentValidation.AspNetCore`.
- * **Cross-Cutting Concerns:** Unified configuration via `Serilog` (structured logging with `MachineName` and `ThreadId` enrichers) and centralized exception handling via `IExceptionHandler` + RFC 7807 `ProblemDetails`.
- * **Data Redaction:** Sensitive log properties (PII, secrets) are classified with `Microsoft.Extensions.Compliance` (`[PersonalData]`, `[SensitiveData]`) and HMAC-redacted before writing.
- * **Authentication:** Pre-configured Keycloak JWT + BFF Cookie dual-auth with production hardening: secure-only cookies in production, server-side session store (`DragonflyTicketStore`) backed by DragonFly, silent token refresh before expiry, and CSRF protection (`X-CSRF: 1` header required for cookie-authenticated mutations).
- * **Observability:** Health Checks (`/health`) natively tracking PostgreSQL, MongoDB, and DragonFly state.
-* **Role-Based Access Control:** Three-tier role model (`PlatformAdmin`, `TenantAdmin`, `User`) enforced via Keycloak claims and ASP.NET Core policy-based authorization. `PermissionRequirement` handlers gate controller actions and GraphQL mutations by role.
-* **Robust Testing Engine:** Provides isolated `Integration` tests using `UseInMemoryDatabase` combined with `WebApplicationFactory` for fast feedback, **Testcontainers PostgreSQL** for high-fidelity tenant isolation and transaction tests, plus a comprehensive `Unit` test suite (Moq, Shouldly, FluentValidation.TestHelper).
-
----
-
-## 🏗 Architecture Diagram
-
-The application leverages a single `.csproj` separated rationally via namespaces that conform to typical clean layer boundaries. The goal is friction-free deployments and dependency chains while ensuring long-term code organization.
-
-```mermaid
-graph TD
- subgraph APITemplate [APITemplate Web API]
- direction TB
-
- subgraph PresentationLayer [API Layer]
- REST[Controllers V1]
- GQL[GraphQL Queries & Mutations]
- UI[Scalar / Nitro UI]
- MID[Middleware & Logging]
- end
-
- subgraph ApplicationLayer [Application Layer]
- Services[Business Services]
- DTO[Data Transfer Objects]
- Validators[Fluent Validation]
- Spec[Ardalis Specifications]
- end
-
- subgraph DomainLayer [Domain Layer]
- Entities[Entities & Aggregate Roots]
- Ex[Domain Exceptions]
- Irepo[Abstract Interfaces]
- end
-
- subgraph InfrastructureLayer [Infrastructure Layer]
- Repo[Concrete Repositories]
- UoW[Unit of Work]
- EF[EF Core AppDbContext]
- Mongo[MongoDbContext]
- end
-
- %% Linkages representing Dependencies
- REST --> MID
- GQL --> MID
- REST --> Services
- GQL --> Services
- GQL -.-> DataLoaders[DataLoaders]
- DataLoaders --> Services
-
- Services --> Irepo
- Services --> Spec
- Services -.-> DTO
- Services -.-> Validators
-
- Repo -.-> Irepo
- Repo --> EF
- Repo --> Mongo
- UoW -.-> Irepo
- Irepo -.-> Entities
- EF -.-> Entities
- Mongo -.-> Entities
-
- PresentationLayer --> ApplicationLayer
- ApplicationLayer --> DomainLayer
- InfrastructureLayer --> DomainLayer
- end
-
- DB[(PostgreSQL)]
- MDB[(MongoDB)]
- DF[(DragonFly)]
- EF ---> DB
- Mongo ---> MDB
- REST -..-> DF
-```
-
----
-
-## 📦 Domain Class Diagram
-
-This class diagram models the aggregate roots and entities located natively within `Domain/Entities/`.
-
-```mermaid
-classDiagram
- class Tenant {
- +Guid Id
- +string Code
- +string Name
- +bool IsActive
- +ICollection~AppUser~ Users
- +AuditInfo Audit
- +bool IsDeleted
- }
-
- class AppUser {
- +Guid Id
- +string Username
- +string NormalizedUsername
- +string Email
- +string PasswordHash
- +bool IsActive
- +UserRole Role
- +Tenant Tenant
- +Guid TenantId
- +AuditInfo Audit
- +bool IsDeleted
- }
-
- class Category {
- +Guid Id
- +string Name
- +string? Description
- +Guid TenantId
- +AuditInfo Audit
- +bool IsDeleted
- +ICollection~Product~ Products
- }
-
- class Product {
- +Guid Id
- +string Name
- +string? Description
- +decimal Price
- +Guid? CategoryId
- +Guid TenantId
- +AuditInfo Audit
- +bool IsDeleted
- +ICollection~ProductReview~ Reviews
- }
-
- class ProductReview {
- +Guid Id
- +Guid ProductId
- +Guid UserId
- +string? Comment
- +int Rating
- +AppUser User
- +Guid TenantId
- +AuditInfo Audit
- +bool IsDeleted
- +Product Product
- }
-
- class AuditInfo {
- +DateTime CreatedAtUtc
- +Guid CreatedBy
- +DateTime UpdatedAtUtc
- +Guid UpdatedBy
- }
-
- class ProductData {
- <>
- +string Id
- +string Title
- +string? Description
- +DateTime CreatedAt
- }
-
- class ImageProductData {
- +int Width
- +int Height
- +string Format
- +long FileSizeBytes
- }
-
- class VideoProductData {
- +int DurationSeconds
- +string Resolution
- +string Format
- +long FileSizeBytes
- }
-
- Tenant "1" --> "0..*" AppUser : users
- Tenant "1" --> "0..*" Category : scope
- Tenant "1" --> "0..*" Product : scope
- Tenant "1" --> "0..*" ProductReview : scope
- Category "1" --> "0..*" Product : products/category
- Product "1" --> "0..*" ProductReview : reviews/product
- AppUser "1" --> "0..*" ProductReview : reviews/user
- Product --> AuditInfo
- Category --> AuditInfo
- AppUser --> AuditInfo
- Tenant --> AuditInfo
- ProductReview --> AuditInfo
- ProductData <|-- ImageProductData : discriminator image
- ProductData <|-- VideoProductData : discriminator video
-```
-
----
-
-## 🛠 Technology Stack
-
-* **Runtime:** `.NET 10.0` Web SDK
-* **Relational Database:** PostgreSQL 18 (`Npgsql`)
-* **Document Database:** MongoDB 8 (`MongoDB.Driver`)
-* **Cache / Rate Limit Backing Store:** DragonFly 1.27 (Redis-compatible, `StackExchange.Redis`)
-* **ORM:** Entity Framework Core (`Microsoft.EntityFrameworkCore.Design`, `10.0`)
-* **API Toolkit:** ASP.NET Core, Asp.Versioning, `Scalar.AspNetCore`
-* **GraphQL Core:** HotChocolate `15.1`
-* **Auth:** Keycloak 26 (JWT Bearer + BFF Cookie via OIDC)
-* **Utilities:** `Serilog.AspNetCore`, `FluentValidation`, `Ardalis.Specification`, `Kot.MongoDB.Migrations`
-* **Test Suite:** xUnit 3, `Microsoft.AspNetCore.Mvc.Testing`, Moq, Shouldly, `FluentValidation.TestHelper`, Testcontainers.PostgreSql, Respawn
-
----
-
-## 📂 Project Structure
-
-The solution follows a strict **four-project Clean Architecture** split. Each project has a single, well-defined responsibility and a one-way dependency rule: outer layers depend on inner layers — never the reverse.
-
-```
-APITemplate.Domain ← APITemplate.Application ← APITemplate.Infrastructure
- ← ← APITemplate.Api
-```
-
-### Project responsibilities
-
-| Project | Role | Key rule |
-| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
-| `APITemplate.Domain` | Core business model — entities, enums, domain exceptions, repository interfaces | No dependencies on any other project or NuGet package except .NET BCL |
-| `APITemplate.Application` | Use-case layer — Wolverine commands/queries/handlers, DTOs, FluentValidation validators, specifications | Depends only on Domain; never references EF Core, ASP.NET, or any infrastructure detail |
-| `APITemplate.Infrastructure` | Technical implementations — EF Core `AppDbContext`, MongoDB context, repository classes, Unit of Work, migrations, security services, observability | Depends on Domain (implements interfaces) and Application (reads options) |
-| `APITemplate.Api` | Presentation entry point — REST controllers, GraphQL types/queries/mutations/DataLoaders, middleware, DI composition root, `Program.cs` | Depends on all other projects; owns `IMessageBus` dispatch and HTTP/GraphQL mapping |
-| `APITemplate.Tests` | Test suite — unit tests (Moq), in-memory integration tests (`WebApplicationFactory`), Testcontainers PostgreSQL tests (Respawn) | References all production projects; never ships to production |
-
-### Folder layout
-
-```text
-src/APITemplate.Api/
-├── Api/
-│ ├── Controllers/V1/ # REST endpoints (ProductsController, CategoriesController, …)
-│ ├── GraphQL/ # Types, Queries, Mutations, DataLoaders
-│ ├── Middleware/ # RequestContextMiddleware, CsrfValidationMiddleware
-│ ├── Authorization/ # PermissionRequirement, BffAuthenticationSchemes
-│ ├── Cache/ # TenantAwareOutputCachePolicy, CacheInvalidationNotificationHandler
-│ ├── OpenApi/ # Scalar OAuth2 transformer
-│ └── ExceptionHandling/ # ApiExceptionHandler → RFC 7807 ProblemDetails
-├── Extensions/ # AddApplicationServices, AddPersistence, AddGraphQLConfiguration, …
-├── Program.cs
-└── appsettings*.json
-
-src/APITemplate.Application/
-├── Features/
-│ ├── Product/ # GetProductsQuery, CreateProductCommand, ProductRequestHandlers, DTOs, validators
-│ ├── Category/ # same vertical slice structure
-│ ├── ProductReview/
-│ ├── ProductData/
-│ └── User/
-├── Common/
-│ ├── Behaviors/ # ValidationBehavior (IPipelineBehavior)
-│ ├── Events/ # ProductsChangedNotification, CategoriesChangedNotification, …
-│ ├── Options/ # BffOptions, RateLimitingOptions, CachingOptions, …
-│ └── Security/ # Permission constants, custom claim types
-
-src/APITemplate.Domain/
-├── Entities/ # Tenant, AppUser, Category, Product, ProductReview, ProductData, …
-├── Enums/ # UserRole
-├── Exceptions/ # NotFoundException, ValidationException, ConflictException, …
-└── Interfaces/ # IProductRepository, ICategoryRepository, IUnitOfWork, …
-
-src/APITemplate.Infrastructure/
-├── Persistence/ # AppDbContext (EF Core), MongoDbContext, UnitOfWork
-├── Repositories/ # ProductRepository, CategoryRepository, ProductDataRepository, …
-├── Migrations/ # EF Core migrations + Kot.MongoDB.Migrations
-├── Database/ # Embedded SQL stored-procedure scripts
-├── Security/ # DragonflyTicketStore, CookieSessionRefresher, KeycloakClaimMapper, CsrfValidationMiddleware
-└── Observability/ # Health checks (PostgreSQL, MongoDB, DragonFly, Keycloak)
-
-tests/APITemplate.Tests/
-├── Integration/ # CustomWebApplicationFactory (InMemory DB + mocked infra)
-│ ├── Postgres/ # PostgresWebApplicationFactory (Testcontainers + Respawn)
-│ └── *.cs # REST, GraphQL, BFF/CSRF integration tests
-└── Unit/
- ├── Services/
- ├── Repositories/
- ├── Validators/
- ├── Middleware/
- └── ExceptionHandling/
-```
-
-### Dependency rule in practice
-
-- A handler in `APITemplate.Application` calls `IProductRepository` (Domain interface) — it never imports `ProductRepository` (Infrastructure class).
-- `APITemplate.Infrastructure` implements `IProductRepository` and registers it in DI inside `APITemplate.Api`'s composition root.
-- `APITemplate.Api` controllers reference only `IMessageBus` (Wolverine) — they have no direct dependency on any Application service or Infrastructure class.
-
----
-
-## 🌐 REST API Reference
-
-All versioned REST resource endpoints sit under the base path `api/v{version}`. JWT `Authorization: Bearer ` is required for these versioned API routes. Authentication is handled externally by Keycloak (see [Authentication](#-authentication) section). Utility endpoints such as `/health` and `/graphql/ui` are anonymous, and `/scalar` is only mapped in Development.
-
-> **Rate limiting:** all controller routes require the `fixed` rate-limit policy (100 requests per minute per authenticated user or remote IP).
-
-### Products
-
-| Method | Path | Auth Required | Description |
-| -------- | ----------------------- | :-----------: | ---------------------------------------------- |
-| `GET` | `/api/v1/Products` | ✅ | List products with filtering, sorting & paging |
-| `GET` | `/api/v1/Products/{id}` | ✅ | Get a single product by GUID |
-| `POST` | `/api/v1/Products` | ✅ | Create a new product |
-| `PUT` | `/api/v1/Products/{id}` | ✅ | Update an existing product |
-| `DELETE` | `/api/v1/Products/{id}` | ✅ | Soft-delete a product (cascades to reviews) |
-
-### Categories
-
-| Method | Path | Auth Required | Description |
-| -------- | ------------------------------- | :-----------: | ------------------------------------- |
-| `GET` | `/api/v1/Categories` | ✅ | List all categories |
-| `GET` | `/api/v1/Categories/{id}` | ✅ | Get a category by GUID |
-| `POST` | `/api/v1/Categories` | ✅ | Create a new category |
-| `PUT` | `/api/v1/Categories/{id}` | ✅ | Update a category |
-| `DELETE` | `/api/v1/Categories/{id}` | ✅ | Soft-delete a category |
-| `GET` | `/api/v1/Categories/{id}/stats` | ✅ | Aggregated stats via stored procedure |
-
-### Product Reviews
-
-| Method | Path | Auth Required | Description |
-| -------- | ----------------------------------------------- | :-----------: | ------------------------------------ |
-| `GET` | `/api/v1/ProductReviews` | ✅ | List reviews with filtering & paging |
-| `GET` | `/api/v1/ProductReviews/{id}` | ✅ | Get a review by GUID |
-| `GET` | `/api/v1/ProductReviews/by-product/{productId}` | ✅ | All reviews for a given product |
-| `POST` | `/api/v1/ProductReviews` | ✅ | Create a new review |
-| `DELETE` | `/api/v1/ProductReviews/{id}` | ✅ | Soft-delete a review |
-
-### Product Data (MongoDB)
-
-| Method | Path | Auth Required | Description |
-| -------- | ---------------------------- | :-----------: | ------------------------------------------ |
-| `GET` | `/api/v1/product-data` | ✅ | List all or filter by `type` (image/video) |
-| `GET` | `/api/v1/product-data/{id}` | ✅ | Get by MongoDB ObjectId |
-| `POST` | `/api/v1/product-data/image` | ✅ | Create image media metadata |
-| `POST` | `/api/v1/product-data/video` | ✅ | Create video media metadata |
-| `DELETE` | `/api/v1/product-data/{id}` | ✅ | Delete by MongoDB ObjectId |
-
-### Users
-
-| Method | Path | Auth Required | Description |
-| ------ | ------------------------------- | :-----------: | ----------------------------------------------------- |
-| `GET` | `/api/v1/Users` | ✅ | List all users (PlatformAdmin only) |
-| `GET` | `/api/v1/Users/{id}` | ✅ | Get a user by GUID |
-| `POST` | `/api/v1/Users/register` | ❌ | Register a new user |
-| `PUT` | `/api/v1/Users/{id}/activate` | ✅ | Activate a user (TenantAdmin / PlatformAdmin) |
-| `PUT` | `/api/v1/Users/{id}/deactivate` | ✅ | Deactivate a user (TenantAdmin / PlatformAdmin) |
-| `PUT` | `/api/v1/Users/{id}/role` | ✅ | Assign a role to a user (TenantAdmin / PlatformAdmin) |
-
-### Utility
-
-| Method | Path | Auth Required | Description |
-| ------ | ------------- | :-----------: | ----------------------------------------------------------------------------- |
-| `GET` | `/health` | ❌ | JSON health status for PostgreSQL, MongoDB & DragonFly |
-| `GET` | `/scalar` | ❌ | Interactive Scalar OpenAPI UI (**Development only** — disabled in Production) |
-| `GET` | `/graphql/ui` | ❌ | HotChocolate Nitro GraphQL IDE |
-
----
-
-## ⚙️ Configuration Reference
-
-All configuration lives in `appsettings.json` (production defaults) and is overridden by `appsettings.Development.json` locally or by environment variables at runtime.
-
-**Override priority (highest → lowest):**
-1. Environment variables (e.g. `ConnectionStrings__DefaultConnection=...`)
-2. `appsettings.Development.json` (local development)
-3. `appsettings.json` (production baseline — committed to source control, must not contain real secrets)
-
-> **Security note:** Never commit real secrets to `appsettings.json`. Supply `Keycloak:credentials:secret`, database passwords, and any other sensitive values via environment variables, Docker secrets, or a secret manager such as Azure Key Vault.
-
-Configuration sections are bound to strongly-typed `IOptions` classes registered in DI (e.g. `RateLimitingOptions`, `CachingOptions`, `BffOptions`), so every setting is validated at startup and injectable into any service without raw `IConfiguration` access.
-
-### Databases
-
-| Key | Example Value | Description |
-| ------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `ConnectionStrings:DefaultConnection` | `Host=localhost;Port=5432;Database=apitemplate;Username=postgres;Password=postgres` | Npgsql connection string for the primary PostgreSQL database. Used by EF Core `AppDbContext` for all relational data (tenants, users, products, categories, reviews). |
-| `MongoDB:ConnectionString` | `mongodb://localhost:27017` | MongoDB connection string. Used by `MongoDbContext` for the `product_data` collection (polymorphic media metadata). |
-| `MongoDB:DatabaseName` | `apitemplate` | Name of the MongoDB database. All MongoDB collections are created inside this database. |
-
-### Cache & Session
-
-| Key | Example Value | Description |
-| ---------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `Dragonfly:ConnectionString` | `localhost:6379` | StackExchange.Redis connection string pointing to a DragonFly instance. Used for three purposes: distributed output cache (GET responses), server-side BFF session store (`DragonflyTicketStore`), and shared DataProtection key ring. **Omit or leave empty** to fall back to in-memory cache — suitable for single-instance development only. |
-
-### Authentication — Keycloak
-
-| Key | Example Value | Description |
-| ----------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `Keycloak:auth-server-url` | `http://localhost:8180/` | Base URL of the Keycloak server. Used for JWT token validation (OIDC discovery endpoint) and BFF OIDC login flow. |
-| `Keycloak:realm` | `api-template` | Name of the Keycloak realm that issues tokens for this application. |
-| `Keycloak:resource` | `api-template` | Keycloak client ID. Must match the client configured in the realm. Used as the JWT `aud` (audience) claim. |
-| `Keycloak:credentials:secret` | `dev-client-secret` | Keycloak client secret for the confidential client. Required for BFF OIDC code exchange and token refresh. **Never commit a real secret** — supply via environment variable or secret manager in production. |
-| `Keycloak:SkipReadinessCheck` | `false` | When `true`, the startup `WaitForKeycloakAsync()` probe is skipped. Useful in CI environments where Keycloak is not available. |
-
-### BFF Cookie Session
-
-| Key | Example Value | Description |
-| ---------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `Bff:CookieName` | `.APITemplate.Auth` | Name of the `httpOnly` session cookie issued after a successful BFF login. The cookie contains only a session key — the actual auth ticket is stored in DragonFly. |
-| `Bff:SessionTimeoutMinutes` | `60` | How long the BFF session cookie remains valid after the last activity. |
-| `Bff:PostLogoutRedirectUri` | `/` | URI the browser is redirected to after `GET /api/v1/bff/logout` completes the Keycloak back-channel logout. |
-| `Bff:Scopes` | `["openid","profile","email","offline_access"]` | OIDC scopes requested from Keycloak during the BFF login flow. `offline_access` is required for silent token refresh via refresh token. |
-| `Bff:TokenRefreshThresholdMinutes` | `2` | `CookieSessionRefresher` exchanges the refresh token with Keycloak when the access token will expire within this many minutes. Prevents mid-request token expiry without requiring a full re-login. |
-
-### Rate Limiting
-
-| Key | Example Value | Description |
-| ---------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `RateLimiting:Fixed:PermitLimit` | `100` | Maximum number of requests allowed per client within a single window. Partition key: JWT username → remote IP → `"anonymous"`. Exceeded requests receive HTTP 429. |
-| `RateLimiting:Fixed:WindowMinutes` | `1` | Duration of the fixed rate-limit window in minutes. The counter resets at the end of each window. |
-
-### Output Caching
-
-| Key | Example Value | Description |
-| ------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `Caching:ProductsExpirationSeconds` | `30` | Cache TTL for the `Products` output-cache policy applied to `GET /api/v1/Products` and `GET /api/v1/Products/{id}`. Entries are also evicted immediately when any product mutation publishes `ProductsChangedNotification`. |
-| `Caching:CategoriesExpirationSeconds` | `60` | Cache TTL for the `Categories` output-cache policy. |
-| `Caching:ReviewsExpirationSeconds` | `30` | Cache TTL for the `Reviews` output-cache policy. |
-
-### Persistence & Transactions
-
-| Key | Example Value | Description |
-| -------------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `Persistence:Transactions:IsolationLevel` | `ReadCommitted` | Default SQL isolation level for explicit `IUnitOfWork.ExecuteInTransactionAsync(...)` calls. Accepted values: `ReadUncommitted`, `ReadCommitted`, `RepeatableRead`, `Serializable`. Per-call overrides are possible via `TransactionOptions`. |
-| `Persistence:Transactions:TimeoutSeconds` | `30` | Command timeout applied to the database connection while an explicit transaction is active. Prevents long-running transactions from holding locks indefinitely. |
-| `Persistence:Transactions:RetryEnabled` | `true` | Enables the Npgsql EF Core execution strategy that automatically retries the entire transaction block on transient failures (e.g. connection drops, deadlocks). |
-| `Persistence:Transactions:RetryCount` | `3` | Maximum number of retry attempts before the execution strategy gives up and re-throws. |
-| `Persistence:Transactions:RetryDelaySeconds` | `5` | Maximum back-off delay (in seconds) between retry attempts. Actual delay is randomised up to this value. |
-
-### Bootstrap & Identity
-
-| Key | Example Value | Description |
-| ------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `Bootstrap:Tenant:Code` | `default` | Short code of the seed tenant created automatically on first startup if no tenants exist yet. Used as the default tenant for the seeded admin user. |
-| `Bootstrap:Tenant:Name` | `Default Tenant` | Human-readable display name of the seed tenant. |
-| `SystemIdentity:DefaultActorId` | `00000000-0000-0000-0000-000000000000` | Fallback `CreatedBy` / `UpdatedBy` GUID stamped in audit fields when no authenticated user is present (e.g. during startup seeding). |
-
-### CORS
-
-| Key | Example Value | Description |
-| --------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `Cors:AllowedOrigins` | `["http://localhost:3000","http://localhost:5173"]` | List of origins permitted by the default CORS policy. Add your SPA development server and production domain here. Requests from unlisted origins will be blocked by the browser preflight check. |
-
-> **Security note:** `Keycloak:credentials:secret` must be supplied via an environment variable or secret manager in production — never from a committed config file.
-
----
-
-## 🔐 Authentication
-
-Authentication is handled by **Keycloak** using a hybrid approach that supports both **JWT Bearer tokens** (for API clients and Scalar) and **BFF Cookie sessions** (for SPA frontends).
-
-| Flow | Use Case | How it works |
-| -------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
-| **JWT Bearer** | Scalar UI, API clients, service-to-service | `Authorization: Bearer ` header |
-| **BFF Cookie** | SPA frontend | `/api/v1/bff/login` → Keycloak login → session cookie → direct API calls with cookie + `X-CSRF: 1` header |
-
-#### BFF Production Hardening
-
-| Feature | Detail |
-| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **Secure cookie** | `CookieSecurePolicy.Always` in production; `SameAsRequest` in development |
-| **Server-side session store** | `DragonflyTicketStore` serialises the auth ticket to DragonFly — the cookie contains only a GUID key, keeping cookie size small and preventing token leakage |
-| **Shared DataProtection keys** | Keys persisted to DragonFly under `DataProtection:Keys` so multiple instances can decrypt each other's cookies |
-| **Silent token refresh** | `CookieSessionRefresher.OnValidatePrincipal` exchanges the refresh token with Keycloak when the access token is within `Bff:TokenRefreshThresholdMinutes` (default 2 min) of expiry |
-| **CSRF protection** | `CsrfValidationMiddleware` requires the `X-CSRF: 1` header on all non-GET/HEAD/OPTIONS requests authenticated via the cookie scheme. JWT Bearer requests are exempt. Call `GET /api/v1/bff/csrf` to retrieve the expected header name/value |
-
-### BFF Endpoints
-
-| Method | Path | Auth | Description |
-| ------ | -------------------- | :---: | ---------------------------------------------------------------- |
-| `GET` | `/api/v1/bff/login` | ❌ | Redirects to Keycloak login page |
-| `GET` | `/api/v1/bff/logout` | 🍪 | Signs out from both cookie and Keycloak |
-| `GET` | `/api/v1/bff/user` | 🍪 | Returns current user info (id, username, email, tenantId, roles) |
-| `GET` | `/api/v1/bff/csrf` | ❌ | Returns the required CSRF header name and value (`X-CSRF: 1`) |
-
-### Manual Testing Guide
-
-#### Option A: Scalar UI with OAuth2 (recommended)
-
-1. Start the infrastructure:
- ```bash
- docker compose up -d
- ```
-2. Run the API (via VS Code debugger or CLI):
- ```bash
- dotnet run --project src/APITemplate
- ```
-3. Open **Scalar UI**: `http://localhost:5174/scalar`
-4. Click the **Authorize** button in Scalar
-5. You will be redirected to Keycloak — log in with `admin` / `Admin123`
-6. After successful login, Scalar will automatically attach the JWT token to all requests
-7. Try any endpoint (e.g. `GET /api/v1/Products`)
-
-#### Option B: BFF Cookie flow (browser)
-
-1. Open `http://localhost:5174/api/v1/bff/login` in a browser
-2. Log in with `admin` / `Admin123` on the Keycloak page
-3. After redirect, call API endpoints directly in the browser — the session cookie is sent automatically with every request
-4. Check your session: `http://localhost:5174/api/v1/bff/user`
-
-#### Option C: Direct token via cURL
-
-```bash
-# Get a token from Keycloak (requires Direct Access Grants enabled on the client)
-TOKEN=$(curl -s -X POST http://localhost:8180/realms/api-template/protocol/openid-connect/token \
- -d "grant_type=password&client_id=api-template&client_secret=dev-client-secret&username=admin&password=Admin123" \
- | jq -r '.access_token')
-
-# Use the token
-curl -H "Authorization: Bearer $TOKEN" http://localhost:5174/api/v1/products
-```
-
-> **Note:** Direct Access Grants (password grant) is disabled by default. Enable it in Keycloak Admin (`http://localhost:8180/admin` → api-template client → Settings) if needed.
-
-### ⚡ GraphQL DataLoaders (N+1 Problem Solved)
-By leveraging HotChocolate's built-in **DataLoaders** pipeline (`ProductReviewsByProductDataLoader`), fetching deeply nested parent-child relationships avoids querying the database `n` times. The framework collects IDs requested entirely within the GraphQL query, then queries the underlying EF Core PostgreSQL implementation precisely *once*.
-
-**Example GraphQL Query:**
-```graphql
-query {
- products(input: { pageNumber: 1, pageSize: 10 }) {
- items {
- id
- name
- price
- # Below triggers ONE bulk DataLoader fetch under the hood!
- reviews {
- reviewerName
- rating
- }
- }
- pageNumber
- pageSize
- totalCount
- }
-}
-```
-
-**Example GraphQL Mutation:**
-```graphql
-mutation {
- createProducts(input: {
- items: [
- {
- name: "New Masterpiece Board Game"
- price: 49.99
- description: "An epic adventure game"
- }
- ]
- }) {
- successCount
- failureCount
- }
-}
-```
-
----
-
-## 🏆 Design Patterns & Best Practices
-
-This template deliberately applies a number of industry-accepted patterns. Understanding *why* each pattern is used helps when extending the solution.
-
-### 1 — Repository Pattern
-
-Every data-store interaction is hidden behind a typed interface defined in `Domain/Interfaces/`. Application services depend only on `IProductRepository`, `ICategoryRepository`, etc., while controllers depend on those services — never directly on `AppDbContext` or `IMongoCollection`.
-
-**Benefits:**
-- Database provider can be swapped without touching business logic.
-- Repositories can be replaced with in-memory fakes or Moq mocks in tests.
-
-### 2 — Unit of Work Pattern
-
-`IUnitOfWork` (implemented by `UnitOfWork`) is the only commit boundary for relational persistence. Repositories stage changes in EF Core's change tracker, but they never call `SaveChangesAsync` directly. Relational write services call `ExecuteInTransactionAsync(...)` directly when they need an explicit transaction boundary.
-
-**Rules:**
-- Query services own API/read-model reads that return DTOs.
-- Paginated, filtered, cross-aggregate, and batching reads belong in query services, usually backed by specifications or projections.
-- Command-side validation lookups stay in the write service and use repositories directly.
-- Write services load entities they intend to mutate through repositories, not query services.
-- `ExecuteInTransactionAsync(...)` is the explicit relational transaction entry point used by services.
-- Some single-write flows do not strictly require an explicit transaction; use `CommitAsync()` when a direct save is enough and `ExecuteInTransactionAsync(...)` when you want one explicit transaction shape.
-- `Persistence:Transactions` configures the default isolation level, timeout, and retry policy for explicit relational transactions.
-- Explicit transactional writes run inside EF Core's execution strategy so the full transaction block can be replayed on transient provider failures.
-- Nested transactional writes use savepoints inside the current `UnitOfWork` transaction instead of opening a second top-level transaction.
-- Per-call overrides use `ExecuteInTransactionAsync(action, ct, new TransactionOptions { ... })`; effective policy is `configured defaults + per-call override`.
-- Nested transaction calls inherit the active outer policy. Passing conflicting nested options fails fast instead of silently changing isolation, timeout, or retry behavior.
-
-```csharp
-// Wraps two repository writes in a single database transaction
-await _unitOfWork.ExecuteInTransactionAsync(async () =>
-{
- await _productRepository.AddAsync(product);
- await _reviewRepository.AddAsync(review);
-});
-// Both rows committed or both rolled back
-```
-
-```csharp
-await _unitOfWork.ExecuteInTransactionAsync(
- async () =>
- {
- await _productRepository.AddAsync(product, ct);
- await _reviewRepository.AddAsync(review, ct);
- },
- ct,
- new TransactionOptions
- {
- IsolationLevel = IsolationLevel.Serializable,
- TimeoutSeconds = 15,
- RetryEnabled = false
- });
-```
-
-Service code can call `IUnitOfWork` directly for explicit transactional writes:
-
-```csharp
-await _unitOfWork.ExecuteInTransactionAsync(async () =>
-{
- await _repository.AddAsync(product, ct);
- return product;
-}, ct);
-```
-
-### 3 — Specification Pattern (Ardalis.Specification)
-
-Query logic — filtering, ordering, pagination — lives in reusable `Specification` classes rather than being scattered across services or repositories. A single `ProductSpecification` encapsulates all product-list query rules.
-
-```csharp
-// Application/Specifications/ProductSpecification.cs
-public sealed class ProductSpecification : Specification
-{
- public ProductSpecification(ProductFilter filter)
- {
- Query.ApplyFilter(filter); // dynamic WHERE clauses
- Query.OrderByDescending(p => p.CreatedAt)
- .Select(p => new ProductResponse(...)); // projection to DTO
- Query.Skip((filter.PageNumber - 1) * filter.PageSize)
- .Take(filter.PageSize);
- }
-}
-```
-
-**Benefits:**
-- Keeps EF Core queries out of service classes.
-- Specifications are independently testable.
-- `ISpecificationEvaluator` (provided by `Ardalis.Specification.EntityFrameworkCore`) translates specs to SQL.
-
-### 4 — FluentValidation with Auto-Validation & Cross-Field Rules
-
-Models are validated automatically by `FluentValidationActionFilter` before the controller action body executes. Unlike Data Annotations, FluentValidation supports dynamic, cross-field business rules:
-
-```csharp
-// A shared base validator reused by both Create and Update validators
-public abstract class ProductRequestValidatorBase : AbstractValidator
- where T : IProductRequest
-{
- protected ProductRequestValidatorBase()
- {
- // Cross-field: Description is required only for expensive products
- RuleFor(x => x.Description)
- .NotEmpty().WithMessage("Description is required for products priced above 1000.")
- .When(x => x.Price > 1000);
- }
-}
-```
-
-Validator classes are auto-discovered via `AddValidatorsFromAssemblyContaining()` — no manual registration needed.
-
-### 5 — Global Exception Handling (`IExceptionHandler` + ProblemDetails)
-
-`ApiExceptionHandler` sits in the ASP.NET exception pipeline (`UseExceptionHandler`) and converts typed `AppException` instances into RFC 7807 `ProblemDetails` responses. HTTP status/title are mapped by exception type (`ValidationException`, `NotFoundException`, `ConflictException`, `ForbiddenException`), while `ErrorCode` is resolved from `AppException.ErrorCode` or metadata fallback. `DbUpdateConcurrencyException` is mapped directly to HTTP 409.
-
-| Exception type | HTTP Status | Logged at |
-| ------------------------------ | ----------- | --------- |
-| `NotFoundException` | 404 | Warning |
-| `ValidationException` | 400 | Warning |
-| `ForbiddenException` | 403 | Warning |
-| `ConflictException` | 409 | Warning |
-| `DbUpdateConcurrencyException` | 409 | Warning |
-| Anything else | 500 | Error |
-
-Response extensions are standardized through `AddProblemDetails(...)` customization:
-- `errorCode` (primary code, e.g. `PRD-0404`)
-- `traceId` (request correlation)
-- `metadata` (optional structured details for business errors)
-
-Example payload:
-
-```json
-{
- "type": "https://api-template.local/errors/PRD-0404",
- "title": "Not Found",
- "status": 404,
- "detail": "Product with id '...' not found.",
- "instance": "/api/v1/products/...",
- "traceId": "0HN...",
- "errorCode": "PRD-0404"
-}
-```
-
-Error code catalog:
-
-| Code | HTTP | Meaning |
-| ---------------------- | ---- | ---------------------------------------- |
-| `GEN-0001` | 500 | Unknown/unhandled server error |
-| `GEN-0400` | 400 | Generic validation failure |
-| `GEN-0404` | 404 | Generic resource not found |
-| `GEN-0409` | 409 | Generic conflict |
-| `GEN-0409-CONCURRENCY` | 409 | Optimistic concurrency conflict |
-| `AUTH-0403` | 403 | Forbidden |
-| `PRD-0404` | 404 | Product not found |
-| `CAT-0404` | 404 | Category not found |
-| `REV-0404` | 404 | Review not found |
-| `REV-2101` | 404 | Product not found when creating a review |
-
-> GraphQL requests are explicitly bypassed — HotChocolate handles its own error serialisation.
-
-### 6 — API Versioning (URL Segment)
-
-All controllers use URL-segment versioning (`/api/v1/…`) via `Asp.Versioning.Mvc`. The default version is `1.0`; new breaking changes should be introduced as `v2` controllers rather than modifying existing ones.
-
-```csharp
-[ApiVersion(1.0)]
-[Route("api/v{version:apiVersion}/[controller]")]
-public sealed class ProductsController : ControllerBase { ... }
-```
-
-### 7 — Multi-Tenancy & Audit
-
-All relational entities implement `IAuditableTenantEntity` (combines `ITenantEntity`, `IAuditableEntity`, `ISoftDeletable`). `AppDbContext` automatically:
-
-- **Applies global query filters** on every read: `!entity.IsDeleted && entity.TenantId == currentTenant`.
-- **Stamps audit fields** on Add (`CreatedAtUtc`, `CreatedBy`) and Modify (`UpdatedAtUtc`, `UpdatedBy`).
-- **Auto-assigns TenantId** on insert from the JWT claim resolved by `HttpTenantProvider`.
-- **Converts hard deletes to soft deletes**, running registered `ISoftDeleteCascadeRule` implementations to propagate to dependents (e.g. `ProductSoftDeleteCascadeRule` cascades to `ProductReviews`).
-
-### 8 — Rate Limiting
-
-All REST controller routes require the `fixed` rate-limit policy. Partitioning isolates limits per authenticated user or per IP for anonymous callers:
-
-```
-Partition key priority:
- 1. JWT username (authenticated users each get their own bucket)
- 2. Remote IP (anonymous callers share a per-IP bucket)
- 3. "anonymous" (fallback when neither is available)
-```
-
-Limits are configured in `appsettings.json` under `RateLimiting:Fixed` and resolved via `IOptions` so integration tests can override them without rebuilding the host.
-
-### 9 — Output Caching (Tenant-Isolated, DragonFly-backed)
-
-GET endpoints on Products, Categories, and Reviews use `[OutputCache(PolicyName = ...)]` with the `TenantAwareOutputCachePolicy`. This policy:
-
-1. **Enables caching for authenticated requests** (ASP.NET Core's default skips Authorization-header requests).
-2. **Varies the cache key by tenant ID** so one tenant never receives another tenant's cached response.
-
-When `Dragonfly:ConnectionString` is configured, all cache entries are stored in **DragonFly** so every application instance shares a single distributed cache. Without it, each instance maintains its own in-memory cache.
-
-Mutations (Create / Update / Delete) evict the relevant tag via `IOutputCacheStore.EvictByTagAsync()` so stale data is immediately invalidated.
-
-### 10 — GraphQL Security & Performance Guards
-
-HotChocolate is configured with several safeguards:
-
-| Guard | Setting | Purpose |
-| ----------------------------- | ---------------------- | ------------------------------------------------- |
-| `MaxPageSize` | 100 | Prevents unbounded result sets |
-| `DefaultPageSize` | 20 | Sensible default for clients |
-| `AddMaxExecutionDepthRule(5)` | depth ≤ 5 | Prevents deeply nested query attacks |
-| `AddAuthorization()` | policy support enabled | Enables `[Authorize]` on GraphQL fields/mutations |
-
-GraphQL query and mutation fields are protected with `[Authorize]`.
-
-### 11 — Automatic Schema Migration at Startup
-
-`UseDatabaseAsync()` runs EF Core migrations, auth bootstrap seeding, and MongoDB migrations automatically on startup. This means a fresh container deployment is fully self-initialising — no manual `dotnet ef database update` step required in production.
-
-```csharp
-// Extensions/ApplicationBuilderExtensions.cs
-await dbContext.Database.MigrateAsync(); // PostgreSQL (skipped for InMemory provider)
-await seeder.SeedAsync(); // bootstrap tenant + admin user
-await migrator.MigrateAsync(); // MongoDB (Kot.MongoDB.Migrations)
-```
-
-### 12 — Multi-Stage Docker Build
-
-The `Dockerfile` follows Docker's multi-stage build best practice to minimise the final image size:
-
-```
-Stage 1 (build) — mcr.microsoft.com/dotnet/sdk:10.0 ← includes compiler tools
-Stage 2 (publish) — same SDK, runs dotnet publish -c Release
-Stage 3 (final) — mcr.microsoft.com/dotnet/aspnet:10.0 ← runtime only, ~60 MB
-```
-
-Only the compiled artefacts from Stage 2 are copied into the slim Stage 3 runtime image.
-
-### 13 — Polyglot Persistence Decision Guide
-
-| Data characteristic | Recommended store |
-| ----------------------------------- | ----------------------------- |
-| Relational data with foreign keys | PostgreSQL |
-| Fixed, well-defined schema | PostgreSQL |
-| ACID transactions across tables | PostgreSQL |
-| Complex aggregations / reporting | PostgreSQL + stored procedure |
-| Semi-structured or evolving schemas | MongoDB |
-| Polymorphic document hierarchies | MongoDB |
-| Media metadata, logs, audit events | MongoDB |
-
-### 14 — Message Dispatch + CQRS Pattern (WolverineFx)
-
-All application logic is dispatched through **WolverineFx**. Controllers and GraphQL resolvers never call services directly — they send a typed command or query object through `IMessageBus`, and Wolverine routes it to the correct handler by convention.
-
-```
-Controller / GraphQL Resolver
- │ bus.InvokeAsync(new GetProductsQuery(filter))
- ▼
- Wolverine pipeline
- │ FluentValidation middleware (UseFluentValidation()) ← validation runs here
- ▼
- Handler (static HandleAsync method)
- │ dependencies injected as method parameters
- ▼
- Response returned to caller
-```
-
-#### Commands and Queries
-
-Each feature vertical defines commands/queries as plain records, each with a dedicated handler class containing a static `HandleAsync` method. Dependencies are injected as method parameters:
-
-```csharp
-// Application/Features/Product/Queries/GetProductsQuery.cs
-public sealed record GetProductsQuery(ProductFilter Filter);
-
-public sealed class GetProductsQueryHandler
-{
- public static async Task HandleAsync(
- GetProductsQuery query,
- IProductRepository repository,
- CancellationToken ct)
- {
- return await repository.GetPagedAsync(
- new ProductSpecification(query.Filter), query.Filter.PageNumber, query.Filter.PageSize, ct);
- }
-}
-
-// Application/Features/Product/Commands/CreateProductsCommand.cs
-public sealed record CreateProductsCommand(CreateProductsRequest Request);
-
-public sealed class CreateProductsCommandHandler
-{
- public static async Task HandleAsync(
- CreateProductsCommand command,
- IProductRepository repository,
- IUnitOfWork unitOfWork,
- IMessageBus bus,
- CancellationToken ct)
- { ... }
-}
-```
-
-The same pattern applies across all features: Products, Categories, ProductReviews, Users, and ProductData.
-
-#### Controller dispatch via IMessageBus
-
-Controllers inject only `IMessageBus` — they have no reference to any service or repository:
-
-```csharp
-public sealed class ProductsController(IMessageBus bus) : ApiControllerBase
-{
- [HttpGet]
- public async Task> GetAll(
- [FromQuery] ProductFilter filter, CancellationToken ct)
- => Ok(await bus.InvokeAsync(new GetProductsQuery(filter), ct));
-
- [HttpPost]
- public async Task> Create(
- CreateProductRequest request, CancellationToken ct)
- {
- var product = await bus.InvokeAsync(new CreateProductCommand(request), ct);
- return CreatedAtAction(nameof(GetById), new { id = product.Id, version = "1.0" }, product);
- }
-}
-```
-
-GraphQL resolvers and DataLoaders follow the same pattern using `[Service] IMessageBus bus` parameter injection.
-
-#### Cache invalidation via IMessageBus
-
-Write handlers publish cache invalidation events after a successful mutation using `IMessageBus.PublishAsync`. A dedicated handler listens and evicts the affected output-cache tags — keeping the mutation handler decoupled from any caching concern:
-
-```csharp
-// Application/Common/Events/CacheInvalidationNotification.cs
-public sealed record CacheInvalidationNotification(string CacheTag);
-
-// Handler publishes after mutation:
-await bus.PublishAsync(new CacheInvalidationNotification(CacheTags.Products));
-```
-
-#### FluentValidation middleware
-
-Wolverine's `UseFluentValidation()` middleware runs before every handler. It collects all `FluentValidation` failures for the request and throws a domain `ValidationException` if any fail — so handler code never receives invalid input. No manual pipeline behavior registration is needed.
-
-#### DI registration
-
-Wolverine discovers handlers by convention from registered assemblies — no manual per-handler registration needed:
-
-```csharp
-builder.Host.UseWolverine(opts =>
-{
- opts.Discovery.IncludeAssembly(typeof(CreateProductsCommand).Assembly); // Application handlers
- opts.Discovery.IncludeAssembly(typeof(Program).Assembly); // API handlers
- opts.UseFluentValidation(); // validation middleware
- opts.Durability.Mode = DurabilityMode.MediatorOnly; // in-process only
-});
-```
-
-**Benefits:**
-- Controllers and GraphQL resolvers are free of business logic — they only translate HTTP/GraphQL inputs to commands/queries.
-- Handlers are simple static methods with no base class or interface ceremony — dependencies arrive as method parameters.
-- Adding a new cross-cutting concern (logging, authorisation checks, timing) requires only a new Wolverine middleware (Before/After conventions) — no changes to any handler.
-- Each command or query is an explicit, named contract; the full request/response shape is visible at a glance.
-- Handler classes are independently unit-testable by directly instantiating them with mocked repositories.
-
----
-
-## 🗄 Stored Procedure Pattern (EF Core + PostgreSQL)
-
-EF Core's `FromSql()` lets you call stored procedures while still getting full object materialisation and parameterised queries. The pattern below is used for the `GET /api/v1/categories/{id}/stats` endpoint.
-
-### When to use a stored procedure
-
-| Situation | Use LINQ | Use Stored Procedure |
-| ----------------------------------- | -------- | -------------------- |
-| Simple CRUD filtering / paging | ✅ | |
-| Complex multi-table aggregations | | ✅ |
-| Reusable DB-side business logic | | ✅ |
-| Query needs full EF change tracking | ✅ | |
-
-### 4-step implementation
-
-**Step 1 — Keyless entity** (no backing table, only a result-set shape)
-
-```csharp
-// Domain/Entities/ProductCategoryStats.cs
-public sealed class ProductCategoryStats
-{
- public Guid CategoryId { get; set; }
- public string CategoryName { get; set; } = string.Empty;
- public long ProductCount { get; set; }
- public decimal AveragePrice { get; set; }
- public long TotalReviews { get; set; }
-}
-```
-
-**Step 2 — EF configuration** (`HasNoKey` + `ExcludeFromMigrations`)
-
-```csharp
-// Infrastructure/Persistence/Configurations/ProductCategoryStatsConfiguration.cs
-public sealed class ProductCategoryStatsConfiguration : IEntityTypeConfiguration
-{
- public void Configure(EntityTypeBuilder builder)
- {
- builder.HasNoKey();
- // No backing table — skip this type during 'dotnet ef migrations add'
- builder.ToTable("ProductCategoryStats", t => t.ExcludeFromMigrations());
- }
-}
-```
-
-**Step 3 — Migration** (create the PostgreSQL function in `Up`, drop it in `Down`)
-
-```csharp
-migrationBuilder.Sql("""
- CREATE OR REPLACE FUNCTION get_product_category_stats(p_category_id UUID)
- RETURNS TABLE(
- category_id UUID, category_name TEXT,
- product_count BIGINT, average_price NUMERIC, total_reviews BIGINT
- )
- LANGUAGE plpgsql AS $$
- BEGIN
- RETURN QUERY
- SELECT c."Id", c."Name"::TEXT,
- COUNT(DISTINCT p."Id"),
- COALESCE(AVG(p."Price"), 0),
- COUNT(pr."Id")
- FROM "Categories" c
- LEFT JOIN "Products" p ON p."CategoryId" = c."Id"
- LEFT JOIN "ProductReviews" pr ON pr."ProductId" = p."Id"
- WHERE c."Id" = p_category_id
- GROUP BY c."Id", c."Name";
- END;
- $$;
- """);
-
-// Down:
-migrationBuilder.Sql("DROP FUNCTION IF EXISTS get_product_category_stats(UUID);");
-```
-
-**Step 4 — Repository call** via `FromSql` (auto-parameterised, injection-safe)
-
-```csharp
-// Infrastructure/Repositories/CategoryRepository.cs
-public Task GetStatsByIdAsync(Guid categoryId, CancellationToken ct = default)
-{
- // The interpolated {categoryId} is converted to a @p0 parameter by EF Core —
- // never use string concatenation here.
- return AppDb.ProductCategoryStats
- .FromSql($"SELECT * FROM get_product_category_stats({categoryId})")
- .FirstOrDefaultAsync(ct);
-}
-```
-
-### Full request flow
-
-```
-GET /api/v1/categories/{id}/stats
- │
- ▼
-CategoriesController.GetStats()
- │
- ▼
-CategoryService.GetStatsAsync()
- │
- ▼
-CategoryRepository.GetStatsByIdAsync()
- │ FromSql($"SELECT * FROM get_product_category_stats({id})")
- ▼
-PostgreSQL → get_product_category_stats(p_category_id)
- │ returns: category_id, category_name, product_count, average_price, total_reviews
- ▼
-EF Core maps columns → ProductCategoryStats (keyless entity)
- │
- ▼
-ProductCategoryStatsResponse (DTO returned to client)
-```
-
----
-
-## 🍃 MongoDB Polymorphic Pattern (ProductData)
-
-The `ProductData` feature demonstrates a **polymorphic document model** in MongoDB, where a single collection stores two distinct subtypes (`ImageProductData`, `VideoProductData`) using the BSON discriminator pattern.
-
-### When to use MongoDB vs PostgreSQL
-
-| Situation | Use PostgreSQL | Use MongoDB |
-| ----------------------------------- | -------------- | ----------- |
-| Relational data with foreign keys | ✅ | |
-| Fixed, well-defined schema | ✅ | |
-| ACID transactions across tables | ✅ | |
-| Semi-structured or evolving schemas | | ✅ |
-| Polymorphic document hierarchies | | ✅ |
-| Media metadata, logs, events | | ✅ |
-
-### Discriminator-based inheritance
-
-```csharp
-// Domain/Entities/ProductData.cs
-[BsonDiscriminator(RootClass = true)]
-[BsonKnownTypes(typeof(ImageProductData), typeof(VideoProductData))]
-public abstract class ProductData
-{
- [BsonId]
- [BsonRepresentation(BsonType.ObjectId)]
- public string Id { get; init; } = ObjectId.GenerateNewId().ToString();
- public string Title { get; init; } = string.Empty;
- public string? Description { get; init; }
- public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
-}
-
-// Domain/Entities/ImageProductData.cs
-[BsonDiscriminator("image")]
-public sealed class ImageProductData : ProductData
-{
- public int Width { get; init; }
- public int Height { get; init; }
- public string Format { get; init; } = string.Empty; // jpg | png | gif | webp
- public long FileSizeBytes { get; init; }
-}
-
-// Domain/Entities/VideoProductData.cs
-[BsonDiscriminator("video")]
-public sealed class VideoProductData : ProductData
-{
- public int DurationSeconds { get; init; }
- public string Resolution { get; init; } = string.Empty; // 720p | 1080p | 4K
- public string Format { get; init; } = string.Empty; // mp4 | avi | mkv
- public long FileSizeBytes { get; init; }
-}
-```
-
-MongoDB stores a `_t` discriminator field automatically, enabling polymorphic queries against the single `product_data` collection.
-
-### REST endpoints
-
-Base route: `api/v{version}/product-data` — all endpoints require JWT authorization.
-
-| Method | Endpoint | Request | Response | Purpose |
-| -------- | -------- | ------------------------------- | --------------------------- | -------------------------- |
-| `GET` | `/` | Query: `type` (optional) | `List` | List all or filter by type |
-| `GET` | `/{id}` | MongoDB ObjectId string | `ProductDataResponse` / 404 | Get by ID |
-| `POST` | `/image` | `CreateImageProductDataRequest` | `ProductDataResponse` 201 | Create image metadata |
-| `POST` | `/video` | `CreateVideoProductDataRequest` | `ProductDataResponse` 201 | Create video metadata |
-| `DELETE` | `/{id}` | MongoDB ObjectId string | 204 No Content | Delete by ID |
-
-### Full request flow
-
-```
-POST /api/v1/product-data/image
- │
- ▼
-ProductDataController.CreateImage()
- │ FluentValidation auto-validates CreateImageProductDataRequest
- ▼
-ProductDataService.CreateImageAsync()
- │ Maps request → ImageProductData entity
- ▼
-ProductDataRepository.CreateAsync()
- │ InsertOneAsync into product_data collection
- ▼
-MongoDB → stores { _t: "image", Title, Width, Height, Format, ... }
- │
- ▼
-ProductDataMappings.ToResponse() (switch expression, polymorphic)
- │
- ▼
-ProductDataResponse (Type, Id, Title, Width, Height, Format, ...)
-```
-
----
-
-## 🚀 CI/CD & Deployments
-
-While not natively shipped via default configuration files, this structure allows simple portability across cloud ecosystems:
-
-**GitHub Actions / Azure Pipelines Structure:**
-1. **Restore:** `dotnet restore APITemplate.slnx`
-2. **Build:** `dotnet build --no-restore APITemplate.slnx`
-3. **Test:** `dotnet test --no-build APITemplate.slnx`
-4. **Publish Container:** `docker build -t apitemplate-image:1.0 -f src/APITemplate.Api/Dockerfile .`
-5. **Push Registry:** `docker push /apitemplate-image:1.0`
-
-Because the application encompasses the database (natively via DI) and HTTP context fully self-contained using containerization, it scales efficiently behind Kubernetes Ingress (Nginx) or any App Service / Container Apps equivalent, maintaining state natively using PostgreSQL and MongoDB.
-
----
-
-## 🧪 Testing
-
-The repository maintains an inclusive combination of **Unit Tests** and **Integration Tests** executing over a seamless Test-Host infrastructure.
-
-### Test structure
-
-| Folder | Technology | What it tests |
-| ------------------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------- |
-| `tests/APITemplate.Tests/Unit/Services/` | xUnit + Moq | Service business logic in isolation |
-| `tests/APITemplate.Tests/Unit/Repositories/` | xUnit + Moq | Repository filtering/query logic |
-| `tests/APITemplate.Tests/Unit/Validators/` | xUnit + FluentValidation.TestHelper | Validator rules per DTO |
-| `tests/APITemplate.Tests/Unit/ExceptionHandling/` | xUnit + Moq | Explicit `errorCode` mapping and exception-to-HTTP conversion in `ApiExceptionHandler` |
-| `tests/APITemplate.Tests/Integration/` | xUnit + `WebApplicationFactory` | Full HTTP round-trips over in-memory database |
-| `tests/APITemplate.Tests/Integration/Postgres/` | xUnit + Testcontainers.PostgreSql | Tenant isolation and transaction behaviour against a real PostgreSQL instance |
-
-### Integration test isolation
-
-`CustomWebApplicationFactory` replaces the Npgsql provider with `UseInMemoryDatabase`, removes `MongoDbContext`, and registers a mocked `IProductDataRepository` so DI validation passes. Each test class gets its own database name (a fresh `Guid`) so tests never share state.
-
-```csharp
-// Each factory instance gets its own isolated in-memory database
-private readonly string _dbName = Guid.NewGuid().ToString();
-services.AddDbContext(options =>
- options.UseInMemoryDatabase(_dbName));
-```
-
-### Running tests
-
-```bash
-# Run all tests
-dotnet test
-
-# Run only unit tests
-dotnet test --filter "FullyQualifiedName~Unit"
-
-# Run only integration tests (in-memory, no external dependencies)
-dotnet test --filter "FullyQualifiedName~Integration&Category!=Integration.Postgres"
-
-# Run Testcontainers PostgreSQL tests (requires Docker)
-dotnet test --filter "Category=Integration.Postgres"
-```
-
----
-
-## 🏃 Getting Started
-
-### Prerequisites
-* [.NET 10 SDK installed locally](https://dotnet.microsoft.com/)
-* [Docker Desktop](https://www.docker.com/) (Optional, convenient for running infrastructure).
-
-### Quick Start (Using Docker Compose)
-
-The template consists of a ready-to-use Docker environment to spool up PostgreSQL, MongoDB, Keycloak, DragonFly, and the built API container:
-
-```bash
-# Start up all services including the API container
-docker compose up -d --build
-```
-> The API will bind natively to `http://localhost:8080`.
-
-### Running Locally without Containerization
-
-Start the infrastructure services only, then run the API on the host:
-
-```bash
-# Start only the databases and Keycloak
-docker compose up -d postgres mongodb keycloak dragonfly
-```
-
-Apply your connection strings in `src/APITemplate.Api/appsettings.Development.json`, then run:
-
-```bash
-dotnet run --project src/APITemplate.Api
-```
-
-EF Core migrations and MongoDB migrations run automatically at startup — no manual `dotnet ef database update` needed.
-
-### Available Endpoints & User Interfaces
-
-Once fully spun up under a Development environment, check out:
-- **Interactive REST API Documentation (Scalar):** `http://localhost:/scalar`
-- **Native GraphQL IDE (Nitro UI):** `http://localhost:/graphql/ui`
-- **Environment & Database Health Check:** `http://localhost:/health`
diff --git a/monolith/API-Template-mono/TODO-Architecture.md b/monolith/API-Template-mono/TODO-Architecture.md
deleted file mode 100644
index cbeb9836..00000000
--- a/monolith/API-Template-mono/TODO-Architecture.md
+++ /dev/null
@@ -1,267 +0,0 @@
-# Architecture Evolution: Monolith to Microservices
-
-## Current State
-
-Well-structured **Clean Architecture monolith** with:
-- Vertical slice features (Product, Category, User, Tenant, Email, Webhooks, Jobs)
-- Wolverine as message bus (supports distributed transport)
-- Dual database (PostgreSQL + MongoDB)
-- Multi-tenancy with isolated query filters
-- Domain events for cross-feature communication
-
----
-
-## Identified Bounded Contexts
-
-| # | Service | Entities | Database |
-|---|---------|----------|----------|
-| 1 | **Product Catalog** | Product, Category, ProductData, ProductDataLink, ProductCategoryStats | PostgreSQL + MongoDB |
-| 2 | **Reviews** | ProductReview | PostgreSQL |
-| 3 | **Identity & Tenancy** | AppUser, Tenant, TenantInvitation | PostgreSQL + Keycloak |
-| 4 | **Notifications** | FailedEmail, email templates | PostgreSQL |
-| 5 | **File Storage** | StoredFile | PostgreSQL + filesystem/S3 |
-| 6 | **Background Jobs** | JobExecution | PostgreSQL (TickerQ) |
-| 7 | **Webhooks** | Incoming/Outgoing webhooks | In-memory queues |
-
----
-
-## Recommended Strategy: Modular Monolith → Strangler Fig
-
-### Phase 1 — Modular Monolith
-
-Transform the current monolith into isolated modules with explicit boundaries while keeping everything in a single deployable unit.
-
-#### Step 1: Create Module Structure
-
-Create a new directory layout under `src/Modules/`:
-
-```
-src/Modules/
- ProductCatalog/
- ProductCatalog.Domain/
- ProductCatalog.Application/
- ProductCatalog.Infrastructure/
- ProductCatalog.Api/
- Reviews/
- Reviews.Domain/
- Reviews.Application/
- Reviews.Infrastructure/
- Reviews.Api/
- Identity/
- Identity.Domain/
- Identity.Application/
- Identity.Infrastructure/
- Identity.Api/
- Notifications/
- Notifications.Domain/
- Notifications.Application/
- Notifications.Infrastructure/
- Notifications.Api/
- FileStorage/
- FileStorage.Domain/
- FileStorage.Application/
- FileStorage.Infrastructure/
- FileStorage.Api/
- BackgroundJobs/
- BackgroundJobs.Domain/
- BackgroundJobs.Application/
- BackgroundJobs.Infrastructure/
- BackgroundJobs.Api/
- Webhooks/
- Webhooks.Domain/
- Webhooks.Application/
- Webhooks.Infrastructure/
- Webhooks.Api/
-```
-
-#### Step 2: Extract Shared Kernel
-
-Create `src/SharedKernel/` containing cross-cutting concerns shared by all modules:
-
-- `IAuditableTenantEntity`, `IAuditableEntity`, `ISoftDeletable`, `IHasId`
-- `AuditInfo` value object
-- `ITenantProvider`, `IActorProvider`
-- `PagedResponse`
-- `IUnitOfWork` abstraction
-- `IRepository` base interface
-- Multi-tenancy infrastructure (global query filters, tenant resolution)
-- Soft-delete base infrastructure
-- Common domain exceptions (`NotFoundException`, `ValidationException`)
-- Audit stamping logic
-
-#### Step 3: Split AppDbContext
-
-Replace the single `AppDbContext` with per-module DbContexts:
-
-- `ProductCatalogDbContext` — Products, Categories, ProductDataLinks, ProductCategoryStats
-- `ReviewsDbContext` — ProductReviews
-- `IdentityDbContext` — AppUsers, Tenants, TenantInvitations
-- `NotificationsDbContext` — FailedEmails
-- `FileStorageDbContext` — StoredFiles
-- `BackgroundJobsDbContext` — JobExecutions
-
-All contexts share the same PostgreSQL database but enforce module boundaries — a module must not query another module's tables directly.
-
-#### Step 4: Define Module Contracts (Events)
-
-Create `src/Contracts/` as a shared NuGet package containing only:
-
-- Integration events (cross-module communication)
-- Shared DTOs for inter-module queries
-- No domain logic, no entities
-
-Example events:
-```
-ProductCreatedEvent { ProductId, TenantId, Name }
-ProductDeletedEvent { ProductId, TenantId }
-UserRegisteredEvent { UserId, TenantId, Email }
-TenantDeactivatedEvent { TenantId }
-```
-
-#### Step 5: Replace Direct Cross-Module Calls with Events
-
-Current direct dependencies to refactor:
-
-| Caller | Callee | Current | Target |
-|--------|--------|---------|--------|
-| Product soft-delete | Reviews cascade | `ProductSoftDeleteCascadeRule` calls ReviewRepository directly | Publish `ProductDeletedEvent` → Reviews module handles cascade |
-| Tenant soft-delete | Users, Products cascade | `TenantSoftDeleteCascadeRule` accesses multiple repositories | Publish `TenantDeactivatedEvent` → each module handles own cleanup |
-| ProductReview creation | User validation | Queries UserRepository | Reviews module stores read-only user projection, updated via `UserUpdatedEvent` |
-| Product creation | Category validation | Queries CategoryRepository | Both in same module (Product Catalog) — no change needed |
-| Email handlers | User/Tenant data | Queries user/tenant repos | Notifications module receives all needed data in the event payload |
-
-#### Step 6: Enforce Module Isolation
-
-- Each module exposes only its public API (controllers, events, query interfaces)
-- No module references another module's `Domain` or `Infrastructure` project
-- Communication exclusively through Wolverine events (in-process for now)
-- Add architecture tests (NetArchTest or ArchUnitNET) to enforce boundaries
-
-#### Step 7: Split GraphQL Schema
-
-- Each module defines its own GraphQL types, queries, and mutations
-- Use Hot Chocolate Schema Stitching to compose the unified schema
-- Prepare for future Hot Chocolate Federation when modules become services
-
-#### Step 8: Split REST Controllers
-
-- Move controllers into their respective module's `Api` project
-- Host module still composes all endpoints in `Program.cs`
-- Each module registers its own services via `IServiceCollection` extensions
-
----
-
-### Phase 2 — Strangler Fig Extraction
-
-Extract modules into independent services when scaling demands it. Start with the least coupled modules.
-
-#### Extraction Order (least to most coupled)
-
-1. **Notifications** — no inbound queries, only consumes events
-2. **File Storage** — simple CRUD, minimal dependencies
-3. **Webhooks** — event-driven by nature
-4. **Background Jobs** — independent scheduler
-5. **Reviews** — depends on Product (read-only projection)
-6. **Identity & Tenancy** — central but well-defined API (Keycloak handles heavy lifting)
-7. **Product Catalog** — core domain, extract last
-
-#### Step 1: Deploy API Gateway
-
-- Add YARP or Ocelot as reverse proxy
-- Route all traffic through gateway
-- Initially, gateway proxies everything to the monolith
-
-#### Step 2: Extract First Service (Notifications)
-
-1. Create standalone ASP.NET project from Notifications module
-2. Give it its own PostgreSQL database (or schema)
-3. Switch Wolverine transport from in-process to RabbitMQ:
- ```csharp
- // Before (in-process)
- opts.PublishMessage().Locally();
-
- // After (distributed)
- opts.PublishMessage()
- .ToRabbitQueue("notifications");
- ```
-4. Update API Gateway to route `/api/v1/notifications/*` to new service
-5. Remove Notifications module from monolith
-
-#### Step 3: Configure Distributed Messaging
-
-- Deploy RabbitMQ (or Azure Service Bus)
-- Enable Wolverine outbox pattern for guaranteed delivery:
- ```csharp
- opts.UseRabbitMq(rabbit => { ... })
- .AutoProvision()
- .UseConventionalRouting();
- opts.Policies.UseDurableOutboxOnAllSendingEndpoints();
- ```
-- Each service gets its own durable inbox/outbox
-
-#### Step 4: Database-per-Service
-
-- Each extracted service gets its own PostgreSQL database
-- Migrate data from shared DB to service-owned DB
-- Remove tables from monolith's DB after migration
-- MongoDB stays with Product Catalog service
-
-#### Step 5: Repeat for Each Service
-
-Follow the same pattern for each module in extraction order:
-1. Stand up independent service from module code
-2. Point its Wolverine transport to RabbitMQ
-3. Give it its own database
-4. Update API Gateway routing
-5. Remove module from monolith
-
-#### Step 6: Handle Cross-Service Queries
-
-For queries that span multiple services:
-
-- **API Composition** — Gateway aggregates responses from multiple services
-- **CQRS Read Models** — Services maintain denormalized read projections updated via events
-- **GraphQL Federation** — Hot Chocolate Federation composes subgraphs from each service
-
----
-
-## Infrastructure Requirements
-
-### Phase 1 (Modular Monolith)
-- No new infrastructure needed
-- Same PostgreSQL, MongoDB, Redis/DragonFly, Keycloak
-
-### Phase 2 (Microservices)
-- **Message Broker:** RabbitMQ or Azure Service Bus
-- **API Gateway:** YARP or Ocelot
-- **Container Orchestration:** Docker Compose (dev) → Kubernetes (prod)
-- **Service Discovery:** Kubernetes DNS or Consul
-- **Distributed Tracing:** Already have OpenTelemetry — works across services
-- **Centralized Logging:** Already have Serilog + OTLP — works across services
-- **Per-Service Databases:** Multiple PostgreSQL instances (or schemas)
-
----
-
-## Risk Mitigation
-
-| Risk | Mitigation |
-|------|------------|
-| Data inconsistency across services | Wolverine outbox pattern + idempotent handlers (already have `IIdempotencyStore`) |
-| Lost events | Durable messaging with RabbitMQ persistent queues + Wolverine dead letter queue |
-| Debugging complexity | OpenTelemetry distributed tracing (already configured) |
-| Service discovery failures | Kubernetes DNS + health checks (already have health check infrastructure) |
-| Database migration errors | Per-module DbContext in Phase 1 validates data boundaries before physical split |
-| GraphQL schema fragmentation | Hot Chocolate Federation maintains unified schema |
-
----
-
-## Key Wolverine Advantages
-
-The current Wolverine setup makes this transition significantly easier:
-
-1. **Transport agnostic** — switch from in-process to RabbitMQ/Azure SB with config change, not code change
-2. **Built-in outbox** — guaranteed message delivery across service boundaries
-3. **Durable inbox** — idempotent message processing
-4. **Saga support** — orchestrate multi-service workflows
-5. **FluentValidation middleware** — works identically in monolith and microservices
-6. **Handler discovery** — static handlers work the same regardless of deployment topology
diff --git a/monolith/API-Template-mono/TODO.md b/monolith/API-Template-mono/TODO.md
deleted file mode 100644
index da020c59..00000000
--- a/monolith/API-Template-mono/TODO.md
+++ /dev/null
@@ -1,127 +0,0 @@
-# TODO
-
-## Observability
-
-- [x] Add observability stack and instrumentation for metrics, tracing, and alerting.
-- [x] Add OpenTelemetry for traces, metrics, and correlation across database, HTTP, and cache operations.
-
-## User Workflows
-
-- [x] Add user registration workflow.
-- [x] Add user lifecycle workflows such as activation, deactivation, and role management.
-
-## Tenant Management
-
-- [ ] Add tenant creation workflow.
-- [ ] Add tenant removal workflow.
-
-## Product Data
-
-- [x] Add workflow for attaching `ProductData` records to products.
-- [x] Support many-to-many relationship where a single product can have multiple `ProductData` entries.
-
-## Notifications
-
-- [ ] Add email notification for user registration.
-- [ ] Add email notification for tenant invitation workflow.
-- [ ] Add email notification for password reset workflow.
-- [ ] Add email notification for user role changes.
-
-## Real-Time Communication (SignalR)
-
-Implement real-time notifications and chat using ASP.NET Core SignalR.
-
-**Architecture:**
-- NotificationHub: job status, data updates, user status
-- ChatHub: 1:1, groups, channels
-- Redis backplane for multi-instance
-- Optional persistence (flexible, add later if needed)
-
-**Implementation:**
-- [ ] Setup SignalR infrastructure (Hubs, backplane, middleware)
-- [ ] NotificationHub: job/product/user status updates
-- [ ] ChatHub: 1:1 messaging
-- [ ] ChatHub: group and channel messaging
-- [ ] Authorization and connection management
-- [ ] Client SDK (JavaScript/TypeScript)
-- [ ] Message persistence layer (pluggable design)
-- [ ] Error handling, reconnection, idempotency
-
-## Contracts
-
-- [ ] Extract request/response DTOs and shared contract models into a separate NuGet package.
-
-## Search
-
-- [x] Add full-text search for products and categories.
-- [x] Add faceted filtering for search results.
-
-## Background Jobs
-
-- [x] Add cleanup jobs for expired or orphaned data.
-- [x] Add reindex jobs for search data.
-- [x] Add retry jobs for failed notifications.
-- [x] Add periodic synchronization tasks for external integrations.
-- [x] Cursor-based pagination for orphaned ProductData cleanup to bound memory usage at scale.
-- [x] Distributed locking (`SELECT ... FOR UPDATE SKIP LOCKED` or claim column) for email retry to prevent duplicate sends in multi-instance deployments.
-- [x] Migrate from `PeriodicTimer` to Quartz.NET (or TickerQ) for CRON scheduling, persistent job state, and distributed locking.
-
-## Permissions
-
-- [ ] Add a finer-grained permissions model beyond roles.
-- [ ] Add policy-based access control per action and resource.
-
-## File and Media Handling
-
-- [ ] Add file upload support for `ProductData`.
-- [ ] Add storage abstraction for local and S3-compatible backends.
-- [ ] Add cleanup workflow for orphaned files.
-
-
-## Soft delete and Data Retention
-- [x] Hard delete for soft-deleted products after a configurable retention period.
-- [x] Add workflow for permanently deleting soft-deleted products after retention period.
-- [ ] Wolverine durable outbox or CAP for reliable messaging and eventual consistency in data deletion across related entities. (WolverineFx is now integrated as the in-process mediator; durable outbox mode can be enabled when needed.)
-
-## Result Pattern
-
-- [ ] Introduce `Result` pattern (e.g. via `OneOf` or custom type) for expected failures instead of exceptions as flow control.
-- [ ] Migrate validation, not-found, and conflict scenarios from exceptions to explicit return types.
-
-## Testing Improvements
-
-- [ ] Migrate key integration tests from in-memory EF Core to Testcontainers PostgreSQL for realistic database behavior.
-- [ ] Add tests covering PostgreSQL-specific behavior: migrations, `xmin` concurrency tokens, full-text search queries.
-
-## Modularization (Phase 1)
-
-- [ ] Split `AppDbContext` into per-module contexts (ProductCatalogDbContext, ReviewsDbContext, IdentityDbContext, etc.).
-- [ ] Replace direct cross-module calls (soft-delete cascade rules) with Wolverine integration events.
-- [ ] Add ArchUnitNET or NetArchTest architecture tests to enforce module boundaries.
-- [ ] See `TODO-Architecture.md` for full modular monolith plan.
-
-## Wolverine Outbox
-
-- [ ] Enable `UseDurableOutboxOnAllSendingEndpoints()` for reliable eventual consistency across modules.
-
-## Prioritization
-
-### High Priority
-
-**Tenant Management** — Tenant creation and removal workflows are core functionality for a multi-tenant system. Without them, tenants cannot be fully managed — currently only a bootstrap tenant exists via configuration. Includes tenant creation, admin assignment, deactivation, and complete removal with cascading cleanup of all related data (users, products, categories).
-
-**Notifications** — Email infrastructure is fully in place (SMTP client, FailedEmail entity, retry jobs with distributed locking). Only business logic is missing — email templates and handlers for registration, tenant invitation, password reset, and role changes. Minimal effort with high UX impact.
-
-### Medium Priority
-
-**Modularization (Phase 1)** — Split the monolith into isolated modules (ProductCatalog, Reviews, Identity, Notifications, FileStorage, BackgroundJobs, Webhooks). Includes splitting `AppDbContext` into per-module contexts, replacing direct cross-module calls with Wolverine integration events, and adding architecture tests to enforce boundaries. Prepares the project for future microservices extraction without changing business logic. See `TODO-Architecture.md` for the full plan.
-
-**Testing Improvements** — Migrate key integration tests from in-memory EF Core to Testcontainers PostgreSQL for realistic database behavior. The in-memory provider does not capture PostgreSQL-specific behavior — `xmin` concurrency tokens, full-text search, migrations, JSON operators. Testcontainers setup already exists in the project and needs to be extended to critical test suites.
-
-### Lower Priority
-
-**Result Pattern** — Gradually migrate from exceptions (`ValidationException`, `NotFoundException`) to explicit return types for expected failures. Removes exception throwing overhead in common scenarios and makes method signatures more transparent. Best introduced incrementally, starting with new features.
-
-**Contracts NuGet Package** — Extract request/response DTOs into a standalone package. Allows clients to reference only contracts without depending on the Application layer. Essential for future microservices extraction and sharing types with frontend clients.
-
-**Permissions** — Extend the 3-tier role model (PlatformAdmin, TenantAdmin, User) with finer-grained policy-based access control. Per-action and per-resource permissions enable more granular access control without needing to create new roles for every combination of privileges.
diff --git a/monolith/API-Template-mono/docker-compose.production.yml b/monolith/API-Template-mono/docker-compose.production.yml
deleted file mode 100644
index 81d8b064..00000000
--- a/monolith/API-Template-mono/docker-compose.production.yml
+++ /dev/null
@@ -1,206 +0,0 @@
-services:
- alloy:
- image: grafana/alloy:v1.10.2
- restart: unless-stopped
- volumes:
- - ./infrastructure/observability/alloy/config.alloy:/etc/alloy/config.alloy:ro
- command:
- - run
- - /etc/alloy/config.alloy
-
- prometheus:
- image: prom/prometheus:v3.4.2
- restart: unless-stopped
- command:
- - --config.file=/etc/prometheus/prometheus.yml
- - --storage.tsdb.path=/prometheus
- - --web.enable-remote-write-receiver
- volumes:
- - ./infrastructure/observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- - ./infrastructure/observability/prometheus/rules:/etc/prometheus/rules:ro
- - prometheusdata:/prometheus
-
- loki:
- image: grafana/loki:3.5.5
- restart: unless-stopped
- command: ["-config.file=/etc/loki/config.yml"]
- volumes:
- - ./infrastructure/observability/loki/config.yml:/etc/loki/config.yml:ro
- - lokidata:/loki
-
- tempo:
- image: grafana/tempo:2.9.1
- restart: unless-stopped
- command: ["-config.file=/etc/tempo/config.yml"]
- volumes:
- - ./infrastructure/observability/tempo/config.yml:/etc/tempo/config.yml:ro
- - tempodata:/var/tempo
-
- grafana:
- image: grafana/grafana:12.3.1
- restart: unless-stopped
- environment:
- GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
- GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD must be set}
- GF_AUTH_ANONYMOUS_ENABLED: "false"
- depends_on:
- - prometheus
- - loki
- - tempo
- volumes:
- - ./infrastructure/observability/grafana/grafana.ini:/etc/grafana/grafana.ini:ro
- - ./infrastructure/observability/grafana/provisioning:/etc/grafana/provisioning:ro
- - ./infrastructure/observability/grafana/dashboards:/var/lib/grafana/dashboards:ro
- - grafanadata:/var/lib/grafana
-
- postgres:
- image: postgres:17.2
- restart: unless-stopped
- environment:
- POSTGRES_USER: ${DB_USERNAME}
- POSTGRES_PASSWORD: ${DB_PASSWORD}
- POSTGRES_DB: ${DB_NAME:-apitemplate}
- volumes:
- - pgdata:/var/lib/postgresql/data
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME}"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- mongodb:
- image: mongo:8.0.4
- restart: unless-stopped
- volumes:
- - mongodata:/data/db
- healthcheck:
- test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- keycloak-db:
- image: postgres:17.2
- restart: unless-stopped
- environment:
- POSTGRES_USER: ${KC_DB_USERNAME}
- POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
- POSTGRES_DB: keycloak
- volumes:
- - keycloak-pgdata:/var/lib/postgresql/data
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U ${KC_DB_USERNAME}"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- keycloak:
- image: quay.io/keycloak/keycloak:26.1
- restart: unless-stopped
- command: start --optimized
- environment:
- KC_DB: postgres
- KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak
- KC_DB_USERNAME: ${KC_DB_USERNAME}
- KC_DB_PASSWORD: ${KC_DB_PASSWORD}
- KC_HOSTNAME: ${KC_HOSTNAME}
- KC_HTTPS_CERTIFICATE_FILE: /opt/keycloak/conf/server.crt
- KC_HTTPS_CERTIFICATE_KEY_FILE: /opt/keycloak/conf/server.key
- ports:
- - "8443:8443"
- depends_on:
- keycloak-db:
- condition: service_healthy
- healthcheck:
- test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8443"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 60s
-
- dragonfly-master:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
- restart: unless-stopped
- volumes:
- - dragonfly-master-data:/data
- command: dragonfly --maxmemory 512mb --proactor_threads 2 --requirepass ${DRAGONFLY_PASSWORD:-}
- healthcheck:
- test: ["CMD", "redis-cli", "-a", "${DRAGONFLY_PASSWORD:-}", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- dragonfly-replica:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
- restart: unless-stopped
- volumes:
- - dragonfly-replica-data:/data
- command: dragonfly --maxmemory 512mb --proactor_threads 2 --replicaof dragonfly-master 6379 --masterauth ${DRAGONFLY_PASSWORD:-} --requirepass ${DRAGONFLY_PASSWORD:-}
- depends_on:
- dragonfly-master:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "redis-cli", "-a", "${DRAGONFLY_PASSWORD:-}", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- dragonfly-proxy:
- image: haproxy:3.1-alpine
- restart: unless-stopped
- volumes:
- - ./infrastructure/dragonfly/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- depends_on:
- dragonfly-master:
- condition: service_healthy
- dragonfly-replica:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "haproxy", "-c", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- api:
- build:
- context: .
- dockerfile: src/APITemplate.Api/Dockerfile
- restart: unless-stopped
- ports:
- - "8080:8080"
- environment:
- ASPNETCORE_ENVIRONMENT: Production
- ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=${DB_NAME:-apitemplate};Username=${DB_USERNAME};Password=${DB_PASSWORD}"
- MongoDB__ConnectionString: "mongodb://mongodb:27017"
- MongoDB__DatabaseName: "${DB_NAME:-apitemplate}"
- Keycloak__realm: "${KC_REALM:-api-template}"
- Keycloak__auth-server-url: "https://keycloak:8443/"
- Keycloak__resource: "${KC_CLIENT_ID:-api-template}"
- Keycloak__credentials__secret: "${KC_CLIENT_SECRET}"
- Dragonfly__ConnectionString: "dragonfly-proxy:6379,password=${DRAGONFLY_PASSWORD:-}"
- APITEMPLATE_REDACTION_HMAC_KEY: "${REDACTION_HMAC_KEY}"
- Observability__Otlp__Endpoint: "http://alloy:4317"
- Observability__Exporters__Otlp__Enabled: "true"
- Observability__Exporters__Aspire__Enabled: "false"
- depends_on:
- alloy:
- condition: service_started
- postgres:
- condition: service_healthy
- mongodb:
- condition: service_healthy
- keycloak:
- condition: service_healthy
- dragonfly-proxy:
- condition: service_healthy
-
-volumes:
- prometheusdata:
- lokidata:
- tempodata:
- grafanadata:
- pgdata:
- mongodata:
- keycloak-pgdata:
- dragonfly-master-data:
- dragonfly-replica-data:
diff --git a/monolith/API-Template-mono/docker-compose.yml b/monolith/API-Template-mono/docker-compose.yml
deleted file mode 100644
index 775ddd3b..00000000
--- a/monolith/API-Template-mono/docker-compose.yml
+++ /dev/null
@@ -1,216 +0,0 @@
-services:
- alloy:
- image: grafana/alloy:v1.10.2
- ports:
- - "4317:4317"
- - "4318:4318"
- - "12345:12345"
- volumes:
- - ./infrastructure/observability/alloy/config.alloy:/etc/alloy/config.alloy:ro
- command:
- - run
- - /etc/alloy/config.alloy
-
- prometheus:
- image: prom/prometheus:v3.4.2
- command:
- - --config.file=/etc/prometheus/prometheus.yml
- - --storage.tsdb.path=/prometheus
- - --web.enable-remote-write-receiver
- ports:
- - "9090:9090"
- volumes:
- - ./infrastructure/observability/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- - ./infrastructure/observability/prometheus/rules:/etc/prometheus/rules:ro
- - prometheusdata:/prometheus
-
- loki:
- image: grafana/loki:3.5.5
- command: ["-config.file=/etc/loki/config.yml"]
- ports:
- - "3100:3100"
- volumes:
- - ./infrastructure/observability/loki/config.yml:/etc/loki/config.yml:ro
- - lokidata:/loki
-
- tempo:
- image: grafana/tempo:2.9.1
- command: ["-config.file=/etc/tempo/config.yml"]
- ports:
- - "3200:3200"
- - "4319:4317"
- volumes:
- - ./infrastructure/observability/tempo/config.yml:/etc/tempo/config.yml:ro
- - tempodata:/var/tempo
-
- grafana:
- image: grafana/grafana:12.3.1
- environment:
- GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
- GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
- GF_AUTH_ANONYMOUS_ENABLED: "false"
- ports:
- - "3001:3000"
- depends_on:
- - prometheus
- - loki
- - tempo
- volumes:
- - ./infrastructure/observability/grafana/grafana.ini:/etc/grafana/grafana.ini:ro
- - ./infrastructure/observability/grafana/provisioning:/etc/grafana/provisioning:ro
- - ./infrastructure/observability/grafana/dashboards:/var/lib/grafana/dashboards:ro
- - grafanadata:/var/lib/grafana
-
- aspire-dashboard:
- image: mcr.microsoft.com/dotnet/aspire-dashboard:9.5
- profiles: ["aspire"]
- ports:
- - "${ASPIRE_OTLP_GRPC_PORT:-4317}:18889"
- - "${ASPIRE_OTLP_HTTP_PORT:-4318}:18890"
- - "18888:18888"
- environment:
- ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS: "true"
- ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL: http://0.0.0.0:18890
-
- postgres:
- image: postgres:18.3
- environment:
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: postgres
- POSTGRES_DB: apitemplate
- ports:
- - "5432:5432"
- volumes:
- - pgdata:/var/lib/postgresql
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U postgres"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- mongodb:
- image: mongo:8.2
- ports:
- - "27017:27017"
- volumes:
- - mongodata:/data/db
- healthcheck:
- test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- keycloak-db:
- image: postgres:18.3
- environment:
- POSTGRES_USER: keycloak
- POSTGRES_PASSWORD: keycloak
- POSTGRES_DB: keycloak
- volumes:
- - keycloak-pgdata:/var/lib/postgresql
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U keycloak"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- keycloak:
- image: quay.io/keycloak/keycloak:26.5
- command: start-dev --import-realm
- environment:
- KC_DB: postgres
- KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak
- KC_DB_USERNAME: keycloak
- KC_DB_PASSWORD: keycloak
- KC_HTTP_PORT: 8180
- KC_BOOTSTRAP_ADMIN_USERNAME: admin
- KC_BOOTSTRAP_ADMIN_PASSWORD: admin
- ports:
- - "8180:8180"
- depends_on:
- keycloak-db:
- condition: service_healthy
- volumes:
- - ./infrastructure/keycloak/realms:/opt/keycloak/data/import
- healthcheck:
- test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8180"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 60s
-
- dragonfly:
- image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
- ports:
- - "6379:6379"
- volumes:
- - dragonflydata:/data
- command: dragonfly --maxmemory 512mb --proactor_threads 2
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- mailpit:
- image: axllent/mailpit:v1.29.0
- ports:
- - "8025:8025"
- - "1025:1025"
- environment:
- MP_MAX_MESSAGES: 5000
- MP_SMTP_AUTH_ACCEPT_ANY: 1
- MP_SMTP_AUTH_ALLOW_INSECURE: 1
- healthcheck:
- test: ["CMD", "mailpit", "ready-check"]
- interval: 10s
- timeout: 5s
- retries: 5
-
- api:
- build:
- context: .
- dockerfile: src/APITemplate.Api/Dockerfile
- ports:
- - "8080:8080"
- environment:
- ASPNETCORE_ENVIRONMENT: Development
- ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=apitemplate;Username=postgres;Password=postgres"
- MongoDB__ConnectionString: "mongodb://mongodb:27017"
- MongoDB__DatabaseName: "apitemplate"
- Keycloak__realm: "api-template"
- Keycloak__auth-server-url: "http://keycloak:8180/"
- Keycloak__resource: "api-template"
- Keycloak__credentials__secret: "${KC_DEV_CLIENT_SECRET:-dev-client-secret}"
- Dragonfly__ConnectionString: "dragonfly:6379"
- Observability__Otlp__Endpoint: "http://alloy:4317"
- Observability__Exporters__Otlp__Enabled: "true"
- Observability__Exporters__Aspire__Enabled: "false"
- Email__SmtpHost: "mailpit"
- Email__SmtpPort: "1025"
- Email__UseSsl: "false"
- depends_on:
- alloy:
- condition: service_started
- postgres:
- condition: service_healthy
- mongodb:
- condition: service_healthy
- keycloak:
- condition: service_healthy
- dragonfly:
- condition: service_healthy
- mailpit:
- condition: service_healthy
-
-volumes:
- prometheusdata:
- lokidata:
- tempodata:
- grafanadata:
- pgdata:
- mongodata:
- keycloak-pgdata:
- dragonflydata:
-
-
diff --git a/monolith/API-Template-mono/global.json b/monolith/API-Template-mono/global.json
deleted file mode 100644
index a11f48e1..00000000
--- a/monolith/API-Template-mono/global.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "sdk": {
- "version": "10.0.0",
- "rollForward": "latestMajor",
- "allowPrerelease": true
- }
-}
\ No newline at end of file
diff --git a/monolith/API-Template-mono/infrastructure/dragonfly/haproxy.cfg b/monolith/API-Template-mono/infrastructure/dragonfly/haproxy.cfg
deleted file mode 100644
index c744fa95..00000000
--- a/monolith/API-Template-mono/infrastructure/dragonfly/haproxy.cfg
+++ /dev/null
@@ -1,19 +0,0 @@
-global
- maxconn 256
-
-defaults
- mode tcp
- timeout connect 5s
- timeout client 30s
- timeout server 30s
-
-frontend dragonfly_front
- bind *:6379
- default_backend dragonfly_back
-
-backend dragonfly_back
- option tcp-check
- tcp-check send "PING\r\n"
- tcp-check expect string +PONG
- server master dragonfly-master:6379 check inter 3s fall 3 rise 2
- server replica dragonfly-replica:6379 check inter 3s fall 3 rise 2 backup
diff --git a/monolith/API-Template-mono/infrastructure/keycloak/realms/api-template-realm.json b/monolith/API-Template-mono/infrastructure/keycloak/realms/api-template-realm.json
deleted file mode 100644
index 419a9422..00000000
--- a/monolith/API-Template-mono/infrastructure/keycloak/realms/api-template-realm.json
+++ /dev/null
@@ -1,178 +0,0 @@
-{
- "realm": "api-template",
- "enabled": true,
- "rememberMe": true,
- "ssoSessionIdleTimeout": 1800,
- "ssoSessionMaxLifespan": 36000,
- "ssoSessionIdleTimeoutRememberMe": 604800,
- "ssoSessionMaxLifespanRememberMe": 1296000,
- "sslRequired": "none",
- "registrationAllowed": false,
- "loginWithEmailAllowed": true,
- "duplicateEmailsAllowed": false,
- "resetPasswordAllowed": false,
- "editUsernameAllowed": false,
- "passwordPolicy": "length(8) and upperCase(1) and digits(1) and forceExpiredPasswordChange(365)",
- "bruteForceProtected": true,
- "failureFactor": 5,
- "waitIncrementSeconds": 60,
- "maxFailureWaitSeconds": 900,
- "maxDeltaTimeSeconds": 3600,
- "revokeRefreshToken": true,
- "refreshTokenMaxReuse": 0,
- "roles": {
- "realm": [
- {
- "name": "PlatformAdmin",
- "description": "Platform administrator with full access"
- },
- {
- "name": "User",
- "description": "Regular user"
- }
- ]
- },
- "clients": [
- {
- "clientId": "api-template-scalar",
- "name": "API Template Scalar UI",
- "enabled": true,
- "publicClient": true,
- "redirectUris": ["http://localhost:5174/*", "http://localhost:8080/*"],
- "webOrigins": ["http://localhost:5174", "http://localhost:8080"],
- "attributes": {
- "pkce.code.challenge.method": "S256"
- },
- "standardFlowEnabled": true,
- "directAccessGrantsEnabled": false,
- "protocol": "openid-connect",
- "fullScopeAllowed": true,
- "protocolMappers": [
- {
- "name": "tenant_id",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "tenant_id",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "tenant_id",
- "jsonType.label": "String"
- }
- },
- {
- "name": "audience-mapper",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-mapper",
- "consentRequired": false,
- "config": {
- "included.client.audience": "api-template",
- "id.token.claim": "false",
- "access.token.claim": "true"
- }
- },
- {
- "name": "realm-roles",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-realm-role-mapper",
- "consentRequired": false,
- "config": {
- "claim.name": "realm_access.roles",
- "multivalued": "true",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }
- ]
- },
- {
- "clientId": "api-template",
- "name": "API Template Client",
- "enabled": true,
- "clientAuthenticatorType": "client-secret",
- "secret": "dev-client-secret",
- "redirectUris": [
- "http://localhost:5174/*",
- "http://localhost:8080/*"
- ],
- "webOrigins": [
- "http://localhost:5174",
- "http://localhost:8080"
- ],
- "attributes": {
- "post.logout.redirect.uris": "http://localhost:5174/*##http://localhost:8080/*",
- "pkce.code.challenge.method": "S256"
- },
- "standardFlowEnabled": true,
- "serviceAccountsEnabled": true,
- "directAccessGrantsEnabled": false,
- "publicClient": false,
- "protocol": "openid-connect",
- "fullScopeAllowed": true,
- "protocolMappers": [
- {
- "name": "tenant_id",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-attribute-mapper",
- "consentRequired": false,
- "config": {
- "userinfo.token.claim": "true",
- "user.attribute": "tenant_id",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "claim.name": "tenant_id",
- "jsonType.label": "String"
- }
- },
- {
- "name": "audience-mapper",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-audience-mapper",
- "consentRequired": false,
- "config": {
- "included.client.audience": "api-template",
- "id.token.claim": "false",
- "access.token.claim": "true"
- }
- },
- {
- "name": "realm-roles",
- "protocol": "openid-connect",
- "protocolMapper": "oidc-usermodel-realm-role-mapper",
- "consentRequired": false,
- "config": {
- "claim.name": "realm_access.roles",
- "multivalued": "true",
- "id.token.claim": "true",
- "access.token.claim": "true",
- "userinfo.token.claim": "true"
- }
- }
- ]
- }
- ],
- "users": [
- {
- "username": "admin",
- "enabled": true,
- "emailVerified": true,
- "email": "admin@example.com",
- "firstName": "Admin",
- "lastName": "User",
- "attributes": {
- "tenant_id": ["00000000-0000-0000-0000-000000000001"]
- },
- "credentials": [
- {
- "type": "password",
- "value": "Admin123",
- "temporary": false
- }
- ],
- "realmRoles": ["PlatformAdmin"]
- }
- ]
-}
diff --git a/monolith/API-Template-mono/infrastructure/kubernetes/dragonfly/README.md b/monolith/API-Template-mono/infrastructure/kubernetes/dragonfly/README.md
deleted file mode 100644
index 9e44dab8..00000000
--- a/monolith/API-Template-mono/infrastructure/kubernetes/dragonfly/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# DragonFly on Kubernetes
-
-## Prerequisites
-
-Install the DragonFly Kubernetes operator via Helm:
-
-```bash
-helm repo add dragonfly https://dragonflydb.github.io/dragonfly-operator/
-helm repo update
-helm install dragonfly-operator dragonfly/dragonfly-operator --namespace dragonfly-operator-system --create-namespace
-```
-
-## Deploy
-
-```bash
-kubectl create namespace apitemplate
-kubectl apply -f dragonfly.yml
-```
-
-## Connection
-
-The API connects to DragonFly via the operator-managed service:
-
-```
-dragonfly.apitemplate.svc.cluster.local:6379
-```
-
-Set this as the `Dragonfly__ConnectionString` environment variable in your API deployment.
-
-## How It Works
-
-The DragonFly operator manages:
-
-- **Automatic failover** — if the master pod fails, the operator promotes a replica to master
-- **Replica management** — maintains the desired replica count
-- **Rolling updates** — zero-downtime upgrades when the DragonFly image version changes
-
-No HAProxy is needed in Kubernetes — the operator handles service routing internally.
diff --git a/monolith/API-Template-mono/infrastructure/kubernetes/dragonfly/dragonfly.yml b/monolith/API-Template-mono/infrastructure/kubernetes/dragonfly/dragonfly.yml
deleted file mode 100644
index 75b2233d..00000000
--- a/monolith/API-Template-mono/infrastructure/kubernetes/dragonfly/dragonfly.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-apiVersion: dragonflydb.io/v1alpha1
-kind: Dragonfly
-metadata:
- name: dragonfly
- namespace: apitemplate
-spec:
- replicas: 2
- resources:
- requests:
- cpu: 100m
- memory: 512Mi
- limits:
- cpu: 500m
- memory: 512Mi
- args:
- - "--maxmemory"
- - "500mb"
- - "--proactor_threads"
- - "2"
diff --git a/monolith/API-Template-mono/infrastructure/observability/alloy/config.alloy b/monolith/API-Template-mono/infrastructure/observability/alloy/config.alloy
deleted file mode 100644
index ecf3747e..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/alloy/config.alloy
+++ /dev/null
@@ -1,54 +0,0 @@
-logging {
- level = "info"
- format = "logfmt"
-}
-
-otelcol.receiver.otlp "apitemplate" {
- grpc {
- endpoint = "0.0.0.0:4317"
- }
-
- http {
- endpoint = "0.0.0.0:4318"
- }
-
- output {
- metrics = [otelcol.exporter.prometheus.apitemplate.input]
- logs = [otelcol.exporter.otlphttp.loki.input]
- traces = [otelcol.processor.batch.traces.input]
- }
-}
-
-otelcol.processor.batch "traces" {
- output {
- traces = [otelcol.exporter.otlp.tempo.input]
- }
-}
-
-otelcol.exporter.otlp "tempo" {
- client {
- endpoint = "tempo:4317"
- tls {
- insecure = true
- }
- }
-}
-
-otelcol.exporter.otlphttp "loki" {
- client {
- endpoint = "http://loki:3100/otlp"
- tls {
- insecure = true
- }
- }
-}
-
-otelcol.exporter.prometheus "apitemplate" {
- forward_to = [prometheus.remote_write.default.receiver]
-}
-
-prometheus.remote_write "default" {
- endpoint {
- url = "http://prometheus:9090/api/v1/write"
- }
-}
diff --git a/monolith/API-Template-mono/infrastructure/observability/grafana/dashboards/apitemplate-overview.json b/monolith/API-Template-mono/infrastructure/observability/grafana/dashboards/apitemplate-overview.json
deleted file mode 100644
index cadbef61..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/grafana/dashboards/apitemplate-overview.json
+++ /dev/null
@@ -1,464 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "graphTooltip": 0,
- "id": null,
- "panels": [
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "orange",
- "value": 0.5
- },
- {
- "color": "red",
- "value": 1
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 0,
- "y": 0
- },
- "id": 1,
- "options": {
- "colorMode": "background",
- "graphMode": "area",
- "justifyMode": "center",
- "orientation": "auto",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "sum(rate(http_server_request_duration_seconds_count[5m]))",
- "refId": "A"
- }
- ],
- "title": "Request Rate",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "orange",
- "value": 0.02
- },
- {
- "color": "red",
- "value": 0.05
- }
- ]
- },
- "unit": "percentunit"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 6,
- "y": 0
- },
- "id": 2,
- "options": {
- "colorMode": "background",
- "graphMode": "none",
- "justifyMode": "center",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "sum(rate(http_server_request_duration_seconds_count{http_response_status_code=~\"5..\"}[5m])) / clamp_min(sum(rate(http_server_request_duration_seconds_count[5m])), 1)",
- "refId": "A"
- }
- ],
- "title": "5xx Ratio",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "orange",
- "value": 0.5
- },
- {
- "color": "red",
- "value": 0.75
- }
- ]
- },
- "unit": "s"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 12,
- "y": 0
- },
- "id": 3,
- "options": {
- "colorMode": "background",
- "graphMode": "none",
- "justifyMode": "center",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, sum by (le) (rate(http_server_request_duration_seconds_bucket[5m])))",
- "refId": "A"
- }
- ],
- "title": "p95 Latency",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "unit": "bytes"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 18,
- "y": 0
- },
- "id": 4,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "center",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "process_runtime_dotnet_gc_heap_size",
- "refId": "A"
- }
- ],
- "title": ".NET Heap Size",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 6
- },
- "id": 5,
- "targets": [
- {
- "expr": "sum by (http_route) (rate(http_server_request_duration_seconds_count[5m]))",
- "legendFormat": "{{http_route}}",
- "refId": "A"
- }
- ],
- "title": "Requests by Route",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 6
- },
- "id": 6,
- "targets": [
- {
- "expr": "sum by (http_method, server_address) (rate(http_client_request_duration_seconds_count[5m]))",
- "legendFormat": "{{http_method}} {{server_address}}",
- "refId": "A"
- }
- ],
- "title": "Outbound HTTP Calls",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 0,
- "y": 14
- },
- "id": 7,
- "targets": [
- {
- "expr": "max_over_time(apitemplate_healthcheck_status[5m])",
- "legendFormat": "{{service}}",
- "refId": "A"
- }
- ],
- "title": "Dependency Health",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "loki",
- "uid": "loki"
- },
- "gridPos": {
- "h": 8,
- "w": 12,
- "x": 12,
- "y": 14
- },
- "id": 8,
- "targets": [
- {
- "expr": "{service_name=\"APITemplate\"}",
- "queryType": "range",
- "refId": "A"
- }
- ],
- "title": "Recent API Logs",
- "type": "logs"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 0,
- "y": 22
- },
- "id": 9,
- "options": {
- "colorMode": "background",
- "graphMode": "area",
- "justifyMode": "center",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "sum(rate(apitemplate_graphql_requests[5m]))",
- "refId": "A"
- }
- ],
- "title": "GraphQL Request Rate",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "unit": "ms"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 6,
- "y": 22
- },
- "id": 10,
- "options": {
- "colorMode": "background",
- "graphMode": "none",
- "justifyMode": "center",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "histogram_quantile(0.95, sum by (le) (rate(apitemplate_graphql_request_duration_bucket[5m]))) * 1000",
- "refId": "A"
- }
- ],
- "title": "GraphQL p95",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "fieldConfig": {
- "defaults": {
- "unit": "ops"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 12,
- "y": 22
- },
- "id": 11,
- "options": {
- "colorMode": "background",
- "graphMode": "none",
- "justifyMode": "center",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- }
- },
- "targets": [
- {
- "expr": "sum(rate(apitemplate_rate_limit_rejections[5m]))",
- "refId": "A"
- }
- ],
- "title": "Rate Limit Rejections",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "gridPos": {
- "h": 6,
- "w": 6,
- "x": 18,
- "y": 22
- },
- "id": 12,
- "targets": [
- {
- "expr": "sum(rate(apitemplate_exceptions_handled[5m])) by (error_code, http_response_status_code)",
- "legendFormat": "{{error_code}} / {{http_response_status_code}}",
- "refId": "A"
- }
- ],
- "title": "Handled Exceptions",
- "type": "timeseries"
- }
- ],
- "refresh": "10s",
- "schemaVersion": 41,
- "style": "dark",
- "tags": ["apitemplate", "observability"],
- "templating": {
- "list": []
- },
- "time": {
- "from": "now-1h",
- "to": "now"
- },
- "timezone": "browser",
- "title": "APITemplate Overview",
- "uid": "apitemplate-overview",
- "version": 1
-}
diff --git a/monolith/API-Template-mono/infrastructure/observability/grafana/grafana.ini b/monolith/API-Template-mono/infrastructure/observability/grafana/grafana.ini
deleted file mode 100644
index 903d2170..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/grafana/grafana.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[security]
-allow_embedding = false
-
-[auth.anonymous]
-enabled = false
-
-[paths]
-provisioning = /etc/grafana/provisioning
diff --git a/monolith/API-Template-mono/infrastructure/observability/grafana/provisioning/dashboards/dashboards.yml b/monolith/API-Template-mono/infrastructure/observability/grafana/provisioning/dashboards/dashboards.yml
deleted file mode 100644
index 17893a17..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/grafana/provisioning/dashboards/dashboards.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: 1
-
-providers:
- - name: APITemplate
- folder: APITemplate
- type: file
- disableDeletion: false
- updateIntervalSeconds: 30
- options:
- path: /var/lib/grafana/dashboards
diff --git a/monolith/API-Template-mono/infrastructure/observability/grafana/provisioning/datasources/datasources.yml b/monolith/API-Template-mono/infrastructure/observability/grafana/provisioning/datasources/datasources.yml
deleted file mode 100644
index 8c6f021d..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/grafana/provisioning/datasources/datasources.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-apiVersion: 1
-
-datasources:
- - name: Prometheus
- uid: prometheus
- type: prometheus
- access: proxy
- url: http://prometheus:9090
- isDefault: true
- editable: false
-
- - name: Loki
- uid: loki
- type: loki
- access: proxy
- url: http://loki:3100
- editable: false
-
- - name: Tempo
- uid: tempo
- type: tempo
- access: proxy
- url: http://tempo:3200
- editable: false
- jsonData:
- tracesToLogsV2:
- datasourceUid: loki
- spanStartTimeShift: "-5m"
- spanEndTimeShift: "5m"
- tags:
- - key: service.name
- value: service_name
- serviceMap:
- datasourceUid: prometheus
diff --git a/monolith/API-Template-mono/infrastructure/observability/loki/config.yml b/monolith/API-Template-mono/infrastructure/observability/loki/config.yml
deleted file mode 100644
index e5e6aabe..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/loki/config.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-auth_enabled: false
-
-server:
- http_listen_port: 3100
-
-pattern_ingester:
- enabled: true
-
-distributor:
- otlp_config:
- default_resource_attributes_as_index_labels:
- - service.name
- - service.namespace
- - service.version
- - deployment.environment.name
- - host.name
-
-common:
- path_prefix: /loki
- replication_factor: 1
- ring:
- kvstore:
- store: inmemory
-
-schema_config:
- configs:
- - from: 2024-01-01
- store: tsdb
- object_store: filesystem
- schema: v13
- index:
- prefix: index_
- period: 24h
-
-storage_config:
- filesystem:
- directory: /loki/chunks
-
-ruler:
- alertmanager_url: http://localhost:9093
-
-limits_config:
- allow_structured_metadata: true
- volume_enabled: true
- discover_log_levels: true
- discover_service_name:
- - service_name
- - service
- - app
- - application
- - name
- - app_kubernetes_io_name
- - container
- - container_name
- - component
- - workload
- - job
- otlp_config:
- resource_attributes:
- ignore_defaults: false
- attributes_config:
- - action: index_label
- attributes:
- - service.name
- - service.namespace
- - service.version
- - deployment.environment.name
- - host.name
-
-analytics:
- reporting_enabled: false
diff --git a/monolith/API-Template-mono/infrastructure/observability/prometheus/prometheus.yml b/monolith/API-Template-mono/infrastructure/observability/prometheus/prometheus.yml
deleted file mode 100644
index f3a66c77..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/prometheus/prometheus.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-global:
- scrape_interval: 15s
- evaluation_interval: 15s
-
-rule_files:
- - /etc/prometheus/rules/*.yml
-
-scrape_configs:
- - job_name: prometheus
- static_configs:
- - targets: ["prometheus:9090"]
-
- - job_name: alloy
- static_configs:
- - targets: ["alloy:12345"]
-
- - job_name: tempo
- static_configs:
- - targets: ["tempo:3200"]
-
- - job_name: loki
- metrics_path: /metrics
- static_configs:
- - targets: ["loki:3100"]
diff --git a/monolith/API-Template-mono/infrastructure/observability/prometheus/rules/apitemplate-alerts.yml b/monolith/API-Template-mono/infrastructure/observability/prometheus/rules/apitemplate-alerts.yml
deleted file mode 100644
index e65acd92..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/prometheus/rules/apitemplate-alerts.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-groups:
- - name: apitemplate-observability
- rules:
- - alert: APITemplateTelemetryMissing
- expr: absent(http_server_request_duration_seconds_count)
- for: 10m
- labels:
- severity: warning
- annotations:
- summary: APITemplate telemetry is missing
- description: Prometheus has not received request metrics from the API for 10 minutes.
-
- - alert: APITemplateHigh5xxRate
- expr: sum(rate(http_server_request_duration_seconds_count{http_response_status_code=~"5.."}[5m])) / clamp_min(sum(rate(http_server_request_duration_seconds_count[5m])), 1) > 0.05
- for: 5m
- labels:
- severity: critical
- annotations:
- summary: APITemplate 5xx rate is elevated
- description: More than 5% of requests returned 5xx responses over the last 5 minutes.
-
- - alert: APITemplateHighP95Latency
- expr: histogram_quantile(0.95, sum by (le) (rate(http_server_request_duration_seconds_bucket[5m]))) > 0.75
- for: 10m
- labels:
- severity: warning
- annotations:
- summary: APITemplate p95 latency is elevated
- description: The API p95 latency has been above 750 ms for 10 minutes.
-
- - alert: APITemplateBackendHealthDegraded
- expr: max_over_time(apitemplate_healthcheck_status{service=~"postgresql|mongodb|dragonfly"}[5m]) < 1
- for: 5m
- labels:
- severity: critical
- annotations:
- summary: A backend dependency health check is failing
- description: One of the database or cache dependencies has reported an unhealthy state.
diff --git a/monolith/API-Template-mono/infrastructure/observability/tempo/config.yml b/monolith/API-Template-mono/infrastructure/observability/tempo/config.yml
deleted file mode 100644
index fdbb4945..00000000
--- a/monolith/API-Template-mono/infrastructure/observability/tempo/config.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-server:
- http_listen_port: 3200
-
-distributor:
- receivers:
- otlp:
- protocols:
- grpc:
- endpoint: 0.0.0.0:4317
- http:
- endpoint: 0.0.0.0:4318
-
-ingester:
- max_block_duration: 5m
-
-compactor:
- compaction:
- block_retention: 24h
-
-storage:
- trace:
- backend: local
- wal:
- path: /var/tempo/wal
- local:
- path: /var/tempo/blocks
-
-metrics_generator:
- storage:
- path: /var/tempo/generator
- processor:
- local_blocks:
- filter_server_spans: false
- flush_to_storage: true
-
-overrides:
- defaults:
- metrics_generator:
- processors: [service-graphs, span-metrics, local-blocks]
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.cs b/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.cs
deleted file mode 100644
index efcc325f..00000000
--- a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using APITemplate.Infrastructure.Database;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace APITemplate.Migrations
-{
- ///
- public partial class AddCategory : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "Categories",
- columns: table => new
- {
- Id = table.Column(type: "uuid", nullable: false),
- Name = table.Column(
- type: "character varying(100)",
- maxLength: 100,
- nullable: false
- ),
- Description = table.Column(
- type: "character varying(500)",
- maxLength: 500,
- nullable: true
- ),
- CreatedAt = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Categories", x => x.Id);
- }
- );
-
- migrationBuilder.CreateTable(
- name: "Products",
- columns: table => new
- {
- Id = table.Column(type: "uuid", nullable: false),
- Name = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: false
- ),
- Description = table.Column(
- type: "character varying(1000)",
- maxLength: 1000,
- nullable: true
- ),
- Price = table.Column(
- type: "numeric(18,2)",
- precision: 18,
- scale: 2,
- nullable: false
- ),
- CreatedAt = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- CategoryId = table.Column(type: "uuid", nullable: true),
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Products", x => x.Id);
- table.ForeignKey(
- name: "FK_Products_Categories_CategoryId",
- column: x => x.CategoryId,
- principalTable: "Categories",
- principalColumn: "Id",
- onDelete: ReferentialAction.SetNull
- );
- }
- );
-
- migrationBuilder.CreateTable(
- name: "ProductReviews",
- columns: table => new
- {
- Id = table.Column(type: "uuid", nullable: false),
- ProductId = table.Column(type: "uuid", nullable: false),
- ReviewerName = table.Column(
- type: "character varying(100)",
- maxLength: 100,
- nullable: false
- ),
- Comment = table.Column(
- type: "character varying(2000)",
- maxLength: 2000,
- nullable: true
- ),
- Rating = table.Column(type: "integer", nullable: false),
- CreatedAt = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_ProductReviews", x => x.Id);
- table.ForeignKey(
- name: "FK_ProductReviews_Products_ProductId",
- column: x => x.ProductId,
- principalTable: "Products",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade
- );
- }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_ProductReviews_ProductId",
- table: "ProductReviews",
- column: "ProductId"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_Products_CategoryId",
- table: "Products",
- column: "CategoryId"
- );
-
- migrationBuilder.Sql(
- SqlResource.Load("Procedures.get_product_category_stats_v1_up.sql")
- );
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.Sql(
- SqlResource.Load("Procedures.get_product_category_stats_v1_down.sql")
- );
-
- migrationBuilder.DropTable(name: "ProductReviews");
- migrationBuilder.DropTable(name: "Products");
- migrationBuilder.DropTable(name: "Categories");
- }
- }
-}
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260315000709_AddTenantInvitationNormalizedEmail.cs b/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260315000709_AddTenantInvitationNormalizedEmail.cs
deleted file mode 100644
index a8b80f09..00000000
--- a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260315000709_AddTenantInvitationNormalizedEmail.cs
+++ /dev/null
@@ -1,221 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace APITemplate.Migrations
-{
- ///
- public partial class AddTenantInvitationNormalizedEmail : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "PasswordResetTokens",
- columns: table => new
- {
- Id = table.Column(type: "uuid", nullable: false),
- UserId = table.Column(type: "uuid", nullable: false),
- TokenHash = table.Column(
- type: "character varying(128)",
- maxLength: 128,
- nullable: false
- ),
- ExpiresAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false
- ),
- IsUsed = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- TenantId = table.Column(type: "uuid", nullable: false),
- CreatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- CreatedBy = table.Column(
- type: "uuid",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- ),
- UpdatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- UpdatedBy = table.Column(
- type: "uuid",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- ),
- IsDeleted = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- DeletedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: true
- ),
- DeletedBy = table.Column(type: "uuid", nullable: true),
- xmin = table.Column(type: "xid", rowVersion: true, nullable: false),
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_PasswordResetTokens", x => x.Id);
- table.CheckConstraint(
- "CK_PasswordResetTokens_SoftDeleteConsistency",
- "\"IsDeleted\" OR (\"DeletedAtUtc\" IS NULL AND \"DeletedBy\" IS NULL)"
- );
- table.ForeignKey(
- name: "FK_PasswordResetTokens_Users_UserId",
- column: x => x.UserId,
- principalTable: "Users",
- principalColumn: "Id",
- onDelete: ReferentialAction.Restrict
- );
- }
- );
-
- migrationBuilder.CreateTable(
- name: "TenantInvitations",
- columns: table => new
- {
- Id = table.Column(type: "uuid", nullable: false),
- Email = table.Column(
- type: "character varying(320)",
- maxLength: 320,
- nullable: false
- ),
- NormalizedEmail = table.Column(
- type: "character varying(320)",
- maxLength: 320,
- nullable: false
- ),
- TokenHash = table.Column(
- type: "character varying(128)",
- maxLength: 128,
- nullable: false
- ),
- ExpiresAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false
- ),
- Status = table.Column(
- type: "character varying(32)",
- maxLength: 32,
- nullable: false,
- defaultValue: "Pending"
- ),
- TenantId = table.Column(type: "uuid", nullable: false),
- CreatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- CreatedBy = table.Column(
- type: "uuid",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- ),
- UpdatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- UpdatedBy = table.Column(
- type: "uuid",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- ),
- IsDeleted = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- DeletedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: true
- ),
- DeletedBy = table.Column(type: "uuid", nullable: true),
- xmin = table.Column(type: "xid", rowVersion: true, nullable: false),
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_TenantInvitations", x => x.Id);
- table.CheckConstraint(
- "CK_TenantInvitations_SoftDeleteConsistency",
- "\"IsDeleted\" OR (\"DeletedAtUtc\" IS NULL AND \"DeletedBy\" IS NULL)"
- );
- table.ForeignKey(
- name: "FK_TenantInvitations_Tenants_TenantId",
- column: x => x.TenantId,
- principalTable: "Tenants",
- principalColumn: "Id",
- onDelete: ReferentialAction.Restrict
- );
- }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_TenantId",
- table: "PasswordResetTokens",
- column: "TenantId"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_TenantId_IsDeleted",
- table: "PasswordResetTokens",
- columns: new[] { "TenantId", "IsDeleted" }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_TokenHash",
- table: "PasswordResetTokens",
- column: "TokenHash"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_UserId",
- table: "PasswordResetTokens",
- column: "UserId"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_TenantInvitations_TenantId",
- table: "TenantInvitations",
- column: "TenantId"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_TenantInvitations_TenantId_IsDeleted",
- table: "TenantInvitations",
- columns: new[] { "TenantId", "IsDeleted" }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_TenantInvitations_TenantId_NormalizedEmail",
- table: "TenantInvitations",
- columns: new[] { "TenantId", "NormalizedEmail" }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_TenantInvitations_TokenHash",
- table: "TenantInvitations",
- column: "TokenHash"
- );
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(name: "PasswordResetTokens");
-
- migrationBuilder.DropTable(name: "TenantInvitations");
- }
- }
-}
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260315014428_DropPasswordResetTokensTable.cs b/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260315014428_DropPasswordResetTokensTable.cs
deleted file mode 100644
index 4ec35f91..00000000
--- a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260315014428_DropPasswordResetTokensTable.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace APITemplate.Migrations
-{
- ///
- public partial class DropPasswordResetTokensTable : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(name: "PasswordResetTokens");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "PasswordResetTokens",
- columns: table => new
- {
- Id = table.Column(type: "uuid", nullable: false),
- UserId = table.Column(type: "uuid", nullable: false),
- DeletedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: true
- ),
- DeletedBy = table.Column(type: "uuid", nullable: true),
- ExpiresAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false
- ),
- IsDeleted = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- IsUsed = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- TenantId = table.Column(type: "uuid", nullable: false),
- TokenHash = table.Column(
- type: "character varying(128)",
- maxLength: 128,
- nullable: false
- ),
- xmin = table.Column(type: "xid", rowVersion: true, nullable: false),
- CreatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- CreatedBy = table.Column(
- type: "uuid",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- ),
- UpdatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- UpdatedBy = table.Column(
- type: "uuid",
- nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- ),
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_PasswordResetTokens", x => x.Id);
- table.CheckConstraint(
- "CK_PasswordResetTokens_SoftDeleteConsistency",
- "\"IsDeleted\" OR (\"DeletedAtUtc\" IS NULL AND \"DeletedBy\" IS NULL)"
- );
- table.ForeignKey(
- name: "FK_PasswordResetTokens_Users_UserId",
- column: x => x.UserId,
- principalTable: "Users",
- principalColumn: "Id",
- onDelete: ReferentialAction.Restrict
- );
- }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_TenantId",
- table: "PasswordResetTokens",
- column: "TenantId"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_TenantId_IsDeleted",
- table: "PasswordResetTokens",
- columns: new[] { "TenantId", "IsDeleted" }
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_TokenHash",
- table: "PasswordResetTokens",
- column: "TokenHash"
- );
-
- migrationBuilder.CreateIndex(
- name: "IX_PasswordResetTokens_UserId",
- table: "PasswordResetTokens",
- column: "UserId"
- );
- }
- }
-}
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/M002_AddProductDataSoftDeleteIndexes.cs b/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/M002_AddProductDataSoftDeleteIndexes.cs
deleted file mode 100644
index 091b25a7..00000000
--- a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/M002_AddProductDataSoftDeleteIndexes.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using APITemplate.Domain.Entities;
-using Kot.MongoDB.Migrations;
-using MongoDB.Driver;
-
-namespace APITemplate.Infrastructure.Migrations;
-
-public sealed class M002_AddProductDataSoftDeleteIndexes : MongoMigration
-{
- public M002_AddProductDataSoftDeleteIndexes()
- : base("1.1.0") { }
-
- public override Task UpAsync(
- IMongoDatabase db,
- IClientSessionHandle session,
- CancellationToken ct
- )
- {
- var collection = db.GetCollection("product_data");
-
- return collection.Indexes.CreateManyAsync(
- [
- new CreateIndexModel(
- Builders
- .IndexKeys.Ascending(x => x.TenantId)
- .Ascending(x => x.IsDeleted)
- .Ascending("_t"),
- new CreateIndexOptions { Name = "idx_tenant_is_deleted_type" }
- ),
- new CreateIndexModel(
- Builders
- .IndexKeys.Ascending(x => x.TenantId)
- .Ascending(x => x.IsDeleted)
- .Descending(x => x.CreatedAt),
- new CreateIndexOptions { Name = "idx_tenant_is_deleted_created" }
- ),
- new CreateIndexModel(
- Builders
- .IndexKeys.Ascending(x => x.TenantId)
- .Ascending(x => x.Id)
- .Ascending(x => x.IsDeleted),
- new CreateIndexOptions { Name = "idx_tenant_id_is_deleted" }
- ),
- ],
- ct
- );
- }
-
- public override async Task DownAsync(
- IMongoDatabase db,
- IClientSessionHandle session,
- CancellationToken ct
- )
- {
- var collection = db.GetCollection("product_data");
- await collection.Indexes.DropOneAsync("idx_tenant_is_deleted_type", ct);
- await collection.Indexes.DropOneAsync("idx_tenant_is_deleted_created", ct);
- await collection.Indexes.DropOneAsync("idx_tenant_id_is_deleted", ct);
- }
-}
diff --git a/monolith/API-Template-mono/tests/APITemplate.Tests/Unit/Handlers/UnitOfWorkMockExtensions.cs b/monolith/API-Template-mono/tests/APITemplate.Tests/Unit/Handlers/UnitOfWorkMockExtensions.cs
deleted file mode 100644
index 42395dda..00000000
--- a/monolith/API-Template-mono/tests/APITemplate.Tests/Unit/Handlers/UnitOfWorkMockExtensions.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using APITemplate.Domain.Interfaces;
-using APITemplate.Domain.Options;
-using Moq;
-
-namespace APITemplate.Tests.Unit.Handlers;
-
-internal static class UnitOfWorkMockExtensions
-{
- public static void SetupImmediateTransactionExecution(this Mock unitOfWorkMock)
- {
- unitOfWorkMock
- .Setup(u =>
- u.ExecuteInTransactionAsync(
- It.IsAny>(),
- It.IsAny(),
- It.IsAny()
- )
- )
- .Returns((Func action, CancellationToken _, TransactionOptions? _) => action());
- }
-
- public static void SetupImmediateTransactionExecution(this Mock unitOfWorkMock)
- {
- unitOfWorkMock
- .Setup(u =>
- u.ExecuteInTransactionAsync(
- It.IsAny>>(),
- It.IsAny(),
- It.IsAny()
- )
- )
- .Returns(
- (Func> action, CancellationToken _, TransactionOptions? _) => action()
- );
- }
-}
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/APITemplate.Api.csproj b/src/APITemplate.Api/APITemplate.Api.csproj
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/APITemplate.Api.csproj
rename to src/APITemplate.Api/APITemplate.Api.csproj
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/APITemplate.http b/src/APITemplate.Api/APITemplate.http
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/APITemplate.http
rename to src/APITemplate.Api/APITemplate.http
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/PermissionAuthorizationHandler.cs b/src/APITemplate.Api/Api/Authorization/PermissionAuthorizationHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/PermissionAuthorizationHandler.cs
rename to src/APITemplate.Api/Api/Authorization/PermissionAuthorizationHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/PermissionPolicyProvider.cs b/src/APITemplate.Api/Api/Authorization/PermissionPolicyProvider.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/PermissionPolicyProvider.cs
rename to src/APITemplate.Api/Api/Authorization/PermissionPolicyProvider.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/PermissionRequirement.cs b/src/APITemplate.Api/Api/Authorization/PermissionRequirement.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/PermissionRequirement.cs
rename to src/APITemplate.Api/Api/Authorization/PermissionRequirement.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/RequirePermissionAttribute.cs b/src/APITemplate.Api/Api/Authorization/RequirePermissionAttribute.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Authorization/RequirePermissionAttribute.cs
rename to src/APITemplate.Api/Api/Authorization/RequirePermissionAttribute.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/CacheInvalidationHandler.cs b/src/APITemplate.Api/Api/Cache/CacheInvalidationHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/CacheInvalidationHandler.cs
rename to src/APITemplate.Api/Api/Cache/CacheInvalidationHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/CachingOptions.cs b/src/APITemplate.Api/Api/Cache/CachingOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/CachingOptions.cs
rename to src/APITemplate.Api/Api/Cache/CachingOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/IOutputCacheInvalidationService.cs b/src/APITemplate.Api/Api/Cache/IOutputCacheInvalidationService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/IOutputCacheInvalidationService.cs
rename to src/APITemplate.Api/Api/Cache/IOutputCacheInvalidationService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/OutputCacheInvalidationService.cs b/src/APITemplate.Api/Api/Cache/OutputCacheInvalidationService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/OutputCacheInvalidationService.cs
rename to src/APITemplate.Api/Api/Cache/OutputCacheInvalidationService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/RedisInstanceNames.cs b/src/APITemplate.Api/Api/Cache/RedisInstanceNames.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/RedisInstanceNames.cs
rename to src/APITemplate.Api/Api/Cache/RedisInstanceNames.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/TenantAwareOutputCachePolicy.cs b/src/APITemplate.Api/Api/Cache/TenantAwareOutputCachePolicy.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Cache/TenantAwareOutputCachePolicy.cs
rename to src/APITemplate.Api/Api/Cache/TenantAwareOutputCachePolicy.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/ApiControllerBase.cs b/src/APITemplate.Api/Api/Controllers/ApiControllerBase.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/ApiControllerBase.cs
rename to src/APITemplate.Api/Api/Controllers/ApiControllerBase.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/BffController.cs b/src/APITemplate.Api/Api/Controllers/V1/BffController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/BffController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/BffController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/CategoriesController.cs b/src/APITemplate.Api/Api/Controllers/V1/CategoriesController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/CategoriesController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/CategoriesController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/FilesController.cs b/src/APITemplate.Api/Api/Controllers/V1/FilesController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/FilesController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/FilesController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/IdempotentController.cs b/src/APITemplate.Api/Api/Controllers/V1/IdempotentController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/IdempotentController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/IdempotentController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/JobsController.cs b/src/APITemplate.Api/Api/Controllers/V1/JobsController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/JobsController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/JobsController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/PatchController.cs b/src/APITemplate.Api/Api/Controllers/V1/PatchController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/PatchController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/PatchController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/ProductDataController.cs b/src/APITemplate.Api/Api/Controllers/V1/ProductDataController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/ProductDataController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/ProductDataController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/ProductReviewsController.cs b/src/APITemplate.Api/Api/Controllers/V1/ProductReviewsController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/ProductReviewsController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/ProductReviewsController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/ProductsController.cs b/src/APITemplate.Api/Api/Controllers/V1/ProductsController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/ProductsController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/ProductsController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/SseController.cs b/src/APITemplate.Api/Api/Controllers/V1/SseController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/SseController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/SseController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/TenantInvitationsController.cs b/src/APITemplate.Api/Api/Controllers/V1/TenantInvitationsController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/TenantInvitationsController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/TenantInvitationsController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/TenantsController.cs b/src/APITemplate.Api/Api/Controllers/V1/TenantsController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/TenantsController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/TenantsController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/UsersController.cs b/src/APITemplate.Api/Api/Controllers/V1/UsersController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/UsersController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/UsersController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/WebhooksController.cs b/src/APITemplate.Api/Api/Controllers/V1/WebhooksController.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Controllers/V1/WebhooksController.cs
rename to src/APITemplate.Api/Api/Controllers/V1/WebhooksController.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/ErrorOrMapping/ErrorOrExtensions.cs b/src/APITemplate.Api/Api/ErrorOrMapping/ErrorOrExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/ErrorOrMapping/ErrorOrExtensions.cs
rename to src/APITemplate.Api/Api/ErrorOrMapping/ErrorOrExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandler.cs b/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandler.cs
rename to src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandlerLogs.cs b/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandlerLogs.cs
similarity index 90%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandlerLogs.cs
rename to src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandlerLogs.cs
index 05638a78..6b77de85 100644
--- a/monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandlerLogs.cs
+++ b/src/APITemplate.Api/Api/ExceptionHandling/ApiExceptionHandlerLogs.cs
@@ -19,15 +19,13 @@ internal static partial class ApiExceptionHandlerLogs
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Error,
- Message = "Unhandled exception. StatusCode: {StatusCode}, ErrorCode: {ErrorCode}, TraceId: {TraceId}"
- )]
+ Message = "Unhandled exception. StatusCode: {StatusCode}, ErrorCode: {ErrorCode}, TraceId: {TraceId}")]
public static partial void UnhandledException(
this ILogger logger,
Exception exception,
int statusCode,
[SensitiveData] string errorCode,
- [PersonalData] string traceId
- );
+ [PersonalData] string traceId);
///
/// Logs a handled application exception (typically HTTP 4xx).
@@ -40,13 +38,11 @@ [PersonalData] string traceId
[LoggerMessage(
EventId = 1002,
Level = LogLevel.Warning,
- Message = "Handled application exception. StatusCode: {StatusCode}, ErrorCode: {ErrorCode}, TraceId: {TraceId}"
- )]
+ Message = "Handled application exception. StatusCode: {StatusCode}, ErrorCode: {ErrorCode}, TraceId: {TraceId}")]
public static partial void HandledApplicationException(
this ILogger logger,
Exception exception,
int statusCode,
[SensitiveData] string errorCode,
- [PersonalData] string traceId
- );
+ [PersonalData] string traceId);
}
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiProblemDetailsOptions.cs b/src/APITemplate.Api/Api/ExceptionHandling/ApiProblemDetailsOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/ExceptionHandling/ApiProblemDetailsOptions.cs
rename to src/APITemplate.Api/Api/ExceptionHandling/ApiProblemDetailsOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyActionFilter.cs b/src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyActionFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyActionFilter.cs
rename to src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyActionFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyConstants.cs b/src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyConstants.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyConstants.cs
rename to src/APITemplate.Api/Api/Filters/Idempotency/IdempotencyConstants.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Idempotency/IdempotentAttribute.cs b/src/APITemplate.Api/Api/Filters/Idempotency/IdempotentAttribute.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Idempotency/IdempotentAttribute.cs
rename to src/APITemplate.Api/Api/Filters/Idempotency/IdempotentAttribute.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Validation/FluentValidationActionFilter.cs b/src/APITemplate.Api/Api/Filters/Validation/FluentValidationActionFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Validation/FluentValidationActionFilter.cs
rename to src/APITemplate.Api/Api/Filters/Validation/FluentValidationActionFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Webhooks/ValidateWebhookSignatureAttribute.cs b/src/APITemplate.Api/Api/Filters/Webhooks/ValidateWebhookSignatureAttribute.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Webhooks/ValidateWebhookSignatureAttribute.cs
rename to src/APITemplate.Api/Api/Filters/Webhooks/ValidateWebhookSignatureAttribute.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Webhooks/WebhookSignatureResourceFilter.cs b/src/APITemplate.Api/Api/Filters/Webhooks/WebhookSignatureResourceFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Filters/Webhooks/WebhookSignatureResourceFilter.cs
rename to src/APITemplate.Api/Api/Filters/Webhooks/WebhookSignatureResourceFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/DataLoaders/ProductReviewsByProductDataLoader.cs b/src/APITemplate.Api/Api/GraphQL/DataLoaders/ProductReviewsByProductDataLoader.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/DataLoaders/ProductReviewsByProductDataLoader.cs
rename to src/APITemplate.Api/Api/GraphQL/DataLoaders/ProductReviewsByProductDataLoader.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/ErrorOrGraphQLExtensions.cs b/src/APITemplate.Api/Api/GraphQL/ErrorOrGraphQLExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/ErrorOrGraphQLExtensions.cs
rename to src/APITemplate.Api/Api/GraphQL/ErrorOrGraphQLExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Instrumentation/GraphQlExecutionMetricsListener.cs b/src/APITemplate.Api/Api/GraphQL/Instrumentation/GraphQlExecutionMetricsListener.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Instrumentation/GraphQlExecutionMetricsListener.cs
rename to src/APITemplate.Api/Api/GraphQL/Instrumentation/GraphQlExecutionMetricsListener.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/CategoryPageResult.cs b/src/APITemplate.Api/Api/GraphQL/Models/CategoryPageResult.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/CategoryPageResult.cs
rename to src/APITemplate.Api/Api/GraphQL/Models/CategoryPageResult.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/CategoryQueryInput.cs b/src/APITemplate.Api/Api/GraphQL/Models/CategoryQueryInput.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/CategoryQueryInput.cs
rename to src/APITemplate.Api/Api/GraphQL/Models/CategoryQueryInput.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductPageResult.cs b/src/APITemplate.Api/Api/GraphQL/Models/ProductPageResult.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductPageResult.cs
rename to src/APITemplate.Api/Api/GraphQL/Models/ProductPageResult.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductQueryInput.cs b/src/APITemplate.Api/Api/GraphQL/Models/ProductQueryInput.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductQueryInput.cs
rename to src/APITemplate.Api/Api/GraphQL/Models/ProductQueryInput.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductReviewPageResult.cs b/src/APITemplate.Api/Api/GraphQL/Models/ProductReviewPageResult.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductReviewPageResult.cs
rename to src/APITemplate.Api/Api/GraphQL/Models/ProductReviewPageResult.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductReviewQueryInput.cs b/src/APITemplate.Api/Api/GraphQL/Models/ProductReviewQueryInput.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Models/ProductReviewQueryInput.cs
rename to src/APITemplate.Api/Api/GraphQL/Models/ProductReviewQueryInput.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Mutations/ProductMutations.cs b/src/APITemplate.Api/Api/GraphQL/Mutations/ProductMutations.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Mutations/ProductMutations.cs
rename to src/APITemplate.Api/Api/GraphQL/Mutations/ProductMutations.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Mutations/ProductReviewMutations.cs b/src/APITemplate.Api/Api/GraphQL/Mutations/ProductReviewMutations.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Mutations/ProductReviewMutations.cs
rename to src/APITemplate.Api/Api/GraphQL/Mutations/ProductReviewMutations.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Queries/CategoryQueries.cs b/src/APITemplate.Api/Api/GraphQL/Queries/CategoryQueries.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Queries/CategoryQueries.cs
rename to src/APITemplate.Api/Api/GraphQL/Queries/CategoryQueries.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Queries/ProductQueries.cs b/src/APITemplate.Api/Api/GraphQL/Queries/ProductQueries.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Queries/ProductQueries.cs
rename to src/APITemplate.Api/Api/GraphQL/Queries/ProductQueries.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Queries/ProductReviewQueries.cs b/src/APITemplate.Api/Api/GraphQL/Queries/ProductReviewQueries.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Queries/ProductReviewQueries.cs
rename to src/APITemplate.Api/Api/GraphQL/Queries/ProductReviewQueries.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Types/ProductReviewType.cs b/src/APITemplate.Api/Api/GraphQL/Types/ProductReviewType.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Types/ProductReviewType.cs
rename to src/APITemplate.Api/Api/GraphQL/Types/ProductReviewType.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Types/ProductType.cs b/src/APITemplate.Api/Api/GraphQL/Types/ProductType.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Types/ProductType.cs
rename to src/APITemplate.Api/Api/GraphQL/Types/ProductType.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Types/ProductTypeResolvers.cs b/src/APITemplate.Api/Api/GraphQL/Types/ProductTypeResolvers.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/GraphQL/Types/ProductTypeResolvers.cs
rename to src/APITemplate.Api/Api/GraphQL/Types/ProductTypeResolvers.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Middleware/CsrfValidationMiddleware.cs b/src/APITemplate.Api/Api/Middleware/CsrfValidationMiddleware.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Middleware/CsrfValidationMiddleware.cs
rename to src/APITemplate.Api/Api/Middleware/CsrfValidationMiddleware.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Middleware/RequestContextMiddleware.cs b/src/APITemplate.Api/Api/Middleware/RequestContextMiddleware.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Middleware/RequestContextMiddleware.cs
rename to src/APITemplate.Api/Api/Middleware/RequestContextMiddleware.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/AuthorizationResponsesOperationTransformer.cs b/src/APITemplate.Api/Api/OpenApi/AuthorizationResponsesOperationTransformer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/AuthorizationResponsesOperationTransformer.cs
rename to src/APITemplate.Api/Api/OpenApi/AuthorizationResponsesOperationTransformer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/BearerSecuritySchemeDocumentTransformer.cs b/src/APITemplate.Api/Api/OpenApi/BearerSecuritySchemeDocumentTransformer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/BearerSecuritySchemeDocumentTransformer.cs
rename to src/APITemplate.Api/Api/OpenApi/BearerSecuritySchemeDocumentTransformer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/HealthCheckOpenApiDocumentTransformer.cs b/src/APITemplate.Api/Api/OpenApi/HealthCheckOpenApiDocumentTransformer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/HealthCheckOpenApiDocumentTransformer.cs
rename to src/APITemplate.Api/Api/OpenApi/HealthCheckOpenApiDocumentTransformer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/OpenApiErrorResponseHelper.cs b/src/APITemplate.Api/Api/OpenApi/OpenApiErrorResponseHelper.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/OpenApiErrorResponseHelper.cs
rename to src/APITemplate.Api/Api/OpenApi/OpenApiErrorResponseHelper.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/ProblemDetailsOpenApiTransformer.cs b/src/APITemplate.Api/Api/OpenApi/ProblemDetailsOpenApiTransformer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/OpenApi/ProblemDetailsOpenApiTransformer.cs
rename to src/APITemplate.Api/Api/OpenApi/ProblemDetailsOpenApiTransformer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Api/Requests/FileUploadRequest.cs b/src/APITemplate.Api/Api/Requests/FileUploadRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Api/Requests/FileUploadRequest.cs
rename to src/APITemplate.Api/Api/Requests/FileUploadRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Dockerfile b/src/APITemplate.Api/Dockerfile
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Dockerfile
rename to src/APITemplate.Api/Dockerfile
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/ApiServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/ApiServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/ApiServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/ApiServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/AuthenticationServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/AuthenticationServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/AuthenticationServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/AuthenticationServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/BackgroundJobsServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/BackgroundJobsServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/BackgroundJobsServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/BackgroundJobsServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/ConfigurationExtensions.cs b/src/APITemplate.Api/Extensions/Configuration/ConfigurationExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/ConfigurationExtensions.cs
rename to src/APITemplate.Api/Extensions/Configuration/ConfigurationExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/ConfigurationSections.cs b/src/APITemplate.Api/Extensions/Configuration/ConfigurationSections.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/ConfigurationSections.cs
rename to src/APITemplate.Api/Extensions/Configuration/ConfigurationSections.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/KeycloakStartupLogs.cs b/src/APITemplate.Api/Extensions/Configuration/KeycloakStartupLogs.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/KeycloakStartupLogs.cs
rename to src/APITemplate.Api/Extensions/Configuration/KeycloakStartupLogs.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/ServiceCollectionOptionsExtensions.cs b/src/APITemplate.Api/Extensions/Configuration/ServiceCollectionOptionsExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Configuration/ServiceCollectionOptionsExtensions.cs
rename to src/APITemplate.Api/Extensions/Configuration/ServiceCollectionOptionsExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/ControllerExtensions.cs b/src/APITemplate.Api/Extensions/ControllerExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/ControllerExtensions.cs
rename to src/APITemplate.Api/Extensions/ControllerExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/EmailServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/EmailServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/EmailServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/EmailServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/GraphQLServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/GraphQLServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/GraphQLServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/GraphQLServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/InfrastructureServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/InfrastructureServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/InfrastructureServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/InfrastructureServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/KeycloakAdminServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/KeycloakAdminServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/KeycloakAdminServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/KeycloakAdminServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/ObservabilityServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/ObservabilityServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/ObservabilityServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/ObservabilityServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/PersistenceServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/PersistenceServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/PersistenceServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/PersistenceServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Resilience/ResilienceDefaults.cs b/src/APITemplate.Api/Extensions/Resilience/ResilienceDefaults.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Resilience/ResilienceDefaults.cs
rename to src/APITemplate.Api/Extensions/Resilience/ResilienceDefaults.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/ServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/ServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/ServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/ServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Startup/ApplicationBuilderExtensions.cs b/src/APITemplate.Api/Extensions/Startup/ApplicationBuilderExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Startup/ApplicationBuilderExtensions.cs
rename to src/APITemplate.Api/Extensions/Startup/ApplicationBuilderExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/Startup/LoggingExtensions.cs b/src/APITemplate.Api/Extensions/Startup/LoggingExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/Startup/LoggingExtensions.cs
rename to src/APITemplate.Api/Extensions/Startup/LoggingExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/WebhookServiceCollectionExtensions.cs b/src/APITemplate.Api/Extensions/WebhookServiceCollectionExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/WebhookServiceCollectionExtensions.cs
rename to src/APITemplate.Api/Extensions/WebhookServiceCollectionExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/WolverineHandlerChainExtensions.cs b/src/APITemplate.Api/Extensions/WolverineHandlerChainExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/WolverineHandlerChainExtensions.cs
rename to src/APITemplate.Api/Extensions/WolverineHandlerChainExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Extensions/WolverineTypeExtensions.cs b/src/APITemplate.Api/Extensions/WolverineTypeExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Extensions/WolverineTypeExtensions.cs
rename to src/APITemplate.Api/Extensions/WolverineTypeExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/GlobalUsings.cs b/src/APITemplate.Api/GlobalUsings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/GlobalUsings.cs
rename to src/APITemplate.Api/GlobalUsings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Program.cs b/src/APITemplate.Api/Program.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Program.cs
rename to src/APITemplate.Api/Program.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/Properties/launchSettings.json b/src/APITemplate.Api/Properties/launchSettings.json
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/Properties/launchSettings.json
rename to src/APITemplate.Api/Properties/launchSettings.json
diff --git a/src/APITemplate.Api/appsettings.Development.json b/src/APITemplate.Api/appsettings.Development.json
new file mode 100644
index 00000000..eaee0f10
--- /dev/null
+++ b/src/APITemplate.Api/appsettings.Development.json
@@ -0,0 +1,43 @@
+{
+ "Dragonfly": {
+ "ConnectionString": "localhost:6379"
+ },
+ "Keycloak": {
+ "realm": "api-template",
+ "auth-server-url": "http://localhost:8180/",
+ "ssl-required": "none",
+ "resource": "api-template",
+ "verify-token-audience": true,
+ "credentials": {
+ "secret": "dev-client-secret"
+ },
+ "confidential-port": 0
+ },
+ "Email": {
+ "SmtpHost": "localhost",
+ "SmtpPort": 1025,
+ "UseSsl": false,
+ "SenderEmail": "noreply@apitemplate.local",
+ "SenderName": "APITemplate (Dev)",
+ "BaseUrl": "http://localhost:5000"
+ },
+ "Redaction": {
+ "HmacKeyEnvironmentVariable": "APITEMPLATE_REDACTION_HMAC_KEY",
+ "HmacKey": "mV7XhO9YXNw1fGKxvRrQz6CkKUL5jvN3i8A0Jv3cL2Q=",
+ "HmacKeyNote": "Development/debug only. Do not use this key in Release/Production; prefer APITEMPLATE_REDACTION_HMAC_KEY environment variable.",
+ "KeyId": 1001
+ },
+ "Webhook": {
+ "Secret": "dev-webhook-secret-at-least-16-chars"
+ },
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Debug",
+ "Override": {
+ "Microsoft": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "System": "Information"
+ }
+ }
+ }
+}
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/appsettings.Production.json b/src/APITemplate.Api/appsettings.Production.json
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/appsettings.Production.json
rename to src/APITemplate.Api/appsettings.Production.json
diff --git a/monolith/API-Template-mono/src/APITemplate.Api/appsettings.json b/src/APITemplate.Api/appsettings.json
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Api/appsettings.json
rename to src/APITemplate.Api/appsettings.json
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/APITemplate.Application.csproj b/src/APITemplate.Application/APITemplate.Application.csproj
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/APITemplate.Application.csproj
rename to src/APITemplate.Application/APITemplate.Application.csproj
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/ICleanupService.cs b/src/APITemplate.Application/Common/BackgroundJobs/ICleanupService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/ICleanupService.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/ICleanupService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IEmailRetryService.cs b/src/APITemplate.Application/Common/BackgroundJobs/IEmailRetryService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IEmailRetryService.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IEmailRetryService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IExternalIntegrationSyncService.cs b/src/APITemplate.Application/Common/BackgroundJobs/IExternalIntegrationSyncService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IExternalIntegrationSyncService.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IExternalIntegrationSyncService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IJobQueue.cs b/src/APITemplate.Application/Common/BackgroundJobs/IJobQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IJobQueue.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IJobQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IOutgoingWebhookQueue.cs b/src/APITemplate.Application/Common/BackgroundJobs/IOutgoingWebhookQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IOutgoingWebhookQueue.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IOutgoingWebhookQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IQueue.cs b/src/APITemplate.Application/Common/BackgroundJobs/IQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IQueue.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IQueueReader.cs b/src/APITemplate.Application/Common/BackgroundJobs/IQueueReader.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IQueueReader.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IQueueReader.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IRecurringBackgroundJobRegistration.cs b/src/APITemplate.Application/Common/BackgroundJobs/IRecurringBackgroundJobRegistration.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IRecurringBackgroundJobRegistration.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IRecurringBackgroundJobRegistration.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IReindexService.cs b/src/APITemplate.Application/Common/BackgroundJobs/IReindexService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IReindexService.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IReindexService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IWebhookProcessingQueue.cs b/src/APITemplate.Application/Common/BackgroundJobs/IWebhookProcessingQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/IWebhookProcessingQueue.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/IWebhookProcessingQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/RecurringBackgroundJobDefinition.cs b/src/APITemplate.Application/Common/BackgroundJobs/RecurringBackgroundJobDefinition.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/BackgroundJobs/RecurringBackgroundJobDefinition.cs
rename to src/APITemplate.Application/Common/BackgroundJobs/RecurringBackgroundJobDefinition.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/BatchFailureContext.cs b/src/APITemplate.Application/Common/Batch/BatchFailureContext.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/BatchFailureContext.cs
rename to src/APITemplate.Application/Common/Batch/BatchFailureContext.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/BatchFailureMerge.cs b/src/APITemplate.Application/Common/Batch/BatchFailureMerge.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/BatchFailureMerge.cs
rename to src/APITemplate.Application/Common/Batch/BatchFailureMerge.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/EntityLookup.cs b/src/APITemplate.Application/Common/Batch/EntityLookup.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/EntityLookup.cs
rename to src/APITemplate.Application/Common/Batch/EntityLookup.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/IBatchRule.cs b/src/APITemplate.Application/Common/Batch/IBatchRule.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/IBatchRule.cs
rename to src/APITemplate.Application/Common/Batch/IBatchRule.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/Rules/FluentValidationBatchRule.cs b/src/APITemplate.Application/Common/Batch/Rules/FluentValidationBatchRule.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/Rules/FluentValidationBatchRule.cs
rename to src/APITemplate.Application/Common/Batch/Rules/FluentValidationBatchRule.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/Rules/MarkMissingByIdBatchRule.cs b/src/APITemplate.Application/Common/Batch/Rules/MarkMissingByIdBatchRule.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Batch/Rules/MarkMissingByIdBatchRule.cs
rename to src/APITemplate.Application/Common/Batch/Rules/MarkMissingByIdBatchRule.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Context/IActorProvider.cs b/src/APITemplate.Application/Common/Context/IActorProvider.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Context/IActorProvider.cs
rename to src/APITemplate.Application/Common/Context/IActorProvider.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Context/ITenantProvider.cs b/src/APITemplate.Application/Common/Context/ITenantProvider.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Context/ITenantProvider.cs
rename to src/APITemplate.Application/Common/Context/ITenantProvider.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IDateRangeFilter.cs b/src/APITemplate.Application/Common/Contracts/IDateRangeFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IDateRangeFilter.cs
rename to src/APITemplate.Application/Common/Contracts/IDateRangeFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IFileStorageService.cs b/src/APITemplate.Application/Common/Contracts/IFileStorageService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IFileStorageService.cs
rename to src/APITemplate.Application/Common/Contracts/IFileStorageService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IIdempotencyStore.cs b/src/APITemplate.Application/Common/Contracts/IIdempotencyStore.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IIdempotencyStore.cs
rename to src/APITemplate.Application/Common/Contracts/IIdempotencyStore.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IProductRequest.cs b/src/APITemplate.Application/Common/Contracts/IProductRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IProductRequest.cs
rename to src/APITemplate.Application/Common/Contracts/IProductRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/ISortableFilter.cs b/src/APITemplate.Application/Common/Contracts/ISortableFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/ISortableFilter.cs
rename to src/APITemplate.Application/Common/Contracts/ISortableFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IWebhookEventHandler.cs b/src/APITemplate.Application/Common/Contracts/IWebhookEventHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IWebhookEventHandler.cs
rename to src/APITemplate.Application/Common/Contracts/IWebhookEventHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IWebhookPayloadSigner.cs b/src/APITemplate.Application/Common/Contracts/IWebhookPayloadSigner.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IWebhookPayloadSigner.cs
rename to src/APITemplate.Application/Common/Contracts/IWebhookPayloadSigner.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IWebhookPayloadValidator.cs b/src/APITemplate.Application/Common/Contracts/IWebhookPayloadValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Contracts/IWebhookPayloadValidator.cs
rename to src/APITemplate.Application/Common/Contracts/IWebhookPayloadValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/BatchDeleteRequest.cs b/src/APITemplate.Application/Common/DTOs/BatchDeleteRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/BatchDeleteRequest.cs
rename to src/APITemplate.Application/Common/DTOs/BatchDeleteRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/BatchResponse.cs b/src/APITemplate.Application/Common/DTOs/BatchResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/BatchResponse.cs
rename to src/APITemplate.Application/Common/DTOs/BatchResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/IHasFacets.cs b/src/APITemplate.Application/Common/DTOs/IHasFacets.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/IHasFacets.cs
rename to src/APITemplate.Application/Common/DTOs/IHasFacets.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/IPagedItems.cs b/src/APITemplate.Application/Common/DTOs/IPagedItems.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/IPagedItems.cs
rename to src/APITemplate.Application/Common/DTOs/IPagedItems.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/PaginationFilter.cs b/src/APITemplate.Application/Common/DTOs/PaginationFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/DTOs/PaginationFilter.cs
rename to src/APITemplate.Application/Common/DTOs/PaginationFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/EmailMessage.cs b/src/APITemplate.Application/Common/Email/EmailMessage.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/EmailMessage.cs
rename to src/APITemplate.Application/Common/Email/EmailMessage.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/EmailTemplateNames.cs b/src/APITemplate.Application/Common/Email/EmailTemplateNames.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/EmailTemplateNames.cs
rename to src/APITemplate.Application/Common/Email/EmailTemplateNames.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IEmailQueue.cs b/src/APITemplate.Application/Common/Email/IEmailQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IEmailQueue.cs
rename to src/APITemplate.Application/Common/Email/IEmailQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IEmailSender.cs b/src/APITemplate.Application/Common/Email/IEmailSender.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IEmailSender.cs
rename to src/APITemplate.Application/Common/Email/IEmailSender.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IEmailTemplateRenderer.cs b/src/APITemplate.Application/Common/Email/IEmailTemplateRenderer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IEmailTemplateRenderer.cs
rename to src/APITemplate.Application/Common/Email/IEmailTemplateRenderer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IFailedEmailStore.cs b/src/APITemplate.Application/Common/Email/IFailedEmailStore.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/IFailedEmailStore.cs
rename to src/APITemplate.Application/Common/Email/IFailedEmailStore.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Email/ISecureTokenGenerator.cs b/src/APITemplate.Application/Common/Email/ISecureTokenGenerator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Email/ISecureTokenGenerator.cs
rename to src/APITemplate.Application/Common/Email/ISecureTokenGenerator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Errors/DomainErrors.cs b/src/APITemplate.Application/Common/Errors/DomainErrors.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Errors/DomainErrors.cs
rename to src/APITemplate.Application/Common/Errors/DomainErrors.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Errors/ErrorCatalog.cs b/src/APITemplate.Application/Common/Errors/ErrorCatalog.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Errors/ErrorCatalog.cs
rename to src/APITemplate.Application/Common/Errors/ErrorCatalog.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/CacheEvents.cs b/src/APITemplate.Application/Common/Events/CacheEvents.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/CacheEvents.cs
rename to src/APITemplate.Application/Common/Events/CacheEvents.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/CacheTags.cs b/src/APITemplate.Application/Common/Events/CacheTags.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/CacheTags.cs
rename to src/APITemplate.Application/Common/Events/CacheTags.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/EmailEvents.cs b/src/APITemplate.Application/Common/Events/EmailEvents.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/EmailEvents.cs
rename to src/APITemplate.Application/Common/Events/EmailEvents.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/MessageBusExtensions.cs b/src/APITemplate.Application/Common/Events/MessageBusExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/MessageBusExtensions.cs
rename to src/APITemplate.Application/Common/Events/MessageBusExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/SoftDeleteEvents.cs b/src/APITemplate.Application/Common/Events/SoftDeleteEvents.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/SoftDeleteEvents.cs
rename to src/APITemplate.Application/Common/Events/SoftDeleteEvents.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/TenantInvitationEmailHandler.cs b/src/APITemplate.Application/Common/Events/TenantInvitationEmailHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/TenantInvitationEmailHandler.cs
rename to src/APITemplate.Application/Common/Events/TenantInvitationEmailHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/UserRegisteredEmailHandler.cs b/src/APITemplate.Application/Common/Events/UserRegisteredEmailHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/UserRegisteredEmailHandler.cs
rename to src/APITemplate.Application/Common/Events/UserRegisteredEmailHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Events/UserRoleChangedEmailHandler.cs b/src/APITemplate.Application/Common/Events/UserRoleChangedEmailHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Events/UserRoleChangedEmailHandler.cs
rename to src/APITemplate.Application/Common/Events/UserRoleChangedEmailHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Extensions/RepositoryExtensions.cs b/src/APITemplate.Application/Common/Extensions/RepositoryExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Extensions/RepositoryExtensions.cs
rename to src/APITemplate.Application/Common/Extensions/RepositoryExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Http/RateLimitPolicies.cs b/src/APITemplate.Application/Common/Http/RateLimitPolicies.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Http/RateLimitPolicies.cs
rename to src/APITemplate.Application/Common/Http/RateLimitPolicies.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Http/RequestContextConstants.cs b/src/APITemplate.Application/Common/Http/RequestContextConstants.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Http/RequestContextConstants.cs
rename to src/APITemplate.Application/Common/Http/RequestContextConstants.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Middleware/ErrorOrValidationMiddleware.cs b/src/APITemplate.Application/Common/Middleware/ErrorOrValidationMiddleware.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Middleware/ErrorOrValidationMiddleware.cs
rename to src/APITemplate.Application/Common/Middleware/ErrorOrValidationMiddleware.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/AppOptions.cs b/src/APITemplate.Application/Common/Options/AppOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/AppOptions.cs
rename to src/APITemplate.Application/Common/Options/AppOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/BackgroundJobsOptions.cs b/src/APITemplate.Application/Common/Options/BackgroundJobs/BackgroundJobsOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/BackgroundJobsOptions.cs
rename to src/APITemplate.Application/Common/Options/BackgroundJobs/BackgroundJobsOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/CleanupJobOptions.cs b/src/APITemplate.Application/Common/Options/BackgroundJobs/CleanupJobOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/CleanupJobOptions.cs
rename to src/APITemplate.Application/Common/Options/BackgroundJobs/CleanupJobOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/EmailRetryJobOptions.cs b/src/APITemplate.Application/Common/Options/BackgroundJobs/EmailRetryJobOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/EmailRetryJobOptions.cs
rename to src/APITemplate.Application/Common/Options/BackgroundJobs/EmailRetryJobOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/ExternalSyncJobOptions.cs b/src/APITemplate.Application/Common/Options/BackgroundJobs/ExternalSyncJobOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/ExternalSyncJobOptions.cs
rename to src/APITemplate.Application/Common/Options/BackgroundJobs/ExternalSyncJobOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/ReindexJobOptions.cs b/src/APITemplate.Application/Common/Options/BackgroundJobs/ReindexJobOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/ReindexJobOptions.cs
rename to src/APITemplate.Application/Common/Options/BackgroundJobs/ReindexJobOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/TickerQSchedulerOptions.cs b/src/APITemplate.Application/Common/Options/BackgroundJobs/TickerQSchedulerOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BackgroundJobs/TickerQSchedulerOptions.cs
rename to src/APITemplate.Application/Common/Options/BackgroundJobs/TickerQSchedulerOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BootstrapTenantOptions.cs b/src/APITemplate.Application/Common/Options/BootstrapTenantOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/BootstrapTenantOptions.cs
rename to src/APITemplate.Application/Common/Options/BootstrapTenantOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/DragonflyOptions.cs b/src/APITemplate.Application/Common/Options/Infrastructure/DragonflyOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/DragonflyOptions.cs
rename to src/APITemplate.Application/Common/Options/Infrastructure/DragonflyOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/EmailOptions.cs b/src/APITemplate.Application/Common/Options/Infrastructure/EmailOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/EmailOptions.cs
rename to src/APITemplate.Application/Common/Options/Infrastructure/EmailOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/FileStorageOptions.cs b/src/APITemplate.Application/Common/Options/Infrastructure/FileStorageOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/FileStorageOptions.cs
rename to src/APITemplate.Application/Common/Options/Infrastructure/FileStorageOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/ObservabilityOptions.cs b/src/APITemplate.Application/Common/Options/Infrastructure/ObservabilityOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/ObservabilityOptions.cs
rename to src/APITemplate.Application/Common/Options/Infrastructure/ObservabilityOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/TransactionDefaultsOptions.cs b/src/APITemplate.Application/Common/Options/Infrastructure/TransactionDefaultsOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/TransactionDefaultsOptions.cs
rename to src/APITemplate.Application/Common/Options/Infrastructure/TransactionDefaultsOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/WebhookOptions.cs b/src/APITemplate.Application/Common/Options/Infrastructure/WebhookOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Infrastructure/WebhookOptions.cs
rename to src/APITemplate.Application/Common/Options/Infrastructure/WebhookOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/BffOptions.cs b/src/APITemplate.Application/Common/Options/Security/BffOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/BffOptions.cs
rename to src/APITemplate.Application/Common/Options/Security/BffOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/CorsOptions.cs b/src/APITemplate.Application/Common/Options/Security/CorsOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/CorsOptions.cs
rename to src/APITemplate.Application/Common/Options/Security/CorsOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/KeycloakOptions.cs b/src/APITemplate.Application/Common/Options/Security/KeycloakOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/KeycloakOptions.cs
rename to src/APITemplate.Application/Common/Options/Security/KeycloakOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/RateLimitingOptions.cs b/src/APITemplate.Application/Common/Options/Security/RateLimitingOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/RateLimitingOptions.cs
rename to src/APITemplate.Application/Common/Options/Security/RateLimitingOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/RedactionOptions.cs b/src/APITemplate.Application/Common/Options/Security/RedactionOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/RedactionOptions.cs
rename to src/APITemplate.Application/Common/Options/Security/RedactionOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/SystemIdentityOptions.cs b/src/APITemplate.Application/Common/Options/Security/SystemIdentityOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Options/Security/SystemIdentityOptions.cs
rename to src/APITemplate.Application/Common/Options/Security/SystemIdentityOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Resilience/ResiliencePipelineKeys.cs b/src/APITemplate.Application/Common/Resilience/ResiliencePipelineKeys.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Resilience/ResiliencePipelineKeys.cs
rename to src/APITemplate.Application/Common/Resilience/ResiliencePipelineKeys.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Search/SearchDefaults.cs b/src/APITemplate.Application/Common/Search/SearchDefaults.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Search/SearchDefaults.cs
rename to src/APITemplate.Application/Common/Search/SearchDefaults.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Security/AuthConstants.cs b/src/APITemplate.Application/Common/Security/AuthConstants.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Security/AuthConstants.cs
rename to src/APITemplate.Application/Common/Security/AuthConstants.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Security/IKeycloakAdminService.cs b/src/APITemplate.Application/Common/Security/IKeycloakAdminService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Security/IKeycloakAdminService.cs
rename to src/APITemplate.Application/Common/Security/IKeycloakAdminService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Security/IRolePermissionMap.cs b/src/APITemplate.Application/Common/Security/IRolePermissionMap.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Security/IRolePermissionMap.cs
rename to src/APITemplate.Application/Common/Security/IRolePermissionMap.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Security/IUserProvisioningService.cs b/src/APITemplate.Application/Common/Security/IUserProvisioningService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Security/IUserProvisioningService.cs
rename to src/APITemplate.Application/Common/Security/IUserProvisioningService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Security/Permission.cs b/src/APITemplate.Application/Common/Security/Permission.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Security/Permission.cs
rename to src/APITemplate.Application/Common/Security/Permission.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Security/StaticRolePermissionMap.cs b/src/APITemplate.Application/Common/Security/StaticRolePermissionMap.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Security/StaticRolePermissionMap.cs
rename to src/APITemplate.Application/Common/Security/StaticRolePermissionMap.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Sorting/SortField.cs b/src/APITemplate.Application/Common/Sorting/SortField.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Sorting/SortField.cs
rename to src/APITemplate.Application/Common/Sorting/SortField.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Sorting/SortFieldMap.cs b/src/APITemplate.Application/Common/Sorting/SortFieldMap.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Sorting/SortFieldMap.cs
rename to src/APITemplate.Application/Common/Sorting/SortFieldMap.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Startup/IStartupTaskCoordinator.cs b/src/APITemplate.Application/Common/Startup/IStartupTaskCoordinator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Startup/IStartupTaskCoordinator.cs
rename to src/APITemplate.Application/Common/Startup/IStartupTaskCoordinator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Startup/StartupTaskNames.cs b/src/APITemplate.Application/Common/Startup/StartupTaskNames.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Startup/StartupTaskNames.cs
rename to src/APITemplate.Application/Common/Startup/StartupTaskNames.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/DataAnnotationsValidator.cs b/src/APITemplate.Application/Common/Validation/DataAnnotationsValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/DataAnnotationsValidator.cs
rename to src/APITemplate.Application/Common/Validation/DataAnnotationsValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/DateRangeFilterValidator.cs b/src/APITemplate.Application/Common/Validation/DateRangeFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/DateRangeFilterValidator.cs
rename to src/APITemplate.Application/Common/Validation/DateRangeFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/FluentValidationExtensions.cs b/src/APITemplate.Application/Common/Validation/FluentValidationExtensions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/FluentValidationExtensions.cs
rename to src/APITemplate.Application/Common/Validation/FluentValidationExtensions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/NotEmptyAttribute.cs b/src/APITemplate.Application/Common/Validation/NotEmptyAttribute.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/NotEmptyAttribute.cs
rename to src/APITemplate.Application/Common/Validation/NotEmptyAttribute.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/PaginationFilterValidator.cs b/src/APITemplate.Application/Common/Validation/PaginationFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/PaginationFilterValidator.cs
rename to src/APITemplate.Application/Common/Validation/PaginationFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/SortableFilterValidator.cs b/src/APITemplate.Application/Common/Validation/SortableFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Common/Validation/SortableFilterValidator.cs
rename to src/APITemplate.Application/Common/Validation/SortableFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Bff/DTOs/BffUserResponse.cs b/src/APITemplate.Application/Features/Bff/DTOs/BffUserResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Bff/DTOs/BffUserResponse.cs
rename to src/APITemplate.Application/Features/Bff/DTOs/BffUserResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/CategorySortFields.cs b/src/APITemplate.Application/Features/Category/CategorySortFields.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/CategorySortFields.cs
rename to src/APITemplate.Application/Features/Category/CategorySortFields.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Commands/CreateCategoriesCommand.cs b/src/APITemplate.Application/Features/Category/Commands/CreateCategoriesCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Commands/CreateCategoriesCommand.cs
rename to src/APITemplate.Application/Features/Category/Commands/CreateCategoriesCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Commands/DeleteCategoriesCommand.cs b/src/APITemplate.Application/Features/Category/Commands/DeleteCategoriesCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Commands/DeleteCategoriesCommand.cs
rename to src/APITemplate.Application/Features/Category/Commands/DeleteCategoriesCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Commands/UpdateCategoriesCommand.cs b/src/APITemplate.Application/Features/Category/Commands/UpdateCategoriesCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Commands/UpdateCategoriesCommand.cs
rename to src/APITemplate.Application/Features/Category/Commands/UpdateCategoriesCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CategoryFilter.cs b/src/APITemplate.Application/Features/Category/DTOs/CategoryFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CategoryFilter.cs
rename to src/APITemplate.Application/Features/Category/DTOs/CategoryFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CategoryResponse.cs b/src/APITemplate.Application/Features/Category/DTOs/CategoryResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CategoryResponse.cs
rename to src/APITemplate.Application/Features/Category/DTOs/CategoryResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CreateCategoriesRequest.cs b/src/APITemplate.Application/Features/Category/DTOs/CreateCategoriesRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CreateCategoriesRequest.cs
rename to src/APITemplate.Application/Features/Category/DTOs/CreateCategoriesRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CreateCategoryRequest.cs b/src/APITemplate.Application/Features/Category/DTOs/CreateCategoryRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/CreateCategoryRequest.cs
rename to src/APITemplate.Application/Features/Category/DTOs/CreateCategoryRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/ProductCategoryStatsResponse.cs b/src/APITemplate.Application/Features/Category/DTOs/ProductCategoryStatsResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/ProductCategoryStatsResponse.cs
rename to src/APITemplate.Application/Features/Category/DTOs/ProductCategoryStatsResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/UpdateCategoriesRequest.cs b/src/APITemplate.Application/Features/Category/DTOs/UpdateCategoriesRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/UpdateCategoriesRequest.cs
rename to src/APITemplate.Application/Features/Category/DTOs/UpdateCategoriesRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/UpdateCategoryRequest.cs b/src/APITemplate.Application/Features/Category/DTOs/UpdateCategoryRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/DTOs/UpdateCategoryRequest.cs
rename to src/APITemplate.Application/Features/Category/DTOs/UpdateCategoryRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Mappings/CategoryMappings.cs b/src/APITemplate.Application/Features/Category/Mappings/CategoryMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Mappings/CategoryMappings.cs
rename to src/APITemplate.Application/Features/Category/Mappings/CategoryMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Queries/GetCategoriesQuery.cs b/src/APITemplate.Application/Features/Category/Queries/GetCategoriesQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Queries/GetCategoriesQuery.cs
rename to src/APITemplate.Application/Features/Category/Queries/GetCategoriesQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Queries/GetCategoryByIdQuery.cs b/src/APITemplate.Application/Features/Category/Queries/GetCategoryByIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Queries/GetCategoryByIdQuery.cs
rename to src/APITemplate.Application/Features/Category/Queries/GetCategoryByIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Queries/GetCategoryStatsQuery.cs b/src/APITemplate.Application/Features/Category/Queries/GetCategoryStatsQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Queries/GetCategoryStatsQuery.cs
rename to src/APITemplate.Application/Features/Category/Queries/GetCategoryStatsQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategoriesByIdsSpecification.cs b/src/APITemplate.Application/Features/Category/Specifications/CategoriesByIdsSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategoriesByIdsSpecification.cs
rename to src/APITemplate.Application/Features/Category/Specifications/CategoriesByIdsSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategoryByIdSpecification.cs b/src/APITemplate.Application/Features/Category/Specifications/CategoryByIdSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategoryByIdSpecification.cs
rename to src/APITemplate.Application/Features/Category/Specifications/CategoryByIdSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategoryFilterCriteria.cs b/src/APITemplate.Application/Features/Category/Specifications/CategoryFilterCriteria.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategoryFilterCriteria.cs
rename to src/APITemplate.Application/Features/Category/Specifications/CategoryFilterCriteria.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategorySpecification.cs b/src/APITemplate.Application/Features/Category/Specifications/CategorySpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Specifications/CategorySpecification.cs
rename to src/APITemplate.Application/Features/Category/Specifications/CategorySpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Validation/CategoryFilterValidator.cs b/src/APITemplate.Application/Features/Category/Validation/CategoryFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Validation/CategoryFilterValidator.cs
rename to src/APITemplate.Application/Features/Category/Validation/CategoryFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Validation/CreateCategoryRequestValidator.cs b/src/APITemplate.Application/Features/Category/Validation/CreateCategoryRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Validation/CreateCategoryRequestValidator.cs
rename to src/APITemplate.Application/Features/Category/Validation/CreateCategoryRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Validation/UpdateCategoryItemValidator.cs b/src/APITemplate.Application/Features/Category/Validation/UpdateCategoryItemValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Category/Validation/UpdateCategoryItemValidator.cs
rename to src/APITemplate.Application/Features/Category/Validation/UpdateCategoryItemValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/IdempotentCreateCommand.cs b/src/APITemplate.Application/Features/Examples/Commands/IdempotentCreateCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/IdempotentCreateCommand.cs
rename to src/APITemplate.Application/Features/Examples/Commands/IdempotentCreateCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/PatchProductCommand.cs b/src/APITemplate.Application/Features/Examples/Commands/PatchProductCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/PatchProductCommand.cs
rename to src/APITemplate.Application/Features/Examples/Commands/PatchProductCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/SubmitJobCommand.cs b/src/APITemplate.Application/Features/Examples/Commands/SubmitJobCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/SubmitJobCommand.cs
rename to src/APITemplate.Application/Features/Examples/Commands/SubmitJobCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/UploadFileCommand.cs b/src/APITemplate.Application/Features/Examples/Commands/UploadFileCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Commands/UploadFileCommand.cs
rename to src/APITemplate.Application/Features/Examples/Commands/UploadFileCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/DownloadFileRequest.cs b/src/APITemplate.Application/Features/Examples/DTOs/DownloadFileRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/DownloadFileRequest.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/DownloadFileRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/FileUploadResponse.cs b/src/APITemplate.Application/Features/Examples/DTOs/FileUploadResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/FileUploadResponse.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/FileUploadResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/GetJobStatusRequest.cs b/src/APITemplate.Application/Features/Examples/DTOs/GetJobStatusRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/GetJobStatusRequest.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/GetJobStatusRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateRequest.cs b/src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateRequest.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateResponse.cs b/src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateResponse.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/IdempotentCreateResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/JobStatusResponse.cs b/src/APITemplate.Application/Features/Examples/DTOs/JobStatusResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/JobStatusResponse.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/JobStatusResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/OutgoingWebhookDTOs.cs b/src/APITemplate.Application/Features/Examples/DTOs/OutgoingWebhookDTOs.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/OutgoingWebhookDTOs.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/OutgoingWebhookDTOs.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/PatchableProductDto.cs b/src/APITemplate.Application/Features/Examples/DTOs/PatchableProductDto.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/PatchableProductDto.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/PatchableProductDto.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/SseNotificationItem.cs b/src/APITemplate.Application/Features/Examples/DTOs/SseNotificationItem.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/SseNotificationItem.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/SseNotificationItem.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/SseStreamRequest.cs b/src/APITemplate.Application/Features/Examples/DTOs/SseStreamRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/SseStreamRequest.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/SseStreamRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/SubmitJobRequest.cs b/src/APITemplate.Application/Features/Examples/DTOs/SubmitJobRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/SubmitJobRequest.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/SubmitJobRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/UploadFileRequest.cs b/src/APITemplate.Application/Features/Examples/DTOs/UploadFileRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/UploadFileRequest.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/UploadFileRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/WebhookConstants.cs b/src/APITemplate.Application/Features/Examples/DTOs/WebhookConstants.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/WebhookConstants.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/WebhookConstants.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/WebhookPayload.cs b/src/APITemplate.Application/Features/Examples/DTOs/WebhookPayload.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/DTOs/WebhookPayload.cs
rename to src/APITemplate.Application/Features/Examples/DTOs/WebhookPayload.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Mappings/JobResponseMapper.cs b/src/APITemplate.Application/Features/Examples/Mappings/JobResponseMapper.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Mappings/JobResponseMapper.cs
rename to src/APITemplate.Application/Features/Examples/Mappings/JobResponseMapper.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Queries/DownloadFileQuery.cs b/src/APITemplate.Application/Features/Examples/Queries/DownloadFileQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Queries/DownloadFileQuery.cs
rename to src/APITemplate.Application/Features/Examples/Queries/DownloadFileQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Queries/GetJobStatusQuery.cs b/src/APITemplate.Application/Features/Examples/Queries/GetJobStatusQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Queries/GetJobStatusQuery.cs
rename to src/APITemplate.Application/Features/Examples/Queries/GetJobStatusQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Queries/GetNotificationStreamQuery.cs b/src/APITemplate.Application/Features/Examples/Queries/GetNotificationStreamQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Queries/GetNotificationStreamQuery.cs
rename to src/APITemplate.Application/Features/Examples/Queries/GetNotificationStreamQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Validation/IdempotentCreateRequestValidator.cs b/src/APITemplate.Application/Features/Examples/Validation/IdempotentCreateRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Validation/IdempotentCreateRequestValidator.cs
rename to src/APITemplate.Application/Features/Examples/Validation/IdempotentCreateRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Validation/PatchableProductDtoValidator.cs b/src/APITemplate.Application/Features/Examples/Validation/PatchableProductDtoValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Validation/PatchableProductDtoValidator.cs
rename to src/APITemplate.Application/Features/Examples/Validation/PatchableProductDtoValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Validation/SubmitJobRequestValidator.cs b/src/APITemplate.Application/Features/Examples/Validation/SubmitJobRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Examples/Validation/SubmitJobRequestValidator.cs
rename to src/APITemplate.Application/Features/Examples/Validation/SubmitJobRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/CreateProductsCommand.cs b/src/APITemplate.Application/Features/Product/Commands/CreateProductsCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/CreateProductsCommand.cs
rename to src/APITemplate.Application/Features/Product/Commands/CreateProductsCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/DeleteProductsCommand.cs b/src/APITemplate.Application/Features/Product/Commands/DeleteProductsCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/DeleteProductsCommand.cs
rename to src/APITemplate.Application/Features/Product/Commands/DeleteProductsCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/UpdateProductsCommand.cs b/src/APITemplate.Application/Features/Product/Commands/UpdateProductsCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/UpdateProductsCommand.cs
rename to src/APITemplate.Application/Features/Product/Commands/UpdateProductsCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/UpdateProductsValidator.cs b/src/APITemplate.Application/Features/Product/Commands/UpdateProductsValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Commands/UpdateProductsValidator.cs
rename to src/APITemplate.Application/Features/Product/Commands/UpdateProductsValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/CreateProductRequest.cs b/src/APITemplate.Application/Features/Product/DTOs/CreateProductRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/CreateProductRequest.cs
rename to src/APITemplate.Application/Features/Product/DTOs/CreateProductRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/CreateProductsRequest.cs b/src/APITemplate.Application/Features/Product/DTOs/CreateProductsRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/CreateProductsRequest.cs
rename to src/APITemplate.Application/Features/Product/DTOs/CreateProductsRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductCategoryFacetValue.cs b/src/APITemplate.Application/Features/Product/DTOs/ProductCategoryFacetValue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductCategoryFacetValue.cs
rename to src/APITemplate.Application/Features/Product/DTOs/ProductCategoryFacetValue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductFilter.cs b/src/APITemplate.Application/Features/Product/DTOs/ProductFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductFilter.cs
rename to src/APITemplate.Application/Features/Product/DTOs/ProductFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductPriceFacetBucketResponse.cs b/src/APITemplate.Application/Features/Product/DTOs/ProductPriceFacetBucketResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductPriceFacetBucketResponse.cs
rename to src/APITemplate.Application/Features/Product/DTOs/ProductPriceFacetBucketResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductResponse.cs b/src/APITemplate.Application/Features/Product/DTOs/ProductResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductResponse.cs
rename to src/APITemplate.Application/Features/Product/DTOs/ProductResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductSearchFacetsResponse.cs b/src/APITemplate.Application/Features/Product/DTOs/ProductSearchFacetsResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductSearchFacetsResponse.cs
rename to src/APITemplate.Application/Features/Product/DTOs/ProductSearchFacetsResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductsResponse.cs b/src/APITemplate.Application/Features/Product/DTOs/ProductsResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/ProductsResponse.cs
rename to src/APITemplate.Application/Features/Product/DTOs/ProductsResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/UpdateProductRequest.cs b/src/APITemplate.Application/Features/Product/DTOs/UpdateProductRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/UpdateProductRequest.cs
rename to src/APITemplate.Application/Features/Product/DTOs/UpdateProductRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/UpdateProductsRequest.cs b/src/APITemplate.Application/Features/Product/DTOs/UpdateProductsRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/DTOs/UpdateProductsRequest.cs
rename to src/APITemplate.Application/Features/Product/DTOs/UpdateProductsRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Mappings/ProductMappings.cs b/src/APITemplate.Application/Features/Product/Mappings/ProductMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Mappings/ProductMappings.cs
rename to src/APITemplate.Application/Features/Product/Mappings/ProductMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/ProductSortFields.cs b/src/APITemplate.Application/Features/Product/ProductSortFields.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/ProductSortFields.cs
rename to src/APITemplate.Application/Features/Product/ProductSortFields.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/ProductValidationHelper.cs b/src/APITemplate.Application/Features/Product/ProductValidationHelper.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/ProductValidationHelper.cs
rename to src/APITemplate.Application/Features/Product/ProductValidationHelper.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Queries/GetProductByIdQuery.cs b/src/APITemplate.Application/Features/Product/Queries/GetProductByIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Queries/GetProductByIdQuery.cs
rename to src/APITemplate.Application/Features/Product/Queries/GetProductByIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Queries/GetProductsQuery.cs b/src/APITemplate.Application/Features/Product/Queries/GetProductsQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Queries/GetProductsQuery.cs
rename to src/APITemplate.Application/Features/Product/Queries/GetProductsQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Repositories/IProductRepository.cs b/src/APITemplate.Application/Features/Product/Repositories/IProductRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Repositories/IProductRepository.cs
rename to src/APITemplate.Application/Features/Product/Repositories/IProductRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductByIdSpecification.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductByIdSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductByIdSpecification.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductByIdSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductByIdWithLinksSpecification.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductByIdWithLinksSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductByIdWithLinksSpecification.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductByIdWithLinksSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductCategoryFacetSpecification.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductCategoryFacetSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductCategoryFacetSpecification.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductCategoryFacetSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductFilterCriteria.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductFilterCriteria.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductFilterCriteria.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductFilterCriteria.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductPriceFacetSpecification.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductPriceFacetSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductPriceFacetSpecification.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductPriceFacetSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductSpecification.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductSpecification.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductsByIdsWithLinksSpecification.cs b/src/APITemplate.Application/Features/Product/Specifications/ProductsByIdsWithLinksSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Specifications/ProductsByIdsWithLinksSpecification.cs
rename to src/APITemplate.Application/Features/Product/Specifications/ProductsByIdsWithLinksSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/CreateProductRequestValidator.cs b/src/APITemplate.Application/Features/Product/Validation/CreateProductRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/CreateProductRequestValidator.cs
rename to src/APITemplate.Application/Features/Product/Validation/CreateProductRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/ProductFilterValidator.cs b/src/APITemplate.Application/Features/Product/Validation/ProductFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/ProductFilterValidator.cs
rename to src/APITemplate.Application/Features/Product/Validation/ProductFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/ProductRequestValidatorBase.cs b/src/APITemplate.Application/Features/Product/Validation/ProductRequestValidatorBase.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/ProductRequestValidatorBase.cs
rename to src/APITemplate.Application/Features/Product/Validation/ProductRequestValidatorBase.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/UpdateProductItemValidator.cs b/src/APITemplate.Application/Features/Product/Validation/UpdateProductItemValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/UpdateProductItemValidator.cs
rename to src/APITemplate.Application/Features/Product/Validation/UpdateProductItemValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/UpdateProductRequestValidator.cs b/src/APITemplate.Application/Features/Product/Validation/UpdateProductRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Product/Validation/UpdateProductRequestValidator.cs
rename to src/APITemplate.Application/Features/Product/Validation/UpdateProductRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Commands/CreateImageProductDataCommand.cs b/src/APITemplate.Application/Features/ProductData/Commands/CreateImageProductDataCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Commands/CreateImageProductDataCommand.cs
rename to src/APITemplate.Application/Features/ProductData/Commands/CreateImageProductDataCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Commands/CreateVideoProductDataCommand.cs b/src/APITemplate.Application/Features/ProductData/Commands/CreateVideoProductDataCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Commands/CreateVideoProductDataCommand.cs
rename to src/APITemplate.Application/Features/ProductData/Commands/CreateVideoProductDataCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Commands/DeleteProductDataCommand.cs b/src/APITemplate.Application/Features/ProductData/Commands/DeleteProductDataCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Commands/DeleteProductDataCommand.cs
rename to src/APITemplate.Application/Features/ProductData/Commands/DeleteProductDataCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/DTOs/CreateImageProductDataRequest.cs b/src/APITemplate.Application/Features/ProductData/DTOs/CreateImageProductDataRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/DTOs/CreateImageProductDataRequest.cs
rename to src/APITemplate.Application/Features/ProductData/DTOs/CreateImageProductDataRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/DTOs/CreateVideoProductDataRequest.cs b/src/APITemplate.Application/Features/ProductData/DTOs/CreateVideoProductDataRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/DTOs/CreateVideoProductDataRequest.cs
rename to src/APITemplate.Application/Features/ProductData/DTOs/CreateVideoProductDataRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/DTOs/ProductDataResponse.cs b/src/APITemplate.Application/Features/ProductData/DTOs/ProductDataResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/DTOs/ProductDataResponse.cs
rename to src/APITemplate.Application/Features/ProductData/DTOs/ProductDataResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Handlers/ProductDataCascadeDeleteHandler.cs b/src/APITemplate.Application/Features/ProductData/Handlers/ProductDataCascadeDeleteHandler.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Handlers/ProductDataCascadeDeleteHandler.cs
rename to src/APITemplate.Application/Features/ProductData/Handlers/ProductDataCascadeDeleteHandler.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Mappings/ProductDataMappings.cs b/src/APITemplate.Application/Features/ProductData/Mappings/ProductDataMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Mappings/ProductDataMappings.cs
rename to src/APITemplate.Application/Features/ProductData/Mappings/ProductDataMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Queries/GetProductDataByIdQuery.cs b/src/APITemplate.Application/Features/ProductData/Queries/GetProductDataByIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Queries/GetProductDataByIdQuery.cs
rename to src/APITemplate.Application/Features/ProductData/Queries/GetProductDataByIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Queries/GetProductDataQuery.cs b/src/APITemplate.Application/Features/ProductData/Queries/GetProductDataQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Queries/GetProductDataQuery.cs
rename to src/APITemplate.Application/Features/ProductData/Queries/GetProductDataQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Validation/CreateImageProductDataRequestValidator.cs b/src/APITemplate.Application/Features/ProductData/Validation/CreateImageProductDataRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Validation/CreateImageProductDataRequestValidator.cs
rename to src/APITemplate.Application/Features/ProductData/Validation/CreateImageProductDataRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Validation/CreateVideoProductDataRequestValidator.cs b/src/APITemplate.Application/Features/ProductData/Validation/CreateVideoProductDataRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductData/Validation/CreateVideoProductDataRequestValidator.cs
rename to src/APITemplate.Application/Features/ProductData/Validation/CreateVideoProductDataRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Commands/CreateProductReviewCommand.cs b/src/APITemplate.Application/Features/ProductReview/Commands/CreateProductReviewCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Commands/CreateProductReviewCommand.cs
rename to src/APITemplate.Application/Features/ProductReview/Commands/CreateProductReviewCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Commands/DeleteProductReviewCommand.cs b/src/APITemplate.Application/Features/ProductReview/Commands/DeleteProductReviewCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Commands/DeleteProductReviewCommand.cs
rename to src/APITemplate.Application/Features/ProductReview/Commands/DeleteProductReviewCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/DTOs/CreateProductReviewRequest.cs b/src/APITemplate.Application/Features/ProductReview/DTOs/CreateProductReviewRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/DTOs/CreateProductReviewRequest.cs
rename to src/APITemplate.Application/Features/ProductReview/DTOs/CreateProductReviewRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewFilter.cs b/src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewFilter.cs
rename to src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewResponse.cs b/src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewResponse.cs
rename to src/APITemplate.Application/Features/ProductReview/DTOs/ProductReviewResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Mappings/ProductReviewMappings.cs b/src/APITemplate.Application/Features/ProductReview/Mappings/ProductReviewMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Mappings/ProductReviewMappings.cs
rename to src/APITemplate.Application/Features/ProductReview/Mappings/ProductReviewMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/ProductReviewSortFields.cs b/src/APITemplate.Application/Features/ProductReview/ProductReviewSortFields.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/ProductReviewSortFields.cs
rename to src/APITemplate.Application/Features/ProductReview/ProductReviewSortFields.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewByIdQuery.cs b/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewByIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewByIdQuery.cs
rename to src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewByIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdQuery.cs b/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdQuery.cs
rename to src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdsQuery.cs b/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdsQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdsQuery.cs
rename to src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsByProductIdsQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsQuery.cs b/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsQuery.cs
rename to src/APITemplate.Application/Features/ProductReview/Queries/GetProductReviewsQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdSpecification.cs b/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdSpecification.cs
rename to src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdsSpecification.cs b/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdsSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdsSpecification.cs
rename to src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewByProductIdsSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewFilterCriteria.cs b/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewFilterCriteria.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewFilterCriteria.cs
rename to src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewFilterCriteria.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewSpecification.cs b/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewSpecification.cs
rename to src/APITemplate.Application/Features/ProductReview/Specifications/ProductReviewSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Validation/CreateProductReviewRequestValidator.cs b/src/APITemplate.Application/Features/ProductReview/Validation/CreateProductReviewRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Validation/CreateProductReviewRequestValidator.cs
rename to src/APITemplate.Application/Features/ProductReview/Validation/CreateProductReviewRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Validation/ProductReviewFilterValidator.cs b/src/APITemplate.Application/Features/ProductReview/Validation/ProductReviewFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/ProductReview/Validation/ProductReviewFilterValidator.cs
rename to src/APITemplate.Application/Features/ProductReview/Validation/ProductReviewFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Commands/CreateTenantCommand.cs b/src/APITemplate.Application/Features/Tenant/Commands/CreateTenantCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Commands/CreateTenantCommand.cs
rename to src/APITemplate.Application/Features/Tenant/Commands/CreateTenantCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Commands/DeleteTenantCommand.cs b/src/APITemplate.Application/Features/Tenant/Commands/DeleteTenantCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Commands/DeleteTenantCommand.cs
rename to src/APITemplate.Application/Features/Tenant/Commands/DeleteTenantCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/DTOs/CreateTenantRequest.cs b/src/APITemplate.Application/Features/Tenant/DTOs/CreateTenantRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/DTOs/CreateTenantRequest.cs
rename to src/APITemplate.Application/Features/Tenant/DTOs/CreateTenantRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/DTOs/TenantFilter.cs b/src/APITemplate.Application/Features/Tenant/DTOs/TenantFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/DTOs/TenantFilter.cs
rename to src/APITemplate.Application/Features/Tenant/DTOs/TenantFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/DTOs/TenantResponse.cs b/src/APITemplate.Application/Features/Tenant/DTOs/TenantResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/DTOs/TenantResponse.cs
rename to src/APITemplate.Application/Features/Tenant/DTOs/TenantResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Mappings/TenantMappings.cs b/src/APITemplate.Application/Features/Tenant/Mappings/TenantMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Mappings/TenantMappings.cs
rename to src/APITemplate.Application/Features/Tenant/Mappings/TenantMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Queries/GetTenantByIdQuery.cs b/src/APITemplate.Application/Features/Tenant/Queries/GetTenantByIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Queries/GetTenantByIdQuery.cs
rename to src/APITemplate.Application/Features/Tenant/Queries/GetTenantByIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Queries/GetTenantsQuery.cs b/src/APITemplate.Application/Features/Tenant/Queries/GetTenantsQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Queries/GetTenantsQuery.cs
rename to src/APITemplate.Application/Features/Tenant/Queries/GetTenantsQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Specifications/TenantByIdSpecification.cs b/src/APITemplate.Application/Features/Tenant/Specifications/TenantByIdSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Specifications/TenantByIdSpecification.cs
rename to src/APITemplate.Application/Features/Tenant/Specifications/TenantByIdSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Specifications/TenantFilterCriteria.cs b/src/APITemplate.Application/Features/Tenant/Specifications/TenantFilterCriteria.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Specifications/TenantFilterCriteria.cs
rename to src/APITemplate.Application/Features/Tenant/Specifications/TenantFilterCriteria.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Specifications/TenantSpecification.cs b/src/APITemplate.Application/Features/Tenant/Specifications/TenantSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Specifications/TenantSpecification.cs
rename to src/APITemplate.Application/Features/Tenant/Specifications/TenantSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/TenantSortFields.cs b/src/APITemplate.Application/Features/Tenant/TenantSortFields.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/TenantSortFields.cs
rename to src/APITemplate.Application/Features/Tenant/TenantSortFields.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Validation/CreateTenantRequestValidator.cs b/src/APITemplate.Application/Features/Tenant/Validation/CreateTenantRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Validation/CreateTenantRequestValidator.cs
rename to src/APITemplate.Application/Features/Tenant/Validation/CreateTenantRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Validation/TenantFilterValidator.cs b/src/APITemplate.Application/Features/Tenant/Validation/TenantFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/Tenant/Validation/TenantFilterValidator.cs
rename to src/APITemplate.Application/Features/Tenant/Validation/TenantFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/AcceptTenantInvitationCommand.cs b/src/APITemplate.Application/Features/TenantInvitation/Commands/AcceptTenantInvitationCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/AcceptTenantInvitationCommand.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Commands/AcceptTenantInvitationCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/CreateTenantInvitationCommand.cs b/src/APITemplate.Application/Features/TenantInvitation/Commands/CreateTenantInvitationCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/CreateTenantInvitationCommand.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Commands/CreateTenantInvitationCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/ResendTenantInvitationCommand.cs b/src/APITemplate.Application/Features/TenantInvitation/Commands/ResendTenantInvitationCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/ResendTenantInvitationCommand.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Commands/ResendTenantInvitationCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/RevokeTenantInvitationCommand.cs b/src/APITemplate.Application/Features/TenantInvitation/Commands/RevokeTenantInvitationCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Commands/RevokeTenantInvitationCommand.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Commands/RevokeTenantInvitationCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/AcceptInvitationRequest.cs b/src/APITemplate.Application/Features/TenantInvitation/DTOs/AcceptInvitationRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/AcceptInvitationRequest.cs
rename to src/APITemplate.Application/Features/TenantInvitation/DTOs/AcceptInvitationRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/CreateTenantInvitationRequest.cs b/src/APITemplate.Application/Features/TenantInvitation/DTOs/CreateTenantInvitationRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/CreateTenantInvitationRequest.cs
rename to src/APITemplate.Application/Features/TenantInvitation/DTOs/CreateTenantInvitationRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationFilter.cs b/src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationFilter.cs
rename to src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationResponse.cs b/src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationResponse.cs
rename to src/APITemplate.Application/Features/TenantInvitation/DTOs/TenantInvitationResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Mappings/TenantInvitationMappings.cs b/src/APITemplate.Application/Features/TenantInvitation/Mappings/TenantInvitationMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Mappings/TenantInvitationMappings.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Mappings/TenantInvitationMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Queries/GetTenantInvitationsQuery.cs b/src/APITemplate.Application/Features/TenantInvitation/Queries/GetTenantInvitationsQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Queries/GetTenantInvitationsQuery.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Queries/GetTenantInvitationsQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Specifications/TenantInvitationFilterSpecification.cs b/src/APITemplate.Application/Features/TenantInvitation/Specifications/TenantInvitationFilterSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Specifications/TenantInvitationFilterSpecification.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Specifications/TenantInvitationFilterSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Validation/CreateTenantInvitationRequestValidator.cs b/src/APITemplate.Application/Features/TenantInvitation/Validation/CreateTenantInvitationRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/TenantInvitation/Validation/CreateTenantInvitationRequestValidator.cs
rename to src/APITemplate.Application/Features/TenantInvitation/Validation/CreateTenantInvitationRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/ChangeUserRoleCommand.cs b/src/APITemplate.Application/Features/User/Commands/ChangeUserRoleCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/ChangeUserRoleCommand.cs
rename to src/APITemplate.Application/Features/User/Commands/ChangeUserRoleCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/CreateUserCommand.cs b/src/APITemplate.Application/Features/User/Commands/CreateUserCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/CreateUserCommand.cs
rename to src/APITemplate.Application/Features/User/Commands/CreateUserCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/DeleteUserCommand.cs b/src/APITemplate.Application/Features/User/Commands/DeleteUserCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/DeleteUserCommand.cs
rename to src/APITemplate.Application/Features/User/Commands/DeleteUserCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/KeycloakPasswordResetCommand.cs b/src/APITemplate.Application/Features/User/Commands/KeycloakPasswordResetCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/KeycloakPasswordResetCommand.cs
rename to src/APITemplate.Application/Features/User/Commands/KeycloakPasswordResetCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/SetUserActiveCommand.cs b/src/APITemplate.Application/Features/User/Commands/SetUserActiveCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/SetUserActiveCommand.cs
rename to src/APITemplate.Application/Features/User/Commands/SetUserActiveCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/UpdateUserCommand.cs b/src/APITemplate.Application/Features/User/Commands/UpdateUserCommand.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Commands/UpdateUserCommand.cs
rename to src/APITemplate.Application/Features/User/Commands/UpdateUserCommand.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/ChangeUserRoleRequest.cs b/src/APITemplate.Application/Features/User/DTOs/ChangeUserRoleRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/ChangeUserRoleRequest.cs
rename to src/APITemplate.Application/Features/User/DTOs/ChangeUserRoleRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/CreateUserRequest.cs b/src/APITemplate.Application/Features/User/DTOs/CreateUserRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/CreateUserRequest.cs
rename to src/APITemplate.Application/Features/User/DTOs/CreateUserRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/RequestPasswordResetRequest.cs b/src/APITemplate.Application/Features/User/DTOs/RequestPasswordResetRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/RequestPasswordResetRequest.cs
rename to src/APITemplate.Application/Features/User/DTOs/RequestPasswordResetRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/UpdateUserRequest.cs b/src/APITemplate.Application/Features/User/DTOs/UpdateUserRequest.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/UpdateUserRequest.cs
rename to src/APITemplate.Application/Features/User/DTOs/UpdateUserRequest.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/UserFilter.cs b/src/APITemplate.Application/Features/User/DTOs/UserFilter.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/UserFilter.cs
rename to src/APITemplate.Application/Features/User/DTOs/UserFilter.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/UserResponse.cs b/src/APITemplate.Application/Features/User/DTOs/UserResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/DTOs/UserResponse.cs
rename to src/APITemplate.Application/Features/User/DTOs/UserResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Mappings/UserMappings.cs b/src/APITemplate.Application/Features/User/Mappings/UserMappings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Mappings/UserMappings.cs
rename to src/APITemplate.Application/Features/User/Mappings/UserMappings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Queries/GetUserByIdQuery.cs b/src/APITemplate.Application/Features/User/Queries/GetUserByIdQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Queries/GetUserByIdQuery.cs
rename to src/APITemplate.Application/Features/User/Queries/GetUserByIdQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Queries/GetUsersQuery.cs b/src/APITemplate.Application/Features/User/Queries/GetUsersQuery.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Queries/GetUsersQuery.cs
rename to src/APITemplate.Application/Features/User/Queries/GetUsersQuery.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserByEmailSpecification.cs b/src/APITemplate.Application/Features/User/Specifications/UserByEmailSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserByEmailSpecification.cs
rename to src/APITemplate.Application/Features/User/Specifications/UserByEmailSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserByIdSpecification.cs b/src/APITemplate.Application/Features/User/Specifications/UserByIdSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserByIdSpecification.cs
rename to src/APITemplate.Application/Features/User/Specifications/UserByIdSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserByUsernameSpecification.cs b/src/APITemplate.Application/Features/User/Specifications/UserByUsernameSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserByUsernameSpecification.cs
rename to src/APITemplate.Application/Features/User/Specifications/UserByUsernameSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserFilterCriteria.cs b/src/APITemplate.Application/Features/User/Specifications/UserFilterCriteria.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserFilterCriteria.cs
rename to src/APITemplate.Application/Features/User/Specifications/UserFilterCriteria.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserFilterSpecification.cs b/src/APITemplate.Application/Features/User/Specifications/UserFilterSpecification.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Specifications/UserFilterSpecification.cs
rename to src/APITemplate.Application/Features/User/Specifications/UserFilterSpecification.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/UserSortFields.cs b/src/APITemplate.Application/Features/User/UserSortFields.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/UserSortFields.cs
rename to src/APITemplate.Application/Features/User/UserSortFields.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/UserValidationHelper.cs b/src/APITemplate.Application/Features/User/UserValidationHelper.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/UserValidationHelper.cs
rename to src/APITemplate.Application/Features/User/UserValidationHelper.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/ChangeUserRoleRequestValidator.cs b/src/APITemplate.Application/Features/User/Validation/ChangeUserRoleRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/ChangeUserRoleRequestValidator.cs
rename to src/APITemplate.Application/Features/User/Validation/ChangeUserRoleRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/CreateUserRequestValidator.cs b/src/APITemplate.Application/Features/User/Validation/CreateUserRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/CreateUserRequestValidator.cs
rename to src/APITemplate.Application/Features/User/Validation/CreateUserRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/UpdateUserRequestValidator.cs b/src/APITemplate.Application/Features/User/Validation/UpdateUserRequestValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/UpdateUserRequestValidator.cs
rename to src/APITemplate.Application/Features/User/Validation/UpdateUserRequestValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/UserFilterValidator.cs b/src/APITemplate.Application/Features/User/Validation/UserFilterValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/Features/User/Validation/UserFilterValidator.cs
rename to src/APITemplate.Application/Features/User/Validation/UserFilterValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Application/GlobalUsings.ApplicationFeatures.cs b/src/APITemplate.Application/GlobalUsings.ApplicationFeatures.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Application/GlobalUsings.ApplicationFeatures.cs
rename to src/APITemplate.Application/GlobalUsings.ApplicationFeatures.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/APITemplate.Domain.csproj b/src/APITemplate.Domain/APITemplate.Domain.csproj
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/APITemplate.Domain.csproj
rename to src/APITemplate.Domain/APITemplate.Domain.csproj
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Common/PagedResponse.cs b/src/APITemplate.Domain/Common/PagedResponse.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Common/PagedResponse.cs
rename to src/APITemplate.Domain/Common/PagedResponse.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/AppUser.cs b/src/APITemplate.Domain/Entities/AppUser.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/AppUser.cs
rename to src/APITemplate.Domain/Entities/AppUser.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/AuditDefaults.cs b/src/APITemplate.Domain/Entities/AuditDefaults.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/AuditDefaults.cs
rename to src/APITemplate.Domain/Entities/AuditDefaults.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/AuditInfo.cs b/src/APITemplate.Domain/Entities/AuditInfo.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/AuditInfo.cs
rename to src/APITemplate.Domain/Entities/AuditInfo.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Category.cs b/src/APITemplate.Domain/Entities/Category.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Category.cs
rename to src/APITemplate.Domain/Entities/Category.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/IAuditableEntity.cs b/src/APITemplate.Domain/Entities/Contracts/IAuditableEntity.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/IAuditableEntity.cs
rename to src/APITemplate.Domain/Entities/Contracts/IAuditableEntity.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/IAuditableTenantEntity.cs b/src/APITemplate.Domain/Entities/Contracts/IAuditableTenantEntity.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/IAuditableTenantEntity.cs
rename to src/APITemplate.Domain/Entities/Contracts/IAuditableTenantEntity.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/IHasId.cs b/src/APITemplate.Domain/Entities/Contracts/IHasId.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/IHasId.cs
rename to src/APITemplate.Domain/Entities/Contracts/IHasId.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/ISoftDeletable.cs b/src/APITemplate.Domain/Entities/Contracts/ISoftDeletable.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/ISoftDeletable.cs
rename to src/APITemplate.Domain/Entities/Contracts/ISoftDeletable.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/ITenantEntity.cs b/src/APITemplate.Domain/Entities/Contracts/ITenantEntity.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Contracts/ITenantEntity.cs
rename to src/APITemplate.Domain/Entities/Contracts/ITenantEntity.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/FailedEmail.cs b/src/APITemplate.Domain/Entities/FailedEmail.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/FailedEmail.cs
rename to src/APITemplate.Domain/Entities/FailedEmail.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/JobExecution.cs b/src/APITemplate.Domain/Entities/JobExecution.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/JobExecution.cs
rename to src/APITemplate.Domain/Entities/JobExecution.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Product.cs b/src/APITemplate.Domain/Entities/Product.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Product.cs
rename to src/APITemplate.Domain/Entities/Product.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductCategoryStats.cs b/src/APITemplate.Domain/Entities/ProductCategoryStats.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductCategoryStats.cs
rename to src/APITemplate.Domain/Entities/ProductCategoryStats.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductData/ImageProductData.cs b/src/APITemplate.Domain/Entities/ProductData/ImageProductData.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductData/ImageProductData.cs
rename to src/APITemplate.Domain/Entities/ProductData/ImageProductData.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductData/ProductData.cs b/src/APITemplate.Domain/Entities/ProductData/ProductData.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductData/ProductData.cs
rename to src/APITemplate.Domain/Entities/ProductData/ProductData.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductData/VideoProductData.cs b/src/APITemplate.Domain/Entities/ProductData/VideoProductData.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductData/VideoProductData.cs
rename to src/APITemplate.Domain/Entities/ProductData/VideoProductData.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductDataLink.cs b/src/APITemplate.Domain/Entities/ProductDataLink.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductDataLink.cs
rename to src/APITemplate.Domain/Entities/ProductDataLink.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductReview.cs b/src/APITemplate.Domain/Entities/ProductReview.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/ProductReview.cs
rename to src/APITemplate.Domain/Entities/ProductReview.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/StoredFile.cs b/src/APITemplate.Domain/Entities/StoredFile.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/StoredFile.cs
rename to src/APITemplate.Domain/Entities/StoredFile.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/Tenant.cs b/src/APITemplate.Domain/Entities/Tenant.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/Tenant.cs
rename to src/APITemplate.Domain/Entities/Tenant.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Entities/TenantInvitation.cs b/src/APITemplate.Domain/Entities/TenantInvitation.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Entities/TenantInvitation.cs
rename to src/APITemplate.Domain/Entities/TenantInvitation.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Enums/InvitationStatus.cs b/src/APITemplate.Domain/Enums/InvitationStatus.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Enums/InvitationStatus.cs
rename to src/APITemplate.Domain/Enums/InvitationStatus.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Enums/JobStatus.cs b/src/APITemplate.Domain/Enums/JobStatus.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Enums/JobStatus.cs
rename to src/APITemplate.Domain/Enums/JobStatus.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Enums/UserRole.cs b/src/APITemplate.Domain/Enums/UserRole.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Enums/UserRole.cs
rename to src/APITemplate.Domain/Enums/UserRole.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/AppException.cs b/src/APITemplate.Domain/Exceptions/AppException.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/AppException.cs
rename to src/APITemplate.Domain/Exceptions/AppException.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/ConflictException.cs b/src/APITemplate.Domain/Exceptions/ConflictException.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/ConflictException.cs
rename to src/APITemplate.Domain/Exceptions/ConflictException.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/ForbiddenException.cs b/src/APITemplate.Domain/Exceptions/ForbiddenException.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/ForbiddenException.cs
rename to src/APITemplate.Domain/Exceptions/ForbiddenException.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/NotFoundException.cs b/src/APITemplate.Domain/Exceptions/NotFoundException.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/NotFoundException.cs
rename to src/APITemplate.Domain/Exceptions/NotFoundException.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/UnauthorizedException.cs b/src/APITemplate.Domain/Exceptions/UnauthorizedException.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/UnauthorizedException.cs
rename to src/APITemplate.Domain/Exceptions/UnauthorizedException.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/ValidationException.cs b/src/APITemplate.Domain/Exceptions/ValidationException.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Exceptions/ValidationException.cs
rename to src/APITemplate.Domain/Exceptions/ValidationException.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/GlobalUsings.cs b/src/APITemplate.Domain/GlobalUsings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/GlobalUsings.cs
rename to src/APITemplate.Domain/GlobalUsings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/ICategoryRepository.cs b/src/APITemplate.Domain/Interfaces/ICategoryRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/ICategoryRepository.cs
rename to src/APITemplate.Domain/Interfaces/ICategoryRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IFailedEmailRepository.cs b/src/APITemplate.Domain/Interfaces/IFailedEmailRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IFailedEmailRepository.cs
rename to src/APITemplate.Domain/Interfaces/IFailedEmailRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IJobExecutionRepository.cs b/src/APITemplate.Domain/Interfaces/IJobExecutionRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IJobExecutionRepository.cs
rename to src/APITemplate.Domain/Interfaces/IJobExecutionRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IProductDataLinkRepository.cs b/src/APITemplate.Domain/Interfaces/IProductDataLinkRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IProductDataLinkRepository.cs
rename to src/APITemplate.Domain/Interfaces/IProductDataLinkRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IProductDataRepository.cs b/src/APITemplate.Domain/Interfaces/IProductDataRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IProductDataRepository.cs
rename to src/APITemplate.Domain/Interfaces/IProductDataRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IProductReviewRepository.cs b/src/APITemplate.Domain/Interfaces/IProductReviewRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IProductReviewRepository.cs
rename to src/APITemplate.Domain/Interfaces/IProductReviewRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IRepository.cs b/src/APITemplate.Domain/Interfaces/IRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IRepository.cs
rename to src/APITemplate.Domain/Interfaces/IRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredFileRepository.cs b/src/APITemplate.Domain/Interfaces/IStoredFileRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredFileRepository.cs
rename to src/APITemplate.Domain/Interfaces/IStoredFileRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredProcedure.cs b/src/APITemplate.Domain/Interfaces/IStoredProcedure.cs
similarity index 93%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredProcedure.cs
rename to src/APITemplate.Domain/Interfaces/IStoredProcedure.cs
index 1da37411..10cd5123 100644
--- a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredProcedure.cs
+++ b/src/APITemplate.Domain/Interfaces/IStoredProcedure.cs
@@ -17,8 +17,7 @@ namespace APITemplate.Domain.Interfaces;
/// The keyless entity type that EF Core will materialise from the procedure result set.
/// Must be registered with HasNoKey() in the DbContext.
///
-public interface IStoredProcedure
- where TResult : class
+public interface IStoredProcedure where TResult : class
{
///
/// Returns an interpolated SQL string with all parameter values embedded.
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredProcedureExecutor.cs b/src/APITemplate.Domain/Interfaces/IStoredProcedureExecutor.cs
similarity index 92%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredProcedureExecutor.cs
rename to src/APITemplate.Domain/Interfaces/IStoredProcedureExecutor.cs
index 17228c2b..7fdf224b 100644
--- a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IStoredProcedureExecutor.cs
+++ b/src/APITemplate.Domain/Interfaces/IStoredProcedureExecutor.cs
@@ -13,8 +13,7 @@ public interface IStoredProcedureExecutor
///
Task QueryFirstAsync(
IStoredProcedure procedure,
- CancellationToken ct = default
- )
+ CancellationToken ct = default)
where TResult : class;
///
@@ -22,8 +21,7 @@ public interface IStoredProcedureExecutor
///
Task> QueryManyAsync(
IStoredProcedure procedure,
- CancellationToken ct = default
- )
+ CancellationToken ct = default)
where TResult : class;
///
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/ITenantInvitationRepository.cs b/src/APITemplate.Domain/Interfaces/ITenantInvitationRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/ITenantInvitationRepository.cs
rename to src/APITemplate.Domain/Interfaces/ITenantInvitationRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/ITenantRepository.cs b/src/APITemplate.Domain/Interfaces/ITenantRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/ITenantRepository.cs
rename to src/APITemplate.Domain/Interfaces/ITenantRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IUnitOfWork.cs b/src/APITemplate.Domain/Interfaces/IUnitOfWork.cs
similarity index 97%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IUnitOfWork.cs
rename to src/APITemplate.Domain/Interfaces/IUnitOfWork.cs
index 30e47e49..c3c52f1d 100644
--- a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IUnitOfWork.cs
+++ b/src/APITemplate.Domain/Interfaces/IUnitOfWork.cs
@@ -51,8 +51,7 @@ public interface IUnitOfWork
Task ExecuteInTransactionAsync(
Func action,
CancellationToken ct = default,
- TransactionOptions? options = null
- );
+ TransactionOptions? options = null);
///
/// Runs a multi-step relational write flow in one explicit transaction and returns a value.
@@ -65,6 +64,5 @@ Task ExecuteInTransactionAsync(
Task ExecuteInTransactionAsync(
Func> action,
CancellationToken ct = default,
- TransactionOptions? options = null
- );
+ TransactionOptions? options = null);
}
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IUserRepository.cs b/src/APITemplate.Domain/Interfaces/IUserRepository.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Interfaces/IUserRepository.cs
rename to src/APITemplate.Domain/Interfaces/IUserRepository.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Domain/Options/TransactionOptions.cs b/src/APITemplate.Domain/Options/TransactionOptions.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Domain/Options/TransactionOptions.cs
rename to src/APITemplate.Domain/Options/TransactionOptions.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/APITemplate.Infrastructure.csproj b/src/APITemplate.Infrastructure/APITemplate.Infrastructure.csproj
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/APITemplate.Infrastructure.csproj
rename to src/APITemplate.Infrastructure/APITemplate.Infrastructure.csproj
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/BoundedChannelQueue.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/BoundedChannelQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/BoundedChannelQueue.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/BoundedChannelQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ChannelJobQueue.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/ChannelJobQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ChannelJobQueue.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/ChannelJobQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/CleanupService.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/CleanupService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/CleanupService.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/CleanupService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/EmailRetryService.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/EmailRetryService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/EmailRetryService.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/EmailRetryService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ExternalIntegrationSyncServicePreview.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/ExternalIntegrationSyncServicePreview.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ExternalIntegrationSyncServicePreview.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/ExternalIntegrationSyncServicePreview.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ISoftDeleteCleanupStrategy.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/ISoftDeleteCleanupStrategy.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ISoftDeleteCleanupStrategy.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/ISoftDeleteCleanupStrategy.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/JobProcessingBackgroundService.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/JobProcessingBackgroundService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/JobProcessingBackgroundService.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/JobProcessingBackgroundService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/QueueConsumerBackgroundService.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/QueueConsumerBackgroundService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/QueueConsumerBackgroundService.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/QueueConsumerBackgroundService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ReindexService.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/ReindexService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/ReindexService.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/ReindexService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/SoftDeleteCleanupStrategy.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Services/SoftDeleteCleanupStrategy.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Services/SoftDeleteCleanupStrategy.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Services/SoftDeleteCleanupStrategy.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/DragonflyDistributedJobCoordinator.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/DragonflyDistributedJobCoordinator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/DragonflyDistributedJobCoordinator.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/DragonflyDistributedJobCoordinator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/IDistributedJobCoordinator.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/IDistributedJobCoordinator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/IDistributedJobCoordinator.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Coordination/IDistributedJobCoordinator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/CleanupRecurringJob.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/CleanupRecurringJob.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/CleanupRecurringJob.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/CleanupRecurringJob.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/EmailRetryRecurringJob.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/EmailRetryRecurringJob.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/EmailRetryRecurringJob.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/EmailRetryRecurringJob.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ExternalSyncRecurringJob.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ExternalSyncRecurringJob.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ExternalSyncRecurringJob.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ExternalSyncRecurringJob.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ReindexRecurringJob.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ReindexRecurringJob.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ReindexRecurringJob.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/Jobs/ReindexRecurringJob.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/CleanupRecurringJobRegistration.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/CleanupRecurringJobRegistration.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/CleanupRecurringJobRegistration.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/CleanupRecurringJobRegistration.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/EmailRetryRecurringJobRegistration.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/EmailRetryRecurringJobRegistration.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/EmailRetryRecurringJobRegistration.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/EmailRetryRecurringJobRegistration.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ExternalSyncRecurringJobRegistration.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ExternalSyncRecurringJobRegistration.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ExternalSyncRecurringJobRegistration.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ExternalSyncRecurringJobRegistration.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ReindexRecurringJobRegistration.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ReindexRecurringJobRegistration.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ReindexRecurringJobRegistration.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/RecurringJobRegistrations/ReindexRecurringJobRegistration.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQFunctionNames.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQFunctionNames.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQFunctionNames.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQFunctionNames.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQJobIds.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQJobIds.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQJobIds.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQJobIds.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQRecurringJobRegistrar.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQRecurringJobRegistrar.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQRecurringJobRegistrar.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQRecurringJobRegistrar.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContext.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContext.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContext.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContext.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContextFactory.cs b/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContextFactory.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContextFactory.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/TickerQ/TickerQSchedulerDbContextFactory.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Validation/BackgroundJobsOptionsValidator.cs b/src/APITemplate.Infrastructure/BackgroundJobs/Validation/BackgroundJobsOptionsValidator.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/BackgroundJobs/Validation/BackgroundJobsOptionsValidator.cs
rename to src/APITemplate.Infrastructure/BackgroundJobs/Validation/BackgroundJobsOptionsValidator.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_down.sql b/src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_down.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_up.sql b/src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_up.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/claim_expired_failed_emails_v1_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_down.sql b/src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_down.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_up.sql b/src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_up.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/claim_retryable_failed_emails_v1_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_down.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_down.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_up.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_up.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_fts_index_names_v1_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_down.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_down.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_up.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_up.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_index_bloat_percent_v1_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_down.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_down.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_up.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_up.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v1_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_down.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_down.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_up.sql b/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_up.sql
rename to src/APITemplate.Infrastructure/Database/Procedures/get_product_category_stats_v2_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/SqlResource.cs b/src/APITemplate.Infrastructure/Database/SqlResource.cs
similarity index 66%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/SqlResource.cs
rename to src/APITemplate.Infrastructure/Database/SqlResource.cs
index 8ab276c8..c087b983 100644
--- a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/SqlResource.cs
+++ b/src/APITemplate.Infrastructure/Database/SqlResource.cs
@@ -13,14 +13,13 @@ public static class SqlResource
public static string Load(string relativeResourcePath)
{
- var normalizedPath = relativeResourcePath.Replace('\\', '.').Replace('/', '.');
+ var normalizedPath = relativeResourcePath
+ .Replace('\\', '.')
+ .Replace('/', '.');
var resourceName = $"{Namespace}.{normalizedPath}";
- var stream =
- Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)
- ?? throw new FileNotFoundException(
- $"Embedded SQL resource '{resourceName}' not found."
- );
+ var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)
+ ?? throw new FileNotFoundException($"Embedded SQL resource '{resourceName}' not found.");
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_down.sql b/src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_down.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_down.sql
rename to src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_down.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_up.sql b/src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_up.sql
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_up.sql
rename to src/APITemplate.Infrastructure/Database/Triggers/row_version_triggers_v1_up.sql
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/ChannelEmailQueue.cs b/src/APITemplate.Infrastructure/Email/ChannelEmailQueue.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/ChannelEmailQueue.cs
rename to src/APITemplate.Infrastructure/Email/ChannelEmailQueue.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/EmailSendingBackgroundService.cs b/src/APITemplate.Infrastructure/Email/EmailSendingBackgroundService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/EmailSendingBackgroundService.cs
rename to src/APITemplate.Infrastructure/Email/EmailSendingBackgroundService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/FailedEmailErrorNormalizer.cs b/src/APITemplate.Infrastructure/Email/FailedEmailErrorNormalizer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/FailedEmailErrorNormalizer.cs
rename to src/APITemplate.Infrastructure/Email/FailedEmailErrorNormalizer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/FailedEmailStore.cs b/src/APITemplate.Infrastructure/Email/FailedEmailStore.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/FailedEmailStore.cs
rename to src/APITemplate.Infrastructure/Email/FailedEmailStore.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/FluidEmailTemplateRenderer.cs b/src/APITemplate.Infrastructure/Email/FluidEmailTemplateRenderer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/FluidEmailTemplateRenderer.cs
rename to src/APITemplate.Infrastructure/Email/FluidEmailTemplateRenderer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/MailKitEmailSender.cs b/src/APITemplate.Infrastructure/Email/MailKitEmailSender.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/MailKitEmailSender.cs
rename to src/APITemplate.Infrastructure/Email/MailKitEmailSender.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/Templates/tenant-invitation.liquid b/src/APITemplate.Infrastructure/Email/Templates/tenant-invitation.liquid
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/Templates/tenant-invitation.liquid
rename to src/APITemplate.Infrastructure/Email/Templates/tenant-invitation.liquid
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/Templates/user-registration.liquid b/src/APITemplate.Infrastructure/Email/Templates/user-registration.liquid
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/Templates/user-registration.liquid
rename to src/APITemplate.Infrastructure/Email/Templates/user-registration.liquid
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/Templates/user-role-changed.liquid b/src/APITemplate.Infrastructure/Email/Templates/user-role-changed.liquid
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Email/Templates/user-role-changed.liquid
rename to src/APITemplate.Infrastructure/Email/Templates/user-role-changed.liquid
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/FileStorage/LocalFileStorageService.cs b/src/APITemplate.Infrastructure/FileStorage/LocalFileStorageService.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/FileStorage/LocalFileStorageService.cs
rename to src/APITemplate.Infrastructure/FileStorage/LocalFileStorageService.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/GlobalUsings.cs b/src/APITemplate.Infrastructure/GlobalUsings.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/GlobalUsings.cs
rename to src/APITemplate.Infrastructure/GlobalUsings.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Health/HealthCheckNames.cs b/src/APITemplate.Infrastructure/Health/HealthCheckNames.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Health/HealthCheckNames.cs
rename to src/APITemplate.Infrastructure/Health/HealthCheckNames.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Health/KeycloakHealthCheck.cs b/src/APITemplate.Infrastructure/Health/KeycloakHealthCheck.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Health/KeycloakHealthCheck.cs
rename to src/APITemplate.Infrastructure/Health/KeycloakHealthCheck.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Health/MongoDbHealthCheck.cs b/src/APITemplate.Infrastructure/Health/MongoDbHealthCheck.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Health/MongoDbHealthCheck.cs
rename to src/APITemplate.Infrastructure/Health/MongoDbHealthCheck.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Idempotency/DistributedCacheIdempotencyStore.cs b/src/APITemplate.Infrastructure/Idempotency/DistributedCacheIdempotencyStore.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Idempotency/DistributedCacheIdempotencyStore.cs
rename to src/APITemplate.Infrastructure/Idempotency/DistributedCacheIdempotencyStore.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Idempotency/IdempotencyStoreConstants.cs b/src/APITemplate.Infrastructure/Idempotency/IdempotencyStoreConstants.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Idempotency/IdempotencyStoreConstants.cs
rename to src/APITemplate.Infrastructure/Idempotency/IdempotencyStoreConstants.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Idempotency/InMemoryIdempotencyStore.cs b/src/APITemplate.Infrastructure/Idempotency/InMemoryIdempotencyStore.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Idempotency/InMemoryIdempotencyStore.cs
rename to src/APITemplate.Infrastructure/Idempotency/InMemoryIdempotencyStore.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Logging/ActivityTraceEnricher.cs b/src/APITemplate.Infrastructure/Logging/ActivityTraceEnricher.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Logging/ActivityTraceEnricher.cs
rename to src/APITemplate.Infrastructure/Logging/ActivityTraceEnricher.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Logging/LogDataClassifications.cs b/src/APITemplate.Infrastructure/Logging/LogDataClassifications.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Logging/LogDataClassifications.cs
rename to src/APITemplate.Infrastructure/Logging/LogDataClassifications.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Logging/RedactionConfiguration.cs b/src/APITemplate.Infrastructure/Logging/RedactionConfiguration.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Logging/RedactionConfiguration.cs
rename to src/APITemplate.Infrastructure/Logging/RedactionConfiguration.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.Designer.cs b/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.Designer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.Designer.cs
rename to src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.Designer.cs
diff --git a/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.cs b/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.cs
new file mode 100644
index 00000000..8a6af202
--- /dev/null
+++ b/src/APITemplate.Infrastructure/Migrations/20260302153430_AddCategory.cs
@@ -0,0 +1,95 @@
+using APITemplate.Infrastructure.Database;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace APITemplate.Migrations
+{
+ ///
+ public partial class AddCategory : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Categories",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Categories", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Products",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false),
+ Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true),
+ Price = table.Column(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
+ CategoryId = table.Column(type: "uuid", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Products", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Products_Categories_CategoryId",
+ column: x => x.CategoryId,
+ principalTable: "Categories",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.SetNull);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ProductReviews",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ ProductId = table.Column(type: "uuid", nullable: false),
+ ReviewerName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ Comment = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true),
+ Rating = table.Column(type: "integer", nullable: false),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ProductReviews", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ProductReviews_Products_ProductId",
+ column: x => x.ProductId,
+ principalTable: "Products",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ProductReviews_ProductId",
+ table: "ProductReviews",
+ column: "ProductId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Products_CategoryId",
+ table: "Products",
+ column: "CategoryId");
+
+ migrationBuilder.Sql(SqlResource.Load("Procedures.get_product_category_stats_v1_up.sql"));
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql(SqlResource.Load("Procedures.get_product_category_stats_v1_down.sql"));
+
+ migrationBuilder.DropTable(name: "ProductReviews");
+ migrationBuilder.DropTable(name: "Products");
+ migrationBuilder.DropTable(name: "Categories");
+ }
+ }
+}
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.Designer.cs b/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.Designer.cs
similarity index 100%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.Designer.cs
rename to src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.Designer.cs
diff --git a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.cs b/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.cs
similarity index 51%
rename from monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.cs
rename to src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.cs
index 0c9a702c..f697b9f4 100644
--- a/monolith/API-Template-mono/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.cs
+++ b/src/APITemplate.Infrastructure/Migrations/20260304124643_AddMultiTenantAuditSoftDelete.cs
@@ -17,28 +17,24 @@ protected override void Up(MigrationBuilder migrationBuilder)
migrationBuilder.RenameColumn(
name: "CreatedAt",
table: "Products",
- newName: "UpdatedAtUtc"
- );
+ newName: "UpdatedAtUtc");
migrationBuilder.RenameColumn(
name: "CreatedAt",
table: "ProductReviews",
- newName: "UpdatedAtUtc"
- );
+ newName: "UpdatedAtUtc");
migrationBuilder.RenameColumn(
name: "CreatedAt",
table: "Categories",
- newName: "UpdatedAtUtc"
- );
+ newName: "UpdatedAtUtc");
migrationBuilder.AddColumn(
name: "CreatedAtUtc",
table: "Products",
type: "timestamp with time zone",
nullable: false,
- defaultValueSql: "now()"
- );
+ defaultValueSql: "now()");
migrationBuilder.AddColumn(
name: "CreatedBy",
@@ -46,31 +42,27 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "character varying(200)",
maxLength: 200,
nullable: false,
- defaultValue: "system"
- );
+ defaultValue: "system");
migrationBuilder.AddColumn(
name: "DeletedAtUtc",
table: "Products",
type: "timestamp with time zone",
- nullable: true
- );
+ nullable: true);
migrationBuilder.AddColumn(
name: "DeletedBy",
table: "Products",
type: "character varying(200)",
maxLength: 200,
- nullable: true
- );
+ nullable: true);
migrationBuilder.AddColumn(
name: "IsDeleted",
table: "Products",
type: "boolean",
nullable: false,
- defaultValue: false
- );
+ defaultValue: false);
migrationBuilder.AddColumn(
name: "RowVersion",
@@ -78,16 +70,14 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "bytea",
rowVersion: true,
nullable: false,
- defaultValue: new byte[0]
- );
+ defaultValue: new byte[0]);
migrationBuilder.AddColumn(
name: "TenantId",
table: "Products",
type: "uuid",
nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- );
+ defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn(
name: "UpdatedBy",
@@ -95,16 +85,14 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "character varying(200)",
maxLength: 200,
nullable: false,
- defaultValue: "system"
- );
+ defaultValue: "system");
migrationBuilder.AddColumn(
name: "CreatedAtUtc",
table: "ProductReviews",
type: "timestamp with time zone",
nullable: false,
- defaultValueSql: "now()"
- );
+ defaultValueSql: "now()");
migrationBuilder.AddColumn(
name: "CreatedBy",
@@ -112,31 +100,27 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "character varying(200)",
maxLength: 200,
nullable: false,
- defaultValue: "system"
- );
+ defaultValue: "system");
migrationBuilder.AddColumn(
name: "DeletedAtUtc",
table: "ProductReviews",
type: "timestamp with time zone",
- nullable: true
- );
+ nullable: true);
migrationBuilder.AddColumn(
name: "DeletedBy",
table: "ProductReviews",
type: "character varying(200)",
maxLength: 200,
- nullable: true
- );
+ nullable: true);
migrationBuilder.AddColumn(
name: "IsDeleted",
table: "ProductReviews",
type: "boolean",
nullable: false,
- defaultValue: false
- );
+ defaultValue: false);
migrationBuilder.AddColumn(
name: "RowVersion",
@@ -144,16 +128,14 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "bytea",
rowVersion: true,
nullable: false,
- defaultValue: new byte[0]
- );
+ defaultValue: new byte[0]);
migrationBuilder.AddColumn(
name: "TenantId",
table: "ProductReviews",
type: "uuid",
nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- );
+ defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn(
name: "UpdatedBy",
@@ -161,16 +143,14 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "character varying(200)",
maxLength: 200,
nullable: false,
- defaultValue: "system"
- );
+ defaultValue: "system");
migrationBuilder.AddColumn(
name: "CreatedAtUtc",
table: "Categories",
type: "timestamp with time zone",
nullable: false,
- defaultValueSql: "now()"
- );
+ defaultValueSql: "now()");
migrationBuilder.AddColumn(
name: "CreatedBy",
@@ -178,31 +158,27 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "character varying(200)",
maxLength: 200,
nullable: false,
- defaultValue: "system"
- );
+ defaultValue: "system");
migrationBuilder.AddColumn(
name: "DeletedAtUtc",
table: "Categories",
type: "timestamp with time zone",
- nullable: true
- );
+ nullable: true);
migrationBuilder.AddColumn(
name: "DeletedBy",
table: "Categories",
type: "character varying(200)",
maxLength: 200,
- nullable: true
- );
+ nullable: true);
migrationBuilder.AddColumn(
name: "IsDeleted",
table: "Categories",
type: "boolean",
nullable: false,
- defaultValue: false
- );
+ defaultValue: false);
migrationBuilder.AddColumn(
name: "RowVersion",
@@ -210,16 +186,14 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "bytea",
rowVersion: true,
nullable: false,
- defaultValue: new byte[0]
- );
+ defaultValue: new byte[0]);
migrationBuilder.AddColumn(
name: "TenantId",
table: "Categories",
type: "uuid",
nullable: false,
- defaultValue: new Guid("00000000-0000-0000-0000-000000000000")
- );
+ defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
migrationBuilder.AddColumn(
name: "UpdatedBy",
@@ -227,359 +201,206 @@ protected override void Up(MigrationBuilder migrationBuilder)
type: "character varying(200)",
maxLength: 200,
nullable: false,
- defaultValue: "system"
- );
+ defaultValue: "system");
migrationBuilder.CreateTable(
name: "Tenants",
columns: table => new
{
Id = table.Column(type: "uuid", nullable: false),
- Code = table.Column(
- type: "character varying(100)",
- maxLength: 100,
- nullable: false
- ),
- Name = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: false
- ),
- IsActive = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: true
- ),
+ Code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false),
+ IsActive = table.Column(type: "boolean", nullable: false, defaultValue: true),
TenantId = table.Column(type: "uuid", nullable: false),
- CreatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- CreatedBy = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: false,
- defaultValue: "system"
- ),
- UpdatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- UpdatedBy = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: false,
- defaultValue: "system"
- ),
- IsDeleted = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- DeletedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: true
- ),
- DeletedBy = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: true
- ),
- RowVersion = table.Column(
- type: "bytea",
- rowVersion: true,
- nullable: false
- ),
+ CreatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
+ CreatedBy = table.Column(type: "character varying(200)", maxLength: 200, nullable: false, defaultValue: "system"),
+ UpdatedAtUtc = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
+ UpdatedBy = table.Column(type: "character varying(200)", maxLength: 200, nullable: false, defaultValue: "system"),
+ IsDeleted = table.Column(type: "boolean", nullable: false, defaultValue: false),
+ DeletedAtUtc = table.Column(type: "timestamp with time zone", nullable: true),
+ DeletedBy = table.Column(type: "character varying(200)", maxLength: 200, nullable: true),
+ RowVersion = table.Column(type: "bytea", rowVersion: true, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tenants", x => x.Id);
- table.CheckConstraint(
- "CK_Tenants_SoftDeleteConsistency",
- "\"IsDeleted\" OR (\"DeletedAtUtc\" IS NULL AND \"DeletedBy\" IS NULL)"
- );
- }
- );
+ table.CheckConstraint("CK_Tenants_SoftDeleteConsistency", "\"IsDeleted\" OR (\"DeletedAtUtc\" IS NULL AND \"DeletedBy\" IS NULL)");
+ });
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column(type: "uuid", nullable: false),
- Username = table.Column(
- type: "character varying(100)",
- maxLength: 100,
- nullable: false
- ),
- Email = table.Column(
- type: "character varying(320)",
- maxLength: 320,
- nullable: false
- ),
- PasswordHash = table.Column(
- type: "character varying(1000)",
- maxLength: 1000,
- nullable: false
- ),
- IsActive = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: true
- ),
+ Username = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ Email = table.Column(type: "character varying(320)", maxLength: 320, nullable: false),
+ PasswordHash = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false),
+ IsActive = table.Column(type: "boolean", nullable: false, defaultValue: true),
TenantId = table.Column(type: "uuid", nullable: false),
- CreatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- CreatedBy = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: false,
- defaultValue: "system"
- ),
- UpdatedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: false,
- defaultValueSql: "now()"
- ),
- UpdatedBy = table.Column(
- type: "character varying(200)",
- maxLength: 200,
- nullable: false,
- defaultValue: "system"
- ),
- IsDeleted = table.Column(
- type: "boolean",
- nullable: false,
- defaultValue: false
- ),
- DeletedAtUtc = table.Column(
- type: "timestamp with time zone",
- nullable: true
- ),
- DeletedBy = table.Column