1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-18 16:11:28 -05:00

[PM-20576] OrganizationReport - Queries and Command (#5983)

This commit is contained in:
Vijay Oommen
2025-06-24 09:13:43 -05:00
committed by GitHub
parent 494c41e3b1
commit 86a4ce5a51
19 changed files with 962 additions and 1 deletions

View File

@ -0,0 +1,74 @@
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 AddOrganizationReportCommand : IAddOrganizationReportCommand
{
private readonly IOrganizationRepository _organizationRepo;
private readonly IOrganizationReportRepository _organizationReportRepo;
private ILogger<AddOrganizationReportCommand> _logger;
public AddOrganizationReportCommand(
IOrganizationRepository organizationRepository,
IOrganizationReportRepository organizationReportRepository,
ILogger<AddOrganizationReportCommand> logger)
{
_organizationRepo = organizationRepository;
_organizationReportRepo = organizationReportRepository;
_logger = logger;
}
public async Task<OrganizationReport> AddOrganizationReportAsync(AddOrganizationReportRequest request)
{
_logger.LogInformation("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);
throw new BadRequestException(errorMessage);
}
var organizationReport = new OrganizationReport
{
OrganizationId = request.OrganizationId,
ReportData = request.ReportData,
Date = request.Date == default ? DateTime.UtcNow : request.Date,
CreationDate = DateTime.UtcNow,
};
organizationReport.SetNewId();
var data = await _organizationReportRepo.CreateAsync(organizationReport);
_logger.LogInformation("Successfully added organization report for organization {organizationId}, {organizationReportId}",
request.OrganizationId, data.Id);
return data;
}
private async Task<(bool IsValid, string errorMessage)> ValidateRequestAsync(
AddOrganizationReportRequest request)
{
// verify that the organization exists
var organization = await _organizationRepo.GetByIdAsync(request.OrganizationId);
if (organization == null)
{
return (false, "Invalid Organization");
}
// ensure that we have report data
if (string.IsNullOrWhiteSpace(request.ReportData))
{
return (false, "Report Data is required");
}
return (true, string.Empty);
}
}

View File

@ -0,0 +1,42 @@
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 _ =>
{
_logger.LogInformation("Dropping organization report {organizationReportId} for organization {organizationId}",
_.Id, request.OrganizationId);
await _organizationReportRepo.DeleteAsync(_);
});
}
}

View File

@ -0,0 +1,43 @@
using Bit.Core.Dirt.Entities;
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 GetOrganizationReportQuery : IGetOrganizationReportQuery
{
private IOrganizationReportRepository _organizationReportRepo;
private ILogger<GetOrganizationReportQuery> _logger;
public GetOrganizationReportQuery(
IOrganizationReportRepository organizationReportRepo,
ILogger<GetOrganizationReportQuery> logger)
{
_organizationReportRepo = organizationReportRepo;
_logger = logger;
}
public async Task<IEnumerable<OrganizationReport>> GetOrganizationReportAsync(Guid organizationId)
{
if (organizationId == Guid.Empty)
{
throw new BadRequestException("OrganizationId is required.");
}
_logger.LogInformation("Fetching organization reports for organization {organizationId}", organizationId);
return await _organizationReportRepo.GetByOrganizationIdAsync(organizationId);
}
public async Task<OrganizationReport> GetLatestOrganizationReportAsync(Guid organizationId)
{
if (organizationId == Guid.Empty)
{
throw new BadRequestException("OrganizationId is required.");
}
_logger.LogInformation("Fetching latest organization report for organization {organizationId}", organizationId);
return await _organizationReportRepo.GetLatestByOrganizationIdAsync(organizationId);
}
}

View File

@ -0,0 +1,10 @@

using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
public interface IAddOrganizationReportCommand
{
Task<OrganizationReport> AddOrganizationReportAsync(AddOrganizationReportRequest request);
}

View File

@ -0,0 +1,9 @@

using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
public interface IDropOrganizationReportCommand
{
Task DropOrganizationReportAsync(DropOrganizationReportRequest request);
}

View File

@ -0,0 +1,9 @@
using Bit.Core.Dirt.Entities;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
public interface IGetOrganizationReportQuery
{
Task<IEnumerable<OrganizationReport>> GetOrganizationReportAsync(Guid organizationId);
Task<OrganizationReport> GetLatestOrganizationReportAsync(Guid organizationId);
}

View File

@ -13,5 +13,8 @@ public static class ReportingServiceCollectionExtensions
services.AddScoped<IAddPasswordHealthReportApplicationCommand, AddPasswordHealthReportApplicationCommand>();
services.AddScoped<IGetPasswordHealthReportApplicationQuery, GetPasswordHealthReportApplicationQuery>();
services.AddScoped<IDropPasswordHealthReportApplicationCommand, DropPasswordHealthReportApplicationCommand>();
services.AddScoped<IAddOrganizationReportCommand, AddOrganizationReportCommand>();
services.AddScoped<IDropOrganizationReportCommand, DropOrganizationReportCommand>();
services.AddScoped<IGetOrganizationReportQuery, GetOrganizationReportQuery>();
}
}

View File

@ -0,0 +1,8 @@
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
public class AddOrganizationReportRequest
{
public Guid OrganizationId { get; set; }
public string ReportData { get; set; }
public DateTime Date { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
public class DropOrganizationReportRequest
{
public Guid OrganizationId { get; set; }
public IEnumerable<Guid> OrganizationReportIds { get; set; }
}

View File

@ -6,5 +6,7 @@ namespace Bit.Core.Dirt.Repositories;
public interface IOrganizationReportRepository : IRepository<OrganizationReport, Guid>
{
Task<ICollection<OrganizationReport>> GetByOrganizationIdAsync(Guid organizationId);
Task<OrganizationReport> GetLatestByOrganizationIdAsync(Guid organizationId);
}