Refactor RecActions to RecActionViews namespaces

Renamed command and query files, namespaces, and usings from RecActions to RecActionViews for improved clarity and organization. Updated controller and mapping profile references accordingly. No changes to business logic or handler implementations.
This commit is contained in:
2025-12-12 14:43:32 +01:00
parent 28f35101f9
commit 2aa7cabcbd
7 changed files with 9 additions and 9 deletions

View File

@@ -1,45 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using ReC.Application.Endpoints.Commands;
using ReC.Domain.Entities;
namespace ReC.Application.RecActions.Commands;
public record CreateRecActionCommand : IRequest
{
public long ProfileId { get; init; }
public bool Active { get; init; } = true;
public long? EndpointId { get; set; }
public string? EndpointUri { get; init; }
public string Type { get; init; } = null!;
public string? HeaderQuery { get; init; }
public string BodyQuery { get; init; } = null!;
public byte Sequence { get; set; } = 1;
public long? EndpointAuthId { get; set; }
}
public class CreateRecActionCommandHandler(ISender sender, IRepository<RecAction> repo) : IRequestHandler<CreateRecActionCommand>
{
public async Task Handle(CreateRecActionCommand request, CancellationToken cancel)
{
if(request.EndpointId is null)
if(request.EndpointUri is string endpointUri)
{
var endpoint = await sender.Send(new ObtainEndpointCommand { Uri = endpointUri }, cancel);
request.EndpointId = endpoint.Id;
}
else
throw new BadRequestException("Either EndpointId or EndpointUri must be provided.");
await repo.CreateAsync(request, cancel);
}
}

View File

@@ -1,24 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Domain.Entities;
namespace ReC.Application.RecActions.Commands;
public class DeleteRecActionsCommand : IRequest
{
public required long ProfileId { get; init; }
}
public class DeleteRecActionsCommandHandler(IRepository<RecAction> repo) : IRequestHandler<DeleteRecActionsCommand>
{
public async Task Handle(DeleteRecActionsCommand request, CancellationToken cancel)
{
// TODO: update DeleteAsync (in Core) to return number of deleted records
if (!await repo.Where(act => act.ProfileId == request.ProfileId).AnyAsync(cancel))
throw new NotFoundException();
await repo.DeleteAsync(act => act.ProfileId == request.ProfileId, cancel);
}
}

View File

@@ -1,34 +0,0 @@
using MediatR;
using ReC.Application.RecActions.Queries;
using ReC.Domain.Constants;
namespace ReC.Application.RecActions.Commands;
public record InvokeBatchRecActionsCommand : ReadRecActionQueryBase, IRequest;
public static class InvokeBatchRecActionsCommandExtensions
{
public static Task InvokeBatchRecAction(this ISender sender, long profileId, CancellationToken cancel = default)
=> sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId }, cancel);
}
public class InvokeRecActionsCommandHandler(ISender sender) : IRequestHandler<InvokeBatchRecActionsCommand>
{
public async Task Handle(InvokeBatchRecActionsCommand request, CancellationToken cancel)
{
var actions = await sender.Send(request.ToReadQuery(q => q.Invoked = false), cancel);
foreach (var action in actions)
{
var ok = await sender.Send(action.ToInvokeCommand(), cancel);
if (!ok)
switch (action.ErrorAction)
{
case ErrorAction.Continue:
break;
default:
return;
}
}
}
}

View File

