using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Nexus.Api.DTOs; using Nexus.Api.Models; using Nexus.Api.Services; namespace Nexus.Api.Controllers; [Authorize] [ApiController] [Route("api/v1/projects")] public class ProjectsController(IProjectService projectService) : ControllerBase { [HttpGet] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task>> GetAll(CancellationToken ct) => Ok((await projectService.GetAllAsync(ct)).Select(project => Map(project)).ToArray()); [HttpGet("{id:guid}")] [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById(Guid id, CancellationToken ct) { var project = await projectService.GetByIdAsync(id, ct); return project is null ? NotFound() : Ok(Map(project)); } [HttpGet("{id:guid}/tasks")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task>> GetTasks( Guid id, CancellationToken ct) { if (await projectService.GetByIdAsync(id, ct) is null) return NotFound(); var tasks = await projectService.GetTasksAsync(id, ct); return Ok(tasks.Select(task => new ProjectTaskDto( task.Id, task.Title, task.State, task.Priority, id, task.AssignedTo, task.ExpectedFrom, task.IsAgentTask, task.UpdatedAt)).ToArray()); } [HttpPost] [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] public async Task> Create( [FromBody] CreateProjectRequest request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Name)) { ModelState.AddModelError("name", "Name is required."); return ValidationProblem(ModelState); } var project = await projectService.CreateAsync(request, ct); return Created( $"/api/v1/projects/{project.Id}", Map(project, ProjectOperation(project, "created"))); } [HttpPatch("{id:guid}")] [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> Update( Guid id, [FromBody] UpdateProjectRequest request, CancellationToken ct) { var project = await projectService.UpdateAsync(id, request, ct); return project is null ? NotFound() : Ok(Map(project, ProjectOperation(project, "updated"))); } [HttpDelete("{id:guid}")] [ProducesResponseType(typeof(ProjectDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(Guid id, CancellationToken ct) { var result = await projectService.DeleteAsync(id, ct); return result.Outcome switch { ProjectDeleteOutcome.NotFound => NotFound(), ProjectDeleteOutcome.Archived => Ok(Map( result.Project!, ProjectOperation(result.Project!, "archived"))), _ => Ok(Map( result.Project!, ProjectOperation(result.Project!, "deleted"))) }; } private OperationResultDto ProjectOperation( Nexus.Api.Data.Project project, string status) => OperationResultFactory.FromHttpContext( HttpContext, status, new EntityRefDto("project", project.Id.ToString(), project.Name)); private static ProjectDto Map( Nexus.Api.Data.Project project, OperationResultDto? operation = null) => new( project.Id, project.Name, project.Description, project.Status.ToString(), project.Progress, project.UpdatedAt, operation); }