Compare commits

...

162 Commits

Author SHA1 Message Date
Developer 02
9628b46ba0 Add project description to ReC.Client.csproj
A `<Description>` tag was added to the `ReC.Client.csproj` file.
This tag describes the project as a client library for interacting
with the ReC.API, offering typed HTTP access and DI integration.
This change improves project metadata for better documentation
and package management.
2025-12-06 00:43:30 +01:00
Developer 02
1f8142852e Add packaging metadata and solution items project
Added a new "Solution Items" project to the solution file, including an `assets/icon.png` file. Updated `ReC.Client.csproj` with packaging metadata such as `PackageId`, `Authors`, `Company`, and `Version`. Included `icon.png` in the package and updated `<TargetFrameworks>` to support `net8.0`. Added the `icon.png` file to the project.
2025-12-06 00:41:48 +01:00
Developer 02
bdd78be66c Add static BuildStaticClient method with Obsolete warning
A new static method `BuildStaticClient(Action<HttpClient> configureClient)`
was added to the `ReC.Client` namespace in `ReCClient.cs`. This method
configures and builds a static `IServiceProvider` for creating `ReCClient`
instances. It includes XML documentation detailing its purpose, usage,
and parameters, and warns that it should only be called once during
application startup.

The method accepts an `Action<HttpClient>` parameter for `HttpClient`
configuration and throws an `InvalidOperationException` if the static
provider is already built. It is marked `[Obsolete]` to encourage the
use of a local service collection instead of the static provider.

Additionally, the XML documentation for the `ReCClient` creation method
was updated to reference the new `BuildStaticClient` method.
2025-12-06 00:13:50 +01:00
Developer 02
470902911e Refactor HTTP client setup in BuildStaticClient
Simplified the HTTP client configuration in the `BuildStaticClient` method of the `ReC.Client` namespace. Replaced the explicit use of `Services.AddHttpClient` with a call to `Services.AddRecClient(apiUri)`. This change improves code readability and reusability by encapsulating the HTTP client setup logic in a dedicated method or extension.
2025-12-06 00:12:36 +01:00
Developer 02
3f7ebdb632 Simplify ReCClient instantiation in Create method
Refactored the `Create` method in the `ReCClient` class to directly resolve a `ReCClient` instance from the dependency injection container using `Provider.GetRequiredService<ReCClient>()`.

Removed the intermediate step of retrieving an `IHttpClientFactory` and manually creating a `ReCClient` object. This change reduces boilerplate code and assumes `ReCClient` is already registered in the container, improving maintainability.
2025-12-06 00:10:50 +01:00
Developer 02
23ef1a5797 Add scoped ReCClient and update project dependencies
Added `services.AddScoped<ReCClient>()` to both `AddRecClient`
method overloads in `DependencyInjection.cs` to ensure proper
scoped registration of `ReCClient`.

Updated `ReC.Client.csproj` to include necessary package
references for dependency injection and HTTP client support:
- `Microsoft.Extensions.DependencyInjection` (v10.0.0)
- `Microsoft.Extensions.Http` (v8.0.0)
- `System.Net.Http` (v4.3.4)
2025-12-06 00:10:08 +01:00
Developer 02
4a7f2a41fa Mark static provider methods as obsolete
The `BuildStaticClient` and `Create` methods in the `ReC.Client` namespace have been marked with the `[Obsolete]` attribute. These methods now include a message advising developers to use a local service collection instead of the static provider. This change serves as a warning that these methods are outdated and may be removed in future versions, encouraging a transition to a more modern design.
2025-12-06 00:02:30 +01:00
Developer 02
5f9e716ca6 Enhance ReC.Client library with new features and cleanup
- Added comprehensive XML documentation for `DependencyInjection`
  and `ReCClient` classes to improve code readability.
- Introduced overload for `AddRecClient` to allow flexible
  `HttpClient` configuration.
- Added static `BuildStaticClient` and `Create` methods for
  simplified client instantiation.
- Marked synchronous `InvokeRecAction` method as obsolete to
  encourage asynchronous usage.
- Updated `ReC.Client.csproj`:
  - Added `<DocumentationFile>` property for XML doc generation.
  - Downgraded `Microsoft.Extensions.Http` to version 8.0.0.
  - Removed `System.Text.Json` package reference.
- Removed `System.Text.Json` dependency from `ReCClient.cs`.
- Generated unique `HttpClient` name for `ReCClient` instances.
- Performed general code cleanup and improved method remarks.
2025-12-05 23:59:11 +01:00
Developer 02
91c8b98f44 Refactor ReCClient.Static to Create() method
Replaced the `ReCClient.Static` property with a `ReCClient.Create()` method to improve clarity and align with best practices. The new method retains the same functionality, including throwing an `InvalidOperationException` if the `Provider` is not built. The logic for retrieving the `IHttpClientFactory` and creating a `ReCClient` instance remains unchanged.
2025-12-05 23:54:16 +01:00
Developer 02
10fc56b262 Refactor: Rename ServiceProvider to Provider
Renamed the static field `ServiceProvider` to `Provider` across
the `ReC.Client` namespace in `ReCClient.cs` for consistency
and clarity. Updated all occurrences, including declarations,
usages, and exception messages. Adjusted conditional compilation
blocks to reflect the new name. Ensured the `BuildStaticClient`
method and `Static` property use the renamed field appropriately.
2025-12-05 23:52:25 +01:00
Developer 02
71368e5c85 Refactor ServiceProvider with conditional compilation
Updated the `ServiceProvider` field in the `ReC.Client` namespace to use conditional compilation for framework-specific behavior:
- Added `NET8_0_OR_GREATER` directive to define `ServiceProvider` as nullable (`IServiceProvider?`) for .NET 8.0 or greater.
- Retained non-nullable `IServiceProvider` for other frameworks.
- Removed redundant `#if nullable` directives to simplify the code.
- Streamlined initialization logic for better clarity and maintainability.
2025-12-05 23:51:03 +01:00
Developer 02
fb649a5c68 Add static DI-based ReCClient initialization
Introduced dependency injection to the `ReCClient` class by adding `Microsoft.Extensions.DependencyInjection`. Added a static mechanism for creating and managing a `ReCClient` instance, including a `BuildStaticClient` method to configure the HTTP client and a `Static` property to retrieve the client. Implemented error handling to ensure proper initialization of the static service provider.
2025-12-05 23:47:28 +01:00
Developer 02
f4abac1103 Mark InvokeRecAction as obsolete with guidance
Added the `[Obsolete]` attribute to the `InvokeRecAction` method
in the `ReC.Client` namespace to discourage its use. The attribute
recommends using the `InvokeRecActionAsync` method instead to
avoid potential deadlocks and improve performance.
2025-12-05 23:39:22 +01:00
Developer 02
4c8e9be695 Add synchronous InvokeRecAction method
Added a synchronous version of the `InvokeRecAction` method to
the `ReC.Client` namespace in `ReCClient.cs`. This method
invokes the `POST api/RecAction/invoke/{profileId}` endpoint
synchronously using `GetAwaiter().GetResult()` and returns a
boolean indicating success.

Also included XML documentation comments for the new method,
detailing its purpose, parameters, and return value.
2025-12-05 23:37:53 +01:00
Developer 02
b190e4f5e9 Update InvokeRecActionAsync to return success status
The method `InvokeRecActionAsync` in the `ReC.Client` namespace was updated to return a `bool` instead of `Task`. This change allows the method to indicate whether the HTTP request was successful.

- Updated the return type from `Task` to `Task<bool>`.
- Modified the `<returns>` XML documentation to reflect the new behavior.
- Replaced `resp.EnsureSuccessStatusCode()` with `resp.IsSuccessStatusCode` to return the success status directly.
2025-12-05 23:36:45 +01:00
Developer 02
4a1b221478 Rename 'ct' to 'cancellationToken' for clarity
Updated the parameter name in the `InvokeRecActionAsync` method
to `cancellationToken` for better readability and alignment
with standard naming conventions. Adjusted the method signature
and internal usage to reflect this change.
2025-12-05 23:31:17 +01:00
Developer 02
475acc0a56 Remove InvokeRecActionFakeAsync method
Removed the `InvokeRecActionFakeAsync` method, which was used to invoke a fake RecAction via the `api/RecAction/invoke/fake` endpoint. This method is no longer needed or relevant.

The `InvokeRecActionAsync` method remains in place to handle RecActions for specific profile IDs via the `api/RecAction/invoke/{profileId}` endpoint.
2025-12-05 23:29:43 +01:00
Developer 02
f8dd16454f Add AddRecClient method and conditional using directive
Added a `using System.Net.Http;` directive within a `#if NETFRAMEWORK` block in `DependencyInjection.cs` to ensure compatibility with .NET Framework.

Introduced the `AddRecClient` extension method in the `DependencyInjection` class to enable custom configuration of `HttpClient` instances via an `Action<HttpClient>` delegate.
2025-12-05 23:24:44 +01:00
Developer 02
8cad1df95d Add DependencyInjection support for ReCClient
Introduced a `DependencyInjection` class in the `ReC.Client`
namespace to enable dependency injection for `ReCClient`.

Added an `AddRecClient` extension method for `IServiceCollection`
to register an `HttpClient` with a configurable `apiUri` as the
base address.

Included conditional `using System;` for .NET Framework
compatibility and added `Microsoft.Extensions.DependencyInjection`
to support DI services.
2025-12-05 23:23:17 +01:00
Developer 02
4ee79d6cd8 Refactor ReCClient for cleaner and more flexible design
- Removed unused `System.Xml.Linq` namespace import.
- Simplified code by removing `#if NETFRAMEWORK` directives.
- Added `ClientName` field to uniquely identify HTTP clients.
- Updated constructor to use named HTTP client with `ClientName`.
- Removed unused `_jsonOptions` field for better code clarity.
2025-12-05 23:17:45 +01:00
Developer 02
c0c0650fee Refactor ReCClient to use IHttpClientFactory
Updated ReCClient to use IHttpClientFactory for improved
resource management and testability. Added package
references for `Microsoft.Extensions.Http`, `System.Net.Http`,
and `System.Text.Json` to support HTTP client functionality
and JSON serialization. Included `System.Xml.Linq` for
potential XML-related operations. Updated constructor logic
to create `HttpClient` instances via the factory pattern.
2025-12-05 23:11:35 +01:00
Developer 02
73059d4554 Add InvokeRecActionFakeAsync method to ReCClient
A new asynchronous method `InvokeRecActionFakeAsync` was added to the `ReC.Client` namespace in `ReCClient.cs`. This method sends a POST request to the `api/RecAction/invoke/fake` endpoint. It includes XML documentation comments and accepts an optional `CancellationToken` parameter. The method ensures the response has a successful status code.
2025-12-05 23:03:03 +01:00
Developer 02
e774afc85e Add InvokeRecActionAsync method to ReCClient class
Added the `InvokeRecActionAsync` method to the `ReCClient` class in the `ReC.Client` namespace. This method performs an asynchronous HTTP POST request to the `api/RecAction/invoke/{profileId}` endpoint. It accepts a `profileId` parameter to specify the profile ID and an optional `CancellationToken` for task cancellation. The method ensures the HTTP response indicates success by calling `EnsureSuccessStatusCode()`. Included XML documentation comments for clarity and maintainability.
2025-12-05 23:02:45 +01:00
Developer 02
1fc4570210 Add ReCClient class and update project dependencies
Added `ReCClient` class to handle HTTP requests with JSON
serialization/deserialization support. Updated `ReC.Client.csproj`
to include `System.Net.Http` (v4.3.4) and `System.Text.Json`
(v9.0.11) as package references. Introduced conditional `using`
directives for `NETFRAMEWORK` compatibility.
2025-12-05 22:45:19 +01:00
b71ea7d346 Update log file naming convention in NLog config
The `logFileNamePrefix` variable in the `NLog` configuration was updated to use a period (`.`) instead of a hyphen (`-`) as the separator between the date and the application name.

