Skip to content

Commit

Permalink
feat: TaskOwnerReport code without KPIs and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathanio123 committed Oct 22, 2024
1 parent 74772ba commit 18e54a9
Show file tree
Hide file tree
Showing 19 changed files with 764 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/Fusion.Summary.Api/BaseController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public class BaseController : ControllerBase
protected ActionResult DepartmentNotFound(string sapDepartmentId) =>
FusionApiError.NotFound(sapDepartmentId, $"Department with sap id '{sapDepartmentId}' was not found");

protected ActionResult ProjectNotFound(Guid projectId) =>
FusionApiError.NotFound(projectId, $"Project with id '{projectId}' was not found");

protected ActionResult SapDepartmentIdRequired() =>
FusionApiError.InvalidOperation("SapDepartmentIdRequired", "SapDepartmentId route parameter is required");

Expand Down
28 changes: 28 additions & 0 deletions src/Fusion.Summary.Api/Controllers/ApiModels/ApiProject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Fusion.Summary.Api.Domain.Models;

namespace Fusion.Summary.Api.Controllers.ApiModels;

public class ApiProject
{
public required Guid Id { get; set; }

public required string Name { get; set; }
public required Guid OrgProjectExternalId { get; set; }

public Guid? DirectorAzureUniqueId { get; set; }

public Guid[] AssignedAdminsAzureUniqueId { get; set; } = [];


public static ApiProject FromQueryProject(QueryProject queryProject)
{
return new ApiProject()
{
Id = queryProject.Id,
Name = queryProject.Name,
OrgProjectExternalId = queryProject.OrgProjectExternalId,
AssignedAdminsAzureUniqueId = queryProject.AssignedAdminsAzureUniqueId.ToArray(),
DirectorAzureUniqueId = queryProject.DirectorAzureUniqueId
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Fusion.Summary.Api.Domain.Models;

namespace Fusion.Summary.Api.Controllers.ApiModels;

public class ApiWeeklyTaskOwnerReport
{
public required Guid Id { get; set; }
public required Guid ProjectId { get; set; }
public required DateTime PeriodStart { get; set; }
public required DateTime PeriodEnd { get; set; }


public static ApiWeeklyTaskOwnerReport FromQueryWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryWeeklyTaskOwnerReport)
{
return new ApiWeeklyTaskOwnerReport
{
Id = queryWeeklyTaskOwnerReport.Id,
ProjectId = queryWeeklyTaskOwnerReport.ProjectId,
PeriodStart = queryWeeklyTaskOwnerReport.Period.Start,
PeriodEnd = queryWeeklyTaskOwnerReport.Period.End
};
}
}
121 changes: 121 additions & 0 deletions src/Fusion.Summary.Api/Controllers/ProjectsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System.Net.Mime;
using Fusion.AspNetCore.FluentAuthorization;
using Fusion.Authorization;
using Fusion.Integration.Profile;
using Fusion.Summary.Api.Authorization.Extensions;
using Fusion.Summary.Api.Controllers.ApiModels;
using Fusion.Summary.Api.Controllers.Requests;
using Fusion.Summary.Api.Domain.Commands;
using Fusion.Summary.Api.Domain.Queries;
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fusion.Summary.Api.Controllers;

[Authorize]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
[ApiVersion("1.0")]
public class ProjectsController : BaseController
{
[HttpGet("projects")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<ApiProject[]>> GetProjectsV1()
{
#region Authorization

var authResult = await Request.RequireAuthorizationAsync(r =>
{
r.AlwaysAccessWhen().ResourcesFullControl();
r.AnyOf(or => { or.BeTrustedApplication(); });
});

if (authResult.Unauthorized)
return authResult.CreateForbiddenResponse();

#endregion Authorization

var projects = await DispatchAsync(new GetProjects());

var apiProjects = projects.Select(ApiProject.FromQueryProject);

return Ok(apiProjects);
}


[HttpGet("projects/{projectId:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ApiProject?>> GetProjectsV1(Guid projectId)
{
#region Authorization

var authResult = await Request.RequireAuthorizationAsync(r =>
{
r.AlwaysAccessWhen().ResourcesFullControl();
r.AnyOf(or => { or.BeTrustedApplication(); });
});

if (authResult.Unauthorized)
return authResult.CreateForbiddenResponse();

#endregion Authorization

var projects = await DispatchAsync(new GetProjects().WhereProjectId(projectId));

var apiProjects = projects.Select(ApiProject.FromQueryProject);

return Ok(apiProjects.FirstOrDefault());
}

[HttpPut("projects/{projectId:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<ApiProject?>> PutProjectsV1(Guid projectId, PutProjectRequest request)
{
#region Authorization

var authResult = await Request.RequireAuthorizationAsync(r =>
{
r.AlwaysAccessWhen().ResourcesFullControl();
r.AnyOf(or => { or.BeTrustedApplication(); });
});

if (authResult.Unauthorized)
return authResult.CreateForbiddenResponse();

#endregion Authorization

var personIdentifiers = request.AssignedAdminsAzureUniqueId
.Select(p => new PersonIdentifier(p));

if (request.DirectorAzureUniqueId.HasValue)
personIdentifiers = personIdentifiers.Append(new PersonIdentifier(request.DirectorAzureUniqueId.Value));

var unresolvedProfiles = (await ResolvePersonsAsync(personIdentifiers))
.Where(r => !r.Success)
.ToList();

if (unresolvedProfiles.Count != 0)
return FusionApiError.NotFound(string.Join(',', unresolvedProfiles), "Profiles could not be resolved");


var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault();

if (project == null)
{
await DispatchAsync(new CreateProject(request));

return Created(Request.GetUri(), null);
}

await DispatchAsync(new UpdateProject(project.Id, request));

return NoContent();
}
}
23 changes: 23 additions & 0 deletions src/Fusion.Summary.Api/Controllers/Requests/PutProjectRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using FluentValidation;

namespace Fusion.Summary.Api.Controllers.Requests;

public class PutProjectRequest
{
public required string Name { get; set; }
public required Guid OrgProjectExternalId { get; set; }

public Guid? DirectorAzureUniqueId { get; set; }

public Guid[] AssignedAdminsAzureUniqueId { get; set; } = [];


public class Validator : AbstractValidator<PutProjectRequest>
{
public Validator()
{
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.OrgProjectExternalId).NotEmpty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using FluentValidation;

namespace Fusion.Summary.Api.Controllers.Requests;

public class PutWeeklyTaskOwnerReportRequest
{
public DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }


public class Validator : AbstractValidator<PutWeeklyTaskOwnerReportRequest>
{
public Validator()
{
RuleFor(x => x.PeriodStart).NotEmpty();
RuleFor(x => x.PeriodEnd).NotEmpty();
RuleFor(x => x.PeriodStart).LessThan(x => x.PeriodEnd);
RuleFor(x => x.PeriodStart).Must(x => x.DayOfWeek == DayOfWeek.Monday).WithMessage("Period start must be a Monday");
RuleFor(x => x.PeriodEnd).Must(x => x.DayOfWeek == DayOfWeek.Monday).WithMessage("Period end must be a Monday");

RuleFor(x => x)
.Must(x => x.PeriodEnd.Date == x.PeriodStart.Date.AddDays(7))
.WithMessage("Period must be exactly 7 days");
}
}
}
116 changes: 116 additions & 0 deletions src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Net.Mime;
using Fusion.AspNetCore.FluentAuthorization;
using Fusion.AspNetCore.OData;
using Fusion.Authorization;
using Fusion.Summary.Api.Authorization.Extensions;
using Fusion.Summary.Api.Controllers.ApiModels;
using Fusion.Summary.Api.Controllers.Requests;
using Fusion.Summary.Api.Domain.Commands;
using Fusion.Summary.Api.Domain.Queries;
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fusion.Summary.Api.Controllers;

[Authorize]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
[ApiVersion("1.0")]
public class TaskOwnerSummaryController : BaseController
{
[HttpGet("task-owners-summary-reports/{projectId:guid}/weekly")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ODataTop(100), ODataSkip]
public async Task<ActionResult<ApiCollection<ApiWeeklyTaskOwnerReport>>> GetWeeklyTaskOwnerReportsV1(Guid projectId, ODataQueryParams query)
{
#region Authorization

var authResult =
await Request.RequireAuthorizationAsync(r =>
{
r.AlwaysAccessWhen().ResourcesFullControl();
r.AnyOf(or => { or.BeTrustedApplication(); });
});

if (authResult.Unauthorized)
return authResult.CreateForbiddenResponse();

#endregion

if ((await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault() is null)
return ProjectNotFound(projectId);


var projects = await DispatchAsync(new GetWeeklyTaskOwnerReports(projectId, query));

return Ok(ApiCollection<ApiWeeklyTaskOwnerReport>.FromQueryCollection(projects, ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport));
}

[HttpGet("task-owners-summary-reports/{projectId:guid}/weekly/{reportId:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ApiWeeklyTaskOwnerReport?>> GetWeeklyTaskOwnerReportV1(Guid projectId, Guid reportId)
{
#region Authorization

var authResult =
await Request.RequireAuthorizationAsync(r =>
{
r.AlwaysAccessWhen().ResourcesFullControl();
r.AnyOf(or => { or.BeTrustedApplication(); });
});

if (authResult.Unauthorized)
return authResult.CreateForbiddenResponse();

#endregion

if ((await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault() is null)
return ProjectNotFound(projectId);

var report = (await DispatchAsync(new GetWeeklyTaskOwnerReports(projectId, new ODataQueryParams()).WhereReportId(reportId))).FirstOrDefault();

return report is null ? NotFound() : Ok(ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport(report));
}

/// <summary>
/// Summary report key is composed of the project id and the period start and end dates.
/// If a report already exists for the given period and project id, it will be replaced.
/// </summary>
[HttpPut("task-owners-summary-reports/{projectId:guid}/weekly")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> PutWeeklyTaskOwnerReportV1(Guid projectId, [FromBody] PutWeeklyTaskOwnerReportRequest request)
{
#region Authorization

var authResult =
await Request.RequireAuthorizationAsync(r =>
{
r.AlwaysAccessWhen().ResourcesFullControl();
r.AnyOf(or => { or.BeTrustedApplication(); });
});

if (authResult.Unauthorized)
return authResult.CreateForbiddenResponse();

#endregion

var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault();
if (project is null)
return ProjectNotFound(projectId);

var command = new PutWeeklyTaskOwnerReport(project.Id, request);

var newReportCreated = await DispatchAsync(command);

return newReportCreated ? Created(Request.GetUri(), null) : NoContent();
}
}
5 changes: 5 additions & 0 deletions src/Fusion.Summary.Api/Database/Models/DbProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ public class DbProject
public required string Name { get; set; }
public required Guid OrgProjectExternalId { get; set; }

public Guid? DirectorAzureUniqueId { get; set; }

public List<Guid> AssignedAdminsAzureUniqueId { get; set; } = [];


internal static void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DbProject>(project =>
{
project.ToTable("Projects");
project.HasKey(p => p.Id);
project.Property(p => p.Name).HasMaxLength(500);
project.HasIndex(p => p.OrgProjectExternalId).IsUnique();
});
}
Expand Down
Loading

0 comments on commit 18e54a9

Please sign in to comment.