using Extrudex.API.DTOs.Materials;
using Extrudex.Domain.Entities;
using Extrudex.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Extrudex.API.Controllers;
///
/// Controller for managing material finishes — the surface finish descriptor
/// for a material. This is REQUIRED on every spool record. Default: "Basic".
///
[ApiController]
[Route("api/material-finishes")]
public class MaterialFinishesController : ControllerBase
{
private readonly ExtrudexDbContext _dbContext;
private readonly ILogger _logger;
public MaterialFinishesController(
ExtrudexDbContext dbContext,
ILogger logger)
{
_dbContext = dbContext;
_logger = logger;
}
///
/// Gets all material finishes, optionally filtered by material base.
///
/// Optional filter: return finishes for this material base only.
/// A list of material finishes ordered by base material name, then finish name.
[HttpGet]
[ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)]
public async Task>> GetMaterialFinishes(
[FromQuery] Guid? materialBaseId)
{
_logger.LogDebug("Getting material finishes, filter: {MaterialBaseId}", materialBaseId);
var query = _dbContext.MaterialFinishes
.Include(mf => mf.MaterialBase)
.AsQueryable();
if (materialBaseId.HasValue)
query = query.Where(mf => mf.MaterialBaseId == materialBaseId.Value);
var finishes = await query
.OrderBy(mf => mf.MaterialBase.Name)
.ThenBy(mf => mf.Name)
.Select(mf => MapToResponse(mf))
.ToListAsync();
return Ok(finishes);
}
///
/// Gets a specific material finish by ID.
///
/// The unique identifier of the finish.
/// The material finish details.
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> GetMaterialFinish(Guid id)
{
_logger.LogDebug("Getting material finish {Id}", id);
var mf = await _dbContext.MaterialFinishes
.Include(mf => mf.MaterialBase)
.FirstOrDefaultAsync(mf => mf.Id == id);
if (mf is null)
{
_logger.LogWarning("Material finish {Id} not found", id);
return NotFound(new { error = $"Material finish with ID '{id}' not found." });
}
return Ok(MapToResponse(mf));
}
///
/// Creates a new material finish.
///
/// The material finish creation request.
/// The created material finish.
[HttpPost]
[ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task> CreateMaterialFinish(
[FromBody] CreateMaterialFinishRequest request)
{
_logger.LogInformation("Creating material finish: {Name} for base {MaterialBaseId}",
request.Name, request.MaterialBaseId);
var materialBase = await _dbContext.MaterialBases.FindAsync(request.MaterialBaseId);
if (materialBase is null)
{
return BadRequest(new { error = $"MaterialBase with ID '{request.MaterialBaseId}' not found." });
}
var entity = new MaterialFinish
{
Name = request.Name,
MaterialBaseId = request.MaterialBaseId
};
_dbContext.MaterialFinishes.Add(entity);
await _dbContext.SaveChangesAsync();
// Reload with navigation for response mapping
await _dbContext.Entry(entity).Reference(e => e.MaterialBase).LoadAsync();
var response = MapToResponse(entity);
return CreatedAtAction(nameof(GetMaterialFinish), new { id = entity.Id }, response);
}
///
/// Updates an existing material finish.
///
/// The unique identifier of the finish to update.
/// The material finish update request.
/// The updated material finish.
[HttpPut("{id:guid}")]
[ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task> UpdateMaterialFinish(
Guid id, [FromBody] UpdateMaterialFinishRequest request)
{
_logger.LogInformation("Updating material finish {Id}", id);
var entity = await _dbContext.MaterialFinishes.FindAsync(id);
if (entity is null)
{
_logger.LogWarning("Material finish {Id} not found for update", id);
return NotFound(new { error = $"Material finish with ID '{id}' not found." });
}
var materialBase = await _dbContext.MaterialBases.FindAsync(request.MaterialBaseId);
if (materialBase is null)
{
return BadRequest(new { error = $"MaterialBase with ID '{request.MaterialBaseId}' not found." });
}
entity.Name = request.Name;
entity.MaterialBaseId = request.MaterialBaseId;
await _dbContext.SaveChangesAsync();
// Reload with navigation for response mapping
await _dbContext.Entry(entity).Reference(e => e.MaterialBase).LoadAsync();
return Ok(MapToResponse(entity));
}
///
/// Deletes a material finish.
///
/// The unique identifier of the finish to delete.
[HttpDelete("{id:guid}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DeleteMaterialFinish(Guid id)
{
_logger.LogInformation("Deleting material finish {Id}", id);
var entity = await _dbContext.MaterialFinishes.FindAsync(id);
if (entity is null)
{
_logger.LogWarning("Material finish {Id} not found for deletion", id);
return NotFound(new { error = $"Material finish with ID '{id}' not found." });
}
_dbContext.MaterialFinishes.Remove(entity);
await _dbContext.SaveChangesAsync();
return NoContent();
}
// ── Mapping helper ──────────────────────────────────────────
private static MaterialFinishResponse MapToResponse(MaterialFinish mf) => new()
{
Id = mf.Id,
Name = mf.Name,
MaterialBaseId = mf.MaterialBaseId,
MaterialBaseName = mf.MaterialBase?.Name ?? string.Empty,
CreatedAt = mf.CreatedAt,
UpdatedAt = mf.UpdatedAt
};
}