From f1b4f53cd42a3735b05301473b26d571c8ddc253 Mon Sep 17 00:00:00 2001 From: vidufour1 Date: Sat, 16 Mar 2024 21:23:55 +0100 Subject: [PATCH 1/4] share tactic to team and user --- API/API.csproj.user | 6 ++++ API/Controllers/TacticsController.cs | 1 + API/Controllers/TeamsController.cs | 33 +++++++++++++++++- API/Controllers/UsersController.cs | 42 +++++++++++++++++++++++ AppContext/AppContext.cs | 2 ++ AppContext/Entities/SharedTacticEntity.cs | 12 +++++++ Converters/ModelToEntities.cs | 5 +++ DbServices/DbTacticService.cs | 27 +++++++++++++++ DbServices/DbTeamService.cs | 29 ++++++++++++++++ DbServices/DbUserService.cs | 23 +++++++++++++ Model/SharedTactic.cs | 3 ++ Services/ITacticService.cs | 3 ++ Services/ITeamService.cs | 4 +++ Services/UserService.cs | 3 ++ UnitTests/UserControllerTest.cs | 10 ++++++ 15 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 API/API.csproj.user create mode 100644 AppContext/Entities/SharedTacticEntity.cs create mode 100644 Model/SharedTactic.cs diff --git a/API/API.csproj.user b/API/API.csproj.user new file mode 100644 index 0000000..9ff5820 --- /dev/null +++ b/API/API.csproj.user @@ -0,0 +1,6 @@ + + + + https + + \ No newline at end of file diff --git a/API/Controllers/TacticsController.cs b/API/Controllers/TacticsController.cs index 9452f11..518df42 100644 --- a/API/Controllers/TacticsController.cs +++ b/API/Controllers/TacticsController.cs @@ -151,4 +151,5 @@ public class TacticController(ITacticService service, IContextAccessor accessor) await service.SetTacticStepContent(tacticId, stepId, JsonSerializer.Serialize(req.Content)); return Ok(); } + } \ No newline at end of file diff --git a/API/Controllers/TeamsController.cs b/API/Controllers/TeamsController.cs index aecfc67..c674085 100644 --- a/API/Controllers/TeamsController.cs +++ b/API/Controllers/TeamsController.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using API.Context; using API.Validation; +using AppContext.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Model; @@ -10,7 +11,7 @@ namespace API.Controllers; [ApiController] [Authorize] -public class TeamsController(ITeamService service, IContextAccessor accessor) : ControllerBase +public class TeamsController(ITeamService service, ITacticService tactics,IContextAccessor accessor) : ControllerBase { public record CreateTeamRequest( [Name] string Name, @@ -74,4 +75,34 @@ public class TeamsController(ITeamService service, IContextAccessor accessor) : var removed = await service.RemoveMember(teamId, userId); return removed ? Ok() : NotFound(); } + + public record ShareTacticToTeamRequest( + int TacticId, + int TeamId + ); + + [HttpPost("/team/share-tactic")] + [Authorize] + public async Task ShareTactic([FromBody] ShareTacticToTeamRequest sharedTactic) + { + var userId = accessor.CurrentUserId(HttpContext); + var success = await tactics.ShareTactic(sharedTactic.TacticId, null, sharedTactic.TeamId); + + return success ? Ok() : BadRequest(); + } + + [HttpGet("/tactics/shared/team/{teamId:int}")] + [Authorize] + public async Task GetSharedTacticsToTeam(int teamId) + { + var currentUserId = accessor.CurrentUserId(HttpContext); + + if (!await service.IsUserInTeam(currentUserId, teamId)) + { + return Unauthorized(); + } + + var sharedTactics = await service.GetSharedTacticsToTeam(teamId); + return sharedTactics != null ? Ok(sharedTactics) : NotFound(); + } } \ No newline at end of file diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 2bb6a4f..e5e7f37 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using API.Context; using API.DTO; +using AppContext.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Model; @@ -35,4 +36,45 @@ public class UsersController(IUserService users, ITeamService teams, ITacticServ var userTactics = await tactics.ListTacticsOf(userId); return new GetUserDataResponse(userTeams.ToArray(), userTactics.Select(t => t.ToDto()).ToArray()); } + + public record ShareTacticToUserRequest( + int TacticId, + int UserId + ); + + [HttpPost("/user/share-tactic")] + [Authorize] + public async Task ShareTactic([FromBody] ShareTacticToUserRequest sharedTactic) + { + var currentUserId = accessor.CurrentUserId(HttpContext); + var tactic = await tactics.GetTactic(sharedTactic.TacticId); + + if (tactic == null) + { + return NotFound(); + } + + if (currentUserId != tactic.OwnerId) + { + return Unauthorized(); + } + + var result = await tactics.ShareTactic(sharedTactic.TacticId, sharedTactic.UserId, null); + return result ? Ok() : NotFound(); + } + + + [HttpGet("/tactics/shared/user/{userId:int}")] + [Authorize] + public async Task GetSharedTacticsToUser(int userId) + { + var currentUserId = accessor.CurrentUserId(HttpContext); + if (currentUserId != userId) + { + return Unauthorized(); + } + + var sharedTactics = await users.GetSharedTacticsToUser(userId); + return sharedTactics != null ? Ok(sharedTactics) : NotFound(); + } } \ No newline at end of file diff --git a/AppContext/AppContext.cs b/AppContext/AppContext.cs index f875684..e47c05d 100644 --- a/AppContext/AppContext.cs +++ b/AppContext/AppContext.cs @@ -13,7 +13,9 @@ public class AppContext : DbContext public DbSet Teams { get; init; } public DbSet Members { get; init; } public DbSet TacticSteps { get; set; } + public DbSet SharedTactics { get; set; } + public AppContext() { diff --git a/AppContext/Entities/SharedTacticEntity.cs b/AppContext/Entities/SharedTacticEntity.cs new file mode 100644 index 0000000..852dd92 --- /dev/null +++ b/AppContext/Entities/SharedTacticEntity.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace AppContext.Entities +{ + public class SharedTacticEntity + { + [Key] public int Id { get; set; } + public int TacticId { get; set; } + public int? SharedWithUserId { get; set; } + public int? SharedWithTeamId { get; set; } + } +} \ No newline at end of file diff --git a/Converters/ModelToEntities.cs b/Converters/ModelToEntities.cs index 6c8006c..001aed6 100644 --- a/Converters/ModelToEntities.cs +++ b/Converters/ModelToEntities.cs @@ -37,4 +37,9 @@ public static class EntitiesToModels { return new Member(entity.TeamId, entity.UserId, entity.Role); } + + public static SharedTactic ToModel(this SharedTacticEntity entity) + { + return new SharedTactic(entity.Id, entity.TacticId, entity.SharedWithUserId, entity.SharedWithTeamId); + } } \ No newline at end of file diff --git a/DbServices/DbTacticService.cs b/DbServices/DbTacticService.cs index 1fcb1b3..3312cd1 100644 --- a/DbServices/DbTacticService.cs +++ b/DbServices/DbTacticService.cs @@ -152,4 +152,31 @@ public class DbTacticService(AppContext.AppContext context) : ITacticService return await context.SaveChangesAsync() > 0; } + + public async Task ShareTactic(int tacticId, int? userId, int? teamId) + { + var sharedTactic = new SharedTacticEntity + { + TacticId = tacticId, + SharedWithUserId = userId, + SharedWithTeamId = teamId + }; + + await context.SharedTactics.AddAsync(sharedTactic); + return await context.SaveChangesAsync() > 0; + } + + public async Task UnshareTactic(int tacticId, int? userId, int? teamId) + { + var sharedTactic = await context.SharedTactics + .FirstOrDefaultAsync(st => st.TacticId == tacticId && st.SharedWithUserId == userId && st.SharedWithTeamId == teamId); + + if (sharedTactic == null) + { + return false; + } + + context.SharedTactics.Remove(sharedTactic); + return await context.SaveChangesAsync() > 0; + } } \ No newline at end of file diff --git a/DbServices/DbTeamService.cs b/DbServices/DbTeamService.cs index 6ca6e1e..82aa4a0 100644 --- a/DbServices/DbTeamService.cs +++ b/DbServices/DbTeamService.cs @@ -83,6 +83,29 @@ public class DbTeamService(AppContext.AppContext context) : ITeamService return await context.SaveChangesAsync() > 0; } + public async Task> GetSharedTacticsToTeam(int teamId) + { + var sharedTactics = await context.SharedTactics + .Where(st => st.SharedWithTeamId == teamId) + .ToListAsync(); + + var tactics = new List(); + foreach (var sharedTactic in sharedTactics) + { + var tactic = await context.Tactics + .Where(t => t.Id == sharedTactic.TacticId) + .Select(t => t.ToModel()) + .FirstOrDefaultAsync(); + + if (tactic != null) + { + tactics.Add(tactic); + } + } + + return tactics; + } + public IEnumerable GetMembersOf(int teamId) { @@ -121,4 +144,10 @@ public class DbTeamService(AppContext.AppContext context) : ITeamService .ExecuteDeleteAsync(); return await context.SaveChangesAsync() > 0; } + + public async Task IsUserInTeam(int userId, int teamId) + { + return await context.Members + .AnyAsync(m => m.TeamId == teamId && m.UserId == userId); + } } \ No newline at end of file diff --git a/DbServices/DbUserService.cs b/DbServices/DbUserService.cs index 83aa821..3d1268a 100644 --- a/DbServices/DbUserService.cs +++ b/DbServices/DbUserService.cs @@ -94,4 +94,27 @@ public class DbUserService(AppContext.AppContext context) : IUserService .FirstOrDefaultAsync(u => u.Email == email)) ?.ToModel(); } + + public async Task> GetSharedTacticsToUser(int userId) + { + var sharedTactics = await context.SharedTactics + .Where(st => st.SharedWithUserId == userId) + .ToListAsync(); + + var tactics = new List(); + foreach (var sharedTactic in sharedTactics) + { + var tactic = await context.Tactics + .Where(t => t.Id == sharedTactic.TacticId) + .Select(t => t.ToModel()) + .FirstOrDefaultAsync(); + + if (tactic != null) + { + tactics.Add(tactic); + } + } + + return tactics; + } } \ No newline at end of file diff --git a/Model/SharedTactic.cs b/Model/SharedTactic.cs new file mode 100644 index 0000000..8fc29e8 --- /dev/null +++ b/Model/SharedTactic.cs @@ -0,0 +1,3 @@ +namespace Model; + +public record SharedTactic(int Id, int TacticId, int? SharedWithUserId, int? SharedWithTeamId); \ No newline at end of file diff --git a/Services/ITacticService.cs b/Services/ITacticService.cs index 98d68e7..e8bb83d 100644 --- a/Services/ITacticService.cs +++ b/Services/ITacticService.cs @@ -23,4 +23,7 @@ public interface ITacticService public Task> ListUserTactics(int userId); public Task AddTacticStep(int tacticId, int parentStepId, string initialJson); public Task RemoveTacticStep(int tacticId, int stepId); + + public Task ShareTactic(int tacticId, int? userId, int? teamId); + public Task UnshareTactic(int tacticId, int? userId, int? teamId); } \ No newline at end of file diff --git a/Services/ITeamService.cs b/Services/ITeamService.cs index 63deaeb..90efd1c 100644 --- a/Services/ITeamService.cs +++ b/Services/ITeamService.cs @@ -23,4 +23,8 @@ public interface ITeamService public Task UpdateTeam(Team team); + public Task> GetSharedTacticsToTeam(int teamId); + + public Task IsUserInTeam(int userId, int teamId); + } \ No newline at end of file diff --git a/Services/UserService.cs b/Services/UserService.cs index 4e67597..558c42d 100644 --- a/Services/UserService.cs +++ b/Services/UserService.cs @@ -21,5 +21,8 @@ public interface IUserService Task UpdateUser(User user); public Task Authorize(string email, string password); + + public Task> GetSharedTacticsToUser(int userId); + } \ No newline at end of file diff --git a/UnitTests/UserControllerTest.cs b/UnitTests/UserControllerTest.cs index cb98c44..a57b0f6 100644 --- a/UnitTests/UserControllerTest.cs +++ b/UnitTests/UserControllerTest.cs @@ -1,6 +1,8 @@ using API.Controllers; +using AppContext.Entities; using DbServices; using FluentAssertions; +using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Model; @@ -46,4 +48,12 @@ public class UsersControllerTest var result = await controller.GetUserData(); result.Should().BeEquivalentTo(new UsersController.GetUserDataResponse([], [])); } + + [Fact] + public async Task ShareTacticTest() + { + var controller = GetUserController(1); + var result = await controller.ShareTactic(new UsersController.ShareTacticToUserRequest(1, 2)); + result.Should().BeOfType(); + } } \ No newline at end of file From bd26d85d34011ee63f71dc15c17f0a328ebe73e0 Mon Sep 17 00:00:00 2001 From: vidufour1 Date: Sat, 16 Mar 2024 22:15:05 +0100 Subject: [PATCH 2/4] unshare tactic and unit test --- API/Controllers/UsersController.cs | 19 +++++++++++++++++++ DbServices/DbTacticService.cs | 15 ++++++++++++--- StubContext/StubAppContext.cs | 8 ++++++++ UnitTests/UserControllerTest.cs | 26 ++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index e5e7f37..ea73e3a 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -63,6 +63,25 @@ public class UsersController(IUserService users, ITeamService teams, ITacticServ return result ? Ok() : NotFound(); } + [HttpDelete("/tactics/shared/{tacticId:int}/user/{userId:int}")] + [Authorize] + public async Task UnshareTactic(int tacticId, int userId) + { + var currentUserId = accessor.CurrentUserId(HttpContext); + var tactic = await tactics.GetTactic(tacticId); + + if (tactic == null) + { + return NotFound(); + } + if (currentUserId != tactic.OwnerId) + { + return Unauthorized(); + } + + var success = await tactics.UnshareTactic(tacticId, userId, null); + return success ? Ok() : NotFound(); + } [HttpGet("/tactics/shared/user/{userId:int}")] [Authorize] diff --git a/DbServices/DbTacticService.cs b/DbServices/DbTacticService.cs index 3312cd1..78fa157 100644 --- a/DbServices/DbTacticService.cs +++ b/DbServices/DbTacticService.cs @@ -168,9 +168,18 @@ public class DbTacticService(AppContext.AppContext context) : ITacticService public async Task UnshareTactic(int tacticId, int? userId, int? teamId) { - var sharedTactic = await context.SharedTactics - .FirstOrDefaultAsync(st => st.TacticId == tacticId && st.SharedWithUserId == userId && st.SharedWithTeamId == teamId); - + SharedTacticEntity? sharedTactic = null; + if (userId.HasValue) + { + sharedTactic = await context.SharedTactics + .FirstOrDefaultAsync(st => st.TacticId == tacticId && st.SharedWithUserId == userId); + } + else if (teamId.HasValue) + { + sharedTactic = await context.SharedTactics + .FirstOrDefaultAsync(st => st.TacticId == tacticId && st.SharedWithTeamId == teamId); + } + if (sharedTactic == null) { return false; diff --git a/StubContext/StubAppContext.cs b/StubContext/StubAppContext.cs index f740de9..6027f4a 100644 --- a/StubContext/StubAppContext.cs +++ b/StubContext/StubAppContext.cs @@ -56,5 +56,13 @@ public class StubAppContext(DbContextOptions options) : AppContext(o TacticId = 1, ParentId = null }); + + builder.Entity() + .HasData(new SharedTacticEntity + { + Id = 1, + TacticId = 1, + SharedWithUserId = 2 + }); } } \ No newline at end of file diff --git a/UnitTests/UserControllerTest.cs b/UnitTests/UserControllerTest.cs index a57b0f6..fb28078 100644 --- a/UnitTests/UserControllerTest.cs +++ b/UnitTests/UserControllerTest.cs @@ -7,6 +7,7 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Model; using StubContext; +using Xunit.Abstractions; namespace UnitTests; @@ -56,4 +57,29 @@ public class UsersControllerTest var result = await controller.ShareTactic(new UsersController.ShareTacticToUserRequest(1, 2)); result.Should().BeOfType(); } + + [Fact] + public async Task GetSharedTacticsToUserTest() + { + var controller = GetUserController(2); + var result = await controller.GetSharedTacticsToUser(2); + + var okResult = result as OkObjectResult; + var sharedTactics = okResult.Value as IEnumerable; + + sharedTactics.Should().NotBeNull(); + sharedTactics.Should().ContainSingle(); + + var tactic = sharedTactics.First(); + tactic.Id.Should().Be(1); + } + + [Fact] + public async Task UnshareTacticTest() + { + var controller = GetUserController(1); + var result = await controller.UnshareTactic(1, 2); + result.Should().BeOfType(); + } + } \ No newline at end of file From 6218b1e8285a47ad05bf4ce3c21c676c18016089 Mon Sep 17 00:00:00 2001 From: vidufour1 Date: Sat, 16 Mar 2024 22:17:14 +0100 Subject: [PATCH 3/4] unsharetactic to team --- API/Controllers/TeamsController.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/API/Controllers/TeamsController.cs b/API/Controllers/TeamsController.cs index c674085..66de2b6 100644 --- a/API/Controllers/TeamsController.cs +++ b/API/Controllers/TeamsController.cs @@ -91,6 +91,26 @@ public class TeamsController(ITeamService service, ITacticService tactics,IConte return success ? Ok() : BadRequest(); } + [HttpDelete("/tactics/shared/{tacticId:int}/team/{teamId:int}")] + [Authorize] + public async Task UnshareTactic(int tacticId, int teamId) + { + var currentUserId = accessor.CurrentUserId(HttpContext); + var tactic = await tactics.GetTactic(tacticId); + + if (tactic == null) + { + return NotFound(); + } + if (currentUserId != tactic.OwnerId) + { + return Unauthorized(); + } + + var success = await tactics.UnshareTactic(tacticId, null, teamId); + return success ? Ok() : NotFound(); + } + [HttpGet("/tactics/shared/team/{teamId:int}")] [Authorize] public async Task GetSharedTacticsToTeam(int teamId) From 4131223667687ad727425adc04aea6997a58d49c Mon Sep 17 00:00:00 2001 From: vidufour1 Date: Sun, 17 Mar 2024 10:52:46 +0100 Subject: [PATCH 4/4] modif authorize --- API/Controllers/TeamsController.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/API/Controllers/TeamsController.cs b/API/Controllers/TeamsController.cs index 66de2b6..29918fd 100644 --- a/API/Controllers/TeamsController.cs +++ b/API/Controllers/TeamsController.cs @@ -82,7 +82,6 @@ public class TeamsController(ITeamService service, ITacticService tactics,IConte ); [HttpPost("/team/share-tactic")] - [Authorize] public async Task ShareTactic([FromBody] ShareTacticToTeamRequest sharedTactic) { var userId = accessor.CurrentUserId(HttpContext); @@ -92,7 +91,6 @@ public class TeamsController(ITeamService service, ITacticService tactics,IConte } [HttpDelete("/tactics/shared/{tacticId:int}/team/{teamId:int}")] - [Authorize] public async Task UnshareTactic(int tacticId, int teamId) { var currentUserId = accessor.CurrentUserId(HttpContext); @@ -112,7 +110,6 @@ public class TeamsController(ITeamService service, ITacticService tactics,IConte } [HttpGet("/tactics/shared/team/{teamId:int}")] - [Authorize] public async Task GetSharedTacticsToTeam(int teamId) { var currentUserId = accessor.CurrentUserId(HttpContext);