Compare commits

...

46 Commits

Author SHA1 Message Date
4ed080f58a Refactor ExceptionHandlingMiddleware responses
Refactored `ExceptionHandlingMiddleware` to use `ValidationProblemDetails` for structured and consistent error responses. Marked the middleware as obsolete with a recommendation to use `DigitalData.Core.Exceptions.Middleware`. Updated exception handling logic to replace error message strings with detailed problem descriptions, including titles and details for each exception type. Improved the default case for unhandled exceptions with a user-friendly message. Enhanced response serialization using `WriteAsJsonAsync`.
2025-12-03 17:08:12 +01:00
bf98efd3a3 Improve exception handling and validation responses
Updated `ExceptionHandlingMiddleware` to enhance error handling:
- Added `Microsoft.AspNetCore.Mvc` and `System.Text.Json` for structured JSON responses.
- Made `message` nullable to handle cases without explicit messages.
- Refactored `ValidationException` handling:
  - Changed HTTP status code from 422 to 400.
  - Grouped validation errors by property and returned them in a `ValidationProblemDetails` object.
- Conditionally wrote `message` to the response only if not null.
- Improved structure and clarity of validation error responses.
2025-12-03 15:35:06 +01:00
459cc1cb9a Add ValidationException handling to middleware
Enhanced the `ExceptionHandlingMiddleware` to handle
`ValidationException` from the `FluentValidation` library.
Set HTTP status code to 422 (Unprocessable Entity) for
validation errors and constructed error messages by
aggregating validation error details. Improved error
response clarity for validation-related issues.
2025-12-03 15:03:22 +01:00
cd53d7fbae Add validation pipeline with FluentValidation and MediatR
Introduced a validation pipeline using FluentValidation and
MediatR to enhance request validation. Added the following:

- Registered FluentValidation and MediatR dependencies in
  `ReC.Application.csproj`.
- Updated `DependencyInjection.cs` to register validators
  and MediatR behaviors, including `ValidationBehavior<,>`.
- Added `ValidationBehavior<TRequest, TResponse>` to handle
  request validation in the MediatR pipeline.
- Created `ReadOutResQueryValidator` to enforce validation
  rules for `ReadOutResQuery`.
- Refactored namespaces and imports for better organization.

These changes improve extensibility, maintainability, and
separation of concerns in the application.
2025-12-03 15:00:56 +01:00
b3ce5ad28a Make ProfileId and ActionId properties nullable
Changed the `ProfileId` and `ActionId` properties in the
`ReadOutResQuery` class from non-nullable `long` to nullable
`long?`. This allows these properties to hold `null` values,
making them optional for scenarios where they are not always
required or applicable.
2025-12-03 14:43:07 +01:00
d7c2796d79 Add OutResDto and ReadOutResQuery for output handling
Added a new `OutResDto` record in the `ReC.Application.Common.Dto` namespace to represent output result data with properties like `Id`, `ActionId`, `Header`, `Body`, and audit fields.

