using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using RobotNet10.MapEditor.Shared.DTOs.Responses; using RobotNet10.MapManager.Services; namespace RobotNet10.MapManager.Controllers; /// /// Controller for managing background images for layout levels /// [ApiController] [Route("api/images")] [Authorize] public class ImagesController( IImageStorageService imageStorageService, ILogger logger) : ControllerBase { private readonly IImageStorageService _imageStorageService = imageStorageService; private readonly ILogger _logger = logger; /// /// Get background image for a layout level /// /// Layout level ID /// Image file (PNG) [HttpGet("layout/{layoutLevelId}")] [ProducesResponseType(typeof(FileStreamResult), 200)] [ProducesResponseType(404)] public async Task GetLayoutImage(Guid layoutLevelId) { try { var imageStream = await _imageStorageService.GetImageAsync(layoutLevelId); if (imageStream == null) { if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Image not found for layout level {LevelId}", layoutLevelId); return NotFound(CreateErrorResponse($"Image not found for layout level '{layoutLevelId}'", "IMAGE_NOT_FOUND")); } return File(imageStream, "image/png", $"{layoutLevelId}.png"); } catch (Exception ex) { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error retrieving image for layout level {LevelId}", layoutLevelId); return StatusCode(500, CreateErrorResponse("Internal server error while retrieving image", "INTERNAL_ERROR")); } } /// /// Upload or replace background image for a layout level /// /// Layout level ID /// Image file (PNG format) /// Success message [HttpPost("layout/{layoutLevelId}")] [ProducesResponseType(200)] [ProducesResponseType(400)] public async Task UploadLayoutImage(Guid layoutLevelId, IFormFile file) { // Validate file if (file == null || file.Length == 0) { return BadRequest(CreateErrorResponse("No file provided", "VALIDATION_ERROR")); } // Validate file type if (!file.ContentType.Equals("image/png", StringComparison.OrdinalIgnoreCase)) { return BadRequest(CreateErrorResponse("Only PNG images are supported", "VALIDATION_ERROR")); } // Validate file size (max 10MB) const long maxFileSize = 10 * 1024 * 1024; // 10MB if (file.Length > maxFileSize) { return BadRequest(CreateErrorResponse($"File size exceeds maximum of {maxFileSize / 1024 / 1024}MB", "VALIDATION_ERROR")); } try { using var stream = file.OpenReadStream(); await _imageStorageService.SaveImageAsync(layoutLevelId, stream); return Ok(new { message = "Image uploaded successfully", layoutLevelId, fileName = $"{layoutLevelId}.png", size = file.Length }); } catch (Exception ex) { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error uploading image for layout level {LevelId}", layoutLevelId); return StatusCode(500, CreateErrorResponse("Internal server error while uploading image", "INTERNAL_ERROR")); } } /// /// Delete background image for a layout level /// /// Layout level ID /// No content on success [HttpDelete("layout/{layoutLevelId}")] [ProducesResponseType(204)] [ProducesResponseType(404)] public async Task DeleteLayoutImage(Guid layoutLevelId) { try { var deleted = await _imageStorageService.DeleteImageAsync(layoutLevelId); if (!deleted) { if (_logger.IsEnabled(LogLevel.Warning)) _logger.LogWarning("Image not found for deletion: {LevelId}", layoutLevelId); return NotFound(CreateErrorResponse($"Image not found for layout level '{layoutLevelId}'", "IMAGE_NOT_FOUND")); } return NoContent(); } catch (Exception ex) { if (_logger.IsEnabled(LogLevel.Error)) _logger.LogError(ex, "Error deleting image for layout level {LevelId}", layoutLevelId); return StatusCode(500, CreateErrorResponse("Internal server error while deleting image", "INTERNAL_ERROR")); } } private static ErrorResponseDto CreateErrorResponse( string error, string? errorCode = null, Dictionary? details = null) { return new ErrorResponseDto { Error = error, ErrorCode = errorCode, Details = details }; } }