fleshout backend

This commit is contained in:
KS Jannette
2026-08-03 04:23:32 -04:00
parent a00282b1e6
commit 092ef17439
19 changed files with 500 additions and 52 deletions

View File

@@ -1,10 +1,11 @@
using Backend.Gateway;
using Microsoft.AspNetCore.Mvc;
namespace Backend.Controllers;
[Route("v1/[controller]")]
[ApiController]
public class InfoController : ControllerBase
[Route(ApiRoutes.V1 + "/[controller]")]
public sealed class InfoController : ControllerBase
{
private readonly IWebHostEnvironment _environment;

View File

@@ -1,17 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Backend.Data;
namespace Backend.Controllers
{
[Route("v1/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
}
}

View File

@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Backend.Controllers;
// Validation attributes must target the primary constructor parameters, not the
// generated properties; MVC rejects record types with property-level metadata.
public sealed record CreateUserRequest(
[Required][EmailAddress] string Email,
[Required][StringLength(100, MinimumLength = 1)] string DisplayName);
public sealed record UpdateUserRequest(
[Required][EmailAddress] string Email,
[Required][StringLength(100, MinimumLength = 1)] string DisplayName);

View File

@@ -0,0 +1,96 @@
using Backend.Data;
using Backend.Gateway;
using Microsoft.AspNetCore.Mvc;
namespace Backend.Controllers;
[ApiController]
[Route(ApiRoutes.V1 + "/[controller]")]
public sealed class UsersController : ControllerBase
{
private readonly IUserRepository _userRepository;
public UsersController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
[HttpGet]
[ProducesResponseType<IReadOnlyList<User>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll(CancellationToken cancellationToken)
=> Ok(await _userRepository.GetAllAsync(cancellationToken));
[HttpGet("{id:guid}")]
[ProducesResponseType<User>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(Guid id, CancellationToken cancellationToken)
{
var user = await _userRepository.GetByIdAsync(id, cancellationToken);
return user is null ? NotFound() : Ok(user);
}
[HttpPost]
[ProducesResponseType<User>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<IActionResult> Create(
CreateUserRequest request,
CancellationToken cancellationToken)
{
if (await _userRepository.GetByEmailAsync(request.Email, cancellationToken) is not null)
{
return Conflict(new ProblemDetails
{
Title = "Email already in use",
Status = StatusCodes.Status409Conflict,
});
}
var user = await _userRepository.AddAsync(
new User { Email = request.Email, DisplayName = request.DisplayName },
cancellationToken);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
[HttpPut("{id:guid}")]
[ProducesResponseType<User>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<IActionResult> Update(
Guid id,
UpdateUserRequest request,
CancellationToken cancellationToken)
{
var existing = await _userRepository.GetByIdAsync(id, cancellationToken);
if (existing is null)
{
return NotFound();
}
var emailOwner = await _userRepository.GetByEmailAsync(request.Email, cancellationToken);
if (emailOwner is not null && emailOwner.Id != id)
{
return Conflict(new ProblemDetails
{
Title = "Email already in use",
Status = StatusCodes.Status409Conflict,
});
}
existing.Email = request.Email;
existing.DisplayName = request.DisplayName;
var updated = await _userRepository.UpdateAsync(existing, cancellationToken);
return updated is null ? NotFound() : Ok(updated);
}
[HttpDelete("{id:guid}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
=> await _userRepository.DeleteAsync(id, cancellationToken) ? NoContent() : NotFound();
}