diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index a5bd04f..cf88d90 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -58,7 +58,7 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | dotnet tool install --global dotnet-sonarscanner - dotnet-sonarscanner begin /k:"CorruptComputer_Bones" /o:"corruptcomputer" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=./Tests/*/*.UnitTests/TestResults/*/coverage.opencover.xml /d:sonar.scanner.scanAll=false + dotnet-sonarscanner begin /k:"CorruptComputer_Bones" /o:"corruptcomputer" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths=./Tests/*/*.UnitTests/TestResults/*/coverage.opencover.xml /d:sonar.scanner.scanAll=false /d:sonar.exclusions="**/Bones.Database/Migrations/**,**/AutoGen*.cs" dotnet build --configuration Debug --no-incremental dotnet test --no-build --collect:"XPlat Code Coverage;Format=opencover" dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/Backend/Bones.Api/Controllers/AccountController.Responses.cs b/Backend/Bones.Api/Controllers/AccountController.Responses.cs index 7efd4bd..ad4beb6 100644 --- a/Backend/Bones.Api/Controllers/AccountController.Responses.cs +++ b/Backend/Bones.Api/Controllers/AccountController.Responses.cs @@ -4,15 +4,6 @@ namespace Bones.Api.Controllers; public sealed partial class AccountController { - /// - /// - /// - /// - /// - [Serializable] - [JsonSerializable(typeof(GetMyBasicInfoResponse))] - public record GetMyBasicInfoResponse(string Email, string DisplayName); - /// /// /// @@ -21,7 +12,8 @@ public record GetMyBasicInfoResponse(string Email, string DisplayName); /// /// /// + /// [Serializable] [JsonSerializable(typeof(GetMyProfileResponse))] - public record GetMyProfileResponse(string Email, bool EmailConfirmed, DateTimeOffset? EmailConfirmedDateTime, string DisplayName, DateTimeOffset CreateDateTime); + public record GetMyProfileResponse(string Email, bool EmailConfirmed, DateTimeOffset? EmailConfirmedDateTime, string DisplayName, DateTimeOffset CreateDateTime, bool IsSysAdmin); } \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/AccountController.cs b/Backend/Bones.Api/Controllers/AccountController.cs index e1bed68..ca6c99f 100644 --- a/Backend/Bones.Api/Controllers/AccountController.cs +++ b/Backend/Bones.Api/Controllers/AccountController.cs @@ -1,4 +1,5 @@ using Bones.Database.DbSets.AccountManagement; +using Bones.Shared.Consts; using Microsoft.AspNetCore.Mvc; namespace Bones.Api.Controllers; @@ -13,38 +14,23 @@ namespace Bones.Api.Controllers; /// public sealed partial class AccountController(ISender sender) : BonesControllerBase(sender) { - /// - /// - /// - /// - [HttpGet("my/basic-info", Name = "GetMyBasicInfoAsync")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task GetMyBasicInfoAsync() - { - BonesUser user = await GetCurrentBonesUserAsync(); - - return Ok(new GetMyBasicInfoResponse( - user.Email ?? string.Empty, - user.DisplayName ?? user.Email ?? string.Empty)); - } - /// /// Returns a users own full profile info /// /// [HttpGet("my/profile", Name = "GetMyProfileAsync")] [ProducesResponseType(StatusCodes.Status200OK)] - public async ValueTask GetMyProfileAsync() + public async ValueTask> GetMyProfileAsync() { BonesUser user = await GetCurrentBonesUserAsync(); - - return Ok(new GetMyProfileResponse( + return new GetMyProfileResponse( user.Email ?? string.Empty, user.EmailConfirmed, user.EmailConfirmedDateTime, user.DisplayName ?? user.Email ?? string.Empty, - user.CreateDateTime - )); + user.CreateDateTime, + IsSysAdmin: User.IsInRole(SystemRoles.SYSTEM_ADMINISTRATORS) + ); } } \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/AnonymousController.cs b/Backend/Bones.Api/Controllers/AnonymousController.cs index fade576..67c867c 100644 --- a/Backend/Bones.Api/Controllers/AnonymousController.cs +++ b/Backend/Bones.Api/Controllers/AnonymousController.cs @@ -26,7 +26,7 @@ public sealed partial class AnonymousController(ISender sender) : BonesControlle [HttpPost("register", Name = "RegisterAsync")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] - public async Task RegisterAsync([FromBody] RegisterUserApiRequest registration) + public async ValueTask> RegisterAsync([FromBody] RegisterUserApiRequest registration) { QueryResponse result = await Sender.Send(new RegisterUserQuery(registration.Email, registration.Password)); @@ -35,7 +35,7 @@ public async Task RegisterAsync([FromBody] RegisterUserApiRequest return BadRequest(ReadErrorsFromIdentityResult(result.Result ?? IdentityResult.Failed())); } - return Ok(EmptyResponse.Value); + return EmptyResponse.Value; } /// @@ -47,7 +47,7 @@ public async Task RegisterAsync([FromBody] RegisterUserApiRequest /// [HttpGet("confirm-email", Name = "ConfirmEmailAsync")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ConfirmEmailAsync([FromQuery][Required] Guid userId, [FromQuery][Required] string code, [FromQuery] string? changedEmail) + public async ValueTask> ConfirmEmailAsync([FromQuery][Required] Guid userId, [FromQuery][Required] string code, [FromQuery] string? changedEmail) { QueryResponse result = await Sender.Send(new ConfirmEmailQuery(userId, code, changedEmail)); @@ -56,7 +56,7 @@ public async Task ConfirmEmailAsync([FromQuery][Required] Guid use return Unauthorized(EmptyResponse.Value); } - return Ok(EmptyResponse.Value); + return EmptyResponse.Value; } /// @@ -66,7 +66,7 @@ public async Task ConfirmEmailAsync([FromQuery][Required] Guid use /// [HttpPost("resend-confirmation-email", Name = "ResendConfirmationEmailAsync")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ResendConfirmationEmailAsync([FromQuery][Required] string email) + public async ValueTask> ResendConfirmationEmailAsync([FromQuery][Required] string email) { CommandResponse result = await Sender.Send(new QueueResendConfirmationEmailCommand(email)); @@ -75,7 +75,7 @@ public async Task ResendConfirmationEmailAsync([FromQuery][Require return Unauthorized(EmptyResponse.Value); } - return Ok(EmptyResponse.Value); + return EmptyResponse.Value; } /// @@ -85,10 +85,10 @@ public async Task ResendConfirmationEmailAsync([FromQuery][Require /// [HttpPost("forgot-password", Name = "ForgotPasswordAsync")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ForgotPasswordAsync([FromQuery][Required] string email) + public async ValueTask> ForgotPasswordAsync([FromQuery][Required] string email) { await Sender.Send(new QueueForgotPasswordEmailCommand(email)); - return Ok(EmptyResponse.Value); + return EmptyResponse.Value; } } \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/BonesControllerBase.cs b/Backend/Bones.Api/Controllers/BonesControllerBase.cs index 6bfccf5..7a2d62e 100644 --- a/Backend/Bones.Api/Controllers/BonesControllerBase.cs +++ b/Backend/Bones.Api/Controllers/BonesControllerBase.cs @@ -18,9 +18,9 @@ namespace Bones.Api.Controllers; [Route("[controller]")] [Consumes(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json)] -[ProducesResponseType(StatusCodes.Status401Unauthorized)] -[ProducesResponseType(StatusCodes.Status403Forbidden)] -[ProducesResponseType(StatusCodes.Status500InternalServerError)] +[ProducesResponseType(StatusCodes.Status401Unauthorized)] +[ProducesResponseType(StatusCodes.Status403Forbidden)] +[ProducesResponseType(StatusCodes.Status500InternalServerError)] public class BonesControllerBase(ISender sender) : ControllerBase { /// diff --git a/Backend/Bones.Api/Controllers/LoginController.cs b/Backend/Bones.Api/Controllers/LoginController.cs index 11a1c0c..858026d 100644 --- a/Backend/Bones.Api/Controllers/LoginController.cs +++ b/Backend/Bones.Api/Controllers/LoginController.cs @@ -27,7 +27,7 @@ public sealed partial class LoginController(SignInManager signInManag [HttpPost("login", Name = "LoginAsync")] [ProducesResponseType(StatusCodes.Status200OK)] [AllowAnonymous] - public async Task LoginAsync([FromBody] LoginUserApiRequest login) + public async ValueTask> LoginAsync([FromBody] LoginUserApiRequest login) { signInManager.AuthenticationScheme = IdentityConstants.ApplicationScheme; @@ -56,7 +56,7 @@ public async Task LoginAsync([FromBody] LoginUserApiRequest login) return Unauthorized(EmptyResponse.Value); } - return Ok(EmptyResponse.Value); + return EmptyResponse.Value; } /// @@ -66,7 +66,7 @@ public async Task LoginAsync([FromBody] LoginUserApiRequest login) [HttpPost("logout", Name = "LogoutAsync")] [ProducesResponseType(StatusCodes.Status200OK)] [AllowAnonymous] - public ActionResult LogoutAsync() + public ActionResult LogoutAsync() { Response.Cookies.Append(".AspNetCore.Identity.Application", string.Empty, new() { @@ -75,6 +75,6 @@ public ActionResult LogoutAsync() Expires = DateTimeOffset.Now.AddDays(-1) }); - return Ok(EmptyResponse.Value); + return EmptyResponse.Value; } } \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/OrganizationController.cs b/Backend/Bones.Api/Controllers/OrganizationController.cs new file mode 100644 index 0000000..0ba6a03 --- /dev/null +++ b/Backend/Bones.Api/Controllers/OrganizationController.cs @@ -0,0 +1,9 @@ +namespace Bones.Api.Controllers; + +/// +/// Handles organizations +/// +public class OrganizationController(ISender sender) : BonesControllerBase(sender) +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/ProjectController.cs b/Backend/Bones.Api/Controllers/ProjectController.cs index beb959f..f43b0e7 100644 --- a/Backend/Bones.Api/Controllers/ProjectController.cs +++ b/Backend/Bones.Api/Controllers/ProjectController.cs @@ -26,8 +26,9 @@ public sealed partial class ProjectController(ISender sender) : BonesControllerB /// Ok with the results if successful, otherwise BadRequest with a message of what went wrong. [HttpGet("projects/by-owner", Name = "GetProjectsByOwnerAsync")] [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] - public async Task GetProjectsByOwnerAsync([FromBody] GetProjectsByOwnerRequest request) + public async ValueTask>> GetProjectsByOwnerAsync([FromBody] GetProjectsByOwnerRequest request) { BonesUser currentUser = await GetCurrentBonesUserAsync(); QueryResponse> response = await Sender.Send(new GetProjectsByOwnerQuery(request.OwnerType, request.OrganizationId ?? currentUser.Id, currentUser)); @@ -36,7 +37,12 @@ public async Task GetProjectsByOwnerAsync([FromBody] GetProjectsBy return BadRequest(response.FailureReasons); } - return Ok(response.Result); + if (response.Result == null || response.Result.Count == 0) + { + return NoContent(); + } + + return response.Result; } /// @@ -45,9 +51,9 @@ public async Task GetProjectsByOwnerAsync([FromBody] GetProjectsBy /// Ok with the results if successful, otherwise BadRequest with a message of what went wrong. [HttpGet("projects/quick-select", Name = "GetProjectQuickSelectAsync")] [ProducesResponseType>(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] - public async Task GetProjectQuickSelectAsync() + public async ValueTask>> GetProjectQuickSelectAsync() { BonesUser currentUser = await GetCurrentBonesUserAsync(); @@ -63,7 +69,7 @@ public async Task GetProjectQuickSelectAsync() return NotFound(EmptyResponse.Value); } - return Ok(response.Result.Select(kvp => new GetProjectQuickSelectResponse(kvp.Key, kvp.Value))); + return response.Result.Select(kvp => new GetProjectQuickSelectResponse(kvp.Key, kvp.Value)).ToList(); } /// @@ -74,7 +80,7 @@ public async Task GetProjectQuickSelectAsync() [HttpGet("{projectId:guid}/dashboard", Name = "GetProjectDashboardAsync")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] - public async Task GetProjectDashboardAsync(Guid projectId) + public async ValueTask> GetProjectDashboardAsync(Guid projectId) { BonesUser currentUser = await GetCurrentBonesUserAsync(); QueryResponse projectResponse = await Sender.Send(new GetProjectByIdQuery(projectId, currentUser)); @@ -103,7 +109,7 @@ public async Task GetProjectDashboardAsync(Guid projectId) initiativesResponse.Result.Count, initiativesResponse.Result.Select(i => new InitiativeListModel(i.Id, i.Name, i.Queues.Count)).ToList()); - return Ok(resp); + return resp; } #endregion @@ -116,7 +122,7 @@ public async Task GetProjectDashboardAsync(Guid projectId) [HttpPost("create", Name = "CreateProjectAsync")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateProjectAsync([FromBody] CreateProjectRequest request) + public async ValueTask> CreateProjectAsync([FromBody] CreateProjectRequest request) { CommandResponse response = await Sender.Send(new CreateProjectCommand(request.Name, await GetCurrentBonesUserAsync(), request.OrganizationId)); if (!response.Success) @@ -124,7 +130,7 @@ public async Task CreateProjectAsync([FromBody] CreateProjectReque return BadRequest(ErrorResponse.FromCommandResponse(response)); } - return Ok(response.Id); + return response.Id ?? Guid.Empty; } /// @@ -136,7 +142,7 @@ public async Task CreateProjectAsync([FromBody] CreateProjectReque [HttpPost("{projectId:guid}/initiative/create", Name = "CreateInitiativeAsync")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateInitiativeAsync(Guid projectId, [FromBody] CreateInitiativeRequest request) + public async ValueTask> CreateInitiativeAsync(Guid projectId, [FromBody] CreateInitiativeRequest request) { CommandResponse response = await Sender.Send(new CreateInitiativeCommand(request.Name, projectId, await GetCurrentBonesUserAsync())); if (!response.Success) @@ -144,7 +150,7 @@ public async Task CreateInitiativeAsync(Guid projectId, [FromBody] return BadRequest(ErrorResponse.FromCommandResponse(response)); } - return Ok(response.Id); + return response.Id ?? Guid.Empty; } #endregion diff --git a/Backend/Bones.Api/Controllers/SysAdminController.cs b/Backend/Bones.Api/Controllers/SysAdminController.cs new file mode 100644 index 0000000..9236163 --- /dev/null +++ b/Backend/Bones.Api/Controllers/SysAdminController.cs @@ -0,0 +1,24 @@ +using Bones.Api.Models; +using Bones.Shared.Consts; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bones.Api.Controllers; + +/// +/// Handles SysAdmin stuffs +/// +[Authorize(Roles = SystemRoles.SYSTEM_ADMINISTRATORS)] +public class SysAdminController(ISender sender) : BonesControllerBase(sender) +{ + /// + /// Ping, pong! + /// + /// Super secret passphrase that should absolutely never be shared under any circumstances. + [HttpPost("ping", Name = "PingAsync")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult PingAsync() + { + return "pong"; + } +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/WorkItemController.cs b/Backend/Bones.Api/Controllers/WorkItemController.cs new file mode 100644 index 0000000..cd6f80e --- /dev/null +++ b/Backend/Bones.Api/Controllers/WorkItemController.cs @@ -0,0 +1,10 @@ +namespace Bones.Api.Controllers; + +/// +/// Handles work items +/// +/// +public class WorkItemController(ISender sender) : BonesControllerBase(sender) +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Api/Handlers/ApiExceptionHandler.cs b/Backend/Bones.Api/Handlers/ApiExceptionHandler.cs index f2981cf..3c2160e 100644 --- a/Backend/Bones.Api/Handlers/ApiExceptionHandler.cs +++ b/Backend/Bones.Api/Handlers/ApiExceptionHandler.cs @@ -1,3 +1,6 @@ +using System.Text.Json; +using Bones.Api.Models; +using Bones.Shared.Backend.Models; using Bones.Shared.Exceptions; using Bones.Shared.Extensions; using Microsoft.AspNetCore.Diagnostics; @@ -5,30 +8,38 @@ namespace Bones.Api.Handlers; /// -/// +/// Handles exceptions thrown by the API before they are returned to the user /// public class ApiExceptionHandler : IExceptionHandler { - /// - /// Handles exceptions that bubble up to the API - /// - /// - /// - /// - /// + /// public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { if (exception is ForbiddenAccessException) { httpContext.Response.StatusCode = StatusCodes.Status403Forbidden; httpContext.Response.ContentType = "application/json"; - httpContext.Response.Body = await "{}".ToStreamAsync(cancellationToken); + httpContext.Response.Body = await JsonSerializer.Serialize(new ErrorResponse() + { + Errors = new() + { + { BonesResponseBase.GENERIC_SERVER_ERROR_KEY, [BonesResponseBase.FORBIDDEN_ERROR_VALUE] } + } + }).ToStreamAsync(cancellationToken); + return true; } httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; httpContext.Response.ContentType = "application/json"; - httpContext.Response.Body = await "{}".ToStreamAsync(cancellationToken); + httpContext.Response.Body = await JsonSerializer.Serialize(new ErrorResponse() + { + Errors = new() + { + { BonesResponseBase.GENERIC_SERVER_ERROR_KEY, [exception.Message] } + } + }).ToStreamAsync(cancellationToken); + return true; } } \ No newline at end of file diff --git a/Backend/Bones.Api/Models/EmptyResponse.cs b/Backend/Bones.Api/Models/EmptyResponse.cs index ffd9565..432efe2 100644 --- a/Backend/Bones.Api/Models/EmptyResponse.cs +++ b/Backend/Bones.Api/Models/EmptyResponse.cs @@ -14,5 +14,6 @@ private EmptyResponse() { } /// /// Gets a new empty response /// + [JsonIgnore] public static readonly EmptyResponse Value = new(); } \ No newline at end of file diff --git a/Backend/Bones.Api/Models/ErrorResponse.cs b/Backend/Bones.Api/Models/ErrorResponse.cs index b276998..d148c29 100644 --- a/Backend/Bones.Api/Models/ErrorResponse.cs +++ b/Backend/Bones.Api/Models/ErrorResponse.cs @@ -10,7 +10,11 @@ namespace Bones.Api.Models; [JsonSerializable(typeof(ErrorResponse))] public sealed record ErrorResponse { - private ErrorResponse(Dictionary>? errors) + /// + /// + /// + /// + public ErrorResponse(Dictionary>? errors = null) { Errors = errors; } diff --git a/Backend/Bones.Api/OpenApi/swagger.json b/Backend/Bones.Api/OpenApi/swagger.json index 7adb7e9..2e76fec 100644 --- a/Backend/Bones.Api/OpenApi/swagger.json +++ b/Backend/Bones.Api/OpenApi/swagger.json @@ -14,57 +14,6 @@ "version": "develop" }, "paths": { - "/Account/my/basic-info": { - "get": { - "tags": [ - "Account" - ], - "summary": "", - "operationId": "GetMyBasicInfoAsync", - "responses": { - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmptyResponse" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmptyResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmptyResponse" - } - } - } - }, - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetMyBasicInfoResponse" - } - } - } - } - } - } - }, "/Account/my/profile": { "get": { "tags": [ @@ -78,7 +27,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -88,7 +37,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -98,7 +47,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -139,7 +88,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -149,7 +98,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -159,7 +108,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -235,7 +184,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -245,7 +194,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -255,7 +204,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -297,7 +246,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -307,7 +256,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -317,7 +266,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -359,7 +308,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -369,7 +318,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -379,7 +328,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -420,7 +369,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -430,7 +379,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -440,7 +389,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -471,7 +420,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -481,7 +430,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -491,7 +440,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -532,7 +481,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -542,7 +491,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -552,7 +501,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -570,6 +519,9 @@ } } }, + "204": { + "description": "No Content" + }, "400": { "description": "Bad Request", "content": { @@ -602,7 +554,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -612,7 +564,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -622,7 +574,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -645,7 +597,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResult" + "$ref": "#/components/schemas/EmptyResponse" } } } @@ -694,7 +646,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -704,7 +656,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -714,7 +666,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -771,7 +723,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -781,7 +733,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -791,7 +743,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -855,7 +807,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/UnauthorizedResult" } } } @@ -865,7 +817,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -875,7 +827,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponse" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -903,6 +855,57 @@ } } } + }, + "/SysAdmin/ping": { + "post": { + "tags": [ + "SysAdmin" + ], + "summary": "Ping, pong!", + "operationId": "PingAsync", + "responses": { + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } } }, "components": { @@ -942,10 +945,6 @@ "additionalProperties": false, "description": "The response body is empty, this is a workaround for the limitations of the API client." }, - "EmptyResult": { - "type": "object", - "additionalProperties": false - }, "ErrorResponse": { "type": "object", "properties": { @@ -964,23 +963,6 @@ "additionalProperties": false, "description": "The response body is empty, this is a workaround for the limitations of the API client." }, - "GetMyBasicInfoResponse": { - "type": "object", - "properties": { - "email": { - "type": "string", - "description": "", - "nullable": true - }, - "displayName": { - "type": "string", - "description": "", - "nullable": true - } - }, - "additionalProperties": false, - "description": "" - }, "GetMyProfileResponse": { "type": "object", "properties": { @@ -1008,6 +990,10 @@ "type": "string", "description": "", "format": "date-time" + }, + "isSysAdmin": { + "type": "boolean", + "description": "" } }, "additionalProperties": false, @@ -1156,6 +1142,16 @@ }, "additionalProperties": false, "description": "Request to register a new user" + }, + "UnauthorizedResult": { + "type": "object", + "properties": { + "statusCode": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false } } } diff --git a/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs b/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs index 716c6ef..9c87d27 100644 --- a/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs +++ b/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs @@ -30,4 +30,19 @@ protected BonesResponseBase() { } /// If the command failed, why? /// public Dictionary>? FailureReasons { get; init; } + + /// + /// It's from the server, not a validation issue with the query + /// + public const string GENERIC_SERVER_ERROR_KEY = "server"; + + /// + /// Forbidden access was detected + /// + public const string FORBIDDEN_ERROR_VALUE = "Forbidden."; + + /// + /// Something went wrong, and we don't know what, oops! + /// + public const string UNKNOWN_ERROR_VALUE = "Unknown failure reason"; } \ No newline at end of file diff --git a/Backend/Bones.Shared.Backend/Models/CommandResponse.cs b/Backend/Bones.Shared.Backend/Models/CommandResponse.cs index 08e7521..5e96482 100644 --- a/Backend/Bones.Shared.Backend/Models/CommandResponse.cs +++ b/Backend/Bones.Shared.Backend/Models/CommandResponse.cs @@ -48,7 +48,7 @@ private CommandResponse() { } Success = false, FailureReasons = new() { - { "server", [ failureReason ?? "Unknown failure reason" ] } + { GENERIC_SERVER_ERROR_KEY, [ failureReason ?? UNKNOWN_ERROR_VALUE ] } } }; @@ -61,7 +61,7 @@ private CommandResponse() { } Success = false, FailureReasons = new() { - {"server", [ "Forbidden." ] } + { GENERIC_SERVER_ERROR_KEY, [ FORBIDDEN_ERROR_VALUE ] } }, Forbidden = true }; diff --git a/Backend/Bones.Shared.Backend/Models/QueryResponse.cs b/Backend/Bones.Shared.Backend/Models/QueryResponse.cs index 263ef33..1747e07 100644 --- a/Backend/Bones.Shared.Backend/Models/QueryResponse.cs +++ b/Backend/Bones.Shared.Backend/Models/QueryResponse.cs @@ -51,7 +51,7 @@ private QueryResponse() { } Success = false, FailureReasons = new() { - { "server", [ failureReason ?? "Unknown failure reason" ] } + { GENERIC_SERVER_ERROR_KEY, [ failureReason ?? UNKNOWN_ERROR_VALUE ] } } }; @@ -64,7 +64,7 @@ private QueryResponse() { } Success = false, FailureReasons = new() { - {"server", [ "Forbidden." ] } + { GENERIC_SERVER_ERROR_KEY, [ FORBIDDEN_ERROR_VALUE ] } }, Forbidden = true }; diff --git a/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs b/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs index 609b971..7cbcaf2 100644 --- a/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs +++ b/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs @@ -54,107 +54,6 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetMyBasicInfoAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - - // Operation Path: "Account/my/basic-info" - urlBuilder_.Append("Account/my/basic-info"); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 403) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// /// Returns a users own full profile info @@ -202,32 +101,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -311,32 +210,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -439,32 +338,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -550,32 +449,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -661,32 +560,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -770,32 +669,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -872,32 +771,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -981,32 +880,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -1019,6 +918,12 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() return objectResponse_.Object; } else + if (status_ == 204) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("No Content", status_, responseText_, headers_, null); + } + else if (status_ == 400) { var objectResponse_ = await ReadObjectResponseAsync>>(response_, headers_, cancellationToken).ConfigureAwait(false); @@ -1095,32 +1000,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -1224,32 +1129,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -1343,32 +1248,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -1468,32 +1373,32 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() var status_ = (int)response_.StatusCode; if (status_ == 401) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 403) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 200) @@ -1535,6 +1440,111 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() } } + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Ping, pong! + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task PingAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + + // Operation Path: "SysAdmin/ping" + urlBuilder_.Append("SysAdmin/ping"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 403) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Forbidden", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Internal Server Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + protected struct ObjectResponseResult { public ObjectResponseResult(T responseObject, string responseText) @@ -1696,18 +1706,6 @@ public partial record ErrorResponse } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial record GetMyBasicInfoResponse - { - - [System.Text.Json.Serialization.JsonPropertyName("email")] - public string Email { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("displayName")] - public string DisplayName { get; set; } - - } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial record GetMyProfileResponse { @@ -1727,6 +1725,9 @@ public partial record GetMyProfileResponse [System.Text.Json.Serialization.JsonPropertyName("createDateTime")] public System.DateTimeOffset? CreateDateTime { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("isSysAdmin")] + public bool? IsSysAdmin { get; set; } + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] @@ -1872,6 +1873,15 @@ public partial record RegisterUserApiRequest } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial record UnauthorizedResult + { + + [System.Text.Json.Serialization.JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } + + } + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] diff --git a/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs b/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs index 90a34ec..6d9b7f8 100644 --- a/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs +++ b/Frontend/Bones.WebUI/Infrastructure/BonesAuthenticationStateProvider.cs @@ -14,18 +14,23 @@ public class BonesAuthenticationStateProvider(LocalStorageService localStorageSe /// public override async Task GetAuthenticationStateAsync() { - GetMyBasicInfoResponse? currentUser = await GetCurrentUserAsync(CancellationToken.None); + GetMyProfileResponse? currentUser = await GetCurrentUserAsync(CancellationToken.None); if (currentUser == null) { return new(new()); } - Claim[] claims = [ - new(BonesClaimTypes.User.EMAIL, currentUser.Email ?? string.Empty), - new(BonesClaimTypes.User.DISPLAY_NAME, currentUser.DisplayName ?? string.Empty) + List claims = [ + new(BonesClaimTypes.User.EMAIL, currentUser.Email), + new(BonesClaimTypes.User.DISPLAY_NAME, currentUser.DisplayName) ]; + if (currentUser.IsSysAdmin ?? false) + { + claims.Add(new(BonesClaimTypes.Role.System.SYSTEM_ADMINISTRATOR, ClaimValues.YES)); + } + return new(new(new ClaimsIdentity(claims, authenticationType: nameof(BonesAuthenticationStateProvider)))); } @@ -35,7 +40,7 @@ public override async Task GetAuthenticationStateAsync() /// /// /// - public async Task SetCurrentUserAsync(GetMyBasicInfoResponse currentUser, CancellationToken cancellationToken) + public async Task SetCurrentUserAsync(GetMyProfileResponse currentUser, CancellationToken cancellationToken) { await localStorageService.SetItemAsync(LocalStorageService.CURRENT_USER_KEY, currentUser, cancellationToken); @@ -59,8 +64,8 @@ public async Task ClearCurrentUserAsync(CancellationToken cancellationToken) /// /// /// - public Task GetCurrentUserAsync(CancellationToken cancellationToken) + public Task GetCurrentUserAsync(CancellationToken cancellationToken) { - return localStorageService.GetItemAsync(LocalStorageService.CURRENT_USER_KEY, cancellationToken); + return localStorageService.GetItemAsync(LocalStorageService.CURRENT_USER_KEY, cancellationToken); } } \ No newline at end of file diff --git a/Frontend/Bones.WebUI/Layout/SysAdminLayout.razor b/Frontend/Bones.WebUI/Layout/SysAdminLayout.razor new file mode 100644 index 0000000..1b8d5d3 --- /dev/null +++ b/Frontend/Bones.WebUI/Layout/SysAdminLayout.razor @@ -0,0 +1,12 @@ +@using Bones.WebUI.Components.Auth +@layout MainLayout +@inherits LayoutComponentBase + + + + @Body + + + + + \ No newline at end of file diff --git a/Frontend/Bones.WebUI/Pages/Account/LoginPage.razor.cs b/Frontend/Bones.WebUI/Pages/Account/LoginPage.razor.cs index c716289..8d05f12 100644 --- a/Frontend/Bones.WebUI/Pages/Account/LoginPage.razor.cs +++ b/Frontend/Bones.WebUI/Pages/Account/LoginPage.razor.cs @@ -49,7 +49,7 @@ await ApiClient.LoginAsync(new() }).ConfigureAwait(true); // Now refresh the Authentication State: - GetMyBasicInfoResponse? me = await ApiClient.GetMyBasicInfoAsync().ConfigureAwait(true); + GetMyProfileResponse? me = await ApiClient.GetMyProfileAsync().ConfigureAwait(true); if (me == null) { ErrorLoggingIn = true; @@ -58,7 +58,7 @@ await ApiClient.LoginAsync(new() await AuthStateProvider.SetCurrentUserAsync(me, CancellationToken.None); - NavManager.NavigateTo(GetNavigationUrl()); + NavManager.NavigateTo(GetNavigationUrl(), forceLoad: true); } catch (Exception ex) {