Added a new `ReadOutResQuery` class in the `ReC.Application.OutResults.Queries` namespace to facilitate querying output results based on `ProfileId` and `ActionId`.
2025-12-03 14:42:36 +01:00
d27c3f3fc7 Update MappingProfiles with AddedWhen and AddedWho mappings
Reordered `using` directives for clarity and added `ReC.Domain.Entities`.
Added a `TODO` comment to indicate that `AddedWho` should be injected from the current host/user context.
Updated `CreateMap` in `MappingProfiles` to set `AddedWhen` to `DateTime.UtcNow` and `AddedWho` to the hardcoded value `"ReC.API"`.
2025-12-03 13:57:13 +01:00
bf02cc80d1 Add new POST endpoint and update profileId type to long
Added a new HTTP POST endpoint `invoke/fake` in `RecActionController`
to invoke batch actions with a fake profile ID. Updated the `profileId`
parameter type in the `InvokeBatchRecAction` extension method from
`int` to `long` to support larger profile IDs. These changes improve
flexibility and introduce a new endpoint for specific use cases.
2025-12-03 13:50:23 +01:00
6de45e3feb Refactor RecActionController and add new endpoints
- Introduced `FakeProfileId` constant to replace hardcoded values.
- Added `HttpGet` endpoint `api/[controller]/fake` to retrieve data using `FakeProfileId`.
- Added `HttpPost` endpoint `api/[controller]/invoke/{profileId}` for batch actions.
- Updated `CreateAction` and `Delete` methods to use `FakeProfileId`.
- Added `HttpDelete` endpoint `api/[controller]/fake` to delete actions using `FakeProfileId`.
- Improved code maintainability by centralizing the fake profile ID and adding dedicated endpoints.
2025-12-03 13:49:26 +01:00
dd33d74863 Update EndpointAuthId in RecActionController to 4
The `EndpointAuthId` property in the object initialization
within the `RecActionController` class was updated from `1`
to `4`. This change likely reflects an update to the
authentication configuration or a shift to a different
endpoint authorization identifier.
2025-12-03 13:46:10 +01:00
6eebd10dc1 Add DELETE endpoint to RecActionController for testing
A new HTTP DELETE endpoint has been added to the
`RecActionController` class, accessible via the `"fake"` route.
The `Delete` method is asynchronous and uses the `mediator.Send`
method to execute a `DeleteRecActionsCommand` with a hardcoded
`ProfileId` value of `2`. The method returns an HTTP 204 No
Content response. This endpoint appears to be intended for
testing or placeholder purposes.
2025-12-03 13:43:39 +01:00
1a0da4140b Add EndpointAuthId to support endpoint authentication
Introduced a new `EndpointAuthId` property in `RecActionController` and `CreateRecActionCommand` to enable handling of endpoint authentication. Updated the `RecActionController` to initialize `EndpointAuthId` with a default value of `1`. Modified the `CreateRecActionCommand` record to include a nullable `EndpointAuthId` property of type `long?`, allowing optional specification of authentication details.
2025-12-03 13:43:08 +01:00
94561fe014 Add Sequence property to CreateRecActionCommand
A new `Sequence` property of type `byte` was added to the
`CreateRecActionCommand` record. It is initialized with a
default value of `1` and includes a `set` accessor, making
it mutable. This property allows the record to store or
manipulate sequence-related information, enhancing its
functionality.
2025-12-03 13:20:46 +01:00
4e107d928e Enhance mapping in MappingProfile.cs
Updated the `MappingProfile` class to include custom mappings for the `CreateRecActionCommand` to `RecAction` transformation:
- Set `Active` to `true` by default.
- Set `AddedWhen` to the current UTC time.
- Set `AddedWho` to `"ReC.API"` as a placeholder.
Added a `TODO` comment to replace the hardcoded `AddedWho` value with dynamic injection from the current host/user context.
2025-12-03 13:08:00 +01:00
3e6c2ea12b Add repository interaction to CreateRecActionCommandHandler
Updated `CreateRecActionCommandHandler` to include a repository
dependency (`IRepository<RecAction>`) for persisting data. Added
a call to `repo.CreateAsync` in the `Handle` method to save the
command request. Updated `using` directives to include necessary
dependencies (`AutoMapper`, `DigitalData.Core.Abstraction.Application.Repository`,
and `ReC.Domain.Entities`). Validation logic for `EndpointId` or
`EndpointUri` remains unchanged.
2025-12-03 13:03:50 +01:00
606a6727f4 Refactor RecActionController and add CRUD methods
Refactored the `RecActionController` to improve structure and
readability by introducing a `#region CRUD` section. Updated
the `HttpPost` route for invoking actions to `"invoke/{profileId}"`.

Added a new `CreateAction` method to handle action creation
and renamed the `HttpDelete` method from `GetActions` to
`Delete` for clarity. Introduced the `Invoke` method for batch
action invocation. Improved overall code organization and
maintainability.
2025-12-03 12:54:39 +01:00
1e21d133ae Add mappings for AddedWhen and AddedWho in MappingProfile
Added a TODO comment to update `AddedWho` to be injected
from the current host/user context. Configured `AddedWhen`
to use the current UTC time (`DateTime.UtcNow`) and set
`AddedWho` to the string `"ReC.API"`.
2025-12-03 12:50:38 +01:00
6e68083a8d Set Active property to true in ObtainEndpointCommand map
Updated the `MappingProfile` class to configure the mapping
between `ObtainEndpointCommand` and `Endpoint` to explicitly
set the `Active` property of `Endpoint` to `true` using the
`ForMember` method. This ensures that the `Active` property
is initialized to `true` during the mapping process, aligning
with the intended default behavior for new `Endpoint` objects.
2025-12-03 12:43:51 +01:00
b67da5434e Fix incorrect conditional logic in ReadRecActionQueryHandler
The conditional check in the `Handle` method of the
`ReadRecActionQueryHandler` class was updated. Previously, the code
threw a `NotFoundException` if the `actions` list was not empty
(`actions.Count != 0`). This logic was inverted to throw the exception
when the `actions` list is empty (`actions.Count == 0`).

This change ensures the exception is thrown only when no actions are
found for the given profile, aligning with the intended behavior
described in the exception message.
2025-12-03 12:06:47 +01:00
a7f4677ad1 Refactor and enhance DbContext configuration
Updated `AddRecInfrastructure` to improve DbContext setup:
- Renamed parameters for clarity (`dbContextOpt` to `opt`).
- Renamed `connectionString` to `cnnStr` for consistency.
- Added `LogTo` for SQL query logging at `Trace` level.
- Enabled `EnableSensitiveDataLogging` for debugging.
- Enabled `EnableDetailedErrors` for detailed error messages.
2025-12-03 11:48:23 +01:00
edf2468a33 Enhance DbContext configuration with logging features
Added advanced logging and debugging capabilities to the
DbContext configuration in `AddRecInfrastructure`:
- Integrated `ILogger<RecDbContext>` for logging.
- Enabled SQL query logging with `LogTo` at `Trace` level.
- Enabled sensitive data logging for debugging purposes.
- Enabled detailed error messages for better diagnostics.
2025-12-03 11:47:54 +01:00
60e5adbf1a Refactor DbContext configuration for flexibility
Updated `ConfigureDbContext` to accept `IServiceProvider`, enabling dependency injection during database context setup. Modified `DependencyInjection.cs` to align with this change by updating `DbContextOptionsAction` and its related method signature.

