Skip to content

Commit

Permalink
Create project and project dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
CorruptComputer committed Nov 10, 2024
1 parent 914cfcb commit 98a1747
Show file tree
Hide file tree
Showing 29 changed files with 1,076 additions and 217 deletions.
77 changes: 73 additions & 4 deletions Backend/Bones.Api/Controllers/ProjectController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
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.Database.DbSets.AccountManagement;
using Bones.Database.DbSets.ProjectManagement;
using Bones.Shared.Backend.Enums;
using Bones.Shared.Backend.Models;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -51,19 +55,84 @@ public record GetProjectsByOwnerRequest(OwnershipType OwnerType, Guid? Organizat
/// Gets the projects for the current user, or specified organization
/// </summary>
/// <param name="request">The request</param>
/// <returns>Created if created, otherwise BadRequest with a message of what went wrong.</returns>
[HttpGet("projects", Name = "GetProjectsByOwnerAsync")]
[ProducesResponseType<List<(Guid, string)>>(StatusCodes.Status200OK)]
/// <returns>Ok with the results if successful, otherwise BadRequest with a message of what went wrong.</returns>
[HttpGet("projects/by-owner", Name = "GetProjectsByOwnerAsync")]
[ProducesResponseType<Dictionary<Guid, string>>(StatusCodes.Status200OK)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> GetProjectsByOwnerAsync([FromBody] GetProjectsByOwnerRequest request)
{
BonesUser currentUser = await GetCurrentBonesUserAsync();
QueryResponse<List<(Guid Id, string Name)>> response = await Sender.Send(new GetProjectsByOwnerQuery(request.OwnerType, request.OrganizationId ?? currentUser.Id, currentUser));
QueryResponse<Dictionary<Guid, string>> response = await Sender.Send(new GetProjectsByOwnerQuery(request.OwnerType, request.OrganizationId ?? currentUser.Id, currentUser));
if (!response.Success)
{
return BadRequest(response.FailureReasons);
}

return Ok(response.Result);
}

/// <summary>
/// Gets the projects the current user is able to access
/// </summary>
/// <returns>Ok with the results if successful, otherwise BadRequest with a message of what went wrong.</returns>
[HttpGet("projects", Name = "GetProjectsUserCanAccessAsync")]
[ProducesResponseType<Dictionary<Guid, string>>(StatusCodes.Status200OK)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> GetProjectsUserCanAccessAsync()
{
BonesUser currentUser = await GetCurrentBonesUserAsync();
QueryResponse<Dictionary<Guid, string>> response = await Sender.Send(new GetProjectsUserCanAccessQuery(currentUser));
if (!response.Success)
{
return BadRequest(response.FailureReasons);
}

return Ok(response.Result);
}

/// <summary>
///
/// </summary>
/// <param name="ProjectId"></param>
/// <param name="ProjectName"></param>
/// <param name="InitiativeCount"></param>
/// <param name="OwnerType"></param>
/// <param name="OwnerId"></param>
[Serializable]
[JsonSerializable(typeof(GetProjectDashboardResponse))]
public record GetProjectDashboardResponse(
Guid ProjectId,
string ProjectName,
int InitiativeCount,
OwnershipType OwnerType,
Guid OwnerId
);

/// <summary>
/// Gets a projects dashboard information
/// </summary>
/// <param name="projectId"></param>
/// <returns>Ok with the results if successful, otherwise BadRequest with a message of what went wrong.</returns>
[HttpGet("{projectId:guid}/dashboard", Name = "GetProjectDashboardAsync")]
[ProducesResponseType<GetProjectDashboardResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<Dictionary<string, string[]>>(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> GetProjectDashboardAsync(Guid projectId)
{
BonesUser currentUser = await GetCurrentBonesUserAsync();
QueryResponse<Project> response = await Sender.Send(new GetProjectByIdQuery(projectId, currentUser));
if (!response.Success || response.Result == null)
{
return BadRequest(response.FailureReasons);
}

// We know they won't be null
Guid ownerId = response.Result.OwnerType == OwnershipType.User
? response.Result.OwningUser!.Id
: response.Result.OwningOrganization!.Id;

GetProjectDashboardResponse resp = new(response.Result.Id, response.Result.Name,
response.Result.Initiatives.Count, response.Result.OwnerType, ownerId);

return Ok(resp);
}
}
191 changes: 183 additions & 8 deletions Backend/Bones.Api/OpenApi/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@
}
}
},
"/Project/projects": {
"/Project/projects/by-owner": {
"get": {
"tags": [
"Project"
Expand Down Expand Up @@ -639,9 +639,79 @@
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/GuidStringValueTuple"
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/Project/projects": {
"get": {
"tags": [
"Project"
],
"summary": "Gets the projects the current user is able to access",
"operationId": "GetProjectsUserCanAccessAsync",
"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": "object",
"additionalProperties": {
"type": "string"
}
}
}
Expand All @@ -666,6 +736,85 @@
}
}
},
"/Project/{projectId}/dashboard": {
"get": {
"tags": [
"Project"
],
"summary": "Gets a projects dashboard information",
"operationId": "GetProjectDashboardAsync",
"parameters": [
{
"name": "projectId",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmptyResponse"
}
}
}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmptyResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmptyResponse"
}
}
}
},
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetProjectDashboardResponse"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/Test/bones-exception": {
"get": {
"tags": [
Expand Down Expand Up @@ -859,6 +1008,36 @@
"additionalProperties": false,
"description": ""
},
"GetProjectDashboardResponse": {
"type": "object",
"properties": {
"projectId": {
"type": "string",
"description": "",
"format": "uuid"
},
"projectName": {
"type": "string",
"description": "",
"nullable": true
},
"initiativeCount": {
"type": "integer",
"description": "",
"format": "int32"
},
"ownerType": {
"$ref": "#/components/schemas/OwnershipType"
},
"ownerId": {
"type": "string",
"description": "",
"format": "uuid"
}
},
"additionalProperties": false,
"description": ""
},
"GetProjectsByOwnerRequest": {
"type": "object",
"properties": {
Expand All @@ -875,10 +1054,6 @@
"additionalProperties": false,
"description": "Request to get the projects for a given User/Organization"
},
"GuidStringValueTuple": {
"type": "object",
"additionalProperties": false
},
"LoginUserApiRequest": {
"type": "object",
"properties": {
Expand Down
1 change: 0 additions & 1 deletion Backend/Bones.Backend/Bones.Backend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<ItemGroup>
<Folder Include="Features\ProjectManagement\Initiatives\" />
<Folder Include="Features\ProjectManagement\Projects\DeleteProjectById\" />
<Folder Include="Features\ProjectManagement\Projects\GetProjectById\" />
<Folder Include="Features\ProjectManagement\Projects\UpdateProject\" />
<Folder Include="Managers\" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async Task<CommandResponse> Handle(CreateProjectCommand request, Cancella
return CommandResponse.Forbid();
}

const string perm = BonesClaimTypes.Role.Organization.Project.CREATE_PROJECT;
const string perm = BonesClaimTypes.Role.Project.CREATE_PROJECT;
bool? hasOrganizationPermission =
await sender.Send(new UserHasOrganizationPermissionQuery(organization.Id, request.RequestingUser, perm), cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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<GetProjectByIdQuery, QueryResponse<Project>>
{
public async Task<QueryResponse<Project>> 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<Project>.Forbid();
}

Project? project = await sender.Send(new GetProjectByIdDbQuery(request.ProjectId), cancellationToken);

if (project is null)
{
return QueryResponse<Project>.Fail("Project not found");
}

return QueryResponse<Project>.Pass(project);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Bones.Database.DbSets.AccountManagement;
using Bones.Database.DbSets.ProjectManagement;

namespace Bones.Backend.Features.ProjectManagement.Projects.GetProjectById;

/// <summary>
///
/// </summary>
/// <param name="ProjectId"></param>
/// <param name="RequestingUser"></param>
public sealed record GetProjectByIdQuery(Guid ProjectId, BonesUser RequestingUser) : IRequest<QueryResponse<Project>>;
Loading

0 comments on commit 98a1747

Please sign in to comment.