Old format: `${shortdate}-Rec.API.Web`
New format: `${shortdate}.Rec.API`

This change aligns the log file naming convention with a new standard or improves consistency across file naming practices.
2025-12-05 10:32:11 +01:00
3764fdaf01 Enhance logging and refactor app startup
- Integrated `NLog` for improved logging capabilities.
- Added a logger instance and initialized logging with `NLog`.
- Wrapped app setup in a `try-catch` block to handle startup exceptions.
- Configured logging to use `NLog` in non-development environments.
- Refactored service registration for better organization.
- Retained key configurations for `AddRecServices` and `AddRecInfrastructure`.
- Reorganized middleware pipeline setup for clarity.
- Added exception logging during startup to improve debugging.
2025-12-05 10:27:37 +01:00
5e7287bf86 Update copyright and add NLog dependencies
Updated the copyright year to 2025 for "Digital Data GmbH."
Added `NLog` (v5.2.5) and `NLog.Web.AspNetCore` (v5.3.0)
package references to introduce logging capabilities.
No functional changes were made to the `Swashbuckle.AspNetCore`
package reference, though it was reformatted slightly.
2025-12-05 10:00:58 +01:00
9992086e48 Add NLog configuration and minor property updates
Added a new NLog configuration section in `appsettings.json` to enable structured logging. This includes settings for log file storage, log targets for different levels (Info, Warn, Error, Fatal), and retention policies.

Added the `NLog` property to the configuration. Introduced a new `AddedWho` property with the value `ReC.API`.

Removed and re-added the `FakeProfileId` property without changing its value. No changes were made to `LuckyPennySoftwareLicenseKey` or `RecAction` sections.
2025-12-05 09:58:05 +01:00
40019bf693 Update build package path for dynamic versioning
The `<DesktopBuildPackageLocation>` property in `IISProfile.pubxml`
was updated to remove the hardcoded `net8` subdirectory. The path
now dynamically uses the `$(Version)` variable directly under the
`API` directory, improving flexibility and reducing the need for
manual updates when versions change.
2025-12-05 09:37:47 +01:00
4b9e577d41 Add IIS publish profile configuration
Added an XML declaration and structured the IIS publish profile
with a `<Project>` root element and a `<PropertyGroup>`
containing key properties for web publishing. Configured the
publish method as `Package`, set the build configuration to
`Release`, and specified the IIS deployment path. Included
settings for packaging as a single file and defined the build
package location with a version placeholder.
2025-12-05 09:35:45 +01:00
4e6cb20dc2 Add metadata for NuGet packaging in ReC.API.csproj
Enhanced the project file (`ReC.API.csproj`) with metadata
to prepare the package for distribution. Added properties
such as `PackageId`, `Authors`, `Company`, `Product`,
`PackageIcon`, `PackageTags`, `Version`, `AssemblyVersion`,
`FileVersion`, `InformationalVersion`, and `Copyright`.
These changes ensure the package contains detailed
information for consumers and aligns with NuGet standards.
2025-12-05 09:29:58 +01:00
9a12643eb6 Refactor RecActionController and DeleteRecActionsCommand
Updated the `Invoke` method in `RecActionController` to use `cmd` as the route parameter instead of `profileId`, modifying the HTTP POST route to `invoke/{cmd}`.

Refactored the `Delete` method to accept a `DeleteRecActionsCommand` object (`cmd`) instead of `profileId`. Removed the hardcoded `ProfileId` assignment and passed the `cmd` object directly to `mediator.Send`.

Made the `ProfileId` property in `DeleteRecActionsCommand` required by removing its default value, enforcing explicit initialization. This improves validation and ensures flexibility for future enhancements.

These changes enhance the API's clarity, flexibility, and maintainability.
2025-12-04 15:37:06 +01:00
f9c0a8be55 Refactor Get method to accept query object directly
Simplified the `Get` method in `RecActionController` by replacing
the `profileId` parameter with a `ReadRecActionQuery` object.
This eliminates the need to manually construct the query object
within the method, improving code readability and reducing
redundancy.
2025-12-04 15:27:43 +01:00
77fde199e1 Update invoked parameter binding in Get method
The `Get` method in the `RecActionController` class was updated to use the `[FromQuery]` attribute for the `invoked` parameter. This change ensures that the parameter value is explicitly bound from the query string of the HTTP request, improving clarity and alignment with expected usage.
2025-12-04 15:25:50 +01:00
9d15dfe8a5 Add 'invoked' parameter to Get method in controller
Updated the Get method in RecActionController to include a
new optional parameter, `invoked`, with a default value of
`false`. Updated XML documentation to reflect this change.

Modified the `ReadRecActionQuery` initialization to pass the
`invoked` parameter. Removed the previous method signature
that only accepted a `CancellationToken`.
2025-12-04 15:17:38 +01:00
c41c394f48 Refactor query handling for dynamic customization
Updated `InvokeBatchRecActionsCommandExtensions` to filter actions with `Invoked = false` using a lambda in `ToReadQuery`.

Refactored `ReadRecActionQueryBase` to remove the `Invoked` property and updated `ToReadQuery` to accept a delegate for external query modifications.

Moved the `Invoked` property to `ReadRecActionQuery` and added a parameterless constructor.

These changes improve flexibility and enable dynamic query customization.
2025-12-04 15:15:09 +01:00
34d0741ac8 Add Invoked filter to ReadRecActionQuery handler
Introduced a nullable `Invoked` property in `ReadRecActionQueryBase`
to enable conditional filtering of actions based on `Root.OutRes`.
Added a `ToReadQuery` method for easier query conversion.

Refactored `ReadRecActionQueryHandler` to apply dynamic filtering
based on the `Invoked` property:
- `true`: Filters actions with non-null `Root.OutRes`.
- `false`: Filters actions with null `Root.OutRes`.
- `null`: No additional filtering applied.

Replaced hardcoded filtering logic with the new dynamic approach.
2025-12-04 14:50:25 +01:00
0f3fd320b0 Refactor RootAction to Root in RecActionView
Renamed the `RootAction` property to `Root` in the `RecActionView` class to improve clarity and align with naming conventions. Updated the `ReadRecActionQueryHandler` class to reference the renamed property in its LINQ query, ensuring consistency between the data model and query logic.
2025-12-04 14:46:30 +01:00
3b6df031a6 Add filtering for non-null OutRes in ReadRecActionQuery
Enhanced the `Handle` method in `ReadRecActionQueryHandler`
to include an additional filtering condition when querying
the repository. The query now filters actions where
`RootAction.OutRes` is not null, in addition to matching
the `ProfileId`. This ensures that only relevant actions
with a valid `OutRes` are included in the result set.
2025-12-04 14:46:13 +01:00
b00902e461 Add one-to-one relationship between RecAction and OutRes
Introduced a new `OutRes` navigation property in the `RecAction`
class to establish a one-to-one relationship with the `OutRes`
entity. Updated `RecDbContext` to configure this relationship
using the Fluent API. The `OutRes` entity references its
associated `RecAction` via the `Action` navigation property,
with `ActionId` as the foreign key.
2025-12-04 14:42:00 +01:00
74f4d06031 Add RootAction navigation property to RecActionView
Introduced a new nullable `RootAction` property in the `RecActionView` class. This property is decorated with the `[ForeignKey("Id")]` attribute, establishing a foreign key relationship with the `Id` property. This change enables the `RecActionView` entity to reference an optional `RecAction` entity, enhancing the data model and supporting entity relationships.
2025-12-04 14:32:36 +01:00
5ee3ca2d99 Add foreign key relationship to RecActionView
Introduced a `Profile` navigation property in the `RecActionView` class, linked to the `ProfileId` column using the `[ForeignKey]` attribute. The `Profile` property is nullable to support cases where no related `Profile` exists.
2025-12-04 14:20:54 +01:00
a62923c8d6 Enhance OutResController with new features and docs
Added XML documentation to `OutResController` methods for clarity.
Introduced `ResultType` enum to specify result parts (Full, Header, Body).
Enhanced `Get` methods to support fake/test profiles and actions.
Added `[ProducesResponseType(StatusCodes.Status200OK)]` for explicit
response status codes. Updated `Get` overloads for various scenarios.
Utilized `config.GetFakeProfileId()` for dynamic fake profile handling.
2025-12-04 14:07:17 +01:00
9901726f5a Add CancellationToken support to OutResController methods
Updated `Get` methods in `OutResController` to include a
`CancellationToken` parameter, enabling support for request
cancellation during asynchronous operations. Updated all
`mediator.Send` calls to propagate the `CancellationToken`.

Simplified the "fake/{actionId}" route by removing the explicit
assignment of `HttpContext.Response.ContentType` for non-
`ResultType.Full` cases.
2025-12-04 14:01:47 +01:00
b8074cfaf1 Add endpoints for RecActions and improve documentation
Added new endpoints for invoking, retrieving, creating, and deleting
RecActions, including support for both specific and fake/test profiles.
Endpoints include:

- `Invoke` for batch invocation of RecActions.
- `Get` for retrieving RecActions by profile.
- `CreateAction` for creating new RecActions.
- `CreateFakeAction` for creating test RecActions.
- `Delete` for deleting RecActions by profile.

Enhanced all endpoints with XML documentation for clarity and added
`ProducesResponseType` attributes to specify expected HTTP status codes.
2025-12-04 14:00:05 +01:00
dbfae5cdad Refactor error handling in JsonExtensions
Replaced the `catch` block in the `JsonExtensions` class to suppress exceptions silently instead of returning the original string when JSON deserialization fails. The `return str;` statement was moved outside the `try-catch` block, making it the default return value. This change removes explicit handling of invalid JSON inputs and may impact debugging.
2025-12-04 13:54:48 +01:00
d02bebc6e2 Refactor and enhance JSON handling with extensions
Refactored `ConfigExtensions.cs` to move `ConfigurationExtensions`
to the `ReC.API.Extensions` namespace and re-added the
`GetFakeProfileId` method. Introduced `JsonExtensions.cs` with
a `JsonToDynamic` method for recursive JSON deserialization,
simplifying nested JSON handling. Updated `OutResController.cs`
and `RecActionController.cs` to use the new `JsonToDynamic`
method, improving code readability and reducing duplication.
Overall, modularized and cleaned up code for better maintainability.
2025-12-04 13:44:10 +01:00
9165f9d746 Add endpoint for filtered OutRes results by actionId
Introduced a new `HttpGet` endpoint `fake/{actionId}` in the
`OutResController` to support fetching OutRes data filtered
by `actionId` and an optional `resultType` parameter.

Added logic to handle different `resultType` values (`Full`,
`Header`, `Body`) and return the appropriate part of the
result. Updated response `ContentType` for non-`Full` results
and ensured null-safe handling for `Body` and `Header`.

