mirror of
https://github.com/bitwarden/server
synced 2025-12-11 13:53:40 +00:00
Stricter scene and query types
SeederAPI only serves Scenes, Recipes are inteded to be used locally only.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
using Xunit;
|
||||
@@ -40,11 +40,12 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
||||
});
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var result = await response.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||
var result = await response.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEqual(Guid.Empty, result.SeedId);
|
||||
Assert.NotNull(result.Result);
|
||||
Assert.NotNull(result.MangleMap);
|
||||
Assert.Null(result.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -83,7 +84,7 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
||||
});
|
||||
|
||||
seedResponse.EnsureSuccessStatusCode();
|
||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||
Assert.NotNull(seedResult);
|
||||
|
||||
var deleteResponse = await _client.DeleteAsync($"/seed/{seedResult.SeedId}");
|
||||
@@ -117,7 +118,7 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
||||
});
|
||||
|
||||
seedResponse.EnsureSuccessStatusCode();
|
||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||
Assert.NotNull(seedResult);
|
||||
Assert.NotNull(seedResult.SeedId);
|
||||
seedIds.Add(seedResult.SeedId.Value);
|
||||
@@ -149,7 +150,7 @@ public class SeedControllerTests : IClassFixture<SeederApiApplicationFactory>, I
|
||||
});
|
||||
|
||||
seedResponse.EnsureSuccessStatusCode();
|
||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SeedResponseModel>();
|
||||
var seedResult = await seedResponse.Content.ReadFromJsonAsync<SceneResponseModel>();
|
||||
Assert.NotNull(seedResult);
|
||||
|
||||
// Try to delete with mix of valid and invalid IDs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Bit.Seeder;
|
||||
namespace Bit.Seeder;
|
||||
|
||||
public interface IQuery
|
||||
{
|
||||
@@ -6,9 +6,9 @@ public interface IQuery
|
||||
object Execute(object request);
|
||||
}
|
||||
|
||||
public interface IQuery<TRequest> : IQuery where TRequest : class
|
||||
public interface IQuery<TRequest, TResult> : IQuery where TRequest : class where TResult : class
|
||||
{
|
||||
object Execute(TRequest request);
|
||||
TResult Execute(TRequest request);
|
||||
|
||||
Type IQuery.GetRequestType() => typeof(TRequest);
|
||||
object IQuery.Execute(object request) => Execute((TRequest)request);
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
namespace Bit.Seeder;
|
||||
namespace Bit.Seeder;
|
||||
|
||||
public interface IScene
|
||||
{
|
||||
Type GetRequestType();
|
||||
SceneResult Seed(object request);
|
||||
SceneResult<object?> Seed(object request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic scene interface for seeding operations with a specific request type. Does not return a value beyond tracking entities and a mangle map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRequest"></typeparam>
|
||||
public interface IScene<TRequest> : IScene where TRequest : class
|
||||
{
|
||||
SceneResult Seed(TRequest request);
|
||||
Type IScene.GetRequestType() => typeof(TRequest);
|
||||
SceneResult<object?> IScene.Seed(object request)
|
||||
{
|
||||
var result = Seed((TRequest)request);
|
||||
return new SceneResult(mangleMap: result.MangleMap, trackedEntities: result.TrackedEntities);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IScene<TRequest, TResult> : IScene where TRequest : class where TResult : class
|
||||
{
|
||||
SceneResult<TResult> Seed(TRequest request);
|
||||
|
||||
Type IScene.GetRequestType() => typeof(TRequest);
|
||||
SceneResult IScene.Seed(object request) => Seed((TRequest)request);
|
||||
SceneResult<object?> IScene.Seed(object request) => (SceneResult<object?>)Seed((TRequest)request);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
@@ -9,7 +8,7 @@ namespace Bit.Seeder.Queries;
|
||||
public class EmergencyAccessInviteQuery(
|
||||
DatabaseContext db,
|
||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> dataProtectorTokenizer)
|
||||
: IQuery<EmergencyAccessInviteQuery.Request>
|
||||
: IQuery<EmergencyAccessInviteQuery.Request, IEnumerable<string>>
|
||||
{
|
||||
public class Request
|
||||
{
|
||||
@@ -17,7 +16,7 @@ public class EmergencyAccessInviteQuery(
|
||||
public required string Email { get; set; }
|
||||
}
|
||||
|
||||
public object Execute(Request request)
|
||||
public IEnumerable<string> Execute(Request request)
|
||||
{
|
||||
var invites = db.EmergencyAccesses
|
||||
.Where(ea => ea.Email == request.Email).ToList().Select(ea =>
|
||||
|
||||
6
util/Seeder/RecipeResult.cs
Normal file
6
util/Seeder/RecipeResult.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Seeder;
|
||||
|
||||
public class RecipeResult
|
||||
{
|
||||
public Dictionary<string, List<Guid>> TrackedEntities { get; init; } = new();
|
||||
}
|
||||
@@ -1,7 +1,35 @@
|
||||
namespace Bit.Seeder;
|
||||
|
||||
public class SceneResult
|
||||
public class SceneResult : SceneResult<object?>
|
||||
{
|
||||
public required object Result { get; init; }
|
||||
public Dictionary<string, List<Guid>> TrackedEntities { get; init; } = new();
|
||||
public SceneResult(Dictionary<string, string?> mangleMap, Dictionary<string, List<Guid>> trackedEntities)
|
||||
: base(result: null, mangleMap: mangleMap, trackedEntities: trackedEntities) { }
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Result = result;
|
||||
MangleMap = mangleMap;
|
||||
TrackedEntities = trackedEntities;
|
||||
}
|
||||
|
||||
public static explicit operator SceneResult<object?>(SceneResult<TResult> v)
|
||||
{
|
||||
var result = v.Result;
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
return new SceneResult<object?>(result: null, mangleMap: v.MangleMap, trackedEntities: v.TrackedEntities);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SceneResult<object?>(result: result, mangleMap: v.MangleMap, trackedEntities: v.TrackedEntities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Enums;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Factories;
|
||||
|
||||
@@ -22,23 +21,19 @@ public class SingleUserScene(DatabaseContext db, UserSeeder userSeeder) : IScene
|
||||
db.Add(user);
|
||||
db.SaveChanges();
|
||||
|
||||
return new SceneResult
|
||||
return new SceneResult(mangleMap: userSeeder.GetMangleMap(user, new UserData
|
||||
{
|
||||
Result = userSeeder.GetMangleMap(user, new UserData
|
||||
{
|
||||
Email = request.Email,
|
||||
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"),
|
||||
Key = "seeded_key",
|
||||
PublicKey = "seeded_public_key",
|
||||
PrivateKey = "seeded_private_key",
|
||||
ApiKey = "seeded_api_key",
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = 600_000,
|
||||
}),
|
||||
TrackedEntities = new Dictionary<string, List<Guid>>
|
||||
{
|
||||
["User"] = [user.Id]
|
||||
}
|
||||
};
|
||||
Email = request.Email,
|
||||
Id = user.Id,
|
||||
Key = user.Key,
|
||||
PublicKey = user.PublicKey,
|
||||
PrivateKey = user.PrivateKey,
|
||||
ApiKey = user.ApiKey,
|
||||
Kdf = user.Kdf,
|
||||
KdfIterations = user.KdfIterations,
|
||||
}), trackedEntities: new Dictionary<string, List<Guid>>
|
||||
{
|
||||
["User"] = [user.Id]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.SeederApi.Controllers
|
||||
namespace Bit.SeederApi.Controllers;
|
||||
|
||||
[Route("query")]
|
||||
public class QueryController(ILogger<QueryController> logger, ISeedService recipeService)
|
||||
: Controller
|
||||
{
|
||||
[Route("query")]
|
||||
public class QueryController(ILogger<QueryController> logger, IRecipeService recipeService)
|
||||
: Controller
|
||||
[HttpPost]
|
||||
public IActionResult Query([FromBody] QueryRequestModel request)
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Query([FromBody] QueryRequestModel request)
|
||||
logger.LogInformation("Executing query: {Query}", request.Template);
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Executing query: {Query}", request.Template);
|
||||
var result = recipeService.ExecuteQuery(request.Template, request.Arguments);
|
||||
|
||||
try
|
||||
return Json(new { Result = result });
|
||||
}
|
||||
catch (RecipeNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { Error = ex.Message });
|
||||
}
|
||||
catch (RecipeExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
||||
return BadRequest(new
|
||||
{
|
||||
var result = recipeService.ExecuteQuery(request.Template, request.Arguments);
|
||||
|
||||
return Json(new { Result = result });
|
||||
}
|
||||
catch (RecipeNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { Error = ex.Message });
|
||||
}
|
||||
catch (RecipeExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
}
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +1,133 @@
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
using Bit.SeederApi.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.SeederApi.Controllers
|
||||
namespace Bit.SeederApi.Controllers;
|
||||
|
||||
[Route("seed")]
|
||||
public class SeedController(ILogger<SeedController> logger, ISeedService recipeService)
|
||||
: Controller
|
||||
{
|
||||
[Route("seed")]
|
||||
public class SeedController(ILogger<SeedController> logger, IRecipeService recipeService)
|
||||
: Controller
|
||||
[HttpPost]
|
||||
public IActionResult Seed([FromBody] SeedRequestModel request)
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Seed([FromBody] SeedRequestModel request)
|
||||
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
||||
var response = recipeService.ExecuteScene(request.Template, request.Arguments);
|
||||
|
||||
try
|
||||
{
|
||||
var (result, seedId) = recipeService.ExecuteRecipe(request.Template, request.Arguments);
|
||||
|
||||
return Json(new SeedResponseModel
|
||||
{
|
||||
SeedId = seedId,
|
||||
Result = result,
|
||||
});
|
||||
}
|
||||
catch (RecipeNotFoundException ex)
|
||||
{
|
||||
return NotFound(new { Error = ex.Message });
|
||||
}
|
||||
catch (RecipeExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing scene: {Template}", request.Template);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
}
|
||||
return Json(response);
|
||||
}
|
||||
|
||||
[HttpDelete("batch")]
|
||||
public async Task<IActionResult> DeleteBatch([FromBody] List<Guid> seedIds)
|
||||
catch (RecipeNotFoundException ex)
|
||||
{
|
||||
logger.LogInformation("Deleting batch of seeded data with IDs: {SeedIds}", string.Join(", ", seedIds));
|
||||
|
||||
var aggregateException = new AggregateException();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var seedId in seedIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await recipeService.DestroyRecipe(seedId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
aggregateException = new AggregateException(aggregateException, ex);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", seedId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (aggregateException.InnerExceptions.Count > 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = "One or more errors occurred while deleting seeded data",
|
||||
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||
});
|
||||
}
|
||||
return Ok(new
|
||||
{
|
||||
Message = "Batch delete completed successfully"
|
||||
});
|
||||
return NotFound(new { Error = ex.Message });
|
||||
}
|
||||
|
||||
[HttpDelete("{seedId}")]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid seedId)
|
||||
catch (RecipeExecutionException ex)
|
||||
{
|
||||
logger.LogInformation("Deleting seeded data with ID: {SeedId}", seedId);
|
||||
|
||||
try
|
||||
logger.LogError(ex, "Error executing scene: {Template}", request.Template);
|
||||
return BadRequest(new
|
||||
{
|
||||
var result = await recipeService.DestroyRecipe(seedId);
|
||||
|
||||
return Json(result);
|
||||
}
|
||||
catch (RecipeExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", seedId);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteAll()
|
||||
{
|
||||
logger.LogInformation("Deleting all seeded data");
|
||||
|
||||
// Pull all Seeded Data ids
|
||||
var seededData = recipeService.GetAllSeededData();
|
||||
|
||||
var aggregateException = new AggregateException();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var sd in seededData)
|
||||
{
|
||||
try
|
||||
{
|
||||
await recipeService.DestroyRecipe(sd.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
aggregateException = new AggregateException(aggregateException, ex);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", sd.Id);
|
||||
}
|
||||
}
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
|
||||
if (aggregateException.InnerExceptions.Count > 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = "One or more errors occurred while deleting seeded data",
|
||||
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||
});
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("batch")]
|
||||
public async Task<IActionResult> DeleteBatch([FromBody] List<Guid> seedIds)
|
||||
{
|
||||
logger.LogInformation("Deleting batch of seeded data with IDs: {SeedIds}", string.Join(", ", seedIds));
|
||||
|
||||
var aggregateException = new AggregateException();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var seedId in seedIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
await recipeService.DestroyRecipe(seedId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
aggregateException = new AggregateException(aggregateException, ex);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", seedId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (aggregateException.InnerExceptions.Count > 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = "One or more errors occurred while deleting seeded data",
|
||||
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||
});
|
||||
}
|
||||
return Ok(new
|
||||
{
|
||||
Message = "Batch delete completed successfully"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpDelete("{seedId}")]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid seedId)
|
||||
{
|
||||
logger.LogInformation("Deleting seeded data with ID: {SeedId}", seedId);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await recipeService.DestroyRecipe(seedId);
|
||||
|
||||
return Json(result);
|
||||
}
|
||||
catch (RecipeExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", seedId);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteAll()
|
||||
{
|
||||
logger.LogInformation("Deleting all seeded data");
|
||||
|
||||
// Pull all Seeded Data ids
|
||||
var seededData = recipeService.GetAllSeededData();
|
||||
|
||||
var aggregateException = new AggregateException();
|
||||
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var sd in seededData)
|
||||
{
|
||||
try
|
||||
{
|
||||
await recipeService.DestroyRecipe(sd.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
aggregateException = new AggregateException(aggregateException, ex);
|
||||
logger.LogError(ex, "Error deleting seeded data: {SeedId}", sd.Id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (aggregateException.InnerExceptions.Count > 0)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = "One or more errors occurred while deleting seeded data",
|
||||
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||
});
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using Bit.Seeder;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
@@ -12,11 +12,15 @@ public static class ServiceCollectionExtensions
|
||||
/// </summary>
|
||||
public static IServiceCollection AddScenes(this IServiceCollection services)
|
||||
{
|
||||
var iSceneType1 = typeof(IScene<>);
|
||||
var iSceneType2 = typeof(IScene<,>);
|
||||
var isIScene = (Type t) => t == iSceneType1 || t == iSceneType2;
|
||||
|
||||
var seederAssembly = Assembly.Load("Seeder");
|
||||
var sceneTypes = seederAssembly.GetTypes()
|
||||
.Where(t => t is { IsClass: true, IsAbstract: false } &&
|
||||
t.GetInterfaces().Any(i => i.IsGenericType &&
|
||||
i.GetGenericTypeDefinition().Name == "IScene`1"));
|
||||
isIScene(i.GetGenericTypeDefinition())));
|
||||
|
||||
foreach (var sceneType in sceneTypes)
|
||||
{
|
||||
@@ -33,11 +37,12 @@ public static class ServiceCollectionExtensions
|
||||
/// </summary>
|
||||
public static IServiceCollection AddQueries(this IServiceCollection services)
|
||||
{
|
||||
var iQueryType = typeof(IQuery<,>);
|
||||
var seederAssembly = Assembly.Load("Seeder");
|
||||
var queryTypes = seederAssembly.GetTypes()
|
||||
.Where(t => t is { IsClass: true, IsAbstract: false } &&
|
||||
t.GetInterfaces().Any(i => i.IsGenericType &&
|
||||
i.GetGenericTypeDefinition().Name == "IQuery`1"));
|
||||
i.GetGenericTypeDefinition() == iQueryType));
|
||||
|
||||
foreach (var queryType in queryTypes)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
namespace Bit.SeederApi.Models.Response;
|
||||
using Bit.Seeder;
|
||||
|
||||
public class SeedResponseModel
|
||||
namespace Bit.SeederApi.Models.Response;
|
||||
|
||||
public class SceneResponseModel
|
||||
{
|
||||
public Guid? SeedId { get; set; }
|
||||
public object? Result { get; set; }
|
||||
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)
|
||||
{
|
||||
return new SceneResponseModel
|
||||
{
|
||||
Result = sceneResult.Result,
|
||||
MangleMap = sceneResult.MangleMap,
|
||||
SeedId = seedId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ builder.Services.AddScoped<Microsoft.AspNetCore.Identity.IPasswordHasher<Bit.Cor
|
||||
// Seeder services
|
||||
builder.Services.AddSingleton<Bit.RustSDK.RustSdkService>();
|
||||
builder.Services.AddScoped<Bit.Seeder.Factories.UserSeeder>();
|
||||
builder.Services.AddScoped<IRecipeService, RecipeService>();
|
||||
builder.Services.AddScoped<ISeedService, SeedService>();
|
||||
builder.Services.AddScoped<MangleId>(_ => new MangleId());
|
||||
builder.Services.AddScenes();
|
||||
builder.Services.AddQueries();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
|
||||
namespace Bit.SeederApi.Services;
|
||||
|
||||
public interface IRecipeService
|
||||
public interface ISeedService
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a scene with the given template name and arguments.
|
||||
@@ -13,7 +14,7 @@ public interface IRecipeService
|
||||
/// <returns>A tuple containing the result and optional seed ID for tracked entities</returns>
|
||||
/// <exception cref="RecipeNotFoundException">Thrown when the scene template is not found</exception>
|
||||
/// <exception cref="RecipeExecutionException">Thrown when there's an error executing the scene</exception>
|
||||
(object? Result, Guid? SeedId) ExecuteRecipe(string templateName, JsonElement? arguments);
|
||||
SceneResponseModel ExecuteScene(string templateName, JsonElement? arguments);
|
||||
|
||||
/// <summary>
|
||||
/// Destroys data created by a scene using the seeded data ID.
|
||||
|
||||
@@ -3,16 +3,17 @@ using Bit.Core.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
|
||||
namespace Bit.SeederApi.Services;
|
||||
|
||||
public class RecipeService(
|
||||
public class SeedService(
|
||||
DatabaseContext databaseContext,
|
||||
ILogger<RecipeService> logger,
|
||||
ILogger<SeedService> logger,
|
||||
IServiceProvider serviceProvider,
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository)
|
||||
: IRecipeService
|
||||
: ISeedService
|
||||
{
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
@@ -25,13 +26,13 @@ public class RecipeService(
|
||||
return databaseContext.SeededData.ToList();
|
||||
}
|
||||
|
||||
public (object? Result, Guid? SeedId) ExecuteRecipe(string templateName, JsonElement? arguments)
|
||||
public SceneResponseModel ExecuteScene(string templateName, JsonElement? arguments)
|
||||
{
|
||||
var result = ExecuteRecipeMethod(templateName, arguments, "Seed");
|
||||
var result = ExecuteSceneMethod(templateName, arguments, "Seed");
|
||||
|
||||
if (result.TrackedEntities.Count == 0)
|
||||
{
|
||||
return (Result: result.Result, SeedId: null);
|
||||
return SceneResponseModel.FromSceneResult(result, seedId: null);
|
||||
}
|
||||
|
||||
var seededData = new SeededData
|
||||
@@ -48,7 +49,7 @@ public class RecipeService(
|
||||
logger.LogInformation("Saved seeded data with ID {SeedId} for scene {RecipeName}",
|
||||
seededData.Id, templateName);
|
||||
|
||||
return (Result: result.Result, SeedId: seededData.Id);
|
||||
return SceneResponseModel.FromSceneResult(result, seededData.Id);
|
||||
}
|
||||
|
||||
public object ExecuteQuery(string queryName, JsonElement? arguments)
|
||||
@@ -166,7 +167,7 @@ public class RecipeService(
|
||||
return new { SeedId = seedId, RecipeName = seededData.RecipeName };
|
||||
}
|
||||
|
||||
private SceneResult ExecuteRecipeMethod(string templateName, JsonElement? arguments, string methodName)
|
||||
private SceneResult<object?> ExecuteSceneMethod(string templateName, JsonElement? arguments, string methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user