Add TodosController with GET/POST/PATCH/DELETE endpoints
This commit is contained in:
77
backend/Controllers/TodosController.cs
Normal file
77
backend/Controllers/TodosController.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Backend.Data;
|
||||||
|
using Backend.Dtos;
|
||||||
|
using Backend.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Backend.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/todos")]
|
||||||
|
[Authorize]
|
||||||
|
public class TodosController(AppDbContext db) : ControllerBase
|
||||||
|
{
|
||||||
|
private Guid CurrentUserId()
|
||||||
|
{
|
||||||
|
var sub = User.FindFirstValue("sub")
|
||||||
|
?? throw new InvalidOperationException("Authenticated request missing 'sub' claim");
|
||||||
|
return Guid.Parse(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<IEnumerable<TodoDto>>> List()
|
||||||
|
{
|
||||||
|
var userId = CurrentUserId();
|
||||||
|
var todos = await db.Todos
|
||||||
|
.Where(t => t.UserId == userId)
|
||||||
|
.OrderByDescending(t => t.CreatedAt)
|
||||||
|
.Select(t => new TodoDto(t.Id, t.Title, t.Completed, t.CreatedAt))
|
||||||
|
.ToListAsync();
|
||||||
|
return Ok(todos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<ActionResult<TodoDto>> Create([FromBody] CreateTodoRequest body)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid) return ValidationProblem(ModelState);
|
||||||
|
|
||||||
|
var userId = CurrentUserId();
|
||||||
|
var todo = new Todo
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Title = body.Title,
|
||||||
|
Completed = false,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow
|
||||||
|
};
|
||||||
|
db.Todos.Add(todo);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var dto = new TodoDto(todo.Id, todo.Title, todo.Completed, todo.CreatedAt);
|
||||||
|
return CreatedAtAction(nameof(List), new { id = todo.Id }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id:long}")]
|
||||||
|
public async Task<ActionResult<TodoDto>> Update(long id, [FromBody] UpdateTodoRequest body)
|
||||||
|
{
|
||||||
|
var userId = CurrentUserId();
|
||||||
|
var todo = await db.Todos.FirstOrDefaultAsync(t => t.Id == id && t.UserId == userId);
|
||||||
|
if (todo is null) return NotFound();
|
||||||
|
|
||||||
|
if (body.Completed.HasValue) todo.Completed = body.Completed.Value;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(new TodoDto(todo.Id, todo.Title, todo.Completed, todo.CreatedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id:long}")]
|
||||||
|
public async Task<IActionResult> Delete(long id)
|
||||||
|
{
|
||||||
|
var userId = CurrentUserId();
|
||||||
|
var affected = await db.Todos
|
||||||
|
.Where(t => t.Id == id && t.UserId == userId)
|
||||||
|
.ExecuteDeleteAsync();
|
||||||
|
return affected == 0 ? NotFound() : NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
backend/Dtos/CreateTodoRequest.cs
Normal file
5
backend/Dtos/CreateTodoRequest.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Backend.Dtos;
|
||||||
|
|
||||||
|
public record CreateTodoRequest([Required, StringLength(500, MinimumLength = 1)] string Title);
|
||||||
3
backend/Dtos/TodoDto.cs
Normal file
3
backend/Dtos/TodoDto.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Backend.Dtos;
|
||||||
|
|
||||||
|
public record TodoDto(long Id, string Title, bool Completed, DateTimeOffset CreatedAt);
|
||||||
3
backend/Dtos/UpdateTodoRequest.cs
Normal file
3
backend/Dtos/UpdateTodoRequest.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Backend.Dtos;
|
||||||
|
|
||||||
|
public record UpdateTodoRequest(bool? Completed);
|
||||||
Reference in New Issue
Block a user