Included a new `ResultType` enum and added necessary `using`
directives for the new functionality.
2025-12-04 13:19:43 +01:00
5a226bfcea Update query to return IEnumerable<OutResDto>
Reordered using directives in DtoMappingProfile.cs.
Added AutoMapper mapping for OutRes to OutResDto.
Modified ReadOutResQuery to return IEnumerable<OutResDto>.
Updated ReadOutResHandler to handle collection results:
- Changed Handle method to return IEnumerable<OutResDto>.
- Updated mapper.Map to map to a collection of OutResDto.
2025-12-04 11:54:38 +01:00
5cefe1457f Refactor to use scoped services in command handler
Updated `InvokeRecActionsCommandHandler` to use `IServiceScopeFactory` for creating scoped services. Replaced direct `IHttpClientFactory` usage with dynamic resolution of `ISender` within a service scope. Improved dependency injection adherence and ensured proper scoping of services. Added `Microsoft.Extensions.DependencyInjection` to `using` directives.
2025-12-04 11:48:22 +01:00
f4390d992a Refactor to use configurable FakeProfileId
Replaced hardcoded FakeProfileId with a dynamic value retrieved
from the configuration using a new GetFakeProfileId extension
method. Updated OutResController and RecActionController to
inject IConfiguration and use the new method. Added a new
HttpGet endpoint in OutResController for fake profile queries.

Modified appsettings.json to include a FakeProfileId setting
with a default value of 2. Introduced ConfigurationExtensions
to centralize configuration access logic, improving code
maintainability and flexibility.
2025-12-04 11:14:37 +01:00
906d99105c Add name to validation rule in ReadOutResQueryValidator
The validation rule in the `ReadOutResQueryValidator` class was updated to include a `WithName("Identifier")` call. This assigns a name ("Identifier") to the rule, improving the clarity and usability of validation error messages, especially in scenarios where multiple rules are applied and need to be distinguished.
2025-12-04 10:17:57 +01:00
3b4671c8e5 Add HttpGet endpoint to OutResController for data retrieval
Added a new asynchronous `Get` method to the `OutResController`
class in the `ReC.API.Controllers` namespace. This method handles
HTTP GET requests, accepts a `ReadOutResQuery` object as a query
parameter, and uses the `mediator` to process the query. The
result is returned in an HTTP 200 OK response. This change
enables retrieval of resources or data based on the provided
query parameters.
2025-12-04 10:14:33 +01:00
534b254d0a Add NotFoundException handling to ReadOutResHandler
Updated `using` directives to include `DigitalData.Core.Exceptions` for custom exception handling. Renamed `dtos` to `resList` for clarity. Added a check to throw `NotFoundException` when no results are found in the query. Updated the return logic to ensure mapping occurs only when results exist. These changes enhance error handling and improve code robustness.
2025-12-04 10:13:46 +01:00
2cd7c035eb Add OutResController with MediatR integration
Introduced a new `OutResController` in the `ReC.API.Controllers`
namespace to handle API requests. The controller uses MediatR
for request handling and is decorated with `[ApiController]`
and `[Route("api/[controller]")]` attributes.

Added a `Get` method to process `ReadOutResQuery` objects
from query parameters and return the result via MediatR.
Included necessary `using` directives for dependencies.
2025-12-04 10:11:20 +01:00
aa34bdd279 Add ProfileId filter and navigation property to OutRes
Enhanced `ReadOutResHandler` to support filtering by `ProfileId`
when querying `OutRes` entities. This was implemented by adding
a condition to filter results based on the `ProfileId` of the
associated `Action`.

Updated the `OutRes` class to include a navigation property
`Action`, annotated with `[ForeignKey]` to link it to the
`ActionId` column. This establishes a relationship between
`OutRes` and `RecAction`, improving data access and maintainability.
2025-12-04 10:06:56 +01:00
2de0b5bfa3 Refactor ReadOutResQuery and add query handler
Refactored `ReadOutResQuery` to a record type implementing
`IRequest<OutResDto>` for MediatR. Introduced `ReadOutResHandler`
to handle the query, leveraging `IRepository` and `IMapper` for
database access and mapping. Added support for filtering by
`ActionId` and asynchronous query execution. Streamlined design
by moving `ProfileId` and `ActionId` to immutable `init`
properties in the record.
2025-12-04 10:03:57 +01:00
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
8624742eb3 Add CreateFakeAction endpoint and update command defaults
The ActionController class was updated to include a new
CreateFakeAction endpoint. This endpoint accepts optional
parameters (endpointUri, type, and bodyQuery) and sends a
CreateRecActionCommand with hardcoded and default values.

The CreateRecActionCommand record was modified to remove
default values for ProfileId, Type, and BodyQuery. These
properties now require explicit initialization, with Type
and BodyQuery marked as non-nullable using the null! syntax.

These changes improve flexibility and add support for
creating "fake" actions via the new endpoint.
2025-12-01 16:48:05 +01:00
a27000a75b Refactor DeleteRecActionsCommandHandler.Handle method
Simplified the `Handle` method by removing the `async` modifier
and replacing the `await` call with a direct `return` statement
for `repo.DeleteAsync`. This optimization eliminates the
overhead of creating an async state machine, as no additional
asynchronous operations or logic are performed in the method.
2025-12-01 16:29:41 +01:00
e74ee56f42 Add DeleteRecActionsCommand and handler for deletion
Introduced `DeleteRecActionsCommand` and its handler to enable
deletion of `RecAction` entities based on `ProfileId`. Added
necessary `using` directives and organized the code under the
`ReC.Application.RecActions.Commands` namespace. The handler
uses dependency injection for the repository and performs
asynchronous deletion with cancellation support.
2025-12-01 16:19:51 +01:00
ac214dc8e1 Add CreateAction POST endpoint to ActionController
A new HTTP POST endpoint `CreateAction` was added to the
`ActionController` class. This endpoint accepts a
`CreateRecActionCommand` object from the request body, sends
it to the `mediator` for processing, and returns a `201 Created`
response using the `CreatedAtAction` method. This change
enables the creation of actions via HTTP POST requests,
enhancing the controller's functionality.
2025-12-01 16:16:35 +01:00
d6b914b9c8 Add new DbSet properties to RecDbContext
Expanded RecDbContext to include new DbSet properties for
managing `Connections`, `Endpoints`, `EndpointAuths`,
`Profiles`, and `RecActions`. These additions enable
interaction with corresponding database tables/entities.

Updated the `OnModelCreating` method to ensure proper
configuration of the context. Removed an extraneous
closing brace to maintain proper syntax.
2025-12-01 16:14:15 +01:00
d764668cd7 Add CreateAction endpoint to ActionController
Introduced a new asynchronous `CreateAction` method in the
`ActionController` class to handle HTTP POST requests for
creating "rec actions". The method accepts a `CreateRecActionCommand`
object from the request body, processes it using `mediator.Send`,
and returns a `201 Created` response using `CreatedAtAction`.
2025-12-01 16:10:57 +01:00
6208a1cf93 Set default ProfileId and clean up unused directives
Removed unused `using` directives for `DigitalData.Core.Abstraction.Application.Repository` and `ReC.Domain.Entities`. Reordered `using` directives for better organization. Updated the `ProfileId` property in `CreateRecActionCommand` to have a default value of `2`, improving code clarity and reducing the need for explicit initialization.
2025-12-01 16:07:52 +01:00
a7ad55e973 Add MappingProfile for CreateRecActionCommand mapping
Introduced a new `MappingProfile` class in the `ReC.Application.RecActions` namespace to define object-to-object mapping configurations using AutoMapper.

The profile maps `CreateRecActionCommand` to `RecAction`, enabling seamless data transformation between these types. Added necessary `using` directives for `ReC.Application.RecActions.Commands` and `ReC.Domain.Entities`.
2025-12-01 16:07:11 +01:00
918372371e Refactor GetOrCreateEndpointCommand to ObtainEndpointCommand
Replaced `GetOrCreateEndpointCommand` with `ObtainEndpointCommand`
to improve clarity and align with naming conventions. Removed
`GetOrCreateEndpointCommand` and its handler, and introduced
`ObtainEndpointCommand` with equivalent functionality.

Updated `MappingProfile.cs` to map `ObtainEndpointCommand` to
`Endpoint`. Refactored `CreateRecActionCommand.cs` to use the
new `ObtainEndpointCommand` for retrieving or creating `Endpoint`
entities.

These changes ensure consistent naming and maintain the same
behavior while improving code readability.
2025-12-01 16:00:41 +01:00
a962299c95 Refactor GetOrCreateCommand to GetOrCreateEndpointCommand
Renamed `GetOrCreateCommand` to `GetOrCreateEndpointCommand`
to improve clarity and consistency. Removed the old
`GetOrCreateCommand` class and handler, and introduced the
new `GetOrCreateEndpointCommand` class with equivalent
functionality.

Updated `MappingProfile` to reflect the new command name
and adjusted `CreateRecActionCommand` to use the renamed
command. Added the appropriate namespace for the new class
to ensure proper organization.
2025-12-01 15:56:25 +01:00
cacd5eddbe Make Id property in Endpoint class non-nullable
The `Id` property in the `Endpoint` class was changed from a
nullable `long?` to a non-nullable `long`. This ensures that
the `Id` property is always assigned a value and cannot be
`null`, improving data integrity and aligning with potential
database or application requirements.
2025-12-01 15:55:23 +01:00
d30feb6034 Add handler for CreateRecActionCommand with validation
Updated `CreateRecActionCommand` to implement `IRequest` for MediatR compatibility. Introduced `CreateRecActionCommandHandler` to process requests, including validation for `EndpointId` and `EndpointUri`. Added logic to fetch or create an endpoint when `EndpointUri` is provided. Updated `EndpointId` property to be mutable. Added necessary `using` directives and adjusted the namespace declaration.
2025-12-01 15:55:01 +01:00
c6f2437300 Refactor endpoint commands and mappings
Removed unused `using` directives across files for cleanup. Deleted `CreateEndpointCommand` and `Endpoint` classes as they are no longer needed. Added `GetOrCreateCommand` to handle endpoint retrieval or creation. Updated `MappingProfile` to remove mappings for `CreateEndpointCommand` and ensure proper inheritance from `AutoMapper.Profile`.
2025-12-01 15:48:58 +01:00
46664d62ba Refactor GetOrCreateCommand and add AutoMapper profile
Refactored the `GetOrCreateCommand` to simplify the creation
of `Endpoint` entities by delegating the creation logic to
`repo.CreateAsync`.

Added a new `MappingProfile` class using AutoMapper to define
mappings between command objects (`CreateEndpointCommand` and
`GetOrCreateCommand`) and the `Endpoint` entity, improving
code maintainability and reducing boilerplate.

Included necessary `using` directives in `MappingProfile.cs`
to support the new mapping functionality.
2025-12-01 15:47:58 +01:00
4d6df7ccc8 Add CreateEndpointCommand and Endpoint classes
Introduced the `CreateEndpointCommand` class as a placeholder for future implementation. Added the `Endpoint` class with properties `Active`, `Description`, and `Uri` to represent endpoint details. Included necessary `using` directives and organized the code under the `ReC.Application.Endpoints.Commands` namespace.
2025-12-01 15:45:04 +01:00
79e5097590 Add GetOrCreateCommandHandler for endpoint management
Introduced `GetOrCreateCommandHandler` to handle `GetOrCreateCommand` requests.
The handler checks if an `Endpoint` with the specified `Uri` exists in the repository
and returns it if found. If not, it creates a new `Endpoint`, saves it, and returns
the newly created object. Added necessary `using` directives for repository abstraction
and Entity Framework Core functionality.
2025-12-01 15:41:38 +01:00
594057c4db Add GetOrCreateCommand for endpoint handling
Introduced the `GetOrCreateCommand` class in the `ReC.Application.Endpoints.Commands` namespace. This class implements the `IRequest<Endpoint>` interface from MediatR to handle requests for retrieving or creating an `Endpoint`.

