using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using RobotNet10.MapEditor.Shared.DTOs.Node; using RobotNet10.MapEditor.Shared.DTOs.Requests; using RobotNet10.MapEditor.Shared.DTOs.Responses; using RobotNet10.MapEditor.Shared.DTOs.Station; using RobotNet10.MapManager.Services; namespace RobotNet10.MapManager.Controllers; /// /// Controller for managing stations /// [ApiController] [Route("api/stations")] [Authorize] public class StationsController( IStationService stationService, ILogger logger) : ControllerBase { private readonly IStationService _stationService = stationService; private readonly ILogger _logger = logger; /// /// Create a new station /// /// Station creation request /// Created station [HttpPost] [ProducesResponseType(typeof(StationDto), 201)] [ProducesResponseType(400)] public async Task> CreateStation([FromBody] CreateStationRequest request) { try { var station = await _stationService.CreateAsync(request); var dto = MapToDto(station); return CreatedAtAction( nameof(GetStation), new { stationId = station.Id }, dto); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Failed to create station: {StationId}", request.StationId); return BadRequest(CreateErrorResponse(ex.Message, "VALIDATION_ERROR")); } } /// /// Get all stations for a layout level /// /// Layout level ID /// List of stations with interaction nodes [HttpGet("level/{layoutLevelId}")] [ProducesResponseType(typeof(List), 200)] public async Task>> GetStationsByLevel(Guid layoutLevelId) { var stations = await _stationService.GetStationsByLevelAsync(layoutLevelId, includeInteractionNodes: true); var dtos = stations.Select(MapToDto).ToList(); return Ok(dtos); } /// /// Get station by ID /// /// Station database ID /// Station details with interaction nodes [HttpGet("{stationId}")] [ProducesResponseType(typeof(StationDto), 200)] [ProducesResponseType(404)] public async Task> GetStation(Guid stationId) { var station = await _stationService.GetByIdAsync(stationId, includeInteractionNodes: true); if (station == null) { return NotFound(CreateErrorResponse($"Station with ID '{stationId}' not found", "STATION_NOT_FOUND")); } return Ok(MapToDto(station)); } /// /// Update station /// /// Station database ID /// Update request /// Updated station [HttpPut("{stationId}")] [ProducesResponseType(typeof(StationDto), 200)] [ProducesResponseType(400)] [ProducesResponseType(404)] public async Task> UpdateStation( Guid stationId, [FromBody] UpdateStationRequest request) { try { var station = await _stationService.UpdateAsync(stationId, request); return Ok(MapToDto(station)); } catch (InvalidOperationException ex) { if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning(ex, "Failed to update station: {StationId}", stationId); if (ex.Message.Contains("not found")) return NotFound(CreateErrorResponse(ex.Message, "STATION_NOT_FOUND")); return BadRequest(CreateErrorResponse(ex.Message, "VALIDATION_ERROR")); } } /// /// Delete station /// Deletes station and cascade deletes interaction nodes (but NOT the linked nodes) /// /// Station database ID /// No content on success [HttpDelete("{stationId}")] [ProducesResponseType(204)] [ProducesResponseType(404)] public async Task DeleteStation(Guid stationId) { var deleted = await _stationService.DeleteAsync(stationId); if (!deleted) { return NotFound(CreateErrorResponse($"Station with ID '{stationId}' not found", "STATION_NOT_FOUND")); } return NoContent(); } // Helper method to map entity to DTO private static StationDto MapToDto(Data.Station station) { return new StationDto { Id = station.Id, LevelId = station.LevelId, StationId = station.StationId, StationName = station.StationName, StationDescription = station.StationDescription, StationHeight = station.StationHeight, X = station.X, Y = station.Y, Theta = station.Theta, InteractionNodes = station.InteractionNodes?.Select(sin => new StationInteractionNodeDto { Id = sin.Id, StationId = sin.StationId, NodeId = sin.NodeId, Node = sin.Node != null ? new NodeDto { Id = sin.Node.Id, NodeId = sin.Node.NodeId, NodeName = sin.Node.NodeName, X = sin.Node.X, Y = sin.Node.Y, VehicleProperties = sin.Node.VehicleProperties?.Select(vp => new NodeVehiclePropertyDto { Id = vp.Id, VehicleTypeId = vp.VehicleTypeId, Theta = vp.Theta, Actions = vp.Actions }).ToList() } : null }).ToList() }; } private static ErrorResponseDto CreateErrorResponse( string error, string? errorCode = null, Dictionary? details = null) { return new ErrorResponseDto { Error = error, ErrorCode = errorCode, Details = details }; } }