mirror of
https://github.com/bitwarden/server
synced 2025-12-13 14:53:34 +00:00
Add documentation
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
using System.Net;
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Request;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.SeederApi.IntegrationTest;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Net;
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Request;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
namespace Bit.Seeder;
|
||||
|
||||
public class SceneResult : SceneResult<object?>
|
||||
{
|
||||
public SceneResult(Dictionary<string, string?> mangleMap)
|
||||
: base(result: null, mangleMap: mangleMap) { }
|
||||
}
|
||||
public class SceneResult(Dictionary<string, string?> mangleMap)
|
||||
: SceneResult<object?>(result: null, mangleMap: mangleMap);
|
||||
|
||||
public class SceneResult<TResult>
|
||||
public class SceneResult<TResult>(TResult result, Dictionary<string, string?> mangleMap)
|
||||
{
|
||||
public TResult Result { get; init; }
|
||||
public Dictionary<string, string?> MangleMap { get; init; }
|
||||
|
||||
public SceneResult(TResult result, Dictionary<string, string?> mangleMap)
|
||||
{
|
||||
Result = result;
|
||||
MangleMap = mangleMap;
|
||||
}
|
||||
public TResult Result { get; init; } = result;
|
||||
public Dictionary<string, string?> MangleMap { get; init; } = mangleMap;
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SceneResult<object?>(result: result, mangleMap: v.MangleMap);
|
||||
}
|
||||
return result is null
|
||||
? new SceneResult<object?>(result: null, mangleMap: v.MangleMap)
|
||||
: new SceneResult<object?>(result: result, mangleMap: v.MangleMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Settings\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\Infrastructure.EntityFramework\Infrastructure.EntityFramework.csproj" />
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Request;
|
||||
using Bit.SeederApi.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.SeederApi.Controllers;
|
||||
|
||||
[Route("query")]
|
||||
public class QueryController(ILogger<QueryController> logger, IQueryService queryService)
|
||||
: Controller
|
||||
public class QueryController(ILogger<QueryController> logger, IQueryService queryService) : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Query([FromBody] QueryRequestModel request)
|
||||
@@ -26,11 +25,7 @@ public class QueryController(ILogger<QueryController> logger, IQueryService quer
|
||||
catch (SceneExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Bit.SeederApi.Models.Requests;
|
||||
using Bit.SeederApi.Models.Request;
|
||||
using Bit.SeederApi.Models.Response;
|
||||
using Bit.SeederApi.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -6,18 +6,20 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace Bit.SeederApi.Controllers;
|
||||
|
||||
[Route("seed")]
|
||||
public class SeedController(ILogger<SeedController> logger, ISceneService sceneService, IServiceProvider serviceProvider)
|
||||
: Controller
|
||||
public class SeedController(
|
||||
ILogger<SeedController> logger,
|
||||
ISceneService sceneService,
|
||||
IServiceProvider serviceProvider) : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Seed([FromBody] SeedRequestModel request)
|
||||
public async Task<IActionResult> SeedAsync([FromBody] SeedRequestModel request)
|
||||
{
|
||||
logger.LogInformation("Received seed request {Provider}", serviceProvider.GetType().FullName);
|
||||
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
||||
|
||||
try
|
||||
{
|
||||
SceneResponseModel response = await sceneService.ExecuteScene(request.Template, request.Arguments);
|
||||
var response = await sceneService.ExecuteScene(request.Template, request.Arguments);
|
||||
|
||||
return Json(response);
|
||||
}
|
||||
@@ -28,16 +30,12 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
catch (SceneExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing scene: {Template}", request.Template);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("batch")]
|
||||
public async Task<IActionResult> DeleteBatch([FromBody] List<string> playIds)
|
||||
public async Task<IActionResult> DeleteBatchAsync([FromBody] List<string> playIds)
|
||||
{
|
||||
logger.LogInformation("Deleting batch of seeded data with IDs: {PlayIds}", string.Join(", ", playIds));
|
||||
|
||||
@@ -67,14 +65,12 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||
});
|
||||
}
|
||||
return Ok(new
|
||||
{
|
||||
Message = "Batch delete completed successfully"
|
||||
});
|
||||
|
||||
return Ok(new { Message = "Batch delete completed successfully" });
|
||||
}
|
||||
|
||||
[HttpDelete("{playId}")]
|
||||
public async Task<IActionResult> Delete([FromRoute] string playId)
|
||||
public async Task<IActionResult> DeleteAsync([FromRoute] string playId)
|
||||
{
|
||||
logger.LogInformation("Deleting seeded data with ID: {PlayId}", playId);
|
||||
|
||||
@@ -87,17 +83,13 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
catch (SceneExecutionException ex)
|
||||
{
|
||||
logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId);
|
||||
return BadRequest(new
|
||||
{
|
||||
Error = ex.Message,
|
||||
Details = ex.InnerException?.Message
|
||||
});
|
||||
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteAll()
|
||||
public async Task<IActionResult> DeleteAllAsync()
|
||||
{
|
||||
logger.LogInformation("Deleting all seeded data");
|
||||
|
||||
@@ -128,6 +120,7 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
||||
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Bit.SeederApi.Extensions;
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Dynamically registers all scene types that implement IScene<TRequest> from the Seeder assembly.
|
||||
/// Dynamically registers all scene types that implement IScene<TRequest> from the Seeder assembly.
|
||||
/// Scenes are registered as keyed scoped services using their class name as the key.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddScenes(this IServiceCollection services)
|
||||
@@ -32,7 +32,7 @@ public static class ServiceCollectionExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically registers all query types that implement IQuery<TRequest> from the Seeder assembly.
|
||||
/// Dynamically registers all query types that implement IQuery<TRequest> from the Seeder assembly.
|
||||
/// Queries are registered as keyed scoped services using their class name as the key.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddQueries(this IServiceCollection services)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.SeederApi.Models.Requests;
|
||||
namespace Bit.SeederApi.Models.Request;
|
||||
|
||||
public class QueryRequestModel
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.SeederApi.Models.Requests;
|
||||
namespace Bit.SeederApi.Models.Request;
|
||||
|
||||
public class SeedRequestModel
|
||||
{
|
||||
|
||||
@@ -41,6 +41,3 @@ app.UseRouting();
|
||||
app.MapControllerRoute(name: "default", pattern: "{controller=Seed}/{action=Index}/{id?}");
|
||||
|
||||
app.Run();
|
||||
|
||||
// Make Program class accessible for integration tests
|
||||
public partial class Program { }
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
namespace Bit.SeederApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for executing query operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The query service provides a mechanism to execute read-only query operations by name with optional JSON arguments.
|
||||
/// Queries retrieve existing data from the system without modifying state or tracking entities.
|
||||
/// </remarks>
|
||||
public interface IQueryService
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,14 @@ using Bit.SeederApi.Models.Response;
|
||||
|
||||
namespace Bit.SeederApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for executing and managing scene operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The scene service provides a mechanism to execute scene operations by name with optional JSON arguments.
|
||||
/// Scenes create and configure test data, track entities for cleanup, and support destruction of seeded data.
|
||||
/// Each scene execution can be assigned a play ID for tracking and subsequent cleanup operations.
|
||||
/// </remarks>
|
||||
public interface ISceneService
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,5 +30,10 @@ public interface ISceneService
|
||||
/// <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(string playId);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all play IDs for currently tracked seeded data.
|
||||
/// </summary>
|
||||
/// <returns>A list of play IDs representing active seeded data that can be destroyed.</returns>
|
||||
List<string> GetAllPlayIds();
|
||||
}
|
||||
|
||||
@@ -35,67 +35,6 @@ public class SceneService(
|
||||
return SceneResponseModel.FromSceneResult(result);
|
||||
}
|
||||
|
||||
public object ExecuteQuery(string queryName, JsonElement? arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = serviceProvider.GetKeyedService<IQuery>(queryName)
|
||||
?? throw new SceneNotFoundException(queryName);
|
||||
|
||||
var requestType = query.GetRequestType();
|
||||
|
||||
// Deserialize the arguments into the request model
|
||||
object? requestModel;
|
||||
if (arguments == null)
|
||||
{
|
||||
// Try to create an instance with default values
|
||||
try
|
||||
{
|
||||
requestModel = Activator.CreateInstance(requestType);
|
||||
if (requestModel == null)
|
||||
{
|
||||
throw new SceneExecutionException(
|
||||
$"Arguments are required for query '{queryName}'");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new SceneExecutionException(
|
||||
$"Arguments are required for query '{queryName}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
requestModel = JsonSerializer.Deserialize(arguments.Value.GetRawText(), requestType, _jsonOptions);
|
||||
if (requestModel == null)
|
||||
{
|
||||
throw new SceneExecutionException(
|
||||
$"Failed to deserialize request model for query '{queryName}'");
|
||||
}
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new SceneExecutionException(
|
||||
$"Failed to deserialize request model for query '{queryName}': {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var result = query.Execute(requestModel);
|
||||
|
||||
logger.LogInformation("Successfully executed query: {QueryName}", queryName);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex) when (ex is not SceneNotFoundException and not SceneExecutionException)
|
||||
{
|
||||
logger.LogError(ex, "Unexpected error executing query: {QueryName}", queryName);
|
||||
throw new SceneExecutionException(
|
||||
$"An unexpected error occurred while executing query '{queryName}'",
|
||||
ex.InnerException ?? ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<object?> DestroyScene(string playId)
|
||||
{
|
||||
// Note, delete cascade will remove PlayData entries
|
||||
|
||||
Reference in New Issue
Block a user