mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 16:33:12 +00:00
* refactor(api): enforce service and data-service boundaries * refactor(api): complete feature service boundaries * refactor(identity): enforce service and data boundaries * refactor(vendors): enforce service and data boundaries * refactor(workorders): enforce service and data boundaries * refactor(backend): enforce architecture and optimize dispatch * style(backend): format changed architecture files * fix(architecture): address backend review follow-ups * fix(backend): sanitize exception disclosure in changed API endpoints Replace raw exception-message disclosure (ex.Message) returned to API callers with a stable sanitized public message plus correlated structured internal logging, across the endpoints changed in this PR. - Add SanitizedErrors helper: logs the original exception at Error with a generated correlation id and returns a stable public message referencing it so support can trace without exposing internals. - Inject ILogger<T> into the 14 changed controllers and route every ex.Message/dbex.Message disclosure through the helper, preserving status codes, response shapes, and business data (e.g. OpenWorkOrders). - Leave FluentValidation (vex.Errors) and existing fixed-message catches untouched; out-of-scope controllers (Account/Contact/Employee/Asset/ PMSchedule) are unchanged. - Add focused tests proving internal exception text is not returned and that Error logging carrying the original exception is invoked. * fix(architecture): abstract job run state access * style: format board update service * test: use collection assertion idiom
214 lines
7.8 KiB
C#
214 lines
7.8 KiB
C#
using Api.SeaHavenIndustries.DTOs;
|
|
using Api.SeaHavenIndustries.Helper;
|
|
using Data.SeaHavenIndustries;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Exceptions;
|
|
using SeaHaven.Services.Interfaces;
|
|
using System.Security.Claims;
|
|
|
|
namespace Api.SeaHavenIndustries.Controllers
|
|
{
|
|
[Authorize]
|
|
[ApiController]
|
|
[Route("api/WorkOrder")]
|
|
[Route("api/workorders")]
|
|
public class WorkOrderDispatchController : Controller
|
|
{
|
|
private readonly IWorkOrderDispatchService _dispatchService;
|
|
private readonly ILogger<WorkOrderDispatchController> _logger;
|
|
|
|
public WorkOrderDispatchController(
|
|
IWorkOrderDispatchService dispatchService,
|
|
ILogger<WorkOrderDispatchController> logger)
|
|
{
|
|
_dispatchService = dispatchService;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpPost]
|
|
[Route("DispatchToVendor")]
|
|
public async Task<IActionResult> DispatchToVendor([FromBody] Dispatch_DTO model, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
var input = new DispatchToVendorInput
|
|
{
|
|
WorkOrderIds = model.WorkOrderIds,
|
|
VendorIds = model.VendorIds,
|
|
NTEAmount = model.NTEAmount,
|
|
Description = model.Description,
|
|
ScheduledDate = model.ScheduledDate,
|
|
TaskListTemplateId = model.TaskListTemplateId,
|
|
CustomChecklistItems = model.CustomChecklistItems,
|
|
UserId = userId
|
|
};
|
|
|
|
var result = await _dispatchService.DispatchToVendorAsync(input, cancellationToken);
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpGet("GetDispatches/{workOrderId}")]
|
|
public async Task<IActionResult> GetDispatches(int workOrderId)
|
|
{
|
|
var data = await _dispatchService.GetDispatchesAsync(workOrderId);
|
|
return Ok(data);
|
|
}
|
|
|
|
[HttpGet("GetDispatch/{id}")]
|
|
public async Task<IActionResult> GetDispatchById(int id)
|
|
{
|
|
var d = await _dispatchService.GetDispatchDetailAsync(id);
|
|
if (d == null)
|
|
return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
return Ok(d);
|
|
}
|
|
|
|
[HttpPost("UpdateDispatch")]
|
|
public async Task<IActionResult> UpdateDispatch([FromBody] UpdateDispatch_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var input = new UpdateDispatchInput
|
|
{
|
|
Id = model.Id,
|
|
Status = model.Status,
|
|
NTEAmount = model.NTEAmount,
|
|
ScheduledDate = model.ScheduledDate,
|
|
CompletedDate = model.CompletedDate,
|
|
Description = model.Description,
|
|
UserId = userId
|
|
};
|
|
|
|
var success = await _dispatchService.UpdateDispatchAsync(input);
|
|
if (!success)
|
|
return BadRequest(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
|
|
return Ok(new { message = "Dispatch updated" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpPost("AddDispatchComment")]
|
|
public async Task<IActionResult> AddDispatchComment([FromBody] DispatchComment_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var input = new AddDispatchCommentInput
|
|
{
|
|
DispatchId = model.DispatchId,
|
|
Text = model.Text,
|
|
SendEmail = model.SendEmail,
|
|
UserId = userId
|
|
};
|
|
|
|
var result = await _dispatchService.AddDispatchCommentAsync(input);
|
|
if (result == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpPost("VerifyDispatch")]
|
|
public async Task<IActionResult> VerifyDispatch(int dispatchId)
|
|
{
|
|
try
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var result = await _dispatchService.VerifyDispatchAsync(dispatchId, userId!);
|
|
if (result == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
|
|
return Ok(result);
|
|
}
|
|
catch (DispatchVerificationException ex)
|
|
{
|
|
return BadRequest(new { status = "Error", message = "Cannot verify", missing = ex.Missing });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpPost("AddDispatchSignoff")]
|
|
public async Task<IActionResult> AddDispatchSignoff([FromBody] DispatchSignoff_DTO model)
|
|
{
|
|
try
|
|
{
|
|
var input = new AddDispatchSignoffInput
|
|
{
|
|
DispatchId = model.DispatchId,
|
|
SignoffType = model.SignoffType,
|
|
Name = model.Name,
|
|
Signature = model.Signature,
|
|
SignatureMethod = model.SignatureMethod
|
|
};
|
|
|
|
var result = await _dispatchService.AddDispatchSignoffAsync(input);
|
|
if (result == null)
|
|
return BadRequest(new Response { Status = "Error", Message = "Dispatch not found" });
|
|
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex, "The dispatch signoff could not be added") });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return BadRequest(new Response { Status = "Error", Message = _logger.Sanitize(ex) });
|
|
}
|
|
}
|
|
|
|
[HttpPost("UpdateChecklistItem")]
|
|
public async Task<IActionResult> UpdateChecklistItem([FromBody] ChecklistItemUpdate_DTO model)
|
|
{
|
|
var input = new ChecklistItemUpdateInput
|
|
{
|
|
Id = model.Id,
|
|
IsCompleted = model.IsCompleted,
|
|
CompletedBy = model.CompletedBy
|
|
};
|
|
|
|
var result = await _dispatchService.UpdateChecklistItemAsync(input);
|
|
if (result == null)
|
|
return NotFound(new Response { Status = "Error", Message = "Checklist item not found" });
|
|
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpPost("AddChecklistItem")]
|
|
public async Task<IActionResult> AddChecklistItem([FromBody] AddChecklistItem_DTO model)
|
|
{
|
|
var input = new AddChecklistItemInput
|
|
{
|
|
DispatchId = model.DispatchId,
|
|
WorkOrderId = model.WorkOrderId,
|
|
ItemText = model.ItemText
|
|
};
|
|
|
|
var result = await _dispatchService.AddChecklistItemAsync(input);
|
|
return Ok(result);
|
|
}
|
|
}
|
|
}
|