shoc-backend/SeaHaven.Services/Implementation/CalendarService.cs
Alexandre Brandizzi 7d245eb717
refactor: enforce backend boundaries and optimize dispatch (#30)
* 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
2026-07-24 17:35:34 -03:00

125 lines
4.7 KiB
C#

using Data.SeaHavenIndustries;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class CalendarService : ICalendarService
{
private readonly ICalendarDataService _dataService;
public CalendarService(ICalendarDataService dataService)
{
_dataService = dataService;
}
public async Task CreateEventAsync(EditEventInput input, CancellationToken cancellationToken)
{
var model = new Events
{
Title = input.Title,
Description = input.Description,
Location = input.Location,
RecordID = input.RecordID,
RecordName = input.RecordName,
RecordTargetType = input.RecordTargetType,
ReminderMinutes = input.ReminderMinutes,
EventColor = input.EventColor,
StartDate = input.StartDate,
StartTime = input.StartTime,
EndDate = input.EndDate,
EndTime = input.EndTime,
AllDay = input.AllDay,
CreatedDate = DateTime.Now,
IsDeleted = false
};
await _dataService.AddEventAsync(model, cancellationToken);
}
public async Task<bool> UpdateEventAsync(EditEventInput input, CancellationToken cancellationToken)
{
var model = await _dataService.GetEventForEditAsync(input.Id!.Value, cancellationToken);
if (model == null || model.IsDeleted == true)
return false;
model.Title = input.Title;
model.Description = input.Description;
model.Location = input.Location;
model.RecordID = input.RecordID;
model.RecordName = input.RecordName;
model.RecordTargetType = input.RecordTargetType;
model.ReminderMinutes = input.ReminderMinutes;
model.EventColor = input.EventColor;
model.StartDate = input.StartDate;
model.StartTime = input.StartTime;
model.EndDate = input.EndDate;
model.EndTime = input.EndTime;
model.AllDay = input.AllDay;
model.LastModificationTime = DateTime.Now;
await _dataService.UpdateEventAsync(model, cancellationToken);
return true;
}
public async Task<EventDTO?> GetEventByIdAsync(int id, CancellationToken cancellationToken)
{
var model = await _dataService.GetEventByIdAsync(id, cancellationToken);
return model == null ? null : MapToDTO(model);
}
public async Task<bool> DeleteEventAsync(int id, CancellationToken cancellationToken)
{
var model = await _dataService.GetEventForEditAsync(id, cancellationToken);
if (model == null)
return false;
model.IsDeleted = true;
model.DeletionTime = DateTime.Now;
await _dataService.UpdateEventAsync(model, cancellationToken);
return true;
}
public async Task<PagedEventsResult> GetEventListAsync(string? search, int page, int pageSize, CancellationToken cancellationToken)
{
var (items, totalCount) = await _dataService.GetEventListPagedAsync(page, pageSize, search, cancellationToken);
return new PagedEventsResult
{
Rows = items.Select(MapToDTO).ToList(),
TotalCount = totalCount
};
}
public async Task<IEnumerable<EventDTO>> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken)
{
var events = await _dataService.GetEventsForCalendarAsync(start, end, cancellationToken);
return events.Select(MapToDTO);
}
private static EventDTO MapToDTO(Events model)
{
return new EventDTO
{
Id = model.Id,
Title = model.Title,
Description = model.Description,
Location = model.Location,
RecordID = model.RecordID,
RecordName = model.RecordName,
RecordTargetType = model.RecordTargetType,
ReminderMinutes = model.ReminderMinutes,
EventColor = model.EventColor,
StartDate = model.StartDate,
StartTime = model.StartTime,
EndDate = model.EndDate,
EndTime = model.EndTime,
AllDay = model.AllDay,
CreatedDate = model.CreatedDate,
createdby = model.createdby
};
}
}
}