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
);
}