Removed unused `System.IO` and `System.Text.Json` namespaces from `RecActionController.cs` to improve code cleanliness.
2025-12-03 11:47:04 +01:00
dc408e7794 Add CancellationToken support to controller methods
Updated `CreateAction`, `CreateFakeAction`, and `GetActions`
methods in `RecActionController` to include a `CancellationToken`
parameter. This enables handling of cancellation requests during
asynchronous operations. Updated `mediator.Send` calls to pass
the `CancellationToken`, improving responsiveness and resource
management.
2025-12-03 11:34:00 +01:00
c34a87771d Add CancellationToken support to RecAction methods
Updated the `Invoke` method in `RecActionController` to accept a `CancellationToken` parameter, enabling cancellation handling during execution. Modified the `InvokeBatchRecAction` extension method in `InvokeBatchRecActionsCommandExtensions` to propagate the `CancellationToken` to the `sender.Send` method, ensuring cancellation support for batch action commands.
2025-12-03 11:31:38 +01:00
6041d57948 Add CancellationToken support to Get method
The `Get` method in `RecActionController` was updated to include a `CancellationToken` parameter, enabling support for operation cancellation. The `mediator.Send` call was modified to pass the token. No changes were made to the `Invoke` method or other unrelated parts of the class.
2025-12-03 11:30:18 +01:00
a99f2d55b2 Add Get endpoint and refactor ReadRecActionQuery
Added a new Get endpoint to RecActionController to fetch data
based on profileId using ReadRecActionQuery. Updated
ReadRecActionQuery to support long ProfileId, added a
parameterless constructor, and refactored its structure.
Adjusted ReadRecActionQueryHandler to align with these changes.
2025-12-03 11:29:31 +01:00
edfbfd8e6c Update Profile.Id type and fix FK mapping in RecAction
Changed the `Id` property in the `Profile` class from `short?` to `long` to support larger values and resolve a type mismatch with the `ProfileId` foreign key in the `RecAction` class.

Removed the `[NotMapped]` attribute from the `Profile` navigation property in `RecAction` as the type mismatch issue has been resolved. Also removed the related TODO comment. These changes improve database schema consistency and integrity.
2025-12-03 11:18:36 +01:00
23ccd44bd6 Add table mappings and navigation property updates
Added `[Table]` attributes to `Connection`, `Endpoint`, `EndpointAuth`, and `Profile` classes to define database table mappings. Updated `Profile` with schema information.

Introduced a `Profile` navigation property in `RecAction` with a `[ForeignKey]` attribute to establish a relationship with the `Profile` entity. Temporarily marked it as `[NotMapped]` due to a foreign key type mismatch, with a `TODO` to resolve this in the future.

Included `System.ComponentModel.DataAnnotations.Schema` in `using` directives to support these changes.
2025-12-03 11:15:07 +01:00
2c005c35fb Add database connection string to appsettings.json
Added a new "ConnectionStrings" section to the `appsettings.json`
file, including a "Default" connection string for SQL Server.
The connection string specifies the server, database, user
credentials, and options to disable encryption and trust the
server certificate. This change configures database connectivity
for the application.
2025-12-03 11:08:53 +01:00
771eb80b9e Make DeleteRecActionsCommand robust and async-safe
Enhanced the DeleteRecActionsCommandHandler to include a check
for the existence of records before deletion, throwing a
NotFoundException if none are found. Made the Handle method
asynchronous and added necessary namespace imports for
exception handling and database operations. Added a comment
to suggest updating the DeleteAsync method in the Core library
to return the number of deleted records.
2025-12-03 10:46:23 +01:00
1cb9ce18b4 Add DELETE endpoint to RecActionController
A new HTTP DELETE endpoint has been added to the `RecActionController` class. The endpoint, implemented as the `GetActions` method, accepts a `profileId` as a query parameter and uses the mediator pattern to send a `DeleteRecActionsCommand` with the provided `profileId`.

The method returns an HTTP 204 No Content response upon successful deletion, ensuring a clean separation of concerns and adherence to the CQRS pattern.
2025-12-03 10:42:47 +01:00
5b16d19541 Rename ActionController to RecActionController
The `ActionController` class was removed and replaced with a new `RecActionController` class. The new class retains the same functionality, methods (`Invoke`, `CreateAction`, `CreateFakeAction`), and dependencies as the removed class.

The namespace (`ReC.API.Controllers`) and route attributes remain unchanged. Dependency injection for `IMediator` is preserved. This change is primarily a renaming of the controller to better align with its purpose while maintaining existing behavior.
2025-12-03 10:39:34 +01:00
b5b1f53e21 Refactor CreateFakeAction to use FakeRequest model
Refactored the `CreateFakeAction` method in `ActionController`:
- Replaced individual parameters with a strongly-typed `FakeRequest` model for the request body.
- Added `[FromQuery]` attributes for `endpointUri`, `endpointPath`, and `type` to allow query parameter overrides.
- Updated serialization logic for `Body` and `Header` to handle null values more explicitly.
- Adjusted `BodyQuery` and `HeaderQuery` assignments to improve null handling.

