Skip to content

Commit 96aec6f

Browse files
Implement autosave task patch/move with optimistic concurrency
1 parent 45f03e3 commit 96aec6f

19 files changed

+776
-178
lines changed
Lines changed: 101 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
using Microsoft.AspNetCore.Authorization;
22
using Microsoft.AspNetCore.Mvc;
3-
using Microsoft.EntityFrameworkCore;
4-
using Microsoft.IdentityModel.Tokens;
5-
using Todo.Data;
6-
using Todo.Migrations;
3+
using System.Security.Claims;
74
using Todo.Models;
85
using Todo.Services;
96

107
namespace Todo.Controllers
118
{
129
[ApiController]
13-
1410
public class TaskManagerController : ControllerBase
1511
{
1612
private readonly TaskManagerServices _taskManagerServices;
@@ -29,7 +25,7 @@ public IActionResult Get()
2925
var tasksToDo = _taskManagerServices.GetTasksToDo();
3026
return Ok(tasksToDo);
3127
}
32-
catch (System.Exception)
28+
catch
3329
{
3430
return BadRequest();
3531
}
@@ -38,14 +34,15 @@ public IActionResult Get()
3834
[HttpGet("/ListTaskDone")]
3935
public IActionResult ListTaskDone()
4036
{
41-
try
42-
{
37+
try
38+
{
4339
var tasksDone = _taskManagerServices.GetTaskDone();
4440
return Ok(tasksDone);
45-
}catch(System.Exception)
41+
}
42+
catch
4643
{
47-
return BadRequest();
48-
}
44+
return BadRequest();
45+
}
4946
}
5047

5148
[Authorize]
@@ -56,22 +53,23 @@ public IActionResult ListAllTasks()
5653
{
5754
var allTasks = _taskManagerServices.GetAllTasks();
5855
return Ok(allTasks);
59-
}catch(System.Exception)
60-
{
56+
}
57+
catch
58+
{
6159
return BadRequest();
6260
}
6361
}
6462

6563
[Authorize]
6664
[HttpGet("ListTaskByUser/{userId}")]
67-
public IActionResult ListTarefaByUser(
68-
[FromRoute] int userId)
65+
public IActionResult ListTarefaByUser([FromRoute] int userId)
6966
{
7067
try
7168
{
7269
var tasksByUser = _taskManagerServices.GetTasksByUser(userId);
7370
return Ok(tasksByUser);
74-
}catch(System.Exception)
71+
}
72+
catch
7573
{
7674
return BadRequest();
7775
}
@@ -81,92 +79,151 @@ public IActionResult ListTarefaByUser(
8179
[HttpGet("/GetById/{id:int}")]
8280
public IActionResult GetById([FromRoute] int id)
8381
{
84-
try
85-
{
82+
try
83+
{
8684
var taskById = _taskManagerServices.GetById(id);
8785
return Ok(taskById);
88-
}catch(System.Exception)
86+
}
87+
catch
8988
{
90-
return BadRequest();
91-
}
89+
return BadRequest();
90+
}
9291
}
9392

9493
[Authorize]
9594
[HttpPost("/insertTask/{userId}")]
96-
public IActionResult Post(
97-
[FromBody] TaskModel model,
98-
[FromRoute] int userId)
95+
public IActionResult Post([FromBody] TaskModel model, [FromRoute] int userId)
9996
{
10097
try
10198
{
10299
var task = _taskManagerServices.InsertTask(model, userId);
103100
return Ok(task);
104-
}catch(System.Exception)
101+
}
102+
catch
105103
{
106104
return BadRequest();
107-
}
105+
}
108106
}
109107

110108
[Authorize]
111109
[HttpPut("/edit/{id:int}")]
112-
public IActionResult Put(
113-
[FromRoute] int id,
114-
[FromBody] TaskModel model)
110+
public IActionResult Put([FromRoute] int id, [FromBody] TaskModel model)
115111
{
116-
try
117-
{
112+
try
113+
{
118114
var taskToEdit = _taskManagerServices.EditTask(model, id);
119115
return Ok(taskToEdit);
120-
}catch(System.Exception)
121-
{
116+
}
117+
catch
118+
{
122119
return BadRequest();
123-
}
120+
}
124121
}
125122

