mirror of
https://github.com/bitwarden/server
synced 2026-01-05 10:03:23 +00:00
Organization report tables, repos, services, and endpoints (#6158)
* PM-23754 initial commit * pm-23754 fixing controller tests * pm-23754 adding commands and queries * pm-23754 adding endpoints, command/queries, repositories, and sql migrations * pm-23754 add new sql scripts * PM-23754 adding sql scripts * pm-23754 * PM-23754 fixing migration script * PM-23754 fixing migration script again * PM-23754 fixing migration script validation * PM-23754 fixing db validation script issue * PM-23754 fixing endpoint and db validation * PM-23754 fixing unit tests * PM-23754 fixing implementation based on comments and tests * PM-23754 updating logging statements * PM-23754 making changes based on PR comments. * updating migration scripts * removing old migration files * update code based testing for whole data object for OrganizationReport and add a stored procedure. * updating services, unit tests, repository tests * fixing unit tests * fixing migration script * fixing migration script again * fixing migration script * another fix * fixing sql file, updating controller to account for different orgIds in the url and body. * updating error message in controllers without a body * making a change to the command * Refactor ReportsController by removing organization reports The IDropOrganizationReportCommand is no longer needed * will code based on PR comments. * fixing unit test * fixing migration script based on last changes. * adding another check in endpoint and adding unit tests * fixing route parameter. * PM-23754 updating data fields to return just the column * PM-23754 fixing repository method signatures * PM-23754 making change to orgId parameter through out code to align with api naming --------- Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com>
This commit is contained in:
@@ -9,12 +9,15 @@ public class OrganizationReport : ITableObject<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public string ReportData { get; set; } = string.Empty;
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public string ContentEncryptionKey { get; set; } = string.Empty;
|
||||
|
||||
public string? SummaryData { get; set; } = null;
|
||||
public string? ApplicationData { get; set; } = null;
|
||||
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.Dirt.Models.Data;
|
||||
|
||||
public class OrganizationReportApplicationDataResponse
|
||||
{
|
||||
public string? ApplicationData { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.Dirt.Models.Data;
|
||||
|
||||
public class OrganizationReportDataResponse
|
||||
{
|
||||
public string? ReportData { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.Dirt.Models.Data;
|
||||
|
||||
public class OrganizationReportSummaryDataResponse
|
||||
{
|
||||
public string? SummaryData { get; set; }
|
||||
}
|
||||
@@ -26,12 +26,12 @@ public class AddOrganizationReportCommand : IAddOrganizationReportCommand
|
||||
|
||||
public async Task<OrganizationReport> AddOrganizationReportAsync(AddOrganizationReportRequest request)
|
||||
{
|
||||
_logger.LogInformation("Adding organization report for organization {organizationId}", request.OrganizationId);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Adding organization report for organization {organizationId}", request.OrganizationId);
|
||||
|
||||
var (isValid, errorMessage) = await ValidateRequestAsync(request);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogInformation("Failed to add organization {organizationId} report: {errorMessage}", request.OrganizationId, errorMessage);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Failed to add organization {organizationId} report: {errorMessage}", request.OrganizationId, errorMessage);
|
||||
throw new BadRequestException(errorMessage);
|
||||
}
|
||||
|
||||
@@ -39,15 +39,18 @@ public class AddOrganizationReportCommand : IAddOrganizationReportCommand
|
||||
{
|
||||
OrganizationId = request.OrganizationId,
|
||||
ReportData = request.ReportData,
|
||||
Date = request.Date == default ? DateTime.UtcNow : request.Date,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
ContentEncryptionKey = request.ContentEncryptionKey,
|
||||
SummaryData = request.SummaryData,
|
||||
ApplicationData = request.ApplicationData,
|
||||
RevisionDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
organizationReport.SetNewId();
|
||||
|
||||
var data = await _organizationReportRepo.CreateAsync(organizationReport);
|
||||
|
||||
_logger.LogInformation("Successfully added organization report for organization {organizationId}, {organizationReportId}",
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully added organization report for organization {organizationId}, {organizationReportId}",
|
||||
request.OrganizationId, data.Id);
|
||||
|
||||
return data;
|
||||
@@ -63,12 +66,26 @@ public class AddOrganizationReportCommand : IAddOrganizationReportCommand
|
||||
return (false, "Invalid Organization");
|
||||
}
|
||||
|
||||
// ensure that we have report data
|
||||
if (string.IsNullOrWhiteSpace(request.ContentEncryptionKey))
|
||||
{
|
||||
return (false, "Content Encryption Key is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ReportData))
|
||||
{
|
||||
return (false, "Report Data is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.SummaryData))
|
||||
{
|
||||
return (false, "Summary Data is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ApplicationData))
|
||||
{
|
||||
return (false, "Application Data is required");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class DropOrganizationReportCommand : IDropOrganizationReportCommand
|
||||
{
|
||||
private IOrganizationReportRepository _organizationReportRepo;
|
||||
private ILogger<DropOrganizationReportCommand> _logger;
|
||||
|
||||
public DropOrganizationReportCommand(
|
||||
IOrganizationReportRepository organizationReportRepository,
|
||||
ILogger<DropOrganizationReportCommand> logger)
|
||||
{
|
||||
_organizationReportRepo = organizationReportRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task DropOrganizationReportAsync(DropOrganizationReportRequest request)
|
||||
{
|
||||
_logger.LogInformation("Dropping organization report for organization {organizationId}",
|
||||
request.OrganizationId);
|
||||
|
||||
var data = await _organizationReportRepo.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
if (data == null || data.Count() == 0)
|
||||
{
|
||||
_logger.LogInformation("No organization reports found for organization {organizationId}", request.OrganizationId);
|
||||
throw new BadRequestException("No data found.");
|
||||
}
|
||||
|
||||
data
|
||||
.Where(_ => request.OrganizationReportIds.Contains(_.Id))
|
||||
.ToList()
|
||||
.ForEach(async reportId =>
|
||||
{
|
||||
_logger.LogInformation("Dropping organization report {organizationReportId} for organization {organizationId}",
|
||||
reportId, request.OrganizationId);
|
||||
|
||||
await _organizationReportRepo.DeleteAsync(reportId);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class GetOrganizationReportApplicationDataQuery : IGetOrganizationReportApplicationDataQuery
|
||||
{
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<GetOrganizationReportApplicationDataQuery> _logger;
|
||||
|
||||
public GetOrganizationReportApplicationDataQuery(
|
||||
IOrganizationReportRepository organizationReportRepo,
|
||||
ILogger<GetOrganizationReportApplicationDataQuery> logger)
|
||||
{
|
||||
_organizationReportRepo = organizationReportRepo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReportApplicationDataResponse> GetOrganizationReportApplicationDataAsync(Guid organizationId, Guid reportId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Fetching organization report application data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
|
||||
if (organizationId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportApplicationDataAsync called with empty OrganizationId");
|
||||
throw new BadRequestException("OrganizationId is required.");
|
||||
}
|
||||
|
||||
if (reportId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportApplicationDataAsync called with empty ReportId");
|
||||
throw new BadRequestException("ReportId is required.");
|
||||
}
|
||||
|
||||
var applicationDataResponse = await _organizationReportRepo.GetApplicationDataAsync(reportId);
|
||||
|
||||
if (applicationDataResponse == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "No application data found for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
throw new NotFoundException("Organization report application data not found.");
|
||||
}
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully retrieved organization report application data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
|
||||
return applicationDataResponse;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching organization report application data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class GetOrganizationReportDataQuery : IGetOrganizationReportDataQuery
|
||||
{
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<GetOrganizationReportDataQuery> _logger;
|
||||
|
||||
public GetOrganizationReportDataQuery(
|
||||
IOrganizationReportRepository organizationReportRepo,
|
||||
ILogger<GetOrganizationReportDataQuery> logger)
|
||||
{
|
||||
_organizationReportRepo = organizationReportRepo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReportDataResponse> GetOrganizationReportDataAsync(Guid organizationId, Guid reportId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Fetching organization report data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
|
||||
if (organizationId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportDataAsync called with empty OrganizationId");
|
||||
throw new BadRequestException("OrganizationId is required.");
|
||||
}
|
||||
|
||||
if (reportId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportDataAsync called with empty ReportId");
|
||||
throw new BadRequestException("ReportId is required.");
|
||||
}
|
||||
|
||||
var reportDataResponse = await _organizationReportRepo.GetReportDataAsync(reportId);
|
||||
|
||||
if (reportDataResponse == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "No report data found for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
throw new NotFoundException("Organization report data not found.");
|
||||
}
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully retrieved organization report data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
|
||||
return reportDataResponse;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching organization report data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,23 @@ public class GetOrganizationReportQuery : IGetOrganizationReportQuery
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrganizationReport>> GetOrganizationReportAsync(Guid organizationId)
|
||||
public async Task<OrganizationReport> GetOrganizationReportAsync(Guid reportId)
|
||||
{
|
||||
if (organizationId == Guid.Empty)
|
||||
if (reportId == Guid.Empty)
|
||||
{
|
||||
throw new BadRequestException("OrganizationId is required.");
|
||||
throw new BadRequestException("Id of report is required.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Fetching organization reports for organization {organizationId}", organizationId);
|
||||
return await _organizationReportRepo.GetByOrganizationIdAsync(organizationId);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Fetching organization reports for organization by Id: {reportId}", reportId);
|
||||
|
||||
var results = await _organizationReportRepo.GetByIdAsync(reportId);
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
throw new NotFoundException($"No report found for Id: {reportId}");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReport> GetLatestOrganizationReportAsync(Guid organizationId)
|
||||
@@ -37,7 +45,7 @@ public class GetOrganizationReportQuery : IGetOrganizationReportQuery
|
||||
throw new BadRequestException("OrganizationId is required.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Fetching latest organization report for organization {organizationId}", organizationId);
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Fetching latest organization report for organization {organizationId}", organizationId);
|
||||
return await _organizationReportRepo.GetLatestByOrganizationIdAsync(organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class GetOrganizationReportSummaryDataByDateRangeQuery : IGetOrganizationReportSummaryDataByDateRangeQuery
|
||||
{
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<GetOrganizationReportSummaryDataByDateRangeQuery> _logger;
|
||||
|
||||
public GetOrganizationReportSummaryDataByDateRangeQuery(
|
||||
IOrganizationReportRepository organizationReportRepo,
|
||||
ILogger<GetOrganizationReportSummaryDataByDateRangeQuery> logger)
|
||||
{
|
||||
_organizationReportRepo = organizationReportRepo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrganizationReportSummaryDataResponse>> GetOrganizationReportSummaryDataByDateRangeAsync(Guid organizationId, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Fetching organization report summary data by date range for organization {organizationId}, from {startDate} to {endDate}",
|
||||
organizationId, startDate, endDate);
|
||||
|
||||
var (isValid, errorMessage) = ValidateRequest(organizationId, startDate, endDate);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportSummaryDataByDateRangeAsync validation failed: {errorMessage}", errorMessage);
|
||||
throw new BadRequestException(errorMessage);
|
||||
}
|
||||
|
||||
IEnumerable<OrganizationReportSummaryDataResponse> summaryDataList = (await _organizationReportRepo
|
||||
.GetSummaryDataByDateRangeAsync(organizationId, startDate, endDate)) ??
|
||||
Enumerable.Empty<OrganizationReportSummaryDataResponse>();
|
||||
|
||||
var resultList = summaryDataList.ToList();
|
||||
|
||||
if (!resultList.Any())
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "No summary data found for organization {organizationId} in date range {startDate} to {endDate}",
|
||||
organizationId, startDate, endDate);
|
||||
return Enumerable.Empty<OrganizationReportSummaryDataResponse>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully retrieved {count} organization report summary data records for organization {organizationId} in date range {startDate} to {endDate}",
|
||||
resultList.Count, organizationId, startDate, endDate);
|
||||
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException))
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching organization report summary data by date range for organization {organizationId}, from {startDate} to {endDate}",
|
||||
organizationId, startDate, endDate);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static (bool IsValid, string errorMessage) ValidateRequest(Guid organizationId, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
if (organizationId == Guid.Empty)
|
||||
{
|
||||
return (false, "OrganizationId is required");
|
||||
}
|
||||
|
||||
if (startDate == default)
|
||||
{
|
||||
return (false, "StartDate is required");
|
||||
}
|
||||
|
||||
if (endDate == default)
|
||||
{
|
||||
return (false, "EndDate is required");
|
||||
}
|
||||
|
||||
if (startDate > endDate)
|
||||
{
|
||||
return (false, "StartDate must be earlier than or equal to EndDate");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class GetOrganizationReportSummaryDataQuery : IGetOrganizationReportSummaryDataQuery
|
||||
{
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<GetOrganizationReportSummaryDataQuery> _logger;
|
||||
|
||||
public GetOrganizationReportSummaryDataQuery(
|
||||
IOrganizationReportRepository organizationReportRepo,
|
||||
ILogger<GetOrganizationReportSummaryDataQuery> logger)
|
||||
{
|
||||
_organizationReportRepo = organizationReportRepo;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReportSummaryDataResponse> GetOrganizationReportSummaryDataAsync(Guid organizationId, Guid reportId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Fetching organization report summary data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
|
||||
if (organizationId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportSummaryDataAsync called with empty OrganizationId");
|
||||
throw new BadRequestException("OrganizationId is required.");
|
||||
}
|
||||
|
||||
if (reportId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "GetOrganizationReportSummaryDataAsync called with empty ReportId");
|
||||
throw new BadRequestException("ReportId is required.");
|
||||
}
|
||||
|
||||
var summaryDataResponse = await _organizationReportRepo.GetSummaryDataAsync(reportId);
|
||||
|
||||
if (summaryDataResponse == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "No summary data found for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
throw new NotFoundException("Organization report summary data not found.");
|
||||
}
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully retrieved organization report summary data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
|
||||
return summaryDataResponse;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching organization report summary data for organization {organizationId} and report {reportId}",
|
||||
organizationId, reportId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IDropOrganizationReportCommand
|
||||
{
|
||||
Task DropOrganizationReportAsync(DropOrganizationReportRequest request);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IGetOrganizationReportApplicationDataQuery
|
||||
{
|
||||
Task<OrganizationReportApplicationDataResponse> GetOrganizationReportApplicationDataAsync(Guid organizationId, Guid reportId);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IGetOrganizationReportDataQuery
|
||||
{
|
||||
Task<OrganizationReportDataResponse> GetOrganizationReportDataAsync(Guid organizationId, Guid reportId);
|
||||
}
|
||||
@@ -4,6 +4,6 @@ namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IGetOrganizationReportQuery
|
||||
{
|
||||
Task<IEnumerable<OrganizationReport>> GetOrganizationReportAsync(Guid organizationId);
|
||||
Task<OrganizationReport> GetOrganizationReportAsync(Guid organizationId);
|
||||
Task<OrganizationReport> GetLatestOrganizationReportAsync(Guid organizationId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IGetOrganizationReportSummaryDataByDateRangeQuery
|
||||
{
|
||||
Task<IEnumerable<OrganizationReportSummaryDataResponse>> GetOrganizationReportSummaryDataByDateRangeAsync(
|
||||
Guid organizationId, DateTime startDate, DateTime endDate);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IGetOrganizationReportSummaryDataQuery
|
||||
{
|
||||
Task<OrganizationReportSummaryDataResponse> GetOrganizationReportSummaryDataAsync(Guid organizationId, Guid reportId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IUpdateOrganizationReportApplicationDataCommand
|
||||
{
|
||||
Task<OrganizationReport> UpdateOrganizationReportApplicationDataAsync(UpdateOrganizationReportApplicationDataRequest request);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IUpdateOrganizationReportCommand
|
||||
{
|
||||
Task<OrganizationReport> UpdateOrganizationReportAsync(UpdateOrganizationReportRequest request);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IUpdateOrganizationReportDataCommand
|
||||
{
|
||||
Task<OrganizationReport> UpdateOrganizationReportDataAsync(UpdateOrganizationReportDataRequest request);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
|
||||
public interface IUpdateOrganizationReportSummaryCommand
|
||||
{
|
||||
Task<OrganizationReport> UpdateOrganizationReportSummaryAsync(UpdateOrganizationReportSummaryRequest request);
|
||||
}
|
||||
@@ -14,7 +14,14 @@ public static class ReportingServiceCollectionExtensions
|
||||
services.AddScoped<IGetPasswordHealthReportApplicationQuery, GetPasswordHealthReportApplicationQuery>();
|
||||
services.AddScoped<IDropPasswordHealthReportApplicationCommand, DropPasswordHealthReportApplicationCommand>();
|
||||
services.AddScoped<IAddOrganizationReportCommand, AddOrganizationReportCommand>();
|
||||
services.AddScoped<IDropOrganizationReportCommand, DropOrganizationReportCommand>();
|
||||
services.AddScoped<IGetOrganizationReportQuery, GetOrganizationReportQuery>();
|
||||
services.AddScoped<IUpdateOrganizationReportCommand, UpdateOrganizationReportCommand>();
|
||||
services.AddScoped<IUpdateOrganizationReportSummaryCommand, UpdateOrganizationReportSummaryCommand>();
|
||||
services.AddScoped<IGetOrganizationReportSummaryDataQuery, GetOrganizationReportSummaryDataQuery>();
|
||||
services.AddScoped<IGetOrganizationReportSummaryDataByDateRangeQuery, GetOrganizationReportSummaryDataByDateRangeQuery>();
|
||||
services.AddScoped<IGetOrganizationReportDataQuery, GetOrganizationReportDataQuery>();
|
||||
services.AddScoped<IUpdateOrganizationReportDataCommand, UpdateOrganizationReportDataCommand>();
|
||||
services.AddScoped<IGetOrganizationReportApplicationDataQuery, GetOrganizationReportApplicationDataQuery>();
|
||||
services.AddScoped<IUpdateOrganizationReportApplicationDataCommand, UpdateOrganizationReportApplicationDataCommand>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,10 @@ public class AddOrganizationReportRequest
|
||||
{
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string ReportData { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public string ContentEncryptionKey { get; set; }
|
||||
|
||||
public string SummaryData { get; set; }
|
||||
|
||||
public string ApplicationData { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
public class GetOrganizationReportSummaryDataByDateRangeRequest
|
||||
{
|
||||
public Guid OrganizationId { get; set; }
|
||||
public DateTime StartDate { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
public class UpdateOrganizationReportApplicationDataRequest
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string ApplicationData { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
public class UpdateOrganizationReportDataRequest
|
||||
{
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid ReportId { get; set; }
|
||||
public string ReportData { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
public class UpdateOrganizationReportRequest
|
||||
{
|
||||
public Guid ReportId { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public string ReportData { get; set; }
|
||||
public string ContentEncryptionKey { get; set; }
|
||||
public string SummaryData { get; set; } = null;
|
||||
public string ApplicationData { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
|
||||
public class UpdateOrganizationReportSummaryRequest
|
||||
{
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid ReportId { get; set; }
|
||||
public string SummaryData { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class UpdateOrganizationReportApplicationDataCommand : IUpdateOrganizationReportApplicationDataCommand
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepo;
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<UpdateOrganizationReportApplicationDataCommand> _logger;
|
||||
|
||||
public UpdateOrganizationReportApplicationDataCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationReportRepository organizationReportRepository,
|
||||
ILogger<UpdateOrganizationReportApplicationDataCommand> logger)
|
||||
{
|
||||
_organizationRepo = organizationRepository;
|
||||
_organizationReportRepo = organizationReportRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReport> UpdateOrganizationReportApplicationDataAsync(UpdateOrganizationReportApplicationDataRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Updating organization report application data {reportId} for organization {organizationId}",
|
||||
request.Id, request.OrganizationId);
|
||||
|
||||
var (isValid, errorMessage) = await ValidateRequestAsync(request);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Failed to update organization report application data {reportId} for organization {organizationId}: {errorMessage}",
|
||||
request.Id, request.OrganizationId, errorMessage);
|
||||
throw new BadRequestException(errorMessage);
|
||||
}
|
||||
|
||||
var existingReport = await _organizationReportRepo.GetByIdAsync(request.Id);
|
||||
if (existingReport == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} not found", request.Id);
|
||||
throw new NotFoundException("Organization report not found");
|
||||
}
|
||||
|
||||
if (existingReport.OrganizationId != request.OrganizationId)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} does not belong to organization {organizationId}",
|
||||
request.Id, request.OrganizationId);
|
||||
throw new BadRequestException("Organization report does not belong to the specified organization");
|
||||
}
|
||||
|
||||
var updatedReport = await _organizationReportRepo.UpdateApplicationDataAsync(request.OrganizationId, request.Id, request.ApplicationData);
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated organization report application data {reportId} for organization {organizationId}",
|
||||
request.Id, request.OrganizationId);
|
||||
|
||||
return updatedReport;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error updating organization report application data {reportId} for organization {organizationId}",
|
||||
request.Id, request.OrganizationId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool isValid, string errorMessage)> ValidateRequestAsync(UpdateOrganizationReportApplicationDataRequest request)
|
||||
{
|
||||
if (request.OrganizationId == Guid.Empty)
|
||||
{
|
||||
return (false, "OrganizationId is required");
|
||||
}
|
||||
|
||||
if (request.Id == Guid.Empty)
|
||||
{
|
||||
return (false, "Id is required");
|
||||
}
|
||||
|
||||
var organization = await _organizationRepo.GetByIdAsync(request.OrganizationId);
|
||||
if (organization == null)
|
||||
{
|
||||
return (false, "Invalid Organization");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ApplicationData))
|
||||
{
|
||||
return (false, "Application Data is required");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class UpdateOrganizationReportCommand : IUpdateOrganizationReportCommand
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepo;
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<UpdateOrganizationReportCommand> _logger;
|
||||
|
||||
public UpdateOrganizationReportCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationReportRepository organizationReportRepository,
|
||||
ILogger<UpdateOrganizationReportCommand> logger)
|
||||
{
|
||||
_organizationRepo = organizationRepository;
|
||||
_organizationReportRepo = organizationReportRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReport> UpdateOrganizationReportAsync(UpdateOrganizationReportRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Updating organization report {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
|
||||
var (isValid, errorMessage) = await ValidateRequestAsync(request);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Failed to update organization report {reportId} for organization {organizationId}: {errorMessage}",
|
||||
request.ReportId, request.OrganizationId, errorMessage);
|
||||
throw new BadRequestException(errorMessage);
|
||||
}
|
||||
|
||||
var existingReport = await _organizationReportRepo.GetByIdAsync(request.ReportId);
|
||||
if (existingReport == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} not found", request.ReportId);
|
||||
throw new NotFoundException("Organization report not found");
|
||||
}
|
||||
|
||||
if (existingReport.OrganizationId != request.OrganizationId)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} does not belong to organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
throw new BadRequestException("Organization report does not belong to the specified organization");
|
||||
}
|
||||
|
||||
existingReport.ContentEncryptionKey = request.ContentEncryptionKey;
|
||||
existingReport.SummaryData = request.SummaryData;
|
||||
existingReport.ReportData = request.ReportData;
|
||||
existingReport.ApplicationData = request.ApplicationData;
|
||||
existingReport.RevisionDate = DateTime.UtcNow;
|
||||
|
||||
await _organizationReportRepo.UpsertAsync(existingReport);
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated organization report {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
|
||||
var response = await _organizationReportRepo.GetByIdAsync(request.ReportId);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} not found after update", request.ReportId);
|
||||
throw new NotFoundException("Organization report not found after update");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error updating organization report {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool IsValid, string errorMessage)> ValidateRequestAsync(UpdateOrganizationReportRequest request)
|
||||
{
|
||||
if (request.OrganizationId == Guid.Empty)
|
||||
{
|
||||
return (false, "OrganizationId is required");
|
||||
}
|
||||
|
||||
if (request.ReportId == Guid.Empty)
|
||||
{
|
||||
return (false, "ReportId is required");
|
||||
}
|
||||
|
||||
var organization = await _organizationRepo.GetByIdAsync(request.OrganizationId);
|
||||
if (organization == null)
|
||||
{
|
||||
return (false, "Invalid Organization");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ContentEncryptionKey))
|
||||
{
|
||||
return (false, "ContentEncryptionKey is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ReportData))
|
||||
{
|
||||
return (false, "Report Data is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.SummaryData))
|
||||
{
|
||||
return (false, "Summary Data is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ApplicationData))
|
||||
{
|
||||
return (false, "Application Data is required");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class UpdateOrganizationReportDataCommand : IUpdateOrganizationReportDataCommand
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepo;
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<UpdateOrganizationReportDataCommand> _logger;
|
||||
|
||||
public UpdateOrganizationReportDataCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationReportRepository organizationReportRepository,
|
||||
ILogger<UpdateOrganizationReportDataCommand> logger)
|
||||
{
|
||||
_organizationRepo = organizationRepository;
|
||||
_organizationReportRepo = organizationReportRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReport> UpdateOrganizationReportDataAsync(UpdateOrganizationReportDataRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Updating organization report data {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
|
||||
var (isValid, errorMessage) = await ValidateRequestAsync(request);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Failed to update organization report data {reportId} for organization {organizationId}: {errorMessage}",
|
||||
request.ReportId, request.OrganizationId, errorMessage);
|
||||
throw new BadRequestException(errorMessage);
|
||||
}
|
||||
|
||||
var existingReport = await _organizationReportRepo.GetByIdAsync(request.ReportId);
|
||||
if (existingReport == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} not found", request.ReportId);
|
||||
throw new NotFoundException("Organization report not found");
|
||||
}
|
||||
|
||||
if (existingReport.OrganizationId != request.OrganizationId)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} does not belong to organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
throw new BadRequestException("Organization report does not belong to the specified organization");
|
||||
}
|
||||
|
||||
var updatedReport = await _organizationReportRepo.UpdateReportDataAsync(request.OrganizationId, request.ReportId, request.ReportData);
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated organization report data {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
|
||||
return updatedReport;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error updating organization report data {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool IsValid, string errorMessage)> ValidateRequestAsync(UpdateOrganizationReportDataRequest request)
|
||||
{
|
||||
if (request.OrganizationId == Guid.Empty)
|
||||
{
|
||||
return (false, "OrganizationId is required");
|
||||
}
|
||||
|
||||
if (request.ReportId == Guid.Empty)
|
||||
{
|
||||
return (false, "ReportId is required");
|
||||
}
|
||||
|
||||
var organization = await _organizationRepo.GetByIdAsync(request.OrganizationId);
|
||||
if (organization == null)
|
||||
{
|
||||
return (false, "Invalid Organization");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.ReportData))
|
||||
{
|
||||
return (false, "Report Data is required");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
|
||||
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
|
||||
using Bit.Core.Dirt.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.Dirt.Reports.ReportFeatures;
|
||||
|
||||
public class UpdateOrganizationReportSummaryCommand : IUpdateOrganizationReportSummaryCommand
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepo;
|
||||
private readonly IOrganizationReportRepository _organizationReportRepo;
|
||||
private readonly ILogger<UpdateOrganizationReportSummaryCommand> _logger;
|
||||
|
||||
public UpdateOrganizationReportSummaryCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationReportRepository organizationReportRepository,
|
||||
ILogger<UpdateOrganizationReportSummaryCommand> logger)
|
||||
{
|
||||
_organizationRepo = organizationRepository;
|
||||
_organizationReportRepo = organizationReportRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<OrganizationReport> UpdateOrganizationReportSummaryAsync(UpdateOrganizationReportSummaryRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Updating organization report summary {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
|
||||
var (isValid, errorMessage) = await ValidateRequestAsync(request);
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Failed to update organization report summary {reportId} for organization {organizationId}: {errorMessage}",
|
||||
request.ReportId, request.OrganizationId, errorMessage);
|
||||
throw new BadRequestException(errorMessage);
|
||||
}
|
||||
|
||||
var existingReport = await _organizationReportRepo.GetByIdAsync(request.ReportId);
|
||||
if (existingReport == null)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} not found", request.ReportId);
|
||||
throw new NotFoundException("Organization report not found");
|
||||
}
|
||||
|
||||
if (existingReport.OrganizationId != request.OrganizationId)
|
||||
{
|
||||
_logger.LogWarning(Constants.BypassFiltersEventId, "Organization report {reportId} does not belong to organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
throw new BadRequestException("Organization report does not belong to the specified organization");
|
||||
}
|
||||
|
||||
var updatedReport = await _organizationReportRepo.UpdateSummaryDataAsync(request.OrganizationId, request.ReportId, request.SummaryData);
|
||||
|
||||
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated organization report summary {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
|
||||
return updatedReport;
|
||||
}
|
||||
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
|
||||
{
|
||||
_logger.LogError(ex, "Error updating organization report summary {reportId} for organization {organizationId}",
|
||||
request.ReportId, request.OrganizationId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool IsValid, string errorMessage)> ValidateRequestAsync(UpdateOrganizationReportSummaryRequest request)
|
||||
{
|
||||
if (request.OrganizationId == Guid.Empty)
|
||||
{
|
||||
return (false, "OrganizationId is required");
|
||||
}
|
||||
|
||||
if (request.ReportId == Guid.Empty)
|
||||
{
|
||||
return (false, "ReportId is required");
|
||||
}
|
||||
|
||||
var organization = await _organizationRepo.GetByIdAsync(request.OrganizationId);
|
||||
if (organization == null)
|
||||
{
|
||||
return (false, "Invalid Organization");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.SummaryData))
|
||||
{
|
||||
return (false, "Summary Data is required");
|
||||
}
|
||||
|
||||
return (true, string.Empty);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,25 @@
|
||||
using Bit.Core.Dirt.Entities;
|
||||
using Bit.Core.Dirt.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.Dirt.Repositories;
|
||||
|
||||
public interface IOrganizationReportRepository : IRepository<OrganizationReport, Guid>
|
||||
{
|
||||
Task<ICollection<OrganizationReport>> GetByOrganizationIdAsync(Guid organizationId);
|
||||
|
||||
// Whole OrganizationReport methods
|
||||
Task<OrganizationReport> GetLatestByOrganizationIdAsync(Guid organizationId);
|
||||
|
||||
// SummaryData methods
|
||||
Task<IEnumerable<OrganizationReportSummaryDataResponse>> GetSummaryDataByDateRangeAsync(Guid organizationId, DateTime startDate, DateTime endDate);
|
||||
Task<OrganizationReportSummaryDataResponse> GetSummaryDataAsync(Guid reportId);
|
||||
Task<OrganizationReport> UpdateSummaryDataAsync(Guid orgId, Guid reportId, string summaryData);
|
||||
|
||||
// ReportData methods
|
||||
Task<OrganizationReportDataResponse> GetReportDataAsync(Guid reportId);
|
||||
Task<OrganizationReport> UpdateReportDataAsync(Guid orgId, Guid reportId, string reportData);
|
||||
|
||||
// ApplicationData methods
|
||||
Task<OrganizationReportApplicationDataResponse> GetApplicationDataAsync(Guid reportId);
|
||||
Task<OrganizationReport> UpdateApplicationDataAsync(Guid orgId, Guid reportId, string applicationData);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user