Also updated `using` directives to include `ReC.API.Models` and `ReC.Application.RecActions.Commands`.
2025-12-03 10:27:34 +01:00
81aca90c39 Add FakeRequest model to handle request data
Introduced a new `FakeRequest` class in the `ReC.API.Models` namespace.
The class includes `Body` and `Header` properties, both of which
are nullable dictionaries with string keys and object values.
These properties are marked as `init`-only to ensure immutability
after initialization. This model is designed to support flexible
handling of request payloads and headers.
2025-12-03 10:22:13 +01:00
047f8fc258 Update HeaderQueryBehavior to use two generic parameters
Updated the `AddOpenBehaviors` method call in `DependencyInjection.cs` to reflect changes in the `HeaderQueryBehavior` class, which now requires two generic type parameters instead of one. This ensures compatibility with the updated class definition.
2025-12-03 10:16:57 +01:00
4b0208ca56 Refactor HeaderQueryBehavior for generic request/response
Updated HeaderQueryBehavior to support two generic type
parameters, TRequest and TResponse, for improved flexibility.
Replaced the single TRecAction type parameter and Unit return
type with a more generic implementation. Updated where
constraints to reflect the new generic types. Modified the
Handle method signature and logic to align with the updated
generic parameters.
2025-12-03 10:16:29 +01:00
0e62011f92 Update BodyQueryBehavior to use two-type parameters
Modified the `AddOpenBehaviors` method in the `DependencyInjection` class to update `BodyQueryBehavior<>` to its two-type parameter version, `BodyQueryBehavior<,>`. The `HeaderQueryBehavior<>` remains unchanged.
2025-12-03 10:12:56 +01:00
3998c9ce0b Refactor BodyQueryBehavior for generic request-response
Updated BodyQueryBehavior to support a generic request-response
pipeline. Replaced fixed `TRecAction` and `Unit` types with
`TRequest` and `TResponse` generics. Added `where` constraints
for `TRequest` (`RecActionDto`) and `TResponse` (`notnull`).
Updated the `Handle` method signature and return type to align
with the new generics.
2025-12-03 10:12:35 +01:00
269578194a Replace MediatRLicense with LuckyPennySoftwareLicenseKey
The `MediatRLicense` key was removed from `appsettings.json`
and replaced with the `LuckyPennySoftwareLicenseKey`. This
change likely reflects a transition to a new licensing
mechanism. No other modifications were made to the file.
2025-12-03 10:08:15 +01:00
6a3e0b7d50 Refactor AutoMapper profiles and add mappings
Updated `DtoMappingProfile` and `MappingProfiles` to explicitly inherit from `AutoMapper.Profile` for clarity. Added mapping configurations for `RecActionView` to `RecActionDto` and `CreateOutResCommand` to `OutRes` using `CreateMap` in their respective constructors.
2025-12-03 10:01:38 +01:00
50e092d9e2 Add JSON body/header support to CreateFakeAction method
Updated ActionController to support JSON serialization for
request body and headers in the CreateFakeAction method.
Replaced `bodyQuery` with `body` and `header` parameters,
serialized to JSON. Updated `BodyQuery` and added
`HeaderQuery` in `CreateRecActionCommand`. Refactored
endpoint URI construction and improved response handling.
2025-12-03 10:00:37 +01:00
1000be0c89 Add enhancements to ActionController and CreateFakeAction
Refactored ActionController to include IMediator dependency
in the constructor. Updated CreateFakeAction method to:
- Set a default value for `endpointUri`.
- Add an optional `endpointPath` parameter and logic to
  construct a full URI.
- Include the `Active` property in CreateRecActionCommand.
- Ensure `endpointUri` is non-nullable.

Also added the `System.IO` namespace for potential future use.
2025-12-03 09:52:25 +01:00
4d593e8a8e Add ExceptionHandlingMiddleware to request pipeline
Refactor `using` directives in `Program.cs` to include `ReC.API.Middleware` for middleware functionality.

Introduce `ExceptionHandlingMiddleware` to the HTTP request pipeline to handle exceptions globally. Suppress CS0618 warnings for its usage. No changes were made to the existing controller service configuration.
2025-12-03 09:34:49 +01:00
d239d43c1c Add handling for DataIntegrityException in middleware
Enhanced the ExceptionHandlingMiddleware to handle
DataIntegrityException explicitly. Added a new `using`
directive for `ReC.Application.Common.Exceptions` to
support this change. When a DataIntegrityException is
caught, the middleware now sets the HTTP status code to
409 Conflict and returns the exception's message.

Also updated the default case to ensure unhandled
exceptions are logged and return a generic error message
with a 500 Internal Server Error status code.
2025-12-03 09:34:02 +01:00
586b99ddc7 Add global exception handling middleware
Introduced `ExceptionHandlingMiddleware` to handle exceptions globally, log them, and return appropriate HTTP responses with JSON error messages.

- Added `HandleExceptionAsync` for exception processing.
- Handled `BadRequestException` (400) and `NotFoundException` (404).
- Included default handling for unhandled exceptions (500).
- Marked middleware as `[Obsolete]` with a note to use `DigitalData.Core.Exceptions.Middleware`.
- Added necessary `using` directives and XML documentation.
2025-12-03 09:31:14 +01:00
06af6dd43c Refactor CreateOutResCommandHandler for OutRes entity
Updated CreateOutResCommandHandler to use the OutRes entity
instead of the command itself in repository operations. Added
a new using directive for ReC.Domain.Entities. Updated the
CreateOutResCommand class to implement MediatR's IRequest
interface.
2025-12-03 09:29:02 +01:00
28 changed files with 393 additions and 84 deletions

