mirror of
https://github.com/bitwarden/server
synced 2025-12-13 06:43:45 +00:00
Add documentation
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Bit.SeederApi.Models.Requests;
|
using Bit.SeederApi.Models.Request;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.SeederApi.IntegrationTest;
|
namespace Bit.SeederApi.IntegrationTest;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Bit.SeederApi.Models.Requests;
|
using Bit.SeederApi.Models.Request;
|
||||||
using Bit.SeederApi.Models.Response;
|
using Bit.SeederApi.Models.Response;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,19 @@
|
|||||||
namespace Bit.Seeder;
|
namespace Bit.Seeder;
|
||||||
|
|
||||||
public class SceneResult : SceneResult<object?>
|
public class SceneResult(Dictionary<string, string?> mangleMap)
|
||||||
{
|
: SceneResult<object?>(result: null, mangleMap: mangleMap);
|
||||||
public SceneResult(Dictionary<string, string?> mangleMap)
|
|
||||||
: base(result: null, mangleMap: mangleMap) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SceneResult<TResult>
|
public class SceneResult<TResult>(TResult result, Dictionary<string, string?> mangleMap)
|
||||||
{
|
{
|
||||||
public TResult Result { get; init; }
|
public TResult Result { get; init; } = result;
|
||||||
public Dictionary<string, string?> MangleMap { get; init; }
|
public Dictionary<string, string?> MangleMap { get; init; } = mangleMap;
|
||||||
|
|
||||||
public SceneResult(TResult result, Dictionary<string, string?> mangleMap)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
MangleMap = mangleMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static explicit operator SceneResult<object?>(SceneResult<TResult> v)
|
public static explicit operator SceneResult<object?>(SceneResult<TResult> v)
|
||||||
{
|
{
|
||||||
var result = v.Result;
|
var result = v.Result;
|
||||||
|
|
||||||
if (result is null)
|
return result is null
|
||||||
{
|
? new SceneResult<object?>(result: null, mangleMap: v.MangleMap)
|
||||||
return new SceneResult<object?>(result: null, mangleMap: v.MangleMap);
|
: new SceneResult<object?>(result: result, mangleMap: v.MangleMap);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new SceneResult<object?>(result: result, mangleMap: v.MangleMap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,6 @@
|
|||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Settings\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Infrastructure.EntityFramework\Infrastructure.EntityFramework.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 Bit.SeederApi.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Bit.SeederApi.Controllers;
|
namespace Bit.SeederApi.Controllers;
|
||||||
|
|
||||||
[Route("query")]
|
[Route("query")]
|
||||||
public class QueryController(ILogger<QueryController> logger, IQueryService queryService)
|
public class QueryController(ILogger<QueryController> logger, IQueryService queryService) : Controller
|
||||||
: Controller
|
|
||||||
{
|
{
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Query([FromBody] QueryRequestModel request)
|
public IActionResult Query([FromBody] QueryRequestModel request)
|
||||||
@@ -26,11 +25,7 @@ public class QueryController(ILogger<QueryController> logger, IQueryService quer
|
|||||||
catch (SceneExecutionException ex)
|
catch (SceneExecutionException ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
logger.LogError(ex, "Error executing query: {Query}", request.Template);
|
||||||
return BadRequest(new
|
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||||
{
|
|
||||||
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.Models.Response;
|
||||||
using Bit.SeederApi.Services;
|
using Bit.SeederApi.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -6,18 +6,20 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
namespace Bit.SeederApi.Controllers;
|
namespace Bit.SeederApi.Controllers;
|
||||||
|
|
||||||
[Route("seed")]
|
[Route("seed")]
|
||||||
public class SeedController(ILogger<SeedController> logger, ISceneService sceneService, IServiceProvider serviceProvider)
|
public class SeedController(
|
||||||
: Controller
|
ILogger<SeedController> logger,
|
||||||
|
ISceneService sceneService,
|
||||||
|
IServiceProvider serviceProvider) : Controller
|
||||||
{
|
{
|
||||||
[HttpPost]
|
[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("Received seed request {Provider}", serviceProvider.GetType().FullName);
|
||||||
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
logger.LogInformation("Seeding with template: {Template}", request.Template);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SceneResponseModel response = await sceneService.ExecuteScene(request.Template, request.Arguments);
|
var response = await sceneService.ExecuteScene(request.Template, request.Arguments);
|
||||||
|
|
||||||
return Json(response);
|
return Json(response);
|
||||||
}
|
}
|
||||||
@@ -28,16 +30,12 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
|||||||
catch (SceneExecutionException ex)
|
catch (SceneExecutionException ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error executing scene: {Template}", request.Template);
|
logger.LogError(ex, "Error executing scene: {Template}", request.Template);
|
||||||
return BadRequest(new
|
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||||
{
|
|
||||||
Error = ex.Message,
|
|
||||||
Details = ex.InnerException?.Message
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("batch")]
|
[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));
|
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()
|
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Ok(new
|
|
||||||
{
|
return Ok(new { Message = "Batch delete completed successfully" });
|
||||||
Message = "Batch delete completed successfully"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{playId}")]
|
[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);
|
logger.LogInformation("Deleting seeded data with ID: {PlayId}", playId);
|
||||||
|
|
||||||
@@ -87,17 +83,13 @@ public class SeedController(ILogger<SeedController> logger, ISceneService sceneS
|
|||||||
catch (SceneExecutionException ex)
|
catch (SceneExecutionException ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId);
|
logger.LogError(ex, "Error deleting seeded data: {PlayId}", playId);
|
||||||
return BadRequest(new
|
return BadRequest(new { Error = ex.Message, Details = ex.InnerException?.Message });
|
||||||
{
|
|
||||||
Error = ex.Message,
|
|
||||||
Details = ex.InnerException?.Message
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<IActionResult> DeleteAll()
|
public async Task<IActionResult> DeleteAllAsync()
|
||||||
{
|
{
|
||||||
logger.LogInformation("Deleting all seeded data");
|
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()
|
Details = aggregateException.InnerExceptions.Select(e => e.Message).ToList()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Bit.SeederApi.Extensions;
|
|||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.
|
/// Scenes are registered as keyed scoped services using their class name as the key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection AddScenes(this IServiceCollection services)
|
public static IServiceCollection AddScenes(this IServiceCollection services)
|
||||||
@@ -32,7 +32,7 @@ public static class ServiceCollectionExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Queries are registered as keyed scoped services using their class name as the key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection AddQueries(this IServiceCollection services)
|
public static IServiceCollection AddQueries(this IServiceCollection services)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Bit.SeederApi.Models.Requests;
|
namespace Bit.SeederApi.Models.Request;
|
||||||
|
|
||||||
public class QueryRequestModel
|
public class QueryRequestModel
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Bit.SeederApi.Models.Requests;
|
namespace Bit.SeederApi.Models.Request;
|
||||||
|
|
||||||
public class SeedRequestModel
|
public class SeedRequestModel
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,6 +41,3 @@ app.UseRouting();
|
|||||||
app.MapControllerRoute(name: "default", pattern: "{controller=Seed}/{action=Index}/{id?}");
|
app.MapControllerRoute(name: "default", pattern: "{controller=Seed}/{action=Index}/{id?}");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
// Make Program class accessible for integration tests
|
|
||||||
public partial class Program { }
|
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
namespace Bit.SeederApi.Services;
|
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
|
public interface IQueryService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ using Bit.SeederApi.Models.Response;
|
|||||||
|
|
||||||
namespace Bit.SeederApi.Services;
|
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
|
public interface ISceneService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -22,5 +30,10 @@ public interface ISceneService
|
|||||||
/// <returns>The result of the destroy operation</returns>
|
/// <returns>The result of the destroy operation</returns>
|
||||||
/// <exception cref="SceneExecutionException">Thrown when there's an error destroying the seeded data</exception>
|
/// <exception cref="SceneExecutionException">Thrown when there's an error destroying the seeded data</exception>
|
||||||
Task<object?> DestroyScene(string playId);
|
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();
|
List<string> GetAllPlayIds();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,67 +35,6 @@ public class SceneService(
|
|||||||
return SceneResponseModel.FromSceneResult(result);
|
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)
|
public async Task<object?> DestroyScene(string playId)
|
||||||
{
|
{
|
||||||
// Note, delete cascade will remove PlayData entries
|
// Note, delete cascade will remove PlayData entries
|
||||||
|
|||||||
Reference in New Issue
Block a user