using AnalyticsBLL.Analysis; using AnalyticsBLL.DataSource; using AnalyticsBLL.Workspace; using AnalyticsDAL.CustomCode.AnalysisWorkspace; using AnalyticsDAL.DTO.AnalysisWorkspace; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; namespace AnalyticsBLL.AnalysisWorkspace { public class AnalysisWorkspaceBLL : IAnalysisWorkspaceBLL { private readonly IAnalysisWorkspaceDAL _dal; private readonly IAnalysisBLL _analysisBLL; private readonly IWorkspaceBLL _workspaceBLL; private readonly IDataSourceBLL _dataSourceBLL; private readonly ILogger _logger; public AnalysisWorkspaceBLL( IAnalysisWorkspaceDAL dal, IAnalysisBLL analysisBLL, IWorkspaceBLL workspaceBLL, IDataSourceBLL dataSourceBLL, ILogger logger) { _dal = dal; _analysisBLL = analysisBLL; _workspaceBLL = workspaceBLL; _dataSourceBLL = dataSourceBLL; _logger = logger; } public async Task GetAnalysisWorkspace(int analysisWorkspaceId, LoginDTO login, CancellationToken ct) => await _dal.GetAnalysisWorkspace(analysisWorkspaceId, login, ct).ConfigureAwait(false); public async Task GetWorkspacesByAnalysis(int analysisId, LoginDTO login, CancellationToken ct) => await _dal.GetWorkspacesByAnalysis(analysisId, login, ct).ConfigureAwait(false); public async Task SaveAnalysisWorkspace(AnalysisWorkspaceDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-analysisworkspace", new { dto.AnalysisWorkspaceId, dto.AnalysisId, dto.WorkspaceId }); if (dto.AnalysisId <= 0) throw new ArgumentException("AnalysisId is required."); if (dto.WorkspaceId <= 0) throw new ArgumentException("WorkspaceId is required."); if (dto.DataSourceType > 3) throw new ArgumentException("DataSourceType must be 0–3."); // Ownership boundary: a tenant may only bind an analysis/workspace/override-datasource // that is either shared (TenantId = -1) or already private to their own tenant — never // someone else's private object. See AnalysisBLL/WorkspaceBLL/DataSourceBLL's // ValidateXVisibility, mirroring MenuGroupBLL.ValidateMenuGroupVisibility. await _analysisBLL.ValidateAnalysisVisibility(dto.AnalysisId, login, ct).ConfigureAwait(false); await _workspaceBLL.ValidateWorkspaceVisibility(dto.WorkspaceId, login, ct).ConfigureAwait(false); if (dto.OverrideDataSourceId is int overrideDataSourceId) await _dataSourceBLL.ValidateDataSourceVisibility(overrideDataSourceId, login, ct).ConfigureAwait(false); if (await _dal.IsDuplicate(dto.AnalysisId, dto.WorkspaceId, dto.AnalysisWorkspaceId, login, ct).ConfigureAwait(false)) throw new InvalidOperationException( $"AnalysisId {dto.AnalysisId} is already linked to WorkspaceId {dto.WorkspaceId}."); var isNew = dto.AnalysisWorkspaceId == 0; GB5Trace.Step("save-analysisworkspace", new { dto.AnalysisWorkspaceId, isNew }); if (isNew) { var savedId = await _dal.SaveAnalysisWorkspace(dto, login, ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {savedId}"; } else { await _dal.UpdateAnalysisWorkspace(dto, login, ct).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } } catch (Exception ex) { GB5Trace.MarkFailed("save-analysisworkspace-failed", ex); _logger.LogError(ex, "SaveAnalysisWorkspace failed for AnalysisId {Id}", dto.AnalysisId); throw; } } public async Task DeleteAnalysisWorkspace(int analysisWorkspaceId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-analysisworkspace", new { analysisWorkspaceId }); await _dal.DeleteAnalysisWorkspace(analysisWorkspaceId, login, ct).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-analysisworkspace-failed", ex); _logger.LogError(ex, "DeleteAnalysisWorkspace failed for Id {Id}", analysisWorkspaceId); throw; } } } }