@@ -1,147 +0,0 @@
using MediatR;
using Microsoft.Extensions.Configuration;
using ReC.Application.Common;
using ReC.Application.Common.Constants;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.OutResults.Commands;
using ReC.Domain.Constants;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace ReC.Application.RecActions.Commands;
public record InvokeRecActionCommand : IRequest<bool>
{
public RecActionDto Action { get; set; } = null!;
}
public static class InvokeRecActionCommandExtensions
{
public static InvokeRecActionCommand ToInvokeCommand(this RecActionDto dto) => new() { Action = dto };
}
public class InvokeRecActionCommandHandler(
ISender sender,
IHttpClientFactory clientFactory,
IConfiguration? config = null
) : IRequestHandler<InvokeRecActionCommand, bool>
{
public async Task<bool> Handle(InvokeRecActionCommand request, CancellationToken cancel)
{
var action = request.Action;
using var http = clientFactory.CreateClient(Http.ClientName);
if (action.RestType is null)
throw new DataIntegrityException(
$"Rec action could not be invoked because the RestType value is null. " +
$"ProfileId: {action.ProfileId}, " +
$"Id: {action.Id}"
);
using var httpReq = action.RestType
.ToHttpMethod()
.ToHttpRequestMessage(action.EndpointUri);
if (action.Body is not null)
{
using var reqBody = new StringContent(action.Body);
httpReq.Content = reqBody;
}
if (action.Headers is not null)
foreach (var header in action.Headers)
httpReq.Headers.Add(header.Key, header.Value);
switch (action.EndpointAuthType)
{
case EndpointAuthType.NoAuth:
break;
case EndpointAuthType.ApiKey:
if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue)
{
switch (action.EndpointAuthApiKeyAddTo)
{
case ApiKeyLocation.Header:
httpReq.Headers.Add(apiKey, apiValue);
break;
case ApiKeyLocation.Query:
var uriBuilder = new UriBuilder(httpReq.RequestUri!);
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
query[apiKey] = apiValue;
uriBuilder.Query = query.ToString();
httpReq.RequestUri = uriBuilder.Uri;
break;
default:
throw new DataIntegrityException(
$"The API key location '{action.EndpointAuthApiKeyAddTo}' is not supported. " +
$"ProfileId: {action.ProfileId}, " +
$"Id: {action.Id}"
);
}
}
break;
case EndpointAuthType.BearerToken:
case EndpointAuthType.JwtBearer:
case EndpointAuthType.OAuth2:
if (action.EndpointAuthToken is string authToken)
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
break;
case EndpointAuthType.BasicAuth:
if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword)
{
var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}"));
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
}
break;
case EndpointAuthType.NtlmAuth:
if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername))
{
var credentials = new NetworkCredential(
action.EndpointAuthUsername,
action.EndpointAuthPassword,
action.EndpointAuthDomain);
var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } };
httpReq.Options.Set(new HttpRequestOptionsKey<CredentialCache>("Credentials"), credentialCache);
}
break;
case EndpointAuthType.DigestAuth:
case EndpointAuthType.OAuth1:
case EndpointAuthType.AwsSignature:
// These authentication methods require more complex implementations,
// often involving multi-step handshakes or specialized libraries.
// They are left as placeholders for future implementation.
default:
throw new NotImplementedException(
$"The authentication type '{action.EndpointAuthType}' is not supported yet. " +
$"ProfileId: {action.ProfileId}, " +
$"Id: {action.Id}"
);
}
using var response = await http.SendAsync(httpReq, cancel);
var resBody = await response.Content.ReadAsStringAsync(cancel);
var resHeaders = response.Headers.ToDictionary();
var statusCode = (short)response.StatusCode;
await sender.Send(new CreateOutResCommand
{
Status = statusCode,
ActionId = action.Id,
Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }),
Body = resBody,
AddedWho = config?["AddedWho"]
}, cancel);
return response.IsSuccessStatusCode;
}
}

View File

@@ -1,16 +0,0 @@
using ReC.Application.RecActions.Commands;
using ReC.Domain.Entities;
namespace ReC.Application.RecActions;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfile : AutoMapper.Profile
{
public MappingProfile()
{
CreateMap<CreateRecActionCommand, RecAction>()
.ForMember(e => e.Active, exp => exp.MapFrom(cmd => true))
.ForMember(e => e.AddedWhen, exp => exp.MapFrom(cmd => DateTime.UtcNow))
.ForMember(e => e.AddedWho, exp => exp.MapFrom(cmd => "ReC.API"));
}
}

View File

@@ -1,48 +0,0 @@
using MediatR;
using DigitalData.Core.Abstraction.Application.Repository;
using ReC.Domain.Entities;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using DigitalData.Core.Exceptions;
using ReC.Application.Common.Dto;
namespace ReC.Application.RecActions.Queries;
public record ReadRecActionQueryBase
{
public long ProfileId { get; init; }
public ReadRecActionQuery ToReadQuery(Action<ReadRecActionQuery> modify)
{
ReadRecActionQuery query = new(this);
modify(query);
return query;
}
}
public record ReadRecActionQuery : ReadRecActionQueryBase, IRequest<IEnumerable<RecActionDto>>
{
public ReadRecActionQuery(ReadRecActionQueryBase root) : base(root) { }
public bool? Invoked { get; set; } = null;
public ReadRecActionQuery() { }
}
public class ReadRecActionQueryHandler(IRepository<RecActionView> repo, IMapper mapper) : IRequestHandler<ReadRecActionQuery, IEnumerable<RecActionDto>>
{
public async Task<IEnumerable<RecActionDto>> Handle(ReadRecActionQuery request, CancellationToken cancel)
{
var query = repo.Where(act => act.ProfileId == request.ProfileId);
if (request.Invoked is bool invoked)
query = invoked ? query.Where(act => act.Root!.OutRes != null) : query.Where(act => act.Root!.OutRes == null);
var actions = await query.Include(act => act.EndpointAuth).ToListAsync(cancel);
if (actions.Count == 0)
throw new NotFoundException($"No actions found for the profile {request.ProfileId}.");
return mapper.Map<IEnumerable<RecActionDto>>(actions);
}
}