Added `Uri` property to the command with an `init` accessor for immutability. Included necessary `using` directives for `MediatR` and `ReC.Domain.Entities`.
2025-12-01 15:34:25 +01:00
773b939a5c Rename Guid property to Id in EndpointParam class
The `Guid` property in the `EndpointParam` class was renamed to `Id` to improve code readability and align with naming conventions. The `[Key]` and `[Column("GUID")]` attributes remain unchanged, ensuring the database schema mapping and primary key designation are preserved.
2025-12-01 15:25:33 +01:00
8c47082c7f Make EndpointId nullable and add EndpointUri property
The `EndpointId` property in the `CreateRecActionCommand` class
was updated from a non-nullable `long` to a nullable `long?`
to allow it to hold `null` values.

Additionally, a new nullable `string?` property `EndpointUri`
was introduced to support specifying an optional endpoint URI.
2025-12-01 15:22:49 +01:00
9b22987397 Add CreateRecActionCommand record for RecActions
Introduced a new namespace `ReC.Application.RecActions.Commands`
and added the `CreateRecActionCommand` record. This record
includes properties such as `ProfileId`, `Active`, `EndpointId`,
`Type`, `HeaderQuery`, and `BodyQuery` to encapsulate the
necessary data for creating RecActions. Default values were
provided for `Active`, `Type`, and `BodyQuery` to streamline
initialization.
2025-12-01 15:14:56 +01:00
86e599a102 Add SqlConnection navigation property to RecAction
Introduced a new `SqlConnection` property in the `RecAction` class, marked with the `[ForeignKey("SqlConnectionId")]` attribute. This establishes a foreign key relationship with the `SqlConnectionId` property, enabling ORM navigation between `RecAction` and the `Connection` entity.
2025-12-01 14:08:27 +01:00
420762a463 Add EndpointAuth navigation property to RecAction
The RecAction class in RecAction.cs now includes a new
navigation property, EndpointAuth, which is linked to the
EndpointAuthId column via the [ForeignKey] attribute. This
enhances the entity relationship by associating the
EndpointAuthId property with the EndpointAuth entity.
2025-12-01 14:07:43 +01:00
5584a82d8f Add foreign key relationship to RecAction class
Enhanced the `RecAction` class by adding a navigation property
(`Endpoint`) to represent the relationship with the `Endpoint`
entity. Annotated the `Endpoint` property with the
`[ForeignKey("EndpointId")]` attribute to establish the foreign
key mapping. This improves the data model by explicitly defining
the relationship between `RecAction` and `Endpoint`.
2025-12-01 14:06:52 +01:00
6eac642784 Add navigation property for Profile in RecAction
Introduced a `Profile` navigation property to the `RecAction` class, annotated with `[ForeignKey("ProfileId")]`. This establishes a foreign key relationship between the `ProfileId` property and the `Profile` entity, enabling ORM navigation between `RecAction` and `Profile`.
2025-12-01 14:06:24 +01:00
ffc96efd98 Rename ProfileName property to Name in Profile class
The `ProfileName` property in the `Profile` class was renamed to `Name` to improve clarity or consistency. The `[Column("PROFILE_NAME")]` attribute remains unchanged, ensuring the database column mapping is unaffected.
2025-12-01 14:05:03 +01:00
1960151f77 Add Profile entity class for database mapping
Introduced a new `Profile` class in the `ReC.Domain.Entities` namespace to represent the `TBREC_CFG_PROFILE` table in the `dbo` schema.

- Added Entity Framework annotations to map properties to database columns.
- Defined properties for fields such as `Id`, `Active`, `Type`, `ProfileName`, `Description`, and others.
- Used nullable types for all properties to allow null values.
- Marked `Id` as the primary key with auto-generation enabled.

This change enables ORM support for managing profile-related data.
2025-12-01 14:04:34 +01:00
a2af50e436 Add Connection entity mapped to TBDD_CONNECTION table
Introduce a new `Connection` class in the `ReC.Domain.Entities` namespace.
The class is mapped to the `TBDD_CONNECTION` database table and includes
properties for various columns such as `Id`, `Bezeichnung`, `SqlProvider`,
and others. Attributes like `[Key]`, `[Column]`, and `[DatabaseGenerated]`
are used for database mapping. Nullable types are used for all properties.
Added necessary `using` directives for annotations and schema mapping.
2025-12-01 14:00:20 +01:00
7ebe48204a Add EndpointAuth entity for database table mapping
Introduced the `EndpointAuth` class in the `ReC.Domain.Entities` namespace to represent the `TBREC_CFG_ENDPOINT_AUTH` database table.

- Added Entity Framework annotations for table and column mappings.
- Defined properties for all table columns, including `Id`, `Active`, `Description`, `Type`, `ApiKey`, `ApiValue`, `ApiKeyAddTo`, `Token`, `Username`, `Password`, `Domain`, `Workstation`, `AddedWho`, `AddedWhen`, `ChangedWho`, and `ChangedWhen`.
- Configured `Id` as the primary key with auto-generated values.
2025-12-01 13:56:43 +01:00
6ec6cd4621 Rename Guid to Id in RecAction class
Renamed the property `Guid` to `Id` in the `RecAction` class
to improve clarity and align with naming conventions or
database schema updates. No other changes were made to
the file.
2025-12-01 13:52:48 +01:00
e6cb835829 Add Endpoint entity class for database mapping
Introduced a new `Endpoint` class in the `ReC.Domain.Entities` namespace to represent the `TBREC_CFG_ENDPOINT` database table.

- Added Entity Framework annotations to map properties to database columns.
- Defined properties for `Id`, `Active`, `Description`, `Uri`, `AddedWho`, `AddedWhen`, `ChangedWho`, and `ChangedWhen`.
- Configured `Id` as the primary key with auto-generated values.
2025-12-01 13:52:20 +01:00
6fd438809f Add RecAction entity mapped to TBREC_CFG_ACTION table
Introduced the `RecAction` class in the `ReC.Domain.Entities`
namespace to represent the `TBREC_CFG_ACTION` database table.
Added data annotations for table and column mapping, including
a primary key (`Guid`) and other properties for database fields
such as `ProfileId`, `Active`, `Sequence`, and query-related
fields. Included auditing fields (`AddedWho`, `AddedWhen`,
`ChangedWho`, `ChangedWhen`) for tracking changes. All fields
are nullable to handle missing data gracefully.
2025-12-01 13:37:47 +01:00
4b9f375646 Refactor to use RecActionView instead of RecAction
Replaced `RecAction` with `RecActionView` across the codebase to align with the `VWREC_ACTION` database view. Updated mappings, interfaces, and repository registrations accordingly.

- Updated `DtoMappingProfile` to map `RecActionView` to `RecActionDto`.
- Modified `IRecDbContext` to use `DbSet<RecActionView>`.
- Refactored `ReadRecActionQueryHandler` to use `IRepository<RecActionView>`.
- Removed the `RecAction` class entirely.
- Updated `DependencyInjection` to register `RecActionView`.
- Adjusted `RecDbContext` to replace `RecAction` with `RecActionView` and configure it as a keyless entity.
- Introduced the `RecActionView` class, mirroring the structure of the removed `RecAction` class, with nullable properties for schema flexibility.
2025-12-01 13:32:57 +01:00
8ef48942a3 Remove unused using directives in two files
Cleaned up unused namespaces to improve code maintainability:
- Removed `System.Text.Json` from `CreateOutResCommand.cs`.
- Removed `Microsoft.Extensions.Logging` from `InvokeRecActionCommand.cs`.

This reduces unnecessary dependencies and improves readability.
2025-12-01 13:28:08 +01:00
1b9d6e7d8a Remove logger parameter from InvokeRecActionCommandHandler
The constructor of the `InvokeRecActionCommandHandler` class was updated to remove the optional `ILogger<InvokeRecActionsCommandHandler>? logger` parameter. This change simplifies the constructor by no longer requiring or accepting a logger instance.
2025-12-01 13:02:58 +01:00
a7cb237fb6 Refactor CreateOutResCommand properties
Removed unused System.Text.Json dependency and the `EmptyJson` field. Updated `ActionId` to be required during initialization. Changed `Header` and `Body` properties to nullable strings without default values.
2025-12-01 13:01:58 +01:00
e62dfbcc7a Improve error handling with DataIntegrityException
Introduce a new `DataIntegrityException` class to handle data
integrity issues. Replace the logging and early return for
`null` `RestType` in `InvokeRecActionCommandHandler` with
throwing the new exception. This ensures explicit error
handling and improves runtime issue detection.
2025-12-01 12:51:54 +01:00
a2ebbe83cb Make Action property non-nullable in InvokeRecActionCommand
Updated the `Action` property in the `InvokeRecActionCommand` class
to be non-nullable by initializing it with a default value (`= null!;`).
This change ensures better null safety and prevents potential null
reference issues. The `= null!;` syntax suppresses compiler warnings
while guaranteeing the property will be properly initialized before use.
2025-12-01 12:45:58 +01:00
de17d398ff Rename ActionId to Id across the codebase
Updated the `ActionId` property to `Id` in `RecActionDto`
and `RecAction` classes for consistency. Reflected this
change in all relevant files, including log messages,
property assignments, and database column mappings.
Standardized naming conventions to improve code clarity
and maintainability.
2025-12-01 12:30:57 +01:00
dc777a04f6 Refactor InvokeRecActionCommand to use composition
Refactored `InvokeRecActionCommand` to remove inheritance from
`RecActionDto` and introduced a new `Action` property to
encapsulate the data. Updated the `ToInvokeCommand` extension
method to align with this change.

Modified `InvokeRecActionCommandHandler` to reference the
`Action` property instead of directly accessing inherited
properties. Updated HTTP request creation logic and the
`CreateOutResCommand` instantiation to use the `Action` object.

This change improves code clarity and adheres to composition
over inheritance principles.
2025-12-01 12:30:24 +01:00
9c028c5e66 Make ActionId a required field across DTOs and entities
Updated the `RecActionDto`, `RecAction`, and `InvokeRecActionCommandHandler` to enforce `ActionId` as a required field. This change improves data integrity by removing nullable `ActionId` properties and eliminates the need for null checks or null-forgiveness operators.
2025-12-01 12:27:47 +01:00
211d369509 Add CreateOutResCommand handling to RecActionCommand
Enhanced InvokeRecActionCommandHandler to send a
CreateOutResCommand after processing HTTP responses.
Added dependencies for configuration and command handling.
Updated the constructor to include ISender and optional
IConfiguration for improved extensibility.
2025-12-01 11:39:41 +01:00
3301b35067 Add MediatR support and CreateOutResCommand handler
Integrated MediatR for command handling by modifying the
CreateOutResCommand class to implement the IRequest interface.
Added a static EmptyJson property for default JSON serialization
and updated Header and Body properties to use it. Introduced
CreateOutResCommandHandler to handle command creation using
IRepository asynchronously.
2025-12-01 11:28:57 +01:00
41d00036e4 Add AutoMapper profile for CreateOutResCommand mapping
Introduced a new `MappingProfiles` class in the `ReC.Application.OutResults` namespace to configure object-object mapping using the `AutoMapper` library.

