Skip to content

Commit 00a1e25

Browse files
committed
refactored out projectusers to its own controller
1 parent f7144b7 commit 00a1e25

File tree

10 files changed

+113
-36
lines changed

10 files changed

+113
-36
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using BugLab.Business.Commands.Projects;
2+
using BugLab.Business.Interfaces;
3+
using BugLab.Business.Queries.Projects;
4+
using BugLab.Data.Extensions;
5+
using BugLab.Shared.Responses;
6+
using MediatR;
7+
using Microsoft.AspNetCore.Mvc;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
namespace BugLab.API.Controllers
14+
{
15+
[Route("api/projects/{projectId}/[controller]")]
16+
public class ProjectUsersController : BaseApiController
17+
{
18+
private readonly IAuthService _authService;
19+
20+
public ProjectUsersController(IMediator mediator, IAuthService authService) : base(mediator)
21+
{
22+
_authService = authService;
23+
}
24+
25+
[HttpGet]
26+
public async Task<ActionResult<IEnumerable<UserResponse>>> GetProjectUsers(int projectId, CancellationToken cancellationToken)
27+
{
28+
var users = await _mediator.Send(new GetProjectUsersQuery(projectId), cancellationToken);
29+
30+
return Ok(users);
31+
}
32+
33+
[HttpPost]
34+
public async Task<IActionResult> AddUsersToProject(int projectId, [FromQuery] IEnumerable<string> userIds, CancellationToken cancellationToken)
35+
{
36+
if (!userIds.Any()) return NoContent();
37+
38+
await _authService.HasAccessToProject(User.UserId(), projectId);
39+
await _mediator.Send(new AddProjectUsersCommand(projectId, userIds), cancellationToken);
40+
41+
return NoContent();
42+
}
43+
44+
[HttpDelete("{id}")]
45+
public async Task<IActionResult> DeleteProjectUser(int projectId, string id, CancellationToken cancellationToken)
46+
{
47+
await _authService.HasAccessToProject(User.UserId(), projectId);
48+
await _mediator.Send(new DeleteProjectUserCommand(projectId, id), cancellationToken);
49+
50+
return NoContent();
51+
}
52+
}
53+
}

BugLab.API/Controllers/ProjectsController.cs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,6 @@ public async Task<IActionResult> UpdateProject(int id, UpdateProjectRequest requ
6161
return NoContent();
6262
}
6363

64-
[HttpPost("{id}/users")]
65-
public async Task<IActionResult> AddUsersToProject(int id, [FromQuery] IEnumerable<string> userIds, CancellationToken cancellationToken)
66-
{
67-
if (!userIds.Any()) return NoContent();
68-
69-
await _authService.HasAccessToProject(User.UserId(), id);
70-
await _mediator.Send(new AddProjectUsersCommand(id, userIds), cancellationToken);
71-
72-
return NoContent();
73-
}
74-
7564
[HttpDelete("{id}")]
7665
public async Task<IActionResult> DeleteProject(int id, CancellationToken cancellationToken)
7766
{
@@ -80,15 +69,5 @@ public async Task<IActionResult> DeleteProject(int id, CancellationToken cancell
8069

8170
return NoContent();
8271
}
83-
84-
[HttpDelete("{id}/users/{userId}")]
85-
public async Task<IActionResult> DeleteProjectUser(int id, string userId, CancellationToken cancellationToken)
86-
{
87-
await _authService.HasAccessToProject(User.UserId(), id);
88-
await _mediator.Send(new DeleteProjectUserCommand(id, userId), cancellationToken);
89-
90-
return NoContent();
91-
}
92-
9372
}
9473
}

BugLab.Blazor/Components/Projects/ProjectMembersListComponent.razor

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,7 @@
2626
{
2727
if (Members.Count() == 1) { Snackbar.Add("Removing the last user on the project gives no one access to it", Severity.Error); return; }
2828

29-
var uri = new StringBuilder()
30-
.Append(Endpoints.Projects).Append('/').Append(ProjectId).Append('/')
31-
.Append(nameof(Endpoints.Users)).Append('/').Append(id).ToString();
32-
33-
await Client.DeleteAsync(uri);
34-
29+
await Client.DeleteAsync($"{Endpoints.ProjectUsers(ProjectId)}/{id}");
3530
Members = Members.Where(u => u.Id != id).ToList();
3631
}
3732

BugLab.Blazor/Dialogs/AddProjectMemberDialog.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
private async Task AddUsersToProject()
6464
{
65-
var request = QueryBuilder.Use($"{Endpoints.Projects}/{ProjectId}/users");
65+
var request = QueryBuilder.Use(Endpoints.ProjectUsers(ProjectId));
6666
foreach (var user in _selectedUsers)
6767
{
6868
request.WithParam("userIds", user.Id);

BugLab.Blazor/Helpers/Endpoints.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ public static string BugTypes(int projectId)
1616
{
1717
return $"{Projects}/{projectId}/bugTypes";
1818
}
19+
20+
public static string ProjectUsers(int projectId)
21+
{
22+
return $"{Projects}/{projectId}/projectUsers";
23+
}
1924
}
2025
}

