mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 22:23: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
166 lines
6.9 KiB
C#
166 lines
6.9 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using SeaHaven.DataServices.Dto;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
using SeaHaven.Services.DTOs;
|
|
using SeaHaven.Services.Interfaces;
|
|
|
|
namespace SeaHaven.Services.Implementation
|
|
{
|
|
public class SettingsService : ISettingsService
|
|
{
|
|
private readonly ISettingsDataService _dataService;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
|
|
public SettingsService(ISettingsDataService dataService, UserManager<ApplicationUser> userManager)
|
|
{
|
|
_dataService = dataService;
|
|
_userManager = userManager;
|
|
}
|
|
|
|
public async Task AddCategoryAsync(string? name, CancellationToken cancellationToken)
|
|
{
|
|
await _dataService.AddCategoryAsync(name ?? "", cancellationToken);
|
|
}
|
|
|
|
public Task<bool> UpdateCategoryAsync(int? id, string? name, CancellationToken cancellationToken)
|
|
{
|
|
return _dataService.UpdateCategoryAsync(id ?? 0, name ?? "", cancellationToken);
|
|
}
|
|
|
|
public Task<bool> DeleteCategoryAsync(int id, CancellationToken cancellationToken)
|
|
{
|
|
return _dataService.DeleteCategoryCascadeAsync(id, cancellationToken);
|
|
}
|
|
|
|
public async Task<(IReadOnlyList<SettingsCategoryDTO> Items, int Total)> GetCategoryListAsync(int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
var (items, total) = await _dataService.GetCategoryListPagedAsync(page, pageSize, cancellationToken);
|
|
return (items.Select(c => new SettingsCategoryDTO { Id = c.Id, Name = c.Name }).ToList(), total);
|
|
}
|
|
|
|
public async Task<(IReadOnlyList<SettingsTemplateDTO> Items, int Total)> GetTemplatesAsync(int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
var (items, total) = await _dataService.GetTemplatesPagedAsync(page, pageSize, cancellationToken);
|
|
return (items.Select(MapTemplate).ToList(), total);
|
|
}
|
|
|
|
public async Task<(IReadOnlyList<SettingsUserDTO> Users, int Total)> GetUsersAsync(string? search, int page, int pageSize, CancellationToken cancellationToken)
|
|
{
|
|
var (users, total) = await _dataService.GetUsersPagedAsync(search, page, pageSize, cancellationToken);
|
|
|
|
var filtered = new List<SettingsUserDTO>();
|
|
foreach (var user in users)
|
|
{
|
|
var roles = await _userManager.GetRolesAsync(user);
|
|
if (!roles.Contains("Admin"))
|
|
{
|
|
filtered.Add(new SettingsUserDTO
|
|
{
|
|
RoleName = roles.Count() > 0 ? roles.First() : "",
|
|
Email = user.Email,
|
|
UserName = user.FirstName,
|
|
Date = user.CreatedDate?.Date.ToString(),
|
|
Status = user.UniqueName,
|
|
Id = user.Id
|
|
});
|
|
}
|
|
}
|
|
|
|
return (filtered, total);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<RoleDTO>> GetRolesAsync(CancellationToken cancellationToken)
|
|
{
|
|
var names = await _dataService.GetRoleNamesAsync(cancellationToken);
|
|
var roles = new List<RoleDTO>();
|
|
foreach (var name in names)
|
|
{
|
|
if (name == "Admin")
|
|
{
|
|
roles.Add(new RoleDTO { Name = name, Description = "Full Access" });
|
|
}
|
|
else if (name == "Supervisor")
|
|
{
|
|
roles.Add(new RoleDTO { Name = name, Description = "Access to Team's Work Orders Only" });
|
|
}
|
|
else if (name == "Manager")
|
|
{
|
|
roles.Add(new RoleDTO { Name = name, Description = "Full access accept quotes" });
|
|
}
|
|
else if (name == "User")
|
|
{
|
|
roles.Add(new RoleDTO { Name = name, Description = "Access to Assigned Work Orders Only" });
|
|
}
|
|
}
|
|
return roles;
|
|
}
|
|
|
|
private static SettingsTemplateDTO MapTemplate(WorkOrder w)
|
|
{
|
|
return new SettingsTemplateDTO
|
|
{
|
|
Id = w.Id,
|
|
IsDeleted = w.IsDeleted,
|
|
createdby = w.createdby,
|
|
DeleterUserId = w.DeleterUserId,
|
|
DeletionTime = w.DeletionTime,
|
|
CreatedDate = w.CreatedDate,
|
|
LastModificationTime = w.LastModificationTime,
|
|
LastModifierUserId = w.LastModifierUserId,
|
|
InternalWONumber = w.InternalWONumber,
|
|
ExternalWorkOrderId = w.ExternalWorkOrderId,
|
|
WorkerOrderNumber = w.WorkerOrderNumber,
|
|
WorkerOrderTitle = w.WorkerOrderTitle,
|
|
Description = w.Description,
|
|
Customer = w.Customer,
|
|
SiteCode = w.SiteCode,
|
|
Building = w.Building,
|
|
Severity = w.Severity,
|
|
DueDate = w.DueDate,
|
|
AssignTo = w.AssignTo,
|
|
LocationId = w.LocationId,
|
|
LifecycleStatus = w.LifecycleStatus,
|
|
LegacyStatus = w.LegacyStatus,
|
|
WorkOrderType = w.WorkOrderType,
|
|
Service = w.Service,
|
|
PrimaryDispatchId = w.PrimaryDispatchId,
|
|
ScheduledDate = w.ScheduledDate,
|
|
ScheduledStart = w.ScheduledStart,
|
|
ScheduledEnd = w.ScheduledEnd,
|
|
TargetWeek = w.TargetWeek,
|
|
ScheduleWeekOnly = w.ScheduleWeekOnly,
|
|
OriginalWeek = w.OriginalWeek,
|
|
OriginalDate = w.OriginalDate,
|
|
RescheduleCount = w.RescheduleCount,
|
|
CarriedOver = w.CarriedOver,
|
|
OperationalFlags = w.OperationalFlags,
|
|
DocStatus = w.DocStatus,
|
|
AvettaTask = w.AvettaTask,
|
|
AssignDate = w.AssignDate,
|
|
DateReported = w.DateReported,
|
|
SourceEmailS3Key = w.SourceEmailS3Key,
|
|
Problem = w.Problem,
|
|
Trade = w.Trade,
|
|
SubTrade = w.SubTrade,
|
|
VendorNTE = w.VendorNTE,
|
|
CompletedDate = w.CompletedDate,
|
|
Source = w.Source,
|
|
istemplate = w.istemplate,
|
|
PO = w.PO,
|
|
TT = w.TT,
|
|
Priority = w.Priority,
|
|
Status = w.Status,
|
|
Attachments = w.Attachments,
|
|
BeforPhotoIssue = w.BeforPhotoIssue,
|
|
BeforPhotoAttachment = w.BeforPhotoAttachment,
|
|
AfterPhotoIssue = w.AfterPhotoIssue,
|
|
AfterPhotoAttachment = w.AfterPhotoAttachment,
|
|
SignOffName = w.SignOffName,
|
|
SignOffAttachment = w.SignOffAttachment,
|
|
SignOffSignature = w.SignOffSignature,
|
|
RowVersion = w.RowVersion
|
|
};
|
|
}
|
|
}
|
|
}
|