186 Commits
Author SHA1 Message Date
khwezi 411e603f51 Merge pull request 'Added tenant management' (#128) from finishline into main
Reviewed-on: #128
2026-10-04 09:14:34 +02:00
hermes 8fed46961a Added tenant management
Applied basic access control
2026-10-04 09:13:55 +02:00
khwezi 138ef84973 Merge pull request 'Hardened API and catered for Partner role query filter bypass' (#127) from finishline into main
Reviewed-on: #127
2026-10-04 08:19:48 +02:00
hermes 8100518549 Hardened API and catered for Partner role query filter bypass 2026-10-04 08:18:43 +02:00
khwezi 312721bf51 Merge pull request 'Redesigned dash' (#126) from finishline into main
Reviewed-on: #126
2026-09-29 12:24:27 +02:00
hermes 8d069c2da0 Redesigned dash 2026-09-29 12:24:04 +02:00
khwezi dd59294911 Merge pull request 'Refactored dashboard' (#125) from finishline into main
Reviewed-on: #125
2026-09-29 11:53:13 +02:00
hermes 8643b2625b Refactored dashboard 2026-09-29 11:41:14 +02:00
khwezi 28a3a17a15 Merge pull request 'Fixed missing dependency' (#124) from finishline into main
Reviewed-on: #124
2026-09-28 16:47:20 +02:00
hermes c805a31824 Fixed missing dependency 2026-09-28 16:46:50 +02:00
khwezi 4c2b0cd7d5 Merge pull request 'Added dockerfile to partner ui' (#123) from finishline into main
Reviewed-on: #123
2026-09-28 16:33:44 +02:00
hermes 67fa99c64a Added dockerfile to partner ui 2026-09-28 16:33:18 +02:00
khwezi 275fd109e6 Merge pull request 'Finishline' (#122) from finishline into main
Reviewed-on: #122
2026-09-28 16:27:54 +02:00
hermes 9f6c1e505b Added partner client env var 2026-09-28 16:26:05 +02:00
hermes c4f57b92ef Added pfm-partner to compose file 2026-09-28 16:15:43 +02:00
hermes 7d510d9fbc Added partner app 2026-09-28 16:13:42 +02:00
khwezi 248cc2fa2d Merge pull request 'Fixed timeline height' (#121) from finishline into main
Reviewed-on: #121
2026-09-28 11:56:15 +02:00
hermes 242a4c6fbb Fixed timeline height
Moved export and fixed data output
Added project budget amount widget to project listing
2026-09-28 11:55:52 +02:00
khwezi 761d14beb9 Merge pull request 'Light projects page refactor' (#120) from finishline into main
Reviewed-on: #120
2026-09-28 11:45:35 +02:00
hermes 891801c73d Light projects page refactor 2026-09-28 11:45:05 +02:00
khwezi 931e1a8698 Merge pull request 'Refactored projects and milestones page' (#119) from finishline into main
Reviewed-on: #119
2026-09-28 11:34:32 +02:00
hermes 8b68f7b99c Refactored projects and milestones page 2026-09-28 11:34:11 +02:00
khwezi aff58caeb0 Merge pull request 'Removed duplicate filter in settings page' (#118) from finishline into main
Reviewed-on: #118
2026-09-28 11:13:41 +02:00
hermes 93b3416d2c Removed duplicate filter in settings page
Improved Rule value handling
2026-09-28 11:10:17 +02:00
khwezi 1c5080bf04 Merge pull request 'Added Role column to user directory' (#117) from finishline into main
Reviewed-on: #117
2026-09-28 10:59:34 +02:00
hermes ef13117042 Added Role column to user directory 2026-09-28 10:58:02 +02:00
khwezi 1fb157aa0a Merge pull request 'Finishline' (#116) from finishline into main
Reviewed-on: #116
2026-09-28 10:40:39 +02:00
hermes 8eae097e8b Corrected user role mapping 2026-09-28 10:39:32 +02:00
hermes ea71e55e2d Refactored UI
Added Role filed to user table and endpoint implementation
2026-09-28 10:25:00 +02:00
khwezi 0adfae1616 Merge pull request 'Refactored get compliance item endpoint' (#115) from finishline into main
Reviewed-on: #115
2026-09-27 15:06:14 +02:00
hermes 7037ea72ca Refactored get compliance item endpoint 2026-09-27 15:05:42 +02:00
khwezi 4856b6e6f4 Merge pull request 'fixed create site visit error' (#114) from finishline into main
Reviewed-on: #114
2026-09-27 14:41:30 +02:00
hermes 104f01e515 fixed create site visit error 2026-09-27 14:41:08 +02:00
khwezi 0ba47269aa Merge pull request 'Finishline' (#113) from finishline into main
Reviewed-on: #113
2026-09-27 14:36:16 +02:00
hermes 0d626fd84a Fixed error handling 2026-09-27 14:35:52 +02:00
hermes 9ba48de55b ui tweaks 2026-09-27 14:27:01 +02:00
khwezi 5e25fd7a11 Merge pull request 'fixed new change request logic' (#112) from finishline into main
Reviewed-on: #112
2026-09-27 14:16:31 +02:00
hermes 6107de3e53 fixed new change request logic 2026-09-27 14:16:08 +02:00
khwezi f8fff04065 Merge pull request 'Fixed mismatched endpoint calling' (#111) from finishline into main
Reviewed-on: #111
2026-09-27 14:08:14 +02:00
hermes df167caf55 Fixed mismatched endpoint calling 2026-09-27 14:07:44 +02:00
khwezi aca5882ad3 Merge pull request 'Fixed change management rendering error' (#110) from finishline into main
Reviewed-on: #110
2026-09-27 13:43:34 +02:00
hermes 8afceda1cd Fixed change management rendering error 2026-09-27 13:43:14 +02:00
khwezi 6b027193d1 Merge pull request 'Updated api usage, corrected table designs' (#109) from finishline into main
Reviewed-on: #109
2026-09-27 13:32:01 +02:00
hermes c1c5641d1e Updated api usage, corrected table designs 2026-09-27 13:31:27 +02:00
khwezi 81702a4bf8 Merge pull request 'Refactored docker compose' (#108) from finishline into main
Reviewed-on: #108
2026-09-27 13:08:13 +02:00
hermes 2efd2859c7 Refactored docker compose 2026-09-27 13:07:55 +02:00
khwezi 5eeb1cbfc7 Merge pull request 'Refactored endpoints' (#107) from finishline into main
Reviewed-on: #107
2026-09-27 13:07:18 +02:00
hermes dd6996bdb1 Refactored endpoints 2026-09-27 13:06:50 +02:00
khwezi fb00986f59 Merge pull request 'Issue creation, reolution is optional' (#106) from finishline into main
Reviewed-on: #106
2026-09-27 12:56:05 +02:00
hermes 1df0b97daf Issue creation, reolution is optional 2026-09-27 12:55:29 +02:00
khwezi 0338737385 Merge pull request 'Fixed UI inconsistencies' (#105) from finishline into main
Reviewed-on: #105
2026-09-27 12:04:56 +02:00
hermes e6fdcf3d0d Fixed UI inconsistencies 2026-09-27 12:04:22 +02:00
khwezi 82c0936b4c Merge pull request 'Added, beneficiaries, site visits and change management' (#104) from finishline into main
Reviewed-on: #104
2026-09-27 11:42:03 +02:00
hermes 36e5d3d7c8 Added, beneficiaries, site visits and change management 2026-09-27 11:40:28 +02:00
khwezi 5d2a886f6f Merge pull request 'Removed settings page mock data and replaced with api calls' (#103) from finishline into main
Reviewed-on: #103
2026-09-27 10:58:50 +02:00
hermes fb27cc8c92 Removed settings page mock data and replaced with api calls 2026-09-27 10:58:08 +02:00
khwezi dc4047ca6d Merge pull request 'Added audit log endpoint' (#102) from finishline into main
Reviewed-on: #102
2026-09-27 10:13:33 +02:00
hermes 6ab01830f3 Added audit log endpoint 2026-09-27 10:11:56 +02:00
khwezi 46fe14d9ef Merge pull request 'Revised all UIs' (#101) from finishline into main
Reviewed-on: #101
2026-09-26 16:37:16 +02:00
hermes 6e6419ab51 Revised all UIs 2026-09-26 16:36:45 +02:00
khwezi f17368d939 Merge pull request 'Added budget update' (#100) from finishline into main
Reviewed-on: #100
2026-09-26 15:15:08 +02:00
hermes ed6e57ffa9 Added budget update
Added portfolio and programme value calculator endpoints
2026-09-26 15:14:29 +02:00
khwezi 7aea3fa8de Merge pull request 'Added postcss' (#99) from finishline into main
Reviewed-on: #99
2026-09-26 13:55:49 +02:00
hermes 6be74e9a24 Added postcss 2026-09-26 13:54:39 +02:00
khwezi d43eb17aa3 Merge pull request 'Implemented data export' (#98) from finishline into main
Reviewed-on: #98
2026-09-26 13:46:36 +02:00
hermes 2949d6218b Implemented data export 2026-09-26 13:45:42 +02:00
khwezi 01547d8c3f Merge pull request 'Fixed portfolio retrieval' (#97) from finishline into main
Reviewed-on: #97
2026-09-26 12:04:37 +02:00
hermes 15a8d8f449 Fixed portfolio retrieval 2026-09-26 12:03:47 +02:00
khwezi 18dd9131cb Merge pull request 'Added missing instrument endpoints' (#96) from finishline into main
Reviewed-on: #96
2026-09-26 11:21:27 +02:00
hermes db07ad1a82 Added missing instrument endpoints 2026-09-26 11:20:57 +02:00
khwezi f82a5cef76 Merge pull request 'Fixed update organisation' (#95) from finishline into main
Reviewed-on: #95
2026-09-26 10:39:45 +02:00
hermes 11967e3252 Fixed update organisation 2026-09-26 10:39:22 +02:00
khwezi 614730caab Merge pull request 'Fixed API calls on governance page' (#94) from finishline into main
Reviewed-on: #94
2026-09-26 10:29:52 +02:00
hermes f3a8f78144 Fixed API calls on governance page
Enforced multitenancy
2026-09-26 10:28:43 +02:00
khwezi 9823cd7ea6 Merge pull request 'Surfaced programme rules' (#93) from finishline into main
Reviewed-on: #93
2026-09-26 07:56:38 +02:00
hermes b2688867f3 Surfaced programme rules 2026-09-26 07:55:49 +02:00
khwezi a7a68fc290 Merge pull request 'Finishline' (#92) from finishline into main
Reviewed-on: #92
2026-09-25 19:06:37 +02:00
hermes 0f903a58b5 Recolved endpoint name ambiguity 2026-09-25 19:06:03 +02:00
hermes 86e2bad4fe Implemented remaining business functionality 2026-09-25 19:00:50 +02:00
hermes 1efbf9d99f Added TenantId to mapper methods on all affected models 2026-09-25 08:20:36 +02:00
hermes 24697776d0 Refactored roganisation routines to use TenantOrganisation 2026-09-25 04:01:31 +02:00
hermes 4b806a3202 UserService refactored to integrate into TenantUser 2026-09-25 03:55:20 +02:00
hermes 26df423180 Refactored cache services 2026-09-25 03:04:37 +02:00
khwezi 4dbc74e488 Merge pull request 'Finishline' (#91) from finishline into main
Reviewed-on: #91
2026-09-24 16:08:03 +02:00
hermes f39eb9d8fe Removed tools feature 2026-09-24 16:00:06 +02:00
hermes 593219db8c Stable run,
Added remaining service implementations (stubs)
2026-09-24 15:57:33 +02:00
khwezi d658497545 Merge pull request 'Finishline' (#90) from finishline into main
Reviewed-on: #90
2026-09-24 12:01:36 +02:00
hermes 020bdee36f Stable run using Multi tenancy across entire estate
Audit logging and error logging active
2026-09-24 12:00:47 +02:00
hermes 8e0c099867 Stable multitenancy 2026-09-24 10:25:07 +02:00
hermes ef134a5b3c Implemented audit logging and automated logging 2026-09-23 05:33:16 +02:00
khwezi b947959724 Merge pull request 'Added linkage to primary tenant entities' (#89) from finishline into main
Reviewed-on: #89
2026-09-22 07:09:20 +02:00
hermes 717b891554 Added linkage to primary tenant entities 2026-09-22 07:08:57 +02:00
khwezi 58126e7573 Merge pull request 'Make retrieval of contract file auditable' (#88) from finishline into main
Reviewed-on: #88
2026-09-22 06:29:58 +02:00
hermes 91d325ce4e Make retrieval of contract file auditable 2026-09-22 06:28:54 +02:00
khwezi 2fede443cc Merge pull request 'Added automatic audit feature' (#87) from finishline into main
Reviewed-on: #87
2026-09-22 06:11:18 +02:00
hermes 3cc8514136 Added automatic audit feature 2026-09-22 06:09:51 +02:00
hermes 11311421ad Removed ValueTask generally where not needed
Removed relationships from audit log
2026-09-22 05:44:25 +02:00
hermes f4024d563a Implemented centralised audit logging 2026-09-21 16:32:56 +02:00
hermes 78b03b6996 Colocated entity type configurations with entities
Added tenant entities and models
Modified db context
2026-09-21 09:34:49 +02:00
khwezi 80f220fcae Merge pull request 'Test 2 reoslutions' (#86) from garages3 into main
Reviewed-on: #86
2026-09-20 15:26:35 +02:00
khwezi 06e2c3ee6a Merge pull request 'Remediated test #1 results' (#85) from garages3 into main
Reviewed-on: #85
2026-09-20 13:52:03 +02:00
khwezi 3c0fd8844c Merge pull request 'Refactored UI endpoint usage' (#84) from garages3 into main
Reviewed-on: #84
2026-09-20 08:27:14 +02:00
khwezi b7c135f0fc Merge pull request 'Garages3' (#83) from garages3 into main
Reviewed-on: #83
2026-09-18 16:12:06 +02:00
khwezi db053e3e5f Merge pull request 'Garages3' (#82) from garages3 into main
Reviewed-on: #82
2026-09-17 07:39:34 +02:00
khwezi d7177e1a72 Merge pull request 'Removed package.json' (#81) from garages3 into main
Reviewed-on: #81
2026-09-17 07:23:54 +02:00
khwezi c67041faea Merge pull request 'Refactored UI and compose file' (#80) from garages3 into main
Reviewed-on: #80
2026-09-17 07:08:26 +02:00
khwezi 0e8a5e1169 Merge pull request 'Removed docker debug code' (#79) from garages3 into main
Reviewed-on: #79
2026-09-15 12:13:47 +02:00
khwezi 41fdb067fe Merge pull request 'Refactored ApiVersioning service registration' (#78) from garages3 into main
Reviewed-on: #78
2026-09-15 12:09:47 +02:00
khwezi b4b687dfcc Merge pull request 'Added OPTIONS header handling' (#77) from garages3 into main
Reviewed-on: #77
2026-09-15 08:52:23 +02:00
khwezi f200ccbf60 Merge pull request 'Added RequireCors() to RouteGroup' (#76) from garages3 into main
Reviewed-on: #76
2026-09-15 08:45:25 +02:00
khwezi 27f57150e1 Merge pull request 'Moved .RequireAuthentication() to endpoints, removed it from EndpointGroup' (#75) from garages3 into main
Reviewed-on: #75
2026-09-15 08:37:43 +02:00
khwezi 79c63107e9 Merge pull request 'Applied configurable CORS policy to API' (#74) from garages3 into main
Reviewed-on: #74
2026-09-15 07:30:29 +02:00
khwezi d945731252 Merge pull request 'Fixed UI' (#73) from garages3 into main
Reviewed-on: #73
2026-09-14 11:44:00 +02:00
khwezi c979a4632e Merge pull request 'Debug refactor' (#72) from garages3 into main
Reviewed-on: #72
2026-09-14 10:46:59 +02:00
khwezi 9eed3cff6b Merge pull request 'Debugs refactor' (#71) from garages3 into main
Reviewed-on: #71
2026-09-14 10:39:04 +02:00
khwezi 6db1a2f367 Merge pull request 'Garages3' (#70) from garages3 into main
Reviewed-on: #70
2026-09-14 10:34:46 +02:00
khwezi 06d9952cd7 Merge pull request 'Rebuilt pfm-api dockerfile' (#69) from garages3 into main
Reviewed-on: #69
2026-09-14 10:29:13 +02:00
khwezi 92d481f75d Merge pull request 'Light refactor' (#68) from garages3 into main
Reviewed-on: #68
2026-09-14 10:23:19 +02:00
khwezi 1c36602995 Merge pull request 'Simplified fpm-api dockerfile' (#67) from garages3 into main
Reviewed-on: #67
2026-09-14 10:21:10 +02:00
khwezi 5ae0c500ac Merge pull request 'Added debug code' (#66) from garages3 into main
Reviewed-on: #66
2026-09-14 10:13:58 +02:00
khwezi db04f800e9 Merge pull request 'Changed pfm-api dockerfile paths' (#65) from garages3 into main
Reviewed-on: #65
2026-09-14 10:10:49 +02:00
khwezi e7a19611f0 Merge pull request 'Simplified dockerfile (pfm-api)' (#64) from garages3 into main
Reviewed-on: #64
2026-09-14 10:04:10 +02:00
khwezi 5ad24da7cf Merge pull request 'Refactored compose file to assign same context to both services' (#63) from garages3 into main
Reviewed-on: #63
2026-09-14 10:02:01 +02:00
khwezi 156dae40b7 Merge pull request 'Fixed missing file copy' (#62) from garages3 into main
Reviewed-on: #62
2026-09-14 09:50:32 +02:00
khwezi 1b67d9cf8b Merge pull request 'Debug refactor on dockerfile' (#61) from garages3 into main
Reviewed-on: #61
2026-09-14 09:47:00 +02:00
khwezi 1bd05e64e5 Merge pull request 'Added better dotnet build handling' (#60) from garages3 into main
Reviewed-on: #60
2026-09-14 09:42:10 +02:00
khwezi 303dba3c6f Merge pull request 'Added more verbosity to dotnet build' (#59) from garages3 into main
Reviewed-on: #59
2026-09-14 09:35:30 +02:00
khwezi 1dd4f54a1d Merge pull request 'Added .dockerignore' (#58) from garages3 into main
Reviewed-on: #58
2026-09-14 09:32:21 +02:00
khwezi fae1e080c4 Merge pull request 'Added stream redirection to dotnet commands' (#57) from garages3 into main
Reviewed-on: #57
2026-09-14 09:14:17 +02:00
khwezi 4de23d27b3 Merge pull request 'Commented out pfm-console to see if the api image will build' (#56) from garages3 into main
Reviewed-on: #56
2026-09-14 09:06:50 +02:00
khwezi d4bce12dbb Merge pull request 'Removed stream redirection' (#55) from garages3 into main
Reviewed-on: #55
2026-09-13 18:15:33 +02:00
khwezi 299b876e1c Merge pull request 'Fixed Api docker file paths' (#54) from garages3 into main
Reviewed-on: #54
2026-09-13 18:13:13 +02:00
khwezi dde1dbe056 Merge pull request 'Added debug code to dotnet build and publish lines' (#53) from garages3 into main
Reviewed-on: #53
2026-09-13 18:09:51 +02:00
khwezi b81e6feb45 Merge pull request 'Moved api dockerfile to project folder' (#52) from garages3 into main
Reviewed-on: #52
2026-09-13 18:07:52 +02:00
khwezi 4138a1941f Merge pull request 'Corrected compose service contexts' (#51) from garages3 into main
Reviewed-on: #51
2026-09-13 18:01:52 +02:00
khwezi 17af31d810 Merge pull request 'Fixed dockerfile paths' (#50) from garages3 into main
Reviewed-on: #50
2026-09-13 17:56:19 +02:00
khwezi b85cf6a1cb Merge pull request 'Light refactor' (#49) from garages3 into main
Reviewed-on: #49
2026-09-13 17:51:45 +02:00
khwezi 81ddc11746 Merge pull request 'Refactored API dockerfile' (#48) from garages3 into main
Reviewed-on: #48
2026-09-13 17:50:29 +02:00
khwezi 5b94672ff5 Merge pull request 'Stable API run' (#47) from garages3 into main
Reviewed-on: #47
2026-09-13 17:41:42 +02:00
khwezi c535392ef1 Merge pull request 'Added debug term' (#46) from garages3 into main
Reviewed-on: #46
2026-09-12 12:22:43 +02:00
khwezi 0607de1802 Merge pull request 'Second fix' (#45) from garages3 into main
Reviewed-on: #45
2026-09-12 12:19:34 +02:00
khwezi 4c1e52e452 Merge pull request 'Light refactor' (#44) from garages3 into main
Reviewed-on: #44
2026-09-12 12:14:13 +02:00
khwezi 2414d74d1c Merge pull request 'Light refactor' (#43) from garages3 into main
Reviewed-on: #43
2026-09-12 12:10:17 +02:00
khwezi 93d9661a7f Merge pull request 'Rewrite pfm-api docker file' (#42) from garages3 into main
Reviewed-on: #42
2026-09-12 12:04:47 +02:00
khwezi dba401fe94 Merge pull request 'Fixed publish alias' (#41) from garages3 into main
Reviewed-on: #41
2026-09-12 11:58:37 +02:00
khwezi 42d73fe6cb Merge pull request 'Added error output to dotnet build line' (#40) from garages3 into main
Reviewed-on: #40
2026-09-12 11:54:57 +02:00
khwezi 1475887a7f Merge pull request 'Added working directory switch' (#39) from garages3 into main
Reviewed-on: #39
2026-09-12 11:49:52 +02:00
khwezi 730664d9c4 Merge pull request 'PFM Api docker refactor' (#38) from garages3 into main
Reviewed-on: #38
2026-09-12 11:45:32 +02:00
khwezi f38e569a34 Merge pull request 'Implemented missing file handling endpoints' (#37) from garages3 into main
Reviewed-on: #37
2026-09-12 11:26:45 +02:00
khwezi 7e79171f97 Merge pull request 'Added image name to compose file for console' (#36) from endpoints into main
Reviewed-on: #36
2026-09-11 16:24:11 +02:00
khwezi d7b868f2c1 Merge pull request 'Added more cors origins' (#35) from endpoints into main
Reviewed-on: #35
2026-09-11 16:17:07 +02:00
khwezi d43a09b371 Merge pull request 'Refactored console dockerfile' (#34) from endpoints into main
Reviewed-on: #34
2026-09-11 16:05:21 +02:00
khwezi 0dc0de1502 Merge pull request 'Fixed dockerfile paths' (#33) from endpoints into main
Reviewed-on: #33
2026-09-11 15:47:30 +02:00
khwezi de47dd4a4f Merge pull request 'Supplied defaults to compose env variables' (#32) from endpoints into main
Reviewed-on: #32
2026-09-11 15:33:47 +02:00
khwezi 2810cc669b Merge pull request 'Fixed compose syntax error' (#31) from endpoints into main
Reviewed-on: #31
2026-09-11 15:30:40 +02:00
khwezi 734af8c86f Merge pull request 'Refactored compose file to include the ui in deployment' (#30) from endpoints into main
Reviewed-on: #30
2026-09-11 15:29:21 +02:00
khwezi a754c42d14 Merge pull request 'Updated Console compose file to use variables' (#29) from endpoints into main
Reviewed-on: #29
2026-09-11 15:19:31 +02:00
khwezi bd7ca9372f Merge pull request 'Added specific origin' (#28) from endpoints into main
Reviewed-on: #28
2026-09-11 14:59:34 +02:00
khwezi 0d01d91ab4 Merge pull request 'Added cors after routes' (#27) from endpoints into main
Reviewed-on: #27
2026-09-11 14:54:47 +02:00
khwezi 2c817a8818 Merge pull request 'Endpoints' (#26) from endpoints into main
Reviewed-on: #26
2026-09-11 14:35:02 +02:00
khwezi 4c3a96917b Merge pull request 'Improved results handling in service layer' (#25) from endpoints into main
Reviewed-on: #25
2026-09-11 10:33:47 +02:00
khwezi e5c3a75795 Merge pull request 'Stable run with validations' (#24) from endpoints into main
Reviewed-on: #24
2026-09-06 10:49:57 +02:00
khwezi 1bc7e65960 Merge pull request 'Added tooling' (#23) from endpoints into main
Reviewed-on: #23
2026-09-05 18:27:35 +02:00
khwezi 094126d84b Merge pull request 'Fixed endpointname uniqueness' (#22) from endpoints into main
Reviewed-on: #22
2026-09-05 13:07:41 +02:00
khwezi 2506241a25 Merge pull request 'Fixed endpoint uniqueness' (#21) from endpoints into main
Reviewed-on: #21
2026-09-05 12:58:17 +02:00
khwezi cfe3530639 Merge pull request 'Endpoints' (#20) from endpoints into main
Reviewed-on: #20
2026-09-05 12:50:04 +02:00
khwezi 985557bee4 Merge pull request 'Implemented caching on all get endpoints and service layer' (#19) from endpoints into main
Reviewed-on: #19
2026-09-05 08:30:16 +02:00
khwezi f0b8b3051a Merge pull request 'Improved getusers cache key' (#18) from endpoints into main
Reviewed-on: #18
2026-09-04 06:08:39 +02:00
khwezi 6c88b9e533 Merge pull request 'Endpoints' (#17) from endpoints into main
Reviewed-on: #17
2026-09-04 05:54:30 +02:00
khwezi 7d67408d2c Merge pull request 'Additional redis configs' (#16) from endpoints into main
Reviewed-on: #16
2026-09-04 05:39:44 +02:00
khwezi d1d58a6183 Merge pull request 'Added service level caching to IdentityService, Communication and Evidence' (#15) from endpoints into main
Reviewed-on: #15
2026-09-03 16:38:08 +02:00
khwezi 60ad154841 Merge pull request 'Implemented distributed caching' (#14) from endpoints into main
Reviewed-on: #14
2026-09-03 11:09:52 +02:00
khwezi 2c13170859 Merge pull request 'Endpoints' (#13) from endpoints into main
Reviewed-on: #13
2026-09-02 16:14:34 +02:00
khwezi d87c8aa3c9 Merge pull request 'Updated configurations' (#12) from endpoints into main
Reviewed-on: #12
2026-09-02 09:45:38 +02:00
khwezi 6af7815ad1 Merge pull request 'Removed node tooling install' (#11) from endpoints into main
Reviewed-on: #11
2026-09-02 09:30:06 +02:00
khwezi a169f6c3f6 Merge pull request 'Endpoints' (#10) from endpoints into main
Reviewed-on: #10
2026-09-02 09:28:17 +02:00
khwezi 65ea753c19 Merge pull request 'Endpoints' (#9) from endpoints into main
Reviewed-on: #9
2026-09-01 12:29:47 +02:00
khwezi e6d2663265 Merge pull request 'Endpoints' (#8) from endpoints into main
Reviewed-on: #8
2026-08-30 17:47:24 +02:00
khwezi 91b5814b60 Merge pull request 'Endpoints' (#7) from endpoints into main
Reviewed-on: #7
2026-08-30 16:27:37 +02:00
khwezi 0c4c848568 Merge pull request 'Endpoints' (#6) from endpoints into main
Reviewed-on: #6
2026-08-30 16:11:53 +02:00
khwezi fb05150ebf Merge pull request 'Endpoints' (#5) from endpoints into main
Reviewed-on: #5
2026-08-30 15:47:32 +02:00
khwezi 2f9c20020b Merge pull request 'Reogranised API and Security assets' (#4) from endpoints into main
Reviewed-on: #4
2026-08-30 15:09:32 +02:00
khwezi a771957db3 Merge pull request 'Endpoints' (#3) from endpoints into main
Reviewed-on: #3
2026-08-30 14:54:58 +02:00
khwezi 46267a2a34 Merge pull request 'Endpoints' (#2) from endpoints into main
Reviewed-on: #2
2026-08-30 14:26:08 +02:00
khwezi a7f360cc2a Merge pull request 'Security' (#1) from security into main
Reviewed-on: #1
2026-08-21 11:47:36 +02:00
hermes 67c08b40ca Refactored gitignore 2026-08-21 11:45:33 +02:00
340 changed files with 100072 additions and 11213 deletions
+1
View File
@@ -12,6 +12,7 @@
*.env *.env
*.env.json *.env.json
certificates/ certificates/
package-lock.json
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
@@ -14,12 +14,12 @@ public sealed class GetApplicantInviteEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/communications/invites/applicant/{applicantUserId:Guid}", async (Guid applicantUserId, ICommunicationsService service, builder.MapGet("api/communications/invites/applicants/{applicantUserId:Guid}", async (Guid applicantUserId, ICommunicationsService service,
HttpContext context, CancellationToken cancellationToken = default) => HttpContext context, CancellationToken cancellationToken = default) =>
await service.GetApplicantInviteAsync(applicantUserId, cancellationToken) switch await service.GetApplicantInviteAsync(applicantUserId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var invite } => Results.Ok(invite), { IsSuccess: true, Value: var invite } => Results.Ok(invite),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()), var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
.WithDescription("Gets an applicant's invite") .WithDescription("Gets an applicant's invite")
@@ -27,8 +27,8 @@ public sealed class GetApplicantInviteEndpoint : IEndpoint
.WithName(typeof(GetApplicantInviteEndpoint).ToEndpointName()) .WithName(typeof(GetApplicantInviteEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Invite>(StatusCodes.Status200OK) .Produces<Invite>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications) .WithTags(EndpointTags.Communications)
.CacheOutput(); .CacheOutput();
@@ -19,7 +19,7 @@ public sealed class GetApplicantInvitesEndpoint : IEndpoint
await service.GetApplicantInvitesAsync(applicantUserId, cancellationToken) switch await service.GetApplicantInvitesAsync(applicantUserId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var invite } => Results.Ok(invite), { IsSuccess: true, Value: var invite } => Results.Ok(invite),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()), var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
.WithDescription("Gets an applicant's invite list") .WithDescription("Gets an applicant's invite list")
@@ -27,8 +27,8 @@ public sealed class GetApplicantInvitesEndpoint : IEndpoint
.WithName(typeof(GetApplicantInvitesEndpoint).ToEndpointName()) .WithName(typeof(GetApplicantInvitesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Invite[]>(StatusCodes.Status200OK) .Produces<Invite[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications) .WithTags(EndpointTags.Communications)
.CacheOutput(); .CacheOutput();
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using static PostFundManagement.Application.Extensions.General;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)]
public sealed class GetNotificationsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/communications/norifications/{organisationId}", async (string organisationId,
ICommunicationsService service, CancellationToken cancellationToken = default) =>
await service.GetNotificationsAsync(organisationId, cancellationToken) switch
{
{ IsSuccess: true, Value: var notifications } => Results.Ok(notifications),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()),
})
.WithDescription("Retrieves the notification system report")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetNotificationsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Notification[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Notification[]>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications)
.CacheOutput();
}
}
@@ -15,11 +15,12 @@ public sealed class SendInviteEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/communications/invite", async (InviteRequest request, builder.MapPost("api/communications/invites", async (InviteRequest request,
HttpContext context, ICommunicationsService service, CancellationToken cancellationToken = default) => HttpContext context, ICommunicationsService service, CancellationToken cancellationToken = default) =>
await service.SendInviteAsync(request, context.GetUserId(), cancellationToken) switch await service.SendInviteAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true, Value: var invite } => Results.Ok(invite), { IsSuccess: true, Value: var invite } => Results.Ok(invite),
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -29,6 +30,7 @@ public sealed class SendInviteEndpoint : IEndpoint
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Invite>(StatusCodes.Status200OK) .Produces<Invite>(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Communications); .WithTags(EndpointTags.Communications);
@@ -19,7 +19,7 @@ public class CreateIndicatorEndpoint : IEndpoint
await service.CreateIndicatorAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateIndicatorAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -28,8 +28,8 @@ public class CreateIndicatorEndpoint : IEndpoint
.WithName(typeof(CreateIndicatorEndpoint).ToEndpointName()) .WithName(typeof(CreateIndicatorEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence); .WithTags(EndpointTags.Evidence);
@@ -19,7 +19,7 @@ public class CreateMilestoneEndpoint : IEndpoint
await service.CreateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -30,6 +30,7 @@ public class CreateMilestoneEndpoint : IEndpoint
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence); .WithTags(EndpointTags.Evidence);
} }
@@ -19,7 +19,7 @@ public sealed class GetEvidenceEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var evidence } => Results.Ok(evidence), { IsSuccess: true, Value: var evidence } => Results.Ok(evidence),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get a specific evidence record and assiciated file URLs") .WithDescription("Get a specific evidence record and assiciated file URLs")
@@ -27,9 +27,9 @@ public sealed class GetEvidenceEndpoint : IEndpoint
.WithName(typeof(GetEvidenceEndpoint).ToEndpointName()) .WithName(typeof(GetEvidenceEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Models.Evidence>(StatusCodes.Status200OK) .Produces<Domain.Models.Evidence>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence) .WithTags(EndpointTags.Evidence)
.CacheOutput(); .CacheOutput();
@@ -19,17 +19,17 @@ public sealed class GetEvidenceUrlEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var evidenceUrl } => Results.Ok(evidenceUrl), { IsSuccess: true, Value: var evidenceUrl } => Results.Ok(evidenceUrl),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Gets a secure download URL of the evidence file, URL expires (default: 15min)") .WithDescription("Gets a secure download URL of the evidence file, URL expires (default: 15min)")
.RequireAuthorization(Constants.MainScope) .RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetEvidenceUrlEndpoint).ToEndpointName()) .WithName(typeof(GetEvidenceUrlEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<string>(StatusCodes.Status200OK)
.Produces<string>(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<string>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants) .WithTags(EndpointTags.Grants)
.CacheOutput(); .CacheOutput();
@@ -20,7 +20,7 @@ public sealed class GetIndicatorEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var indicator } => Results.Ok(indicator), { IsSuccess: true, Value: var indicator } => Results.Ok(indicator),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get a specific indicator.") .WithDescription("Get a specific indicator.")
@@ -28,9 +28,9 @@ public sealed class GetIndicatorEndpoint : IEndpoint
.WithName(typeof(GetIndicatorEndpoint).ToEndpointName()) .WithName(typeof(GetIndicatorEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Indicator>(StatusCodes.Status200OK) .Produces<Indicator>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence) .WithTags(EndpointTags.Evidence)
.CacheOutput(); .CacheOutput();
@@ -19,7 +19,7 @@ public sealed class UpdateEvidenceEndpoint : IEndpoint
await service.UpdateEvidenceAsync(request, context.GetUserId(), cancellationToken) switch await service.UpdateEvidenceAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -29,9 +29,9 @@ public sealed class UpdateEvidenceEndpoint : IEndpoint
.WithName(typeof(UpdateEvidenceEndpoint).ToEndpointName()) .WithName(typeof(UpdateEvidenceEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence); .WithTags(EndpointTags.Evidence);
@@ -19,7 +19,7 @@ public class CreateDisbursementEndpoint : IEndpoint
await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()), var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
@@ -28,9 +28,9 @@ public class CreateDisbursementEndpoint : IEndpoint
.WithName(typeof(CreateDisbursementEndpoint).ToEndpointName()) .WithName(typeof(CreateDisbursementEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution); .WithTags(EndpointTags.Execution);
} }
@@ -19,7 +19,7 @@ public class CreateRiskEndpoint : IEndpoint
await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -28,9 +28,9 @@ public class CreateRiskEndpoint : IEndpoint
.WithName(typeof(CreateRiskEndpoint).ToEndpointName()) .WithName(typeof(CreateRiskEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution); .WithTags(EndpointTags.Execution);
} }
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public sealed class CreateRuleEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/execution/rules", async (CreateRuleRequest request, HttpContext context,
IExecutionService service, CancellationToken cancellationToken = default) =>
await service.CreateRuleAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
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("Add rule against a programme")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateRuleEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution);
}
}
@@ -20,7 +20,7 @@ public sealed class GetDisbursementEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var disbursement } => Results.Ok(disbursement), { IsSuccess: true, Value: var disbursement } => Results.Ok(disbursement),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get a specific disbursement") .WithDescription("Get a specific disbursement")
@@ -28,9 +28,9 @@ public sealed class GetDisbursementEndpoint : IEndpoint
.WithName(typeof(GetDisbursementEndpoint).ToEndpointName()) .WithName(typeof(GetDisbursementEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Disbursement>(StatusCodes.Status200OK) .Produces<Disbursement>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution) .WithTags(EndpointTags.Execution)
.CacheOutput(); .CacheOutput();
@@ -20,7 +20,7 @@ public sealed class GetRiskEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var risk } => Results.Ok(risk), { IsSuccess: true, Value: var risk } => Results.Ok(risk),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get a specific risk") .WithDescription("Get a specific risk")
@@ -28,8 +28,8 @@ public sealed class GetRiskEndpoint : IEndpoint
.WithName(typeof(GetRiskEndpoint).ToEndpointName()) .WithName(typeof(GetRiskEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Risk>(StatusCodes.Status200OK) .Produces<Risk>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution) .WithTags(EndpointTags.Execution)
.CacheOutput(); .CacheOutput();
@@ -3,6 +3,7 @@ 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 PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security; using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
@@ -24,7 +25,7 @@ public class GetRisksEndpoint : IEndpoint
.RequireAuthorization(Constants.MainScope) .RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetRisksEndpoint).ToEndpointName()) .WithName(typeof(GetRisksEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces<Risk[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution) .WithTags(EndpointTags.Execution)
@@ -0,0 +1,37 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public sealed class GetRuleEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/execution/rules/{ruleId}", async (string ruleId, IExecutionService service,
CancellationToken cancellationToken = default) =>
await service.GetRuleAsync(ruleId, cancellationToken) switch
{
{ IsSuccess: true, Value: var rules } => Results.Ok(rules),
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("Get a specific programme rule")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetRuleEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Rule>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution)
.CacheOutput();
}
}
@@ -0,0 +1,34 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public sealed class GetRulesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/execution/programmes/rules", async (Pagination pagination, IExecutionService service,
CancellationToken cancellationToken = default) =>
await service.GetRulesAsync(pagination, cancellationToken) switch
{
{ IsSuccess: true, Value: var rules } => Results.Ok(rules),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a list of all programme rules")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetRulesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Rule[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution)
.CacheOutput();
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public sealed class UpdateRuleEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/execution/rules", async (UpdateRuleRequest request, HttpContext context,
IExecutionService service, CancellationToken cancellationToken = default) =>
await service.UpdateRuleAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update the details of a specific programme rule (supplied version auto-increment)")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateRuleEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution);
}
}
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class CreateInstrumentEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/governance/instruments", async (CreateInstrumentRequest request, HttpContext context,
IGovernanceService service, CancellationToken cancellationToken = default) =>
await service.CreateInstrumentAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true} => Results.Ok(),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Creates a new programme funding instrument")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateInstrumentEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class CreateTenantEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/governance/tenants", async (CreateTenantRequest request, IGovernanceService service, HttpContext context,
CancellationToken cancellationToken = default) =>
await service.CreateTenantAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
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("Create a new tenant")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateTenantEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance);
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Shared;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class GetAuditLogsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/governance/auditlog", async (Pagination pagination, AuditService service,
CancellationToken cancellationToken = default) =>
await service.GetAuditLogsAsync(pagination, cancellationToken) switch
{
{ IsSuccess: true, Value: var logs } => Results.Ok(logs),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Retrieves audit logs based on pagination parameters")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetAuditLogsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<AuditLog[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -18,7 +18,7 @@ public class GetInstrumentsEndpoint : IEndpoint
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
await service.GetInstrumentsAsync(programmeId, cancellationToken) switch await service.GetInstrumentsAsync(programmeId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var instrumenrs } => Results.Ok(instrumenrs), { IsSuccess: true, Value: var instruments } => Results.Ok(instruments),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()), var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
@@ -20,7 +20,7 @@ public class GetOrganisationEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var organisation } => Results.Ok(organisation), { IsSuccess: true, Value: var organisation } => Results.Ok(organisation),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get organization profile by ID") .WithDescription("Get organization profile by ID")
@@ -28,9 +28,9 @@ public class GetOrganisationEndpoint : IEndpoint
.WithName(typeof(GetOrganisationEndpoint).ToEndpointName()) .WithName(typeof(GetOrganisationEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Organisation>(StatusCodes.Status200OK) .Produces<Organisation>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance) .WithTags(EndpointTags.Governance)
.CacheOutput(); .CacheOutput();
@@ -0,0 +1,36 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public class GetOrganisationPortfoliosEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/organisations/{organisationId}/portfolio", async (string organisationId,
IGovernanceService service, CancellationToken cancellationToken = default) =>
await service.GetOrganisationPorfoliosAsync(organisationId, cancellationToken) switch
{
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get all portfolios belonging to a specific organisation")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetOrganisationPortfoliosEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Portfolio[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class GetPorfolioValueEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/portfolios/{portfolioId}/value", async (string portfolioId, IGovernanceService service,
CancellationToken cancellationToken = default) =>
await service.GetPorfolioValueAsync(portfolioId, cancellationToken) switch
{
{ IsSuccess: true, Value: var amount } => Results.Ok(amount),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Calculates and returns a portfolio's total project's budget value")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetPorfolioValueEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<decimal>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -19,7 +19,7 @@ public sealed class GetPortfolioEndpoint : IEndpoint
await service.GetPortfolioAsync(portfolioIdHash: portfolioId, cancellationToken) switch await service.GetPortfolioAsync(portfolioIdHash: portfolioId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var portfolio } => Results.Ok(portfolio), { IsSuccess: true, Value: var portfolio } => Results.Ok(portfolio),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get a specific portfolios") .WithDescription("Get a specific portfolios")
@@ -27,9 +27,9 @@ public sealed class GetPortfolioEndpoint : IEndpoint
.WithName(typeof(GetPortfolioEndpoint).ToEndpointName()) .WithName(typeof(GetPortfolioEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Portfolio[]>(StatusCodes.Status200OK) .Produces<Portfolio[]>(StatusCodes.Status200OK)
.Produces<Portfolio[]>(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance) .WithTags(EndpointTags.Governance)
.CacheOutput(); .CacheOutput();
@@ -19,7 +19,7 @@ public sealed class GetPortfolioProgrammesEndpoint : IEndpoint
await service.GetPortfolioProgrammesAsync(portfolioId, cancellationToken) switch await service.GetPortfolioProgrammesAsync(portfolioId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios), { IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -28,9 +28,9 @@ public sealed class GetPortfolioProgrammesEndpoint : IEndpoint
.WithName(typeof(GetPortfolioProgrammesEndpoint).ToEndpointName()) .WithName(typeof(GetPortfolioProgrammesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Programme[]>(StatusCodes.Status200OK) .Produces<Programme[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance) .WithTags(EndpointTags.Governance)
.CacheOutput(); .CacheOutput();
@@ -1,7 +1,7 @@
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security; using PostFundManagement.Domain.Security;
@@ -10,16 +10,15 @@ using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public class GetPortfoliosEndpoint : IEndpoint public sealed class GetPortfoliosEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/governance/organisations/{organisationId}/portfolio", async (string organisationId, builder.MapPost("api/governance/organisations/portfolios", async (Pagination pagination, IGovernanceService service,
IGovernanceService service, CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
await service.GetPorfoliosAsync(organisationId, cancellationToken) switch await service.GetPorfoliosAsync(pagination, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios), { IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get all portfolios") .WithDescription("Get all portfolios")
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class GetProgrammeValueEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/programmes/{programmeId}/value", async (string programmeId, IGovernanceService service,
CancellationToken cancellationToken = default) =>
await service.GetProgrammeValueAsync(programmeId, cancellationToken) switch
{
{ IsSuccess: true, Value: var amount } => Results.Ok(amount),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Calculates and returns a programme's total project budget value")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetProgrammeValueEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<decimal>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -0,0 +1,37 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class GetTenantEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/tenants/{tenantId}", async (string tenantId, IGovernanceService service,
CancellationToken cancellationToken = default) =>
await service.GetTenantAsync(tenantId, cancellationToken) switch
{
{ IsSuccess: true, Value: var tenant } => Results.Ok(tenant),
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 system tenants")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetTenantsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Tenant>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance);
}
}
@@ -0,0 +1,32 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Entities;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class GetTenantsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/tenants", async (IGovernanceService service,
CancellationToken cancellationToken = default) =>
await service.GetTenantsAsync(cancellationToken) switch
{
{ IsSuccess: true, Value: var tenants } => Results.Ok(tenants),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Gets system tenants")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetTenantsEndpoint).ToEndpointName("Retrieval"))
.MapToApiVersion(new ApiVersion(1))
.Produces<Tenant[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance);
}
}
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public sealed class UpdateInstrumentEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/governance/instruments", async (UpdateInstrumentRequest request, HttpContext context,
IGovernanceService service, CancellationToken cancellationToken = default) =>
await service.UpdateInstrumentAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()),
})
.WithDescription("Updates a programme funding instrument")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateInstrumentEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -15,11 +15,13 @@ public sealed class UpdateOrganisationEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPut("api/governance/organisations", async (UpdateOrganisationRequest request, builder.MapPut("api/governance/organisations", async (UpdateOrganisationRequest request,
IGovernanceService service, CancellationToken cancellationToken = default) => IGovernanceService service, HttpContext context, CancellationToken cancellationToken = default) =>
await service.UpdateOrganisationAsync(request, cancellationToken) switch await service.UpdateOrganisationAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Update an existing organization profile") .WithDescription("Update an existing organization profile")
@@ -27,10 +29,10 @@ public sealed class UpdateOrganisationEndpoint : IEndpoint
.WithName(typeof(UpdateOrganisationEndpoint).ToEndpointName()) .WithName(typeof(UpdateOrganisationEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Governance); .WithTags(EndpointTags.Governance);
} }
} }
@@ -1,36 +1,36 @@
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
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 PostFundManagement.Domain.Security; using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Tools; namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public sealed class EncodeIdEndpoint : IEndpoint public sealed class UpdateTenantEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/tools/hashid/{id64:long}", async (long id64, IToolService service, builder.MapPut("api/governance/tenants", async (UpdateTenantRequest request, HttpContext context,
CancellationToken cancellationToken) => IGovernanceService service, CancellationToken cancellationToken = default) =>
await service.EncodeLong(id64, cancellationToken) switch await service.UpdateTenantAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true, Value: var hash } => Results.Ok(hash), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<ValidationError>() => Results.BadRequest(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()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Hashes an int64 identifier") .WithDescription("Updates a specific tenant")
.RequireAuthorization(Constants.MainScope) .RequireAuthorization(Constants.MainScope)
.WithName(typeof(EncodeIdEndpoint).ToEndpointName()) .WithName(typeof(UpdateTenantEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<string>(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Tools) .WithTags(EndpointTags.Governance);
.CacheOutput();
} }
} }
@@ -19,7 +19,7 @@ public class CreateAwardEndpoint : IEndpoint
await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), 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<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
@@ -30,7 +30,7 @@ public class CreateAwardEndpoint : IEndpoint
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants); .WithTags(EndpointTags.Grants);
@@ -20,8 +20,8 @@ public class CreateBudgetEndpoint : IEndpoint
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Create a new budget line (M05)") .WithDescription("Create a new budget line (M05)")
@@ -29,8 +29,8 @@ public class CreateBudgetEndpoint : IEndpoint
.WithName(typeof(CreateBudgetEndpoint).ToEndpointName()) .WithName(typeof(CreateBudgetEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
@@ -19,7 +19,7 @@ public class CreateContractEndpoint : IEndpoint
await service.CreateContractAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateContractAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -29,6 +29,7 @@ public class CreateContractEndpoint : IEndpoint
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants); .WithTags(EndpointTags.Grants);
@@ -20,7 +20,7 @@ public class GetAwardEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var award } => Results.Ok(award), { IsSuccess: true, Value: var award } => Results.Ok(award),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get award by ID") .WithDescription("Get award by ID")
@@ -30,7 +30,7 @@ public class GetAwardEndpoint : IEndpoint
.Produces<Award>(StatusCodes.Status200OK) .Produces<Award>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants) .WithTags(EndpointTags.Grants)
.CacheOutput(); .CacheOutput();
@@ -20,7 +20,7 @@ public class GetBudgetEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var budget } => Results.Ok(budget), { IsSuccess: true, Value: var budget } => Results.Ok(budget),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get budget by ID") .WithDescription("Get budget by ID")
@@ -28,7 +28,7 @@ public class GetBudgetEndpoint : IEndpoint
.WithName(typeof(GetBudgetEndpoint).ToEndpointName()) .WithName(typeof(GetBudgetEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Budget>(StatusCodes.Status200OK) .Produces<Budget>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
@@ -20,7 +20,7 @@ public class GetContractEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var contract } => Results.Ok(contract), { IsSuccess: true, Value: var contract } => Results.Ok(contract),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get contract by ID") .WithDescription("Get contract by ID")
@@ -28,7 +28,7 @@ public class GetContractEndpoint : IEndpoint
.WithName(typeof(GetContractEndpoint).ToEndpointName()) .WithName(typeof(GetContractEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Contract>(StatusCodes.Status200OK) .Produces<Contract>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
@@ -19,7 +19,7 @@ public sealed class GetContractUrlEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var contractUrl} => Results.Ok(contractUrl), { IsSuccess: true, Value: var contractUrl} => Results.Ok(contractUrl),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Gets a secure download URL, has expiry period (default 15min)") .WithDescription("Gets a secure download URL, has expiry period (default 15min)")
@@ -29,7 +29,7 @@ public sealed class GetContractUrlEndpoint : IEndpoint
.Produces<string>(StatusCodes.Status200OK) .Produces<string>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants) .WithTags(EndpointTags.Grants)
.CacheOutput(); .CacheOutput();
@@ -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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants;
[ApiVersionTarget(1)]
public sealed class UpdateBudgetEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/grants/budgets", async (UpdateBudgetRequest request, HttpContext context,
IGrantsService service, CancellationToken cancellationToken = default) =>
await service.UpdateBudgetAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Updates a specific budget")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateBudgetEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -19,7 +19,7 @@ public sealed class UpdateContractEndpoint : IEndpoint
await service.UpdateContractAsync(request, cancellationToken) switch await service.UpdateContractAsync(request, cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), 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<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
@@ -30,7 +30,7 @@ public sealed class UpdateContractEndpoint : IEndpoint
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants) .WithTags(EndpointTags.Grants)
@@ -40,7 +40,6 @@ public sealed class UploadContractEndpoint : IEndpoint
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants) .WithTags(EndpointTags.Grants)
.CacheOutput(); .CacheOutput();
@@ -19,7 +19,7 @@ public class CreateActivityEndpoint : IEndpoint
await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } result => Results.Ok(), { IsSuccess: true } result => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -28,6 +28,7 @@ public class CreateActivityEndpoint : IEndpoint
.WithName(typeof(CreateActivityEndpoint).ToEndpointName()) .WithName(typeof(CreateActivityEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class CreateBeneficiaryEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/beneficiaries", async (CreateBeneficiaryRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.CreateBeneficiaryAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Add a new project beneficiary")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateBeneficiaryEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,38 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class CreateChangeRequestEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/changerequests", async (CreateChangeRequest request, IProjectService service,
HttpContext context, CancellationToken cancellationToken = default) =>
await service.CreateChangeRequestAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
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("Create a new change request against an existing project")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateChangeRequestEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class CreateComplianceItemEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/complianceitems", async (CreateComplianceItemRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.CreateComplianceItemAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
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("Add a new beneficiary compliance item")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateComplianceItemEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class CreateIssueEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/issues", async (CreateIssueRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.CreateIssueAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Add a new project issue")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateIssueEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,36 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class CreateOutcomeEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/beneficiaris/outcomes", async (CreateOutcomeRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.CreateOutcomeAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Add a new beneficiary outcome")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateOutcomeEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -19,7 +19,7 @@ public sealed class CreateProgrammeEndpoint : IEndpoint
await service.CreateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch await service.CreateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), 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<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(), var r when r.HasError<CreateError>() => Results.BadRequest(),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
@@ -29,9 +29,9 @@ public sealed class CreateProgrammeEndpoint : IEndpoint
.WithName(typeof(CreateProgrammeEndpoint).ToEndpointName()) .WithName(typeof(CreateProgrammeEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects); .WithTags(EndpointTags.Projects);
} }
@@ -19,7 +19,7 @@ public class CreateProjectEndpoint : IEndpoint
await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken) switch await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } result => Results.Ok(), { IsSuccess: true } result => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), 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<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
@@ -29,9 +29,9 @@ public class CreateProjectEndpoint : IEndpoint
.WithName(typeof(CreateProjectEndpoint).ToEndpointName()) .WithName(typeof(CreateProjectEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects); .WithTags(EndpointTags.Projects);
} }
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class CreateSiteVisitEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/sitevisits", async (CreateSiteVisitRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.CreateSiteVisitAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Add a new project site visit")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(CreateSiteVisitEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -19,7 +19,7 @@ public class GetActivityEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var activity } => Results.Ok(activity), { IsSuccess: true, Value: var activity } => Results.Ok(activity),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Gets a specific activity by it's id") .WithDescription("Gets a specific activity by it's id")
@@ -27,9 +27,9 @@ public class GetActivityEndpoint : IEndpoint
.WithName(typeof(GetActivityEndpoint).ToEndpointName()) .WithName(typeof(GetActivityEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Models.Activity>(StatusCodes.Status200OK) .Produces<Domain.Models.Activity>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects) .WithTags(EndpointTags.Projects)
.CacheOutput(); .CacheOutput();
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetBeneficiariesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/beneficiaries", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetBeneficiariesAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var beneficiaries } => Results.Ok(beneficiaries),
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 project beneficiaries")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetBeneficiariesEndpoint).ToEndpointName("Retrieval"))
.MapToApiVersion(new ApiVersion(1))
.Produces<Beneficiary[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetBeneficiaryEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/beneficiaries/{beneficiaryId}", async (string beneficiaryId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetBeneficiaryAsync(beneficiaryId, cancellationToken) switch
{
{ IsSuccess: true, Value: var beneficiary } => Results.Ok(beneficiary),
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 project beneficiaries")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetBeneficiariesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Beneficiary>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetChangeRequestEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/changerequests/{changeRequestId}", async (string changeRequestId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetChangeRequestAsync(changeRequestId, cancellationToken) switch
{
{ IsSuccess: true, Value: var changeRequest } => Results.Ok(changeRequest),
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 change request")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetChangeRequestEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<ChangeRequest>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetChangeRequestsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/changerequests", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetChangeRequestsAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var changeRequests } => Results.Ok(changeRequests),
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 project change requests")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetChangeRequestsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<ChangeRequest[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetComplianceItemEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/complianceitems/{complianceItemId}", async (string complianceItemId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetComplianceItemAsync(complianceItemId, cancellationToken) switch
{
{ IsSuccess: true, Value: var complianceItem } => Results.Ok(complianceItem),
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 compliance item")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetComplianceItemEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<ComplianceItem>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetComplianceItemsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/complianceitems", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetComplianceItemsAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var complianceItems } => Results.Ok(complianceItems),
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 project compliance items")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetComplianceItemsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<ComplianceItem[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetIssueEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/issues/{issueId}", async (string issueId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetIssueAsync(issueId, cancellationToken) switch
{
{ IsSuccess: true, Value: var issue } => Results.Ok(issue),
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 issue")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetIssueEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Issue>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetIssuesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/issues", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetIssuesAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var issues } => Results.Ok(issues),
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 project issues")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetIssuesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Issue[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -20,7 +20,7 @@ public class GetMilestoneEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var milestone } => Results.Ok(milestone), { IsSuccess: true, Value: var milestone } => Results.Ok(milestone),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Gets a specific milestone") .WithDescription("Gets a specific milestone")
@@ -28,9 +28,9 @@ public class GetMilestoneEndpoint : IEndpoint
.WithName(typeof(GetMilestoneEndpoint).ToEndpointName()) .WithName(typeof(GetMilestoneEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Milestone>(StatusCodes.Status200OK) .Produces<Milestone>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects) .WithTags(EndpointTags.Projects)
.CacheOutput(); .CacheOutput();
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetOutcomeEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/outcomes/{outcomeId}", async (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())
})
.WithDescription("Gets a specific project outcome")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetOutcomeEndpoint).ToEndpointName())
.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();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetOutcomesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/outcomes/{beneficiatyId}", async (string beneficiatyId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetOutcomesAsync(beneficiatyId, cancellationToken) switch
{
{ IsSuccess: true, Value: var outcomes } => Results.Ok(outcomes),
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 project beneficiary outcomes")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetOutcomesEndpoint).ToEndpointName())
.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();
}
}
@@ -19,7 +19,7 @@ public sealed class GetProgrammeEndpoint : IEndpoint
await service.GetProgrammeAsync(programmeId, cancellationToken) switch await service.GetProgrammeAsync(programmeId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var programme } => Results.Ok(programme), { IsSuccess: true, Value: var programme } => Results.Ok(programme),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -30,6 +30,7 @@ public sealed class GetProgrammeEndpoint : IEndpoint
.Produces<Programme>(StatusCodes.Status200OK) .Produces<Programme>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects) .WithTags(EndpointTags.Projects)
.CacheOutput(); .CacheOutput();
@@ -19,7 +19,7 @@ public sealed class GetProgrammeProjectsEndpoint : IEndpoint
await service.GetProgrammeProjectsAsync(programmeId, cancellationToken) switch await service.GetProgrammeProjectsAsync(programmeId, cancellationToken) switch
{ {
{ IsSuccess: true, Value: var projects } => Results.Ok(projects), { IsSuccess: true, Value: var projects } => Results.Ok(projects),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()), var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
@@ -28,9 +28,9 @@ public sealed class GetProgrammeProjectsEndpoint : IEndpoint
.WithName(typeof(GetProgrammeProjectsEndpoint).ToEndpointName()) .WithName(typeof(GetProgrammeProjectsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Project[]>(StatusCodes.Status200OK) .Produces<Project[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects) .WithTags(EndpointTags.Projects)
.CacheOutput(); .CacheOutput();
@@ -20,7 +20,7 @@ public class GetProjectEndpoint : IEndpoint
{ {
{ IsSuccess: true, Value: var project } => Results.Ok(project), { IsSuccess: true, Value: var project } => Results.Ok(project),
var r when r.HasError<IdDecodeError>() => Results.Forbid(), var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Get project by ID") .WithDescription("Get project by ID")
@@ -28,9 +28,9 @@ public class GetProjectEndpoint : IEndpoint
.WithName(typeof(GetProjectEndpoint).ToEndpointName()) .WithName(typeof(GetProjectEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces<Project[]>(StatusCodes.Status200OK) .Produces<Project[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects) .WithTags(EndpointTags.Projects)
.CacheOutput(); .CacheOutput();
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetSiteVisitEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/sitevisits/{siteVisitId}", async (string siteVisitId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetSiteVisitAsync(siteVisitId, cancellationToken) switch
{
{ IsSuccess: true, Value: var siteVisit } => Results.Ok(siteVisit),
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 project site visit")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetSiteVisitEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<SiteVisit>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,38 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class GetSiteVisitsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/sitevisits", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetSiteVisitsAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var siteVisits } => Results.Ok(siteVisits),
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 project site visits")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(GetSiteVisitsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<SiteVisit[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateBeneficiaryEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/beneficiaries", async (UpdateBeneficiaryRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateBeneficiaryAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update project beneficiary")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateBeneficiaryEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateChangeRequestEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/changerequests", async (UpdateChangeRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateChangeRequestAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update a specific project change request")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateChangeRequestEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateComplianceItemEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/complianceitems", async (UpdateComplianceItemRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateComplianceItemAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update a specific compliance item")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateComplianceItemEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateIssueEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/issues", async (UpdateIssueRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateIssueAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update a specific project issue")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateIssueEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateOutcomeEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/outcomes", async (UpdateOutcomeRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateOutcomeAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update project beneficiary outcome")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateOutcomeEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -19,7 +19,7 @@ public sealed class UpdateProgrammeEndpoint : IEndpoint
await service.UpdateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch await service.UpdateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -30,6 +30,7 @@ public sealed class UpdateProgrammeEndpoint : IEndpoint
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects); .WithTags(EndpointTags.Projects);
@@ -19,7 +19,7 @@ public class UpdateProjectStatusEndpoint : IEndpoint
await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch
{ {
{ IsSuccess: true } => Results.Ok(), { IsSuccess: true } => Results.Ok(),
var r when r.HasError<NotFoundError>() => Results.NotFound(), var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()), var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems()) var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
@@ -29,6 +29,7 @@ public class UpdateProjectStatusEndpoint : IEndpoint
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects); .WithTags(EndpointTags.Projects);
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateSiteVisitEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/sitevisits", async (UpdateSiteVisitRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateSiteVisitAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update project site visit")
.RequireAuthorization(Constants.MainScope)
.WithName(typeof(UpdateSiteVisitEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -16,27 +16,22 @@ public class SyncUserEndpoint : IEndpoint
{ {
builder.MapPost("api/integration/sync", async (IJobOrchestrator jobOrchestrator, builder.MapPost("api/integration/sync", async (IJobOrchestrator jobOrchestrator,
HttpContext context, IUserService service, CancellationToken cancellationToken = default) => HttpContext context, IUserService service, CancellationToken cancellationToken = default) =>
{ await service.SyncUserAsync(context.GetUserId(), context.GetUserEmail(), context.GetUserRole(), cancellationToken) switch
var result = await service.SyncUserAsync(context.GetUserId(), context.GetUserEmail(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
if (result.IsSuccess) var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
await jobOrchestrator.SendAsync(SyncUserEvent.Create(), cancellationToken); var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
return result switch })
{ .WithDescription("Trigger integration and data synchronization in the background")
{ IsSuccess: true } => Results.Ok(), .RequireAuthorization(Constants.MainScope)
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()), .WithName(typeof(SyncUserEndpoint).ToEndpointName())
var r => Results.InternalServerError(r.Errors.ToProblems()) .MapToApiVersion(new ApiVersion(1))
}; .Produces(StatusCodes.Status200OK)
}) .Produces(StatusCodes.Status401Unauthorized)
.WithDescription("Trigger integration and data synchronization in the background") .Produces<Problems>(StatusCodes.Status401Unauthorized)
.RequireAuthorization(Constants.MainScope) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithName(typeof(SyncUserEndpoint).ToEndpointName()) .WithTags(EndpointTags.Users)
.MapToApiVersion(new ApiVersion(1)) .CacheOutput();
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Users)
.CacheOutput();
} }
} }
+13
View File
@@ -1,8 +1,10 @@
using Microsoft.FeatureManagement; using Microsoft.FeatureManagement;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Filters; using PostFundManagement.Application.Filters;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Health; using PostFundManagement.Domain.Health;
using PostFundManagement.Domain.Mediator; using PostFundManagement.Domain.Mediator;
using PostFundManagement.Infrastructure.Database.Services;
using static PostFundManagement.Domain.Extensions.Constants; using static PostFundManagement.Domain.Extensions.Constants;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -27,6 +29,8 @@ builder.Services.AddApiVersioning(options =>
}); });
builder.Services.AddScoped<ActiveUserFilter>(); builder.Services.AddScoped<ActiveUserFilter>();
builder.Services.AddScoped<UserRoleFilter>();
builder.Services.AddTransient<ITenantContextService, TenantContextService>();
builder.Services.AddEndpoints(Assembly.GetExecutingAssembly()); builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
builder.Services.AddApiServices(builder.Configuration); builder.Services.AddApiServices(builder.Configuration);
@@ -153,6 +157,15 @@ var versionGroups = new Dictionary<int, RouteGroupBuilder>
var filter = context.HttpContext.RequestServices.GetRequiredService<ActiveUserFilter>(); var filter = context.HttpContext.RequestServices.GetRequiredService<ActiveUserFilter>();
return await filter.InvokeAsync(context, next);
})
.AddEndpointFilter(async (context, next) =>
{
if (HttpMethods.IsOptions(context.HttpContext.Request.Method))
return await next(context);
var filter = context.HttpContext.RequestServices.GetRequiredService<UserRoleFilter>();
return await filter.InvokeAsync(context, next); return await filter.InvokeAsync(context, next);
}) })
} }
+5 -2
View File
@@ -20,8 +20,11 @@
}, },
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Warning",
"Microsoft.AspNetCore": "Warning" "Quartz": "Error",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database": "Error",
"Microsoft.EntityFrameworkCore.Update": "Error"
} }
}, },
"EmailProcessorCron": "0 */5 * * * ?", "EmailProcessorCron": "0 */5 * * * ?",
@@ -12,8 +12,8 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
public string GenerateCdnUrl(string fileKey) => $"{CdnBaseUrl}/{fileKey}"; public string GenerateCdnUrl(string fileKey) => $"{CdnBaseUrl}/{fileKey}";
public virtual async ValueTask<Result<string>> GeneratePresignedUrlAsync(string fileKey, TimeSpan duration, string? contentType = null, CancellationToken cancellationToken = default) => public virtual async Task<Result<string>> GeneratePresignedUrlAsync(string fileKey, TimeSpan duration, string? contentType = null, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<string>>>)(async () => await Result.Try((Func<Task<Result<string>>>)(async () =>
{ {
if (string.IsNullOrWhiteSpace(BucketName)) if (string.IsNullOrWhiteSpace(BucketName))
return Result.Fail(ValidationError.Create("Bucket name is not configured.")); return Result.Fail(ValidationError.Create("Bucket name is not configured."));
@@ -51,8 +51,8 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
: $"{cleanSubPath}/{fileHash.ToLower(CultureInfo.InvariantCulture)}_{fileName}"; : $"{cleanSubPath}/{fileHash.ToLower(CultureInfo.InvariantCulture)}_{fileName}";
} }
public virtual async ValueTask<Result<string>> UploadFileAsync(string fileName, string subPath, Stream fileStream, string contentType, CancellationToken cancellationToken = default) => public virtual async Task<Result<string>> UploadFileAsync(string fileName, string subPath, Stream fileStream, string contentType, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<string>>>)(async () => await Result.Try((Func<Task<Result<string>>>)(async () =>
{ {
if (string.IsNullOrWhiteSpace(BucketName)) if (string.IsNullOrWhiteSpace(BucketName))
return Result.Fail<string>(ValidationError.Create("Bucket name is not configured.")); return Result.Fail<string>(ValidationError.Create("Bucket name is not configured."));
@@ -92,8 +92,8 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
: Result.Ok(fileKey); : Result.Ok(fileKey);
})); }));
public virtual async ValueTask<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) => public virtual async Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result>>)(async () => await Result.Try((Func<Task<Result>>)(async () =>
{ {
if (string.IsNullOrWhiteSpace(BucketName)) if (string.IsNullOrWhiteSpace(BucketName))
return Result.Fail(NotFoundError.Create("Bucket name is not configured.")); return Result.Fail(NotFoundError.Create("Bucket name is not configured."));
@@ -1,16 +1,36 @@
using PostFundManagement.Application.Services.Communications; using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface ICommunicationsService public interface ICommunicationsService
{ {
ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid applicantUserId, CancellationToken cancellationToken = default); [Log]
Task<Result<Invite>> GetApplicantInviteAsync(Guid applicantUserId, CancellationToken cancellationToken = default);
ValueTask<Result<Invite[]>> GetApplicantInvitesAsync(Guid applicantUserId, CancellationToken cancellationToken = default); [Log]
Task<Result<Invite[]>> GetApplicantInvitesAsync(Guid applicantUserId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateNotificationAsync(CreateNotificationRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateNotificationAsync(UpdateNotificationRequest request, CancellationToken cancellationToken = default);
[Log]
Task<Result<Notification[]>> GetNotificationsAsync(string organisationIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<Notification>> GetNotificationAsync(string notificationIdHash, CancellationToken cancellationToken = default);
} }
@@ -1,32 +1,50 @@
using PostFundManagement.Application.Services.Evidence; using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IEvidenceService public interface IEvidenceService
{ {
ValueTask<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default);
ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken); [Log]
Task<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken);
ValueTask<Result<Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default); [Log]
Task<Result<Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<string>> GetEvidenceUrlAsync(string evidenceIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<string>> GetEvidenceUrlAsync(string evidenceIdHash, CancellationToken cancellationToken = default);
} }
@@ -1,26 +1,55 @@
using PostFundManagement.Application.Services.Execution; using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IExecutionService public interface IExecutionService
{ {
ValueTask<Result<Risk[]>> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateRuleAsync(CreateRuleRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Disbursement[]>> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateRuleAsync(UpdateRuleRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Rule>> GetRuleAsync(string ruleIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Rule[]>> GetRulesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Risk[]>> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Disbursement[]>> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default);
} }
@@ -1,32 +1,98 @@
using PostFundManagement.Application.Services.Governance; using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IGovernanceService public interface IGovernanceService
{ {
ValueTask<Result<Programme[]>> GetProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<decimal>> GetProgrammeValueAsync(string programmeIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Portfolio[]>> GetPorfoliosAsync(string organisationIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<decimal>> GetPorfolioValueAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Organisation[]>> GetOrganisationsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateInstrumentAsync(UpdateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Organisation>> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateInstrumentAsync(CreateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Programme[]>> GetProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Dashboard>> GetDashboardAsync(CancellationToken cancellationToken = default); [Log]
Task<Result<Portfolio[]>> GetPorfoliosAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Portfolio[]>> GetOrganisationPorfoliosAsync(string organisationIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default); [Log]
Task<Result<Organisation[]>> GetOrganisationsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Organisation>> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Dashboard>> GetDashboardAsync(CancellationToken cancellationToken = default);
ValueTask<Result<Portfolio>> GetPortfolioAsync(string portfolioIdHash, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<Portfolio>> GetPortfolioAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateTenantAsync(CreateTenantRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<Tenant[]>> GetTenantsAsync(CancellationToken cancellationToken = default);
[Log]
Task<Result<Tenant>> GetTenantAsync(string tenantIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateTenantAsync(UpdateTenantRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateTenantUserAsync(UpdateTenantUserRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateTenantOrganisationAsync(string organisationIdHash, string tenantIdHash, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateTenantOrganisationAsync(string tenantOrganisationIdHash, ActivityStatus status, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<TenantOrganisation[]>> GetTenantOrganisationsAsync(CancellationToken cancellationToken = default);
[Log]
Task<Result<TenantOrganisation>> GetTenantOrganisationAsync(string tenantOrganisationIdHash, CancellationToken cancellationToken = default);
} }
@@ -1,34 +1,59 @@
using PostFundManagement.Application.Services.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IGrantsService public interface IGrantsService
{ {
ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateBudgetAsync(UpdateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Contract>> GetContractAsync(string contractIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Contract>> GetContractAsync(string contractIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Budget>> GetBudgetAsync(string budgetIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Budget>> GetBudgetAsync(string budgetIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
Task<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<string>> GetContractUrlAsync(string contractIdHash, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> UploadContractAsync(string contractIdHash, Guid userId, IFormFile file, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result<string>> GetContractUrlAsync(string contractIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateContractAsync(UpdateContractRequest request, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UploadContractAsync(string contractIdHash, Guid userId, IFormFile file, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateContractAsync(UpdateContractRequest request, CancellationToken cancellationToken = default);
} }
@@ -1,6 +1,10 @@
namespace PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Domain.Attributes;
namespace PostFundManagement.Application.Abstractions.Services;
public interface IIdentityService public interface IIdentityService
{ {
ValueTask<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default);
} }
@@ -1,46 +1,157 @@
using PostFundManagement.Application.Services.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IProjectService public interface IProjectService
{ {
ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Project>> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Project>> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator[]>> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Indicator[]>> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Programme[]>> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default); [Log]
Task<Result<Programme[]>> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Project[]>> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default); [Log]
Task<Result<Project[]>> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateChangeRequestAsync(CreateChangeRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateChangeRequestAsync(UpdateChangeRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<ChangeRequest[]>> GetChangeRequestsAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<ChangeRequest>> GetChangeRequestAsync(string changeRequestIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateComplianceItemAsync(CreateComplianceItemRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateComplianceItemAsync(UpdateComplianceItemRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<ComplianceItem[]>> GetComplianceItemsAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<ComplianceItem>> GetComplianceItemAsync(string complianceItemIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateBeneficiaryAsync(CreateBeneficiaryRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateBeneficiaryAsync(UpdateBeneficiaryRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<Beneficiary[]>> GetBeneficiariesAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<Beneficiary>> GetBeneficiaryAsync(string beneficiaryIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateIssueAsync(CreateIssueRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateIssueAsync(UpdateIssueRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<Issue[]>> GetIssuesAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<Issue>> GetIssueAsync(string issueIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateOutcomeAsync(CreateOutcomeRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateOutcomeAsync(UpdateOutcomeRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<Outcome[]>> GetOutcomesAsync(string beneficiaryIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<Outcome>> GetOutcomeAsync(string outcomeIdHash, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> CreateSiteVisitAsync(CreateSiteVisitRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> UpdateSiteVisitAsync(UpdateSiteVisitRequest request, Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<SiteVisit[]>> GetSiteVisitsAsync(string projectIdHash, CancellationToken cancellationToken = default);
[Log]
Task<Result<SiteVisit>> GetSiteVisitAsync(string siteVisitIdHash, CancellationToken cancellationToken = default);
} }
@@ -2,5 +2,5 @@
public interface IToolService public interface IToolService
{ {
ValueTask<Result<string>> EncodeLong(long id, CancellationToken cancellationToken); Task<Result<string>> EncodeLong(long id, CancellationToken cancellationToken);
} }
@@ -1,16 +1,23 @@
using PostFundManagement.Application.Services.Users; using PostFundManagement.Application.Services.Users;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Attributes;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services; namespace PostFundManagement.Application.Abstractions.Services;
public interface IUserService public interface IUserService
{ {
ValueTask<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> SyncUserAsync(Guid userId, string email, string role, CancellationToken cancellationToken = default);
ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default); [Log]
Task<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default); [Log]
[Audit]
Task<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default);
ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken); [Log]
Task<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken);
} }
@@ -12,6 +12,8 @@ public static class General
public static string GetUserEmail(this HttpContext context) => (string)context.Items[Domain.Extensions.Constants.EmailtemKey]!; public static string GetUserEmail(this HttpContext context) => (string)context.Items[Domain.Extensions.Constants.EmailtemKey]!;
public static string GetUserRole(this HttpContext context) => (string)context.Items[Domain.Extensions.Constants.UserRoleItemKey]!;
public static (bool Decoded, Guid userId) GetUserId(this ClaimsPrincipal principal) public static (bool Decoded, Guid userId) GetUserId(this ClaimsPrincipal principal)
{ {
var subClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; var subClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
@@ -30,10 +32,19 @@ public static class General
return (decoded, emailClaim!); return (decoded, emailClaim!);
} }
public static Microsoft.AspNetCore.Mvc.ProblemDetails ToProblems(this IReadOnlyList<IError> errors) => new() public static (bool Decoded, string role) GetUserRole(this ClaimsPrincipal principal)
{
var roleClaim = principal.FindFirst("role")?.Value ?? principal.FindFirst(ClaimTypes.Role)?.Value;
var decoded = !string.IsNullOrWhiteSpace(roleClaim);
return (decoded, roleClaim!);
}
public static Microsoft.AspNetCore.Mvc.ProblemDetails ToProblems(this IReadOnlyList<IError> errors, int statusCode = StatusCodes.Status400BadRequest) => new()
{ {
Detail = string.Join(',', errors.Select(e => e.Message)), Detail = string.Join(',', errors.Select(e => e.Message)),
Status = StatusCodes.Status400BadRequest, Status = statusCode,
Title = "Error", Title = "Error",
}; };
} }
@@ -8,7 +8,7 @@ public static class Quartz
{ {
public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration) public static IServiceCollection AddQuartzSchedulerClient(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{ {
var connectionString = configuration.GetConnectionString(DatabaseConfigName); var connectionString = configuration.GetConnectionString(SchedulerDatabaseConfigName);
services.Configure<QuartzOptions>(options => services.Configure<QuartzOptions>(options =>
{ {
@@ -44,7 +44,7 @@ public static class Quartz
public static IServiceCollection AddQuartzScheduler(this IServiceCollection services, string schedulerName, IConfiguration configuration) public static IServiceCollection AddQuartzScheduler(this IServiceCollection services, string schedulerName, IConfiguration configuration)
{ {
var connectionString = configuration.GetConnectionString(DatabaseConfigName); var connectionString = configuration.GetConnectionString(SchedulerDatabaseConfigName);
services.Configure<QuartzOptions>(options => services.Configure<QuartzOptions>(options =>
{ {
@@ -1,5 +1,6 @@
using PostFundManagement.Application.Abstractions; using PostFundManagement.Application.Abstractions;
using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Interceptors;
using PostFundManagement.Application.Services.Communications; using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Application.Services.Evidence; using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Application.Services.Execution; using PostFundManagement.Application.Services.Execution;
@@ -7,7 +8,6 @@ using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Application.Services.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Application.Services.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Application.Services.Shared; using PostFundManagement.Application.Services.Shared;
using PostFundManagement.Application.Services.Tools;
using PostFundManagement.Application.Services.Users; using PostFundManagement.Application.Services.Users;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
@@ -15,34 +15,71 @@ namespace PostFundManagement.Application.Extensions;
public static class Services public static class Services
{ {
public static IServiceCollection AddLoggedCachedServiceScoped<TInterface, TImplementation, TCacheDecorator>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
where TCacheDecorator : class, TInterface
{
services.AddScoped<TImplementation>();
services.AddScoped<TCacheDecorator>();
services.AddScoped(sp =>
{
var proxyGenerator = sp.GetRequiredService<ProxyGenerator>();
var loggingInterceptor = sp.GetRequiredService<LoggingInterceptor>();
var actual = sp.GetRequiredService<TImplementation>();
var cached = ActivatorUtilities.CreateInstance<TCacheDecorator>(sp, actual);
return proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(cached,
loggingInterceptor.ToInterceptor());
});
return services;
}
public static IServiceCollection AddLoggedAuditedCachedServiceScoped<TInterface, TImplementation, TCacheDecorator>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
where TCacheDecorator : class, TInterface
{
services.AddScoped<TImplementation>();
services.AddScoped<TCacheDecorator>();
services.AddScoped(sp =>
{
var proxyGenerator = sp.GetRequiredService<ProxyGenerator>();
var auditInterceptor = sp.GetRequiredService<AuditInterceptor>();
var loggingInterceptor = sp.GetRequiredService<LoggingInterceptor>();
var actual = sp.GetRequiredService<TImplementation>();
var cached = ActivatorUtilities.CreateInstance<TCacheDecorator>(sp, actual);
return proxyGenerator.CreateInterfaceProxyWithTarget<TInterface>(cached,
loggingInterceptor.ToInterceptor(),
auditInterceptor.ToInterceptor());
});
return services;
}
public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration)
{ {
services.AddScoped<ICommunicationsService, CommunicationsService>(); services.AddScoped<AuditInterceptor>();
services.Decorate<ICommunicationsService, CommunicationsCacheService>(); services.AddScoped<LoggingInterceptor>();
services.AddSingleton<ProxyGenerator>();
services.AddScoped<IEvidenceService, EvidenceService>(); services.AddLoggedAuditedCachedServiceScoped<ICommunicationsService, CommunicationsService, CommunicationsCacheService>();
services.Decorate<IEvidenceService, EvidenceCacheService>(); services.AddLoggedAuditedCachedServiceScoped<IEvidenceService, EvidenceService, EvidenceCacheService>();
services.AddLoggedAuditedCachedServiceScoped<IUserService, UserService, UserCacheService>();
services.AddLoggedAuditedCachedServiceScoped<IExecutionService, ExecutionService, ExecutionCacheService>();
services.AddLoggedAuditedCachedServiceScoped<IGovernanceService, GovernanceService, GovernanceCacheService>();
services.AddLoggedAuditedCachedServiceScoped<IGrantsService, GrantsService, GrantsCacheService>();
services.AddLoggedAuditedCachedServiceScoped<IProjectService, ProjectService, ProjectCacheService>();
services.AddScoped<IUserService, UserService>(); services.AddLoggedCachedServiceScoped<IIdentityService, IdentityService, IdentityCacheService>();
services.Decorate<IUserService, UserCacheService>();
services.AddScoped<IIdentityService, IdentityService>();
services.Decorate<IIdentityService, IdentityCacheService>();
services.AddScoped<IExecutionService, ExecutionService>();
services.Decorate<IExecutionService, ExecutionCacheService>();
services.AddScoped<IGovernanceService, GovernanceService>();
services.Decorate<IGovernanceService, GovernanceCacheService>();
services.AddScoped<IGrantsService, GrantsService>();
services.Decorate<IGrantsService, GrantsCacheService>();
services.AddScoped<IProjectService, ProjectService>();
services.Decorate<IProjectService, ProjectCacheService>();
services.AddScoped<IToolService, ToolService>();
services.Decorate<IToolService, ToolCacheService>();
services.AddStackExchangeRedisCache(options => services.AddStackExchangeRedisCache(options =>
{ {
@@ -17,16 +17,20 @@ public sealed class ActiveUserFilter(IIdentityService identityService) : IEndpoi
var (emailDecoded, email) = principal.GetUserEmail(); var (emailDecoded, email) = principal.GetUserEmail();
if (!decoded) if (!decoded)
return Results.Json(new { message = "Invalid user identifier in token." }, statusCode: StatusCodes.Status400BadRequest); return Results.Json(new { message = "Invalid user identifier in token." }, statusCode: StatusCodes.Status401Unauthorized);
if (!emailDecoded) if (!emailDecoded)
return Results.Json(new { message = "Invalid/missing user email in token." }, statusCode: StatusCodes.Status400BadRequest); return Results.Json(new { message = "Invalid/missing user email in token." }, statusCode: StatusCodes.Status401Unauthorized);
var path = context.HttpContext.Request.Path.Value; var path = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrWhiteSpace(path) && !path!.EndsWith("integration/sync", StringComparison.InvariantCultureIgnoreCase)) if (!string.IsNullOrWhiteSpace(path) && !path!.EndsWith("integration/sync", StringComparison.InvariantCultureIgnoreCase))
if ((await identityService.UserIsAuthorisedAsync(userId, context.HttpContext.RequestAborted)).IsFailed) {
return (object?)Results.Json(new { message = "Account approval is pending or inactive." }, statusCode: StatusCodes.Status403Forbidden); var userAuthResult = await identityService.UserIsAuthorisedAsync(userId, context.HttpContext.RequestAborted);
if (userAuthResult.IsFailed)
return Results.Json(new { message = "Account approval is pending or inactive." }, statusCode: StatusCodes.Status403Forbidden);
}
context.HttpContext.Items[UserIdItemKey] = userId; context.HttpContext.Items[UserIdItemKey] = userId;
context.HttpContext.Items[EmailtemKey] = email; context.HttpContext.Items[EmailtemKey] = email;
@@ -0,0 +1,25 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Application.Filters;
public sealed class UserRoleFilter(ITenantContextService tenantContext) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var principal = context.HttpContext.User;
var (roleDecoded, role) = principal.GetUserRole();
if (!roleDecoded)
return Results.Json(new { message = "Invalid/missing user role in token." }, statusCode: StatusCodes.Status401Unauthorized);
context.HttpContext.Items[UserRoleItemKey] = role;
if (role.Equals("Partner", StringComparison.InvariantCultureIgnoreCase))
tenantContext.ByPassTenantQueryFilter = true;
return await next(context);
}
}
@@ -0,0 +1,211 @@
using PostFundManagement.Application.Services.Shared;
using PostFundManagement.Domain.Attributes;
namespace PostFundManagement.Application.Interceptors;
public sealed class AuditInterceptor(AuditService auditService, IHttpContextAccessor httpContextAccessor) : IAsyncInterceptor
{
public void InterceptSynchronous(IInvocation invocation) => invocation.Proceed();
public void InterceptAsynchronous(IInvocation invocation) =>
invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
public void InterceptAsynchronous<TResult>(IInvocation invocation) =>
invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
private static async Task InternalInterceptAsynchronous(IInvocation invocation)
{
invocation.Proceed();
var task = (Task)invocation.ReturnValue;
await task.ConfigureAwait(false);
}
private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
{
invocation.Proceed();
var task = (Task<TResult>)invocation.ReturnValue;
var resultObj = await task.ConfigureAwait(false);
var method = invocation.MethodInvocationTarget ?? invocation.Method;
var auditAttr = method.GetCustomAttribute<AuditAttribute>()
?? invocation.Method.GetCustomAttribute<AuditAttribute>();
if (auditAttr == null) return resultObj;
if (resultObj is IResultBase result && result.IsSuccess)
{
try
{
var action = ResolveActionName(method.Name);
var entityName = ResolveEntityName(method, resultObj, invocation.Arguments);
var entityId = ResolveEntityId(resultObj, invocation.Arguments);
var changes = BuildPayloadSummary(invocation.Method.GetParameters(), invocation.Arguments);
var performedBy = ResolveUserId();
await auditService.LogActionAsync(
entityId: entityId,
entityName: entityName,
action: action,
changes: changes,
performedBy: performedBy).ConfigureAwait(false);
}
catch { /* eat all exceptions, don't interrupt program flow */ }
}
return resultObj;
}
private static string ResolveActionName(string methodName) =>
methodName.EndsWith("Async", StringComparison.Ordinal)
? methodName[..^5]
: methodName;
private static string ResolveEntityName(MethodInfo method, object? resultObj, object[] args)
{
var returnType = method.ReturnType;
Type? innerType = null;
if (returnType.IsGenericType)
{
var genericDef = returnType.GetGenericTypeDefinition();
if (genericDef == typeof(Task<>) || genericDef == typeof(Task<>))
innerType = returnType.GetGenericArguments()[0];
}
if (innerType != null && innerType.IsGenericType && typeof(IResultBase).IsAssignableFrom(innerType))
{
var valueType = innerType.GetGenericArguments().FirstOrDefault();
if (valueType != null && valueType != typeof(bool) && valueType != typeof(int) && valueType != typeof(string))
return valueType.Name;
}
foreach (var arg in args)
{
if (arg == null || arg is CancellationToken or IFormFile) continue;
var argTypeName = arg.GetType().Name;
if (argTypeName.EndsWith("Request", StringComparison.Ordinal))
{
var trimmed = argTypeName[..^7];
return trimmed.Replace("Create", "").Replace("Update", "");
}
}
return ResolveActionName(method.Name);
}
private static string ResolveEntityId(object? resultObj, object[] args)
{
if (resultObj != null)
{
var valueProp = resultObj.GetType().GetProperty("Value");
if (valueProp != null)
{
var value = valueProp.GetValue(resultObj);
if (value != null)
{
var idProp = value.GetType().GetProperty("Id") ?? value.GetType().GetProperty("id");
if (idProp != null)
{
var idVal = idProp.GetValue(value);
if (idVal != null) return idVal.ToString()!;
}
}
}
}
foreach (var arg in args)
{
if (arg is long or int or Guid or string)
return arg.ToString()!;
if (arg != null && arg is not CancellationToken and not IFormFile)
{
var idProp = arg.GetType().GetProperty("Id") ?? arg.GetType().GetProperty("id");
if (idProp != null)
{
var idVal = idProp.GetValue(arg);
if (idVal != null) return idVal.ToString()!;
}
}
}
return "N/A";
}
private static string[] BuildPayloadSummary(ParameterInfo[] parameters, object[] args)
{
var entries = new List<string>();
for (var i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var arg = args[i];
if (param.ParameterType == typeof(CancellationToken))
continue;
if (arg == null)
{
entries.Add($"{param.Name}: null");
continue;
}
if (arg is IFormFile file)
{
entries.Add($"{param.Name}: [File] Name='{file.FileName}', Size={file.Length} bytes, Type='{file.ContentType}'");
continue;
}
if (arg is IEnumerable<IFormFile> files)
{
var fileSummaries = files.Select(f => $"'{f.FileName}' ({f.Length} bytes)");
entries.Add($"{param.Name}: [Files] count={fileSummaries.Count()} [{string.Join(", ", fileSummaries)}]");
continue;
}
if (arg is Stream stream)
{
entries.Add($"{param.Name}: [Stream] Length={(stream.CanSeek ? stream.Length : -1)} bytes");
continue;
}
try
{
var json = JsonSerializer.Serialize(arg);
entries.Add($"{param.Name}: {json}");
}
catch
{
entries.Add($"{param.Name}: [Unserializable]");
}
}
return [.. entries];
}
private Guid ResolveUserId()
{
var user = httpContextAccessor.HttpContext?.User;
var sub = user?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? user?.FindFirst("sub")?.Value;
return Guid.TryParse(sub, out var guid) ? guid : Guid.Empty;
}
}
@@ -0,0 +1,136 @@
using PostFundManagement.Domain.Attributes;
namespace PostFundManagement.Application.Interceptors;
public sealed class LoggingInterceptor(ILogger<LoggingInterceptor> logger) : IAsyncInterceptor
{
public void InterceptSynchronous(IInvocation invocation)
{
invocation.Proceed();
CheckAndLogIfFailed(invocation, invocation.ReturnValue);
}
public void InterceptAsynchronous(IInvocation invocation) =>
invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
public void InterceptAsynchronous<TResult>(IInvocation invocation) =>
invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
private static async Task InternalInterceptAsynchronous(IInvocation invocation)
{
invocation.Proceed();
var task = (Task)invocation.ReturnValue;
await task.ConfigureAwait(false);
}
private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
{
invocation.Proceed();
var task = (Task<TResult>)invocation.ReturnValue;
var resultObj = await task.ConfigureAwait(false);
var method = invocation.MethodInvocationTarget ?? invocation.Method;
var auditAttr = method.GetCustomAttribute<LogAttribute>()
?? invocation.Method.GetCustomAttribute<LogAttribute>();
if (auditAttr == null) return resultObj;
CheckAndLogIfFailed(invocation, resultObj);
return resultObj;
}
private void CheckAndLogIfFailed(IInvocation invocation, object? resultObj)
{
if (resultObj is IResultBase result && result.IsFailed)
{
try
{
var method = invocation.MethodInvocationTarget ?? invocation.Method;
var parameters = BuildPayloadSummary(method.GetParameters(), invocation.Arguments);
var errorMessages = new List<string>();
if (result.Reasons != null)
{
foreach (var reason in result.Reasons)
if (reason is IError error)
errorMessages.Add(error.Message);
}
var errorsString = string.Join("; ", errorMessages);
logger.LogError("Method {MethodName} failed. Errors: [{ErrorType}] {Errors}. Parameters: {@Parameters}",
method.Name, result.Reasons?.FirstOrDefault()?.GetType().Name.Replace("Error", string.Empty), errorsString, parameters);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to write error log for method {MethodName}.", invocation.Method.Name);
}
}
}
private static string[] BuildPayloadSummary(ParameterInfo[] parameters, object[] args)
{
var entries = new List<string>();
for (var i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var arg = args[i];
if (param.ParameterType == typeof(CancellationToken))
continue;
if (arg == null)
{
entries.Add($"{param.Name}: null");
continue;
}
if (arg is IFormFile file)
{
entries.Add($"{param.Name}: [File] Name='{file.FileName}', Size={file.Length} bytes, Type='{file.ContentType}'");
continue;
}
if (arg is IEnumerable<IFormFile> files)
{
var fileSummaries = new List<string>();
foreach (var f in files)
fileSummaries.Add($"'{f.FileName}' ({f.Length} bytes)");
entries.Add($"{param.Name}: [Files] count={fileSummaries.Count} [{string.Join(", ", fileSummaries)}]");
continue;
}
if (arg is Stream stream)
{
entries.Add($"{param.Name}: [Stream] Length={(stream.CanSeek ? stream.Length : -1)} bytes");
continue;
}
try
{
var json = JsonSerializer.Serialize(arg);
entries.Add($"{param.Name}: {json}");
}
catch
{
entries.Add($"{param.Name}: [Unserializable]");
}
}
return [.. entries];
}
}

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