mirror of
https://github.com/bitwarden/server
synced 2025-12-13 14:53:34 +00:00
Use a header to track seeded data. This has benefits client side in simplicity and allows us to track entities added during a test, as long as they include the play id header.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
@@ -11,3 +13,38 @@ public class PlayIdService(IHostEnvironment hostEnvironment) : IPlayIdService
|
||||
return !string.IsNullOrEmpty(PlayId) && hostEnvironment.IsDevelopment();
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayIdSingletonService(IHttpContextAccessor httpContextAccessor, IHostEnvironment hostEnvironment) : IPlayIdService
|
||||
{
|
||||
private PlayIdService Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var httpContext = httpContextAccessor.HttpContext;
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("HttpContext is not available");
|
||||
}
|
||||
return httpContext.RequestServices.GetRequiredService<PlayIdService>();
|
||||
}
|
||||
}
|
||||
|
||||
public string? PlayId
|
||||
{
|
||||
get => Current.PlayId;
|
||||
set => Current.PlayId = value;
|
||||
}
|
||||
|
||||
public bool InPlay(out string playId)
|
||||
{
|
||||
if (hostEnvironment.IsDevelopment())
|
||||
{
|
||||
return Current.InPlay(out playId);
|
||||
}
|
||||
else
|
||||
{
|
||||
playId = string.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Bit.Infrastructure.EntityFramework.Models;
|
||||
|
||||
public class SeededData
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public required string RecipeName { get; set; }
|
||||
/// <summary>
|
||||
/// JSON blob containing all
|
||||
/// </summary>
|
||||
public required string Data { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
@@ -90,7 +90,6 @@ public class DatabaseContext : DbContext
|
||||
public DbSet<OrganizationInstallation> OrganizationInstallations { get; set; }
|
||||
public DbSet<OrganizationReport> OrganizationReports { get; set; }
|
||||
public DbSet<OrganizationApplication> OrganizationApplications { get; set; }
|
||||
public DbSet<SeededData> SeededData { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Bit.SharedWeb.Utilities;
|
||||
|
||||
public sealed class PlayIdMiddleware(RequestDelegate next)
|
||||
{
|
||||
public Task Invoke(HttpContext context, IPlayIdService playIdService)
|
||||
public Task Invoke(HttpContext context, PlayIdService playIdService)
|
||||
{
|
||||
if (context.Request.Headers.TryGetValue("x-play-id", out var playId))
|
||||
{
|
||||
|
||||
@@ -119,8 +119,11 @@ public static class ServiceCollectionExtensions
|
||||
}
|
||||
|
||||
// Include PlayIdService for tracking Play Ids in repositories
|
||||
services.AddScoped<IPlayIdService, PlayIdService>();
|
||||
// We need the http context accessor to use the Singleton version, which pulls from the scoped version
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddSingleton<IPlayIdService, PlayIdSingletonService>();
|
||||
services.AddScoped<PlayIdService>();
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ CREATE TABLE [dbo].[PlayData] (
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
CONSTRAINT [PK_PlayData] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_PlayData_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]),
|
||||
CONSTRAINT [FK_PlayData_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
|
||||
CONSTRAINT [FK_PlayData_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PlayData_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [CK_PlayData_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL))
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
CREATE TABLE [dbo].[SeededData] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[RecipeName] NVARCHAR (MAX) NOT NULL,
|
||||
[Data] NVARCHAR (MAX) NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
IF OBJECT_ID('dbo.SeededData') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE [dbo].[SeededData] (
|
||||
[Id] UNIQUEIDENTIFIER NOT NULL,
|
||||
[RecipeName] NVARCHAR (MAX) NOT NULL,
|
||||
[Data] NVARCHAR (MAX) NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
);
|
||||
END
|
||||
GO
|
||||
@@ -8,8 +8,8 @@ BEGIN
|
||||
[OrganizationId] UNIQUEIDENTIFIER NULL,
|
||||
[CreationDate] DATETIME2 (7) NOT NULL,
|
||||
CONSTRAINT [PK_PlayData] PRIMARY KEY CLUSTERED ([Id] ASC),
|
||||
CONSTRAINT [FK_PlayData_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]),
|
||||
CONSTRAINT [FK_PlayData_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
|
||||
CONSTRAINT [FK_PlayData_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [FK_PlayData_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
|
||||
CONSTRAINT [CK_PlayData_UserOrOrganization] CHECK (([UserId] IS NOT NULL AND [OrganizationId] IS NULL) OR ([UserId] IS NULL AND [OrganizationId] IS NOT NULL))
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
|
||||
namespace Bit.Seeder.Factories;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.RustSDK;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
public interface IScene
|
||||
{
|
||||
Type GetRequestType();
|
||||
SceneResult<object?> Seed(object request);
|
||||
Task<SceneResult<object?>> SeedAsync(object request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -12,19 +12,19 @@ public interface IScene
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
public interface IScene<TRequest> : IScene where TRequest : class
|
||||
{
|
||||
SceneResult Seed(TRequest request);
|
||||
Task<SceneResult> SeedAsync(TRequest request);
|
||||
Type IScene.GetRequestType() => typeof(TRequest);
|
||||
SceneResult<object?> IScene.Seed(object request)
|
||||
async Task<SceneResult<object?>> IScene.SeedAsync(object request)
|
||||
{
|
||||
var result = Seed((TRequest)request);
|
||||
return new SceneResult(mangleMap: result.MangleMap, trackedEntities: result.TrackedEntities);
|
||||
var result = await SeedAsync((TRequest)request);
|
||||
return new SceneResult(mangleMap: result.MangleMap);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IScene<TRequest, TResult> : IScene where TRequest : class where TResult : class
|
||||
{
|
||||
SceneResult<TResult> Seed(TRequest request);
|
||||
Task<SceneResult<TResult>> SeedAsync(TRequest request);
|
||||
|
||||
Type IScene.GetRequestType() => typeof(TRequest);
|
||||
SceneResult<object?> IScene.Seed(object request) => (SceneResult<object?>)Seed((TRequest)request);
|
||||
async Task<SceneResult<object?>> IScene.SeedAsync(object request) => (SceneResult<object?>)await SeedAsync((TRequest)request);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Factories;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
|
||||
@@ -2,21 +2,19 @@
|
||||
|
||||
public class SceneResult : SceneResult<object?>
|
||||
{
|
||||
public SceneResult(Dictionary<string, string?> mangleMap, Dictionary<string, List<Guid>> trackedEntities)
|
||||
: base(result: null, mangleMap: mangleMap, trackedEntities: trackedEntities) { }
|
||||
public SceneResult(Dictionary<string, string?> mangleMap)
|
||||
: base(result: null, mangleMap: mangleMap) { }
|
||||
}
|
||||
|
||||
public class SceneResult<TResult>
|
||||
{
|
||||
public TResult Result { get; init; }
|
||||
public Dictionary<string, string?> MangleMap { get; init; }
|
||||
public Dictionary<string, List<Guid>> TrackedEntities { get; init; }
|
||||
|
||||
public SceneResult(TResult result, Dictionary<string, string?> mangleMap, Dictionary<string, List<Guid>> trackedEntities)
|
||||
public SceneResult(TResult result, Dictionary<string, string?> mangleMap)
|
||||
{
|
||||
Result = result;
|
||||
MangleMap = mangleMap;
|
||||
TrackedEntities = trackedEntities;
|
||||
}
|
||||
|
||||
public static explicit operator SceneResult<object?>(SceneResult<TResult> v)
|
||||
@@ -25,11 +23,11 @@ public class SceneResult<TResult>
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
return new SceneResult<object?>(result: null, mangleMap: v.MangleMap, trackedEntities: v.TrackedEntities);
|
||||
return new SceneResult<object?>(result: null, mangleMap: v.MangleMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SceneResult<object?>(result: result, mangleMap: v.MangleMap, trackedEntities: v.TrackedEntities);
|
||||
return new SceneResult<object?>(result: result, mangleMap: v.MangleMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Seeder.Factories;
|
||||
|
||||
namespace Bit.Seeder.Scenes;
|
||||
|
||||
public class SingleUserScene(DatabaseContext db, UserSeeder userSeeder) : IScene<SingleUserScene.Request>
|
||||
public class SingleUserScene(UserSeeder userSeeder, IUserRepository userRepository) : IScene<SingleUserScene.Request>
|
||||
{
|
||||
public class Request
|
||||
{
|
||||
@@ -14,12 +14,11 @@ public class SingleUserScene(DatabaseContext db, UserSeeder userSeeder) : IScene
|
||||
public bool Premium { get; set; } = false;
|
||||
}
|
||||
|
||||
public SceneResult Seed(Request request)
|
||||
public async Task<SceneResult> SeedAsync(Request request)
|
||||
{
|
||||
var user = userSeeder.CreateUser(request.Email, request.EmailVerified, request.Premium);
|
||||
|
||||
db.Add(user);
|
||||
db.SaveChanges();
|
||||
await userRepository.CreateAsync(user);
|
||||
|
||||
return new SceneResult(mangleMap: userSeeder.GetMangleMap(user, new UserData
|
||||
{
|
||||
@@ -31,9 +30,6 @@ public class SingleUserScene(DatabaseContext db, UserSeeder userSeeder) : IScene
|
||||
ApiKey = user.ApiKey,
|
||||
Kdf = user.Kdf,
|
||||
KdfIterations = user.KdfIterations,
|
||||
}), trackedEntities: new Dictionary<string, List<Guid>>
|
||||
{
|
||||
["User"] = [user.Id]
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,18 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace Bit.SeederApi.Controllers;
|
||||
|
||||
[Route("seed")]
|
||||
public class SeedController(ILogger<SeedController> logger, ISceneService sceneService)
|
||||
public class SeedController(ILogger<SeedController> logger, ISceneService sceneService, IServiceProvider serviceProvider)
|
||||
: Controller
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Seed([FromBody] SeedRequestModel request)
|
||||
public async Task<IActionResult> Seed([FromBody] SeedRequestModel request)
|
||||
{
|
||||
logger.LogInformation("Received seed request {Provider}", serviceProvider.GetType().FullName);
|
||||
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
||||
|
||||
try
|
||||
{
|
||||
SceneResponseModel response = sceneService.ExecuteScene(request.Template, request.Arguments);
|
||||
SceneResponseModel response = await sceneService.ExecuteScene(request.Template, request.Arguments);
|
||||
|
||||
return Json(response);
|
||||
}
|
||||
@@ -36,24 +37,24 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
}
|
||||
|
||||
[HttpDelete("batch")]
|
||||
public async Task<IActionResult> DeleteBatch([FromBody] List<Guid> seedIds)
|
||||
public async Task<IActionResult> DeleteBatch([FromBody] List<string> playIds)
|
||||
{
|
||||
logger.LogInformation("Deleting batch of seeded data with IDs: {SeedIds}", string.Join(", ", seedIds));
|
||||
logger.LogInformation("Deleting batch of seeded data with IDs: {PlayIds}", string.Join(", ", playIds));
|
||||
|
||||
var aggregateException = new AggregateException();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var seedId in seedIds)
|
||||
foreach (var playId in playIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await sceneService.DestroyScene(seedId);
|
||||
await sceneService.DestroyScene(playId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
aggregateException = new AggregateException(aggregateException, ex);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", seedId);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", playId);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -72,20 +73,20 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
});
|
||||
}
|
||||
|
||||
[HttpDelete("{seedId}")]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid seedId)
|
||||
[HttpDelete("{playId}")]
|
||||
public async Task<IActionResult> Delete([FromRoute] string playId)
|
||||
{
|
||||
logger.LogInformation("Deleting seeded data with ID: {SeedId}", seedId);
|
||||
logger.LogInformation("Deleting seeded data with ID: {PlayId}", playId);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await sceneService.DestroyScene(seedId);
|
||||
var result = await sceneService.DestroyScene(playId);
|
||||
|
||||
return Json(result);
|
||||
}
|
||||
catch (SceneExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", seedId);
|
||||
logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
@@ -101,25 +102,23 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
logger.LogInformation("Deleting all seeded data");
|
||||
|
||||
// Pull all Seeded Data ids
|
||||
var seededData = sceneService.GetAllSeededData();
|
||||
|
||||
var playIds = sceneService.GetAllPlayIds();
|
||||
|
||||
var aggregateException = new AggregateException();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var sd in seededData)
|
||||
foreach (var playId in playIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await sceneService.DestroyScene(sd.Id);
|
||||
await sceneService.DestroyScene(playId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
aggregateException = new AggregateException(aggregateException, ex);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", sd.Id);
|
||||
logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (aggregateException.InnerExceptions.Count > 0)
|
||||
{
|
||||
|
||||
@@ -4,17 +4,15 @@ namespace Bit.SeederApi.Models.Response;
|
||||
|
||||
public class SceneResponseModel
|
||||
{
|
||||
public required Guid? SeedId { get; init; }
|
||||
public required Dictionary<string, string?>? MangleMap { get; init; }
|
||||
public required object? Result { get; init; }
|
||||
|
||||
public static SceneResponseModel FromSceneResult<T>(SceneResult<T> sceneResult, Guid? seedId)
|
||||
public static SceneResponseModel FromSceneResult<T>(SceneResult<T> sceneResult)
|
||||
{
|
||||
return new SceneResponseModel
|
||||
{
|
||||
Result = sceneResult.Result,
|
||||
MangleMap = sceneResult.MangleMap,
|
||||
SeedId = seedId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.SharedWeb.Utilities;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
var globalSettings = builder.Services.AddGlobalSettingsServices(builder.Configuration, builder.Environment);
|
||||
|
||||
@@ -26,6 +27,9 @@ builder.Services.AddQueries();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Add PlayIdMiddleware services
|
||||
app.UseMiddleware<PlayIdMiddleware>();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
|
||||
namespace Bit.SeederApi.Services;
|
||||
@@ -14,14 +13,14 @@ public interface ISceneService
|
||||
/// <returns>A tuple containing the result and optional seed ID for tracked entities</returns>
|
||||
/// <exception cref="SceneNotFoundException">Thrown when the scene template is not found</exception>
|
||||
/// <exception cref="SceneExecutionException">Thrown when there's an error executing the scene</exception>
|
||||
SceneResponseModel ExecuteScene(string templateName, JsonElement? arguments);
|
||||
Task<SceneResponseModel> ExecuteScene(string templateName, JsonElement? arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Destroys data created by a scene using the seeded data ID.
|
||||
/// </summary>
|
||||
/// <param name="seedId">The ID of the seeded data to destroy</param>
|
||||
/// <param name="playId">The ID of the seeded data to destroy</param>
|
||||
/// <returns>The result of the destroy operation</returns>
|
||||
/// <exception cref="SceneExecutionException">Thrown when there's an error destroying the seeded data</exception>
|
||||
Task<object?> DestroyScene(Guid seedId);
|
||||
List<SeededData> GetAllSeededData();
|
||||
Task<object?> DestroyScene(string playId);
|
||||
List<string> GetAllPlayIds();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
@@ -12,6 +11,7 @@ public class SceneService(
|
||||
ILogger<SceneService> logger,
|
||||
IServiceProvider serviceProvider,
|
||||
IUserRepository userRepository,
|
||||
IPlayDataRepository playDataRepository,
|
||||
IOrganizationRepository organizationRepository)
|
||||
: ISceneService
|
||||
{
|
||||
@@ -21,35 +21,18 @@ public class SceneService(
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public List<SeededData> GetAllSeededData()
|
||||
public List<string> GetAllPlayIds()
|
||||
{
|
||||
return databaseContext.SeededData.ToList();
|
||||
return [.. databaseContext.PlayData
|
||||
.Select(pd => pd.PlayId)
|
||||
.Distinct()];
|
||||
}
|
||||
|
||||
public SceneResponseModel ExecuteScene(string templateName, JsonElement? arguments)
|
||||
public async Task<SceneResponseModel> ExecuteScene(string templateName, JsonElement? arguments)
|
||||
{
|
||||
var result = ExecuteSceneMethod(templateName, arguments, "Seed");
|
||||
var result = await ExecuteSceneMethod(templateName, arguments, "Seed");
|
||||
|
||||
if (result.TrackedEntities.Count == 0)
|
||||
{
|
||||
return SceneResponseModel.FromSceneResult(result, seedId: null);
|
||||
}
|
||||
|
||||
var seededData = new SeededData
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RecipeName = templateName,
|
||||
Data = JsonSerializer.Serialize(result.TrackedEntities),
|
||||
CreationDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
databaseContext.Add(seededData);
|
||||
databaseContext.SaveChanges();
|
||||
|
||||
logger.LogInformation("Saved seeded data with ID {SeedId} for scene {SceneName}",
|
||||
seededData.Id, templateName);
|
||||
|
||||
return SceneResponseModel.FromSceneResult(result, seededData.Id);
|
||||
return SceneResponseModel.FromSceneResult(result);
|
||||
}
|
||||
|
||||
public object ExecuteQuery(string queryName, JsonElement? arguments)
|
||||
@@ -113,31 +96,23 @@ public class SceneService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<object?> DestroyScene(Guid seedId)
|
||||
public async Task<object?> DestroyScene(string playId)
|
||||
{
|
||||
var seededData = databaseContext.SeededData.FirstOrDefault(s => s.Id == seedId);
|
||||
if (seededData == null)
|
||||
{
|
||||
logger.LogInformation("No seeded data found with ID {SeedId}, skipping", seedId);
|
||||
return null;
|
||||
}
|
||||
// Note, delete cascade will remove PlayData entries
|
||||
|
||||
var trackedEntities = JsonSerializer.Deserialize<Dictionary<string, List<Guid>>>(seededData.Data);
|
||||
if (trackedEntities == null)
|
||||
{
|
||||
throw new SceneExecutionException($"Failed to deserialize tracked entities for seed ID {seedId}");
|
||||
}
|
||||
var playData = await playDataRepository.GetByPlayIdAsync(playId);
|
||||
var userIds = playData.Select(pd => pd.UserId).Distinct().ToList();
|
||||
var organizationIds = playData.Select(pd => pd.OrganizationId).Distinct().ToList();
|
||||
|
||||
// Delete in reverse order to respect foreign key constraints
|
||||
if (trackedEntities.TryGetValue("User", out var userIds))
|
||||
// Delete Users before Oraganizations to respect foreign key constraints
|
||||
if (userIds.Count > 0)
|
||||
{
|
||||
var users = databaseContext.Users.Where(u => userIds.Contains(u.Id));
|
||||
await userRepository.DeleteManyAsync(users);
|
||||
}
|
||||
|
||||
if (trackedEntities.TryGetValue("Organization", out var orgIds))
|
||||
if (organizationIds.Count > 0)
|
||||
{
|
||||
var organizations = databaseContext.Organizations.Where(o => orgIds.Contains(o.Id));
|
||||
var organizations = databaseContext.Organizations.Where(o => organizationIds.Contains(o.Id));
|
||||
var aggregateException = new AggregateException();
|
||||
foreach (var org in organizations)
|
||||
{
|
||||
@@ -153,21 +128,18 @@ public class SceneService(
|
||||
if (aggregateException.InnerExceptions.Count > 0)
|
||||
{
|
||||
throw new SceneExecutionException(
|
||||
$"One or more errors occurred while deleting organizations for seed ID {seedId}",
|
||||
$"One or more errors occurred while deleting organizations for seed ID {playId}",
|
||||
aggregateException);
|
||||
}
|
||||
}
|
||||
|
||||
databaseContext.Remove(seededData);
|
||||
databaseContext.SaveChanges();
|
||||
logger.LogInformation("Successfully destroyed seeded data with ID {PlayId}",
|
||||
playId);
|
||||
|
||||
logger.LogInformation("Successfully destroyed seeded data with ID {SeedId} for scene {SceneName}",
|
||||
seedId, seededData.RecipeName);
|
||||
|
||||
return new { SeedId = seedId, SceneName = seededData.RecipeName };
|
||||
return new { PlayId = playId };
|
||||
}
|
||||
|
||||
private SceneResult<object?> ExecuteSceneMethod(string templateName, JsonElement? arguments, string methodName)
|
||||
private async Task<SceneResult<object?>> ExecuteSceneMethod(string templateName, JsonElement? arguments, string methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -214,7 +186,7 @@ public class SceneService(
|
||||
}
|
||||
}
|
||||
|
||||
var result = scene.Seed(requestModel);
|
||||
var result = await scene.SeedAsync(requestModel);
|
||||
|
||||
logger.LogInformation("Successfully executed {MethodName} on scene: {TemplateName}", methodName, templateName);
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user