using ErrorOr; using Microsoft.AspNetCore.Mvc; using MuiCharts.Contracts.Point; using MuiCharts.Domain.Models; using MuiCharts.Domain.Repositories; namespace MuiCharts.Api.Controllers; /// /// Controller for managing points. /// public class PointsController( ILogger logger, IPointRepository pointRepository ) : ApiControllerBase(logger) { private readonly IPointRepository _repository = pointRepository; /// /// Creates a new point. /// /// The new point model. /// An representing the asynchronous operation result. [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task CreatePointAsync(UpsertPointRequest request) { Logger.LogInformation("Creating point with name {Name} and height {Height}", request.Name, request.Height); Point point = new() { Id = default, Name = request.Name, Height = request.Height }; ErrorOr createResult = await _repository.AddPointAsync(point); if (createResult.IsError) return Problem(createResult.Errors); Logger.LogInformation("Point created with id {Id}", createResult.Value.Id); return CreatedAtPointResult(createResult.Value); } /// /// Retrieves an array of points based on the provided IDs. /// /// The array of point IDs. /// An representing the asynchronous operation result. [HttpPost("Array")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status206PartialContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task GetPointsArrayAsync(int[] ids) { Logger.LogInformation("Getting points with ids {Ids}", ids); if (ids.Length < 1) { Logger.LogInformation("No point IDs provided"); return Problem([Error.Validation()]); } IQueryable query = await _repository.GetPointsRangeAsync(); PointResponse[] points = [ .. query .Where(point => ids.Contains(point.Id)) .Select(point => MapPointResponse(point)) ]; if (points.Length == 0) { Logger.LogInformation("No points found with ids {Ids}", ids); return NotFound(); } if (points.Length != ids.Length) { Logger.LogInformation("Not all points found with ids {Ids}", ids); return StatusCode(StatusCodes.Status206PartialContent, points); } Logger.LogInformation("Returning {Count} points", points.Length); return Ok(points); } /// /// Retrieves a range of points based on the specified page and count. /// /// The request object containing the page and count parameters. /// An representing the asynchronous operation result. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task GetPointsAsync([FromQuery] GetPointsRequest request) { Logger.LogInformation("Getting points with page {Page} and count {Count}", request.Page, request.Count); IQueryable query = await _repository.GetPointsRangeAsync(); PointResponse[] points = [ .. query .OrderBy(i => i.Id) .Skip((request.Page - 1) * request.Count) .Take(request.Count) .Select(point => MapPointResponse(point)) ]; GetPointsResponse response = new( points, query.Count(), points.Length, request.Page ); Logger.LogInformation("Returning {Count} points", response.Count); return Ok(response); } /// /// Retrieves a point with the specified ID. /// /// The ID of the point to retrieve. /// An representing the asynchronous operation result. [HttpGet("{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task GetPointAsync(int id) { Logger.LogInformation("Getting point with id {Id}", id); ErrorOr getResult = await _repository.GetPointAsync(id); if (getResult.IsError) return Problem(getResult.Errors); Logger.LogInformation("Returning point with id {Id}", id); return Ok(MapPointResponse(getResult.Value)); } /// /// Upserts a point with the specified ID and request data. /// /// The ID of the point. /// The request data for the point. /// An representing the asynchronous operation result. [HttpPut("{id:int}")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task UpsertPointAsync(int id, UpsertPointRequest request) { Logger.LogInformation("Upserting point with id {Id}", id); Point point = new() { Id = id, Name = request.Name, Height = request.Height }; ErrorOr upsertResult = await _repository.AddOrUpdatePointAsync(point); if (upsertResult.IsError) return Problem(upsertResult.Errors); if (upsertResult.Value is Point value) { Logger.LogInformation("Point created with id {Id}", value.Id); return CreatedAtPointResult(value); } Logger.LogInformation("Point updated with id {Id}", id); return NoContent(); } /// /// Imports an array of points. /// /// The array of points to import. /// An representing the asynchronous operation result. [HttpPost("Import")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task ImportPointsAsync(Point[] points) { Logger.LogInformation("Importing points"); ErrorOr> importResult = await _repository.AddPointsRangeAsync(points); if (importResult.IsError) return Problem(importResult.Errors); Logger.LogInformation("Imported {Count} points", importResult.Value.Count()); return Ok(importResult.Value); } /// /// Deletes a point with the specified ID. /// /// The ID of the point to delete. /// An representing the asynchronous operation result. [HttpDelete("{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesDefaultResponseType(typeof(ProblemDetails))] public async Task DeletePointAsync(int id) { Logger.LogInformation("Deleting point with id {Id}", id); ErrorOr deleteResult = await _repository.DeletePointAsync(id); if (deleteResult.IsError) return Problem(deleteResult.Errors); Logger.LogInformation("Point deleted with id {Id}", id); return NoContent(); } private CreatedAtActionResult CreatedAtPointResult(Point point) => CreatedAtAction( actionName: nameof(GetPointAsync), routeValues: new { id = point.Id }, value: MapPointResponse(point) ); private static PointResponse MapPointResponse(Point value) => new( value.Id, value.Name, value.Height ); }