View File

@ -1,42 +0,0 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.RecActions.Commands;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ActionController(IMediator mediator) : ControllerBase
{
[HttpPost("{profileId}")]
public async Task<IActionResult> Invoke([FromRoute] int profileId)
{
await mediator.InvokeBatchRecAction(profileId);
return Accepted();
}
[HttpPost]
public async Task<IActionResult> CreateAction([FromBody] CreateRecActionCommand command)
{
await mediator.Send(command);
return CreatedAtAction(nameof(CreateAction), null);
}
[HttpPost("fake")]
public async Task<IActionResult> CreateFakeAction(
string? endpointUri = null,
string type = "GET",
string bodyQuery = "SELECT NULL AS REQUEST_BODY;")
{
await mediator.Send(new CreateRecActionCommand()
{
ProfileId = 2,
EndpointUri = endpointUri,
Type = type,
BodyQuery = bodyQuery
});
return CreatedAtAction(nameof(CreateFakeAction), null);
}
}

View File

@ -0,0 +1,101 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Models;
using ReC.Application.RecActions.Commands;
using ReC.Application.RecActions.Queries;
using System.Text.Json;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class RecActionController(IMediator mediator) : ControllerBase
{
private const long FakeProfileId = 2;
[HttpPost("invoke/{profileId}")]
public async Task<IActionResult> Invoke([FromRoute] int profileId, CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(profileId, cancel);
return Accepted();
}
[HttpPost("invoke/fake")]
public async Task<IActionResult> Invoke(CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(FakeProfileId, cancel);
return Accepted();
}
#region CRUD
[HttpGet]
public async Task<IActionResult> Get([FromQuery] long profileId, CancellationToken cancel) => Ok(await mediator.Send(new ReadRecActionQuery()
{
ProfileId = profileId
}, cancel));
[HttpGet("fake")]
public async Task<IActionResult> Get(CancellationToken cancel) => Ok(await mediator.Send(new ReadRecActionQuery()
{
ProfileId = FakeProfileId
}, cancel));
[HttpPost]
public async Task<IActionResult> CreateAction([FromBody] CreateRecActionCommand command, CancellationToken cancel)
{
await mediator.Send(command, cancel);
return CreatedAtAction(nameof(CreateAction), null);
}
[HttpPost("fake")]
public async Task<IActionResult> CreateFakeAction(
CancellationToken cancel,
[FromBody] FakeRequest? request = null,
[FromQuery] string endpointUri = "https://jsonplaceholder.typicode.com/posts",
[FromQuery] string? endpointPath = "1",
[FromQuery] string type = "GET")
{
if (endpointPath is not null)
endpointUri = new Uri(new Uri(endpointUri.TrimEnd('/') + "/"), endpointPath.TrimStart('/')).ToString();
var bodyJson = request?.Body is not null ? JsonSerializer.Serialize(request.Body, options: new() { WriteIndented = false }) : null;
var headerJson = request?.Header is not null ? JsonSerializer.Serialize(request.Header, options: new() { WriteIndented = false }) : null;
await mediator.Send(new CreateRecActionCommand()
{
ProfileId = FakeProfileId,
EndpointUri = endpointUri,
Type = type,
BodyQuery = $@"SELECT '{bodyJson ?? "NULL"}' AS REQUEST_BODY;",
HeaderQuery = headerJson is not null ? $@"SELECT '{headerJson}' AS REQUEST_HEADER;" : null,
Active = true,
EndpointAuthId = 4
}, cancel);
return CreatedAtAction(nameof(CreateFakeAction), null);
}
[HttpDelete]
public async Task<IActionResult> Delete([FromQuery] int profileId, CancellationToken cancel)
{
await mediator.Send(new DeleteRecActionsCommand()
{
ProfileId = FakeProfileId
}, cancel);
return NoContent();
}
[HttpDelete("fake")]
public async Task<IActionResult> Delete(CancellationToken cancel)
{
await mediator.Send(new DeleteRecActionsCommand()
{
ProfileId = FakeProfileId
}, cancel);
return NoContent();
}
#endregion CRUD
}

View File

@ -0,0 +1,123 @@
using DigitalData.Core.Exceptions;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.Common.Exceptions;
using System.Net;
using System.Text.Json;
namespace ReC.API.Middleware;
//TODO: Fix and use DigitalData.Core.Exceptions.Middleware
/// <summary>
/// Middleware for handling exceptions globally in the application.
/// Captures exceptions thrown during the request pipeline execution,
/// logs them, and returns an appropriate HTTP response with a JSON error details.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions.Middleware")]
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionHandlingMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="logger">The logger instance for logging exceptions.</param>
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
/// <summary>
/// Invokes the middleware to handle the HTTP request.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context); // Continue down the pipeline
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _logger);
}
}
/// <summary>
/// Handles exceptions by logging them and writing an appropriate JSON response.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <param name="exception">The exception that occurred.</param>
/// <param name="logger">The logger instance for logging the exception.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
{
context.Response.ContentType = "application/json";
ValidationProblemDetails? details = null;
switch (exception)
{
case BadRequestException badRequestEx:
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
details = new()
{
Title = "Bad Request",
Detail = badRequestEx.Message
};
break;
case ValidationException validationEx:
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
var errors = validationEx.Errors
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
details = new ValidationProblemDetails()
{
Title = "Validation failed",
Errors = validationEx.Errors
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()),
};
break;
case NotFoundException notFoundEx:
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
details = new()
{
Title = "Not Found",
Detail = notFoundEx.Message
};
break;
case DataIntegrityException dataIntegrityEx:
context.Response.StatusCode = (int)HttpStatusCode.Conflict;
details = new()
{
Title = "Data Integrity Violation",
Detail = dataIntegrityEx.Message
};
break;
default:
logger.LogError(exception, "Unhandled exception occurred.");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
details = new()
{
Title = "Internal Server Error",
Detail = "An unexpected error occurred. Please try again later."
};
break;
}
if (details is not null)
await context.Response.WriteAsJsonAsync(details);
}
}

