diff --git a/Backend/Bones.Api/Bones.Api.csproj b/Backend/Bones.Api/Bones.Api.csproj index 7fc6bcb..3478dd9 100644 --- a/Backend/Bones.Api/Bones.Api.csproj +++ b/Backend/Bones.Api/Bones.Api.csproj @@ -46,4 +46,31 @@ + + + + ProjectController.cs + + + ProjectController.cs + + + LoginController.cs + + + LoginController.cs + + + AnonymousController.cs + + + AnonymousController.cs + + + AccountController.cs + + + AccountController.cs + + diff --git a/Backend/Bones.Api/Controllers/AccountController.Requests.cs b/Backend/Bones.Api/Controllers/AccountController.Requests.cs new file mode 100644 index 0000000..6e72a8c --- /dev/null +++ b/Backend/Bones.Api/Controllers/AccountController.Requests.cs @@ -0,0 +1,6 @@ +namespace Bones.Api.Controllers; + +public sealed partial class AccountController +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/AccountController.Responses.cs b/Backend/Bones.Api/Controllers/AccountController.Responses.cs new file mode 100644 index 0000000..7efd4bd --- /dev/null +++ b/Backend/Bones.Api/Controllers/AccountController.Responses.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace Bones.Api.Controllers; + +public sealed partial class AccountController +{ + /// + /// + /// + /// + /// + [Serializable] + [JsonSerializable(typeof(GetMyBasicInfoResponse))] + public record GetMyBasicInfoResponse(string Email, string DisplayName); + + /// + /// + /// + /// + /// + /// + /// + /// + [Serializable] + [JsonSerializable(typeof(GetMyProfileResponse))] + public record GetMyProfileResponse(string Email, bool EmailConfirmed, DateTimeOffset? EmailConfirmedDateTime, string DisplayName, DateTimeOffset CreateDateTime); +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/AccountController.cs b/Backend/Bones.Api/Controllers/AccountController.cs index 9a8f952..e1bed68 100644 --- a/Backend/Bones.Api/Controllers/AccountController.cs +++ b/Backend/Bones.Api/Controllers/AccountController.cs @@ -11,15 +11,8 @@ namespace Bones.Api.Controllers; /// Created using this as a reference: /// https://github.com/dotnet/aspnetcore/blob/main/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs /// -public sealed class AccountController(ISender sender) : BonesControllerBase(sender) +public sealed partial class AccountController(ISender sender) : BonesControllerBase(sender) { - /// - /// - /// - /// - /// - public record GetMyBasicInfoResponse(string Email, string DisplayName); - /// /// /// @@ -35,16 +28,6 @@ public async Task GetMyBasicInfoAsync() user.DisplayName ?? user.Email ?? string.Empty)); } - /// - /// - /// - /// - /// - /// - /// - /// - public record GetMyProfileResponse(string Email, bool EmailConfirmed, DateTimeOffset? EmailConfirmedDateTime, string DisplayName, DateTimeOffset CreateDateTime); - /// /// Returns a users own full profile info /// diff --git a/Backend/Bones.Api/Controllers/AnonymousController.Requests.cs b/Backend/Bones.Api/Controllers/AnonymousController.Requests.cs new file mode 100644 index 0000000..3001a51 --- /dev/null +++ b/Backend/Bones.Api/Controllers/AnonymousController.Requests.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Bones.Api.Controllers; + +public sealed partial class AnonymousController +{ + /// + /// Request to register a new user + /// + /// Email, must be valid and unique + /// Password, must pass validation (1 upper, 1 lower, 1 number, 1 special character, and at least 8 characters long) + [Serializable] + [JsonSerializable(typeof(RegisterUserApiRequest))] + public sealed record RegisterUserApiRequest([Required] string Email, [Required] string Password); +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/AnonymousController.Responses.cs b/Backend/Bones.Api/Controllers/AnonymousController.Responses.cs new file mode 100644 index 0000000..26c57b8 --- /dev/null +++ b/Backend/Bones.Api/Controllers/AnonymousController.Responses.cs @@ -0,0 +1,6 @@ +namespace Bones.Api.Controllers; + +public sealed partial class AnonymousController +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/AnonymousController.cs b/Backend/Bones.Api/Controllers/AnonymousController.cs index 0a77f19..fade576 100644 --- a/Backend/Bones.Api/Controllers/AnonymousController.cs +++ b/Backend/Bones.Api/Controllers/AnonymousController.cs @@ -1,9 +1,9 @@ using System.ComponentModel.DataAnnotations; using Bones.Api.Models; -using Bones.Backend.Features.AccountManagement.ConfirmEmail; -using Bones.Backend.Features.AccountManagement.QueueForgotPasswordEmail; -using Bones.Backend.Features.AccountManagement.QueueResendConfirmationEmail; -using Bones.Backend.Features.AccountManagement.RegisterUser; +using Bones.Backend.Features.Accounts.ConfirmEmail; +using Bones.Backend.Features.Accounts.QueueForgotPasswordEmail; +using Bones.Backend.Features.Accounts.QueueResendConfirmationEmail; +using Bones.Backend.Features.Accounts.RegisterUser; using Bones.Shared.Backend.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -16,15 +16,8 @@ namespace Bones.Api.Controllers; /// /// MediatR sender [AllowAnonymous] -public class AnonymousController(ISender sender) : BonesControllerBase(sender) +public sealed partial class AnonymousController(ISender sender) : BonesControllerBase(sender) { - /// - /// Request to register a new user - /// - /// Email, must be valid and unique - /// Password, must pass validation (1 upper, 1 lower, 1 number, 1 special character, and at least 8 characters long) - public sealed record RegisterUserApiRequest([Required] string Email, [Required] string Password); - /// /// Registers a new user if all validations pass /// @@ -45,8 +38,6 @@ public async Task RegisterAsync([FromBody] RegisterUserApiRequest return Ok(EmptyResponse.Value); } - - /// /// Confirms a users email address /// diff --git a/Backend/Bones.Api/Controllers/BonesControllerBase.cs b/Backend/Bones.Api/Controllers/BonesControllerBase.cs index fda463a..6bfccf5 100644 --- a/Backend/Bones.Api/Controllers/BonesControllerBase.cs +++ b/Backend/Bones.Api/Controllers/BonesControllerBase.cs @@ -1,6 +1,6 @@ using System.Net.Mime; using Bones.Api.Models; -using Bones.Backend.Features.AccountManagement.GetUserByClaimsPrincipal; +using Bones.Backend.Features.Accounts.GetUserByClaimsPrincipal; using Bones.Database.DbSets.AccountManagement; using Bones.Shared.Exceptions; using Microsoft.AspNetCore.Authorization; diff --git a/Backend/Bones.Api/Controllers/LoginController.Requests.cs b/Backend/Bones.Api/Controllers/LoginController.Requests.cs new file mode 100644 index 0000000..1d8703f --- /dev/null +++ b/Backend/Bones.Api/Controllers/LoginController.Requests.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace Bones.Api.Controllers; + +public sealed partial class LoginController +{ + /// + /// Request to login + /// + /// The users email address + /// The users password + /// If they have 2fa, include the code here + /// If they have 2fa and can't use their authenticator, include a recovery code here + [Serializable] + [JsonSerializable(typeof(LoginUserApiRequest))] + public sealed record LoginUserApiRequest([Required] string Email, [Required] string Password, string? TwoFactorCode, string? TwoFactorRecoveryCode); +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/LoginController.Responses.cs b/Backend/Bones.Api/Controllers/LoginController.Responses.cs new file mode 100644 index 0000000..baac876 --- /dev/null +++ b/Backend/Bones.Api/Controllers/LoginController.Responses.cs @@ -0,0 +1,6 @@ +namespace Bones.Api.Controllers; + +public sealed partial class LoginController +{ + +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/LoginController.cs b/Backend/Bones.Api/Controllers/LoginController.cs index 91fbc8e..11a1c0c 100644 --- a/Backend/Bones.Api/Controllers/LoginController.cs +++ b/Backend/Bones.Api/Controllers/LoginController.cs @@ -17,17 +17,8 @@ namespace Bones.Api.Controllers; /// Created using this as a reference: /// https://github.com/dotnet/aspnetcore/blob/main/src/Identity/Core/src/IdentityApiEndpointRouteBuilderExtensions.cs /// -public class LoginController(SignInManager signInManager, ISender sender) : BonesControllerBase(sender) +public sealed partial class LoginController(SignInManager signInManager, ISender sender) : BonesControllerBase(sender) { - /// - /// Request to login - /// - /// The users email address - /// The users password - /// If they have 2fa, include the code here - /// If they have 2fa and can't use their authenticator, include a recovery code here - public sealed record LoginUserApiRequest([Required] string Email, [Required] string Password, string? TwoFactorCode, string? TwoFactorRecoveryCode); - /// /// Logs in a user, returns the active token as a cookie header if successful /// diff --git a/Backend/Bones.Api/Controllers/ProjectController.Requests.cs b/Backend/Bones.Api/Controllers/ProjectController.Requests.cs new file mode 100644 index 0000000..c4d8539 --- /dev/null +++ b/Backend/Bones.Api/Controllers/ProjectController.Requests.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; +using Bones.Shared.Backend.Enums; + +namespace Bones.Api.Controllers; + +public sealed partial class ProjectController +{ + /// + /// Request to create a new project + /// + /// Name of the project to create + /// Optionally the organization that this should be created under, if not specified will be created for the requesting user. + [Serializable] + [JsonSerializable(typeof(CreateProjectRequest))] + public record CreateProjectRequest([Required] string Name, Guid? OrganizationId = null); + + /// + /// Request to create a new initiative + /// + /// Name of the initiative to create + [Serializable] + [JsonSerializable(typeof(CreateInitiativeRequest))] + public record CreateInitiativeRequest([Required] string Name); + + /// + /// Request to get the projects for a given User/Organization + /// + /// OwnerType to get + /// Optionally the organization that this should be created under, if not specified will be created for the requesting user. + [Serializable] + [JsonSerializable(typeof(GetProjectsByOwnerRequest))] + public record GetProjectsByOwnerRequest([Required] OwnershipType OwnerType, Guid? OrganizationId = null); +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/ProjectController.Responses.cs b/Backend/Bones.Api/Controllers/ProjectController.Responses.cs new file mode 100644 index 0000000..af0d46a --- /dev/null +++ b/Backend/Bones.Api/Controllers/ProjectController.Responses.cs @@ -0,0 +1,53 @@ +using System.Text.Json.Serialization; +using Bones.Shared.Backend.Enums; + +namespace Bones.Api.Controllers; + +public sealed partial class ProjectController +{ + /// + /// + /// + /// + /// + [Serializable] + [JsonSerializable(typeof(GetProjectQuickSelectResponse))] + public record GetProjectQuickSelectResponse( + Guid ProjectId, + string ProjectName + ); + + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Serializable] + [JsonSerializable(typeof(GetProjectDashboardResponse))] + public record GetProjectDashboardResponse( + Guid ProjectId, + string ProjectName, + OwnershipType OwnerType, + Guid OwnerId, + int InitiativeCount, + List Initiatives + ); + + /// + /// + /// + /// + /// + /// + [Serializable] + [JsonSerializable(typeof(InitiativeListModel))] + public sealed record InitiativeListModel( + Guid InitiativeId, + string InitiativeName, + int QueueCount + ); +} \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/ProjectController.cs b/Backend/Bones.Api/Controllers/ProjectController.cs index 0ac6112..beb959f 100644 --- a/Backend/Bones.Api/Controllers/ProjectController.cs +++ b/Backend/Bones.Api/Controllers/ProjectController.cs @@ -1,10 +1,9 @@ -using System.ComponentModel.DataAnnotations; -using System.Text.Json.Serialization; using Bones.Api.Models; -using Bones.Backend.Features.ProjectManagement.Projects.CreateProject; -using Bones.Backend.Features.ProjectManagement.Projects.GetProjectById; -using Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; -using Bones.Backend.Features.ProjectManagement.Projects.GetProjectsUserCanAccess; +using Bones.Backend.Features.Projects.Initiatives; +using Bones.Backend.Features.Projects.Projects.CreateProject; +using Bones.Backend.Features.Projects.Projects.GetProjectById; +using Bones.Backend.Features.Projects.Projects.GetProjectsByOwner; +using Bones.Backend.Features.Projects.Projects.GetProjectsUserCanAccess; using Bones.Database.DbSets.AccountManagement; using Bones.Database.DbSets.ProjectManagement; using Bones.Shared.Backend.Enums; @@ -17,41 +16,9 @@ namespace Bones.Api.Controllers; /// Handles everything related to Managing Projects /// /// MediatR sender -public sealed class ProjectController(ISender sender) : BonesControllerBase(sender) +public sealed partial class ProjectController(ISender sender) : BonesControllerBase(sender) { - /// - /// Request to create a new project - /// - /// Name of the project to create - /// Optionally the organization that this should be created under, if not specified will be created for the requesting user. - public record CreateProjectRequest([Required] string Name, Guid? OrganizationId = null); - - /// - /// Creates a new project - /// - /// The request - /// Created if created, otherwise BadRequest with a message of what went wrong. - [HttpPost("create", Name = "CreateProjectAsync")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType>(StatusCodes.Status400BadRequest)] - public async Task CreateProjectAsync([FromBody] CreateProjectRequest request) - { - CommandResponse response = await Sender.Send(new CreateProjectCommand(request.Name, await GetCurrentBonesUserAsync(), request.OrganizationId)); - if (!response.Success) - { - return BadRequest(response.FailureReasons); - } - - return Ok(response.Id); - } - - /// - /// Request to get the projects for a given User/Organization - /// - /// OwnerType to get - /// Optionally the organization that this should be created under, if not specified will be created for the requesting user. - public record GetProjectsByOwnerRequest([Required] OwnershipType OwnerType, Guid? OrganizationId = null); - + #region GET /// /// Gets the projects for the current user, or specified organization /// @@ -73,41 +40,31 @@ public async Task GetProjectsByOwnerAsync([FromBody] GetProjectsBy } /// - /// Gets the projects the current user is able to access + /// Gets the users current quick select projects /// /// Ok with the results if successful, otherwise BadRequest with a message of what went wrong. - [HttpGet("projects", Name = "GetProjectsUserCanAccessAsync")] - [ProducesResponseType>(StatusCodes.Status200OK)] + [HttpGet("projects/quick-select", Name = "GetProjectQuickSelectAsync")] + [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType>(StatusCodes.Status400BadRequest)] - public async Task GetProjectsUserCanAccessAsync() + public async Task GetProjectQuickSelectAsync() { BonesUser currentUser = await GetCurrentBonesUserAsync(); + + // TODO: Make this customizable QueryResponse> response = await Sender.Send(new GetProjectsUserCanAccessQuery(currentUser)); if (!response.Success) { return BadRequest(response.FailureReasons); } - return Ok(response.Result); - } + if (response.Result is null) + { + return NotFound(EmptyResponse.Value); + } - /// - /// - /// - /// - /// - /// - /// - /// - [Serializable] - [JsonSerializable(typeof(GetProjectDashboardResponse))] - public record GetProjectDashboardResponse( - Guid ProjectId, - string ProjectName, - int InitiativeCount, - OwnershipType OwnerType, - Guid OwnerId - ); + return Ok(response.Result.Select(kvp => new GetProjectQuickSelectResponse(kvp.Key, kvp.Value))); + } /// /// Gets a projects dashboard information @@ -120,20 +77,82 @@ Guid OwnerId public async Task GetProjectDashboardAsync(Guid projectId) { BonesUser currentUser = await GetCurrentBonesUserAsync(); - QueryResponse response = await Sender.Send(new GetProjectByIdQuery(projectId, currentUser)); - if (!response.Success || response.Result == null) + QueryResponse projectResponse = await Sender.Send(new GetProjectByIdQuery(projectId, currentUser)); + QueryResponse> initiativesResponse = await Sender.Send(new GetInitiativesByProjectQuery(projectId, currentUser)); + + if (!projectResponse.Success || projectResponse.Result is null || !initiativesResponse.Success || initiativesResponse.Result is null) { - return BadRequest(response.FailureReasons); + return BadRequest(projectResponse.FailureReasons); + } + + if (!initiativesResponse.Success || initiativesResponse.Result is null) + { + return BadRequest(initiativesResponse.FailureReasons); } // We know they won't be null - Guid ownerId = response.Result.OwnerType == OwnershipType.User - ? response.Result.OwningUser!.Id - : response.Result.OwningOrganization!.Id; + Guid ownerId = projectResponse.Result.OwnerType == OwnershipType.User + ? projectResponse.Result.OwningUser!.Id + : projectResponse.Result.OwningOrganization!.Id; - GetProjectDashboardResponse resp = new(response.Result.Id, response.Result.Name, - response.Result.Initiatives.Count, response.Result.OwnerType, ownerId); + GetProjectDashboardResponse resp = new( + projectResponse.Result.Id, + projectResponse.Result.Name, + projectResponse.Result.OwnerType, + ownerId, + initiativesResponse.Result.Count, + initiativesResponse.Result.Select(i => new InitiativeListModel(i.Id, i.Name, i.Queues.Count)).ToList()); return Ok(resp); } + #endregion + + #region POST + /// + /// Creates a new project + /// + /// The request + /// Created if created, otherwise BadRequest with a message of what went wrong. + [HttpPost("create", Name = "CreateProjectAsync")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateProjectAsync([FromBody] CreateProjectRequest request) + { + CommandResponse response = await Sender.Send(new CreateProjectCommand(request.Name, await GetCurrentBonesUserAsync(), request.OrganizationId)); + if (!response.Success) + { + return BadRequest(ErrorResponse.FromCommandResponse(response)); + } + + return Ok(response.Id); + } + + /// + /// Creates a new project + /// + /// The ID of the project to create this in + /// The request + /// Created if created, otherwise BadRequest with a message of what went wrong. + [HttpPost("{projectId:guid}/initiative/create", Name = "CreateInitiativeAsync")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateInitiativeAsync(Guid projectId, [FromBody] CreateInitiativeRequest request) + { + CommandResponse response = await Sender.Send(new CreateInitiativeCommand(request.Name, projectId, await GetCurrentBonesUserAsync())); + if (!response.Success) + { + return BadRequest(ErrorResponse.FromCommandResponse(response)); + } + + return Ok(response.Id); + } + #endregion + + #region PUT + + #endregion + + #region DELETE + + #endregion } \ No newline at end of file diff --git a/Backend/Bones.Api/Controllers/TestController.cs b/Backend/Bones.Api/Controllers/TestController.cs deleted file mode 100644 index 3efbb37..0000000 --- a/Backend/Bones.Api/Controllers/TestController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Bones.Api.Models; -using Bones.Shared.Exceptions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Bones.Api.Controllers; - -#if DEBUG -/// -/// Controller for testing stuff -/// -[AllowAnonymous] -public class TestController(ISender sender) : BonesControllerBase(sender) -{ - /// - /// Throws a BonesException to test the ApiExceptionHandler - /// - /// - /// - [HttpGet("bones-exception", Name = "GetBonesExceptionAsync")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public Task GetBonesExceptionAsync() - { - throw new BonesException("Meep Merp"); - } - - /// - /// Throws a ForbiddenAccessException to test the ApiExceptionHandler - /// - /// - /// - [HttpGet("forbidden", Name = "GetForbiddenAsync")] - [ProducesResponseType>(StatusCodes.Status200OK)] - public Task GetForbiddenAsync() - { - throw new ForbiddenAccessException(); - } -} -#endif \ No newline at end of file diff --git a/Backend/Bones.Api/Models/ErrorResponse.cs b/Backend/Bones.Api/Models/ErrorResponse.cs new file mode 100644 index 0000000..b276998 --- /dev/null +++ b/Backend/Bones.Api/Models/ErrorResponse.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; +using Bones.Shared.Backend.Models; + +namespace Bones.Api.Models; + +/// +/// The response body is empty, this is a workaround for the limitations of the API client. +/// +[Serializable] +[JsonSerializable(typeof(ErrorResponse))] +public sealed record ErrorResponse +{ + private ErrorResponse(Dictionary>? errors) + { + Errors = errors; + } + + /// + /// Creates an error response from a failed CommandResponse + /// + /// + /// + public static ErrorResponse FromCommandResponse(CommandResponse failedCommandResponse) => new(failedCommandResponse.FailureReasons); + + /// + /// Creates an error response from a failed QueryResponse + /// + /// + /// + public static ErrorResponse FromQueryResponse(QueryResponse failedQueryResponse) => new(failedQueryResponse.FailureReasons); + + /// + /// The errors that occurred, with the key being either the input that was invalid and the list of reasons it was invalid, or 'server' with the list of server errors. + /// + public Dictionary>? Errors { get; init; } +} \ No newline at end of file diff --git a/Backend/Bones.Api/OpenApi/swagger.json b/Backend/Bones.Api/OpenApi/swagger.json index 7cbaaca..7adb7e9 100644 --- a/Backend/Bones.Api/OpenApi/swagger.json +++ b/Backend/Bones.Api/OpenApi/swagger.json @@ -509,84 +509,6 @@ } } }, - "/Project/create": { - "post": { - "tags": [ - "Project" - ], - "summary": "Creates a new project", - "operationId": "CreateProjectAsync", - "requestBody": { - "description": "The request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateProjectRequest" - } - } - } - }, - "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": { - "type": "string", - "format": "uuid" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - } - } - }, "/Project/projects/by-owner": { "get": { "tags": [ @@ -667,13 +589,13 @@ } } }, - "/Project/projects": { + "/Project/projects/quick-select": { "get": { "tags": [ "Project" ], - "summary": "Gets the projects the current user is able to access", - "operationId": "GetProjectsUserCanAccessAsync", + "summary": "Gets the users current quick select projects", + "operationId": "GetProjectQuickSelectAsync", "responses": { "401": { "description": "Unauthorized", @@ -710,14 +632,24 @@ "content": { "application/json": { "schema": { - "type": "object", - "additionalProperties": { - "type": "string" + "type": "array", + "items": { + "$ref": "#/components/schemas/GetProjectQuickSelectResponse" } } } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmptyResult" + } + } + } + }, "400": { "description": "Bad Request", "content": { @@ -816,13 +748,23 @@ } } }, - "/Test/bones-exception": { - "get": { + "/Project/create": { + "post": { "tags": [ - "Test" + "Project" ], - "summary": "Throws a BonesException to test the ApiExceptionHandler", - "operationId": "GetBonesExceptionAsync", + "summary": "Creates a new project", + "operationId": "CreateProjectAsync", + "requestBody": { + "description": "The request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateProjectRequest" + } + } + } + }, "responses": { "401": { "description": "Unauthorized", @@ -859,7 +801,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponseActionResult" + "type": "string", + "format": "uuid" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -867,13 +820,35 @@ } } }, - "/Test/forbidden": { - "get": { + "/Project/{projectId}/initiative/create": { + "post": { "tags": [ - "Test" + "Project" ], - "summary": "Throws a ForbiddenAccessException to test the ApiExceptionHandler", - "operationId": "GetForbiddenAsync", + "summary": "Creates a new project", + "operationId": "CreateInitiativeAsync", + "parameters": [ + { + "name": "projectId", + "in": "path", + "description": "The ID of the project to create this in", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateInitiativeRequest" + } + } + } + }, "responses": { "401": { "description": "Unauthorized", @@ -910,7 +885,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyResponseActionResult" + "type": "string", + "format": "uuid" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -921,9 +907,17 @@ }, "components": { "schemas": { - "ActionResult": { + "CreateInitiativeRequest": { "type": "object", - "additionalProperties": false + "properties": { + "name": { + "type": "string", + "description": "Name of the initiative to create", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Request to create a new initiative" }, "CreateProjectRequest": { "type": "object", @@ -948,17 +942,27 @@ "additionalProperties": false, "description": "The response body is empty, this is a workaround for the limitations of the API client." }, - "EmptyResponseActionResult": { + "EmptyResult": { + "type": "object", + "additionalProperties": false + }, + "ErrorResponse": { "type": "object", "properties": { - "result": { - "$ref": "#/components/schemas/ActionResult" - }, - "value": { - "$ref": "#/components/schemas/EmptyResponse" + "errors": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "The errors that occurred, with the key being either the input that was invalid and the list of reasons it was invalid, or 'server' with the list of server errors.", + "nullable": true } }, - "additionalProperties": false + "additionalProperties": false, + "description": "The response body is empty, this is a workaround for the limitations of the API client." }, "GetMyBasicInfoResponse": { "type": "object", @@ -1022,18 +1026,43 @@ "description": "", "nullable": true }, + "ownerType": { + "$ref": "#/components/schemas/OwnershipType" + }, + "ownerId": { + "type": "string", + "description": "", + "format": "uuid" + }, "initiativeCount": { "type": "integer", "description": "", "format": "int32" }, - "ownerType": { - "$ref": "#/components/schemas/OwnershipType" - }, - "ownerId": { + "initiatives": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InitiativeListModel" + }, + "description": "", + "nullable": true + } + }, + "additionalProperties": false, + "description": "" + }, + "GetProjectQuickSelectResponse": { + "type": "object", + "properties": { + "projectId": { "type": "string", "description": "", "format": "uuid" + }, + "projectName": { + "type": "string", + "description": "", + "nullable": true } }, "additionalProperties": false, @@ -1055,6 +1084,28 @@ "additionalProperties": false, "description": "Request to get the projects for a given User/Organization" }, + "InitiativeListModel": { + "type": "object", + "properties": { + "initiativeId": { + "type": "string", + "description": "", + "format": "uuid" + }, + "initiativeName": { + "type": "string", + "description": "", + "nullable": true + }, + "queueCount": { + "type": "integer", + "description": "", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "" + }, "LoginUserApiRequest": { "type": "object", "properties": { diff --git a/Backend/Bones.Backend/Bones.Backend.csproj b/Backend/Bones.Backend/Bones.Backend.csproj index f67528f..5e9fbd5 100644 --- a/Backend/Bones.Backend/Bones.Backend.csproj +++ b/Backend/Bones.Backend/Bones.Backend.csproj @@ -24,9 +24,8 @@ - - - + + diff --git a/Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailHandler.cs b/Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailHandler.cs similarity index 96% rename from Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailHandler.cs rename to Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailHandler.cs index ff2d71d..5e7cce6 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailHandler.cs @@ -3,7 +3,7 @@ using Bones.Shared.Extensions; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.ConfirmEmail; +namespace Bones.Backend.Features.Accounts.ConfirmEmail; internal class ConfirmEmailHandler(UserManager userManager, ISender sender) : IRequestHandler> { diff --git a/Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailQuery.cs b/Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailQuery.cs similarity index 84% rename from Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailQuery.cs rename to Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailQuery.cs index b021862..0c67a9c 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailQuery.cs +++ b/Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailQuery.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.ConfirmEmail; +namespace Bones.Backend.Features.Accounts.ConfirmEmail; /// /// Backend request for confirming a users email diff --git a/Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailQueryValidator.cs b/Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailQueryValidator.cs similarity index 56% rename from Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailQueryValidator.cs rename to Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailQueryValidator.cs index 6e53ac8..1a624e6 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/ConfirmEmail/ConfirmEmailQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/ConfirmEmail/ConfirmEmailQueryValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.AccountManagement.ConfirmEmail; +namespace Bones.Backend.Features.Accounts.ConfirmEmail; internal class ConfirmEmailQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalHandler.cs b/Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalHandler.cs similarity index 88% rename from Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalHandler.cs rename to Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalHandler.cs index 996f975..e646d93 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalHandler.cs @@ -1,7 +1,7 @@ using Bones.Database.DbSets.AccountManagement; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.GetUserByClaimsPrincipal; +namespace Bones.Backend.Features.Accounts.GetUserByClaimsPrincipal; internal sealed class GetUserByClaimsPrincipalHandler(UserManager userManager) : IRequestHandler> { diff --git a/Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQuery.cs b/Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQuery.cs similarity index 82% rename from Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQuery.cs rename to Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQuery.cs index 2ec734d..a6a39c0 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQuery.cs +++ b/Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQuery.cs @@ -1,7 +1,7 @@ using System.Security.Claims; using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.AccountManagement.GetUserByClaimsPrincipal; +namespace Bones.Backend.Features.Accounts.GetUserByClaimsPrincipal; /// /// Backend request for getting a by a . diff --git a/Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQueryValidator.cs b/Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQueryValidator.cs similarity index 86% rename from Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQueryValidator.cs rename to Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQueryValidator.cs index be4951a..ee965fb 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/GetUserByClaimsPrincipal/GetUserByClaimsPrincipalQueryValidator.cs @@ -1,6 +1,6 @@ using FluentValidation.Results; -namespace Bones.Backend.Features.AccountManagement.GetUserByClaimsPrincipal; +namespace Bones.Backend.Features.Accounts.GetUserByClaimsPrincipal; internal sealed class GetUserByClaimsPrincipalQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailCommand.cs b/Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailCommand.cs similarity index 83% rename from Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailCommand.cs rename to Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailCommand.cs index 7068447..5fbe6e3 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailCommand.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailCommand.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.AccountManagement.QueueConfirmationEmail; +namespace Bones.Backend.Features.Accounts.QueueConfirmationEmail; /// /// Backend command for queueing a confirmation email. diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailCommandValidator.cs b/Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailCommandValidator.cs similarity index 90% rename from Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailCommandValidator.cs rename to Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailCommandValidator.cs index e5e50d4..cabc002 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailCommandValidator.cs @@ -1,7 +1,7 @@ using Bones.Shared.Extensions; using FluentValidation.Results; -namespace Bones.Backend.Features.AccountManagement.QueueConfirmationEmail; +namespace Bones.Backend.Features.Accounts.QueueConfirmationEmail; internal class QueueConfirmationEmailCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailHandler.cs b/Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailHandler.cs similarity index 95% rename from Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailHandler.cs rename to Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailHandler.cs index 470b869..aabc4e8 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueConfirmationEmail/QueueConfirmationEmailHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueConfirmationEmail/QueueConfirmationEmailHandler.cs @@ -6,7 +6,7 @@ using Bones.Shared.Extensions; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.QueueConfirmationEmail; +namespace Bones.Backend.Features.Accounts.QueueConfirmationEmail; internal class QueueConfirmationEmailHandler(UserManager userManager, BackendConfiguration config, ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommand.cs b/Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommand.cs similarity index 78% rename from Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommand.cs rename to Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommand.cs index acb0eb0..ae0554a 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommand.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommand.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Bones.Backend.Features.AccountManagement.QueueForgotPasswordEmail; +namespace Bones.Backend.Features.Accounts.QueueForgotPasswordEmail; /// /// Backend request for queueing a password reset email. diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommandValidator.cs b/Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommandValidator.cs similarity index 90% rename from Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommandValidator.cs rename to Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommandValidator.cs index 5eb6fa4..5950b4a 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailCommandValidator.cs @@ -1,7 +1,7 @@ using Bones.Shared.Extensions; using FluentValidation.Results; -namespace Bones.Backend.Features.AccountManagement.QueueForgotPasswordEmail; +namespace Bones.Backend.Features.Accounts.QueueForgotPasswordEmail; internal class QueueForgotPasswordEmailCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailHandler.cs b/Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailHandler.cs similarity index 95% rename from Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailHandler.cs rename to Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailHandler.cs index 7b3a6dd..260bd4d 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueForgotPasswordEmail/QueueForgotPasswordEmailHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueForgotPasswordEmail/QueueForgotPasswordEmailHandler.cs @@ -6,7 +6,7 @@ using Bones.Shared.Extensions; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.QueueForgotPasswordEmail; +namespace Bones.Backend.Features.Accounts.QueueForgotPasswordEmail; internal class QueueForgotPasswordEmailHandler(UserManager userManager, BackendConfiguration config, ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommand.cs b/Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommand.cs similarity index 77% rename from Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommand.cs rename to Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommand.cs index de89e78..0146fd7 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommand.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommand.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.AccountManagement.QueueResendConfirmationEmail; +namespace Bones.Backend.Features.Accounts.QueueResendConfirmationEmail; /// /// Backend request for resending the confirmation email. diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommandValidator.cs b/Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommandValidator.cs similarity index 60% rename from Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommandValidator.cs rename to Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommandValidator.cs index 48679e5..72d6c9a 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.AccountManagement.QueueResendConfirmationEmail; +namespace Bones.Backend.Features.Accounts.QueueResendConfirmationEmail; internal class QueueResendConfirmationEmailCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailHandler.cs b/Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailHandler.cs similarity index 82% rename from Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailHandler.cs rename to Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailHandler.cs index f2e1ef8..50c6da0 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/QueueResendConfirmationEmail/QueueResendConfirmationEmailHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/QueueResendConfirmationEmail/QueueResendConfirmationEmailHandler.cs @@ -1,8 +1,8 @@ -using Bones.Backend.Features.AccountManagement.QueueConfirmationEmail; +using Bones.Backend.Features.Accounts.QueueConfirmationEmail; using Bones.Database.DbSets.AccountManagement; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.QueueResendConfirmationEmail; +namespace Bones.Backend.Features.Accounts.QueueResendConfirmationEmail; internal class QueueResendConfirmationEmailHandler(UserManager userManager, ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserHandler.cs b/Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserHandler.cs similarity index 90% rename from Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserHandler.cs rename to Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserHandler.cs index d30c303..ed72aa8 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserHandler.cs @@ -1,10 +1,10 @@ -using Bones.Backend.Features.AccountManagement.QueueConfirmationEmail; +using Bones.Backend.Features.Accounts.QueueConfirmationEmail; using Bones.Database.DbSets.AccountManagement; using Bones.Shared.Exceptions; using Bones.Shared.Extensions; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.RegisterUser; +namespace Bones.Backend.Features.Accounts.RegisterUser; internal class RegisterUserHandler(UserManager userManager, ISender sender) : IRequestHandler> { diff --git a/Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserQuery.cs b/Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserQuery.cs similarity index 83% rename from Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserQuery.cs rename to Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserQuery.cs index 136d0dc..38a506c 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserQuery.cs +++ b/Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserQuery.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.AccountManagement.RegisterUser; +namespace Bones.Backend.Features.Accounts.RegisterUser; /// /// Backend request for registering a new user. diff --git a/Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserQueryValidator.cs b/Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserQueryValidator.cs similarity index 96% rename from Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserQueryValidator.cs rename to Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserQueryValidator.cs index 0ac2b5e..611782e 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/RegisterUser/RegisterUserQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/RegisterUser/RegisterUserQueryValidator.cs @@ -2,7 +2,7 @@ using Bones.Shared.Extensions; using FluentValidation.Results; -namespace Bones.Backend.Features.AccountManagement.RegisterUser; +namespace Bones.Backend.Features.Accounts.RegisterUser; internal sealed class RegisterUserQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordCommand.cs b/Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordCommand.cs similarity index 66% rename from Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordCommand.cs rename to Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordCommand.cs index 4aabc8f..c119850 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordCommand.cs +++ b/Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordCommand.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.AccountManagement.ResetPassword; +namespace Bones.Backend.Features.Accounts.ResetPassword; /// /// Request to reset password diff --git a/Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordCommandValidator.cs b/Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordCommandValidator.cs similarity index 58% rename from Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordCommandValidator.cs rename to Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordCommandValidator.cs index 776f37c..2387efb 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.AccountManagement.ResetPassword; +namespace Bones.Backend.Features.Accounts.ResetPassword; internal class ResetPasswordCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordHandler.cs b/Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordHandler.cs similarity index 79% rename from Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordHandler.cs rename to Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordHandler.cs index 5f54172..c82a5ea 100644 --- a/Backend/Bones.Backend/Features/AccountManagement/ResetPassword/ResetPasswordHandler.cs +++ b/Backend/Bones.Backend/Features/Accounts/ResetPassword/ResetPasswordHandler.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.AccountManagement.ResetPassword; +namespace Bones.Backend.Features.Accounts.ResetPassword; internal class ResetPasswordHandler : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionHandler.cs b/Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionHandler.cs similarity index 95% rename from Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionHandler.cs rename to Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionHandler.cs index f9ba564..233251e 100644 --- a/Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionHandler.cs +++ b/Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionHandler.cs @@ -5,7 +5,7 @@ using Bones.Shared.Consts; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.OrganizationManagement.UserHasOrganizationPermission; +namespace Bones.Backend.Features.Organizations.UserHasOrganizationPermission; internal sealed class UserHasOrganizationPermissionHandler(UserManager userManager, RoleManager roleManager, ISender sender) : IRequestHandler> { diff --git a/Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionQuery.cs b/Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionQuery.cs similarity index 82% rename from Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionQuery.cs rename to Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionQuery.cs index 41eba1d..25e7662 100644 --- a/Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionQuery.cs +++ b/Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionQuery.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.OrganizationManagement.UserHasOrganizationPermission; +namespace Bones.Backend.Features.Organizations.UserHasOrganizationPermission; /// /// Checks if the user has permission to do the specified action in the organization. diff --git a/Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionQueryValidator.cs b/Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionQueryValidator.cs similarity index 90% rename from Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionQueryValidator.cs rename to Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionQueryValidator.cs index 6cde6d6..922b23f 100644 --- a/Backend/Bones.Backend/Features/OrganizationManagement/UserHasOrganizationPermission/UserHasOrganizationPermissionQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Organizations/UserHasOrganizationPermission/UserHasOrganizationPermissionQueryValidator.cs @@ -1,6 +1,6 @@ using FluentValidation.Results; -namespace Bones.Backend.Features.OrganizationManagement.UserHasOrganizationPermission; +namespace Bones.Backend.Features.Organizations.UserHasOrganizationPermission; internal sealed class UserHasOrganizationPermissionQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeCommand.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeCommand.cs deleted file mode 100644 index 36a319a..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Security.Claims; - -namespace Bones.Backend.Features.ProjectManagement.Initiatives.CreateInitiative; - -/// -/// Backend Command for creating an Initiative. -/// -/// Name of the initiative -/// Internal ID of the project -/// The claims principal for the user requesting this -public record CreateInitiativeCommand(string Name, Guid ProjectId, ClaimsPrincipal RequestingUser) : IRequest; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeCommandValidator.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeCommandValidator.cs deleted file mode 100644 index e71e747..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeCommandValidator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Initiatives.CreateInitiative; - -internal sealed class CreateInitiativeCommandValidator : AbstractValidator -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeHandler.cs deleted file mode 100644 index c6c0650..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/CreateInitiative/CreateInitiativeHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Bones.Backend.Features.AccountManagement.GetUserByClaimsPrincipal; -using Bones.Database.DbSets.AccountManagement; -using Bones.Database.Operations.ProjectManagement.Initiatives.CreateInitiativeDb; - -namespace Bones.Backend.Features.ProjectManagement.Initiatives.CreateInitiative; - -internal sealed class CreateInitiativeHandler(ISender sender) : IRequestHandler -{ - public async Task Handle(CreateInitiativeCommand request, CancellationToken cancellationToken) - { - // TODO: Validate user permission here - BonesUser? user = await sender.Send(new GetUserByClaimsPrincipalQuery(request.RequestingUser), cancellationToken); - - if (user is null) - { - return CommandResponse.Fail("User could not be found"); - } - - return await sender.Send(new CreateInitiativeDbCommand(request.Name, request.ProjectId), cancellationToken); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdHandler.cs deleted file mode 100644 index 836b7ac..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdHandler.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Bones.Database.DbSets.ProjectManagement; - -namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativeById; - -internal sealed class GetInitiativeByIdHandler : IRequestHandler> -{ - public Task> Handle(GetInitiativeByIdQuery request, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQuery.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQuery.cs deleted file mode 100644 index bee404a..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQuery.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Bones.Database.DbSets.AccountManagement; -using Bones.Database.DbSets.ProjectManagement; - -namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativeById; - -/// -/// Gets the initiative by ID -/// -/// -/// -public sealed record GetInitiativeByIdQuery(Guid InitiativeId, BonesUser RequestingUser) : IRequest>; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQueryValidator.cs b/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQueryValidator.cs deleted file mode 100644 index de967a1..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/GetInitiativeById/GetInitiativeByIdQueryValidator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bones.Backend.Features.ProjectManagement.Initiatives.GetInitiativeById; - -internal sealed class GetInitiativeByIdQueryValidator -{ - -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdHandler.cs b/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdHandler.cs deleted file mode 100644 index b5715d2..0000000 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdHandler.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Bones.Backend.Features.ProjectManagement.Projects.UserHasProjectPermission; -using Bones.Database.DbSets.ProjectManagement; -using Bones.Database.Operations.ProjectManagement.Projects.GetProjectByIdDb; -using Bones.Shared.Consts; - -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectById; - -internal sealed class GetProjectByIdHandler(ISender sender) : IRequestHandler> -{ - public async Task> Handle(GetProjectByIdQuery request, CancellationToken cancellationToken) - { - const string perm = BonesClaimTypes.Role.Project.VIEW_PROJECT; - bool? hasPermission = - await sender.Send(new UserHasProjectPermissionQuery(request.ProjectId, request.RequestingUser, perm), cancellationToken); - - if (hasPermission is not true) - { - return QueryResponse.Forbid(); - } - - Project? project = await sender.Send(new GetProjectByIdDbQuery(request.ProjectId), cancellationToken); - - if (project is null) - { - return QueryResponse.Fail("Project not found"); - } - - return QueryResponse.Pass(project); - } -} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/Projects/Initiatives/CreateInitiative.cs b/Backend/Bones.Backend/Features/Projects/Initiatives/CreateInitiative.cs new file mode 100644 index 0000000..8c1e3db --- /dev/null +++ b/Backend/Bones.Backend/Features/Projects/Initiatives/CreateInitiative.cs @@ -0,0 +1,60 @@ +using Bones.Backend.Features.Projects.Projects.UserHasProjectPermission; +using Bones.Database.DbSets.AccountManagement; +using Bones.Database.DbSets.OrganizationManagement; +using Bones.Database.Operations.OrganizationManagement.GetOrganizationByIdDb; +using Bones.Database.Operations.ProjectManagement.Initiatives.CreateInitiativeDb; +using Bones.Database.Operations.ProjectManagement.Projects; +using Bones.Shared.Backend.Enums; +using Bones.Shared.Consts; + +namespace Bones.Backend.Features.Projects.Initiatives; + +/// +/// Backend Command for creating an Initiative. +/// +/// Name of the initiative +/// Internal ID of the project +/// The user requesting this +public record CreateInitiativeCommand(string Name, Guid ProjectId, BonesUser RequestingUser) : IRequest; + +internal sealed class CreateInitiativeCommandValidator : AbstractValidator +{ + +} + +internal sealed class CreateInitiativeHandler(ISender sender) : IRequestHandler +{ + public async Task Handle(CreateInitiativeCommand request, CancellationToken cancellationToken) + { + Database.DbSets.ProjectManagement.Project? project = await sender.Send(new GetProjectByIdDbQuery(request.ProjectId), cancellationToken); + + if (project is null) + { + return CommandResponse.Fail("Project not found"); + } + + if (project.OwnerType == OwnershipType.User + && project.OwningUser!.Id == request.RequestingUser.Id) + { + return await sender.Send(new CreateInitiativeDbCommand(request.Name, request.ProjectId), cancellationToken); + } + + BonesOrganization? organization = await sender.Send(new GetOrganizationByIdDbQuery(project.OwningOrganization!.Id), cancellationToken); + // Don't want to give away that this org doesn't exist, instead just return forbidden. + if (organization is null) + { + return CommandResponse.Forbid(); + } + + const string perm = BonesClaimTypes.Role.Initiative.CREATE_INITIATIVE; + bool? hasOrganizationPermission = + await sender.Send(new UserHasProjectPermissionQuery(project.Id, request.RequestingUser, perm), cancellationToken); + + if (hasOrganizationPermission != true) + { + return CommandResponse.Forbid(); + } + + return await sender.Send(new CreateInitiativeDbCommand(request.Name, request.ProjectId), cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/Projects/Initiatives/GetInitiativeById.cs b/Backend/Bones.Backend/Features/Projects/Initiatives/GetInitiativeById.cs new file mode 100644 index 0000000..a32a6ca --- /dev/null +++ b/Backend/Bones.Backend/Features/Projects/Initiatives/GetInitiativeById.cs @@ -0,0 +1,24 @@ +using Bones.Database.DbSets.AccountManagement; +using Bones.Database.DbSets.ProjectManagement; + +namespace Bones.Backend.Features.Projects.Initiatives; + +/// +/// Gets the initiative by ID +/// +/// +/// +public sealed record GetInitiativeByIdQuery(Guid InitiativeId, BonesUser RequestingUser) : IRequest>; + +internal sealed class GetInitiativeByIdQueryValidator +{ + +} + +internal sealed class GetInitiativeByIdHandler : IRequestHandler> +{ + public Task> Handle(GetInitiativeByIdQuery request, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/Projects/Initiatives/GetInitiativesByProject.cs b/Backend/Bones.Backend/Features/Projects/Initiatives/GetInitiativesByProject.cs new file mode 100644 index 0000000..374797d --- /dev/null +++ b/Backend/Bones.Backend/Features/Projects/Initiatives/GetInitiativesByProject.cs @@ -0,0 +1,40 @@ +using Bones.Backend.Features.Projects.Projects.UserHasProjectPermission; +using Bones.Database.DbSets.AccountManagement; +using Bones.Database.DbSets.OrganizationManagement; +using Bones.Database.DbSets.ProjectManagement; +using Bones.Database.Operations.OrganizationManagement.GetOrganizationByIdDb; +using Bones.Database.Operations.ProjectManagement.Initiatives; +using Bones.Database.Operations.ProjectManagement.Projects; +using Bones.Shared.Backend.Enums; +using Bones.Shared.Consts; + +namespace Bones.Backend.Features.Projects.Initiatives; + +/// +/// Backend Command for creating an Initiative. +/// +/// Internal ID of the project +/// The user requesting this +public record GetInitiativesByProjectQuery(Guid ProjectId, BonesUser RequestingUser) : IRequest>>; + +internal sealed class GetInitiativesByProjectQueryValidator : AbstractValidator +{ + +} + +internal sealed class GetInitiativesByProjectHandler(ISender sender) : IRequestHandler>> +{ + public async Task>> Handle(GetInitiativesByProjectQuery request, CancellationToken cancellationToken) + { + const string perm = BonesClaimTypes.Role.Initiative.VIEW_INITIATIVE; + bool? hasOrganizationPermission = + await sender.Send(new UserHasProjectPermissionQuery(request.ProjectId, request.RequestingUser, perm), cancellationToken); + + if (hasOrganizationPermission != true) + { + return QueryResponse>.Forbid(); + } + + return await sender.Send(new GetInitiativesByProjectDbQuery(request.ProjectId), cancellationToken); + } +} diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommand.cs b/Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommand.cs similarity index 78% rename from Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommand.cs rename to Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommand.cs index d35cf73..f9afad3 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommand.cs +++ b/Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommand.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.ProjectManagement.Initiatives.QueueDeleteInitiativeById; +namespace Bones.Backend.Features.Projects.Initiatives.QueueDeleteInitiativeById; /// /// Queues the deletion of the initiative diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommandValidator.cs b/Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommandValidator.cs similarity index 57% rename from Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommandValidator.cs rename to Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommandValidator.cs index 4e9c5be..f6dc637 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.Initiatives.QueueDeleteInitiativeById; +namespace Bones.Backend.Features.Projects.Initiatives.QueueDeleteInitiativeById; internal sealed class QueueDeleteInitiativeByIdCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdHandler.cs b/Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdHandler.cs similarity index 85% rename from Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdHandler.cs rename to Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdHandler.cs index d3566a3..6c8943f 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/Initiatives/QueueDeleteInitiativeById/QueueDeleteInitiativeByIdHandler.cs @@ -1,6 +1,6 @@ using Bones.Database.Operations.ProjectManagement.Initiatives.QueueDeleteInitiativeByIdDb; -namespace Bones.Backend.Features.ProjectManagement.Initiatives.QueueDeleteInitiativeById; +namespace Bones.Backend.Features.Projects.Initiatives.QueueDeleteInitiativeById; internal sealed class QueueDeleteInitiativeByIdHandler(ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectCommand.cs b/Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectCommand.cs similarity index 86% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectCommand.cs rename to Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectCommand.cs index 2f9ad64..afdcdf0 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectCommand.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectCommand.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.ProjectManagement.Projects.CreateProject; +namespace Bones.Backend.Features.Projects.Projects.CreateProject; /// /// DB Command for creating a Project. diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectCommandValidator.cs b/Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectCommandValidator.cs similarity index 56% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectCommandValidator.cs rename to Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectCommandValidator.cs index 4c84f51..f889247 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.Projects.CreateProject; +namespace Bones.Backend.Features.Projects.Projects.CreateProject; internal sealed class CreateProjectCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectHandler.cs b/Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectHandler.cs similarity index 90% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectHandler.cs rename to Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectHandler.cs index e5148d5..df81e6d 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/CreateProject/CreateProjectHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/CreateProject/CreateProjectHandler.cs @@ -1,10 +1,10 @@ -using Bones.Backend.Features.OrganizationManagement.UserHasOrganizationPermission; +using Bones.Backend.Features.Organizations.UserHasOrganizationPermission; using Bones.Database.DbSets.OrganizationManagement; using Bones.Database.Operations.OrganizationManagement.GetOrganizationByIdDb; using Bones.Database.Operations.ProjectManagement.Projects.CreateProjectDb; using Bones.Shared.Consts; -namespace Bones.Backend.Features.ProjectManagement.Projects.CreateProject; +namespace Bones.Backend.Features.Projects.Projects.CreateProject; internal sealed class CreateProjectHandler(ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdHandler.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdHandler.cs new file mode 100644 index 0000000..f54d61a --- /dev/null +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdHandler.cs @@ -0,0 +1,29 @@ +using Bones.Backend.Features.Projects.Projects.UserHasProjectPermission; +using Bones.Database.Operations.ProjectManagement.Projects; +using Bones.Shared.Consts; + +namespace Bones.Backend.Features.Projects.Projects.GetProjectById; + +internal sealed class GetProjectByIdHandler(ISender sender) : IRequestHandler> +{ + public async Task> Handle(GetProjectByIdQuery request, CancellationToken cancellationToken) + { + const string perm = BonesClaimTypes.Role.Project.VIEW_PROJECT; + bool? hasPermission = + await sender.Send(new UserHasProjectPermissionQuery(request.ProjectId, request.RequestingUser, perm), cancellationToken); + + if (hasPermission is not true) + { + return QueryResponse.Forbid(); + } + + Database.DbSets.ProjectManagement.Project? project = await sender.Send(new GetProjectByIdDbQuery(request.ProjectId), cancellationToken); + + if (project is null) + { + return QueryResponse.Fail("Project not found"); + } + + return QueryResponse.Pass(project); + } +} \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdQuery.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdQuery.cs similarity index 56% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdQuery.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdQuery.cs index 88c95f1..d711a3b 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdQuery.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdQuery.cs @@ -1,11 +1,10 @@ using Bones.Database.DbSets.AccountManagement; -using Bones.Database.DbSets.ProjectManagement; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectById; +namespace Bones.Backend.Features.Projects.Projects.GetProjectById; /// /// /// /// /// -public sealed record GetProjectByIdQuery(Guid ProjectId, BonesUser RequestingUser) : IRequest>; \ No newline at end of file +public sealed record GetProjectByIdQuery(Guid ProjectId, BonesUser RequestingUser) : IRequest>; \ No newline at end of file diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdQueryValidator.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdQueryValidator.cs similarity index 83% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdQueryValidator.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdQueryValidator.cs index c992d6d..96620fb 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectById/GetProjectByIdQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectById/GetProjectByIdQueryValidator.cs @@ -1,6 +1,6 @@ using FluentValidation.Results; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectById; +namespace Bones.Backend.Features.Projects.Projects.GetProjectById; internal sealed class GetProjectByIdQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerHandler.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerHandler.cs similarity index 80% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerHandler.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerHandler.cs index 7aa6453..2acb3c7 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerHandler.cs @@ -1,8 +1,7 @@ -using Bones.Database.DbSets.ProjectManagement; using Bones.Database.Operations.ProjectManagement.Projects.GetProjectsByOwnerDb; using Bones.Shared.Backend.Enums; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; +namespace Bones.Backend.Features.Projects.Projects.GetProjectsByOwner; internal sealed class GetProjectsByOwnerHandler(ISender sender) : IRequestHandler>> { @@ -11,7 +10,7 @@ public async Task>> Handle(GetProjectsByO if (request.OwnerType == OwnershipType.User && request.OwnerId == request.RequestingUser.Id) { - List? projects = await sender.Send(new GetProjectsByOwnerDbQuery(OwnershipType.User, request.RequestingUser.Id), cancellationToken); + List? projects = await sender.Send(new GetProjectsByOwnerDbQuery(OwnershipType.User, request.RequestingUser.Id), cancellationToken); if (projects is null) { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerQuery.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerQuery.cs similarity index 83% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerQuery.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerQuery.cs index c4a3899..95a49dc 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerQuery.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerQuery.cs @@ -1,7 +1,7 @@ using Bones.Database.DbSets.AccountManagement; using Bones.Shared.Backend.Enums; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; +namespace Bones.Backend.Features.Projects.Projects.GetProjectsByOwner; /// /// diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerQueryValidator.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerQueryValidator.cs similarity index 86% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerQueryValidator.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerQueryValidator.cs index ffda988..5f6f428 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsByOwner/GetProjectsByOwnerQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsByOwner/GetProjectsByOwnerQueryValidator.cs @@ -1,6 +1,6 @@ using FluentValidation.Results; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; +namespace Bones.Backend.Features.Projects.Projects.GetProjectsByOwner; internal sealed class GetProjectsByOwnerQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessHandler.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessHandler.cs similarity index 68% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessHandler.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessHandler.cs index 2cc2b04..35b895d 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessHandler.cs @@ -1,15 +1,13 @@ -using Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; -using Bones.Database.DbSets.ProjectManagement; using Bones.Database.Operations.ProjectManagement.Projects.GetProjectsByOwnerDb; using Bones.Shared.Backend.Enums; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsUserCanAccess; +namespace Bones.Backend.Features.Projects.Projects.GetProjectsUserCanAccess; internal sealed class GetProjectsUserCanAccessHandler(ISender sender) : IRequestHandler>> { public async Task>> Handle(GetProjectsUserCanAccessQuery request, CancellationToken cancellationToken) { - List? projects = await sender.Send(new GetProjectsByOwnerDbQuery(OwnershipType.User, request.RequestingUser.Id), cancellationToken); + List? projects = await sender.Send(new GetProjectsByOwnerDbQuery(OwnershipType.User, request.RequestingUser.Id), cancellationToken); if (projects is null) { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQuery.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQuery.cs similarity index 74% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQuery.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQuery.cs index ff2efb8..dd7e9af 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQuery.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQuery.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsUserCanAccess; +namespace Bones.Backend.Features.Projects.Projects.GetProjectsUserCanAccess; /// /// diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQueryValidator.cs b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQueryValidator.cs similarity index 72% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQueryValidator.cs rename to Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQueryValidator.cs index c2966dc..3854df1 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/GetProjectsUserCanAccess/GetProjectsUserCanAccessQueryValidator.cs @@ -1,7 +1,6 @@ -using Bones.Backend.Features.ProjectManagement.Projects.GetProjectsByOwner; using FluentValidation.Results; -namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectsUserCanAccess; +namespace Bones.Backend.Features.Projects.Projects.GetProjectsUserCanAccess; internal sealed class GetProjectsUserCanAccessQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionHandler.cs b/Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionHandler.cs similarity index 88% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionHandler.cs rename to Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionHandler.cs index f2c9979..04cd79a 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionHandler.cs @@ -1,13 +1,13 @@ using System.Security.Claims; -using Bones.Backend.Features.OrganizationManagement.UserHasOrganizationPermission; +using Bones.Backend.Features.Organizations.UserHasOrganizationPermission; using Bones.Database.DbSets.AccountManagement; using Bones.Database.DbSets.ProjectManagement; -using Bones.Database.Operations.ProjectManagement.Projects.GetProjectByIdDb; +using Bones.Database.Operations.ProjectManagement.Projects; using Bones.Shared.Backend.Enums; using Bones.Shared.Consts; using Microsoft.AspNetCore.Identity; -namespace Bones.Backend.Features.ProjectManagement.Projects.UserHasProjectPermission; +namespace Bones.Backend.Features.Projects.Projects.UserHasProjectPermission; internal sealed class UserHasProjectPermissionHandler(UserManager userManager, RoleManager roleManager, ISender sender) : IRequestHandler> { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionQuery.cs b/Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionQuery.cs similarity index 82% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionQuery.cs rename to Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionQuery.cs index 6e437ff..60314c3 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionQuery.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionQuery.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.ProjectManagement.Projects.UserHasProjectPermission; +namespace Bones.Backend.Features.Projects.Projects.UserHasProjectPermission; /// /// Checks if the user has permission to do the specified action in the project. diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionQueryValidator.cs b/Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionQueryValidator.cs similarity index 90% rename from Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionQueryValidator.cs rename to Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionQueryValidator.cs index 20dd9a8..4a75727 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Projects/UserHasProjectPermission/UserHasProjectPermissionQueryValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Projects/UserHasProjectPermission/UserHasProjectPermissionQueryValidator.cs @@ -1,6 +1,6 @@ using FluentValidation.Results; -namespace Bones.Backend.Features.ProjectManagement.Projects.UserHasProjectPermission; +namespace Bones.Backend.Features.Projects.Projects.UserHasProjectPermission; internal sealed class UserHasProjectPermissionQueryValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommand.cs b/Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueCommand.cs similarity index 84% rename from Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommand.cs rename to Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueCommand.cs index 55b69dd..5db1635 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommand.cs +++ b/Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueCommand.cs @@ -1,6 +1,6 @@ using Bones.Database.DbSets.AccountManagement; -namespace Bones.Backend.Features.ProjectManagement.Queues.CreateQueue; +namespace Bones.Backend.Features.Projects.Queues.CreateQueue; /// /// Command for creating a Queue. diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommandValidator.cs b/Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueCommandValidator.cs similarity index 57% rename from Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommandValidator.cs rename to Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueCommandValidator.cs index a899805..1ce27fe 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.Queues.CreateQueue; +namespace Bones.Backend.Features.Projects.Queues.CreateQueue; internal sealed class CreateQueueCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueHandler.cs b/Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueHandler.cs similarity index 86% rename from Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueHandler.cs rename to Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueHandler.cs index 43a7325..d0eb043 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/Queues/CreateQueue/CreateQueueHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/Queues/CreateQueue/CreateQueueHandler.cs @@ -1,6 +1,6 @@ using Bones.Database.Operations.WorkItemManagement.WorkItemQueues.CreateQueueDb; -namespace Bones.Backend.Features.ProjectManagement.Queues.CreateQueue; +namespace Bones.Backend.Features.Projects.Queues.CreateQueue; internal sealed class CreateQueueHandler(ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs similarity index 83% rename from Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs rename to Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs index b85055e..4055b7b 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs +++ b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemCommand.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItem; +namespace Bones.Backend.Features.Projects.WorkItems.CreateWorkItem; /// /// DB Command for creating a WorkItem. diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs similarity index 56% rename from Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs rename to Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs index dcdbab3..ab9b449 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItem; +namespace Bones.Backend.Features.Projects.WorkItems.CreateWorkItem; internal sealed class CreateWorkItemCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs similarity index 85% rename from Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs rename to Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs index 38a87de..215e183 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItem/CreateWorkItemHandler.cs @@ -1,6 +1,6 @@ using Bones.Database.Operations.WorkItemManagement.WorkItems.CreateWorkItemDb; -namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItem; +namespace Bones.Backend.Features.Projects.WorkItems.CreateWorkItem; internal sealed class CreateWorkItemHandler(ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs similarity index 84% rename from Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs rename to Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs index d8eb748..376b1e0 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs +++ b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommand.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItemVersion; +namespace Bones.Backend.Features.Projects.WorkItems.CreateWorkItemVersion; /// /// DB Command for creating a WorkItem. diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs similarity index 57% rename from Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs rename to Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs index 8ed2f1b..d9f4302 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs +++ b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionCommandValidator.cs @@ -1,4 +1,4 @@ -namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItemVersion; +namespace Bones.Backend.Features.Projects.WorkItems.CreateWorkItemVersion; internal sealed class CreateWorkItemVersionCommandValidator : AbstractValidator { diff --git a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs similarity index 85% rename from Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs rename to Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs index a506f56..1ceadfc 100644 --- a/Backend/Bones.Backend/Features/ProjectManagement/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs +++ b/Backend/Bones.Backend/Features/Projects/WorkItems/CreateWorkItemVersion/CreateWorkItemVersionHandler.cs @@ -1,6 +1,6 @@ using Bones.Database.Operations.WorkItemManagement.WorkItems.CreateWorkItemVersionDb; -namespace Bones.Backend.Features.ProjectManagement.WorkItems.CreateWorkItemVersion; +namespace Bones.Backend.Features.Projects.WorkItems.CreateWorkItemVersion; internal sealed class CreateWorkItemVersionHandler(ISender sender) : IRequestHandler { diff --git a/Backend/Bones.Database/Operations/ProjectManagement/Initiatives/GetInitiativesByProjectDb.cs b/Backend/Bones.Database/Operations/ProjectManagement/Initiatives/GetInitiativesByProjectDb.cs new file mode 100644 index 0000000..510be8b --- /dev/null +++ b/Backend/Bones.Database/Operations/ProjectManagement/Initiatives/GetInitiativesByProjectDb.cs @@ -0,0 +1,28 @@ +using Bones.Database.DbSets.AccountManagement; +using Bones.Database.DbSets.OrganizationManagement; +using Bones.Database.DbSets.ProjectManagement; +using Bones.Database.Operations.OrganizationManagement.GetOrganizationByIdDb; +using Bones.Database.Operations.ProjectManagement.Projects; +using Bones.Shared.Backend.Enums; +using Bones.Shared.Consts; + +namespace Bones.Database.Operations.ProjectManagement.Initiatives; + +/// +/// DB Query for getting the initiatives under a project +/// +/// Internal ID of the project +public record GetInitiativesByProjectDbQuery(Guid ProjectId) : IRequest>>; + +internal sealed class GetInitiativesByProjectQueryDbValidator : AbstractValidator +{ + +} + +internal sealed class GetInitiativesByProjectDbHandler(BonesDbContext dbContext) : IRequestHandler>> +{ + public async Task>> Handle(GetInitiativesByProjectDbQuery request, CancellationToken cancellationToken) + { + return await dbContext.Initiatives.Where(i => i.Project.Id == request.ProjectId).ToListAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb.cs b/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb.cs new file mode 100644 index 0000000..3e35e63 --- /dev/null +++ b/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb.cs @@ -0,0 +1,37 @@ +using Bones.Database.DbSets.ProjectManagement; + +namespace Bones.Database.Operations.ProjectManagement.Projects; + +/// +/// DB Query to get the project by its ID. +/// +/// The Project ID +public sealed record GetProjectByIdDbQuery(Guid ProjectId) : IRequest>; + +internal sealed class GetProjectByIdDbQueryValidator : AbstractValidator +{ + public override Task ValidateAsync(ValidationContext context, CancellationToken cancellation = new()) + { + RuleFor(x => x.ProjectId).NotNull().NotEqual(Guid.Empty); + + return base.ValidateAsync(context, cancellation); + } +} + +internal sealed class GetProjectByIdDb(BonesDbContext dbContext) : IRequestHandler> +{ + public async Task> Handle(GetProjectByIdDbQuery request, CancellationToken cancellationToken) + { + Project? project = await dbContext.Projects + .Include(p => p.OwningOrganization) + .Include(p => p.OwningUser) + .FirstOrDefaultAsync(x => x.Id == request.ProjectId, cancellationToken); + + if (project is null) + { + return QueryResponse.Fail("Project not found"); + } + + return QueryResponse.Pass(project); + } +} \ No newline at end of file diff --git a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbHandler.cs b/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbHandler.cs deleted file mode 100644 index a55a685..0000000 --- a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbHandler.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Bones.Database.DbSets.ProjectManagement; - -namespace Bones.Database.Operations.ProjectManagement.Projects.GetProjectByIdDb; - -internal sealed class GetProjectByIdDbHandler(BonesDbContext dbContext) : IRequestHandler> -{ - public async Task> Handle(GetProjectByIdDbQuery request, CancellationToken cancellationToken) - { - Project? project = await dbContext.Projects.FirstOrDefaultAsync(x => x.Id == request.ProjectId, cancellationToken); - - if (project is null) - { - return QueryResponse.Fail("Project not found"); - } - - return QueryResponse.Pass(project); - } -} \ No newline at end of file diff --git a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbQuery.cs b/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbQuery.cs deleted file mode 100644 index ed02b07..0000000 --- a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Bones.Database.DbSets.ProjectManagement; - -namespace Bones.Database.Operations.ProjectManagement.Projects.GetProjectByIdDb; - -/// -/// DB Query to get the project by its ID. -/// -/// The Project ID -public sealed record GetProjectByIdDbQuery(Guid ProjectId) : IRequest>; diff --git a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbQueryValidator.cs b/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbQueryValidator.cs deleted file mode 100644 index dbc0ebf..0000000 --- a/Backend/Bones.Database/Operations/ProjectManagement/Projects/GetProjectByIdDb/GetProjectByIdDbQueryValidator.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Bones.Database.Operations.ProjectManagement.Projects.GetProjectByIdDb; - -internal sealed class GetProjectByIdDbQueryValidator : AbstractValidator -{ - public override Task ValidateAsync(ValidationContext context, CancellationToken cancellation = new()) - { - RuleFor(x => x.ProjectId).NotNull().NotEqual(Guid.Empty); - - return base.ValidateAsync(context, cancellation); - } -} \ No newline at end of file diff --git a/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs b/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs index 00344fc..716c6ef 100644 --- a/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs +++ b/Backend/Bones.Shared.Backend/Models/BonesResponseBase.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace Bones.Shared.Backend.Models; @@ -17,6 +18,7 @@ protected BonesResponseBase() { } /// /// Was the command successful? /// + [MemberNotNullWhen(returnValue: false, nameof(FailureReasons))] public required bool Success { get; init; } /// @@ -27,5 +29,5 @@ protected BonesResponseBase() { } /// /// If the command failed, why? /// - public Dictionary? FailureReasons { get; init; } + public Dictionary>? FailureReasons { get; init; } } \ 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 06156f2..08e7521 100644 --- a/Backend/Bones.Shared.Backend/Models/CommandResponse.cs +++ b/Backend/Bones.Shared.Backend/Models/CommandResponse.cs @@ -32,7 +32,7 @@ private CommandResponse() { } /// /// /// - public static CommandResponse Fail(Dictionary failureReasons) => new() + public static CommandResponse Fail(Dictionary> failureReasons) => new() { Success = false, FailureReasons = failureReasons @@ -46,9 +46,9 @@ private CommandResponse() { } public static CommandResponse Fail(string? failureReason = null) => new() { Success = false, - FailureReasons = string.IsNullOrEmpty(failureReason) ? null : new() + FailureReasons = new() { - { "Failure", [ failureReason ] } + { "server", [ failureReason ?? "Unknown failure reason" ] } } }; @@ -61,7 +61,7 @@ private CommandResponse() { } Success = false, FailureReasons = new() { - {"Forbidden", [ "Forbidden." ] } + {"server", [ "Forbidden." ] } }, Forbidden = true }; diff --git a/Backend/Bones.Shared.Backend/Models/QueryResponse.cs b/Backend/Bones.Shared.Backend/Models/QueryResponse.cs index 776fcf8..263ef33 100644 --- a/Backend/Bones.Shared.Backend/Models/QueryResponse.cs +++ b/Backend/Bones.Shared.Backend/Models/QueryResponse.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Identity; @@ -34,7 +35,7 @@ private QueryResponse() { } /// /// /// - public static QueryResponse Fail(Dictionary? failureReasons = null) => new() + public static QueryResponse Fail(Dictionary>? failureReasons = null) => new() { Success = false, FailureReasons = failureReasons @@ -48,9 +49,9 @@ private QueryResponse() { } public static QueryResponse Fail(string? failureReason = null) => new() { Success = false, - FailureReasons = string.IsNullOrEmpty(failureReason) ? null : new() + FailureReasons = new() { - { "Failure", [ failureReason ] } + { "server", [ failureReason ?? "Unknown failure reason" ] } } }; @@ -63,7 +64,7 @@ private QueryResponse() { } Success = false, FailureReasons = new() { - {"Forbidden", [ "Forbidden." ] } + {"server", [ "Forbidden." ] } }, Forbidden = true }; diff --git a/Backend/Bones.Shared.Backend/PipelineBehaviors/CommandBehavior.cs b/Backend/Bones.Shared.Backend/PipelineBehaviors/CommandBehavior.cs index a85333a..fdb4aa3 100644 --- a/Backend/Bones.Shared.Backend/PipelineBehaviors/CommandBehavior.cs +++ b/Backend/Bones.Shared.Backend/PipelineBehaviors/CommandBehavior.cs @@ -8,13 +8,13 @@ public class CommandBehavior(IEnumerable> request : PipelineBehaviorBase(requestValidators) where TRequest : notnull { /// - protected override (bool success, Dictionary? failReason, bool forbidden) GetResult(CommandResponse response) + protected override (bool success, Dictionary>? failReason, bool forbidden) GetResult(CommandResponse response) { return (response.Success, response.FailureReasons, response.Forbidden); } /// - protected override CommandResponse GetFailedResponse(Dictionary failReason) + protected override CommandResponse GetFailedResponse(Dictionary> failReason) { return CommandResponse.Fail(failReason); } diff --git a/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs b/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs index ee3350b..a1409a8 100644 --- a/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs +++ b/Backend/Bones.Shared.Backend/PipelineBehaviors/PipelineBehaviorBase.cs @@ -18,14 +18,14 @@ public abstract class PipelineBehaviorBase(IEnumerable /// /// - protected abstract (bool success, Dictionary? failReason, bool forbidden) GetResult(TResponse response); + protected abstract (bool success, Dictionary>? failReason, bool forbidden) GetResult(TResponse response); /// /// Generate a failure response with the provided type /// /// /// - protected abstract TResponse GetFailedResponse(Dictionary failReason); + protected abstract TResponse GetFailedResponse(Dictionary> failReason); // I don't really like using these, but in this case it's safer to do so. // Logging the request could potentially log a password, @@ -60,7 +60,7 @@ public async Task Handle(TRequest request, RequestHandlerDelegate context = new(request); - Dictionary errors = requestValidators + Dictionary> errors = requestValidators .Select(x => x.Validate(context)) .SelectMany(x => x.Errors) .Where(x => x != null) @@ -70,7 +70,7 @@ public async Task Handle(TRequest request, RequestHandlerDelegate new { Key = propertyName, - Values = errorMessages.Distinct().ToArray(), + Values = errorMessages.Distinct().ToList(), }) .ToDictionary(x => x.Key, x => x.Values); @@ -96,7 +96,7 @@ public async Task Handle(TRequest request, RequestHandlerDelegate? failReasons, bool forbidden) = GetResult(response); + (bool success, Dictionary>? failReasons, bool forbidden) = GetResult(response); if (forbidden) { @@ -121,7 +121,7 @@ private void StartDebugLog(TRequest request) _stopwatch = Stopwatch.StartNew(); } - private void StopDebugLog(TRequest request, bool success, Dictionary? failReasons, Exception? exception) + private void StopDebugLog(TRequest request, bool success, Dictionary>? failReasons, Exception? exception) { if (!_debugLog || _stopwatch == null) { diff --git a/Backend/Bones.Shared.Backend/PipelineBehaviors/QueryBehavior.cs b/Backend/Bones.Shared.Backend/PipelineBehaviors/QueryBehavior.cs index 987a49f..aa41d99 100644 --- a/Backend/Bones.Shared.Backend/PipelineBehaviors/QueryBehavior.cs +++ b/Backend/Bones.Shared.Backend/PipelineBehaviors/QueryBehavior.cs @@ -8,13 +8,13 @@ public class QueryBehavior(IEnumerable> r : PipelineBehaviorBase>(requestValidators) where TRequest : notnull { /// - protected override (bool success, Dictionary? failReason, bool forbidden) GetResult(QueryResponse response) + protected override (bool success, Dictionary>? failReason, bool forbidden) GetResult(QueryResponse response) { return (response.Success, response.FailureReasons, response.Forbidden); } /// - protected override QueryResponse GetFailedResponse(Dictionary failReason) + protected override QueryResponse GetFailedResponse(Dictionary> failReason) { return QueryResponse.Fail(failReason); } diff --git a/Bones.Shared/Consts/FrontEndUrls.cs b/Bones.Shared/Consts/FrontEndUrls.cs index e036e49..77663a0 100644 --- a/Bones.Shared/Consts/FrontEndUrls.cs +++ b/Bones.Shared/Consts/FrontEndUrls.cs @@ -69,6 +69,7 @@ public static class Account public static class Project { private const string _project = "/Project"; + private const string _projectWithId = $"{_project}/{{ProjectId:guid}}"; /// /// Create project page @@ -78,9 +79,30 @@ public static class Project /// /// Project dashboard page /// - public const string DASHBOARD = _project + "/{ProjectId:guid}/Dashboard/"; + public const string PROJECT_DASHBOARD = $"{_projectWithId}/Dashboard"; + + /// + /// + /// + public static class Initiative + { + private const string _initiative = $"{_projectWithId}/Initiative"; + private const string _initiativeWithId = $"{_initiative}/{{InitiativeId:guid}}"; + + /// + /// Create initiative page + /// + public const string CREATE_INITIATIVE = $"{_initiative}/Create"; + + /// + /// Initiative dashboard page + /// + public const string INITIATIVE_DASHBOARD = $"{_initiativeWithId}/Dashboard"; + } } + + /// /// System Admin pages /// diff --git a/Docs/.obsidian/workspace.json b/Docs/.obsidian/workspace.json index ac79b15..b1080aa 100644 --- a/Docs/.obsidian/workspace.json +++ b/Docs/.obsidian/workspace.json @@ -155,6 +155,7 @@ }, "left-ribbon": { "hiddenItems": { + "table-editor-obsidian:Advanced Tables Toolbar": false, "switcher:Open quick switcher": false, "graph:Open graph view": false, "canvas:Create new canvas": false, diff --git a/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs b/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs index 27fa025..609b971 100644 --- a/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs +++ b/Frontend/Bones.Api.Client/AutoGenBonesApiClient.cs @@ -351,12 +351,12 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 400) { - 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>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else { @@ -931,12 +931,12 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Creates a new project + /// Gets the projects for the current user, or specified organization /// /// The request /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task CreateProjectAsync(CreateProjectRequest body = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task> GetProjectsByOwnerAsync(GetProjectsByOwnerRequest body = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { var client_ = _httpClient; var disposeClient_ = false; @@ -944,17 +944,17 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, typeof(CreateProjectRequest), JsonSerializerSettings); + var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, typeof(GetProjectsByOwnerRequest), JsonSerializerSettings); var content_ = new System.Net.Http.ByteArrayContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("POST"); + 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: "Project/create" - urlBuilder_.Append("Project/create"); + // Operation Path: "Project/projects/by-owner" + urlBuilder_.Append("Project/projects/by-owner"); PrepareRequest(client_, request_, urlBuilder_); @@ -1011,7 +1011,7 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 200) { - 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); @@ -1021,12 +1021,12 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 400) { - 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>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else { @@ -1050,12 +1050,11 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Gets the projects for the current user, or specified organization + /// Gets the users current quick select projects /// - /// The request /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetProjectsByOwnerAsync(GetProjectsByOwnerRequest body = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task> GetProjectQuickSelectAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { var client_ = _httpClient; var disposeClient_ = false; @@ -1063,17 +1062,13 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, typeof(GetProjectsByOwnerRequest), JsonSerializerSettings); - var content_ = new System.Net.Http.ByteArrayContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; 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: "Project/projects/by-owner" - urlBuilder_.Append("Project/projects/by-owner"); + // Operation Path: "Project/projects/quick-select" + urlBuilder_.Append("Project/projects/quick-select"); PrepareRequest(client_, request_, urlBuilder_); @@ -1130,7 +1125,7 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 200) { - 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); @@ -1138,128 +1133,24 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() return objectResponse_.Object; } else - if (status_ == 400) - { - 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>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - 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. - /// - /// Gets the projects the current user is able to access - /// - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetProjectsUserCanAccessAsync(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: "Project/projects" - urlBuilder_.Append("Project/projects"); - - 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) + if (status_ == 404) { 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; + throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 400) { - 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>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else { @@ -1373,12 +1264,12 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 400) { - 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>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException>>("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else { @@ -1402,11 +1293,12 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Throws a BonesException to test the ApiExceptionHandler + /// Creates a new project /// + /// The request /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetBonesExceptionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task CreateProjectAsync(CreateProjectRequest body = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { var client_ = _httpClient; var disposeClient_ = false; @@ -1414,13 +1306,17 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("GET"); + var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, typeof(CreateProjectRequest), JsonSerializerSettings); + var content_ = new System.Net.Http.ByteArrayContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + 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: "Test/bones-exception" - urlBuilder_.Append("Test/bones-exception"); + // Operation Path: "Project/create" + urlBuilder_.Append("Project/create"); PrepareRequest(client_, request_, urlBuilder_); @@ -1477,7 +1373,7 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 200) { - 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); @@ -1485,6 +1381,16 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() return objectResponse_.Object; } else + if (status_ == 400) + { + 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("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + 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); @@ -1506,25 +1412,36 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Throws a ForbiddenAccessException to test the ApiExceptionHandler + /// Creates a new project /// + /// The ID of the project to create this in + /// The request /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetForbiddenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + public virtual async System.Threading.Tasks.Task CreateInitiativeAsync(System.Guid projectId, CreateInitiativeRequest body = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { + if (projectId == null) + throw new System.ArgumentNullException("projectId"); + var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("GET"); + var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, typeof(CreateInitiativeRequest), JsonSerializerSettings); + var content_ = new System.Net.Http.ByteArrayContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + 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: "Test/forbidden" - urlBuilder_.Append("Test/forbidden"); + // Operation Path: "Project/{projectId}/initiative/create" + urlBuilder_.Append("Project/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(projectId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/initiative/create"); PrepareRequest(client_, request_, urlBuilder_); @@ -1581,7 +1498,7 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() else if (status_ == 200) { - 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); @@ -1589,6 +1506,16 @@ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings() return objectResponse_.Object; } else + if (status_ == 400) + { + 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("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + 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); @@ -1717,6 +1644,21 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu } } + /// + /// Request to create a new initiative + /// + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial record CreateInitiativeRequest + { + /// + /// Name of the initiative to create + /// + + [System.Text.Json.Serialization.JsonPropertyName("name")] + public string Name { get; set; } + + } + /// /// Request to create a new project /// @@ -1739,15 +1681,18 @@ public partial record CreateProjectRequest } + /// + /// The response body is empty, this is a workaround for the limitations of the API client. + /// [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial record EmptyResponseActionResult + public partial record ErrorResponse { + /// + /// The errors that occurred, with the key being either the input that was invalid and the list of reasons it was invalid, or 'server' with the list of server errors. + /// - [System.Text.Json.Serialization.JsonPropertyName("result")] - public object Result { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("value")] - public object Value { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("errors")] + public System.Collections.Generic.Dictionary> Errors { get; set; } } @@ -1794,9 +1739,6 @@ public partial record GetProjectDashboardResponse [System.Text.Json.Serialization.JsonPropertyName("projectName")] public string ProjectName { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("initiativeCount")] - public int? InitiativeCount { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("ownerType")] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] public OwnershipType? OwnerType { get; set; } @@ -1804,6 +1746,24 @@ public partial record GetProjectDashboardResponse [System.Text.Json.Serialization.JsonPropertyName("ownerId")] public System.Guid? OwnerId { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("initiativeCount")] + public int? InitiativeCount { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("initiatives")] + public System.Collections.Generic.List Initiatives { 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 GetProjectQuickSelectResponse + { + + [System.Text.Json.Serialization.JsonPropertyName("projectId")] + public System.Guid? ProjectId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("projectName")] + public string ProjectName { get; set; } + } /// @@ -1826,6 +1786,21 @@ public partial record GetProjectsByOwnerRequest } + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + public partial record InitiativeListModel + { + + [System.Text.Json.Serialization.JsonPropertyName("initiativeId")] + public System.Guid? InitiativeId { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("initiativeName")] + public string InitiativeName { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("queueCount")] + public int? QueueCount { get; set; } + + } + /// /// Request to login /// diff --git a/Frontend/Bones.Api.Client/nswag.json b/Frontend/Bones.Api.Client/nswag.json index 6a596f9..303231b 100644 --- a/Frontend/Bones.Api.Client/nswag.json +++ b/Frontend/Bones.Api.Client/nswag.json @@ -50,10 +50,10 @@ "generateOptionalParameters": true, "generateJsonMethods": false, "enforceFlagEnums": true, - "parameterArrayType": "System.Collections.Generic.IEnumerable", - "parameterDictionaryType": "System.Collections.Generic.IDictionary", - "responseArrayType": "System.Collections.Generic.IEnumerable", - "responseDictionaryType": "System.Collections.Generic.IDictionary", + "parameterArrayType": "System.Collections.Generic.List", + "parameterDictionaryType": "System.Collections.Generic.Dictionary", + "responseArrayType": "System.Collections.Generic.List", + "responseDictionaryType": "System.Collections.Generic.Dictionary", "wrapResponses": false, "wrapResponseMethods": [], "generateResponseClasses": true, @@ -65,9 +65,9 @@ "dateTimeType": "System.DateTimeOffset", "timeType": "System.TimeOnly", "timeSpanType": "System.TimeSpan", - "arrayType": "System.Collections.Generic.IEnumerable", + "arrayType": "System.Collections.Generic.List", "arrayInstanceType": "System.Collections.Generic.List", - "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryType": "System.Collections.Generic.Dictionary", "dictionaryInstanceType": "System.Collections.Generic.Dictionary", "arrayBaseType": "System.Collections.Generic.List", "dictionaryBaseType": "System.Collections.Generic.Dictionary", diff --git a/Frontend/Bones.WebUI/Layout/MainLayout.razor.cs b/Frontend/Bones.WebUI/Layout/MainLayout.razor.cs index daab277..cc0231f 100644 --- a/Frontend/Bones.WebUI/Layout/MainLayout.razor.cs +++ b/Frontend/Bones.WebUI/Layout/MainLayout.razor.cs @@ -1,3 +1,4 @@ +using Bones.Api.Client; using Bones.Shared.Consts; using Microsoft.AspNetCore.Components; using MudBlazor; @@ -55,15 +56,15 @@ protected override async Task OnInitializedAsync() { try { - IDictionary projects = await ApiClient.GetProjectsUserCanAccessAsync(); + List projects = await ApiClient.GetProjectQuickSelectAsync(); Projects = []; - foreach (KeyValuePair proj in projects) + foreach (GetProjectQuickSelectResponse proj in projects) { Projects.Add(new() { - ProjectId = Guid.Parse(proj.Key), - ProjectName = proj.Value + ProjectId = proj.ProjectId, + ProjectName = proj.ProjectName }); } } @@ -97,7 +98,7 @@ private void OnGoToProjectChanged(IEnumerable? selectedProject) } else { - NavManager.NavigateTo(FrontEndUrls.Project.DASHBOARD.Replace("{ProjectId:guid}", selected.Value.ToString())); + NavManager.NavigateTo(FrontEndUrls.Project.PROJECT_DASHBOARD.Replace("{ProjectId:guid}", selected.Value.ToString())); } } } diff --git a/Frontend/Bones.WebUI/Pages/Account/RegisterPage.razor.cs b/Frontend/Bones.WebUI/Pages/Account/RegisterPage.razor.cs index 1c4ba92..a95c65d 100644 --- a/Frontend/Bones.WebUI/Pages/Account/RegisterPage.razor.cs +++ b/Frontend/Bones.WebUI/Pages/Account/RegisterPage.razor.cs @@ -1,3 +1,5 @@ +using System.Net; +using Bones.Api.Client; using Bones.Shared; using Microsoft.AspNetCore.Components; using MudBlazor; @@ -51,7 +53,20 @@ await ApiClient.RegisterAsync(new() RegistrationSuccess = true; } - catch (Exception ex) + catch (ApiException>> ex) + { + if (ex.StatusCode == (int)HttpStatusCode.BadRequest) + { + List apiErrors = []; + foreach (KeyValuePair> kvp in ex.Result) + { + apiErrors.AddRange(kvp.Value.Select(error => $"{kvp.Key}: {error}")); + } + + ValidationErrors = apiErrors.ToArray(); + } + } + catch (ApiException ex) { Logger.LogError(ex, "Error while registering user"); RegistrationSuccess = false; @@ -71,27 +86,27 @@ private IEnumerable PasswordStrengthCheck() if (Password.Text.Length <= 8) { - yield return "Password be at least 8 characters long."; + //yield return "Password be at least 8 characters long."; } if (!StandardRegexes.PasswordContainsUpper().IsMatch(Password.Text)) { - yield return "Password must contain at least one capital letter"; + //yield return "Password must contain at least one capital letter"; } if (!StandardRegexes.PasswordContainsLower().IsMatch(Password.Text)) { - yield return "Password must contain at least one lowercase letter"; + //yield return "Password must contain at least one lowercase letter"; } if (!StandardRegexes.PasswordContainsNumber().IsMatch(Password.Text)) { - yield return "Password must contain at least one digit"; + //yield return "Password must contain at least one digit"; } if (!StandardRegexes.PasswordContainsSpecial().IsMatch(Password.Text)) { - yield return "Password must contain at least one special character"; + //yield return "Password must contain at least one special character"; } } diff --git a/Frontend/Bones.WebUI/Pages/Project/CreateInitiativePage.razor b/Frontend/Bones.WebUI/Pages/Project/CreateInitiativePage.razor new file mode 100644 index 0000000..0ae000e --- /dev/null +++ b/Frontend/Bones.WebUI/Pages/Project/CreateInitiativePage.razor @@ -0,0 +1,30 @@ +@attribute [Route(FrontEndUrls.Project.Initiative.CREATE_INITIATIVE)] +@inject ILogger Logger +@layout AuthenticatedLayout + +

Create Initiative

+ + + + + + +
+ Create +
+
+
+
+ + + + @foreach (string error in ValidationErrors) + { + + @error + + } + +
\ No newline at end of file diff --git a/Frontend/Bones.WebUI/Pages/Project/CreateInitiativePage.razor.cs b/Frontend/Bones.WebUI/Pages/Project/CreateInitiativePage.razor.cs new file mode 100644 index 0000000..f695306 --- /dev/null +++ b/Frontend/Bones.WebUI/Pages/Project/CreateInitiativePage.razor.cs @@ -0,0 +1,60 @@ +using System.Net; +using Bones.Api.Client; +using Bones.Shared; +using Bones.Shared.Consts; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace Bones.WebUI.Pages.Project; + +/// +/// Create Initiative page +/// +public partial class CreateInitiativePage : ComponentBase +{ + /// + /// The ID of the project to load in this dashboard + /// + [Parameter] + public Guid ProjectId { get; set; } + + /// + /// Did the request to the API result in an error? + /// + public bool ApiError { get; set; } = false; + + /// + /// Is the form valid? + /// + public bool FormValid { get; set; } + + /// + /// The issues with the users inputs + /// + public string[] ValidationErrors { get; set; } = []; + + private MudTextField InitiativeName { get; set; } = new(); + + /// + /// Send the request to register to the API, if it errors tell the user what went wrong. + /// + public async Task SendCreateRequestAsync() + { + try + { + ApiError = false; + + Guid projectId = await ApiClient.CreateInitiativeAsync(ProjectId, new() + { + Name = InitiativeName.Text + }); + + NavManager.NavigateTo(FrontEndUrls.Project.PROJECT_DASHBOARD.Replace("{ProjectId:guid}", projectId.ToString())); + } + catch (ApiException ex) + { + Logger.LogError(ex, "Error while creating initiative"); + ApiError = true; + } + } +} \ No newline at end of file diff --git a/Frontend/Bones.WebUI/Pages/Project/CreateProjectPage.razor.cs b/Frontend/Bones.WebUI/Pages/Project/CreateProjectPage.razor.cs index e425515..1d3707d 100644 --- a/Frontend/Bones.WebUI/Pages/Project/CreateProjectPage.razor.cs +++ b/Frontend/Bones.WebUI/Pages/Project/CreateProjectPage.razor.cs @@ -1,3 +1,5 @@ +using System.Net; +using Bones.Api.Client; using Bones.Shared; using Bones.Shared.Consts; using Microsoft.AspNetCore.Components; @@ -42,11 +44,11 @@ public async Task SendCreateRequestAsync() OrganizationId = null }); - NavManager.NavigateTo(FrontEndUrls.Project.DASHBOARD.Replace("{ProjectId:guid}", projectId.ToString())); + NavManager.NavigateTo(FrontEndUrls.Project.PROJECT_DASHBOARD.Replace("{ProjectId:guid}", projectId.ToString())); } - catch (Exception ex) + catch (ApiException ex) { - Logger.LogError(ex, "Error while registering user"); + Logger.LogError(ex, "Error while creating project"); ApiError = true; } } diff --git a/Frontend/Bones.WebUI/Pages/Project/InitiativeDashboardPage.razor b/Frontend/Bones.WebUI/Pages/Project/InitiativeDashboardPage.razor new file mode 100644 index 0000000..dc33358 --- /dev/null +++ b/Frontend/Bones.WebUI/Pages/Project/InitiativeDashboardPage.razor @@ -0,0 +1,35 @@ +@attribute [Route(FrontEndUrls.Project.Initiative.INITIATIVE_DASHBOARD)] +@inject ILogger Logger +@layout AuthenticatedLayout + +Initiative Dashboard (WIP) + + Project ID: @ProjectId + Project Name: @InitiativeName + + +
+ Queues + + Create Initiative +
+ + + + Number of initiatives: @QueueCount + + + + Initiative ID + Initiative Name + Queue Count + Go to Initiative + + + @context.InitiativeId + @context.InitiativeName + @context.QueueCount + Dashboard + + + \ No newline at end of file diff --git a/Frontend/Bones.WebUI/Pages/Project/InitiativeDashboardPage.razor.cs b/Frontend/Bones.WebUI/Pages/Project/InitiativeDashboardPage.razor.cs new file mode 100644 index 0000000..6a17186 --- /dev/null +++ b/Frontend/Bones.WebUI/Pages/Project/InitiativeDashboardPage.razor.cs @@ -0,0 +1,58 @@ +using Bones.Api.Client; +using Microsoft.AspNetCore.Components; + +namespace Bones.WebUI.Pages.Project; + +/// +/// Dashboard for projects +/// +public partial class InitiativeDashboardPage : ComponentBase +{ + /// + /// The ID of the project to load in this dashboard + /// + [Parameter] + public Guid ProjectId { get; set; } + + /// + /// The ID of the initiative to load in this dashboard + /// + [Parameter] + public Guid InitiativeId { get; set; } + + /// + /// The name of the project, received from the API + /// + public string? InitiativeName { get; set; } + + /// + /// The number of initiatives on the project, received from the API + /// + public int? QueueCount { get; set; } + + /// + /// + /// + public bool QueueListLoading { get; set; } = true; + + /// + /// + /// + public List QueueList { get; set; } = []; + + /// + /// + /// + protected override async Task OnInitializedAsync() + { + // TODO: Garbage data for now, do real later + GetProjectDashboardResponse dashboardResponse = await ApiClient.GetProjectDashboardAsync(ProjectId); + InitiativeName = dashboardResponse.ProjectName; + + QueueCount = dashboardResponse.InitiativeCount; + QueueList = dashboardResponse.Initiatives; + QueueListLoading = false; + + await base.OnInitializedAsync(); + } +} \ No newline at end of file diff --git a/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor b/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor index d2ba62c..15e16f2 100644 --- a/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor +++ b/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor @@ -1,13 +1,37 @@ -@attribute [Route(FrontEndUrls.Project.DASHBOARD)] +@attribute [Route(FrontEndUrls.Project.PROJECT_DASHBOARD)] @inject ILogger Logger @layout AuthenticatedLayout -

Project Dashboard

- +Project Dashboard Project ID: @ProjectId Project Name: @ProjectName - Number of initiatives: @InitiativeCount Owner Type: @OwnerType Owner ID: @OwnerId + + +
+ Initiatives + + Create Initiative +
+ + + + Number of initiatives: @InitiativeCount + + + + Initiative ID + Initiative Name + Queue Count + Go to Initiative + + + @context.InitiativeId + @context.InitiativeName + @context.QueueCount + Dashboard + + \ No newline at end of file diff --git a/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor.cs b/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor.cs index 5a0acc9..77d9f90 100644 --- a/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor.cs +++ b/Frontend/Bones.WebUI/Pages/Project/ProjectDashboardPage.razor.cs @@ -19,20 +19,30 @@ public partial class ProjectDashboardPage : ComponentBase ///
public string? ProjectName { get; set; } + /// + /// The type of owner for the project, received from the API + /// + public OwnershipType? OwnerType { get; set; } + + /// + /// The ID of the owner of the project, received from the API + /// + public Guid? OwnerId { get; set; } + /// /// The number of initiatives on the project, received from the API /// public int? InitiativeCount { get; set; } /// - /// The type of owner for the project, received from the API + /// /// - public OwnershipType? OwnerType { get; set; } + public bool InitiativeListLoading { get; set; } = true; /// - /// The ID of the owner of the project, received from the API + /// /// - public Guid? OwnerId { get; set; } + public List InitiativeList { get; set; } = []; /// /// @@ -41,11 +51,13 @@ protected override async Task OnInitializedAsync() { GetProjectDashboardResponse dashboardResponse = await ApiClient.GetProjectDashboardAsync(ProjectId); ProjectName = dashboardResponse.ProjectName; - InitiativeCount = dashboardResponse.InitiativeCount; OwnerType = dashboardResponse.OwnerType; OwnerId = dashboardResponse.OwnerId; - // @page "/Project/{ProjectId:guid}/Dashboard/" - // + + InitiativeCount = dashboardResponse.InitiativeCount; + InitiativeList = dashboardResponse.Initiatives; + InitiativeListLoading = false; + await base.OnInitializedAsync(); } } \ No newline at end of file diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs index d00aaba..96c6020 100644 --- a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueConfirmationEmailTests.cs @@ -1,5 +1,5 @@ -using Bones.Backend.Features.AccountManagement.QueueConfirmationEmail; -using Bones.Backend.Features.AccountManagement.RegisterUser; +using Bones.Backend.Features.Accounts.QueueConfirmationEmail; +using Bones.Backend.Features.Accounts.RegisterUser; using Bones.Database.DbSets.AccountManagement; using Bones.Database.DbSets.SystemQueues; using Bones.Shared.Backend.Models; diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs index 9ae90d4..3ebb255 100644 --- a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/QueueForgotPasswordEmailTests.cs @@ -1,5 +1,5 @@ -using Bones.Backend.Features.AccountManagement.QueueForgotPasswordEmail; -using Bones.Backend.Features.AccountManagement.RegisterUser; +using Bones.Backend.Features.Accounts.QueueForgotPasswordEmail; +using Bones.Backend.Features.Accounts.RegisterUser; using Bones.Database.DbSets.SystemQueues; using Bones.Shared.Backend.Models; using Bones.Testing.Shared.Backend; diff --git a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs index 811c061..850fb9b 100644 --- a/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs +++ b/Tests/Backend/Bones.Backend.UnitTests/Features/AccountManagement/RegisterUserTests.cs @@ -1,4 +1,4 @@ -using Bones.Backend.Features.AccountManagement.RegisterUser; +using Bones.Backend.Features.Accounts.RegisterUser; using Bones.Database.DbSets.AccountManagement; using Bones.Database.DbSets.SystemQueues; using Bones.Shared.Backend.Models;