1
0
mirror of https://github.com/bitwarden/server synced 2025-12-13 14:53:34 +00:00

Add documentation

This commit is contained in:
Hinton
2025-11-13 13:14:43 +01:00
parent 840307fe4a
commit dff45c137d
13 changed files with 52 additions and 126 deletions

View File

@@ -1,5 +1,5 @@
using System.Net;
using Bit.SeederApi.Models.Requests;
using Bit.SeederApi.Models.Request;
using Xunit;
namespace Bit.SeederApi.IntegrationTest;

View File

@@ -1,5 +1,5 @@
using System.Net;
using Bit.SeederApi.Models.Requests;
using Bit.SeederApi.Models.Request;
using Bit.SeederApi.Models.Response;
using Xunit;

View File

@@ -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);
}
}

View File

@@ -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" />

View File

@@ -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 });
}
}
}

View File

@@ -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();
}
}

View File

@@ -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&lt;TRequest&gt; 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&lt;TRequest&gt; 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)

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -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 { }

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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