View File

@ -0,0 +1,8 @@
namespace ReC.API.Models;
public class FakeRequest
{
public Dictionary<string, object>? Body { get; init; }
public Dictionary<string, object>? Header { get; init; }
}

View File

@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using ReC.Infrastructure; using ReC.API.Middleware;
using ReC.Application; using ReC.Application;
using ReC.Infrastructure;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -15,11 +16,16 @@ builder.Services.AddRecServices(options =>
builder.Services.AddRecInfrastructure(options => builder.Services.AddRecInfrastructure(options =>
{ {
options.ConfigureDbContext((dbContextOpt) => options.ConfigureDbContext((provider, opt) =>
{ {
var connectionString = builder.Configuration.GetConnectionString("Default") var cnnStr = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string is not found."); ?? throw new InvalidOperationException("Connection string is not found.");
dbContextOpt.UseSqlServer(connectionString);
var logger = provider.GetRequiredService<ILogger<RecDbContext>>();
opt.UseSqlServer(cnnStr)
.LogTo(log => logger.LogInformation("{log}", log), LogLevel.Trace)
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}); });
}); });
@ -30,6 +36,10 @@ builder.Services.AddSwaggerGen();
var app = builder.Build(); var app = builder.Build();
#pragma warning disable CS0618
app.UseMiddleware<ExceptionHandlingMiddleware>();
#pragma warning restore CS0618
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {

View File

@ -5,8 +5,11 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL19\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"AllowedHosts": "*", "AllowedHosts": "*",
"MediatRLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg", "LuckyPennySoftwareLicenseKey": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg",
"RecAction": { "RecAction": {
"MaxConcurrentInvocations": 5 "MaxConcurrentInvocations": 5
}, },

View File

@ -5,11 +5,12 @@ using Microsoft.EntityFrameworkCore;
namespace ReC.Application.Common.Behaviors; namespace ReC.Application.Common.Behaviors;
public class BodyQueryBehavior<TRecAction>(IRecDbContext dbContext) : IPipelineBehavior<TRecAction, Unit> public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRecAction : RecActionDto where TRequest : RecActionDto
where TResponse : notnull
{ {
public async Task<Unit> Handle(TRecAction action, RequestHandlerDelegate<Unit> next, CancellationToken cancel) public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{ {
if (action.BodyQuery is null) if (action.BodyQuery is null)
return await next(cancel); return await next(cancel);

View File

@ -7,10 +7,11 @@ using System.Text.Json;
namespace ReC.Application.Common.Behaviors; namespace ReC.Application.Common.Behaviors;
public class HeaderQueryBehavior<TRecAction>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRecAction>>? logger = null) : IPipelineBehavior<TRecAction, Unit> public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse>
where TRecAction : RecActionDto where TRequest : RecActionDto
where TResponse : notnull
{ {
public async Task<Unit> Handle(TRecAction action, RequestHandlerDelegate<Unit> next, CancellationToken cancel) public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{ {
if (action.HeaderQuery is null) if (action.HeaderQuery is null)
return await next(cancel); return await next(cancel);

View File

@ -0,0 +1,30 @@
using FluentValidation;
using MediatR;
namespace ReC.Application.Common.Behaviors
{
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators) : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{
if (validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
validators.Select(v =>
v.ValidateAsync(context, cancel)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next(cancel);
}
}
}

View File

@ -3,7 +3,7 @@ using ReC.Domain.Entities;
namespace ReC.Application.Common.Dto; namespace ReC.Application.Common.Dto;
public class DtoMappingProfile : Profile public class DtoMappingProfile : AutoMapper.Profile
{ {
public DtoMappingProfile() public DtoMappingProfile()
{ {

View File

@ -0,0 +1,20 @@
namespace ReC.Application.Common.Dto;
public record OutResDto
{
public long Id { get; set; }
public long ActionId { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public string AddedWho { get; set; } = null!;
public DateTime AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@ -1,8 +1,10 @@
using Microsoft.Extensions.Configuration; using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ReC.Application.Common.Behaviors; using ReC.Application.Common.Behaviors;
using ReC.Application.Common.Options; using ReC.Application.Common.Options;
using System.Reflection; using System.Reflection;
using FluentValidation;
namespace ReC.Application; namespace ReC.Application;
@ -17,6 +19,8 @@ public static class DependencyInjection
configOpt.ApplyConfigurations(services); configOpt.ApplyConfigurations(services);
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddAutoMapper(cfg => services.AddAutoMapper(cfg =>
{ {
cfg.AddMaps(Assembly.GetExecutingAssembly()); cfg.AddMaps(Assembly.GetExecutingAssembly());
@ -26,7 +30,8 @@ public static class DependencyInjection
services.AddMediatR(cfg => services.AddMediatR(cfg =>
{ {
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.AddOpenBehaviors([typeof(BodyQueryBehavior<>), typeof(HeaderQueryBehavior<>)]); cfg.AddOpenBehaviors([typeof(BodyQueryBehavior<,>), typeof(HeaderQueryBehavior<,>)]);
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey; cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey;
}); });

View File

@ -3,10 +3,14 @@ using ReC.Domain.Entities;
namespace ReC.Application.Endpoints; namespace ReC.Application.Endpoints;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfile : AutoMapper.Profile public class MappingProfile : AutoMapper.Profile
{ {
public MappingProfile() public MappingProfile()
{ {
CreateMap<ObtainEndpointCommand, Endpoint>(); CreateMap<ObtainEndpointCommand, Endpoint>()
.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,5 +1,6 @@
using DigitalData.Core.Abstraction.Application.Repository; using DigitalData.Core.Abstraction.Application.Repository;
using MediatR; using MediatR;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults.Commands; namespace ReC.Application.OutResults.Commands;
@ -14,7 +15,7 @@ public class CreateOutResCommand : IRequest
public string? AddedWho { get; set; } public string? AddedWho { get; set; }
} }
public class CreateOutResCommandHandler(IRepository<CreateOutResCommand> repo) : IRequestHandler<CreateOutResCommand> public class CreateOutResCommandHandler(IRepository<OutRes> repo) : IRequestHandler<CreateOutResCommand>
{ {
public Task Handle(CreateOutResCommand request, CancellationToken cancel) public Task Handle(CreateOutResCommand request, CancellationToken cancel)
{ {

View File

@ -1,13 +1,15 @@
using AutoMapper; using ReC.Application.OutResults.Commands;
using ReC.Application.OutResults.Commands;
using ReC.Domain.Entities; using ReC.Domain.Entities;
namespace ReC.Application.OutResults; namespace ReC.Application.OutResults;
public class MappingProfiles : Profile // TODO: update to inject AddedWho from the current host/user contex
public class MappingProfiles : AutoMapper.Profile
{ {
public MappingProfiles() public MappingProfiles()
{ {
CreateMap<CreateOutResCommand, OutRes>(); CreateMap<CreateOutResCommand, OutRes>()
.ForMember(e => e.AddedWhen, exp => exp.MapFrom(cmd => DateTime.UtcNow))
.ForMember(e => e.AddedWho, exp => exp.MapFrom(cmd => "ReC.API"));
} }
} }

View File

@ -0,0 +1,8 @@
namespace ReC.Application.OutResults.Queries;
public class ReadOutResQuery
{
public long? ProfileId { get; set; }
public long? ActionId { get; set; }
}

View File

@ -0,0 +1,13 @@
using FluentValidation;
namespace ReC.Application.OutResults.Queries;
public class ReadOutResQueryValidator : AbstractValidator<ReadOutResQuery>
{
public ReadOutResQueryValidator()
{
RuleFor(x => x)
.Must(x => x.ActionId.HasValue || x.ProfileId.HasValue)
.WithMessage("At least one of ActionId or ProfileId must be provided.");
}
}

View File

@ -11,6 +11,8 @@
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" /> <PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" />
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" /> <PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" /> <PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
<PackageReference Include="FluentValidation" Version="12.1.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.0" />
<PackageReference Include="MediatR" Version="13.1.0" /> <PackageReference Include="MediatR" Version="13.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" />

View File

@ -1,6 +1,8 @@
using DigitalData.Core.Exceptions; using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR; using MediatR;
using ReC.Application.Endpoints.Commands; using ReC.Application.Endpoints.Commands;
using ReC.Domain.Entities;
namespace ReC.Application.RecActions.Commands; namespace ReC.Application.RecActions.Commands;
@ -19,9 +21,13 @@ public record CreateRecActionCommand : IRequest
public string? HeaderQuery { get; init; } public string? HeaderQuery { get; init; }
public string BodyQuery { get; init; } = null!; public string BodyQuery { get; init; } = null!;
public byte Sequence { get; set; } = 1;
public long? EndpointAuthId { get; set; }
} }
public class CreateRecActionCommandHandler(ISender sender) : IRequestHandler<CreateRecActionCommand> public class CreateRecActionCommandHandler(ISender sender, IRepository<RecAction> repo) : IRequestHandler<CreateRecActionCommand>
{ {
public async Task Handle(CreateRecActionCommand request, CancellationToken cancel) public async Task Handle(CreateRecActionCommand request, CancellationToken cancel)
{ {
@ -33,5 +39,7 @@ public class CreateRecActionCommandHandler(ISender sender) : IRequestHandler<Cre
} }
else else
throw new BadRequestException("Either EndpointId or EndpointUri must be provided."); throw new BadRequestException("Either EndpointId or EndpointUri must be provided.");
await repo.CreateAsync(request, cancel);
} }
} }

View File

@ -1,5 +1,7 @@
using DigitalData.Core.Abstraction.Application.Repository; using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR; using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Domain.Entities; using ReC.Domain.Entities;
namespace ReC.Application.RecActions.Commands; namespace ReC.Application.RecActions.Commands;
@ -11,8 +13,12 @@ public class DeleteRecActionsCommand : IRequest
public class DeleteRecActionsCommandHandler(IRepository<RecAction> repo) : IRequestHandler<DeleteRecActionsCommand> public class DeleteRecActionsCommandHandler(IRepository<RecAction> repo) : IRequestHandler<DeleteRecActionsCommand>
{ {
public Task Handle(DeleteRecActionsCommand request, CancellationToken cancel) public async Task Handle(DeleteRecActionsCommand request, CancellationToken cancel)
{ {
return repo.DeleteAsync(act => act.ProfileId == request.ProfileId, 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

@ -8,8 +8,8 @@ public record InvokeBatchRecActionsCommand : ReadRecActionQueryBase, IRequest;
public static class InvokeBatchRecActionsCommandExtensions public static class InvokeBatchRecActionsCommandExtensions
{ {
public static Task InvokeBatchRecAction(this ISender sender, int profileId) public static Task InvokeBatchRecAction(this ISender sender, long profileId, CancellationToken cancel = default)
=> sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId }); => sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId }, cancel);
} }
public class InvokeRecActionsCommandHandler(ISender sender, IHttpClientFactory clientFactory, ILogger<InvokeRecActionsCommandHandler>? logger = null) : IRequestHandler<InvokeBatchRecActionsCommand> public class InvokeRecActionsCommandHandler(ISender sender, IHttpClientFactory clientFactory, ILogger<InvokeRecActionsCommandHandler>? logger = null) : IRequestHandler<InvokeBatchRecActionsCommand>

View File

@ -3,10 +3,14 @@ using ReC.Domain.Entities;
namespace ReC.Application.RecActions; namespace ReC.Application.RecActions;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfile : AutoMapper.Profile public class MappingProfile : AutoMapper.Profile
{ {
public MappingProfile() public MappingProfile()
{ {
CreateMap<CreateRecActionCommand, RecAction>(); 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

@ -10,12 +10,17 @@ namespace ReC.Application.RecActions.Queries;
public record ReadRecActionQueryBase public record ReadRecActionQueryBase
{ {
public int ProfileId { get; init; } public long ProfileId { get; init; }
public ReadRecActionQuery ToReadQuery() => new(this); public ReadRecActionQuery ToReadQuery() => new(this);
} }
public record ReadRecActionQuery(ReadRecActionQueryBase Root) : ReadRecActionQueryBase(Root), IRequest<IEnumerable<RecActionDto>>; public record ReadRecActionQuery : ReadRecActionQueryBase, IRequest<IEnumerable<RecActionDto>>
{
public ReadRecActionQuery(ReadRecActionQueryBase root) : base(root) { }
public ReadRecActionQuery() { }
}
public class ReadRecActionQueryHandler(IRepository<RecActionView> repo, IMapper mapper) : IRequestHandler<ReadRecActionQuery, IEnumerable<RecActionDto>> public class ReadRecActionQueryHandler(IRepository<RecActionView> repo, IMapper mapper) : IRequestHandler<ReadRecActionQuery, IEnumerable<RecActionDto>>
{ {
@ -23,7 +28,7 @@ public class ReadRecActionQueryHandler(IRepository<RecActionView> repo, IMapper
{ {
var actions = await repo.Where(x => x.ProfileId == request.ProfileId).ToListAsync(cancel); var actions = await repo.Where(x => x.ProfileId == request.ProfileId).ToListAsync(cancel);
if(actions.Count != 0) if(actions.Count == 0)
throw new NotFoundException($"No actions found for the profile {request.ProfileId}."); throw new NotFoundException($"No actions found for the profile {request.ProfileId}.");
return mapper.Map<IEnumerable<RecActionDto>>(actions); return mapper.Map<IEnumerable<RecActionDto>>(actions);

View File

@ -1,5 +1,4 @@
using System; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities; namespace ReC.Domain.Entities;

View File

@ -1,5 +1,4 @@
using System; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities; namespace ReC.Domain.Entities;

View File

@ -1,5 +1,4 @@
using System; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities; namespace ReC.Domain.Entities;

View File

@ -1,5 +1,4 @@
using System; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities; namespace ReC.Domain.Entities;
@ -10,7 +9,7 @@ public class Profile
[Key] [Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")] [Column("GUID")]
public short? Id { get; set; } public long Id { get; set; }
[Column("ACTIVE")] [Column("ACTIVE")]
public bool? Active { get; set; } public bool? Active { get; set; }

View File

@ -31,9 +31,9 @@ public static class DependencyInjection
public class ConfigurationOptions public class ConfigurationOptions
{ {
internal Action<DbContextOptionsBuilder>? DbContextOptionsAction { get; private set; } internal Action<IServiceProvider, DbContextOptionsBuilder>? DbContextOptionsAction { get; private set; }
public ConfigurationOptions ConfigureDbContext(Action<DbContextOptionsBuilder> optionsAction) public ConfigurationOptions ConfigureDbContext(Action<IServiceProvider, DbContextOptionsBuilder> optionsAction)
{ {
DbContextOptionsAction = optionsAction; DbContextOptionsAction = optionsAction;
return this; return this;