Files
TaskTracker/TaskTracker.Api/Controllers/TasksController.cs

189 lines
6.4 KiB
C#

using Microsoft.AspNetCore.Mvc;
using TaskTracker.Core.DTOs;
using TaskTracker.Core.Entities;
using TaskTracker.Core.Enums;
using TaskTracker.Core.Interfaces;
namespace TaskTracker.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class TasksController(ITaskRepository taskRepo, ILogger<TasksController> logger) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] WorkTaskStatus? status, [FromQuery] int? parentId, [FromQuery] bool includeSubTasks = false)
{
var tasks = await taskRepo.GetAllAsync(status, parentId, includeSubTasks);
return Ok(ApiResponse<List<WorkTask>>.Ok(tasks));
}
[HttpGet("active")]
public async Task<IActionResult> GetActive()
{
var task = await taskRepo.GetActiveTaskAsync();
if (task is null)
return Ok(ApiResponse<WorkTask?>.Ok(null));
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpGet("{id:int}")]
public async Task<IActionResult> GetById(int id)
{
var task = await taskRepo.GetByIdAsync(id);
if (task is null)
return NotFound(ApiResponse.Fail("Task not found"));
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateTaskRequest request)
{
if (request.ParentTaskId.HasValue)
{
var parent = await taskRepo.GetByIdAsync(request.ParentTaskId.Value);
if (parent is null)
return BadRequest(ApiResponse.Fail("Parent task not found"));
}
var task = new WorkTask
{
Title = request.Title,
Description = request.Description,
Category = request.Category,
ParentTaskId = request.ParentTaskId,
EstimatedMinutes = request.EstimatedMinutes,
Status = WorkTaskStatus.Pending
};
var created = await taskRepo.CreateAsync(task);
logger.LogInformation("Created task {TaskId}: {Title}", created.Id, created.Title);
return CreatedAtAction(nameof(GetById), new { id = created.Id }, ApiResponse<WorkTask>.Ok(created));
}
[HttpPut("{id:int}/start")]
public async Task<IActionResult> Start(int id)
{
var task = await taskRepo.GetByIdAsync(id);
if (task is null)
return NotFound(ApiResponse.Fail("Task not found"));
// Pause any currently active task
var active = await taskRepo.GetActiveTaskAsync();
if (active is not null && active.Id != id)
{
active.Status = WorkTaskStatus.Paused;
await taskRepo.UpdateAsync(active);
}
task.Status = WorkTaskStatus.Active;
task.StartedAt ??= DateTime.UtcNow;
await taskRepo.UpdateAsync(task);
logger.LogInformation("Started task {TaskId}", id);
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpPut("{id:int}/pause")]
public async Task<IActionResult> Pause(int id, [FromBody] TaskActionRequest? request)
{
var task = await taskRepo.GetByIdAsync(id);
if (task is null)
return NotFound(ApiResponse.Fail("Task not found"));
task.Status = WorkTaskStatus.Paused;
if (!string.IsNullOrWhiteSpace(request?.Note))
{
task.Notes.Add(new TaskNote
{
Content = request.Note,
Type = NoteType.PauseNote,
CreatedAt = DateTime.UtcNow
});
}
await taskRepo.UpdateAsync(task);
logger.LogInformation("Paused task {TaskId}", id);
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpPut("{id:int}/resume")]
public async Task<IActionResult> Resume(int id, [FromBody] TaskActionRequest? request)
{
var task = await taskRepo.GetByIdAsync(id);
if (task is null)
return NotFound(ApiResponse.Fail("Task not found"));
// Pause any currently active task
var active = await taskRepo.GetActiveTaskAsync();
if (active is not null && active.Id != id)
{
active.Status = WorkTaskStatus.Paused;
await taskRepo.UpdateAsync(active);
}
task.Status = WorkTaskStatus.Active;
if (!string.IsNullOrWhiteSpace(request?.Note))
{
task.Notes.Add(new TaskNote
{
Content = request.Note,
Type = NoteType.ResumeNote,
CreatedAt = DateTime.UtcNow
});
}
await taskRepo.UpdateAsync(task);
logger.LogInformation("Resumed task {TaskId}", id);
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpPut("{id:int}/complete")]
public async Task<IActionResult> Complete(int id)
{
var task = await taskRepo.GetByIdAsync(id);
if (task is null)
return NotFound(ApiResponse.Fail("Task not found"));
// Block completion if any subtasks are not completed/abandoned
var incompleteSubTasks = task.SubTasks
.Where(st => st.Status != WorkTaskStatus.Completed && st.Status != WorkTaskStatus.Abandoned)
.ToList();
if (incompleteSubTasks.Count > 0)
return BadRequest(ApiResponse.Fail($"Cannot complete task: {incompleteSubTasks.Count} subtask(s) are still incomplete"));
task.Status = WorkTaskStatus.Completed;
task.CompletedAt = DateTime.UtcNow;
await taskRepo.UpdateAsync(task);
logger.LogInformation("Completed task {TaskId}", id);
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpPut("{id:int}")]
public async Task<IActionResult> Update(int id, [FromBody] UpdateTaskRequest request)
{
var task = await taskRepo.GetByIdAsync(id);
if (task is null)
return NotFound(ApiResponse.Fail("Task not found"));
if (request.Title is not null) task.Title = request.Title;
if (request.Description is not null) task.Description = request.Description;
if (request.Category is not null) task.Category = request.Category;
if (request.EstimatedMinutes.HasValue) task.EstimatedMinutes = request.EstimatedMinutes;
await taskRepo.UpdateAsync(task);
return Ok(ApiResponse<WorkTask>.Ok(task));
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id)
{
await taskRepo.DeleteAsync(id);
logger.LogInformation("Abandoned task {TaskId}", id);
return Ok(ApiResponse.Ok());
}
}