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