refactor: Clean Architecture mit Repository Pattern, Controllern und DTOs
- 15 Controller-Klassen ersetzen Minimal APIs in Program.cs - Repository Pattern mit Interfaces + Implementierungen (Project, Task, Activity, User) - AuthService verwendet jetzt IUserRepository statt direktem DbContext-Zugriff - SecurityHeadersMiddleware als eigenständige Middleware-Klasse - PathSecurityHelper als gemeinsamer Helper für Pfadvalidierung - DTOs in eigenem Namespace Nexus.Api.DTOs - EF-Entities in Nexus.Api.Data (vorher Nexus.Api.Domain) - Program.cs auf DI-Registrierung + Middleware reduziert - Alle 43 Endpoints unverändert erhalten - Build + 3/3 Tests erfolgreich
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Nexus.Api.Data;
|
||||
using Nexus.Api.DTOs;
|
||||
using Nexus.Api.Repositories;
|
||||
|
||||
namespace Nexus.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/projects")]
|
||||
public class ProjectsController(IProjectRepository projectRepo, IActivityRepository activityRepo) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IResult> GetAll(CancellationToken ct)
|
||||
=> Results.Ok(await projectRepo.GetAllAsync(ct));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IResult> Create([FromBody] CreateProjectRequest request, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
return Results.ValidationProblem(new Dictionary<string, string[]> { ["name"] = ["Name is required."] });
|
||||
|
||||
var project = new Project
|
||||
{
|
||||
Name = request.Name.Trim(),
|
||||
Description = request.Description?.Trim() ?? string.Empty,
|
||||
Status = OperationalStatus.Online
|
||||
};
|
||||
await projectRepo.AddAsync(project, ct);
|
||||
await activityRepo.AddAsync(new ActivityEvent { Type = "project", Message = $"Project {project.Name} created" }, ct);
|
||||
return Results.Created($"/api/v1/projects/{project.Id}", project);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IResult> GetById(Guid id, CancellationToken ct)
|
||||
{
|
||||
var project = await projectRepo.GetByIdAsync(id, ct);
|
||||
return project is null ? Results.NotFound() : Results.Ok(project);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}")]
|
||||
public async Task<IResult> Update(Guid id, [FromBody] UpdateProjectRequest request, CancellationToken ct)
|
||||
{
|
||||
var project = await projectRepo.GetByIdAsync(id, ct);
|
||||
if (project is null) return Results.NotFound();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Name))
|
||||
project.Name = request.Name.Trim();
|
||||
if (request.Description is not null)
|
||||
project.Description = request.Description.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(request.Status) && Enum.TryParse<OperationalStatus>(request.Status, true, out var parsedStatus))
|
||||
project.Status = parsedStatus;
|
||||
|
||||
await projectRepo.UpdateAsync(project, ct);
|
||||
await activityRepo.AddAsync(new ActivityEvent { Type = "project", Message = $"Project {project.Name} updated" }, ct);
|
||||
return Results.Ok(project);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var project = await projectRepo.GetByIdAsync(id, ct);
|
||||
if (project is null) return Results.NotFound();
|
||||
|
||||
var hasTasks = await projectRepo.HasTasksAsync(id, ct);
|
||||
if (hasTasks)
|
||||
{
|
||||
project.Status = OperationalStatus.Offline;
|
||||
await projectRepo.UpdateAsync(project, ct);
|
||||
await activityRepo.AddAsync(new ActivityEvent { Type = "project", Message = $"Project {project.Name} archived" }, ct);
|
||||
return Results.Ok(project);
|
||||
}
|
||||
|
||||
await projectRepo.DeleteAsync(project, ct);
|
||||
await activityRepo.AddAsync(new ActivityEvent { Type = "project", Message = $"Project {project.Name} deleted" }, ct);
|
||||
return Results.NoContent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user