Compare commits
104 Commits
29bc0cf8b5
...
bugfix/raw
| Author | SHA1 | Date | |
|---|---|---|---|
| 761fd208e5 | |||
| d149cbea3a | |||
| bb2dd4d63b | |||
| 4bde1d090f | |||
| 6681e56afc | |||
| d61f5ce885 | |||
| 6374a5c257 | |||
| 3b4954d387 | |||
| 42d586604e | |||
| 4088a52196 | |||
| 58b3c8ec95 | |||
| 68b3eb53c0 | |||
| 0d9489203f | |||
| 0a564d8aa8 | |||
| f5b2db0296 | |||
| 7a22024624 | |||
| c9cd92a55a | |||
| 93adaba322 | |||
| c16cb2a1c4 | |||
| c20162e051 | |||
| 70c2f7342d | |||
| a10f917084 | |||
| e1c3f74cd4 | |||
| e45aeea2b9 | |||
| 38f91aae84 | |||
| 9bb5c97df6 | |||
| d8c7499436 | |||
| 6d86760354 | |||
| 6b1daf77cb | |||
| d3d5ebac61 | |||
| b1924f2a4a | |||
| c27ed1e744 | |||
| 9eb54565cb | |||
| 05dfb6f424 | |||
| cf6c90ad05 | |||
| 4a9c4341c2 | |||
| ead12b6095 | |||
| 3c7fcb71c0 | |||
| 0b01b4a658 | |||
| 8d511ec81a | |||
| 685c5abca7 | |||
| b7aea848a9 | |||
| e5eb0f19e7 | |||
| 859ff5902e | |||
| 42789567f0 | |||
| 46eef255ca | |||
| aae42949b6 | |||
| bdf273c8e1 | |||
| ba8ab28b03 | |||
| 4cc8d22756 | |||
| 2a4378eb9a | |||
| cb5bbfb722 | |||
| 2736a78d4f | |||
| ddb8b2673e | |||
| a70aee6e28 | |||
| f329543793 | |||
| 645891150c | |||
| 95cb34394c | |||
| 83d6832236 | |||
| e816340755 | |||
| 64e8e2a5cc | |||
| 0edf2626a7 | |||
| 1d16276a8a | |||
| 4eae092031 | |||
| ce7fe03525 | |||
| a93780df5c | |||
| d7a2a01421 | |||
| 329e441ede | |||
| 1ad7ff3b34 | |||
| bcfbd851bd | |||
| 2e157656a7 | |||
| 8042a6f898 | |||
| f25fc627fe | |||
| d6af24cd91 | |||
| bb5eac023c | |||
| 77baf395ce | |||
| 6c9eab6df6 | |||
| c64794755d | |||
| de2185bf0a | |||
| fde9735b27 | |||
| 0342b9e0c6 | |||
| 47698b9046 | |||
| a03d21ebc6 | |||
| acff0aca89 | |||
| ce0e53baf6 | |||
| 620c0eff22 | |||
| 68f4486fa1 | |||
| 2b5e63cb45 | |||
| e9e697fa0d | |||
| 606eccb855 | |||
| 3146acfa45 | |||
| f363872e7a | |||
| ed4683323d | |||
| 4aeef10ef7 | |||
| e04e90d8c6 | |||
| 93b5f976d3 | |||
| b66a49f74d | |||
| 70dc52139d | |||
| 210ed9be8d | |||
| b2544b64e3 | |||
| 0b1e0d25ca | |||
| c1027abfc6 | |||
| 40c8fa359c | |||
| 1375f5f0e4 |
@@ -14,13 +14,18 @@ public class RecActionController(IMediator mediator) : ControllerBase
|
||||
/// Invokes a batch of RecActions for a given profile.
|
||||
/// </summary>
|
||||
/// <param name="profileId">The identifier of the profile whose RecActions should be invoked.</param>
|
||||
/// <param name="references">Optional reference values that are passed through to all result records.</param>
|
||||
/// <param name="cancel">A token to cancel the operation.</param>
|
||||
/// <returns>An HTTP 202 Accepted response indicating the process has been started.</returns>
|
||||
[HttpPost("invoke/{profileId}")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
public async Task<IActionResult> Invoke([FromRoute] long profileId, CancellationToken cancel)
|
||||
public async Task<IActionResult> Invoke([FromRoute] long profileId, [FromBody] InvokeReferences references, CancellationToken cancel = default)
|
||||
{
|
||||
await mediator.Send(new InvokeBatchRecActionViewsCommand { ProfileId = profileId }, cancel);
|
||||
await mediator.Send(new InvokeBatchRecActionViewsCommand
|
||||
{
|
||||
ProfileId = profileId,
|
||||
References = references
|
||||
}, cancel);
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace ReC.API.Models;
|
||||
|
||||
public class FakeRequest
|
||||
{
|
||||
public Dictionary<string, object>? Body { get; init; }
|
||||
|
||||
public Dictionary<string, object>? Header { get; init; }
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace ReC.API.Models;
|
||||
|
||||
public enum ResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the full result object.
|
||||
/// </summary>
|
||||
Full,
|
||||
/// <summary>
|
||||
/// Return only the header part of the result.
|
||||
/// </summary>
|
||||
OnlyHeader,
|
||||
/// <summary>
|
||||
/// Return only the body part of the result.
|
||||
/// </summary>
|
||||
OnlyBody
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<ProjectGuid>420218ad-3c27-4003-9a84-36c92352f175</ProjectGuid>
|
||||
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\ReC\API\$(Version)\Rec.API.zip</DesktopBuildPackageLocation>
|
||||
<DesktopBuildPackageLocation>M:\App&Service\0 DD - Smart UP\ReC\API\$(Version)\Rec.API.zip</DesktopBuildPackageLocation>
|
||||
<PackageAsSingleFile>true</PackageAsSingleFile>
|
||||
<DeployIisAppPath>Rec.API</DeployIisAppPath>
|
||||
<_TargetId>IISWebDeployPackage</_TargetId>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<Product>ReC.API</Product>
|
||||
<PackageIcon>Assets\icon.ico</PackageIcon>
|
||||
<PackageTags>digital data rest-caller rec api</PackageTags>
|
||||
<Version>2.0.2-beta</Version>
|
||||
<AssemblyVersion>2.0.2.0</AssemblyVersion>
|
||||
<FileVersion>2.0.2.0</FileVersion>
|
||||
<InformationalVersion>2.0.1-beta</InformationalVersion>
|
||||
<Version>2.2.1-beta</Version>
|
||||
<AssemblyVersion>2.2.1.0</AssemblyVersion>
|
||||
<FileVersion>2.2.1.0</FileVersion>
|
||||
<InformationalVersion>2.2.0-beta</InformationalVersion>
|
||||
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
|
||||
@@ -1,23 +1,54 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Dto;
|
||||
using ReC.Application.Common.Interfaces;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ReC.Application.Common.Dto;
|
||||
using ReC.Application.Common.Exceptions;
|
||||
using ReC.Application.Common.Interfaces;
|
||||
|
||||
namespace ReC.Application.Common.Behaviors.Action;
|
||||
|
||||
public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : RecActionViewDto
|
||||
where TResponse : notnull
|
||||
where TRequest : notnull
|
||||
where TResponse : IEnumerable<RecActionViewDto>
|
||||
{
|
||||
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
|
||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
|
||||
{
|
||||
if (action.BodyQuery is null)
|
||||
return await next(cancel);
|
||||
var actions = await next(cancel);
|
||||
|
||||
var result = await dbContext.BodyQueryResults.FromSqlRaw(action.BodyQuery).SingleOrDefaultAsync(cancel);
|
||||
foreach (var action in actions)
|
||||
await SetBody(action, cancel);
|
||||
|
||||
action.Body = result?.RawBody;
|
||||
return actions;
|
||||
}
|
||||
|
||||
return await next(cancel);
|
||||
private async Task SetBody(RecActionViewDto action, CancellationToken cancel)
|
||||
{
|
||||
if (action.BodyQuery is not string bodyQuery)
|
||||
return;
|
||||
|
||||
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
|
||||
command.CommandText = bodyQuery;
|
||||
|
||||
await dbContext.Database.OpenConnectionAsync(cancel);
|
||||
try
|
||||
{
|
||||
object? scalar;
|
||||
try
|
||||
{
|
||||
scalar = await command.ExecuteScalarAsync(cancel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DataIntegrityException(
|
||||
$"Body query execution failed. The stored SQL may be malformed. ActionId: {action.Id}, ProfileId: {action.ProfileId}, Error: {ex.Message}");
|
||||
}
|
||||
|
||||
action.Body = scalar as string
|
||||
?? throw new DataIntegrityException(
|
||||
$"Body query returned no result or a null value. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await dbContext.Database.CloseConnectionAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,61 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReC.Application.Common.Dto;
|
||||
using ReC.Application.Common.Exceptions;
|
||||
using ReC.Application.Common.Interfaces;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ReC.Application.Common.Behaviors.Action;
|
||||
|
||||
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : RecActionViewDto
|
||||
where TResponse : notnull
|
||||
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
where TResponse : IEnumerable<RecActionViewDto>
|
||||
{
|
||||
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
|
||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
|
||||
{
|
||||
if (action.HeaderQuery is null)
|
||||
return await next(cancel);
|
||||
var actions = await next(cancel);
|
||||
|
||||
var result = await dbContext.HeaderQueryResults.FromSqlRaw(action.HeaderQuery).SingleOrDefaultAsync(cancel);
|
||||
foreach (var action in actions)
|
||||
await SetHeader(action, cancel);
|
||||
|
||||
if (result?.RawHeader is null)
|
||||
return actions;
|
||||
}
|
||||
|
||||
private async Task SetHeader(RecActionViewDto action, CancellationToken cancel)
|
||||
{
|
||||
if (action.HeaderQuery is not string headerQuery)
|
||||
return;
|
||||
|
||||
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
|
||||
command.CommandText = headerQuery;
|
||||
|
||||
await dbContext.Database.OpenConnectionAsync(cancel);
|
||||
try
|
||||
{
|
||||
logger?.LogWarning("Header query did not return a result or returned a null REQUEST_HEADER. Profile ID: {ProfileId}, Action ID: {Id}", action.ProfileId, action.Id);
|
||||
object? scalar;
|
||||
try
|
||||
{
|
||||
scalar = await command.ExecuteScalarAsync(cancel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DataIntegrityException(
|
||||
$"Header query execution failed. The stored SQL may be malformed. ActionId: {action.Id}, ProfileId: {action.ProfileId}, Error: {ex.Message}");
|
||||
}
|
||||
|
||||
return await next(cancel);
|
||||
if (scalar is not string rawHeader)
|
||||
throw new DataIntegrityException(
|
||||
$"Header query returned no result or a null value. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
|
||||
|
||||
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(rawHeader)
|
||||
?? throw new DataIntegrityException(
|
||||
$"Header query returned invalid JSON. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
|
||||
|
||||
action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString());
|
||||
}
|
||||
|
||||
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result.RawHeader);
|
||||
|
||||
if(headerDict is null)
|
||||
finally
|
||||
{
|
||||
logger?.LogWarning(
|
||||
"Header JSON deserialization returned null. RawHeader: {RawHeader}, ProfileId: {ProfileId}, Id: {Id}",
|
||||
result.RawHeader, action.ProfileId, action.Id);
|
||||
|
||||
return await next(cancel);
|
||||
await dbContext.Database.CloseConnectionAsync();
|
||||
}
|
||||
|
||||
action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString());
|
||||
|
||||
return await next(cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,11 @@ public class PostprocessingBehavior(IRecDbContext context, ISender sender) : IPi
|
||||
|
||||
await sender.Send(new InsertResultCommand()
|
||||
{
|
||||
Status = RecStatus.OK,
|
||||
ActionId = request.Action.Id,
|
||||
Info = info,
|
||||
Type = ResultType.Post
|
||||
InfoDetail = info,
|
||||
Type = ResultType.Post,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
@@ -35,9 +37,11 @@ public class PostprocessingBehavior(IRecDbContext context, ISender sender) : IPi
|
||||
|
||||
await sender.Send(new InsertResultCommand()
|
||||
{
|
||||
Status = RecStatus.Error,
|
||||
ActionId = request.Action.Id,
|
||||
Error = error,
|
||||
Type = ResultType.Post
|
||||
Type = ResultType.Post,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
|
||||
if (request.Action.ErrorAction == ErrorAction.Stop)
|
||||
|
||||
@@ -20,9 +20,11 @@ public class PreprocessingBehavior(IRecDbContext context, ISender sender) : IPip
|
||||
|
||||
await sender.Send(new InsertResultCommand()
|
||||
{
|
||||
Status = RecStatus.OK,
|
||||
ActionId = request.Action.Id,
|
||||
Info = JsonSerializer.Serialize(result),
|
||||
Type = ResultType.Pre
|
||||
InfoDetail = JsonSerializer.Serialize(result),
|
||||
Type = ResultType.Pre,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
@@ -30,9 +32,11 @@ public class PreprocessingBehavior(IRecDbContext context, ISender sender) : IPip
|
||||
{
|
||||
await sender.Send(new InsertResultCommand()
|
||||
{
|
||||
Status = RecStatus.Error,
|
||||
ActionId = request.Action.Id,
|
||||
Error = ex.ToString(),
|
||||
Type = ResultType.Pre
|
||||
Type = ResultType.Pre,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
|
||||
if (request.Action.ErrorAction == ErrorAction.Stop)
|
||||
|
||||
@@ -8,9 +8,9 @@ public class DtoMappingProfile : AutoMapper.Profile
|
||||
{
|
||||
CreateMap<RecActionView, RecActionViewDto>()
|
||||
.ForMember(dest => dest.PreprocessingQuery, opt => opt.MapFrom((src, _) =>
|
||||
src.PreprocessingQuery?.ReplacePlaceholders(src, src.Profile)))
|
||||
src.PreprocessingQuery?.ReplacePlaceholders(src)))
|
||||
.ForMember(dest => dest.PostprocessingQuery, opt => opt.MapFrom((src, _) =>
|
||||
src.PostprocessingQuery?.ReplacePlaceholders(src, src.Profile)));
|
||||
src.PostprocessingQuery?.ReplacePlaceholders(src)));
|
||||
|
||||
CreateMap<ResultView, ResultViewDto>();
|
||||
CreateMap<ProfileView, ProfileViewDto>();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using ReC.Application.Common.Exceptions;
|
||||
|
||||
namespace ReC.Application.Common.Dto;
|
||||
|
||||
@@ -14,10 +13,8 @@ public static partial class PlaceholderExtensions
|
||||
/// Replaces placeholders in the format <c>{#ANY_STRING#COLUMN_NAME}</c> with the corresponding
|
||||
/// property value resolved via <see cref="GetValueByColumnName{T}"/> from the provided objects.
|
||||
/// Values are converted to SQL-compatible string representations.
|
||||
/// If a placeholder cannot be resolved, it is replaced with <c>NULL</c>.
|
||||
/// </summary>
|
||||
/// <exception cref="PlaceholderResolutionException">
|
||||
/// Thrown when a placeholder's column name cannot be resolved from any of the provided objects.
|
||||
/// </exception>
|
||||
public static string ReplacePlaceholders(this string str, params object?[] objects)
|
||||
{
|
||||
return PlaceholderRegex().Replace(str, match =>
|
||||
@@ -37,7 +34,7 @@ public static partial class PlaceholderExtensions
|
||||
return ToSqlLiteral(value);
|
||||
}
|
||||
|
||||
throw new PlaceholderResolutionException(placeholder, columnName, str);
|
||||
return "NULL";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace ReC.Application.Common.Dto;
|
||||
using ReC.Domain.Constants;
|
||||
|
||||
namespace ReC.Application.Common.Dto;
|
||||
|
||||
public record ResultViewDto
|
||||
{
|
||||
@@ -14,7 +16,7 @@ public record ResultViewDto
|
||||
|
||||
public string? ProfileName { get; init; }
|
||||
|
||||
public short? StatusCode { get; init; }
|
||||
public RecStatus Status { get; set; }
|
||||
|
||||
public string? StatusName { get; init; }
|
||||
|
||||
@@ -22,6 +24,14 @@ public record ResultViewDto
|
||||
|
||||
public string? Body { get; init; }
|
||||
|
||||
public string? Info { get; set; }
|
||||
|
||||
public string? InfoDetail { get; set; }
|
||||
|
||||
public string? Error { get; set; }
|
||||
|
||||
public string? BatchId { get; set; }
|
||||
|
||||
public string? AddedWho { get; init; }
|
||||
|
||||
public DateTime? AddedWhen { get; init; }
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace ReC.Application.Common.Exceptions;
|
||||
|
||||
public class PlaceholderResolutionException(string placeholder, string columnName, string input)
|
||||
: Exception($"Failed to resolve placeholder '{placeholder}'. No object contains a property with column name '{columnName}'. Input: '{input}'")
|
||||
{
|
||||
public string Placeholder { get; } = placeholder;
|
||||
|
||||
public string ColumnName { get; } = columnName;
|
||||
|
||||
public string Input { get; } = input;
|
||||
}
|
||||
@@ -11,9 +11,9 @@ namespace ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
public record DeleteObjectProcedure : IRequest<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Target entity: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT
|
||||
/// Target entity for the delete operation.
|
||||
/// </summary>
|
||||
public string Entity { get; set; } = null!;
|
||||
public required EntityType Entity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Start GUID/ID (inclusive)
|
||||
@@ -35,25 +35,16 @@ public class DeleteObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
|
||||
{
|
||||
public async Task<int> Handle(DeleteObjectProcedure request, CancellationToken cancel)
|
||||
{
|
||||
var parameters = new[]
|
||||
{
|
||||
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
|
||||
new SqlParameter("@pSTART", request.Start.ToString()),
|
||||
new SqlParameter("@pEND", request.End.ToString()),
|
||||
new SqlParameter("@pFORCE", (object?)request.Force ?? DBNull.Value)
|
||||
};
|
||||
var sp = new StoredProcedureBuilder("[dbo].[PRREC_DELETE_OBJECT]", "RC")
|
||||
.Add("pENTITY", request.Entity)
|
||||
.Add("pSTART", request.Start.ToString())
|
||||
.Add("pEND", request.End.ToString())
|
||||
.Add("pFORCE", request.Force);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await repo.ExecuteQueryRawAsync(
|
||||
"DECLARE @RC SMALLINT = 0; " +
|
||||
"EXEC @RC = [dbo].[PRREC_DELETE_OBJECT] " +
|
||||
"@pENTITY, @pSTART, @pEND, @pFORCE; " +
|
||||
"SELECT @RC;",
|
||||
parameters,
|
||||
cancel);
|
||||
var result = await repo.ExecuteQueryRawAsync(sp.BuildSql(), sp.BuildParameters(), cancel);
|
||||
|
||||
// The stored procedure returns 0 on success, error codes > 0 on failure
|
||||
if (result > 0)
|
||||
{
|
||||
throw new DeleteObjectFailedException(request, $"DeleteObject stored procedure failed with error code: {result}");
|
||||
|
||||
51
src/ReC.Application/Common/Procedures/EntityType.cs
Normal file
51
src/ReC.Application/Common/Procedures/EntityType.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace ReC.Application.Common.Procedures;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the target entity type for stored procedure operations (Insert, Update, Delete).
|
||||
/// </summary>
|
||||
public enum EntityType
|
||||
{
|
||||
/// <summary>
|
||||
/// A scheduled or configured action within a profile that invokes an endpoint (maps to TBREC_CFG_ACTION).
|
||||
/// </summary>
|
||||
Action,
|
||||
|
||||
/// <summary>
|
||||
/// A REST endpoint URI configuration (maps to TBREC_CFG_ENDPOINT).
|
||||
/// </summary>
|
||||
Endpoint,
|
||||
|
||||
/// <summary>
|
||||
/// Authentication credentials for an endpoint such as API key, Bearer token, or NTLM (maps to TBREC_CFG_ENDPOINT_AUTH).
|
||||
/// </summary>
|
||||
EndpointAuth,
|
||||
|
||||
/// <summary>
|
||||
/// Key-value parameters attached to an endpoint (maps to TBREC_CFG_ENDPOINT_PARAMS).
|
||||
/// </summary>
|
||||
EndpointParams,
|
||||
|
||||
/// <summary>
|
||||
/// A profile that groups one or more actions and defines execution settings (maps to TBREC_CFG_PROFILE).
|
||||
/// </summary>
|
||||
Profile,
|
||||
|
||||
/// <summary>
|
||||
/// The outcome of an action invocation including HTTP status, headers, body, and error details (maps to TBREC_OUT_RESULT).
|
||||
/// </summary>
|
||||
Result
|
||||
}
|
||||
|
||||
public static class EntityTypeExtensions
|
||||
{
|
||||
public static string ToDbString(this EntityType entityType) => entityType switch
|
||||
{
|
||||
EntityType.Action => "ACTION",
|
||||
EntityType.Endpoint => "ENDPOINT",
|
||||
EntityType.EndpointAuth => "ENDPOINT_AUTH",
|
||||
EntityType.EndpointParams => "ENDPOINT_PARAMS",
|
||||
EntityType.Profile => "PROFILE",
|
||||
EntityType.Result => "RESULT",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(entityType), $"Not expected entity type value: {entityType}")
|
||||
};
|
||||
}
|
||||
@@ -11,15 +11,16 @@ using ReC.Application.Endpoints.Commands;
|
||||
using ReC.Application.Profile.Commands;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
using ReC.Application.Results.Commands;
|
||||
using System.Data;
|
||||
|
||||
namespace ReC.Application.Common.Procedures.InsertProcedure;
|
||||
|
||||
public record InsertObjectProcedure : IRequest<long>
|
||||
{
|
||||
/// <summary>
|
||||
/// Target entity: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT
|
||||
/// Target entity for the insert operation.
|
||||
/// </summary>
|
||||
public string Entity { get; set; } = null!;
|
||||
public required EntityType Entity { get; set; }
|
||||
|
||||
//TODO: update to set in authentication middleware or similar, and remove from procedure properties
|
||||
internal string? AddedWho { get; private set; } = "ReC.API";
|
||||
@@ -36,88 +37,69 @@ public class InsertObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
|
||||
{
|
||||
public async Task<long> Handle(InsertObjectProcedure request, CancellationToken cancel)
|
||||
{
|
||||
var parameters = new[]
|
||||
{
|
||||
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
|
||||
|
||||
new SqlParameter("@pADDED_WHO", (object?)request.AddedWho ?? DBNull.Value),
|
||||
new SqlParameter("@pADDED_WHEN", (object?)DateTime.UtcNow ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pACTION_PROFILE_ID", (object?)request.Action?.ProfileId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ACTIVE", (object?)request.Action?.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_SEQUENCE", (object?)request.Action?.Sequence ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ENDPOINT_ID", (object?)request.Action?.EndpointId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ENDPOINT_AUTH_ID", (object?)request.Action?.EndpointAuthId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ENDPOINT_PARAMS_ID", (object?)request.Action?.EndpointParamsId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_SQL_CONNECTION_ID", (object?)request.Action?.SqlConnectionId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_TYPE_ID", (object?)(byte?)request.Action?.TypeId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_PRE_SQL", (object?)request.Action?.PreSql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_HEADER_SQL", (object?)request.Action?.HeaderSql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_BODY_SQL", (object?)request.Action?.BodySql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_POST_SQL", (object?)request.Action?.PostSql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ERROR_ACTION_ID", (object?)request.Action?.ErrorActionId ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pENDPOINT_ACTIVE", (object?)request.Endpoint?.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_DESCRIPTION", (object?)request.Endpoint?.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_URI", (object?)request.Endpoint?.Uri ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pENDPOINT_AUTH_ACTIVE", (object?)request.EndpointAuth?.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_DESCRIPTION", (object?)request.EndpointAuth?.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_TYPE_ID", (object?)request.EndpointAuth?.TypeId ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_API_KEY", (object?)request.EndpointAuth?.ApiKey ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_API_VALUE", (object?)request.EndpointAuth?.ApiValue ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_API_KEY_ADD_TO_ID", (object?)request.EndpointAuth?.ApiKeyAddToId ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_TOKEN", (object?)request.EndpointAuth?.Token ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_USERNAME", (object?)request.EndpointAuth?.Username ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_PASSWORD", (object?)request.EndpointAuth?.Password ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_DOMAIN", (object?)request.EndpointAuth?.Domain ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_WORKSTATION", (object?)request.EndpointAuth?.Workstation ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pPROFILE_ACTIVE", (object?)request.Profile?.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_TYPE_ID", (object?)request.Profile?.TypeId ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_MANDANTOR", (object?)request.Profile?.Mandantor ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_NAME", (object?)request.Profile?.Name ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_DESCRIPTION", (object?)request.Profile?.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_LOG_LEVEL_ID", (object?)request.Profile?.LogLevelId ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_LANGUAGE_ID", (object?)request.Profile?.LanguageId ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pRESULT_ACTION_ID", (object?)request.Result?.ActionId ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_STATUS_ID", (object?)request.Result?.StatusId ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_HEADER", (object?)request.Result?.Header ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_BODY", (object?)request.Result?.Body ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_INFO", (object?)request.Result?.Info ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_ERROR", (object?)request.Result?.Error ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_TYPE_ID", (object?)(byte?)request.Result?.Type ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pENDPOINT_PARAMS_ACTIVE", (object?)request.EndpointParams?.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_DESCRIPTION", (object?)request.EndpointParams?.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_GROUP_ID", (object?)request.EndpointParams?.GroupId ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_SEQUENCE", (object?)request.EndpointParams?.Sequence ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_KEY", (object?)request.EndpointParams?.Key ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_VALUE", (object?)request.EndpointParams?.Value ?? DBNull.Value),
|
||||
|
||||
new SqlParameter
|
||||
{
|
||||
ParameterName = "@oGUID",
|
||||
SqlDbType = System.Data.SqlDbType.BigInt,
|
||||
Direction = System.Data.ParameterDirection.Output
|
||||
}
|
||||
};
|
||||
var sp = new StoredProcedureBuilder("[dbo].[PRREC_INSERT_OBJECT]")
|
||||
.Add("pENTITY", request.Entity)
|
||||
.Add("pADDED_WHO", request.AddedWho)
|
||||
.Add("pADDED_WHEN", DateTime.UtcNow)
|
||||
.Add("pACTION_PROFILE_ID", request.Action?.ProfileId)
|
||||
.Add("pACTION_ACTIVE", request.Action?.Active)
|
||||
.Add("pACTION_SEQUENCE", request.Action?.Sequence, SqlDbType.TinyInt)
|
||||
.Add("pACTION_ENDPOINT_ID", request.Action?.EndpointId)
|
||||
.Add("pACTION_ENDPOINT_AUTH_ID", request.Action?.EndpointAuthId)
|
||||
.Add("pACTION_ENDPOINT_PARAMS_ID", request.Action?.EndpointParamsId, SqlDbType.SmallInt)
|
||||
.Add("pACTION_SQL_CONNECTION_ID", request.Action?.SqlConnectionId, SqlDbType.SmallInt)
|
||||
.Add("pACTION_TYPE_ID", request.Action?.TypeId is not null ? (byte)request.Action.TypeId : null, SqlDbType.TinyInt)
|
||||
.Add("pACTION_PRE_SQL", request.Action?.PreSql)
|
||||
.Add("pACTION_HEADER_SQL", request.Action?.HeaderSql)
|
||||
.Add("pACTION_BODY_SQL", request.Action?.BodySql)
|
||||
.Add("pACTION_POST_SQL", request.Action?.PostSql)
|
||||
.Add("pACTION_ERROR_ACTION_ID", request.Action?.ErrorActionId, SqlDbType.TinyInt)
|
||||
.Add("pENDPOINT_ACTIVE", request.Endpoint?.Active)
|
||||
.Add("pENDPOINT_DESCRIPTION", request.Endpoint?.Description)
|
||||
.Add("pENDPOINT_URI", request.Endpoint?.Uri)
|
||||
.Add("pENDPOINT_AUTH_ACTIVE", request.EndpointAuth?.Active)
|
||||
.Add("pENDPOINT_AUTH_DESCRIPTION", request.EndpointAuth?.Description)
|
||||
.Add("pENDPOINT_AUTH_TYPE_ID", request.EndpointAuth?.TypeId, SqlDbType.TinyInt)
|
||||
.Add("pENDPOINT_AUTH_API_KEY", request.EndpointAuth?.ApiKey)
|
||||
.Add("pENDPOINT_AUTH_API_VALUE", request.EndpointAuth?.ApiValue)
|
||||
.Add("pENDPOINT_AUTH_API_KEY_ADD_TO_ID", request.EndpointAuth?.ApiKeyAddToId)
|
||||
.Add("pENDPOINT_AUTH_TOKEN", request.EndpointAuth?.Token)
|
||||
.Add("pENDPOINT_AUTH_USERNAME", request.EndpointAuth?.Username)
|
||||
.Add("pENDPOINT_AUTH_PASSWORD", request.EndpointAuth?.Password)
|
||||
.Add("pENDPOINT_AUTH_DOMAIN", request.EndpointAuth?.Domain)
|
||||
.Add("pENDPOINT_AUTH_WORKSTATION", request.EndpointAuth?.Workstation)
|
||||
.Add("pPROFILE_ACTIVE", request.Profile?.Active)
|
||||
.Add("pPROFILE_TYPE_ID", request.Profile?.TypeId, SqlDbType.TinyInt)
|
||||
.Add("pPROFILE_MANDANTOR", request.Profile?.Mandantor)
|
||||
.Add("pPROFILE_NAME", request.Profile?.Name)
|
||||
.Add("pPROFILE_DESCRIPTION", request.Profile?.Description)
|
||||
.Add("pPROFILE_LOG_LEVEL_ID", request.Profile?.LogLevelId, SqlDbType.TinyInt)
|
||||
.Add("pPROFILE_LANGUAGE_ID", request.Profile?.LanguageId, SqlDbType.SmallInt)
|
||||
.Add("pRESULT_ACTION_ID", request.Result?.ActionId)
|
||||
.Add("pRESULT_STATUS_ID", request.Result?.Status, SqlDbType.TinyInt)
|
||||
.Add("pRESULT_TYPE_ID", request.Result?.Type is not null ? (byte)request.Result.Type : null, SqlDbType.TinyInt)
|
||||
.Add("pRESULT_HEADER", request.Result?.Header)
|
||||
.Add("pRESULT_BODY", request.Result?.Body)
|
||||
.Add("pRESULT_INFO_ID", request.Result?.Info, SqlDbType.SmallInt)
|
||||
.Add("pRESULT_INFO_DETAIL", request.Result?.InfoDetail)
|
||||
.Add("pRESULT_ERROR", request.Result?.Error)
|
||||
.Add("pRESULT_BATCH_ID", request.Result?.References?.BatchId)
|
||||
.Add("pRESULT_REFERENCE1", request.Result?.References?.Reference1)
|
||||
.Add("pRESULT_REFERENCE2", request.Result?.References?.Reference2)
|
||||
.Add("pRESULT_REFERENCE3", request.Result?.References?.Reference3)
|
||||
.Add("pRESULT_REFERENCE4", request.Result?.References?.Reference4)
|
||||
.Add("pRESULT_REFERENCE5", request.Result?.References?.Reference5)
|
||||
.Add("pENDPOINT_PARAMS_ACTIVE", request.EndpointParams?.Active)
|
||||
.Add("pENDPOINT_PARAMS_DESCRIPTION", request.EndpointParams?.Description)
|
||||
.Add("pENDPOINT_PARAMS_GROUP_ID", request.EndpointParams?.GroupId, SqlDbType.SmallInt)
|
||||
.Add("pENDPOINT_PARAMS_SEQUENCE", request.EndpointParams?.Sequence, SqlDbType.TinyInt)
|
||||
.Add("pENDPOINT_PARAMS_KEY", request.EndpointParams?.Key)
|
||||
.Add("pENDPOINT_PARAMS_VALUE", request.EndpointParams?.Value)
|
||||
.AddOutput("oGUID", SqlDbType.BigInt);
|
||||
|
||||
try
|
||||
{
|
||||
await repo.ExecuteQueryRawAsync(
|
||||
"EXEC [dbo].[PRREC_INSERT_OBJECT] " +
|
||||
"@pENTITY, @pADDED_WHO, @pADDED_WHEN, " +
|
||||
"@pACTION_PROFILE_ID, @pACTION_ACTIVE, @pACTION_SEQUENCE, @pACTION_ENDPOINT_ID, @pACTION_ENDPOINT_AUTH_ID, @pACTION_ENDPOINT_PARAMS_ID, @pACTION_SQL_CONNECTION_ID, @pACTION_TYPE_ID, @pACTION_PRE_SQL, @pACTION_HEADER_SQL, @pACTION_BODY_SQL, @pACTION_POST_SQL, @pACTION_ERROR_ACTION_ID, " +
|
||||
"@pENDPOINT_ACTIVE, @pENDPOINT_DESCRIPTION, @pENDPOINT_URI, " +
|
||||
"@pENDPOINT_AUTH_ACTIVE, @pENDPOINT_AUTH_DESCRIPTION, @pENDPOINT_AUTH_TYPE_ID, @pENDPOINT_AUTH_API_KEY, @pENDPOINT_AUTH_API_VALUE, @pENDPOINT_AUTH_API_KEY_ADD_TO_ID, @pENDPOINT_AUTH_TOKEN, @pENDPOINT_AUTH_USERNAME, @pENDPOINT_AUTH_PASSWORD, @pENDPOINT_AUTH_DOMAIN, @pENDPOINT_AUTH_WORKSTATION, " +
|
||||
"@pPROFILE_ACTIVE, @pPROFILE_TYPE_ID, @pPROFILE_MANDANTOR, @pPROFILE_NAME, @pPROFILE_DESCRIPTION, @pPROFILE_LOG_LEVEL_ID, @pPROFILE_LANGUAGE_ID, " +
|
||||
"@pRESULT_ACTION_ID, @pRESULT_STATUS_ID, @pRESULT_HEADER, @pRESULT_BODY, @pRESULT_INFO, @pRESULT_ERROR, @pRESULT_TYPE_ID, " +
|
||||
"@pENDPOINT_PARAMS_ACTIVE, @pENDPOINT_PARAMS_DESCRIPTION, @pENDPOINT_PARAMS_GROUP_ID, @pENDPOINT_PARAMS_SEQUENCE, @pENDPOINT_PARAMS_KEY, @pENDPOINT_PARAMS_VALUE, " +
|
||||
"@oGUID OUTPUT",
|
||||
parameters,
|
||||
cancel);
|
||||
await repo.ExecuteQueryRawAsync(sp.BuildSql(), sp.BuildParameters(), cancel);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
@@ -127,12 +109,12 @@ public class InsertObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
|
||||
throw;
|
||||
}
|
||||
|
||||
var guidParam = parameters.Last();
|
||||
var guidParam = sp.GetParameter("oGUID");
|
||||
|
||||
if (guidParam.Value != DBNull.Value)
|
||||
if (guidParam.Value is long longValue)
|
||||
if (guidParam?.Value != DBNull.Value)
|
||||
if (guidParam!.Value is long longValue)
|
||||
return longValue;
|
||||
else if (long.TryParse(guidParam.Value.ToString(), out var guid))
|
||||
else if (long.TryParse(guidParam.Value?.ToString(), out var guid))
|
||||
return guid;
|
||||
|
||||
throw new InsertObjectFailedException(request, "InsertObject stored procedure did not return a valid identifier.");
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
|
||||
namespace ReC.Application.Common.Procedures;
|
||||
|
||||
internal sealed class StoredProcedureBuilder(string procedureName, string? returnVariable = null)
|
||||
{
|
||||
private readonly StringBuilder _execSql = returnVariable is not null
|
||||
? new StringBuilder($"EXEC @{returnVariable} = {procedureName}")
|
||||
: new StringBuilder($"EXEC {procedureName}");
|
||||
private readonly List<SqlParameter> _parameters = [];
|
||||
private char _separator = ' ';
|
||||
|
||||
public StoredProcedureBuilder Add(string name, object? value, SqlDbType? dbType = null)
|
||||
{
|
||||
if (value is null) return this;
|
||||
|
||||
_execSql.AppendLine($"{_separator}@{name} = @{name}");
|
||||
_separator = ',';
|
||||
|
||||
if (!dbType.HasValue && value is DateTime)
|
||||
dbType = SqlDbType.DateTime;
|
||||
|
||||
if (dbType.HasValue)
|
||||
_parameters.Add(new SqlParameter($"@{name}", dbType.Value) { Value = value });
|
||||
else
|
||||
_parameters.Add(new SqlParameter($"@{name}", value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StoredProcedureBuilder Add(string name, EntityType entityType)
|
||||
{
|
||||
var entityTypeStr = entityType switch
|
||||
{
|
||||
EntityType.Action => "ACTION",
|
||||
EntityType.Endpoint => "ENDPOINT",
|
||||
EntityType.EndpointAuth => "ENDPOINT_AUTH",
|
||||
EntityType.EndpointParams => "ENDPOINT_PARAMS",
|
||||
EntityType.Profile => "PROFILE",
|
||||
EntityType.Result => "RESULT",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(entityType), $"Not expected entity type value: {entityType}")
|
||||
};
|
||||
|
||||
return Add(name, entityTypeStr);
|
||||
}
|
||||
|
||||
public StoredProcedureBuilder AddOutput(string name, SqlDbType dbType)
|
||||
{
|
||||
_execSql.AppendLine($"{_separator}@{name} = @{name} OUTPUT");
|
||||
_separator = ',';
|
||||
|
||||
_parameters.Add(new SqlParameter
|
||||
{
|
||||
ParameterName = $"@{name}",
|
||||
SqlDbType = dbType,
|
||||
Direction = ParameterDirection.Output
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public string BuildSql()
|
||||
{
|
||||
if (returnVariable is null)
|
||||
return _execSql.ToString();
|
||||
|
||||
return new StringBuilder()
|
||||
.AppendLine($"DECLARE @{returnVariable} SMALLINT = 0;")
|
||||
.Append(_execSql).AppendLine(";")
|
||||
.AppendLine($"SELECT @{returnVariable};")
|
||||
.ToString();
|
||||
}
|
||||
|
||||
public SqlParameter[] BuildParameters() => [.. _parameters];
|
||||
|
||||
public SqlParameter? GetParameter(string name) =>
|
||||
_parameters.Find(p => p.ParameterName == $"@{name}");
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
|
||||
namespace ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
|
||||
public record UpdateResultDto
|
||||
{
|
||||
@@ -6,4 +8,8 @@ public record UpdateResultDto
|
||||
public short? StatusId { get; set; }
|
||||
public string? Header { get; set; }
|
||||
public string? Body { get; set; }
|
||||
public short? Info { get; set; }
|
||||
public string? InfoDetail { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public InvokeReferences? References { get; set; }
|
||||
}
|
||||
@@ -6,21 +6,16 @@ using Microsoft.Extensions.Options;
|
||||
using ReC.Application.Common.Exceptions;
|
||||
using ReC.Application.Common.Options;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.EndpointAuth.Commands;
|
||||
using ReC.Application.EndpointParams.Commands;
|
||||
using ReC.Application.Endpoints.Commands;
|
||||
using ReC.Application.Profile.Commands;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
using ReC.Application.Results.Commands;
|
||||
using System.Data;
|
||||
|
||||
namespace ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
|
||||
public record UpdateObjectProcedure : IRequest<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Target entity: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT
|
||||
/// Target entity for the update operation.
|
||||
/// </summary>
|
||||
public string Entity { get; set; } = null!;
|
||||
public required EntityType Entity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Target GUID to update (required)
|
||||
@@ -42,85 +37,72 @@ public class UpdateObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlE
|
||||
{
|
||||
public async Task<int> Handle(UpdateObjectProcedure request, CancellationToken cancel)
|
||||
{
|
||||
var parameters = new[]
|
||||
{
|
||||
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
|
||||
new SqlParameter("@pGUID", (object?)request.Id ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pCHANGED_WHO", (object?)request.ChangedWho ?? DBNull.Value),
|
||||
new SqlParameter("@pCHANGED_WHEN", (object?)DateTime.UtcNow ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pACTION_PROFILE_ID", (object?)request.Action.ProfileId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ACTIVE", (object?)request.Action.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_SEQUENCE", (object?)request.Action.Sequence ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ENDPOINT_ID", (object?)request.Action.EndpointId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ENDPOINT_AUTH_ID", (object?)request.Action.EndpointAuthId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ENDPOINT_PARAMS_ID", (object?)request.Action.EndpointParamsId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_SQL_CONNECTION_ID", (object?)request.Action.SqlConnectionId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_TYPE_ID", (object?)request.Action.TypeId ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_PRE_SQL", (object?)request.Action.PreSql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_HEADER_SQL", (object?)request.Action.HeaderSql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_BODY_SQL", (object?)request.Action.BodySql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_POST_SQL", (object?)request.Action.PostSql ?? DBNull.Value),
|
||||
new SqlParameter("@pACTION_ERROR_ACTION_ID", (object?)request.Action.ErrorActionId ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pENDPOINT_ACTIVE", (object?)request.Endpoint.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_DESCRIPTION", (object?)request.Endpoint.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_URI", (object?)request.Endpoint.Uri ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pENDPOINT_AUTH_ACTIVE", (object?)request.EndpointAuth.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_DESCRIPTION", (object?)request.EndpointAuth.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_TYPE_ID", (object?)request.EndpointAuth.TypeId ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_API_KEY", (object?)request.EndpointAuth.ApiKey ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_API_VALUE", (object?)request.EndpointAuth.ApiValue ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_API_KEY_ADD_TO_ID", (object?)request.EndpointAuth.ApiKeyAddToId ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_TOKEN", (object?)request.EndpointAuth.Token ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_USERNAME", (object?)request.EndpointAuth.Username ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_PASSWORD", (object?)request.EndpointAuth.Password ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_DOMAIN", (object?)request.EndpointAuth.Domain ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_AUTH_WORKSTATION", (object?)request.EndpointAuth.Workstation ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pENDPOINT_PARAMS_ACTIVE", (object?)request.EndpointParams.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_DESCRIPTION", (object?)request.EndpointParams.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_GROUP_ID", (object?)request.EndpointParams.GroupId ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_SEQUENCE", (object?)request.EndpointParams.Sequence ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_KEY", (object?)request.EndpointParams.Key ?? DBNull.Value),
|
||||
new SqlParameter("@pENDPOINT_PARAMS_VALUE", (object?)request.EndpointParams.Value ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pPROFILE_ACTIVE", (object?)request.Profile.Active ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_TYPE_ID", (object?)request.Profile.TypeId ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_MANDANTOR", (object?)request.Profile.Mandantor ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_NAME", (object?)request.Profile.Name ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_DESCRIPTION", (object?)request.Profile.Description ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_LOG_LEVEL_ID", (object?)request.Profile.LogLevelId ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_LANGUAGE_ID", (object?)request.Profile.LanguageId ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_FIRST_RUN", (object?)request.Profile.FirstRun ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_LAST_RUN", (object?)request.Profile.LastRun ?? DBNull.Value),
|
||||
new SqlParameter("@pPROFILE_LAST_RESULT", (object?)request.Profile.LastResult ?? DBNull.Value),
|
||||
|
||||
new SqlParameter("@pRESULT_ACTION_ID", (object?)request.Result.ActionId ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_STATUS_ID", (object?)request.Result.StatusId ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_HEADER", (object?)request.Result.Header ?? DBNull.Value),
|
||||
new SqlParameter("@pRESULT_BODY", (object?)request.Result.Body ?? DBNull.Value)
|
||||
};
|
||||
var sp = new StoredProcedureBuilder("[dbo].[PRREC_UPDATE_OBJECT]", "RC")
|
||||
.Add("pENTITY", request.Entity)
|
||||
.Add("pGUID", request.Id)
|
||||
.Add("pCHANGED_WHO", request.ChangedWho)
|
||||
.Add("pCHANGED_WHEN", DateTime.UtcNow)
|
||||
.Add("pACTION_PROFILE_ID", request.Action.ProfileId)
|
||||
.Add("pACTION_ACTIVE", request.Action.Active)
|
||||
.Add("pACTION_SEQUENCE", request.Action.Sequence, SqlDbType.TinyInt)
|
||||
.Add("pACTION_ENDPOINT_ID", request.Action.EndpointId)
|
||||
.Add("pACTION_ENDPOINT_AUTH_ID", request.Action.EndpointAuthId)
|
||||
.Add("pACTION_ENDPOINT_PARAMS_ID", request.Action.EndpointParamsId, SqlDbType.SmallInt)
|
||||
.Add("pACTION_SQL_CONNECTION_ID", request.Action.SqlConnectionId, SqlDbType.SmallInt)
|
||||
.Add("pACTION_TYPE_ID", request.Action.TypeId, SqlDbType.TinyInt)
|
||||
.Add("pACTION_PRE_SQL", request.Action.PreSql)
|
||||
.Add("pACTION_HEADER_SQL", request.Action.HeaderSql)
|
||||
.Add("pACTION_BODY_SQL", request.Action.BodySql)
|
||||
.Add("pACTION_POST_SQL", request.Action.PostSql)
|
||||
.Add("pACTION_ERROR_ACTION_ID", request.Action.ErrorActionId, SqlDbType.TinyInt)
|
||||
.Add("pENDPOINT_ACTIVE", request.Endpoint.Active)
|
||||
.Add("pENDPOINT_DESCRIPTION", request.Endpoint.Description)
|
||||
.Add("pENDPOINT_URI", request.Endpoint.Uri)
|
||||
.Add("pENDPOINT_AUTH_ACTIVE", request.EndpointAuth.Active)
|
||||
.Add("pENDPOINT_AUTH_DESCRIPTION", request.EndpointAuth.Description)
|
||||
.Add("pENDPOINT_AUTH_TYPE_ID", request.EndpointAuth.TypeId, SqlDbType.TinyInt)
|
||||
.Add("pENDPOINT_AUTH_API_KEY", request.EndpointAuth.ApiKey)
|
||||
.Add("pENDPOINT_AUTH_API_VALUE", request.EndpointAuth.ApiValue)
|
||||
.Add("pENDPOINT_AUTH_API_KEY_ADD_TO_ID", request.EndpointAuth.ApiKeyAddToId)
|
||||
.Add("pENDPOINT_AUTH_TOKEN", request.EndpointAuth.Token)
|
||||
.Add("pENDPOINT_AUTH_USERNAME", request.EndpointAuth.Username)
|
||||
.Add("pENDPOINT_AUTH_PASSWORD", request.EndpointAuth.Password)
|
||||
.Add("pENDPOINT_AUTH_DOMAIN", request.EndpointAuth.Domain)
|
||||
.Add("pENDPOINT_AUTH_WORKSTATION", request.EndpointAuth.Workstation)
|
||||
.Add("pENDPOINT_PARAMS_ACTIVE", request.EndpointParams.Active)
|
||||
.Add("pENDPOINT_PARAMS_DESCRIPTION", request.EndpointParams.Description)
|
||||
.Add("pENDPOINT_PARAMS_GROUP_ID", request.EndpointParams.GroupId, SqlDbType.SmallInt)
|
||||
.Add("pENDPOINT_PARAMS_SEQUENCE", request.EndpointParams.Sequence, SqlDbType.TinyInt)
|
||||
.Add("pENDPOINT_PARAMS_KEY", request.EndpointParams.Key)
|
||||
.Add("pENDPOINT_PARAMS_VALUE", request.EndpointParams.Value)
|
||||
.Add("pPROFILE_ACTIVE", request.Profile.Active)
|
||||
.Add("pPROFILE_TYPE_ID", request.Profile.TypeId, SqlDbType.TinyInt)
|
||||
.Add("pPROFILE_MANDANTOR", request.Profile.Mandantor)
|
||||
.Add("pPROFILE_NAME", request.Profile.Name)
|
||||
.Add("pPROFILE_DESCRIPTION", request.Profile.Description)
|
||||
.Add("pPROFILE_LOG_LEVEL_ID", request.Profile.LogLevelId, SqlDbType.TinyInt)
|
||||
.Add("pPROFILE_LANGUAGE_ID", request.Profile.LanguageId, SqlDbType.SmallInt)
|
||||
.Add("pPROFILE_FIRST_RUN", request.Profile.FirstRun)
|
||||
.Add("pPROFILE_LAST_RUN", request.Profile.LastRun)
|
||||
.Add("pPROFILE_LAST_RESULT", request.Profile.LastResult)
|
||||
.Add("pRESULT_ACTION_ID", request.Result.ActionId)
|
||||
.Add("pRESULT_STATUS_ID", request.Result.StatusId, SqlDbType.TinyInt)
|
||||
.Add("pRESULT_HEADER", request.Result.Header)
|
||||
.Add("pRESULT_BODY", request.Result.Body)
|
||||
.Add("pRESULT_INFO_ID", request.Result.Info, SqlDbType.SmallInt)
|
||||
.Add("pRESULT_INFO_DETAIL", request.Result.InfoDetail)
|
||||
.Add("pRESULT_ERROR", request.Result.Error)
|
||||
.Add("pRESULT_BATCH_ID", request.Result.References?.BatchId)
|
||||
.Add("pRESULT_REFERENCE1", request.Result.References?.Reference1)
|
||||
.Add("pRESULT_REFERENCE2", request.Result.References?.Reference2)
|
||||
.Add("pRESULT_REFERENCE3", request.Result.References?.Reference3)
|
||||
.Add("pRESULT_REFERENCE4", request.Result.References?.Reference4)
|
||||
.Add("pRESULT_REFERENCE5", request.Result.References?.Reference5);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await repo.ExecuteQueryRawAsync(
|
||||
"DECLARE @RC SMALLINT = 0; " +
|
||||
"EXEC @RC = [dbo].[PRREC_UPDATE_OBJECT] " +
|
||||
"@pENTITY, @pGUID, @pCHANGED_WHO, @pCHANGED_WHEN, " +
|
||||
"@pACTION_PROFILE_ID, @pACTION_ACTIVE, @pACTION_SEQUENCE, @pACTION_ENDPOINT_ID, @pACTION_ENDPOINT_AUTH_ID, @pACTION_ENDPOINT_PARAMS_ID, @pACTION_SQL_CONNECTION_ID, @pACTION_TYPE_ID, @pACTION_PRE_SQL, @pACTION_HEADER_SQL, @pACTION_BODY_SQL, @pACTION_POST_SQL, @pACTION_ERROR_ACTION_ID, " +
|
||||
"@pENDPOINT_ACTIVE, @pENDPOINT_DESCRIPTION, @pENDPOINT_URI, " +
|
||||
"@pENDPOINT_AUTH_ACTIVE, @pENDPOINT_AUTH_DESCRIPTION, @pENDPOINT_AUTH_TYPE_ID, @pENDPOINT_AUTH_API_KEY, @pENDPOINT_AUTH_API_VALUE, @pENDPOINT_AUTH_API_KEY_ADD_TO_ID, @pENDPOINT_AUTH_TOKEN, @pENDPOINT_AUTH_USERNAME, @pENDPOINT_AUTH_PASSWORD, @pENDPOINT_AUTH_DOMAIN, @pENDPOINT_AUTH_WORKSTATION, " +
|
||||
"@pENDPOINT_PARAMS_ACTIVE, @pENDPOINT_PARAMS_DESCRIPTION, @pENDPOINT_PARAMS_GROUP_ID, @pENDPOINT_PARAMS_SEQUENCE, @pENDPOINT_PARAMS_KEY, @pENDPOINT_PARAMS_VALUE, " +
|
||||
"@pPROFILE_ACTIVE, @pPROFILE_TYPE_ID, @pPROFILE_MANDANTOR, @pPROFILE_NAME, @pPROFILE_DESCRIPTION, @pPROFILE_LOG_LEVEL_ID, @pPROFILE_LANGUAGE_ID, @pPROFILE_FIRST_RUN, @pPROFILE_LAST_RUN, @pPROFILE_LAST_RESULT, " +
|
||||
"@pRESULT_ACTION_ID, @pRESULT_STATUS_ID, @pRESULT_HEADER, @pRESULT_BODY; " +
|
||||
"SELECT @RC;",
|
||||
parameters,
|
||||
cancel);
|
||||
var result = await repo.ExecuteQueryRawAsync(sp.BuildSql(), sp.BuildParameters(), cancel);
|
||||
|
||||
// The stored procedure returns 0 on success, error codes > 0 on failure
|
||||
if (result > 0)
|
||||
{
|
||||
throw new UpdateObjectFailedException(request, $"UpdateObject stored procedure failed with error code: {result}");
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class DeleteObjectProcedureValidator : AbstractValidator<DeleteObjectProcedure>
|
||||
{
|
||||
public DeleteObjectProcedureValidator()
|
||||
{
|
||||
RuleFor(x => x.Entity)
|
||||
.IsInEnum()
|
||||
.WithMessage("ENTITY must be a valid EntityType value.");
|
||||
|
||||
RuleFor(x => x.Start)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("Start GUID/ID must be greater than 0.");
|
||||
|
||||
RuleFor(x => x.End)
|
||||
.GreaterThanOrEqualTo(x => x.Start)
|
||||
.WithMessage("End GUID/ID must be greater than or equal to Start GUID/ID.");
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,9 @@ public class InsertObjectProcedureValidator : AbstractValidator<InsertObjectProc
|
||||
{
|
||||
public InsertObjectProcedureValidator()
|
||||
{
|
||||
// ENTITY must be one of the allowed values
|
||||
RuleFor(x => x.Entity)
|
||||
.NotEmpty()
|
||||
.Must(e => e is "ACTION" or "ENDPOINT" or "ENDPOINT_AUTH" or "ENDPOINT_PARAMS" or "PROFILE" or "RESULT")
|
||||
.WithMessage("ENTITY must be one of: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT.");
|
||||
.IsInEnum()
|
||||
.WithMessage("ENTITY must be a valid EntityType value.");
|
||||
|
||||
// ACTION validation
|
||||
When(x => x.Action != null, () =>
|
||||
@@ -57,10 +55,6 @@ public class InsertObjectProcedureValidator : AbstractValidator<InsertObjectProc
|
||||
RuleFor(x => x.Result!.ActionId)
|
||||
.NotNull()
|
||||
.WithMessage("RESULT requires ResultActionId (maps to @pRESULT_ACTION_ID).");
|
||||
|
||||
RuleFor(x => x.Result!)
|
||||
.Must(r => r.StatusId != null || r.Info != null || r.Error != null)
|
||||
.WithMessage("RESULT requires at least one of: StatusId, Info, or Error.");
|
||||
});
|
||||
|
||||
// ENDPOINT_PARAMS validation
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using FluentValidation;
|
||||
using ReC.Application.Results.Commands;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class InsertResultCommandValidator : AbstractValidator<InsertResultCommand>
|
||||
{
|
||||
public InsertResultCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.ActionId)
|
||||
.NotNull()
|
||||
.WithMessage("ActionId is required.")
|
||||
.GreaterThan(0L)
|
||||
.When(x => x.ActionId.HasValue)
|
||||
.WithMessage("ActionId must be greater than 0.");
|
||||
|
||||
RuleFor(x => x.References.BatchId)
|
||||
.NotEmpty()
|
||||
.WithMessage("BatchId is required.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
using ReC.Application.Results.Queries;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class InvokeBatchRecActionViewsCommandValidator : AbstractValidator<InvokeBatchRecActionViewsCommand>
|
||||
{
|
||||
public InvokeBatchRecActionViewsCommandValidator(ISender sender)
|
||||
{
|
||||
RuleFor(x => x.References.BatchId)
|
||||
.NotEmpty()
|
||||
.WithMessage("BatchId is required.")
|
||||
.MustAsync(async (batchId, cancel) =>
|
||||
{
|
||||
var any = await sender.Send(new AnyResultViewQuery(BatchId: batchId), cancel);
|
||||
return !any;
|
||||
})
|
||||
.WithMessage(x => $"Cannot invoke rec actions for batch '{x.References.BatchId}' because there are already results associated with it.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using FluentValidation;
|
||||
using ReC.Application.Profile.Queries;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class ReadProfileViewQueryValidator : AbstractValidator<ReadProfileViewQuery>
|
||||
{
|
||||
public ReadProfileViewQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0)
|
||||
.When(x => x.Id.HasValue)
|
||||
.WithMessage("Id must be greater than 0.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using FluentValidation;
|
||||
using ReC.Application.RecActions.Queries;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class ReadRecActionViewQueryValidator : AbstractValidator<ReadRecActionViewQuery>
|
||||
{
|
||||
public ReadRecActionViewQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.ProfileId)
|
||||
.GreaterThan(0)
|
||||
.When(x => x.ProfileId.HasValue)
|
||||
.WithMessage("ProfileId must be greater than 0.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using FluentValidation;
|
||||
using ReC.Application.Results.Queries;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class ReadResultViewQueryValidator : AbstractValidator<ReadResultViewQuery>
|
||||
{
|
||||
public ReadResultViewQueryValidator()
|
||||
{
|
||||
RuleFor(x => x)
|
||||
.Must(x => x.Id.HasValue || x.ActionId.HasValue || x.ProfileId.HasValue || x.BatchId is not null)
|
||||
.WithMessage("At least one filter (Id, ActionId, ProfileId or BatchId) must be provided.");
|
||||
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0)
|
||||
.When(x => x.Id.HasValue)
|
||||
.WithMessage("Id must be greater than 0.");
|
||||
|
||||
RuleFor(x => x.ActionId)
|
||||
.GreaterThan(0)
|
||||
.When(x => x.ActionId.HasValue)
|
||||
.WithMessage("ActionId must be greater than 0.");
|
||||
|
||||
RuleFor(x => x.ProfileId)
|
||||
.GreaterThan(0)
|
||||
.When(x => x.ProfileId.HasValue)
|
||||
.WithMessage("ProfileId must be greater than 0.");
|
||||
|
||||
RuleFor(x => x.BatchId)
|
||||
.NotEmpty()
|
||||
.When(x => x.BatchId is not null)
|
||||
.WithMessage("BatchId must not be empty when provided.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using FluentValidation;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
|
||||
namespace ReC.Application.Common.Validations;
|
||||
|
||||
public class UpdateObjectProcedureValidator : AbstractValidator<UpdateObjectProcedure>
|
||||
{
|
||||
public UpdateObjectProcedureValidator()
|
||||
{
|
||||
RuleFor(x => x.Entity)
|
||||
.IsInEnum()
|
||||
.WithMessage("ENTITY must be a valid EntityType value.");
|
||||
|
||||
RuleFor(x => x.Id)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("Target GUID/ID must be greater than 0.");
|
||||
|
||||
RuleFor(x => x.ChangedWho)
|
||||
.MaximumLength(50)
|
||||
.When(x => x.ChangedWho != null);
|
||||
|
||||
When(x => x.Endpoint is { Description: not null }, () =>
|
||||
{
|
||||
RuleFor(x => x.Endpoint.Description)
|
||||
.MaximumLength(250);
|
||||
});
|
||||
|
||||
When(x => x.EndpointAuth is { Description: not null }, () =>
|
||||
{
|
||||
RuleFor(x => x.EndpointAuth.Description)
|
||||
.MaximumLength(250);
|
||||
});
|
||||
|
||||
When(x => x.Profile is { Name: not null }, () =>
|
||||
{
|
||||
RuleFor(x => x.Profile.Name)
|
||||
.MaximumLength(50);
|
||||
});
|
||||
|
||||
When(x => x.Profile is { Mandantor: not null }, () =>
|
||||
{
|
||||
RuleFor(x => x.Profile.Mandantor)
|
||||
.MaximumLength(50);
|
||||
});
|
||||
|
||||
When(x => x.Profile is { Description: not null }, () =>
|
||||
{
|
||||
RuleFor(x => x.Profile.Description)
|
||||
.MaximumLength(250);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.EndpointAuth.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class DeleteEndpointAuthProcedureHandler(ISender sender) : IRequestHandle
|
||||
{
|
||||
return await sender.Send(new DeleteObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT_AUTH",
|
||||
Entity = EntityType.EndpointAuth,
|
||||
Start = request.Start,
|
||||
End = request.End,
|
||||
Force = request.Force
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.EndpointAuth.Commands;
|
||||
|
||||
@@ -24,7 +25,7 @@ public class InsertEndpointAuthProcedureHandler(ISender sender) : IRequestHandle
|
||||
{
|
||||
return await sender.Send(new InsertObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT_AUTH",
|
||||
Entity = EntityType.EndpointAuth,
|
||||
EndpointAuth = request
|
||||
}, cancel);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.EndpointAuth.Commands;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class UpdateEndpointAuthProcedureHandler(ISender sender) : IRequestHandle
|
||||
{
|
||||
return await sender.Send(new UpdateObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT_AUTH",
|
||||
Entity = EntityType.EndpointAuth,
|
||||
Id = request.Id,
|
||||
EndpointAuth = request.Data
|
||||
}, cancel);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.EndpointParams.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class DeleteEndpointParamsProcedureHandler(ISender sender) : IRequestHand
|
||||
{
|
||||
return await sender.Send(new DeleteObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT_PARAMS",
|
||||
Entity = EntityType.EndpointParams,
|
||||
Start = request.Start,
|
||||
End = request.End,
|
||||
Force = request.Force
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.EndpointParams.Commands;
|
||||
|
||||
@@ -19,7 +20,7 @@ public class InsertEndpointParamsProcedureHandler(ISender sender) : IRequestHand
|
||||
{
|
||||
return await sender.Send(new InsertObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT_PARAMS",
|
||||
Entity = EntityType.EndpointParams,
|
||||
EndpointParams = request
|
||||
}, cancel);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.EndpointParams.Commands;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class UpdateEndpointParamsProcedureHandler(ISender sender) : IRequestHand
|
||||
{
|
||||
return await sender.Send(new UpdateObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT_PARAMS",
|
||||
Entity = EntityType.EndpointParams,
|
||||
Id = request.Id,
|
||||
EndpointParams = request.Data
|
||||
}, cancel);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Endpoints.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class DeleteEndpointProcedureHandler(ISender sender) : IRequestHandler<De
|
||||
{
|
||||
return await sender.Send(new DeleteObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT",
|
||||
Entity = EntityType.Endpoint,
|
||||
Start = request.Start,
|
||||
End = request.End,
|
||||
Force = request.Force
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Endpoints.Commands;
|
||||
|
||||
@@ -16,7 +17,7 @@ public class InsertEndpointProcedureHandler(ISender sender) : IRequestHandler<In
|
||||
{
|
||||
return await sender.Send(new InsertObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT",
|
||||
Entity = EntityType.Endpoint,
|
||||
Endpoint = request
|
||||
}, cancel);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Endpoints.Commands;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class UpdateEndpointProcedureHandler(ISender sender) : IRequestHandler<Up
|
||||
{
|
||||
return await sender.Send(new UpdateObjectProcedure
|
||||
{
|
||||
Entity = "ENDPOINT",
|
||||
Entity = EntityType.Endpoint,
|
||||
Id = request.Id,
|
||||
Endpoint = request.Data
|
||||
}, cancel);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Profile.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class DeleteProfileProcedureHandler(ISender sender) : IRequestHandler<Del
|
||||
{
|
||||
return await sender.Send(new DeleteObjectProcedure
|
||||
{
|
||||
Entity = "PROFILE",
|
||||
Entity = EntityType.Profile,
|
||||
Start = request.Start,
|
||||
End = request.End,
|
||||
Force = request.Force
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Profile.Commands;
|
||||
|
||||
@@ -20,7 +21,7 @@ public class InsertProfileProcedureHandler(ISender sender) : IRequestHandler<Ins
|
||||
{
|
||||
return await sender.Send(new InsertObjectProcedure
|
||||
{
|
||||
Entity = "PROFILE",
|
||||
Entity = EntityType.Profile,
|
||||
Profile = request
|
||||
}, cancel);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Profile.Commands;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class UpdateProfileProcedureHandler(ISender sender) : IRequestHandler<Upd
|
||||
{
|
||||
return await sender.Send(new UpdateObjectProcedure
|
||||
{
|
||||
Entity = "PROFILE",
|
||||
Entity = EntityType.Profile,
|
||||
Id = request.Id,
|
||||
Profile = request.Data
|
||||
}, cancel);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="15.1.0" />
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.6.0" />
|
||||
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />
|
||||
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.1" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.RecActions.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class DeleteActionProcedureHandler(ISender sender) : IRequestHandler<Dele
|
||||
{
|
||||
return await sender.Send(new DeleteObjectProcedure
|
||||
{
|
||||
Entity = "ACTION",
|
||||
Entity = EntityType.Action,
|
||||
Start = request.Start,
|
||||
End = request.End,
|
||||
Force = request.Force
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Domain.Constants;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.RecActions.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class InsertActionProcedureHandler(ISender sender) : IRequestHandler<Inse
|
||||
{
|
||||
return await sender.Send(new InsertObjectProcedure
|
||||
{
|
||||
Entity = "ACTION",
|
||||
Entity = EntityType.Action,
|
||||
Action = request
|
||||
}, cancel);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace ReC.Application.RecActions.Commands;
|
||||
public record InvokeBatchRecActionViewsCommand : IRequest
|
||||
{
|
||||
public long ProfileId { get; init; }
|
||||
public required InvokeReferences References { get; init; }
|
||||
}
|
||||
|
||||
public class InvokeRecActionViewsCommandHandler(ISender sender, ILogger<InvokeRecActionViewsCommandHandler>? logger = null) : IRequestHandler<InvokeBatchRecActionViewsCommand>
|
||||
@@ -21,7 +22,11 @@ public class InvokeRecActionViewsCommandHandler(ISender sender, ILogger<InvokeRe
|
||||
{
|
||||
try
|
||||
{
|
||||
await sender.Send(new InvokeRecActionViewCommand() { Action = action }, cancel);
|
||||
await sender.Send(new InvokeRecActionViewCommand()
|
||||
{
|
||||
Action = action,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
}
|
||||
catch (RecActionException ex)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using ReC.Application.Common.Exceptions;
|
||||
using ReC.Application.Common.Options;
|
||||
using ReC.Application.Results.Commands;
|
||||
using ReC.Domain.Constants;
|
||||
using ReC.Domain.Views;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
@@ -17,6 +18,17 @@ namespace ReC.Application.RecActions.Commands;
|
||||
public record InvokeRecActionViewCommand : IRequest
|
||||
{
|
||||
public RecActionViewDto Action { get; set; } = null!;
|
||||
public required InvokeReferences References { get; set; }
|
||||
}
|
||||
|
||||
public record InvokeReferences
|
||||
{
|
||||
public required string BatchId { get; init; }
|
||||
public string? Reference1 { get; init; }
|
||||
public string? Reference2 { get; init; }
|
||||
public string? Reference3 { get; init; }
|
||||
public string? Reference4 { get; init; }
|
||||
public string? Reference5 { get; init; }
|
||||
}
|
||||
|
||||
public class InvokeRecActionViewCommandHandler(
|
||||
@@ -142,24 +154,26 @@ public class InvokeRecActionViewCommandHandler(
|
||||
var resBody = await response.Content.ReadAsStringAsync(cancel);
|
||||
var resHeaders = response.Headers.ToDictionary();
|
||||
|
||||
var statusCode = (short)response.StatusCode;
|
||||
|
||||
await sender.Send(new InsertResultCommand()
|
||||
{
|
||||
StatusId = statusCode,
|
||||
Status = response.StatusCode.ToRecStatus(),
|
||||
ActionId = action.Id,
|
||||
Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }),
|
||||
Body = resBody,
|
||||
Type = ResultType.Main
|
||||
Info = (short)response.StatusCode,
|
||||
Type = ResultType.Main,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
await sender.Send(new InsertResultCommand()
|
||||
{
|
||||
Status = RecStatus.Error,
|
||||
ActionId = action.Id,
|
||||
Error = ex.ToString(),
|
||||
Type = ResultType.Main
|
||||
Type = ResultType.Main,
|
||||
References = request.References
|
||||
}, cancel);
|
||||
|
||||
if (action.ErrorAction == ErrorAction.Stop)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.RecActions.Commands;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class UpdateActionProcedureHandler(ISender sender) : IRequestHandler<Upda
|
||||
{
|
||||
return await sender.Send(new UpdateObjectProcedure
|
||||
{
|
||||
Entity = "ACTION",
|
||||
Entity = EntityType.Action,
|
||||
Id = request.Id,
|
||||
Action = request.Data
|
||||
}, cancel);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Results.Commands;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class DeleteResultProcedureHandler(ISender sender) : IRequestHandler<Dele
|
||||
{
|
||||
return await sender.Send(new DeleteObjectProcedure
|
||||
{
|
||||
Entity = "RESULT",
|
||||
Entity = EntityType.Result,
|
||||
Start = request.Start,
|
||||
End = request.End,
|
||||
Force = request.Force
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
using MediatR;
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
using ReC.Domain.Constants;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Results.Commands;
|
||||
|
||||
public record InsertResultCommand : IInsertProcedure
|
||||
{
|
||||
public long? ActionId { get; set; }
|
||||
public short? StatusId { get; set; }
|
||||
public required RecStatus Status { get; set; }
|
||||
public string? Header { get; set; }
|
||||
public string? Body { get; set; }
|
||||
public string? Info { get; set; }
|
||||
public short Info { get; set; }
|
||||
public string? InfoDetail { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public required ResultType Type { get; set; }
|
||||
public required InvokeReferences References { get; set; }
|
||||
}
|
||||
|
||||
public class InsertResultProcedureHandler(ISender sender) : IRequestHandler<InsertResultCommand, long>
|
||||
@@ -21,7 +25,7 @@ public class InsertResultProcedureHandler(ISender sender) : IRequestHandler<Inse
|
||||
{
|
||||
return await sender.Send(new InsertObjectProcedure
|
||||
{
|
||||
Entity = "RESULT",
|
||||
Entity = EntityType.Result,
|
||||
Result = request
|
||||
}, cancel);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||||
using ReC.Application.Common.Procedures;
|
||||
|
||||
namespace ReC.Application.Results.Commands;
|
||||
|
||||
@@ -17,7 +18,7 @@ public class UpdateResultProcedureHandler(ISender sender) : IRequestHandler<Upda
|
||||
{
|
||||
return await sender.Send(new UpdateObjectProcedure
|
||||
{
|
||||
Entity = "RESULT",
|
||||
Entity = EntityType.Result,
|
||||
Id = request.Id,
|
||||
Result = request.Data
|
||||
}, cancel);
|
||||
|
||||
35
src/ReC.Application/Results/Queries/AnyResultViewQuery.cs
Normal file
35
src/ReC.Application/Results/Queries/AnyResultViewQuery.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ReC.Domain.Views;
|
||||
|
||||
namespace ReC.Application.Results.Queries;
|
||||
|
||||
public record AnyResultViewQuery(
|
||||
long? Id = null,
|
||||
long? ActionId = null,
|
||||
long? ProfileId = null,
|
||||
string? BatchId = null
|
||||
) : IRequest<bool>;
|
||||
|
||||
public class AnyResultViewQueryHandler(IRepository<ResultView> repo) : IRequestHandler<AnyResultViewQuery, bool>
|
||||
{
|
||||
public Task<bool> Handle(AnyResultViewQuery request, CancellationToken cancel)
|
||||
{
|
||||
var q = repo.Query;
|
||||
|
||||
if(request.Id is long id)
|
||||
q = q.Where(rv => rv.Id == id);
|
||||
|
||||
if(request.ActionId is long actionId)
|
||||
q = q.Where(rv => rv.ActionId == actionId);
|
||||
|
||||
if(request.ProfileId is long profileId)
|
||||
q = q.Where(rv => rv.ProfileId == profileId);
|
||||
|
||||
if(request.BatchId is string batchId)
|
||||
q = q.Where(rv => rv.BatchId == batchId);
|
||||
|
||||
return q.AnyAsync(cancel);
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,13 @@ public record ReadResultViewQuery : IRequest<IEnumerable<ResultViewDto>>
|
||||
|
||||
public long? ProfileId { get; init; } = null;
|
||||
|
||||
public string? BatchId { get; init; } = null;
|
||||
|
||||
public bool IncludeAction { get; init; } = true;
|
||||
|
||||
public bool IncludeProfile { get; init; } = false;
|
||||
|
||||
public bool Last { get; init; } = false;
|
||||
public bool LastBatch { get; init; } = false;
|
||||
}
|
||||
|
||||
public class ReadResultViewQueryHandler(IRepository<ResultView> repo, IMapper mapper) : IRequestHandler<ReadResultViewQuery, IEnumerable<ResultViewDto>>
|
||||
@@ -39,13 +41,18 @@ public class ReadResultViewQueryHandler(IRepository<ResultView> repo, IMapper ma
|
||||
if(request.ProfileId is long profileId)
|
||||
q = q.Where(rv => rv.ProfileId == profileId);
|
||||
|
||||
if(request.IncludeAction)
|
||||
if(request.BatchId is string batchId)
|
||||
q = q.Where(rv => rv.BatchId == batchId);
|
||||
|
||||
if (request.IncludeAction)
|
||||
q = q.Include(rv => rv.Action);
|
||||
|
||||
if(request.IncludeProfile)
|
||||
q = q.Include(rv => rv.Profile);
|
||||
|
||||
var entities = request.Last ? [await q.OrderBy(rv => rv.AddedWhen).LastOrDefaultAsync(cancel)] : await q.ToListAsync(cancel);
|
||||
var entities = request.LastBatch
|
||||
? await GetLastBatchEntitiesAsync(q, cancel)
|
||||
: await q.ToListAsync(cancel);
|
||||
|
||||
if (entities.Count == 0)
|
||||
throw new NotFoundException($"No result views found for the given criteria. Criteria: {
|
||||
@@ -58,4 +65,20 @@ public class ReadResultViewQueryHandler(IRepository<ResultView> repo, IMapper ma
|
||||
|
||||
return mapper.Map<IEnumerable<ResultViewDto>>(entities);
|
||||
}
|
||||
|
||||
private static async Task<List<ResultView>> GetLastBatchEntitiesAsync(IQueryable<ResultView> q, CancellationToken cancel)
|
||||
{
|
||||
var lastBatchId = await q
|
||||
.Where(rv => rv.BatchId != null)
|
||||
.OrderByDescending(rv => rv.AddedWhen)
|
||||
.Select(rv => rv.BatchId)
|
||||
.FirstOrDefaultAsync(cancel);
|
||||
|
||||
if (lastBatchId is null)
|
||||
return [];
|
||||
|
||||
return await q
|
||||
.Where(rv => rv.BatchId == lastBatchId)
|
||||
.ToListAsync(cancel);
|
||||
}
|
||||
}
|
||||
50
src/ReC.Client/Api/InvokeReferences.cs
Normal file
50
src/ReC.Client/Api/InvokeReferences.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace ReC.Client.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional reference values that are passed through to all result records when invoking a profile.
|
||||
/// </summary>
|
||||
public class InvokeReferences
|
||||
{
|
||||
/// <summary>Batch identifier.</summary>
|
||||
public string
|
||||
#if NET
|
||||
?
|
||||
#endif
|
||||
BatchId { get; set; }
|
||||
|
||||
/// <summary>Reference value 1.</summary>
|
||||
public string
|
||||
#if NET
|
||||
?
|
||||
#endif
|
||||
Reference1 { get; set; }
|
||||
|
||||
/// <summary>Reference value 2.</summary>
|
||||
public string
|
||||
#if NET
|
||||
?
|
||||
#endif
|
||||
Reference2 { get; set; }
|
||||
|
||||
/// <summary>Reference value 3.</summary>
|
||||
public string
|
||||
#if NET
|
||||
?
|
||||
#endif
|
||||
Reference3 { get; set; }
|
||||
|
||||
/// <summary>Reference value 4.</summary>
|
||||
public string
|
||||
#if NET
|
||||
?
|
||||
#endif
|
||||
Reference4 { get; set; }
|
||||
|
||||
/// <summary>Reference value 5.</summary>
|
||||
public string
|
||||
#if NET
|
||||
?
|
||||
#endif
|
||||
Reference5 { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,28 @@ namespace ReC.Client.Api
|
||||
/// Invokes a ReC action for the specified profile.
|
||||
/// </summary>
|
||||
/// <param name="profileId">The profile identifier.</param>
|
||||
/// <param name="references">Optional reference values to pass through to all result records.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns><see langword="true"/> if the request succeeds; otherwise, <see langword="false"/>.</returns>
|
||||
public async Task<bool> InvokeAsync(int profileId, CancellationToken cancellationToken = default)
|
||||
public async Task<bool> InvokeAsync(int profileId, InvokeReferences references, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var resp = await Http.PostAsync($"{ResourcePath}/invoke/{profileId}", content: null, cancellationToken);
|
||||
var content = references != null ? ReCClientHelpers.ToJsonContent(references) : null;
|
||||
var resp = await Http.PostAsync($"{ResourcePath}/invoke/{profileId}", content, cancellationToken);
|
||||
return resp.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a ReC action for the specified profile.
|
||||
/// </summary>
|
||||
/// <param name="profileId">The profile identifier.</param>
|
||||
/// <param name="batchId">Batch identifier.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns><see langword="true"/> if the request succeeds; otherwise, <see langword="false"/>.</returns>
|
||||
public Task<bool> InvokeAsync(int profileId, string batchId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return InvokeAsync(profileId, new InvokeReferences() { BatchId = batchId }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves Rec actions.
|
||||
/// </summary>
|
||||
|
||||
23
src/ReC.Domain/Constants/RecStatus.cs
Normal file
23
src/ReC.Domain/Constants/RecStatus.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace ReC.Domain.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the general outcome of an operation, independent of any specific technology or protocol.
|
||||
/// <para>
|
||||
/// Technology-specific details (e.g., HTTP status codes) are stored separately
|
||||
/// in the <c>RESULT_INFO</c> and <c>RESULT_INFO_DETAIL</c> fields.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <seealso cref="RecStatusExtensions"/>
|
||||
public enum RecStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the operation completed successfully (value 0).
|
||||
/// </summary>
|
||||
OK = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the operation failed (value 1).
|
||||
/// When set, the <c>RESULT_ERROR</c> field should contain the error details.
|
||||
/// </summary>
|
||||
Error = 1
|
||||
}
|
||||
16
src/ReC.Domain/Constants/RecStatusExtensions.cs
Normal file
16
src/ReC.Domain/Constants/RecStatusExtensions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Net;
|
||||
|
||||
namespace ReC.Domain.Constants;
|
||||
|
||||
public static class RecStatusExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an <see cref="HttpStatusCode"/> to a general <see cref="RecStatus"/>
|
||||
/// based on whether the HTTP status represents a success (2xx) or an error.
|
||||
/// </summary>
|
||||
public static RecStatus ToRecStatus(this HttpStatusCode code)
|
||||
{
|
||||
int value = (int)code;
|
||||
return value >= 200 && value <= 299 ? RecStatus.OK : RecStatus.Error;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ReC.Domain.Constants;
|
||||
|
||||
public enum ResultType
|
||||
public enum ResultType : byte
|
||||
{
|
||||
Pre = 1,
|
||||
Main,
|
||||
|
||||
@@ -33,6 +33,9 @@ public class RecActionView
|
||||
[Column("PROFILE_TYPE_ID")]
|
||||
public ProfileType? ProfileType { get; set; }
|
||||
|
||||
[Column("PROFILE_TYPE")]
|
||||
public string? ProfileTypeName { get; set; }
|
||||
|
||||
[Column("SEQUENCE")]
|
||||
public byte? Sequence { get; set; }
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class ResultView
|
||||
public string? ProfileName { get; set; }
|
||||
|
||||
[Column("STATUS_ID")]
|
||||
public short? StatusCode { get; set; }
|
||||
public RecStatus Status { get; set; }
|
||||
|
||||
[Column("STATUS")]
|
||||
public string? StatusName { get; set; }
|
||||
@@ -42,12 +42,36 @@ public class ResultView
|
||||
[Column("RESULT_BODY")]
|
||||
public string? Body { get; set; }
|
||||
|
||||
[Column("RESULT_INFO_ID")]
|
||||
public short? InfoId { get; set; }
|
||||
|
||||
[Column("RESULT_INFO")]
|
||||
public string? Info { get; set; }
|
||||
|
||||
[Column("RESULT_INFO_DETAIL")]
|
||||
public string? InfoDetail { get; set; }
|
||||
|
||||
[Column("RESULT_ERROR")]
|
||||
public string? Error { get; set; }
|
||||
|
||||
[Column("BATCH_ID")]
|
||||
public string? BatchId { get; set; }
|
||||
|
||||
[Column("REFERENCE1")]
|
||||
public string? Reference1 { get; set; }
|
||||
|
||||
[Column("REFERENCE2")]
|
||||
public string? Reference2 { get; set; }
|
||||
|
||||
[Column("REFERENCE3")]
|
||||
public string? Reference3 { get; set; }
|
||||
|
||||
[Column("REFERENCE4")]
|
||||
public string? Reference4 { get; set; }
|
||||
|
||||
[Column("REFERENCE5")]
|
||||
public string? Reference5 { get; set; }
|
||||
|
||||
[Column("ADDED_WHO")]
|
||||
public string? AddedWho { get; set; }
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public static class DependencyInjection
|
||||
opt.RegisterDefaultRepository<TRecDbContext>();
|
||||
});
|
||||
|
||||
services.AddValidatorsFromAssembly(typeof(AuthScopedValidator).Assembly);
|
||||
services.AddValidatorsFromAssembly(typeof(InsertObjectProcedureValidator).Assembly);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using ReC.Application.Common.Dto;
|
||||
using ReC.Application.Common.Exceptions;
|
||||
|
||||
namespace ReC.Tests.Application.Behaviors;
|
||||
|
||||
@@ -211,46 +210,40 @@ public class InvokeActionTests
|
||||
#region ReplacePlaceholders - Exception Tests
|
||||
|
||||
[Test]
|
||||
public void ReplacePlaceholders_UnresolvableColumn_ThrowsPlaceholderResolutionException()
|
||||
public void ReplacePlaceholders_UnresolvableColumn_ReturnsNull()
|
||||
{
|
||||
var input = "WHERE X = {#INT#NON_EXISTING}";
|
||||
|
||||
var ex = Assert.Throws<PlaceholderResolutionException>(() =>
|
||||
input.ReplacePlaceholders(_foo, _bar, _fuz));
|
||||
|
||||
Assert.That(ex!.ColumnName, Is.EqualTo("NON_EXISTING"));
|
||||
Assert.That(ex.Placeholder, Is.EqualTo("{#INT#NON_EXISTING}"));
|
||||
Assert.That(ex.Input, Is.EqualTo(input));
|
||||
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
|
||||
Assert.That(result, Is.EqualTo("WHERE X = NULL"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReplacePlaceholders_NoObjectsProvided_ThrowsPlaceholderResolutionException()
|
||||
public void ReplacePlaceholders_NoObjectsProvided_ReturnsNull()
|
||||
{
|
||||
var input = "WHERE X = {#INT#BAZ}";
|
||||
|
||||
Assert.Throws<PlaceholderResolutionException>(() =>
|
||||
input.ReplacePlaceholders());
|
||||
var result = input.ReplacePlaceholders();
|
||||
Assert.That(result, Is.EqualTo("WHERE X = NULL"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReplacePlaceholders_ObjectWithoutColumnAttributes_ThrowsPlaceholderResolutionException()
|
||||
public void ReplacePlaceholders_ObjectWithoutColumnAttributes_ReturnsNull()
|
||||
{
|
||||
var model = new NoColumnModel { Id = 1, Name = "Test" };
|
||||
var input = "WHERE X = {#INT#Id}";
|
||||
|
||||
Assert.Throws<PlaceholderResolutionException>(() =>
|
||||
input.ReplacePlaceholders(model));
|
||||
var result = input.ReplacePlaceholders(model);
|
||||
Assert.That(result, Is.EqualTo("WHERE X = NULL"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReplacePlaceholders_MixedResolvableAndUnresolvable_ThrowsOnUnresolvable()
|
||||
public void ReplacePlaceholders_MixedResolvableAndUnresolvable_ReturnsNullForUnresolvable()
|
||||
{
|
||||
var input = "WHERE BAZ = {#INT#BAZ} AND X = {#INT#UNKNOWN}";
|
||||
|
||||
var ex = Assert.Throws<PlaceholderResolutionException>(() =>
|
||||
input.ReplacePlaceholders(_foo, _bar, _fuz));
|
||||
|
||||
Assert.That(ex!.ColumnName, Is.EqualTo("UNKNOWN"));
|
||||
var result = input.ReplacePlaceholders(_foo, _bar, _fuz);
|
||||
Assert.That(result, Is.EqualTo("WHERE BAZ = 2 AND X = NULL"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
using ReC.Application.Results.Queries;
|
||||
|
||||
namespace ReC.Tests.Application.RecActions;
|
||||
|
||||
[TestFixture]
|
||||
public class InvokeBatchDuplicateGuardTests : RecApplicationTestBase
|
||||
{
|
||||
private const long ProfileId = 3;
|
||||
|
||||
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
|
||||
{
|
||||
var scope = ServiceProvider.CreateScope();
|
||||
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
|
||||
return (sender, scope);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Invoke_with_existing_batchId_throws_ValidationException()
|
||||
{
|
||||
var (sender, scope) = CreateScopedSender();
|
||||
using var _ = scope;
|
||||
|
||||
// Arrange: read an existing result to get a real BatchId from the database
|
||||
var results = await sender.Send(new ReadResultViewQuery
|
||||
{
|
||||
ProfileId = ProfileId,
|
||||
IncludeAction = false,
|
||||
LastBatch = true
|
||||
});
|
||||
|
||||
var existingBatchId = results.FirstOrDefault()?.BatchId;
|
||||
Assert.That(existingBatchId, Is.Not.Null.And.Not.Empty,
|
||||
$"No results with a BatchId found for ProfileId {ProfileId}. Ensure test data exists in the database.");
|
||||
|
||||
// Act & Assert: invoking with the same BatchId should throw ValidationException
|
||||
var ex = Assert.ThrowsAsync<ValidationException>(async () =>
|
||||
await sender.Send(new InvokeBatchRecActionViewsCommand
|
||||
{
|
||||
ProfileId = ProfileId,
|
||||
References = new InvokeReferences
|
||||
{
|
||||
BatchId = existingBatchId!
|
||||
}
|
||||
}));
|
||||
|
||||
Assert.That(ex!.Errors.Any(e => e.PropertyName.Contains("BatchId")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Invoke_with_new_batchId_does_not_throw_duplicate_guard()
|
||||
{
|
||||
var (sender, scope) = CreateScopedSender();
|
||||
using var _ = scope;
|
||||
|
||||
var uniqueBatchId = $"test-{System.Guid.NewGuid():N}";
|
||||
|
||||
// This should NOT throw ValidationException for duplicate BatchId.
|
||||
// It may throw other exceptions (e.g., no actions found, endpoint errors),
|
||||
// but the duplicate guard should pass.
|
||||
try
|
||||
{
|
||||
sender.Send(new InvokeBatchRecActionViewsCommand
|
||||
{
|
||||
ProfileId = ProfileId,
|
||||
References = new InvokeReferences
|
||||
{
|
||||
BatchId = uniqueBatchId
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (ValidationException valEx) when (valEx.Errors.Any(e => e.PropertyName.Contains("BatchId")))
|
||||
{
|
||||
Assert.Fail("Duplicate guard should not trigger for a unique BatchId.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Other exceptions (endpoint errors, etc.) are acceptable
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -5,7 +6,9 @@ using NUnit.Framework;
|
||||
using ReC.Application.Common.Procedures.DeleteProcedure;
|
||||
using ReC.Application.Common.Procedures.InsertProcedure;
|
||||
using ReC.Application.Common.Procedures.UpdateProcedure;
|
||||
using ReC.Application.RecActions.Commands;
|
||||
using ReC.Application.Results.Commands;
|
||||
using ReC.Domain.Constants;
|
||||
using ReC.Tests.Application;
|
||||
|
||||
namespace ReC.Tests.Application.Results;
|
||||
@@ -23,7 +26,7 @@ public class ResultProcedureTests : RecApplicationTestBase
|
||||
[Test]
|
||||
public async Task InsertResultProcedure_runs_via_mediator()
|
||||
{
|
||||
var procedure = new InsertResultCommand { ActionId = 1, StatusId = 200, Header = "h", Body = "b", Type = Domain.Constants.ResultType.Main };
|
||||
var procedure = new InsertResultCommand { ActionId = 1, Status = HttpStatusCode.OK.ToRecStatus(), Header = "h", Body = "b", Info = 200, Type = ResultType.Main, References = new () { BatchId = DateTime.Now.ToString() } };
|
||||
|
||||
var (sender, scope) = CreateScopedSender();
|
||||
using var _ = scope;
|
||||
|
||||
Reference in New Issue
Block a user