BugLab.Blazor/Pages/ProjectsList.razor

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<MudTh></MudTh>
2727
</HeaderContent>
2828
<RowTemplate>
29-
<MudTd DataLabel="Title" id="title"><MudButton @onclick="() => SelectedProjectChanged(context)">@context.Title</MudButton></MudTd>
29+
<MudTd DataLabel="Title" id="title"><MudButton @onclick="async () => await SelectedProjectChanged(context)">@context.Title</MudButton></MudTd>
3030
<MudTd DataLabel="High prioritized bugs">@context.TotalHighPriorityBugs</MudTd>
3131
<MudTd DataLabel="Total bugs">@context.TotalBugs</MudTd>
3232
<MudTd>
@@ -52,7 +52,7 @@
5252
@if (_showMembers)
5353
{
5454
<MudButton Class="ml-5" OnClick="OpenAddMemberDialog">Add a new member</MudButton>
55-
<ProjectMembersListComponent ProjectId="_selectedProject.Id" Members="@_selectedProject.Users" />
55+
<ProjectMembersListComponent ProjectId="_selectedProject.Id" Members="@_selectedProjectMembers" />
5656
}
5757
</MudItem>
5858
</MudContainer>
@@ -64,6 +64,7 @@
6464
private bool _showAddProject;
6565
private bool _showMembers = true;
6666
private ProjectResponse _selectedProject;
67+
private IEnumerable<UserResponse> _selectedProjectMembers = new List<UserResponse>();
6768

6869
public async Task<TableData<ProjectResponse>> GetProjects(TableState state)
6970
{
@@ -93,9 +94,11 @@
9394
};
9495
}
9596

96-
public void SelectedProjectChanged(ProjectResponse projectResponse)
97+
public async Task SelectedProjectChanged(ProjectResponse projectResponse)
9798
{
9899
_selectedProject = projectResponse;
100+
var projectMembers = await Client.GetFromJsonAsync<IEnumerable<UserResponse>>(Endpoints.ProjectUsers(_selectedProject.Id));
101+
_selectedProjectMembers = projectMembers ?? new List<UserResponse>();
99102
}
100103

101104
public void OnProjectClick(string projectTitle, int projectId)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using BugLab.Shared.Responses;
2+
using MediatR;
3+
using System.Collections.Generic;
4+
5+
namespace BugLab.Business.Queries.Projects
6+
{
7+
public class GetProjectUsersQuery : IRequest<IEnumerable<UserResponse>>
8+
{
9+
public GetProjectUsersQuery(int projectId)
10+
{
11+
ProjectId = projectId;
12+
}
13+
14+
public int ProjectId { get; }
15+
}
16+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using BugLab.Business.Queries.Projects;
2+
using BugLab.Data;
3+
using BugLab.Shared.Responses;
4+
using MediatR;
5+
using Microsoft.EntityFrameworkCore;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace BugLab.Business.QueryHandlers.Projects
12+
{
13+
public class GetProjectUsersHandler : IRequestHandler<GetProjectUsersQuery, IEnumerable<UserResponse>>
14+
{
15+
private readonly AppDbContext _context;
16+
17+
public GetProjectUsersHandler(AppDbContext context)
18+
{
19+
_context = context;
20+
}
21+
22+
public async Task<IEnumerable<UserResponse>> Handle(GetProjectUsersQuery request, CancellationToken cancellationToken)
23+
{
24+
return await _context.Projects.AsNoTracking().Where(p => p.Id == request.ProjectId)
25+
.Select(p => p.Users.Select(u => new UserResponse { Email = u.Email, Id = u.Id }))
26+
.FirstOrDefaultAsync(cancellationToken);
27+
}
28+
}
29+
}
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Collections.Generic;
2-
3-
namespace BugLab.Shared.Responses
1+
namespace BugLab.Shared.Responses
42
{
53
public class ProjectResponse
64
{
@@ -9,6 +7,5 @@ public class ProjectResponse
97
public string Description { get; init; }
108
public int TotalBugs { get; set; }
119
public int TotalHighPriorityBugs { get; set; }
12-
public IEnumerable<UserResponse> Users { get; set; } = new List<UserResponse>();
1310
}
1411
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ Give a user access to a project [![msedge-s-Rxd-ELn30-T.png](https://i.postimg.c
6363
- Attachments (f.e image, gif)
6464
- Home page can have a dashboard
6565
- Background service that sends a weekly stats mail regarding for example the project with most completed bugs, the project with most high prioritized bugs, how many bugs you have completed
66-
- Using the users nav entity on the projects can be tricky due to EF does not have support for including many to many unidirectional relationships. I could not find many code benefits with manually creating a join table and then do Include().ThenInclude() (alt make use of reference by id and then the Project entity wont even need to directly have a list of Users/ProjectUsers). It seems to be something that is coming soon for EF6 though: https://github.com/dotnet/efcore/issues/3864 . This issue will require a lot of refactoring existing code but if you would like to give it a try and convince me that it improves the app code significally (I'm aware of it having a little readability improvment and that there is a small performance boost because we don't have to do a join sometimes but at the same time it requires more code, management and performance is not an issue right now)
66+
- Using the users nav entity on the projects can be tricky due to EF does not have support for including many to many unidirectional relationships. An alternative is to manually create a join table and then do Include().ThenInclude() (or make use of reference by id and then the Project entity wont even need to directly have a list of Users/ProjectUsers). It seems to be something that is coming soon for EF6 though: https://github.com/dotnet/efcore/issues/3864 . This issue will require a lot of refactoring existing code but will improve readability and performance.
6767
- The idea is that we give all users in a project read/write permissions to all items. If you are a performance freak you could change our urls to almost always start with projects/projectId f.e getting a comment would then be projects/projectId/bugs/bugId/comments/id, not the fanciest but since we don't need to fetch the bug in order to get the projectId to see if the user is in it we boost performance a bit but as i said there's no point in changing the code base for a little performance boost unless necessary. One great point is that it makes accessing items in the Api consistent but it also means that we need two endpoints for getting bugs. right now: api/bugs - my bugs and api/bugs?projectId=1 - project's bugs VS api/users/1/bugs and api/projects/1/bugs

0 commit comments

Comments
 (0)