using Api.SeaHavenIndustries.DTOs; using Data.SeaHavenIndustries; using Data.SeaHavenIndustries.Enums; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SeaHaven.Services.DTOs; using SeaHaven.Services.Exceptions; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; using System.Security.Claims; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("api/WorkOrder")] [Route("api/workorders")] public class WorkOrderMediaController : Controller { public const long MaxUploadBytes = 200_000_000; private readonly IWorkOrderMediaService _workOrderMediaService; private readonly IFileStoragePort _fileStorage; public WorkOrderMediaController( IWorkOrderMediaService workOrderMediaService, IFileStoragePort fileStorage) { _workOrderMediaService = workOrderMediaService; _fileStorage = fileStorage; } [HttpGet("{id:int}/media")] public async Task GetMedia(int id, CancellationToken cancellationToken) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); var media = await _workOrderMediaService.GetMediaAsync(id, User, actorId, cancellationToken); if (media == null) return NotFound(new Response { Status = "Error", Message = "Work order not found." }); return Ok(media); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } } [HttpGet("{id:int}/media/{mediaId:int}/content")] public async Task GetMediaContent(int id, int mediaId, CancellationToken cancellationToken) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); var content = await _workOrderMediaService.GetMediaContentAsync( id, mediaId, User, actorId, cancellationToken); return File(content.Content, content.ContentType, content.FileName); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } } [HttpPost("{id:int}/media")] [RequestSizeLimit(MaxUploadBytes)] [RequestFormLimits(MultipartBodyLengthLimit = MaxUploadBytes)] public async Task AddMedia( int id, [FromForm] WorkOrderMediaCategory? category, [FromForm] IFormFile file, CancellationToken cancellationToken) { if (file == null || file.Length == 0) return BadRequest(new Response { Status = "Error", Message = "file is required." }); string? fileUrl = null; try { if (file.Length > MaxUploadBytes) { throw new WorkOrderBoardValidationException( "FileTooLarge", "The uploaded file must not exceed 200 MB."); } WorkOrderMediaFileRules.EnsureAllowed(file, category); var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); await _workOrderMediaService.EnsureCanMutateMediaAsync(id, User, actorId, cancellationToken, category); fileUrl = await _fileStorage.SaveFileAsync(file); var media = await _workOrderMediaService.AddMediaAsync(id, category, fileUrl, User, actorId, cancellationToken); return Ok(media); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { TryCompensateUpload(fileUrl); return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { TryCompensateUpload(fileUrl); return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) { TryCompensateUpload(fileUrl); return UnprocessableEntity(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch { TryCompensateUpload(fileUrl); throw; } } [HttpPatch("{id:int}/media/{mediaId:int}")] public async Task UpdateMediaCategory( int id, int mediaId, [FromForm] WorkOrderMediaCategory category, [FromForm] string? workOrderVersion = null, CancellationToken cancellationToken = default) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); var media = await _workOrderMediaService.UpdateMediaCategoryAsync( id, mediaId, category, workOrderVersion, User, actorId, cancellationToken); return Ok(media); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "ConcurrencyConflict") { return Conflict(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) { return UnprocessableEntity(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } } [HttpDelete("{id:int}/media/{mediaId:int}")] public async Task DeleteMedia( int id, int mediaId, [FromQuery] string? workOrderVersion = null, CancellationToken cancellationToken = default) { try { var actorId = User.FindFirstValue(ClaimTypes.NameIdentifier); await _workOrderMediaService.DeleteMediaAsync(id, mediaId, workOrderVersion, User, actorId, cancellationToken); return NoContent(); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "NotFound") { return NotFound(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "Forbidden") { return StatusCode(StatusCodes.Status403Forbidden, new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) when (ex.Code == "ConcurrencyConflict") { return Conflict(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } catch (WorkOrderBoardValidationException ex) { return UnprocessableEntity(new WorkOrderBoardValidationErrorDto { Code = ex.Code, Message = ex.Message }); } } private void TryCompensateUpload(string? fileUrl) { if (!string.IsNullOrWhiteSpace(fileUrl)) _fileStorage.TryDelete(fileUrl); } } }