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 modifiers — optional additives or /// fillers for a material (e.g., "Carbon Fiber", "Wood Fill", "Glow-in-the-Dark"). /// Not every spool has a modifier. /// [ApiController] [Route("api/material-modifiers")] public class MaterialModifiersController : ControllerBase { private readonly ExtrudexDbContext _dbContext; private readonly ILogger _logger; public MaterialModifiersController( ExtrudexDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; } /// /// Gets all material modifiers, optionally filtered by material base. /// /// Optional filter: return modifiers for this material base only. /// A list of material modifiers ordered by base material name, then modifier name. [HttpGet] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> 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 => MapToResponse(mm)) .ToListAsync(); return Ok(modifiers); } /// /// Gets a specific material modifier by ID. /// /// The unique identifier of the modifier. /// The material modifier details. [HttpGet("{id:guid}")] [ProducesResponseType(typeof(MaterialModifierResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> 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(MapToResponse(mm)); } /// /// Creates a new material modifier. /// /// The material modifier creation request. /// The created material modifier. [HttpPost] [ProducesResponseType(typeof(MaterialModifierResponse), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> 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(); // Reload with navigation for response mapping await _dbContext.Entry(entity).Reference(e => e.MaterialBase).LoadAsync(); var response = MapToResponse(entity); return CreatedAtAction(nameof(GetMaterialModifier), new { id = entity.Id }, response); } /// /// Updates an existing material modifier. /// /// The unique identifier of the modifier to update. /// The material modifier update request. /// The updated material modifier. [HttpPut("{id:guid}")] [ProducesResponseType(typeof(MaterialModifierResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> 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(); // Reload with navigation for response mapping await _dbContext.Entry(entity).Reference(e => e.MaterialBase).LoadAsync(); return Ok(MapToResponse(entity)); } /// /// Deletes a material modifier. /// /// The unique identifier of the modifier to delete. [HttpDelete("{id:guid}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task 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(); } // ── Mapping helper ────────────────────────────────────────── private static MaterialModifierResponse MapToResponse(MaterialModifier mm) => new() { Id = mm.Id, Name = mm.Name, MaterialBaseId = mm.MaterialBaseId, MaterialBaseName = mm.MaterialBase?.Name ?? string.Empty, CreatedAt = mm.CreatedAt, UpdatedAt = mm.UpdatedAt }; }