a79d8282dc
- 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
78 lines
3.1 KiB
C#
78 lines
3.1 KiB
C#
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();
|
|
}
|
|
}
|