Added a mapping configuration between `CreateOutResCommand` and `OutRes` using the `CreateMap` method. This simplifies data transformation between application layers.
2025-12-01 11:10:46 +01:00
bf5e2e1d25 Rename ResultHeader/ResultBody to Header/Body
Renamed the `ResultHeader` and `ResultBody` properties to `Header`
and `Body` in both `CreateOutResCommand` and `OutRes` classes
to improve naming consistency. Default values and database
column mappings remain unchanged.
2025-12-01 11:05:33 +01:00
c5709c148b Add CreateOutResCommand class for handling output results
Introduced the `CreateOutResCommand` class in the `ReC.Application.OutResults.Commands` namespace. This class includes properties for managing output results, such as `ActionId`, `ResultHeader`, `ResultBody`, and `AddedWho`. Added a static readonly `EmptyJson` property for initializing JSON fields with a serialized empty object. Included `System.Text.Json` for JSON serialization functionality.
2025-12-01 11:03:34 +01:00
d4bd8b0d1a Update appsettings.json configuration
Removed `MaxConcurrentInvocations` from the `RecAction` section.
Added a new property `AddedWho` with the value `"ReC.API"`.
2025-12-01 10:38:54 +01:00
3b4858f5b1 Refactor variable names for clarity in response handling
Renamed variables `body` to `resBody` and `headers` to `resHeaders`
to improve clarity and better reflect their association with the
response object. These changes enhance code readability and
maintainability without altering functionality.
2025-12-01 10:35:02 +01:00
2610fc8f07 Remove query execution tracking properties
Simplified the codebase by removing `BodyQueryExecuted`,
`HeaderQueryExecuted`, and related computed properties
(`IsBodyQueryReturnedNoData` and `IsHeaderQueryReturnedNoData`)
from `RecActionDto`. These properties were used to track
whether `BodyQuery` and `HeaderQuery` were executed and
whether they returned data.

Updated `BodyQueryBehavior` and `HeaderQueryBehavior` to
remove logic that set these properties. Also removed the
conditional block in `InvokeRecActionCommandHandler` that
logged warnings based on these properties.

These changes streamline the code and reflect a shift away
from tracking query execution and results at this level.
2025-12-01 10:33:08 +01:00
90e2460716 Refactor query behaviors for execution tracking
Refactored `BodyQueryBehavior` and `HeaderQueryBehavior` to introduce explicit execution tracking with new properties (`BodyQueryExecuted` and `HeaderQueryExecuted`). Updated `RecActionDto` to include computed properties (`IsBodyQueryReturnedNoData` and `IsHeaderQueryReturnedNoData`) for clearer null-checking logic. Removed the `IsReturnedNoData` tuple for simplicity.

Updated `InvokeRecActionCommandHandler` to use the new `IsBodyQueryReturnedNoData` property. Improved logging for better diagnostics and clarified handling of null results in query behaviors.
2025-12-01 10:10:41 +01:00
29f0a82f0f Enhance query handling and diagnostics
Improved robustness and error handling in `BodyQueryBehavior`
and `HeaderQueryBehavior` by adding null checks, logging,
and introducing the `IsReturnedNoData` flag to track query
results. Updated `RecActionDto` to include the new flag for
better state management. Enhanced HTTP request construction
in `InvokeRecActionCommand` to handle `Body` and `Headers`
more reliably and log potential issues with unexecuted
behaviors. These changes improve resilience, traceability,
and diagnostics across the application.
2025-12-01 09:55:06 +01:00
Developer 02
b78b3e43f4 Add support for custom headers in HTTP requests
Enhanced HTTP request handling by adding support for dynamically including custom headers from the `request.Headers` collection. Implemented a null check to ensure robustness and prevent null reference exceptions when processing headers. This change improves flexibility and customization of HTTP requests.
2025-12-01 09:45:41 +01:00
Developer 02
07afcf3aa2 Add logging for unexpected BodyQuery and IsReturnedNoData
Improve observability by adding a warning log when `request.BodyQuery` is not null but `request.IsReturnedNoData.BodyQuery` is false.

The log message highlights potential issues, such as skipped `BodyQueryBehavior` execution or missing control steps, and includes `ProfileId` and `ActionId` for better debugging context.
2025-11-29 01:12:03 +01:00
Developer 02
e0736ff6df Improve HeaderQueryBehavior error handling and logging
Updated HeaderQueryBehavior to handle null or missing
REQUEST_HEADER gracefully by logging warnings and setting
action.IsReturnedNoData.HeaderQuery to true instead of
throwing exceptions. Enhanced log messages for failed
RawHeader deserialization to improve clarity and added
pipeline continuity by calling await next(cancel).
Refactored log formatting for consistency.
2025-11-29 00:55:43 +01:00
Developer 02
0d801466cf Add properties for query tracking and post-processing
Added `IsReturnedNoData` property as a tuple to track the state
of `BodyQuery` and `HeaderQuery` data returns. Introduced
`PostprocessingQuery` property to support initialization of
post-processing queries. These changes enhance the functionality
of the `RecActionDto` class by enabling better query handling
and processing capabilities.
2025-11-29 00:36:03 +01:00
Developer 02
ff53be5d13 Throw exception for null RawHeader in HeaderQueryBehavior
Previously, the code continued execution when the RawHeader
property of the query result was null. This change introduces
an InvalidOperationException to handle this case, ensuring
that the absence of a valid RawHeader is treated as an error.
The exception message includes ProfileId and ActionId for
better debugging context.
2025-11-28 23:41:35 +01:00
Developer 02
cc787f445a Improve null-checking and error handling in Handle
Simplified null-checks in the `Handle` method of the
`BodyQueryBehavior` class using the null-conditional operator.
Enhanced the exception message to include `ProfileId` and
`ActionId` for better debugging context when `result?.RawBody`
is null.
2025-11-28 23:37:09 +01:00
Developer 02
97c57d4fb1 Add validation for BodyQuery result in Handle method
Previously, the `Handle` method in `BodyQueryBehavior<TRecAction>`
did not validate the `BodyQuery` result before setting `action.Body`.
This change introduces a check to ensure that the result and its
`RawBody` are not null. If either is null, an
`InvalidOperationException` is thrown with a clear error message.
This ensures `action.Body` is only set when a valid result is
retrieved, improving robustness and preventing potential null
reference issues.
2025-11-28 23:32:28 +01:00
Developer 02
ff3908cdd2 Support HTTP request body in InvokeRecActionCommandHandler
Added logic to handle non-null `request.Body` in HTTP requests.
Introduced a `StringContent` object to encapsulate the body
content and assigned it to the `Content` property of the
HTTP request. This ensures that the request body is included
when sending data to the specified endpoint.
2025-11-28 23:24:36 +01:00
6208f642be Use SingleOrDefaultAsync for stricter query constraints
Replaced FirstOrDefaultAsync with SingleOrDefaultAsync in
BodyQueryBehavior.cs and HeaderQueryBehavior.cs to enforce
that database queries return at most one result. This change
ensures an exception is thrown if multiple results are found,
making debugging and error handling more explicit.
2025-11-28 12:19:40 +01:00
48 changed files with 1523 additions and 191 deletions

View File

@@ -15,6 +15,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReC.Application", "src\ReC.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReC.Client", "src\ReC.Client\ReC.Client.csproj", "{DA3A6BDD-8045-478F-860B-D1F0EB97F02B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject
assets\icon.png = assets\icon.png
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -1,17 +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();
}
}

View File

@@ -0,0 +1,77 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.Application.OutResults.Queries;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class OutResController(IMediator mediator, IConfiguration config) : ControllerBase
{
/// <summary>
/// Gets output results based on the provided query parameters.
/// </summary>
/// <param name="query">The query to filter output results.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>A list of output results matching the query.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromQuery] ReadOutResQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, cancel));
/// <summary>
/// Gets output results for a fake/test profile.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>A list of output results for the fake profile.</returns>
[HttpGet("fake")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get(CancellationToken cancel) => Ok(await mediator.Send(new ReadOutResQuery()
{
ProfileId = config.GetFakeProfileId()
}, cancel));
/// <summary>
/// Gets a specific output result for a fake/test profile and action.
/// </summary>
/// <param name="actionId">The ID of the action to retrieve the result for.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <param name="resultType">Specifies which part of the result to return (Full, Header, or Body).</param>
/// <returns>The requested output result or a part of it (header/body).</returns>
[HttpGet("fake/{actionId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromRoute] long actionId, CancellationToken cancel, ResultType resultType = ResultType.Full)
{
var res = (await mediator.Send(new ReadOutResQuery()
{
ProfileId = config.GetFakeProfileId(),
ActionId = actionId
}, cancel)).First();
return resultType switch
{
ResultType.Body => res.Body is null ? Ok(new object { }) : Ok(res.Body.JsonToDynamic()),
ResultType.Header => res.Header is null ? Ok(new object { }) : Ok(res.Header.JsonToDynamic()),
_ => Ok(res),
};
}
}
/// <summary>
/// Defines the type of result to be returned from an output result query.
/// </summary>
public enum ResultType
{
/// <summary>
/// Return the full result object.
/// </summary>
Full,
/// <summary>
/// Return only the header part of the result.
/// </summary>
Header,
/// <summary>
/// Return only the body part of the result.
/// </summary>
Body
}

View File

@@ -0,0 +1,151 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
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, IConfiguration config) : ControllerBase
{
/// <summary>
/// Invokes a batch of RecActions for a given profile.
/// </summary>
/// <param name="profileId">The ID of the profile.</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/{cmd}")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> Invoke([FromRoute] int profileId, CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(profileId, cancel);
return Accepted();
}
/// <summary>
/// Invokes a batch of RecActions for a fake/test profile.
/// </summary>
/// <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/fake")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> Invoke(CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(config.GetFakeProfileId(), cancel);
return Accepted();
}
#region CRUD
/// <summary>
/// Gets all RecActions for a given profile.
/// </summary>
/// <param name="profileId">The ID of the profile.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>A list of RecActions for the specified profile.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromQuery] ReadRecActionQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, cancel));
/// <summary>
/// Gets all RecActions for a fake/test profile.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <param name="invoked"></param>
/// <returns>A list of RecActions for the fake profile.</returns>
[HttpGet("fake")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get(CancellationToken cancel, [FromQuery] bool invoked = false) => Ok(await mediator.Send(new ReadRecActionQuery()
{
ProfileId = config.GetFakeProfileId(),
Invoked = invoked
}, cancel));
/// <summary>
/// Creates a new RecAction.
/// </summary>
/// <param name="command">The command containing the details for the new RecAction.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An HTTP 201 Created response.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> CreateAction([FromBody] CreateRecActionCommand command, CancellationToken cancel)
{
await mediator.Send(command, cancel);
return CreatedAtAction(nameof(CreateAction), null);
}
/// <summary>
/// Creates a new fake RecAction for testing purposes.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <param name="request">The optional request body and header for the fake action.</param>
/// <param name="endpointUri">The target endpoint URI.</param>
/// <param name="endpointPath">The optional path to append to the endpoint URI.</param>
/// <param name="type">The HTTP method type (e.g., GET, POST).</param>
/// <returns>An HTTP 201 Created response.</returns>
[HttpPost("fake")]
[ProducesResponseType(StatusCodes.Status201Created)]
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 = config.GetFakeProfileId(),
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);
}
/// <summary>
/// Deletes all RecActions associated with a specific profile.
/// </summary>
/// <param name="cmd"></param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An HTTP 204 No Content response upon successful deletion.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromQuery] DeleteRecActionsCommand cmd, CancellationToken cancel)
{
await mediator.Send(cmd, cancel);
return NoContent();
}
/// <summary>
/// Deletes all RecActions for a fake/test profile.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An HTTP 204 No Content response upon successful deletion.</returns>
[HttpDelete("fake")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete(CancellationToken cancel)
{
await mediator.Send(new DeleteRecActionsCommand()
{
ProfileId = config.GetFakeProfileId()
}, cancel);
return NoContent();
}
#endregion CRUD
}

