533 lines
20 KiB
C#
533 lines
20 KiB
C#
using Extrudex.API.DTOs.Materials;
|
|
using Extrudex.API.Validators;
|
|
using Extrudex.Domain.Entities;
|
|
using Extrudex.Infrastructure.Data;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Extrudex.API.Controllers;
|
|
|
|
/// <summary>
|
|
/// Controller for querying material metadata — bases, finishes, and modifiers.
|
|
/// Provides lookup/reference data for the spool inventory system.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/materials")]
|
|
public class MaterialLookupsController : ControllerBase
|
|
{
|
|
private readonly ExtrudexDbContext _dbContext;
|
|
private readonly ILogger<MaterialLookupsController> _logger;
|
|
|
|
public MaterialLookupsController(ExtrudexDbContext dbContext, ILogger<MaterialLookupsController> logger)
|
|
{
|
|
_dbContext = dbContext;
|
|
_logger = logger;
|
|
}
|
|
|
|
// ── MaterialBase ──────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Gets all material bases (PLA, PETG, ABS, etc.).
|
|
/// </summary>
|
|
/// <returns>A list of all material bases with their densities.</returns>
|
|
[HttpGet("bases")]
|
|
[ProducesResponseType(typeof(IEnumerable<MaterialBaseResponse>), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<MaterialBaseResponse>>> GetMaterialBases()
|
|
{
|
|
_logger.LogDebug("Getting all material bases");
|
|
|
|
var bases = await _dbContext.MaterialBases
|
|
.OrderBy(mb => mb.Name)
|
|
.Select(mb => new MaterialBaseResponse
|
|
{
|
|
Id = mb.Id,
|
|
Name = mb.Name,
|
|
DensityGperCm3 = mb.DensityGperCm3,
|
|
CreatedAt = mb.CreatedAt,
|
|
UpdatedAt = mb.UpdatedAt
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(bases);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a specific material base by ID.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the material base.</param>
|
|
/// <returns>The material base details.</returns>
|
|
[HttpGet("bases/{id:guid}")]
|
|
[ProducesResponseType(typeof(MaterialBaseResponse), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<MaterialBaseResponse>> GetMaterialBase(Guid id)
|
|
{
|
|
_logger.LogDebug("Getting material base {Id}", id);
|
|
|
|
var mb = await _dbContext.MaterialBases.FindAsync(id);
|
|
if (mb is null)
|
|
{
|
|
_logger.LogWarning("Material base {Id} not found", id);
|
|
return NotFound(new { error = $"Material base with ID '{id}' not found." });
|
|
}
|
|
|
|
return Ok(MapToMaterialBaseResponse(mb));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new material base.
|
|
/// </summary>
|
|
/// <param name="request">The material base creation request.</param>
|
|
/// <returns>The created material base.</returns>
|
|
[HttpPost("bases")]
|
|
[ProducesResponseType(typeof(MaterialBaseResponse), StatusCodes.Status201Created)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<MaterialBaseResponse>> CreateMaterialBase(
|
|
[FromBody] CreateMaterialBaseRequest request)
|
|
{
|
|
_logger.LogInformation("Creating material base: {Name}", request.Name);
|
|
|
|
var entity = new MaterialBase
|
|
{
|
|
Name = request.Name,
|
|
DensityGperCm3 = request.DensityGperCm3
|
|
};
|
|
|
|
_dbContext.MaterialBases.Add(entity);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
var response = MapToMaterialBaseResponse(entity);
|
|
return CreatedAtAction(nameof(GetMaterialBase), new { id = entity.Id }, response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing material base.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the material base to update.</param>
|
|
/// <param name="request">The material base update request.</param>
|
|
/// <returns>The updated material base.</returns>
|
|
[HttpPut("bases/{id:guid}")]
|
|
[ProducesResponseType(typeof(MaterialBaseResponse), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<MaterialBaseResponse>> UpdateMaterialBase(
|
|
Guid id, [FromBody] UpdateMaterialBaseRequest request)
|
|
{
|
|
_logger.LogInformation("Updating material base {Id}", id);
|
|
|
|
var entity = await _dbContext.MaterialBases.FindAsync(id);
|
|
if (entity is null)
|
|
{
|
|
_logger.LogWarning("Material base {Id} not found for update", id);
|
|
return NotFound(new { error = $"Material base with ID '{id}' not found." });
|
|
}
|
|
|
|
entity.Name = request.Name;
|
|
entity.DensityGperCm3 = request.DensityGperCm3;
|
|
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
return Ok(MapToMaterialBaseResponse(entity));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a material base.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the material base to delete.</param>
|
|
[HttpDelete("bases/{id:guid}")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<IActionResult> DeleteMaterialBase(Guid id)
|
|
{
|
|
_logger.LogInformation("Deleting material base {Id}", id);
|
|
|
|
var entity = await _dbContext.MaterialBases.FindAsync(id);
|
|
if (entity is null)
|
|
{
|
|
_logger.LogWarning("Material base {Id} not found for deletion", id);
|
|
return NotFound(new { error = $"Material base with ID '{id}' not found." });
|
|
}
|
|
|
|
_dbContext.MaterialBases.Remove(entity);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
// ── MaterialFinish ────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Gets all material finishes, optionally filtered by material base.
|
|
/// </summary>
|
|
/// <param name="materialBaseId">Optional filter: return finishes for this material base only.</param>
|
|
/// <returns>A list of material finishes.</returns>
|
|
[HttpGet("finishes")]
|
|
[ProducesResponseType(typeof(IEnumerable<MaterialFinishResponse>), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<MaterialFinishResponse>>> 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 => new MaterialFinishResponse
|
|
{
|
|
Id = mf.Id,
|
|
Name = mf.Name,
|
|
MaterialBaseId = mf.MaterialBaseId,
|
|
MaterialBaseName = mf.MaterialBase.Name,
|
|
CreatedAt = mf.CreatedAt,
|
|
UpdatedAt = mf.UpdatedAt
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(finishes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a specific material finish by ID.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the finish.</param>
|
|
/// <returns>The material finish details.</returns>
|
|
[HttpGet("finishes/{id:guid}")]
|
|
[ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<MaterialFinishResponse>> 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(MapToMaterialFinishResponse(mf));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new material finish.
|
|
/// </summary>
|
|
/// <param name="request">The material finish creation request.</param>
|
|
/// <returns>The created material finish.</returns>
|
|
[HttpPost("finishes")]
|
|
[ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status201Created)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<MaterialFinishResponse>> 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();
|
|
|
|
var response = MapToMaterialFinishResponse(entity, materialBase.Name);
|
|
return CreatedAtAction(nameof(GetMaterialFinish), new { id = entity.Id }, response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing material finish.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the finish to update.</param>
|
|
/// <param name="request">The material finish update request.</param>
|
|
/// <returns>The updated material finish.</returns>
|
|
[HttpPut("finishes/{id:guid}")]
|
|
[ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<MaterialFinishResponse>> 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();
|
|
|
|
return Ok(MapToMaterialFinishResponse(entity, materialBase.Name));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a material finish.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the finish to delete.</param>
|
|
[HttpDelete("finishes/{id:guid}")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<IActionResult> 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();
|
|
}
|
|
|
|
// ── MaterialModifier ──────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Gets all material modifiers, optionally filtered by material base.
|
|
/// </summary>
|
|
/// <param name="materialBaseId">Optional filter: return modifiers for this material base only.</param>
|
|
/// <returns>A list of material modifiers.</returns>
|
|
[HttpGet("modifiers")]
|
|
[ProducesResponseType(typeof(IEnumerable<MaterialModifierResponse>), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<MaterialModifierResponse>>> GetMaterialModifiers(
|
|
[FromQuery] Guid? materialBaseId)
|
|
{
|
|
_logger.LogDebug("Getting material modifiers, filter: {MaterialBaseId}", materialBaseId);
|
|
|
|
var query = _dbContext.MaterialModifiers
|
|
.Include(mm => mm.MaterialBase)
|
|
.AsQueryable();
|
|
|
|
if (materialBaseId.HasValue)
|
|
{
|
|
query = query.Where(mm => mm.MaterialBaseId == materialBaseId.Value);
|
|
}
|
|
|
|
var modifiers = await query
|
|
.OrderBy(mm => mm.MaterialBase.Name)
|
|
.ThenBy(mm => mm.Name)
|
|
.Select(mm => new MaterialModifierResponse
|
|
{
|
|
Id = mm.Id,
|
|
Name = mm.Name,
|
|
MaterialBaseId = mm.MaterialBaseId,
|
|
MaterialBaseName = mm.MaterialBase.Name,
|
|
CreatedAt = mm.CreatedAt,
|
|
UpdatedAt = mm.UpdatedAt
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(modifiers);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a specific material modifier by ID.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the modifier.</param>
|
|
/// <returns>The material modifier details.</returns>
|
|
[HttpGet("modifiers/{id:guid}")]
|
|
[ProducesResponseType(typeof(MaterialModifierResponse), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<MaterialModifierResponse>> GetMaterialModifier(Guid id)
|
|
{
|
|
_logger.LogDebug("Getting material modifier {Id}", id);
|
|
|
|
var mm = await _dbContext.MaterialModifiers
|
|
.Include(mm => mm.MaterialBase)
|
|
.FirstOrDefaultAsync(mm => mm.Id == id);
|
|
|
|
if (mm is null)
|
|
{
|
|
_logger.LogWarning("Material modifier {Id} not found", id);
|
|
return NotFound(new { error = $"Material modifier with ID '{id}' not found." });
|
|
}
|
|
|
|
return Ok(MapToMaterialModifierResponse(mm));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new material modifier.
|
|
/// </summary>
|
|
/// <param name="request">The material modifier creation request.</param>
|
|
/// <returns>The created material modifier.</returns>
|
|
[HttpPost("modifiers")]
|
|
[ProducesResponseType(typeof(MaterialModifierResponse), StatusCodes.Status201Created)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<MaterialModifierResponse>> CreateMaterialModifier(
|
|
[FromBody] CreateMaterialModifierRequest request)
|
|
{
|
|
_logger.LogInformation("Creating material modifier: {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 MaterialModifier
|
|
{
|
|
Name = request.Name,
|
|
MaterialBaseId = request.MaterialBaseId
|
|
};
|
|
|
|
_dbContext.MaterialModifiers.Add(entity);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
var response = MapToMaterialModifierResponse(entity, materialBase.Name);
|
|
return CreatedAtAction(nameof(GetMaterialModifier), new { id = entity.Id }, response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing material modifier.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the modifier to update.</param>
|
|
/// <param name="request">The material modifier update request.</param>
|
|
/// <returns>The updated material modifier.</returns>
|
|
[HttpPut("modifiers/{id:guid}")]
|
|
[ProducesResponseType(typeof(MaterialModifierResponse), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<MaterialModifierResponse>> UpdateMaterialModifier(
|
|
Guid id, [FromBody] UpdateMaterialModifierRequest request)
|
|
{
|
|
_logger.LogInformation("Updating material modifier {Id}", id);
|
|
|
|
var entity = await _dbContext.MaterialModifiers.FindAsync(id);
|
|
if (entity is null)
|
|
{
|
|
_logger.LogWarning("Material modifier {Id} not found for update", id);
|
|
return NotFound(new { error = $"Material modifier 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();
|
|
|
|
return Ok(MapToMaterialModifierResponse(entity, materialBase.Name));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a material modifier.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the modifier to delete.</param>
|
|
[HttpDelete("modifiers/{id:guid}")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<IActionResult> DeleteMaterialModifier(Guid id)
|
|
{
|
|
_logger.LogInformation("Deleting material modifier {Id}", id);
|
|
|
|
var entity = await _dbContext.MaterialModifiers.FindAsync(id);
|
|
if (entity is null)
|
|
{
|
|
_logger.LogWarning("Material modifier {Id} not found for deletion", id);
|
|
return NotFound(new { error = $"Material modifier with ID '{id}' not found." });
|
|
}
|
|
|
|
_dbContext.MaterialModifiers.Remove(entity);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
// ── Density convenience endpoint ─────────────────────────
|
|
|
|
/// <summary>
|
|
/// Gets all material density data (shorthand for bases with density info).
|
|
/// </summary>
|
|
/// <returns>A list of material bases with their density values.</returns>
|
|
[HttpGet("densities")]
|
|
[ProducesResponseType(typeof(IEnumerable<MaterialBaseResponse>), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<MaterialBaseResponse>>> GetMaterialDensities()
|
|
{
|
|
_logger.LogDebug("Getting material densities");
|
|
|
|
var bases = await _dbContext.MaterialBases
|
|
.OrderBy(mb => mb.Name)
|
|
.Select(mb => new MaterialBaseResponse
|
|
{
|
|
Id = mb.Id,
|
|
Name = mb.Name,
|
|
DensityGperCm3 = mb.DensityGperCm3,
|
|
CreatedAt = mb.CreatedAt,
|
|
UpdatedAt = mb.UpdatedAt
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(bases);
|
|
}
|
|
|
|
// ── Mapping helpers ───────────────────────────────────────
|
|
|
|
private static MaterialBaseResponse MapToMaterialBaseResponse(MaterialBase mb) => new()
|
|
{
|
|
Id = mb.Id,
|
|
Name = mb.Name,
|
|
DensityGperCm3 = mb.DensityGperCm3,
|
|
CreatedAt = mb.CreatedAt,
|
|
UpdatedAt = mb.UpdatedAt
|
|
};
|
|
|
|
private static MaterialFinishResponse MapToMaterialFinishResponse(
|
|
MaterialFinish mf, string? materialBaseName = null) => new()
|
|
{
|
|
Id = mf.Id,
|
|
Name = mf.Name,
|
|
MaterialBaseId = mf.MaterialBaseId,
|
|
MaterialBaseName = materialBaseName ?? mf.MaterialBase?.Name ?? string.Empty,
|
|
CreatedAt = mf.CreatedAt,
|
|
UpdatedAt = mf.UpdatedAt
|
|
};
|
|
|
|
private static MaterialModifierResponse MapToMaterialModifierResponse(
|
|
MaterialModifier mm, string? materialBaseName = null) => new()
|
|
{
|
|
Id = mm.Id,
|
|
Name = mm.Name,
|
|
MaterialBaseId = mm.MaterialBaseId,
|
|
MaterialBaseName = materialBaseName ?? mm.MaterialBase?.Name ?? string.Empty,
|
|
CreatedAt = mm.CreatedAt,
|
|
UpdatedAt = mm.UpdatedAt
|
|
};
|
|
} |