130 Commits
Author SHA1 Message Date
hermes 6114d8f457 Updated documentation 2026-10-28 09:28:06 +02:00
hermes b8c3201c58 Refactored quartz healthcheck on api 2026-10-27 23:59:58 +02:00
hermes a8daea830f Refactored quartz client registration 2026-10-27 23:48:36 +02:00
hermes 5be7c8c56a Removed defaults from feature flags 2026-10-27 23:17:18 +02:00
hermes d24b0dafb1 Added http accessor to scheduler 2026-10-27 23:04:50 +02:00
hermes cd47a2b93a Fixed scheduler and api configuration 2026-10-27 22:08:12 +02:00
hermes c11535ebfc Added scheduler 2026-10-27 21:31:27 +02:00
hermes 129d10c7d1 Added jobs project and updated nuget packages 2026-10-27 08:28:24 +02:00
hermes 9f132eeb29 Fixed UI misalignments 2026-10-26 19:57:32 +02:00
hermes 80c798e5e6 Added Portfolio Analytics refresh button and card-per-award with contract, programme, and portfolio details 2026-10-26 17:45:52 +02:00
hermes 2690b3230e Refactored Evidence to UpdateBy is optional When creating evidence 2026-10-26 17:03:53 +02:00
hermes 155a379ab4 Resolved reply recipient userId from TenantUsers and enabled Beneficiary compliance exploration modal 2026-10-26 10:02:14 +02:00
hermes f56cb70b55 Fixed dshboard render crash 2026-10-26 09:34:15 +02:00
hermes bb77a4dc9a PartnerUI evidence, notification, indicators and actuals fixes 2026-10-26 08:53:05 +02:00
hermes 5ac317cbe3 Fixed partnerui hashing and over all theming 2026-10-25 19:44:46 +02:00
hermes 0bcc3484dc Updated evidence handling 2026-10-25 18:58:30 +02:00
hermes fddf400ddd Updated partner project listing to use returned data 2026-10-25 18:30:32 +02:00
hermes a1b8410bc2 Updated invite creation 2026-10-25 17:46:59 +02:00
hermes cb0da32ce4 Fixed project status update on approval to do perform compliance check 2026-10-25 17:14:42 +02:00
hermes 70e286670e Light refactor 2026-10-25 13:48:15 +02:00
hermes c6075a5c39 Fixed update organisation endpoint constraints 2026-10-25 13:47:33 +02:00
hermes 4e1211a1e1 Fixed idNumber missed assignment when updating organisation 2026-10-25 13:34:52 +02:00
hermes c8e54815ef Fixed update modal linked user assignment 2026-10-25 13:23:25 +02:00
hermes 31d775c917 Fixed frozen org editor modal 2026-10-25 12:50:35 +02:00
hermes 78d147b002 Fix UI freeze by removing cyclical useEffect and stabilizing executive candidate memo 2026-10-25 12:13:15 +02:00
hermes 07018c3ce4 Auto-assign linked executive user and persist userId in organisation update modal 2026-10-25 12:02:30 +02:00
hermes db08bbf162 Added admin override when updating organisation 2026-10-25 11:19:59 +02:00
hermes ac357285c3 Enhanced shieldcheck 2026-10-25 11:07:05 +02:00
hermes a4927f9742 Fix vite env type declarations and tsconfig in PartnerUI 2026-10-25 11:04:29 +02:00
hermes fac88f3471 Fix missing lucide icon imports in GovernancePage and null-safety in DashboardPage 2026-10-25 10:35:27 +02:00
hermes aeb7542cfe Optimised cach usage, and enforced maker checker 2026-10-25 10:11:57 +02:00
hermes 9724bbac24 Removed award validation from budget creation process 2026-10-25 09:17:00 +02:00
hermes deb21dbb9a Redesigned dashboard 2026-10-25 08:52:14 +02:00
khwezi 98fe1e3e3d Merge pull request 'Moved top bar to top' (#173) from finishline into main
Reviewed-on: #173
2026-10-25 08:27:23 +02:00
hermes 48edfa1cbb Moved top bar to top 2026-10-25 08:26:37 +02:00
khwezi d9b65010cf Merge pull request 'Redesigned PartnerUi dashboard' (#172) from finishline into main
Reviewed-on: #172
2026-10-25 08:02:27 +02:00
hermes 3e1d8bc1cc Redesigned PartnerUi dashboard 2026-10-25 08:01:34 +02:00
khwezi bc441c1255 Merge pull request 'Fixed ConsoleUi salt handling' (#171) from finishline into main
Reviewed-on: #171
2026-10-24 18:10:42 +02:00
hermes 17a4149a73 Fixed ConsoleUi salt handling 2026-10-24 18:09:59 +02:00
khwezi 6e786ca796 Merge pull request 'Finishline' (#170) from finishline into main
Reviewed-on: #170
2026-10-24 17:37:15 +02:00
hermes 25e251aa79 Overhauled lockout process 2026-10-24 17:36:30 +02:00
hermes a731c979bb Overhauled lockout process 2026-10-24 17:29:48 +02:00
khwezi 90631d22ae Merge pull request 'Applied extensive leaked secret protection' (#169) from finishline into main
Reviewed-on: #169
2026-10-24 15:57:01 +02:00
hermes 17a99ff084 Applied extensive leaked secret protection 2026-10-24 15:56:25 +02:00
khwezi 010f2d5eaa Merge pull request 'Stopped leaking secrets' (#168) from finishline into main
Reviewed-on: #168
2026-10-24 15:44:08 +02:00
hermes 29a11f0e0b Stopped leaking secrets 2026-10-24 15:43:10 +02:00
khwezi b8f5ebf69a Merge pull request 'Finishline' (#167) from finishline into main
Reviewed-on: #167
2026-10-24 15:23:51 +02:00
hermes 385c76510b Fixed partnerui message viewer 2026-10-24 15:23:12 +02:00
hermes 631a0b7a9a Fixed partner message reader 2026-10-24 15:18:34 +02:00
khwezi 957e75a853 Merge pull request 'Applied CORS fix' (#166) from finishline into main
Reviewed-on: #166
2026-10-24 14:59:28 +02:00
hermes 1233f15b91 Applied CORS fix 2026-10-24 14:58:55 +02:00
khwezi 8a06a86eb3 Merge pull request 'Fixed Console user lockout and unlock implementation' (#165) from finishline into main
Reviewed-on: #165
2026-10-24 14:39:40 +02:00
hermes c43e18f184 Fixed Console user lockout and unlock implementation 2026-10-24 14:38:55 +02:00
khwezi f8f826979d Merge pull request 'Finishline' (#164) from finishline into main
Reviewed-on: #164
2026-10-24 12:51:15 +02:00
hermes d912c5c6c3 Added partner to api cors list 2026-10-24 12:50:28 +02:00
hermes 20bdd8b209 Removed joborchestrator 2026-10-24 12:41:03 +02:00
khwezi c4a0469bae Merge pull request 'Finishline' (#163) from finishline into main
Reviewed-on: #163
2026-10-24 12:13:11 +02:00
hermes bf384d7e6a Reverted changes 2026-10-24 12:12:27 +02:00
hermes 957d2210a9 Refactored sync process 2026-10-24 12:11:48 +02:00
khwezi a3f461478c Merge pull request 'PartnerUi fixes' (#162) from finishline into main
Reviewed-on: #162
2026-10-24 11:49:45 +02:00
hermes 16fb6b9434 Simplified lockout modals 2026-10-24 11:48:19 +02:00
hermes 7220700bac Removed setUserLockout from PartnerUI 2026-10-24 11:39:47 +02:00
khwezi cc62696017 Merge pull request 'Fixed sync handshake' (#161) from finishline into main
Reviewed-on: #161
2026-10-23 10:20:48 +02:00
hermes 3316fc0275 Fixed sync handshake 2026-10-23 10:19:30 +02:00
khwezi d4598e0c6c Merge pull request 'Implemented partnerui' (#160) from finishline into main
Reviewed-on: #160
2026-10-23 00:59:39 +02:00
hermes 8f58e15f0f Implemented partnerui 2026-10-23 00:58:47 +02:00
khwezi 43656e848a Merge pull request 'Applied mailbox constraints' (#159) from finishline into main
Reviewed-on: #159
2026-10-22 23:58:12 +02:00
hermes 9ed210613e Applied mailbox constraints 2026-10-22 23:57:38 +02:00
khwezi 3a3245b643 Merge pull request 'Fixed notification composition' (#158) from finishline into main
Reviewed-on: #158
2026-10-22 23:36:39 +02:00
hermes 5050c901bc Fixed notification composition 2026-10-22 23:36:03 +02:00
khwezi fdaf12a183 Merge pull request 'Implemented notification strategy' (#157) from finishline into main
Reviewed-on: #157
2026-10-22 23:01:14 +02:00
hermes 46c6ddcc80 Implemented notification strategy 2026-10-22 22:59:33 +02:00
khwezi 6cdd03a02f Merge pull request 'Implemented markdown based notification message body formatting' (#156) from finishline into main
Reviewed-on: #156
2026-10-22 18:19:36 +02:00
hermes 7071c404f0 Implemented markdown based notification message body formatting 2026-10-22 18:19:08 +02:00
khwezi 1d1060d031 Merge pull request 'Implemented mailbox in ConsoleUI' (#155) from finishline into main
Reviewed-on: #155
2026-10-22 18:01:54 +02:00
hermes cb89817d88 Implemented mailbox in ConsoleUI 2026-10-22 18:01:22 +02:00
khwezi b0c89362d0 Merge pull request 'Fixed beneficiary page type error' (#154) from finishline into main
Reviewed-on: #154
2026-10-22 17:15:04 +02:00
hermes 6ff016e7ec Fixed beneficiary page type error 2026-10-22 17:14:35 +02:00
khwezi dee9ccf0b1 Merge pull request 'Fixed beneficiary outcomes misconfiguration' (#153) from finishline into main
Reviewed-on: #153
2026-10-22 17:08:35 +02:00
hermes d45a89ab67 Fixed beneficiary outcomes misconfiguration 2026-10-22 17:08:01 +02:00
khwezi d9b3e80f7c Merge pull request 'Finishline' (#152) from finishline into main
Reviewed-on: #152
2026-10-22 16:08:08 +02:00
hermes 3fda1ac5db Applied RBAC to compliance dashboard 2026-10-22 16:06:05 +02:00
hermes efc63ca398 Fixed error with compliance page 2026-10-22 15:58:22 +02:00
hermes 299d6ff4dc Improved compliance monitoring 2026-10-22 15:42:36 +02:00
khwezi 8688c752d6 Merge pull request 'Enforced relationships between activities and indicators, fixed actuals recording' (#151) from finishline into main
Reviewed-on: #151
2026-10-22 13:49:32 +02:00
hermes 9835121fd9 Enforced relationships between activities and indicators, fixed actuals recording 2026-10-22 13:47:54 +02:00
khwezi 937a5595d8 Merge pull request 'Implemented audit logging' (#150) from finishline into main
Reviewed-on: #150
2026-10-22 12:37:14 +02:00
hermes 4dd0899209 Implemented audit logging 2026-10-22 12:31:37 +02:00
khwezi 58ce606410 Merge pull request 'Enforced approval rights' (#149) from finishline into main
Reviewed-on: #149
2026-10-22 11:26:49 +02:00
hermes 92891438f6 Enforced approval rights 2026-10-22 11:25:55 +02:00
khwezi a98bfa4308 Merge pull request 'Refactored project view into modal' (#148) from finishline into main
Reviewed-on: #148
2026-10-22 09:42:26 +02:00
hermes 0261a6093e Refactored project view into modal
Elevated toast message priority
2026-10-22 09:41:41 +02:00
khwezi 6e56a4c223 Merge pull request 'Refactored projects page, fixed security user modal' (#147) from finishline into main
Reviewed-on: #147
2026-10-22 07:40:34 +02:00
hermes ccc49df720 Refactored projects page, fixed security user modal 2026-10-22 07:39:33 +02:00
khwezi e38edd24a0 Merge pull request 'Finishline' (#146) from finishline into main
Reviewed-on: #146
2026-10-22 06:24:18 +02:00
hermes c24e809b88 Applied UI rendering fixes 2026-10-22 06:23:22 +02:00
hermes 87f597c091 docs: formalize C# formatting, brace discipline, FluentResults vs ternary, grouping, and clean namespace rules in GEMINI.md 2026-10-22 05:01:10 +02:00
hermes 5a6c951641 docs: codify C# functional style, low branching, try-catch avoidance, and Clean/Vertical Slice/SOLID/DRY balance in GEMINI.md 2026-10-22 04:55:44 +02:00
khwezi 1ac0018926 Merge pull request 'Finishline' (#145) from finishline into main
Reviewed-on: #145
2026-10-21 11:07:27 +02:00
hermes acf8bc224b Fixed docker compose 2026-10-21 11:06:56 +02:00
hermes 558ca0dbb4 Resolved volnerability 2026-10-21 11:00:48 +02:00
khwezi 5ad59f6569 Merge pull request 'Restructured .net solutions' (#144) from finishline into main
Reviewed-on: #144
2026-10-21 10:41:52 +02:00
hermes 270929948d Restructured .net solutions
Resolved volnerability
2026-10-21 10:41:23 +02:00
khwezi 71a0abf0b2 Merge pull request 'Fixed new user username formating' (#143) from finishline into main
Reviewed-on: #143
2026-10-21 10:18:50 +02:00
hermes 95a86e1a87 Fixed new user username formating 2026-10-21 10:16:54 +02:00
khwezi 627885e36b Merge pull request 'Finishline' (#142) from finishline into main
Reviewed-on: #142
2026-10-21 08:55:56 +02:00
hermes 5d7e2f74ce RBAC Improvements
Caching improvements
Maker-Checker Implementation
2026-10-21 07:27:47 +02:00
hermes dee9098379 Fixed tenant management 2026-10-20 17:56:04 +02:00
hermes d7a168c9fa Create Organisation Fix 2026-10-20 12:47:06 +02:00
hermes 28a821ae91 Added GEMINI.md file 2026-10-19 10:54:11 +02:00
khwezi 447ffb366e Merge pull request 'Implemented rbac' (#141) from finishline into main
Reviewed-on: #141
2026-10-11 11:17:48 +02:00
hermes 1142cfd94e Implemented rbac 2026-10-11 11:16:08 +02:00
khwezi a9bb319eb1 Merge pull request 'Fixed skoruba api value assignment in docker compose' (#140) from finishline into main
Reviewed-on: #140
2026-10-11 08:29:59 +02:00
hermes 45a6c0aca0 Fixed skoruba api value assignment in docker compose 2026-10-11 08:29:30 +02:00
khwezi bbba869cdd Merge pull request 'Fixed skoruba api address configs' (#139) from finishline into main
Reviewed-on: #139
2026-10-11 08:18:06 +02:00
hermes 1b586da2ab Fixed skoruba api address configs 2026-10-11 08:17:21 +02:00
khwezi 5ae2622454 Merge pull request 'Finishline' (#138) from finishline into main
Reviewed-on: #138
2026-10-11 08:02:57 +02:00
hermes 4735551e06 Removed nontest user 2026-10-11 08:02:33 +02:00
hermes a28caa5c69 Corrected admin security docker compose api configs 2026-10-11 08:01:33 +02:00
khwezi bd0f03344d Merge pull request 'Finishline' (#137) from finishline into main
Reviewed-on: #137
2026-10-11 07:50:36 +02:00
hermes c3cc21f334 Refactored docker compose 2026-10-11 07:49:38 +02:00
hermes 9a0371fb08 Added skorubauserservice tests 2026-10-11 07:43:51 +02:00
hermes 381d5b7e94 Stable user lockout, role and claim calls 2026-10-10 18:14:31 +02:00
hermes d3566c5e43 Light refactor 2026-10-10 15:44:27 +02:00
hermes 0352d154a4 Added debug only fact 2026-10-10 15:35:32 +02:00
hermes 8eca793f5f Added skoruba user service integration test 2026-10-10 15:31:36 +02:00
hermes 343f405982 Added consolidated applicant data retrieval endpoint 2026-10-10 11:11:23 +02:00
hermes 2ee7a49122 Added user management endpoints 2026-10-10 10:12:13 +02:00
hermes 1ebd83a2f4 Removed repetetive static usings and aliases 2026-10-05 08:57:39 +02:00
hermes da2c0f9a34 Now using global static usings on endpoints to reduce boilerplate 2026-10-05 08:33:00 +02:00
366 changed files with 48641 additions and 6258 deletions
+318
View File
@@ -0,0 +1,318 @@
# PostFundManagement (PFM) Platform — Architectural Specification
> **Document Version**: 2.0
> **Target Runtimes**: .NET 10.0 (C# 13) | React 19 / TypeScript 5.8 / Vite
> **Scope**: System Architecture, Security Topology, RBAC Enforcement, and Maker-Checker Pattern
---
## 1. System Topology & Architectural Layers
The PFM platform is constructed using **Clean Architecture** and **Domain-Driven Design (DDD)** principles to guarantee strict decoupling of business rules, persistence concerns, and user interfaces.
```
┌────────────────────────────────────────────────────────────────────────┐
│ PRESENTATION TIER │
│ PostFundManagement.ConsoleUI │ PostFundManagement.PartnerUI │
│ (Internal Staff & Governance) │ (External Recipient Portal) │
│ React 19 + TypeScript + Vite │ React 19 + WebGL (OGL) + Vite │
└───────────────────────────────────┬────────────────────────────────────┘
│ HTTPS / REST (v1/api/*) + OIDC PKCE
┌───────────────────────────────────▼────────────────────────────────────┐
│ API GATEWAY │
│ PostFundManagement.Api (ASP.NET Core Minimal APIs / Carter) │
│ Authentication: JWT Bearer + OpenTelemetry Observability │
│ Quartz Client: Scheduler-Only (ZeroSizeThreadPool / Enqueues Jobs) │
└───────────────────────────────────┬────────────────────────────────────┘
│
┌───────────────────────────────────▼────────────────────────────────────┐
│ APPLICATION LAYER │
│ PostFundManagement.Application │
│ CQRS (Mediator), Business Validators, Domain Services, Event Handlers │
└──────────────────┬─────────────────────────────────┬───────────────────┘
│ │
┌──────────────────▼──────────────────┐ ┌──────────▼───────────────────┐
│ DOMAIN LAYER │ │ INFRASTRUCTURE LAYER │
│ PostFundManagement.Domain │ │ PostFundManagement.Infrastr.│
│ Entities, Value Objects, Enums, │ │ PostgreSQL (EF Core 10), │
│ Domain Invariants, Audit Models │ │ Skoruba STS, S3 Garage, │
└─────────────────────────────────────┘ │ Redis, Quartz (pfm-scheduler)│
└──────────────┬───────────────┘
│
┌────────────────────────────────────────────────────────▼───────────────┐
│ BACKGROUND JOBS WORKER HOST │
│ PostFundManagement.Jobs (.NET 10 Background Host / Quartz Runner) │
│ Recurring Job Registration (JobsWorker) & Mediated Execution Engine │
│ Quartz Runner: Full Execution (DefaultThreadPool MaxConcurrency = 5) │
└────────────────────────────────────────────────────────────────────────┘
```
---
## 2. Security & Identity Architecture
Authentication and authorization are separated cleanly across system boundaries:
* **Security Token Service (STS)**: Duende IdentityServer / Skoruba Administration (`https://sts.security.khongisa.co.za`) issues cryptographically signed JWT access tokens using OpenID Connect (OIDC) with Proof Key for Code Exchange (PKCE).
* **Audience & Scopes**: Web clients request standard OIDC scopes (`openid`, `profile`, `roles`, `email`, `address`) along with the target API resource scope (`pfm-api-dev`).
* **Tenant Isolation**: Every authenticated principal carries a `tenant_id` claim. Multitenant query filters in EF Core enforce cross-tenant partition boundaries at the data access layer.
* **Partner Isolation**: Grantees and external recipients possess the `partner` role. The `ConsoleUI` enforces immediate lockout for `partner` roles, restricting external users exclusively to `PartnerUI`.
---
## 3. Role-Based Access Control (RBAC) Architecture
PFM implements a fine-grained, claim-based RBAC model where system permissions are decomposed into **15 operational domains** and **5 progressive action capabilities**.
### 3.1 Role Claims Inventory
| Claim Key | Operational Domain | Functional Scope |
| :--- | :--- | :--- |
| `dashboard` | Executive Dashboard | Macro portfolio KPIs, disbursed capital, and ISO 31000 risk heatmap |
| `governance` | Governance & Structure | Holding entities, funder portfolios, programmes, rules, and instruments |
| `projects_milestones` | Projects & Milestones | Work breakdown structure (WBS), contractual milestones, and activities |
| `grant_financials` | Grants & Financials | Statutory funding awards, budget lines, contracts, and disbursement tranches |
| `evidence_risks` | M&E, Evidence & Risks | Deliverable evidence verification queue, indicators, and risk registers |
| `beneficiaries` | Beneficiaries & Impact | Demographic targets, POPIA compliance items, and outcome deliverables |
| `change_management` | Change Management | Project issues, contractual change requests, and budget variations |
| `site_visits` | Site Visits | Physical field inspections, checklist observations, and inspector findings |
| `settings_invites` | Settings Invites | Onboarding invitations and candidate notifications dispatch |
| `settings_users` | Settings Users | User provisioning, role assignments, and account lockouts |
| `settings_auditlog` | Settings Audit Log | Immutable audit ledger and system telemetry records |
| `settings_notifications`| Settings Notifications | SMTP engines, notification channels, and dispatch telemetry |
| `tenant_self_service` | Tenant Self-Service | Tenant profile configuration and delegated sub-user management |
| `tenant_management` | Tenant Management | Multi-tenant directory, partition allocation, and solution ownership |
| `unrestricted` | System Administration | Root administrative bypass across all system partitions |
### 3.2 Claim Actions & Capability Hierarchy
Action capabilities within a claim domain follow a strict subset hierarchy:
$$\text{view} \subset \text{add} \subset \text{update} \quad \text{and} \quad \text{view} \subset \text{export}$$
* **`view`**: Read-only access to entity lists, detailed records, metrics, and progress scores.
* **`add`**: Can create new records, edit draft records, submit milestone evidence, and change workflow statuses *other than approval* (includes `view`).
* **`update`**: Authoritative governance capability. Can execute **approval** status transitions (`Approved`, `Active` contracts) on records (includes `view` and `add`).
* **`export`**: Can generate and download structured spreadsheets (`.xlsx`) and formal PDF reports (includes `view`).
* **`all`**: Comprehensive authority (`view`, `add`, `update`, `export`) over the claim domain.
### 3.3 Authoritative Role-to-Claims Matrix
| Role | Assigned Claim Domains | Standard Capabilities | Record Approval Authority (`update`) |
| :--- | :--- | :--- | :---: |
| **`Admin`** | `unrestricted` | `all` | **Yes (System-wide)** |
| **`Executive`** | `dashboard`, `governance`, `projects_milestones`, `grant_financials`, `evidence_risks`, `tenant_self_service` | `all` | **Yes** |
| **`Manager`** | `dashboard`, `projects_milestones`, `grant_financials`, `evidence_risks`, `beneficiaries`, `change_management`, `site_visits`, `settings_invites`, `settings_users`, `settings_auditlog`, `settings_notifications`, `tenant_self_service` | `all` | **Yes** |
| **`Supervisor`** | `dashboard`, `projects_milestones`, `grant_financials`, `evidence_risks`, `beneficiaries`, `change_management`, `site_visits`, `settings_invites`, `settings_users` | `view, add, update, export`<br>*(Dashboard: `view, export`)* | **Yes** |
| **`Contributor`** | `projects_milestones`, `grant_financials`, `evidence_risks`, `beneficiaries`, `change_management`, `site_visits`, `settings_invites` | **`view, add`** | ❌ **Strictly Prohibited** |
---
## 4. Maker-Checker Pattern Architecture
To eliminate fraud risk, self-dealing, and conflicts of interest, PFM enforces the **Maker-Checker Pattern** (Segregation of Duties / Dual Authorization).
### 4.1 Core Principle
> **The user who creates or drafts a record (the "Maker") is strictly prohibited from verifying or approving that record (the "Checker"), regardless of their administrative role or RBAC claim privileges.**
Even if an `Executive`, `Manager`, or `Supervisor` possesses the `update` claim action on a domain, they cannot approve a record if they are identified as the record's creator.
```mermaid
flowchart LR
A["Maker (User A)<br/>Creates Record"] --> B["Record Persisted<br/>with createdBy = User A"]
B --> C{"Checker Evaluation<br/>(Approver Login)"}
C -->|"Checker is User A"| D["Approval BLOCKED: Maker-Checker Violation"]
C -->|"Checker is User B with update claim"| E["Approval GRANTED: Status Approved"]
```
### 4.2 Record Attribution Model
Every operational domain entity immutably captures the creator identity:
* `createdBy`: Cryptographic user identifier (Guid / Subject ID) populated from the verified token claim `context.GetUserId()` upon creation.
* `approvedBy`: User identifier of the independent verifier populated upon successful transition to `Approved`.
### 4.3 Client-Side Enforcement Hooks & Helpers
In `PostFundManagement.ConsoleUI`, the Maker-Checker rule is evaluated dynamically via [`useRbac()`](file:///c:/Users/Admin/source/repos/PostFundManagement/PostFundManagement.ConsoleUI/src/auth/rbac.ts):
* **`isMaker(createdBy)`**: Compares `createdBy` against current user `user.id`, `sub`, and `user.email`.
* **`canApproveRecord(claim, createdBy)`**: Evaluates:
$$\text{canApproveRecord} = \text{canApprove}(\text{claim}) \land \neg\text{isMaker}(\text{createdBy})$$
* **`getApprovalRestrictionReason(claim, createdBy)`**: Surfaces explanatory feedback:
- `"Requires update/approval permission"` if the claim lacks `update`.
- `"Maker-Checker policy: Record creators cannot approve their own records"` if `isMaker` is true.
### 4.4 UI Guard Implementations
1. **Status Dropdowns**: The `Approved` option in status `<select>` controls is disabled with contextual labels:
```tsx
<option value={ApprovalStatus.Approved} disabled={!canApproveRecord('projects_milestones', record.createdBy)}>
Approved {isMaker(record.createdBy) ? '(Maker-Checker: Cannot approve own record)' : ''}
</option>
```
2. **Action Buttons**: Fast-track "Approve" buttons (e.g. Budget Approval, Disbursement Authorisation, Evidence Review) are hidden or accompanied by a Maker-Checker indicator badge when viewed by the creator.
3. **Export Controls**: `<ExportButton>` enforces domain claim checks explicitly (`claim="grant_financials"`), rejecting unauthenticated exports.
---
## 5. Caching Strategy, Mutation Versioning & Performance Optimization
To achieve sub-50ms page transitions while guaranteeing data freshness across mutations in a distributed architecture, PFM employs a multi-tiered caching topology.
```
┌────────────────────────────────────────────────────────────────────────┐
│ REACT QUERY CACHE (In-Memory Browser Tier) │
│ staleTime: 2m (dynamic) | 5m (taxonomy) | gcTime: 10m │
└───────────────────────────────────┬────────────────────────────────────┘
│ Cache Miss / Background Refetch
┌───────────────────────────────────▼────────────────────────────────────┐
│ AXIOS CLIENT INTERCEPTOR & MUTATION VERSIONING (_v) │
│ Reads/Browsing: attaches active `_v` epoch token │
│ Mutations (POST/PUT/PATCH/DELETE): auto-bumps epoch -> new `_v` token │
└───────────────────────────────────┬────────────────────────────────────┘
│ HTTP GET /v1/api/*?_v=<epoch>
┌───────────────────────────────────▼────────────────────────────────────┐
│ ASP.NET CORE OUTPUTCACHE (Redis / Memory Tier) │
│ Base Policy: 2-minute expiration partitioned by URI + QueryString │
│ Hits same `_v` -> 2ms response | New `_v` -> DB query & cache refresh │
└────────────────────────────────────────────────────────────────────────┘
```
### 5.1 The OutputCache Stale Data Challenge
ASP.NET Core Minimal APIs in `PostFundManagement.Api` attach `.CacheOutput()` to GET endpoints, using a 2-minute base sliding expiration backed by Redis / Memory cache.
Because OutputCache keys are generated from the HTTP Method, Host, Path, and **Query String**, a naive client refetch after a mutation (e.g. `queryClient.invalidateQueries({ queryKey: ['projects'] })`) calls `GET /v1/api/projects`. OutputCache intercepts this identical URI, returning the pre-mutation response cached earlier. The updated project status or approved milestone does not reflect in the UI until the 2-minute cache expires.
### 5.2 Client-Side Mutation Epoch Versioning
PFM solves this without modifying backend endpoints via **Client-Side Mutation Versioning**:
1. **Epoch Tracking (`activeMutationEpoch`)**: `httpClient.ts` tracks an active mutation timestamp initialized from `sessionStorage` or `Date.now()`.
2. **Automatic Mutation Interceptor**: Every successful mutating HTTP response (`POST`, `PUT`, `PATCH`, `DELETE` with HTTP 2xx) triggers `bumpMutationEpoch()`.
3. **Query Parameter Injection**: Outgoing `GET` requests to internal APIs automatically append `_v: getMutationEpoch()`.
4. **Instant Invalidation**: When a mutation succeeds, the epoch is bumped. Subsequent React Query refetches send the new `_v`, producing an immediate **cache miss** in ASP.NET Core OutputCache. The backend queries PostgreSQL, returns fresh data, and caches the new state under the new `_v` key.
5. **Cache Hit Snappiness**: Navigations between pages while no mutations have occurred reuse the stable `_v`, yielding sub-5ms responses from Redis/Memory OutputCache and 0ms from React Query in-memory cache.
6. **Explicit Freshness (`_t`)**: When `Cache-Control: no-cache` is specified or during explicit refresh operations, a unique timestamp `_t` is attached to force a guaranteed fresh fetch.
### 5.3 RBAC Startup Pipeline Acceleration
Following the introduction of fine-grained RBAC and Maker-Checker checks, startup performance was streamlined to eliminate sequential network waterfalls:
* **Cache-First Role Claims**: `fetchAndCacheSystemRoles` and `fetchAndCacheRoleClaims` check in-memory and `localStorage` caches *before* dispatching network calls.
* **Parallel User Resolution**: `getUserRolesFromApi` and `getUserClaimsFromApi` are resolved concurrently using `Promise.allSettled`.
* **Parallel Claim Resolution**: Claims across all assigned roles are retrieved concurrently rather than in serial loops.
* **Immediate Non-Blocking Session Render**: If valid session tokens and user profiles exist in `localStorage`, `AuthProvider` immediately unblocks UI rendering (`isLoading: false`) and runs tenant lockout checks, account synchronization, and claim revalidation asynchronously in the background.
* **In-Memory User Sync & Lockout Caching**: `isUserSyncedInBrowser` and `verifyTenantLockout` maintain in-memory references to eliminate synchronous `JSON.parse` operations from the request pipeline.
---
## 6. Distributed Background Job Processing Architecture (PostFundManagement.Jobs)
The PFM platform decouples synchronous API request handling from asynchronous, long-running, and scheduled workloads using a distributed **Quartz.NET (v4.6+)** clustering topology backed by PostgreSQL (`pfm-scheduler`).
### 6.1 Split-Node Topology: Scheduler Client vs. Execution Host
To maintain high API throughput, low memory footprint, and complete process isolation, PFM implements a split-node architecture between `PostFundManagement.Api` and `PostFundManagement.Jobs`:
```
┌──────────────────────────────────────────────┐
│ PostFundManagement.Api │
│ (API Gateway / Scheduler Client) │
│ │
│ * AddQuartzSchedulerClient() │
│ * ZeroSizeThreadPool (0 execution threads) │
│ * SchedulerStatus.Created (Standby mode) │
│ * Strictly schedules / enqueues events │
│ * Never acquires triggers or executes jobs │
└──────────────────────┬───────────────────────┘
│ Schedules IEvent jobs via IJobOrchestrator
▼
┌──────────────────────────────────────────────┐
│ PostgreSQL JobStore │
│ (pfm-scheduler) │
│ │
│ * Clustered tables (QRTZ_TRIGGERS, etc.) │
│ * System.Text.Json serialized IEvent payload│
│ * Distributed locking & check-in heartbeat │
└──────────────────────▲───────────────────────┘
│
│ Acquires triggers & executes (Polling loop)
┌──────────────────────┴───────────────────────┐
│ PostFundManagement.Jobs │
│ (Dedicated Worker Service) │
│ │
│ * AddQuartzScheduler() │
│ * DefaultThreadPool (MaxConcurrency = 5) │
│ * SchedulerStatus.Running (Started loop) │
│ * JobsWorker background service: │
│ - Registers recurring cron jobs on start │
│ - Enqueues one-time jobs when needed │
│ * Executes MediatorJob<TNotification> │
│ * Deserializes and publishes via IMediator │
└──────────────────────────────────────────────┘
```
| Characteristic | `PostFundManagement.Api` | `PostFundManagement.Jobs` |
| :--- | :--- | :--- |
| **Architectural Role** | API Gateway & Event Producer | Dedicated Execution Daemon & Consumer |
| **Registration Extension** | `AddQuartzSchedulerClient(...)` | `AddQuartzScheduler(...)` |
| **Thread Pool** | `ZeroSizeThreadPool` (0 threads) | `DefaultThreadPool` (`MaxConcurrency = 5`) |
| **Hosted Service** | None (`AddQuartzHostedService` omitted) | `AddQuartzHostedService(WaitForJobsToComplete = true)` |
| **Lifecycle State** | `SchedulerStatus.Created` (Standby) | `SchedulerStatus.Running` (Active execution loop) |
| **Readiness Check** | Verifies DB connectivity without requiring `Running` | Verifies active execution engine & DB connectivity |
| **Trigger Acquisition** | ❌ **Disabled** (Cannot acquire triggers or run jobs) | ✅ **Active** (Polls DB, acquires triggers, runs jobs) |
### 6.2 Event-Driven Mediated Orchestration (`IEvent` & `MediatorJob`)
Background tasks are modeled as strongly-typed domain events implementing `IEvent : INotification` (`PostFundManagement.Domain.Abstractions`).
```mermaid
sequenceDiagram
autonumber
participant API as PostFundManagement.Api
participant Orchestrator as IJobOrchestrator
participant DB as PostgreSQL pfm-scheduler
participant Host as PostFundManagement.Jobs
participant QuartzThread as Quartz Execution Engine
participant Mediator as IMediator MediatorJob
participant Handler as Domain Event Handlers
Note over API,Orchestrator: 1. Event Scheduling (API Node)
API->>Orchestrator: SendAsync(notification) / ScheduleAsync(notification, cron)
Orchestrator->>DB: Stores JobDetail and Trigger with serialized Payload JSON
Note over DB,Host: 2. Distributed Acquisition (Jobs Node)
QuartzThread->>DB: Polls and acquires due trigger with row-level locks
QuartzThread->>Host: Dispatches to local thread pool
Host->>Mediator: Instantiates MediatorJob[TNotification]
Mediator->>Mediator: Deserializes payload from MergedJobDataMap
Mediator->>Handler: Publish(notification, cancellationToken)
Handler-->>Host: Execution complete and updates business data in pfm DB
Host->>DB: Releases trigger and commits execution state
```
1. **Scheduling via `IJobOrchestrator`**:
- `SendAsync<TNotification>(notification)`: Schedules a durable, one-time execution in the `onetime-jobs` group with `StartNow()` and `DisallowConcurrentExecution()`.
- `ScheduleAsync<TNotification>(notification, cronExpression)`: Configures a durable, cron-driven recurring job in the `scheduled-jobs` group with automatic misfire handling (`CronTriggerMisfireInstruction.IgnoreMisfires`).
- `InterruptAsync(eventName, correlationId)`: Allows runtime cancellation of active job executions across the cluster.
2. **Transparent Deserialization & Dispatch (`MediatorJob<TNotification>`)**:
`MediatorJob<TNotification>` resolves `IMediator` from DI, deserializes the JSON payload, initiates OpenTelemetry distributed tracing (`MediatorTelemetry.Source`), and publishes the event to all registered in-process handlers.
### 6.3 Recurring vs. One-Time Job Patterns
* **Recurring Jobs**: Scheduled by `JobsWorker` (`BackgroundService` in `PostFundManagement.Jobs`) during application startup. These jobs govern periodic health sweeps, SLA tracking, notification batching, and risk evaluations on predictable cron intervals.
* **One-Time Jobs**: Primarily scheduled by `PostFundManagement.Api` as immediate or deferred side-effects of HTTP requests and lifecycle mutations (e.g. user invites, award registration triggers). Furthermore, a running recurring job in `PostFundManagement.Jobs` can dispatch a one-time job (`jobOrchestrator.SendAsync`) when handling fan-out tasks or invoking decoupled activities that cannot be triggered directly by the originating job.
### 6.4 Core Background Processing Capabilities
The background job runner powers four essential operational workloads:
1. **Automated Project Monitoring & Reminders**:
- Sweeps active projects, contractual deliverables, and milestone deadlines.
- Computes planned vs. actual progress variance and escalates overdue milestones (`FR-M04-017`, `FR-M07-009`).
- Dispatches automated reminders to Grantees/Partners prior to milestone due dates and submission cut-offs.
2. **Risk Monitoring & Automated Notifications**:
- Evaluates project risk registers, Key Risk Indicators (KRIs), and issue escalation thresholds (`FR-M09-001` - `011`).
- Detects risk score creep (Likelihood $\times$ Impact) and triggers automated alerts when risk tolerance thresholds are breached.
- Monitors overdue mitigation action plans and escalates unresolved high-severity risks to Programme Managers.
3. **Email Notification Processing**:
- Continuously consumes and batches pending notification records (`Platform.Email` / `NotificationPlatform.Email`).
- Dispatches transactional emails via MailKit / MimeKit SMTP with exponential backoff and retry policies.
- Updates delivery status (`Sent`, `Failed`, retry count) and logs dispatch telemetry for audit compliance (`FR-M25-001`).
4. **Platform Extensibility**:
- New background jobs and recurring routines can be introduced by creating an `IEvent` model in `PostFundManagement.Application` and scheduling it via `JobsWorker` or `IJobOrchestrator`, with zero impact on the API surface.
+102
View File
@@ -0,0 +1,102 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);1701;1702;MA0048;MA0051;AV0029;AV0030</NoWarn>
</PropertyGroup>
<!-- Assembly signing and licensing for non-test projects -->
<PropertyGroup Condition="!$(MSBuildProjectName.EndsWith('Tests'))">
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)PostFundManagement.snk</AssemblyOriginatorKeyFile>
<PackageLicenseFile>$(MSBuildThisFileDirectory)LICENSE</PackageLicenseFile>
</PropertyGroup>
<!-- Centralized Common Usings across solution -->
<ItemGroup>
<!-- System & BCL -->
<Using Include="System.Collections.ObjectModel" />
<Using Include="System.ComponentModel.DataAnnotations" />
<Using Include="System.Globalization" />
<Using Include="System.Net" />
<Using Include="System.Reflection" />
<Using Include="System.Security.Cryptography" />
<Using Include="System.Security.Cryptography.X509Certificates" />
<Using Include="System.Text" />
<Using Include="System.Text.Json" />
<Using Include="System.Text.Json.Serialization" />
<Using Include="System.Text.RegularExpressions" />
<Using Include="System.Threading.Channels" />
<Using Include="System.Web" />
<!-- Microsoft Extensions & Web Core -->
<Using Include="Microsoft.AspNetCore.Builder" />
<Using Include="Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage" />
<Using Include="Microsoft.AspNetCore.DataProtection" />
<Using Include="Microsoft.AspNetCore.Http" />
<Using Include="Microsoft.AspNetCore.Routing" />
<Using Include="Microsoft.Extensions.Configuration" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Microsoft.Extensions.DependencyInjection.Extensions" />
<Using Include="Microsoft.Extensions.Hosting" />
<Using Include="Microsoft.Extensions.Logging" />
<Using Include="Microsoft.Extensions.Options" />
<Using Include="Microsoft.IdentityModel.Tokens" />
<Using Include="Microsoft.FeatureManagement" />
<!-- Domain Constants -->
<Using Static="true" Include="PostFundManagement.Domain.Extensions.Constants" />
<!-- CQRS, Utilities & Persistence -->
<Using Include="FluentResults" />
<Using Include="HashidsNet" />
<Using Include="Humanizer" />
<Using Include="Mediator" />
<Using Include="Microsoft.EntityFrameworkCore" />
<Using Include="Microsoft.EntityFrameworkCore.Design" />
<Using Include="Microsoft.EntityFrameworkCore.Metadata.Builders" />
<Using Include="Quartz" />
<Using Include="Refit" />
<!-- API & Documentation -->
<Using Include="Asp.Versioning" />
<Using Include="Microsoft.AspNetCore.OpenApi" />
<Using Include="Microsoft.OpenApi" />
<Using Include="Scalar.AspNetCore" />
<!-- Authentication -->
<Using Include="IdentityModel.AspNetCore.OAuth2Introspection" />
<Using Include="Microsoft.AspNetCore.Authentication" />
<Using Include="Microsoft.AspNetCore.Authentication.Cookies" />
<Using Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<Using Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<!-- Diagnostics & Health -->
<Using Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
<Using Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<!-- Messaging & Cloud Storage -->
<Using Include="Amazon.Runtime" />
<Using Include="Amazon.S3" />
<Using Include="Amazon.S3.Model" />
<Using Include="MailKit.Net.Smtp" />
<Using Include="MimeKit" />
</ItemGroup>
<!-- Domain-specific Usings (Domain and downstream projects excluding Infrastructure where Claim/Activity/TokenRequest collide) -->
<ItemGroup Condition="'$(MSBuildProjectName)' == 'PostFundManagement.Domain'">
<Using Include="System.Security.Claims" />
<Using Include="AccessTokenClient" />
</ItemGroup>
<!-- Non-Domain / Non-Infrastructure Usings (Application, Api, Tests) -->
<ItemGroup Condition="'$(MSBuildProjectName)' != 'PostFundManagement.Domain' and '$(MSBuildProjectName)' != 'PostFundManagement.Infrastructure'">
<Using Include="System.Diagnostics" />
<Using Include="System.Security.Claims" />
<Using Include="AccessTokenClient" />
<Using Static="true" Include="PostFundManagement.Application.Extensions.General" />
</ItemGroup>
</Project>
+118
View File
@@ -0,0 +1,118 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<!-- Core & Utilities -->
<PackageVersion Include="Castle.Core.AsyncInterceptor" Version="2.1.0" />
<PackageVersion Include="Hashids.net" Version="1.7.0" />
<PackageVersion Include="Humanizer" Version="3.0.10" />
<PackageVersion Include="Scrutor" Version="7.0.0" />
<!-- Microsoft Extensions & Configuration -->
<PackageVersion Include="Microsoft.Extensions.Caching.Hybrid" Version="10.11.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="10.0.14" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.11.0" />
<PackageVersion Include="Microsoft.Extensions.Validation" Version="10.0.14" />
<PackageVersion Include="Microsoft.FeatureManagement.AspNetCore" Version="4.8.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.14" />
<!-- Security, Authentication & Identity -->
<PackageVersion Include="AccessTokenClient.Extensions" Version="5.1.0" />
<PackageVersion Include="Duende.AccessTokenManagement" Version="4.3.0" />
<PackageVersion Include="Duende.AccessTokenManagement.OpenIdConnect" Version="4.3.0" />
<PackageVersion Include="IdentityModel" Version="6.2.0" />
<PackageVersion Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageVersion Include="IdentityModel.AspNetCore.OAuth2introspection" Version="6.2.0" />
<PackageVersion Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageVersion Include="KubernetesClient" Version="21.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Certificate" Version="10.0.14" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.14" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="10.0.14" />
<!-- Upgraded to latest non-vulnerable version -->
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="10.0.14" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.14" />
<!-- Resilience -->
<PackageVersion Include="Polly" Version="8.8.1" />
<PackageVersion Include="Polly.Extensions" Version="8.8.1" />
<!-- API, Web & Documentation -->
<PackageVersion Include="Asp.Versioning.Abstractions" Version="10.2.1" />
<PackageVersion Include="Asp.Versioning.Http" Version="10.2.3" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.2.1" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.14" />
<PackageVersion Include="Microsoft.AspNetCore.OutputCaching.StackExchangeRedis" Version="10.0.14" />
<PackageVersion Include="Refit.HttpClientFactory" Version="16.4.0" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.18.3" />
<!-- Database & Entity Framework Core -->
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.14" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.14" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Analyzers" Version="10.0.14" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.14" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.14" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.14" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.14" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
<!-- Quartz Scheduler -->
<PackageVersion Include="Quartz" Version="4.6.2" />
<PackageVersion Include="Quartz.AspNetCore" Version="4.6.2" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="4.6.2" />
<PackageVersion Include="Quartz.Plugins" Version="4.6.2" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="4.6.2" />
<PackageVersion Include="Quartz.Serialization.SystemTextJson" Version="4.6.2" />
<!-- Health Checks -->
<PackageVersion Include="AspNetCore.HealthChecks.NpgSql" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.Core" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.Data" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
<!-- Observability & OpenTelemetry -->
<PackageVersion Include="OpenTelemetry" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.20.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.20.1" />
<!-- Email & Messaging -->
<PackageVersion Include="MailKit" Version="4.18.2" />
<PackageVersion Include="MimeKit" Version="4.18.2" />
<!-- CQRS & Mediator -->
<PackageVersion Include="FluentResults" Version="4.0.0" />
<PackageVersion Include="Mediator.Abstractions" Version="3.0.2" />
<PackageVersion Include="Mediator.SourceGenerator" Version="3.0.2" />
<!-- Cloud Storage -->
<PackageVersion Include="AWSSDK.Extensions.NetCore.Setup" Version="4.0.102.7" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.104.7" />
<!-- Analyzers -->
<PackageVersion Include="Meziantou.Analyzer" Version="3.0.317" />
<!-- Testing -->
<PackageVersion Include="coverlet.collector" Version="10.1.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="4.0.1" />
</ItemGroup>
</Project>
+1263
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -8,7 +8,11 @@ EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src WORKDIR /src
# Copy csproj files for layer caching # Copy props and csproj files for layer caching
COPY ["Directory.Build.props", "./"]
COPY ["Directory.Packages.props", "./"]
COPY ["PostFundManagement.snk", "./"]
COPY ["LICENSE", "./"]
COPY ["PostFundManagement.Api/PostFundManagement.Api.csproj", "PostFundManagement.Api/"] COPY ["PostFundManagement.Api/PostFundManagement.Api.csproj", "PostFundManagement.Api/"]
COPY ["PostFundManagement.Application/PostFundManagement.Application.csproj", "PostFundManagement.Application/"] COPY ["PostFundManagement.Application/PostFundManagement.Application.csproj", "PostFundManagement.Application/"]
COPY ["PostFundManagement.Domain/PostFundManagement.Domain.csproj", "PostFundManagement.Domain/"] COPY ["PostFundManagement.Domain/PostFundManagement.Domain.csproj", "PostFundManagement.Domain/"]
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)]
public sealed class CreateNotificationEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/communications/notifications", async (CreateNotificationRequest request,
HttpContext context, ICommunicationsService service, CancellationToken cancellationToken = default) =>
await service.CreateNotificationAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Created(),
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Creates a new in-app or outbound notification record")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateNotificationEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status201Created)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Communications);
}
}
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)]
public sealed class GetIApplicantDataEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/communications/applicants/{applicantUserId:Guid}/all", async (Guid applicantUserId,
ICommunicationsService service, CancellationToken cancellationtToken = default ) =>
await service.GetApplicantDataAsync(applicantUserId, cancellationtToken) switch
{
{ IsSuccess: true, Value: var applicantData } => Results.Ok(applicantData),
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()),
})
.WithDescription("Gets an applicant's data")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetIApplicantDataEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Invite>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications)
.CacheOutput();
}
}
@@ -4,8 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using static PostFundManagement.Application.Extensions.General;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
@@ -4,8 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using static PostFundManagement.Application.Extensions.General;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
@@ -0,0 +1,49 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)]
public sealed class GetUserNotificationsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/communications/notifications/user/{userId:guid}", async (Guid userId,
ICommunicationsService service, CancellationToken cancellationToken = default) =>
await service.GetUserNotificationsAsync(userId, cancellationToken) switch
{
{ IsSuccess: true, Value: var notifications } => Results.Ok(notifications),
var r => Results.InternalServerError(r.Errors.ToProblems()),
})
.WithDescription("Retrieves all notifications targeted at a specific user")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetUserNotificationsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Notification[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications)
.CacheOutput();
builder.MapGet("api/communications/notifications/inbox", async (HttpContext context,
ICommunicationsService service, CancellationToken cancellationToken = default) =>
await service.GetUserNotificationsAsync(context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true, Value: var notifications } => Results.Ok(notifications),
var r => Results.InternalServerError(r.Errors.ToProblems()),
})
.WithDescription("Retrieves all in-app and system notifications targeted at the authenticated caller")
.RequireAuthorization(Constants.MainScope)
.WithName($"{typeof(GetUserNotificationsEndpoint).ToEndpointName()}Inbox")
.MapToApiVersion(new ApiVersion(1))
.Produces<Notification[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications)
.CacheOutput();
}
}
@@ -5,7 +5,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
@@ -0,0 +1,37 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)]
public sealed class UpdateNotificationEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/communications/notifications", async (UpdateNotificationRequest request,
ICommunicationsService service, CancellationToken cancellationToken = default) =>
await service.UpdateNotificationAsync(request, cancellationToken) switch
{
{ IsSuccess: true } => Results.NoContent(),
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Updates a notification status, delivery timestamp, or error state")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateNotificationEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status204NoContent)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Communications);
}
}
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -12,7 +11,7 @@ public class GetProjectEvidenceEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/evidence/{projectId}", async (string projectId, IEvidenceService service, builder.MapGet("api/evidence/projects/{projectId}", async (string projectId, IEvidenceService service,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch
{ {
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Governance; using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -1,10 +1,9 @@
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Entities;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -0,0 +1,34 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class GetTenantUsersEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/tenants/{tenantIdHash}/users", async (IGovernanceService service, string tenantIdHash,
CancellationToken cancellationToken = default) =>
await service.GetTenantUsersAsync(tenantIdHash, cancellationToken) switch
{
{ IsSuccess: true, Value: var tenantUsers } => Results.Ok(tenantUsers),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Gets users created and managed by one tenant")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetTenantUsersEndpoint).ToEndpointName("Retrieval"))
.MapToApiVersion(new ApiVersion(1))
.Produces<TenantUser[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance);
}
}
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Entities; using PostFundManagement.Domain.Entities;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
namespace PostFundManagement.Api.Endpoints.Grants;
[ApiVersionTarget(1)]
public sealed class UpdateAwardEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/grants/awards", async (UpdateAwardRequest request, HttpContext context,
IGrantsService service, CancellationToken cancellationToken = default) =>
await service.UpdateAwardAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Updates a registered funding award")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateAwardEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants);
}
}
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -1,10 +1,9 @@
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -13,7 +12,7 @@ public sealed class CreateOutcomeEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/projects/beneficiaris/outcomes", async (CreateOutcomeRequest request, HttpContext context, builder.MapPost("api/projects/beneficiaries/outcomes", async (CreateOutcomeRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) => IProjectService service, CancellationToken cancellationToken = default) =>
await service.CreateOutcomeAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateOutcomeAsync(request, context.GetUserId(), cancellationToken) switch
{ {
@@ -32,4 +31,3 @@ public sealed class CreateOutcomeEndpoint : IEndpoint
.WithTags(EndpointTags.Projects); .WithTags(EndpointTags.Projects);
} }
} }
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -3,7 +3,6 @@ using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -4,7 +4,6 @@ using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -1,10 +1,9 @@
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -13,15 +12,7 @@ public sealed class GetOutcomeEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/projects/outcomes/{outcomeId}", async (string outcomeId, IProjectService service, builder.MapGet("api/projects/outcome/{outcomeId}", HandleGetAsync)
CancellationToken cancellationToken = default) =>
await service.GetOutcomeAsync(outcomeId, cancellationToken) switch
{
{ IsSuccess: true, Value: var outcome } => Results.Ok(outcome),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Gets a specific project outcome") .WithDescription("Gets a specific project outcome")
.RequireAuthorization(Constants.MainScope) .RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetOutcomeEndpoint).ToEndpointName()) .WithName(typeof(GetOutcomeEndpoint).ToEndpointName())
@@ -33,5 +24,28 @@ public sealed class GetOutcomeEndpoint : IEndpoint
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects) .WithTags(EndpointTags.Projects)
.CacheOutput(); .CacheOutput();
builder.MapGet("api/projects/outcomes/single/{outcomeId}", HandleGetAsync)
.WithDescription("Gets a specific project outcome")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetOutcomeEndpoint).ToEndpointName("Single"))
.MapToApiVersion(new ApiVersion(1))
.Produces<Outcome>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
} }
private static async Task<IResult> HandleGetAsync(string outcomeId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetOutcomeAsync(outcomeId, cancellationToken) switch
{
{ IsSuccess: true, Value: var outcome } => Results.Ok(outcome),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
};
} }

Some files were not shown because too many files have changed in this diff Show More