using CMSDAL.CustomCode.BlockDataBinding; using CMSDAL.CustomCode.ContentBlock; using CMSDAL.DTO.BlockDataBinding; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.EventLogPublish; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using static GB5Shared.GB5Constant.Constant; namespace CMSBLL.BlockDataBinding; public class BlockDataBindingBLL : IBlockDataBindingBLL { private readonly IBlockDataBindingDAL _dal; private readonly IContentBlockDAL _contentBlockDal; private readonly ICrossTenantGuard _guard; private readonly BaseEntityAppService _entityApp; private readonly EventLogPublish _eventLogPublish; public BlockDataBindingBLL( IBlockDataBindingDAL dal, IContentBlockDAL contentBlockDal, ICrossTenantGuard guard, BaseEntityAppService entityApp, EventLogPublish eventLogPublish) { _dal = dal; _contentBlockDal = contentBlockDal; _guard = guard; _entityApp = entityApp; _eventLogPublish = eventLogPublish; } public async Task GetByContentBlockId(int contentBlockId, LoginDTO loginDTO, CancellationToken ct = default) { try { return await _dal.GetByContentBlockIdAsync(contentBlockId, loginDTO, ct); } catch { throw; } } public async Task GetBindingForBlockAsync(int contentBlockId, LoginDTO loginDTO, CancellationToken ct = default) { try { var json = await _dal.GetByContentBlockIdAsync(contentBlockId, loginDTO, ct); return JsonConvert.DeserializeObject(json); } catch { throw; } } public async Task SaveBlockDataBinding(BlockDataBindingDTO dto, LoginDTO loginDTO, CancellationToken ct = default) { var isNew = dto.BlockDataBindingId == 0; var eventTypeId = isNew ? EventTypeConstant.SAVECMSBLOCKDATABINDINGEVENTTYPEID : EventTypeConstant.UPDATECMSBLOCKDATABINDINGEVENTTYPEID; try { GB5Trace.Step("validate-blockdatabinding", new { dto.BlockDataBindingId, isNew }); if (dto.ContentBlockId <= 0) throw new ValidationException("ContentBlockId is required."); if (dto.DataSourceId <= 0) throw new ValidationException("DataSourceId is required."); // A binding is scoped to one specific block instance — it must always carry that // block's own true TenantId, resolved authoritatively rather than trusted from the // caller. Only a brand-new binding needs this (an update is already tenant-filtered // by TCMSBLOCKDATABINDING's own WHERE TENANTID = @TenantId). if (isNew) { var ownerTenantId = await _contentBlockDal.GetContentBlockTenantIdAsync(dto.ContentBlockId, loginDTO, ct); _guard.EnsureOwnedByTenant(ownerTenantId, loginDTO.ClientId, "ContentBlock", dto.ContentBlockId); dto.TenantId = ownerTenantId!.Value; } dto.ModifiedById = loginDTO.UserId; dto.ModifiedOn = DateTime.UtcNow; if (isNew) { dto.CreatedById = loginDTO.UserId; dto.CreatedOn = DateTime.UtcNow; } GB5Trace.Step("save-blockdatabinding", new { dto.BlockDataBindingId, isNew }); await _entityApp.ExecuteSaveAsync( EntityConstant.OBJECTCMSBLOCKDATABINDING, eventTypeId, dto, loginDTO, persistFunc: async tx => isNew ? await _dal.SaveAsync(dto, loginDTO, tx, ct).ConfigureAwait(false) : await _dal.UpdateAsync(dto, loginDTO, tx, ct).ConfigureAwait(false), isNewEntity: isNew); GB5Trace.Step("event-publish", new { EventTypeId = eventTypeId }); await _eventLogPublish.PublishEventLogAsync( isNew ? "CMS Block Data Binding Created" : "CMS Block Data Binding Updated", dto, eventTypeId, dto.BlockDataBindingId, loginDTO, ct: ct); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.BlockDataBindingId}" : SuccessResponse.UpdateSuccess; } catch (ValidationException vex) { GB5Trace.MarkFailed("save-blockdatabinding-validation-failed", vex); throw new Exception(vex.Message); } catch (Exception ex) { GB5Trace.MarkFailed("save-blockdatabinding-failed", ex); throw; } } }