using System.Text.RegularExpressions; using AnalyticsDAL.CustomCode.Warehouse; using AnalyticsDAL.DTO.Warehouse; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; namespace AnalyticsBLL.Warehouse { public class WarehouseFactDimensionBLL : IWarehouseFactDimensionBLL { // Identifiers stored in MWAREHOUSEFACTDIMENSION are used at runtime to build SQL JOINs — // validate them here so a typo in an admin form fails loudly at save-time, not at query time. private static readonly Regex SafeIdentifier = new(@"^[A-Za-z0-9_]+$", RegexOptions.Compiled); private readonly IWarehouseFactDimensionDAL _dal; private readonly IValidation _validation; private readonly ILogger _logger; public WarehouseFactDimensionBLL( IWarehouseFactDimensionDAL dal, IValidation validation, ILogger logger) { _dal = dal; _validation = validation; _logger = logger; } public async Task GetByFactAsync(int factId, LoginDTO login, CancellationToken ct) { try { var rows = await _dal.GetByFactAsync(factId, login, ct).ConfigureAwait(false); return System.Text.Json.JsonSerializer.Serialize(rows); } catch (Exception ex) { GB5Trace.MarkFailed("get-fact-dimensions-failed", ex); _logger.LogError(ex, "GetByFact failed for FactId {FactId}", factId); throw; } } public async Task SaveAsync(WarehouseFactDimensionDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-fact-dimension", new { dto.FactDimensionId, dto.FactId }); await _validation.NotEmpty(dto.DimensionTable, nameof(dto.DimensionTable)).ConfigureAwait(false); await _validation.NotEmpty(dto.FactFkColumn, nameof(dto.FactFkColumn)) .ConfigureAwait(false); await _validation.NotEmpty(dto.DimJoinColumn, nameof(dto.DimJoinColumn)) .ConfigureAwait(false); if (!SafeIdentifier.IsMatch(dto.DimensionTable)) throw new ArgumentException($"DimensionTable '{dto.DimensionTable}' is not a valid SQL identifier."); if (!SafeIdentifier.IsMatch(dto.FactFkColumn)) throw new ArgumentException($"FactFkColumn '{dto.FactFkColumn}' is not a valid SQL identifier."); if (!SafeIdentifier.IsMatch(dto.DimJoinColumn)) throw new ArgumentException($"DimJoinColumn '{dto.DimJoinColumn}' is not a valid SQL identifier."); if (dto.FactId == 0) throw new ArgumentException("FactId is required."); var isNew = dto.FactDimensionId == 0; if (isNew) { GB5Trace.Step("insert-fact-dimension", new { dto.FactId, dto.DimensionTable }); var newId = await _dal.InsertAsync(dto, login, ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {newId}"; } GB5Trace.Step("update-fact-dimension", new { dto.FactDimensionId }); await _dal.UpdateAsync(dto, login, ct).ConfigureAwait(false); return $"{SuccessResponse.UpdateSuccessMessage} {dto.FactDimensionId}"; } catch (Exception ex) { GB5Trace.MarkFailed("save-fact-dimension-failed", ex); _logger.LogError(ex, "SaveFactDimension failed for FactId {FactId}", dto.FactId); throw; } } public async Task DeleteAsync(int factDimensionId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-fact-dimension", new { factDimensionId }); await _dal.DeleteAsync(factDimensionId, login, ct).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-fact-dimension-failed", ex); _logger.LogError(ex, "DeleteFactDimension failed for FactDimensionId {FactDimensionId}", factDimensionId); throw; } } } }