diff --git a/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs b/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs index 6d815b03eaa5..2c9337a419b3 100644 --- a/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs +++ b/test/SeederApi.IntegrationTest/SeederApiApplicationFactory.cs @@ -1,6 +1,7 @@ using Bit.Core.Services; using Bit.IntegrationTestCommon; using Bit.IntegrationTestCommon.Factories; +using Microsoft.AspNetCore.TestHost; namespace Bit.SeederApi.IntegrationTest; @@ -15,4 +16,16 @@ public SeederApiApplicationFactory() serviceCollection.AddHttpContextAccessor(); }); } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + base.ConfigureWebHost(builder); + + builder.ConfigureTestServices(services => + { + // Remove scheduled background jobs to prevent errors in parallel test execution + var jobService = services.First(sd => sd.ServiceType == typeof(IHostedService) && sd.ImplementationType == typeof(Jobs.JobsHostedService)); + services.Remove(jobService); + }); + } } diff --git a/util/SeederApi/Controllers/SeedController.cs b/util/SeederApi/Controllers/SeedController.cs index 44f0dbaf2ce7..d81e0053c323 100644 --- a/util/SeederApi/Controllers/SeedController.cs +++ b/util/SeederApi/Controllers/SeedController.cs @@ -75,13 +75,18 @@ public async Task DeleteAsync([FromRoute] string playId) } } - [HttpDelete] - public async Task DeleteAllAsync() + public async Task DeleteAllAsync([FromBody] DateTime? olderThanRequest) { - logger.LogInformation("Deleting all seeded data"); + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var olderThan = olderThanRequest?.ToUniversalTime() ?? DateTime.UtcNow.AddDays(-1); + logger.LogInformation("Deleting all seeded data older than {OlderThan} UTC", olderThan); - var playIds = getAllPlayIdsQuery.GetAllPlayIds(); + var playIds = getAllPlayIdsQuery.GetAllPlayIds(olderThan: olderThan); try { diff --git a/util/SeederApi/Jobs/DeleteOldPlayDataJob.cs b/util/SeederApi/Jobs/DeleteOldPlayDataJob.cs new file mode 100644 index 000000000000..7c8e45cd5a85 --- /dev/null +++ b/util/SeederApi/Jobs/DeleteOldPlayDataJob.cs @@ -0,0 +1,32 @@ +using Bit.Core; +using Bit.Core.Jobs; +using Bit.SeederApi.Commands.Interfaces; +using Bit.SeederApi.Queries.Interfaces; +using Quartz; + +namespace Bit.SeederApi.Jobs; + +public class DeleteOldPlayDataJob : BaseJob +{ + private readonly IGetAllPlayIdsQuery _getAllPlayIdsQuery; + private readonly IDestroyBatchScenesCommand _destroyBatchScenesCommand; + + public DeleteOldPlayDataJob( + IGetAllPlayIdsQuery getAllPlayIdsQuery, + IDestroyBatchScenesCommand destroyBatchScenesCommand, + ILogger logger) + : base(logger) + { + _getAllPlayIdsQuery = getAllPlayIdsQuery; + _destroyBatchScenesCommand = destroyBatchScenesCommand; + } + + protected async override Task ExecuteJobAsync(IJobExecutionContext context) + { + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteOldPlayDataJob"); + var olderThan = DateTime.UtcNow.AddDays(-1); + var playIds = _getAllPlayIdsQuery.GetAllPlayIds(olderThan); + await _destroyBatchScenesCommand.DestroyAsync(playIds); + _logger.LogInformation(Constants.BypassFiltersEventId, "Finished job task: DeleteOldPlayDataJob. Deleted {PlayIdCount} root items", playIds.Count); + } +} diff --git a/util/SeederApi/Jobs/JobsHostedService.cs b/util/SeederApi/Jobs/JobsHostedService.cs new file mode 100644 index 000000000000..a35f969ce4ea --- /dev/null +++ b/util/SeederApi/Jobs/JobsHostedService.cs @@ -0,0 +1,38 @@ +using Bit.Core.Jobs; +using Bit.Core.Settings; +using Quartz; + +namespace Bit.SeederApi.Jobs; + +public class JobsHostedService : BaseJobsHostedService +{ + public JobsHostedService( + GlobalSettings globalSettings, + IServiceProvider serviceProvider, + ILogger logger, + ILogger listenerLogger) + : base(globalSettings, serviceProvider, logger, listenerLogger) { } + + public override async Task StartAsync(CancellationToken cancellationToken) + { + var everyFifteenMinutesTrigger = TriggerBuilder.Create() + .WithIdentity("everyFifteenMinutesTrigger") + .StartNow() + .WithCronSchedule("0 */15 * ? * *") + .Build(); + + + var jobs = new List> + { + new Tuple(typeof(DeleteOldPlayDataJob), everyFifteenMinutesTrigger), + }; + + Jobs = jobs; + await base.StartAsync(cancellationToken); + } + + public static void AddJobsServices(IServiceCollection services) + { + services.AddTransient(); + } +} diff --git a/util/SeederApi/Queries/GetAllPlayIdsQuery.cs b/util/SeederApi/Queries/GetAllPlayIdsQuery.cs index 7bc72e5b07f3..8aa64aa8299d 100644 --- a/util/SeederApi/Queries/GetAllPlayIdsQuery.cs +++ b/util/SeederApi/Queries/GetAllPlayIdsQuery.cs @@ -12,4 +12,13 @@ public List GetAllPlayIds() .Distinct() .ToList(); } + + public List GetAllPlayIds(DateTime olderThan) + { + return databaseContext.PlayItem + .Where(pd => pd.CreationDate < olderThan) + .Select(pd => pd.PlayId) + .Distinct() + .ToList(); + } } diff --git a/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs b/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs index ea9c44991af4..cc3231b66287 100644 --- a/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs +++ b/util/SeederApi/Queries/Interfaces/IGetAllPlayIdsQuery.cs @@ -10,4 +10,10 @@ public interface IGetAllPlayIdsQuery /// /// A list of play IDs representing active seeded data that can be destroyed. List GetAllPlayIds(); + /// + /// Retrieves all play IDs for currently tracked seeded data that were created prior to the given DateTime + /// + /// The cutoff point for PlayId creation date + /// + List GetAllPlayIds(DateTime olderThan); } diff --git a/util/SeederApi/SeederApi.csproj b/util/SeederApi/SeederApi.csproj index 53e9941c1cb4..0ec45d577c92 100644 --- a/util/SeederApi/SeederApi.csproj +++ b/util/SeederApi/SeederApi.csproj @@ -9,6 +9,7 @@ + diff --git a/util/SeederApi/Startup.cs b/util/SeederApi/Startup.cs index a38ff8256bde..ba203a94cb9e 100644 --- a/util/SeederApi/Startup.cs +++ b/util/SeederApi/Startup.cs @@ -41,6 +41,9 @@ public void ConfigureServices(IServiceCollection services) services.AddQueries(); services.AddControllers(); + + Jobs.JobsHostedService.AddJobsServices(services); + services.AddHostedService(); } public void Configure(