126123
[Authorize]
127124
[HttpDelete("/delete/{id:int}")]
128-
public IActionResult Delete(
129-
[FromRoute] int id)
125+
public IActionResult Delete([FromRoute] int id)
130126
{
131127
try
132128
{
133129
var editeTask = _taskManagerServices.DeleteTask(id);
134130
return Ok(editeTask);
135-
}catch(System.Exception)
131+
}
132+
catch
136133
{
137134
return BadRequest();
138135
}
139136
}
140137

141138
[Authorize]
142139
[HttpPut("/done/{id:int}")]
143-
public IActionResult Done(
144-
[FromRoute] int id)
140+
public IActionResult Done([FromRoute] int id)
145141
{
146142
try
147143
{
148144
var taskDone = _taskManagerServices.DoneTask(id);
149145
return Ok(taskDone);
150-
}catch(System.Exception)
146+
}
147+
catch
151148
{
152149
return BadRequest();
153150
}
154151
}
155152

156153
[Authorize]
157154
[HttpPost("/asignTask")]
158-
public IActionResult AsignTask(
159-
[FromBody] TaskModel model)
155+
public IActionResult AsignTask([FromBody] TaskModel model)
160156
{
161157
try
162158
{
163159
var asignTask = _taskManagerServices.AsignTask(model);
164160
return Ok(asignTask);
165-
}catch(System.Exception)
161+
}
162+
catch
166163
{
167164
return BadRequest();
168165
}
169166
}
170167

168+
[Authorize]
169+
[HttpPatch("/api/tasks/{id:int}")]
170+
public async Task<IActionResult> PatchTask([FromRoute] int id, [FromBody] TaskPatchRequest request)
171+
{
172+
var (userId, tenantId) = ResolveIdentity();
173+
if (userId == null || tenantId == null)
174+
return Unauthorized(new ProblemDetails { Title = "Token inválido", Status = 401 });
175+
176+
var ifMatchHeader = Request.Headers.IfMatch.FirstOrDefault()?.Trim('"');
177+
var ifMatch = TaskManagerServices.ParseRowVersion(ifMatchHeader);
178+
179+
var (_, result) = await _taskManagerServices.PatchTaskAsync(id, request, userId.Value, tenantId.Value, ifMatch);
180+
return result;
181+
}
182+
183+
[Authorize]
184+
[HttpPost("/api/tasks/{id:int}/move")]
185+
public async Task<IActionResult> MoveTask([FromRoute] int id, [FromBody] TaskMoveRequest request)
186+
{
187+
var (userId, tenantId) = ResolveIdentity();
188+
if (userId == null || tenantId == null)
189+
return Unauthorized(new ProblemDetails { Title = "Token inválido", Status = 401 });
190+
191+
var (_, result) = await _taskManagerServices.MoveTaskAsync(id, request, userId.Value, tenantId.Value);
192+
return result;
193+
}
194+
195+
[Authorize]
196+
[HttpPut("/api/tasks/{id:int}/time")]
197+
public async Task<IActionResult> UpdateTime([FromRoute] int id, [FromBody] TaskTimeRequest request)
198+
{
199+
var (userId, tenantId) = ResolveIdentity();
200+
if (userId == null || tenantId == null)
201+
return Unauthorized(new ProblemDetails { Title = "Token inválido", Status = 401 });
202+
203+
var (_, result) = await _taskManagerServices.UpdateTaskTimeAsync(id, request, userId.Value, tenantId.Value);
204+
return result;
205+
}
206+
207+
[Authorize]
208+
[HttpGet("/api/boards/{boardId:int}/tasks")]
209+
public async Task<IActionResult> GetBoardTasks([FromRoute] int boardId)
210+
{
211+
var (userId, tenantId) = ResolveIdentity();
212+
if (userId == null || tenantId == null)
213+
return Unauthorized(new ProblemDetails { Title = "Token inválido", Status = 401 });
214+
215+
return await _taskManagerServices.GetBoardTasksAsync(boardId, userId.Value, tenantId.Value);
216+
}
217+
218+
private (int? userId, int? tenantId) ResolveIdentity()
219+
{
220+
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier);
221+
var tenantClaim = User.FindFirstValue("tenant_id") ?? User.FindFirstValue("tenantId");
222+
223+
if (!int.TryParse(userIdClaim, out var userId)) return (null, null);
224+
if (!int.TryParse(tenantClaim, out var tenantId)) tenantId = 1;
225+
226+
return (userId, tenantId);
227+
}
171228
}
172-
}
229+
}

