2026-07-24 17:35:34 -03:00
using Api.SeaHavenIndustries.Helper ;
2026-04-20 13:25:56 -04:00
using Data.SeaHavenIndustries ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
2026-07-24 17:35:34 -03:00
using Microsoft.Extensions.Logging ;
using SeaHaven.Services.DTOs ;
using SeaHaven.Services.Interfaces ;
2026-04-20 13:25:56 -04:00
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller] ")]
[Route("api/uplifts")]
public class UpliftController : Controller
{
2026-07-24 17:35:34 -03:00
private readonly IUpliftService _upliftService ;
private readonly ILogger < UpliftController > _logger ;
2026-04-20 13:25:56 -04:00
2026-07-24 17:35:34 -03:00
public UpliftController ( IUpliftService upliftService , ILogger < UpliftController > logger )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
_upliftService = upliftService ;
_logger = logger ;
2026-04-20 13:25:56 -04:00
}
[HttpGet]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > List ( [ FromQuery ] string? status = "Pending" , [ FromQuery ] int? tier = null , [ FromQuery ] int page = 1 , [ FromQuery ] int pageSize = 25 , CancellationToken cancellationToken = default )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _upliftService . ListAsync ( User , status , tier , page , pageSize , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
2026-04-20 13:25:56 -04:00
}
[HttpGet("dispatch/{dispatchId:int}")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > ListForDispatch ( int dispatchId , CancellationToken cancellationToken = default )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
var items = await _upliftService . ListForDispatchAsync ( User , dispatchId , cancellationToken ) ;
2026-04-20 13:25:56 -04:00
return Ok ( new DataResponse { Status = "Success" , Data = items } ) ;
}
[HttpPost("{id:int}/approve")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > Approve ( int id , [ FromBody ] DecisionRequest ? body , CancellationToken cancellationToken = default )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
try
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _upliftService . ApproveAsync ( User , id , body ? . Note , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException ex )
{
return NotFound ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "Uplift request not found" ) } ) ;
}
catch ( UpliftForbiddenException ex )
{
return StatusCode ( 403 , new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "You are not authorized to perform this action on this uplift request" ) } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "This uplift request cannot be modified in its current state" ) } ) ;
}
2026-04-20 13:25:56 -04:00
}
[HttpPost("{id:int}/deny")]
2026-07-24 17:35:34 -03:00
public async Task < IActionResult > Deny ( int id , [ FromBody ] DecisionRequest ? body , CancellationToken cancellationToken = default )
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
try
2026-04-20 13:25:56 -04:00
{
2026-07-24 17:35:34 -03:00
var result = await _upliftService . DenyAsync ( User , id , body ? . Note , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException ex )
{
return NotFound ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "Uplift request not found" ) } ) ;
}
catch ( UpliftForbiddenException ex )
{
return StatusCode ( 403 , new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "You are not authorized to perform this action on this uplift request" ) } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "This uplift request cannot be modified in its current state" ) } ) ;
}
2026-04-20 13:25:56 -04:00
}
2026-08-11 08:58:19 -03:00
// SH-101: canonical reject route (result is Rejected); deny alias stays compatible.
[HttpPost("{id:int}/reject")]
public async Task < IActionResult > Reject ( int id , [ FromBody ] DecisionRequest ? body , CancellationToken cancellationToken = default )
{
try
{
var result = await _upliftService . RejectAsync ( User , id , body ? . Note , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException ex )
{
return NotFound ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "Uplift request not found" ) } ) ;
}
catch ( UpliftForbiddenException ex )
{
return StatusCode ( 403 , new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "You are not authorized to perform this action on this uplift request" ) } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "This uplift request cannot be modified in its current state" ) } ) ;
}
}
// SH-101: internal request-changes route (note + tier authorization + audit).
[HttpPost("{id:int}/request-changes")]
public async Task < IActionResult > RequestChanges ( int id , [ FromBody ] DecisionRequest ? body , CancellationToken cancellationToken = default )
{
try
{
var result = await _upliftService . RequestChangesAsync ( User , id , body ? . Note ? ? string . Empty , cancellationToken ) ;
return Ok ( new DataResponse { Status = "Success" , Data = result } ) ;
}
catch ( KeyNotFoundException ex )
{
return NotFound ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "Uplift request not found" ) } ) ;
}
catch ( UpliftForbiddenException ex )
{
return StatusCode ( 403 , new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "You are not authorized to perform this action on this uplift request" ) } ) ;
}
catch ( InvalidOperationException ex )
{
return BadRequest ( new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "This uplift request cannot be modified in its current state" ) } ) ;
}
}
2026-04-20 13:25:56 -04:00
[HttpGet("can-approve")]
public IActionResult CanApprove ( [ FromQuery ] int tier )
{
2026-07-24 17:35:34 -03:00
return Ok ( new DataResponse { Status = "Success" , Data = new { canApprove = _upliftService . CanApprove ( User , tier ) } } ) ;
2026-04-20 13:25:56 -04:00
}
2026-08-11 08:58:19 -03:00
// SH-101: authorized internal download of the Passed UpliftEvidence file linked to
// a specific uplift request. Server-side linkage only; no vendor/public path or
// document id is accepted from the client. 404 covers missing request/evidence and
// any non-UpliftEvidence document; 423 covers a scan that has not Passed.
[HttpGet("{id:int}/evidence")]
public async Task < IActionResult > DownloadEvidence ( int id , CancellationToken cancellationToken = default )
{
try
{
var result = await _upliftService . GetEvidenceForDownloadAsync ( User , id , cancellationToken ) ;
return result . Outcome switch
{
VendorDocumentDownloadOutcome . Ok = > File ( result . Content ! , result . ContentType ! , result . FileName ! ) ,
VendorDocumentDownloadOutcome . Locked = > StatusCode ( StatusCodes . Status423Locked , new Response { Status = "Locked" , Message = "The uplift evidence is not available for download yet." } ) ,
_ = > NotFound ( new Response { Status = "Error" , Message = "Uplift evidence not found" } )
} ;
}
catch ( UpliftForbiddenException ex )
{
return StatusCode ( 403 , new Response { Status = "Error" , Message = _logger . Sanitize ( ex , "You are not authorized to view evidence for this uplift request" ) } ) ;
}
}
2026-04-20 13:25:56 -04:00
public class DecisionRequest
{
public string? Note { get ; set ; }
}
}
}