chore: initial commit of TaskTracker project
Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
171
TaskTracker.Api/Controllers/TasksController.cs
Normal file
171
TaskTracker.Api/Controllers/TasksController.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
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,
|
||||
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));
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await taskRepo.DeleteAsync(id);
|
||||
logger.LogInformation("Abandoned task {TaskId}", id);
|
||||
return Ok(ApiResponse.Ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user