Data/AppDbContext.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
using Microsoft.EntityFrameworkCore;
22
using Todo.Domain;
33

4-
namespace Todo.Data {
5-
public class AppDbContext : DbContext {
4+
namespace Todo.Data
5+
{
6+
public class AppDbContext : DbContext
7+
{
68
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
79
public DbSet<TaskEntity> Tasks { get; set; }
810
public DbSet<UserEntity> Users { get; set; }
911
public DbSet<CategorieTaskEntity> CategorieTasks { get; set; }
12+
13+
protected override void OnModelCreating(ModelBuilder modelBuilder)
14+
{
15+
base.OnModelCreating(modelBuilder);
16+
17+
modelBuilder.Entity<TaskEntity>()
18+
.Property(x => x.RowVersion)
19+
.IsRowVersion();
20+
21+
modelBuilder.Entity<TaskEntity>()
22+
.Property(x => x.State)
23+
.HasConversion<string>();
24+
}
1025
}
11-
}
26+
}

Entitys/TaskEntity.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
1+
using System.ComponentModel.DataAnnotations;
12
using System.ComponentModel.DataAnnotations.Schema;
2-
using System.Diagnostics.CodeAnalysis;
33

44
namespace Todo.Domain;
55

6-
public class TaskEntity
6+
public class TaskEntity
77
{
88
public int Id { get; set; }
99
public string? Title { get; set; }
1010
public string? Description { get; set; }
1111
public bool Done { get; set; }
1212
public DateTime CreatedAt { get; set; }
13-
public DateTime FinishedAt { get; set; }
13+
public DateTime? FinishedAt { get; set; }
14+
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
15+
public int UpdatedBy { get; set; }
1416
public int CategorieTaskId { get; set; }
17+
public int Order { get; set; }
18+
public TaskState State { get; set; } = TaskState.Todo;
19+
public int? EstimateMinutes { get; set; }
20+
public int? SpentMinutes { get; set; }
21+
public DateTime? DueDate { get; set; }
22+
public int TenantId { get; set; } = 1;
23+
24+
[Timestamp]
25+
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
1526

1627
[ForeignKey("CategorieTaskId")]
1728
public virtual CategorieTaskEntity? Category { get; set; }
@@ -20,5 +31,4 @@ public class TaskEntity
2031
public virtual UserEntity? User { get; set; }
2132

2233
public int UserId { get; set; }
23-
2434
}

Entitys/TaskState.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Todo.Domain
2+
{
3+
public enum TaskState
4+
{
5+
Todo = 0,
6+
InProgress = 1,
7+
Done = 2
8+
}
9+
}

Entitys/UserEntity.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class UserEntity
99
public string Password { get; set; }
1010
public bool IsAdmin { get; set; }
1111
public bool IsLogged { get; set; }
12+
public int TenantId { get; set; } = 1;
1213
public byte[] ProfilePicture { get; set; }
1314
public DateTime CreatedAt { get; set; } = DateTime.Now;
1415
public virtual ICollection<TaskEntity> Tasks { get; set; } = new List<TaskEntity>();

0 commit comments

Comments
 (0)