View File

@@ -0,0 +1,6 @@
namespace ReC.API.Extensions;
public static class ConfigurationExtensions
{
public static int GetFakeProfileId(this IConfiguration config) => config.GetValue("FakeProfileId", 2);
}

View File

@@ -0,0 +1,53 @@
using System.Text.Json;
public static class JsonExtensions
{
/// <summary>
/// Deserialize JSON string and automatically parse nested JSON strings.
/// </summary>
public static dynamic? JsonToDynamic(this string json)
{
// Deserialize the top-level JSON
var result = JsonSerializer.Deserialize<JsonElement>(json);
// Recursively fix stringified JSON objects
return JsonToDynamic(result);
}
private static dynamic? JsonToDynamic(JsonElement obj)
{
switch (obj.ValueKind)
{
case JsonValueKind.Object:
var dict = new Dictionary<string, dynamic?>();
foreach (var prop in obj.EnumerateObject())
{
dict[prop.Name] = JsonToDynamic(prop.Value);
}
return dict;
case JsonValueKind.Array:
var list = new List<dynamic>();
foreach (var item in obj.EnumerateArray())
{
list.Add(JsonToDynamic(item));
}
return list;
case JsonValueKind.String:
var str = obj.GetString();
// Try to parse string as JSON
if (!string.IsNullOrWhiteSpace(str) && (str.StartsWith('{') || str.StartsWith('[')))
{
try
{
return JsonToDynamic(JsonSerializer.Deserialize<JsonElement>(str));
}
catch { }
}
return str;
default:
return obj.GetRawText();
}
}
}

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,46 +1,78 @@
using Microsoft.EntityFrameworkCore;
using ReC.Infrastructure;
using NLog;
using NLog.Web;
using ReC.API.Middleware;
using ReC.Application;
using ReC.Infrastructure;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
var builder = WebApplication.CreateBuilder(args);
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
var config = builder.Configuration;
// Add services to the container.
builder.Services.AddRecServices(options =>
try
{
options.LuckyPennySoftwareLicenseKey = builder.Configuration["LuckyPennySoftwareLicenseKey"];
options.ConfigureRecActions(config.GetSection("RecAction"));
});
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRecInfrastructure(options =>
{
options.ConfigureDbContext((dbContextOpt) =>
builder.Logging.SetMinimumLevel(LogLevel.Trace);
if (!builder.Environment.IsDevelopment())
{
var connectionString = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string is not found.");
dbContextOpt.UseSqlServer(connectionString);
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
var config = builder.Configuration;
// Add services to the container.
builder.Services.AddRecServices(options =>
{
options.LuckyPennySoftwareLicenseKey = builder.Configuration["LuckyPennySoftwareLicenseKey"];
options.ConfigureRecActions(config.GetSection("RecAction"));
});
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddRecInfrastructure(options =>
{
options.ConfigureDbContext((provider, opt) =>
{
var cnnStr = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string is not found.");
var app = builder.Build();
var logger = provider.GetRequiredService<ILogger<RecDbContext>>();
opt.UseSqlServer(cnnStr)
.LogTo(log => logger.LogInformation("{log}", log), LogLevel.Trace)
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
});
});
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
#pragma warning disable CS0618
app.UseMiddleware<ExceptionHandlingMiddleware>();
#pragma warning restore CS0618
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
catch(Exception ex)
{
logger.Error(ex, "Stopped program because of exception");
throw;
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<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>
<PackageAsSingleFile>true</PackageAsSingleFile>
<DeployIisAppPath>Rec.API</DeployIisAppPath>
<_TargetId>IISWebDeployPackage</_TargetId>
</PropertyGroup>
</Project>

View File

@@ -1,14 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>ReC.API</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>ReC.API</Product>
<PackageIcon>Assets\icon.ico</PackageIcon>
<PackageTags>digital data rest-caller rec api</PackageTags>
<Version>1.0.0-beta</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta</InformationalVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -5,9 +5,65 @@
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL19\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"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": {
"MaxConcurrentInvocations": 5
},
"AddedWho": "ReC.API",
"FakeProfileId": 2,
"NLog": {
"throwConfigExceptions": true,
"variables": {
"logDirectory": "E:\\LogFiles\\Digital Data\\Rec.API",
"logFileNamePrefix": "${shortdate}.Rec.API"
},
"targets": {
"infoLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"warningLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Warning.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
"maxArchiveDays": 30
},
"criticalLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
"maxArchiveDays": 30
}
},
"rules": [
{
"logger": "*",
"level": "Info",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Warn",
"writeTo": "warningLogs"
},
{
"logger": "*",
"level": "Error",
"writeTo": "errorLogs"
},
{
"logger": "*",
"level": "Fatal",
"writeTo": "criticalLogs"
}
]
}
}

View File

@@ -5,15 +5,17 @@ using Microsoft.EntityFrameworkCore;
namespace ReC.Application.Common.Behaviors;
public class BodyQueryBehavior<TRecAction>(IRecDbContext dbContext) : IPipelineBehavior<TRecAction, Unit>
where TRecAction : RecActionDto
public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
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)
return await next(cancel);
var result = await dbContext.BodyQueryResults.FromSqlRaw(action.BodyQuery).FirstOrDefaultAsync(cancel);
var result = await dbContext.BodyQueryResults.FromSqlRaw(action.BodyQuery).SingleOrDefaultAsync(cancel);
action.Body = result?.RawBody;
return await next(cancel);

View File

@@ -7,24 +7,32 @@ using System.Text.Json;
namespace ReC.Application.Common.Behaviors;
public class HeaderQueryBehavior<TRecAction>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRecAction>>? logger = null) : IPipelineBehavior<TRecAction, Unit>
where TRecAction : RecActionDto
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse>
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)
return await next(cancel);
var result = await dbContext.HeaderQueryResults.FromSqlRaw(action.HeaderQuery).FirstOrDefaultAsync(cancel);
var result = await dbContext.HeaderQueryResults.FromSqlRaw(action.HeaderQuery).SingleOrDefaultAsync(cancel);
if (result?.RawHeader is null)
{
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);
if(result?.RawHeader is null)
return await next(cancel);
}
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result.RawHeader);
if(headerDict is null)
{
logger?.LogWarning("Failed to deserialize header query result: {RawHeader}. Profile ID: {ProfileId}, Action ID: {ActionId}", result.RawHeader, action.ProfileId, action.ActionId);
logger?.LogWarning(
"Header JSON deserialization returned null. RawHeader: {RawHeader}, ProfileId: {ProfileId}, Id: {Id}",
result.RawHeader, action.ProfileId, action.Id);
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

@@ -1,12 +1,12 @@
using AutoMapper;
using ReC.Domain.Entities;
using ReC.Domain.Entities;
namespace ReC.Application.Common.Dto;
public class DtoMappingProfile : Profile
public class DtoMappingProfile : AutoMapper.Profile
{
public DtoMappingProfile()
{
CreateMap<RecAction, RecActionDto>();
CreateMap<RecActionView, RecActionDto>();
CreateMap<OutRes, OutResDto>();
}
}

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

@@ -2,7 +2,7 @@
public record RecActionDto
{
public long? ActionId { get; init; }
public required long Id { get; init; }
public long? ProfileId { get; init; }

View File

@@ -0,0 +1,8 @@
namespace ReC.Application.Common.Exceptions;
public class DataIntegrityException : Exception
{
public DataIntegrityException() { }
public DataIntegrityException(string message) : base(message) { }
}

View File

@@ -7,7 +7,7 @@ public interface IRecDbContext
{
public DbSet<EndpointParam> EndpointParams { get; }
public DbSet<RecAction> Actions { get; }
public DbSet<RecActionView> Actions { get; }
public DbSet<OutRes> OutRes { get; }

View File

@@ -1,8 +1,10 @@
using Microsoft.Extensions.Configuration;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ReC.Application.Common.Behaviors;
using ReC.Application.Common.Options;
using System.Reflection;
using FluentValidation;
namespace ReC.Application;
@@ -17,6 +19,8 @@ public static class DependencyInjection
configOpt.ApplyConfigurations(services);
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddAutoMapper(cfg =>
{
cfg.AddMaps(Assembly.GetExecutingAssembly());
@@ -26,7 +30,8 @@ public static class DependencyInjection
services.AddMediatR(cfg =>
{
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;
});

View File

@@ -0,0 +1,25 @@
using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Domain.Entities;
namespace ReC.Application.Endpoints.Commands;
public class ObtainEndpointCommand : IRequest<Endpoint>
{
public string Uri { get; init; } = null!;
}
public class ObtainEndpointCommandHandler(IRepository<Endpoint> repo) : IRequestHandler<ObtainEndpointCommand, Endpoint>
{
public async Task<Endpoint> Handle(ObtainEndpointCommand request, CancellationToken cancel)
{
var endpoint = await repo.Where(e => e.Uri == request.Uri).FirstOrDefaultAsync(cancel);
if (endpoint is not null)
return endpoint;
endpoint = await repo.CreateAsync(request, cancel);
return endpoint;
}
}

View File

@@ -0,0 +1,16 @@
using ReC.Application.Endpoints.Commands;
using ReC.Domain.Entities;
namespace ReC.Application.Endpoints;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfile : AutoMapper.Profile
{
public MappingProfile()
{
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

@@ -0,0 +1,24 @@
using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults.Commands;
public class CreateOutResCommand : IRequest
{
public required long ActionId { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public string? AddedWho { get; set; }
}
public class CreateOutResCommandHandler(IRepository<OutRes> repo) : IRequestHandler<CreateOutResCommand>
{
public Task Handle(CreateOutResCommand request, CancellationToken cancel)
{
return repo.CreateAsync(request, cancel);
}
}

View File

@@ -0,0 +1,15 @@
using ReC.Application.OutResults.Commands;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfiles : AutoMapper.Profile
{
public MappingProfiles()
{
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,37 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Application.Common.Dto;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults.Queries;
public record ReadOutResQuery : IRequest<IEnumerable<OutResDto>>
{
public long? ProfileId { get; init; }
public long? ActionId { get; init; }
}
public class ReadOutResHandler(IRepository<OutRes> repo, IMapper mapper) : IRequestHandler<ReadOutResQuery, IEnumerable<OutResDto>>
{
public async Task<IEnumerable<OutResDto>> Handle(ReadOutResQuery request, CancellationToken cancel)
{
var q = repo.Query;
if(request.ActionId is long actionId)
q = q.Where(res => res.ActionId == actionId);
if(request.ProfileId is long profileId)
q = q.Where(res => res.Action!.ProfileId == profileId);
var resList = await q.ToListAsync(cancel);
if (resList.Count == 0)
throw new NotFoundException();
return mapper.Map<IEnumerable<OutResDto>>(resList);
}
}

View File

@@ -0,0 +1,14 @@
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.")
.WithName("Identifier");
}
}

View File

@@ -11,6 +11,8 @@
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" />
<PackageReference Include="DigitalData.Core.Application" Version="3.4.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="Microsoft.EntityFrameworkCore" Version="9.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" />

View File

@@ -0,0 +1,45 @@
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

@@ -0,0 +1,24 @@
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,4 +1,5 @@
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ReC.Application.RecActions.Queries;
@@ -8,15 +9,15 @@ public record InvokeBatchRecActionsCommand : ReadRecActionQueryBase, IRequest;
public static class InvokeBatchRecActionsCommandExtensions
{
public static Task InvokeBatchRecAction(this ISender sender, int profileId)
=> sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId });
public static Task InvokeBatchRecAction(this ISender sender, long profileId, CancellationToken cancel = default)
=> 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, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory, ILogger<InvokeRecActionsCommandHandler>? logger = null) : IRequestHandler<InvokeBatchRecActionsCommand>
{
public async Task Handle(InvokeBatchRecActionsCommand request, CancellationToken cancel)
{
var actions = await sender.Send(request.ToReadQuery(), cancel);
var actions = await sender.Send(request.ToReadQuery(q => q.Invoked = false), cancel);
var http = clientFactory.CreateClient();
@@ -27,15 +28,17 @@ public class InvokeRecActionsCommandHandler(ISender sender, IHttpClientFactory c
await semaphore.WaitAsync(cancel);
try
{
await sender.Send(action.ToInvokeCommand(), cancel);
using var scope = scopeFactory.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
await sender.Send(action.ToInvokeCommand(), cancel);
}
catch(Exception ex)
{
logger?.LogError(
ex,
"Error invoking Rec action. ProfileId: {ProfileId}, ActionId: {ActionId}",
"Error invoking Rec action. ProfileId: {ProfileId}, Id: {Id}",
action.ProfileId,
action.ActionId
action.Id
);
}
finally

View File

@@ -1,47 +1,65 @@
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using ReC.Application.Common;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.OutResults.Commands;
using System.Text.Json;
namespace ReC.Application.RecActions.Commands;
public record InvokeRecActionCommand : RecActionDto, IRequest
public record InvokeRecActionCommand : IRequest
{
public InvokeRecActionCommand(RecActionDto root) : base(root) { }
public InvokeRecActionCommand() { }
public RecActionDto Action { get; set; } = null!;
}
public static class InvokeRecActionCommandExtensions
{
public static InvokeRecActionCommand ToInvokeCommand(this RecActionDto dto) => new(dto);
public static InvokeRecActionCommand ToInvokeCommand(this RecActionDto dto) => new() { Action = dto };
}
public class InvokeRecActionCommandHandler(
IHttpClientFactory clientFactory,
ILogger<InvokeRecActionsCommandHandler>? logger = null
ISender sender,
IHttpClientFactory clientFactory,
IConfiguration? config = null
) : IRequestHandler<InvokeRecActionCommand>
{
public async Task Handle(InvokeRecActionCommand request, CancellationToken cancel)
{
var action = request.Action;
using var http = clientFactory.CreateClient();
if (request.RestType is null)
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)
{
logger?.LogWarning(
"Rec action could not be invoked because the RestType value is null. ProfileId: {ProfileId}, ActionId: {ActionId}",
request.ProfileId,
request.ActionId
);
return;
using var reqBody = new StringContent(action.Body);
httpReq.Content = reqBody;
}
using var httpReq = request.RestType
.ToHttpMethod()
.ToHttpRequestMessage(request.EndpointUri);
if (action.Headers is not null)
foreach (var header in action.Headers)
httpReq.Headers.Add(header.Key, header.Value);
using var response = await http.SendAsync(httpReq, cancel);
var body = await response.Content.ReadAsStringAsync(cancel);
var headers = response.Headers.ToDictionary();
var resBody = await response.Content.ReadAsStringAsync(cancel);
var resHeaders = response.Headers.ToDictionary();
await sender.Send(new CreateOutResCommand
{
ActionId = action.Id,
Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }),
Body = resBody,
AddedWho = config?["AddedWho"]
}, cancel);
}
}

View File

@@ -0,0 +1,16 @@
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

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

View File

@@ -0,0 +1,41 @@
using Microsoft.Extensions.DependencyInjection;
#if NETFRAMEWORK
using System;
using System.Net.Http;
#endif
namespace ReC.Client
{
/// <summary>
/// Provides extension methods for setting up the ReC client in an <see cref="IServiceCollection"/>.
/// </summary>
public static class DependencyInjection
{
/// <summary>
/// Adds and configures the <see cref="HttpClient"/> for the <see cref="ReCClient"/> to the specified <see cref="IServiceCollection"/>
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="apiUri">The base URI of the ReC API.</param>
/// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
public static IHttpClientBuilder AddRecClient(this IServiceCollection services, string apiUri)
{
services.AddScoped<ReCClient>();
return services.AddHttpClient(ReCClient.ClientName, client =>
{
client.BaseAddress = new Uri(apiUri);
});
}
/// <summary>
/// Adds and configures the <see cref="HttpClient"/> for the <see cref="ReCClient"/> to the specified <see cref="IServiceCollection"/>
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configureClient">An action to configure the <see cref="HttpClient"/>.</param>
/// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
public static IHttpClientBuilder AddRecClient(this IServiceCollection services, Action<HttpClient> configureClient)
{
services.AddScoped<ReCClient>();
return services.AddHttpClient(ReCClient.ClientName, configureClient);
}
}
}

View File

@@ -2,10 +2,36 @@
<PropertyGroup>
<TargetFrameworks>net462;net8.0</TargetFrameworks>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
<PackageId>ReC.Client</PackageId>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>ReC.Client</Product>
<Copyright>Copyright 2025</Copyright>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/Rec.git</RepositoryUrl>
<PackageTags>digital data rec api</PackageTags>
<Version>1.0.0-beta</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Description>Client-Bibliothek für die Interaktion mit der ReC.API, die typisierten HTTP-Zugriff und DI-Integration bietet.</Description>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'net462'">
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\assets\icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
</Project>

124
src/ReC.Client/ReCClient.cs Normal file
View File

@@ -0,0 +1,124 @@
using Microsoft.Extensions.DependencyInjection;
#if NETFRAMEWORK
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
#endif
namespace ReC.Client
{
/// <summary>
/// A client for interacting with the ReC API.
/// </summary>
public class ReCClient
{
private readonly HttpClient _http;
/// <summary>
/// A unique name for the HttpClient used by the ReCClient.
/// </summary>
public static readonly string ClientName = Guid.NewGuid().ToString();
/// <summary>
/// Initializes a new instance of the <see cref="ReCClient"/> class.
/// </summary>
/// <param name="httpClientFactory">The factory to create HttpClients.</param>
public ReCClient(IHttpClientFactory httpClientFactory)
{
_http = httpClientFactory.CreateClient(ClientName);
}
/// <summary>
/// Asynchronously invokes a ReC action for a specific profile.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/RecAction/invoke/{profileId}</c> endpoint.
/// </remarks>
/// <param name="profileId">The ID of the profile to invoke the action for.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation. The task result is <see langword="true"/> if the request was successful; otherwise, <see langword="false"/>.</returns>
public async Task<bool> InvokeRecActionAsync(int profileId, CancellationToken cancellationToken = default)
{
var resp = await _http.PostAsync($"api/RecAction/invoke/{profileId}", content: null, cancellationToken);
return resp.IsSuccessStatusCode;
}
/// <summary>
/// Synchronously invokes a ReC action for a specific profile.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/RecAction/invoke/{profileId}</c> endpoint.
/// This is the synchronous version of <see cref="InvokeRecActionAsync(int, CancellationToken)"/>.
/// </remarks>
/// <param name="profileId">The ID of the profile to invoke the action for.</param>
/// <returns><see langword="true"/> if the request was successful; otherwise, <see langword="false"/>.</returns>
[Obsolete("Use InvokeRecActionAsync instead to avoid potential deadlocks and improve performance.")]
public bool InvokeRecAction(int profileId)
{
var resp = _http.PostAsync($"api/RecAction/invoke/{profileId}", content: null).GetAwaiter().GetResult();
return resp.IsSuccessStatusCode;
}
#region Static
private static readonly IServiceCollection Services = new ServiceCollection();
#if NET8_0_OR_GREATER
private static IServiceProvider? Provider = null;
#else
private static IServiceProvider Provider = null;
#endif
/// <summary>
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
/// </summary>
/// <remarks>
/// This method should only be called once during application startup.
/// </remarks>
/// <param name="apiUri">The base URI of the ReC API.</param>
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static void BuildStaticClient(string apiUri)
{
if(Provider != null)
throw new InvalidOperationException("Static Provider is already built.");
Services.AddRecClient(apiUri);
Provider = Services.BuildServiceProvider();
}
/// <summary>
/// Configures and builds the static <see cref="IServiceProvider"/> for creating <see cref="ReCClient"/> instances.
/// </summary>
/// <remarks>
/// This method should only be called once during application startup.
/// </remarks>
/// <param name="configureClient">An action to configure the <see cref="HttpClient"/>.</param>
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static void BuildStaticClient(Action<HttpClient> configureClient)
{
if (Provider != null)
throw new InvalidOperationException("Static Provider is already built.");
Services.AddRecClient(configureClient);
Provider = Services.BuildServiceProvider();
}
/// <summary>
/// Creates a new <see cref="ReCClient"/> instance using the statically configured provider.
/// </summary>
/// <returns>A new instance of the <see cref="ReCClient"/>.</returns>
/// <exception cref="InvalidOperationException">Thrown if <see cref="BuildStaticClient(string)"/> has not been called yet.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static ReCClient Create()
{
if (Provider == null)
throw new InvalidOperationException("Static Provider is not built. Call BuildStaticClient first.");
return Provider.GetRequiredService<ReCClient>();
}
#endregion
}
}

View File

@@ -0,0 +1,52 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Table("TBDD_CONNECTION")]
public class Connection
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public short? Id { get; set; }
[Column("BEZEICHNUNG")]
public string? Bezeichnung { get; set; }
[Column("SQL_PROVIDER")]
public string? SqlProvider { get; set; }
[Column("SERVER")]
public string? Server { get; set; }
[Column("DATENBANK")]
public string? Datenbank { get; set; }
[Column("USERNAME")]
public string? Username { get; set; }
[Column("PASSWORD")]
public string? Password { get; set; }
[Column("BEMERKUNG")]
public string? Bemerkung { get; set; }
[Column("AKTIV")]
public bool? Aktiv { get; set; }
[Column("ERSTELLTWER")]
public string? ErstelltWer { get; set; }
[Column("ERSTELLTWANN")]
public DateTime? ErstelltWann { get; set; }
[Column("GEANDERTWER")]
public string? GeandertWer { get; set; }
[Column("GEAENDERTWANN")]
public DateTime? GeaendertWann { get; set; }
[Column("SYS_CONNECTION")]
public bool? SysConnection { get; set; }
}

View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Table("TBREC_CFG_ENDPOINT")]
public class Endpoint
{
[Key]
[Column("GUID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("DESCRIPTION")]
public string? Description { get; set; }
[Column("URI")]
public string? Uri { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -0,0 +1,58 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Table("TBREC_CFG_ENDPOINT_AUTH")]
public class EndpointAuth
{
[Key]
[Column("GUID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long? Id { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("DESCRIPTION")]
public string? Description { get; set; }
[Column("TYPE")]
public string? Type { get; set; }
[Column("API_KEY")]
public string? ApiKey { get; set; }
[Column("API_VALUE")]
public string? ApiValue { get; set; }
[Column("API_KEY_ADD_TO")]
public string? ApiKeyAddTo { get; set; }
[Column("TOKEN")]
public string? Token { get; set; }
[Column("USERNAME")]
public string? Username { get; set; }
[Column("PASSWORD")]
public string? Password { get; set; }
[Column("DOMAIN")]
public string? Domain { get; set; }
[Column("WORKSTATION")]
public string? Workstation { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -13,7 +13,7 @@ public class EndpointParam
{
[Key]
[Column("GUID")]
public long? Guid { get; set; }
public long? Id { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }

View File

@@ -14,11 +14,14 @@ public class OutRes
[Column("ACTION_ID")]
public long? ActionId { get; set; }
[ForeignKey("ActionId")]
public RecAction? Action { get; set; }
[Column("RESULT_HEADER")]
public string? ResultHeader { get; set; }
public string? Header { get; set; }
[Column("RESULT_BODY")]
public string? ResultBody { get; set; }
public string? Body { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }

View File

@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Table("TBREC_CFG_PROFILE", Schema = "dbo")]
public class Profile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public long Id { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("TYPE")]
public string? Type { get; set; }
[Column("MANDANTOR")]
public string? Mandantor { get; set; }
[Column("PROFILE_NAME")]
public string? Name { get; set; }
[Column("DESCRIPTION")]
public string? Description { get; set; }
[Column("LOG_LEVEL")]
public string? LogLevel { get; set; }
[Column("LANGUAGE")]
public string? Language { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -3,79 +3,36 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
/// <summary>
/// Represents the VWREC_ACTION view from the database.
///
/// All properties are defined as nullable to provide flexibility in case the underlying view
/// changes in production (e.g., columns added, removed, or modified). This approach prevents
/// runtime mapping errors and ensures the application can handle schema changes without
/// requiring immediate code updates.
/// </summary>
[Table("VWREC_ACTION", Schema = "dbo")]
[Table("TBREC_CFG_ACTION")]
public class RecAction
{
[Column("ACTION_ID")]
public long? ActionId { get; set; }
[Key]
[Column("GUID")]
public long? Id { get; set; }
[Column("PROFILE_ID")]
public long? ProfileId { get; set; }
[Column("PROFILE_NAME")]
[MaxLength(100)]
public string? ProfileName { get; set; }
[ForeignKey("ProfileId")]
public Profile? Profile { get; set; }
[Column("PROFILE_TYPE")]
[MaxLength(20)]
public string? ProfileType { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("PROFILE_SEQUENCE")]
public byte? ProfileSequence { get; set; }
[Column("SEQUENCE")]
public byte? Sequence { get; set; }
[Column("ENDPOINT_ID")]
public long? EndpointId { get; set; }
[Column("ENDPOINT_URI")]
[MaxLength(4000)]
public string? EndpointUri { get; set; }
[ForeignKey("EndpointId")]
public Endpoint? Endpoint { get; set; }
[Column("ENDPOINT_AUTH_ID")]
public long? EndpointAuthId { get; set; }
[Column("ENDPOINT_AUTH_TYPE")]
[MaxLength(50)]
public string? EndpointAuthType { get; set; }
[Column("ENDPOINT_AUTH_API_KEY")]
[MaxLength(300)]
public string? EndpointAuthApiKey { get; set; }
[Column("ENDPOINT_AUTH_API_VALUE")]
[MaxLength(300)]
public string? EndpointAuthApiValue { get; set; }
[Column("ENDPOINT_AUTH_API_KEY_ADD_TO")]
[MaxLength(20)]
public string? EndpointAuthApiKeyAddTo { get; set; }
[Column("ENDPOINT_AUTH_TOKEN")]
[MaxLength(300)]
public string? EndpointAuthToken { get; set; }
[Column("ENDPOINT_AUTH_USERNAME")]
[MaxLength(200)]
public string? EndpointAuthUsername { get; set; }
[Column("ENDPOINT_AUTH_PASSWORD")]
[MaxLength(200)]
public string? EndpointAuthPassword { get; set; }
[Column("ENDPOINT_AUTH_DOMAIN")]
[MaxLength(100)]
public string? EndpointAuthDomain { get; set; }
[Column("ENDPOINT_AUTH_WORKSTATION")]
[MaxLength(100)]
public string? EndpointAuthWorkstation { get; set; }
[ForeignKey("EndpointAuthId")]
public EndpointAuth? EndpointAuth { get; set; }
[Column("ENDPOINT_PARAMS_ID")]
public short? EndpointParamsId { get; set; }
@@ -83,25 +40,11 @@ public class RecAction
[Column("SQL_CONNECTION_ID")]
public short? SqlConnectionId { get; set; }
[Column("SQL_CONNECTION_SERVER")]
[MaxLength(150)]
public string? SqlConnectionServer { get; set; }
[ForeignKey("SqlConnectionId")]
public Connection? SqlConnection { get; set; }
[Column("SQL_CONNECTION_DB")]
[MaxLength(100)]
public string? SqlConnectionDb { get; set; }
[Column("SQL_CONNECTION_USERNAME")]
[MaxLength(100)]
public string? SqlConnectionUsername { get; set; }
[Column("SQL_CONNECTION_PASSWORD")]
[MaxLength(100)]
public string? SqlConnectionPassword { get; set; }
[Column("REST_TYPE")]
[MaxLength(20)]
public string? RestType { get; set; }
[Column("TYPE")]
public string? Type { get; set; }
[Column("PREPROCESSING_QUERY")]
public string? PreprocessingQuery { get; set; }
@@ -114,4 +57,18 @@ public class RecAction
[Column("POSTPROCESSING_QUERY")]
public string? PostprocessingQuery { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
public OutRes? OutRes { get; set; }
}

View File

@@ -0,0 +1,123 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
/// <summary>
/// Represents the VWREC_ACTION view from the database.
///
/// All properties are defined as nullable to provide flexibility in case the underlying view
/// changes in production (e.g., columns added, removed, or modified). This approach prevents
/// runtime mapping errors and ensures the application can handle schema changes without
/// requiring immediate code updates.
/// </summary>
[Table("VWREC_ACTION", Schema = "dbo")]
public class RecActionView
{
[Column("ACTION_ID")]
public required long Id { get; set; }
[ForeignKey("Id")]
public RecAction? Root { get; set; }
[Column("PROFILE_ID")]
public long? ProfileId { get; set; }
[ForeignKey("ProfileId")]
public Profile? Profile { get; set; }
[Column("PROFILE_NAME")]
[MaxLength(100)]
public string? ProfileName { get; set; }
[Column("PROFILE_TYPE")]
[MaxLength(20)]
public string? ProfileType { get; set; }
[Column("PROFILE_SEQUENCE")]
public byte? ProfileSequence { get; set; }
[Column("ENDPOINT_ID")]
public long? EndpointId { get; set; }
[Column("ENDPOINT_URI")]
[MaxLength(4000)]
public string? EndpointUri { get; set; }
[Column("ENDPOINT_AUTH_ID")]
public long? EndpointAuthId { get; set; }
[Column("ENDPOINT_AUTH_TYPE")]
[MaxLength(50)]
public string? EndpointAuthType { get; set; }
[Column("ENDPOINT_AUTH_API_KEY")]
[MaxLength(300)]
public string? EndpointAuthApiKey { get; set; }
[Column("ENDPOINT_AUTH_API_VALUE")]
[MaxLength(300)]
public string? EndpointAuthApiValue { get; set; }
[Column("ENDPOINT_AUTH_API_KEY_ADD_TO")]
[MaxLength(20)]
public string? EndpointAuthApiKeyAddTo { get; set; }
[Column("ENDPOINT_AUTH_TOKEN")]
[MaxLength(300)]
public string? EndpointAuthToken { get; set; }
[Column("ENDPOINT_AUTH_USERNAME")]
[MaxLength(200)]
public string? EndpointAuthUsername { get; set; }
[Column("ENDPOINT_AUTH_PASSWORD")]
[MaxLength(200)]
public string? EndpointAuthPassword { get; set; }
[Column("ENDPOINT_AUTH_DOMAIN")]
[MaxLength(100)]
public string? EndpointAuthDomain { get; set; }
[Column("ENDPOINT_AUTH_WORKSTATION")]
[MaxLength(100)]
public string? EndpointAuthWorkstation { get; set; }
[Column("ENDPOINT_PARAMS_ID")]
public short? EndpointParamsId { get; set; }
[Column("SQL_CONNECTION_ID")]
public short? SqlConnectionId { get; set; }
[Column("SQL_CONNECTION_SERVER")]
[MaxLength(150)]
public string? SqlConnectionServer { get; set; }
[Column("SQL_CONNECTION_DB")]
[MaxLength(100)]
public string? SqlConnectionDb { get; set; }
[Column("SQL_CONNECTION_USERNAME")]
[MaxLength(100)]
public string? SqlConnectionUsername { get; set; }
[Column("SQL_CONNECTION_PASSWORD")]
[MaxLength(100)]
public string? SqlConnectionPassword { get; set; }
[Column("REST_TYPE")]
[MaxLength(20)]
public string? RestType { get; set; }
[Column("PREPROCESSING_QUERY")]
public string? PreprocessingQuery { get; set; }
[Column("HEADER_QUERY")]
public string? HeaderQuery { get; set; }
[Column("BODY_QUERY")]
public string? BodyQuery { get; set; }
[Column("POSTPROCESSING_QUERY")]
public string? PostprocessingQuery { get; set; }
}

View File

@@ -21,7 +21,7 @@ public static class DependencyInjection
services.AddScoped<IRecDbContext>(provider => provider.GetRequiredService<TRecDbContext>());
services.AddDbRepository(opt => opt.RegisterFromAssembly<TRecDbContext>(typeof(RecAction).Assembly));
services.AddDbRepository(opt => opt.RegisterFromAssembly<TRecDbContext>(typeof(RecActionView).Assembly));
return services;
}
@@ -31,9 +31,9 @@ public static class DependencyInjection
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;
return this;

View File

@@ -8,7 +8,7 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
{
public DbSet<EndpointParam> EndpointParams { get; set; }
public DbSet<RecAction> Actions { get; set; }
public DbSet<RecActionView> Actions { get; set; }
public DbSet<OutRes> OutRes { get; set; }
@@ -16,14 +16,29 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
public DbSet<BodyQueryResult> BodyQueryResults { get; set; }
public DbSet<Connection> Connections { get; set; }
public DbSet<Endpoint> Endpoints { get; set; }
public DbSet<EndpointAuth> EndpointAuths { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<RecAction> RecActions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<RecAction>().HasNoKey();
modelBuilder.Entity<RecActionView>().HasNoKey();
modelBuilder.Entity<HeaderQueryResult>().HasNoKey();
modelBuilder.Entity<BodyQueryResult>().HasNoKey();
modelBuilder.Entity<RecAction>()
.HasOne(act => act.OutRes)
.WithOne(res => res.Action)
.HasForeignKey<OutRes>(res => res.ActionId);
}
}
}