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