Compare commits

...

370 Commits

Author SHA1 Message Date
27d731a5b0 Bump version to 2.0.0-beta in ReC.API.csproj
Updated <Version>, <AssemblyVersion>, <FileVersion>, and <InformationalVersion> fields from 1.0.3-beta to 2.0.0-beta to reflect a major version change, indicating significant updates or breaking changes in the project.
2026-01-22 10:41:26 +01:00
b8f797f14d Make EndpointAuthType non-nullable with default NoAuth
Changed EndpointAuthType in RecActionViewDto from nullable to non-nullable and set its default value to EndpointAuthType.NoAuth to ensure it always has a valid value.
2026-01-22 10:37:37 +01:00
6feef53733 Update namespaces and usings for Insert procedures
Refactored InsertActionProcedure to the RecActions.Commands namespace and updated its usings to import InsertObjectProcedure. Added missing using for RecActions.Commands in InsertObjectProcedure.cs. These changes improve code organization and clarify command responsibilities.
2026-01-22 10:17:08 +01:00
878e096c57 Refactor TypeId to use RestType enum in InsertActionProcedure
Changed TypeId from byte? to RestType? in InsertActionProcedure for stronger typing. Updated InsertObjectProcedureHandler to cast RestType to byte? when creating SQL parameters. Added using directive for ReC.Domain.Constants to support the new enum type.
2026-01-22 09:55:35 +01:00
2ded140ad5 Rename SqlExceptionNumber to BadRequestSqlExceptionNumber
Clarify intent by renaming SqlExceptionNumber to BadRequestSqlExceptionNumber in both configuration and code. This makes it explicit that these SQL exception numbers are mapped to HTTP 400 Bad Request errors. All relevant usages and settings have been updated accordingly.
2026-01-22 09:33:54 +01:00
e2ca249d13 Add SQL exception 50000 to handled exception numbers
Updated appsettings.json to include 50000 in the SqlExceptionNumber array, allowing the application to recognize and handle SQL exceptions with this number in addition to existing ones. This change can be applied at runtime without requiring a restart.
2026-01-22 09:31:41 +01:00
e782eab62a Refactor handlers to use IOptionsMonitor for SQL options
Refactored Delete, Insert, and Update procedure handlers to inject IOptionsMonitor<SqlExceptionOptions> instead of SqlExceptionOptions, enabling dynamic configuration updates. Updated all references to use CurrentValue. Added necessary using directives and cleaned up redundant usings in InsertObjectProcedure.cs.
2026-01-22 09:21:39 +01:00
d8e08b237d Simplify SqlException config structure in appsettings.json
Removed the "ErrorMessages" wrapper from the SqlException section,
placing "SqlExceptionNumber" directly under "SqlException".
No changes to the exception numbers themselves. Improved
readability and clarity of the configuration.
2026-01-22 02:04:52 +01:00
Developer 02
88cb1dc16a Handle SqlException in UpdateObjectProcedureHandler
Wrap stored procedure execution in try-catch to handle SqlException.
Throw BadRequestException for configured SQL error numbers.
Update constructor to accept SqlExceptionOptions.
Add necessary using directives for new exception handling.
2026-01-22 01:54:07 +01:00
Developer 02
8d6a09213e Simplify BadRequestException error message on insert failure
Replaced the detailed error message for insert failures with only the original exception message from SqlException, removing extra guidance about referenced entities.
2026-01-22 01:53:55 +01:00
Developer 02
54f412ced2 Improve error handling in DeleteObjectProcedureHandler
Refactored DeleteObjectProcedureHandler to inject SqlExceptionOptions and wrap stored procedure execution in a try-catch block. Added logic to throw custom exceptions (DeleteObjectFailedException, BadRequestException) based on result codes and SQL exception numbers, enhancing robustness and configurability of error handling.
2026-01-22 01:53:40 +01:00
Developer 02
51b9c62188 Add SqlException config options and update dependencies
Added ConfigureSqlException methods to DependencyInjection for flexible SQL exception handling configuration. Updated RecApplicationTestBase to use new options. Bumped DigitalData.Core.Exceptions to v1.1.1.
2026-01-22 01:49:59 +01:00
Developer 02
bb5525778d Improve error handling in InsertObjectProcedureHandler
Wrap SQL execution in try-catch and inject SqlExceptionOptions.
Throw BadRequestException with a clear message when insert fails due to missing referenced entities, based on SqlException number. Other SQL exceptions are rethrown. This provides better feedback for foreign key/reference errors.
2026-01-22 01:49:13 +01:00
Developer 02
ee793632df Add SQL exception handling configuration support
Updated Program.cs to configure SQL exception handling using the new "SqlException" section in appsettings.json. Renamed "SqlExceptionTranslator" to "SqlException" and added error message mappings for specific SQL exception numbers to enable custom exception translation.
2026-01-22 01:46:46 +01:00
Developer 02
22bb585f60 Refactor SQL exception handling and config structure
Simplify SQL exception tracking by replacing error message mappings
with a list of relevant error numbers in appsettings.json. Remove
custom error message logic and related classes, introducing
SqlExceptionOptions to hold tracked error codes.
2026-01-22 01:45:50 +01:00
Developer 02
ed7237c8dd Add SqlExceptionTranslator config and options class
Introduce SqlExceptionTranslator section in appsettings.json to map common SQL error codes to user-friendly messages. Add SqlExceptionTranslatorOptions class to manage error code/message mapping and support configuration binding.
2026-01-22 01:13:02 +01:00
Developer 02
f71bcf37e9 Add SqlExceptionTranslator for custom SQL error handling
Introduced SqlExceptionTranslator and ISqlExceptionTranslator to translate SQL exceptions into user-friendly error messages for bad request scenarios. Uses configurable options to identify relevant error numbers and format messages with templates.
2026-01-22 00:34:35 +01:00
Developer 02
c7e366af60 Refactor ResultController ctor, enhance BadRequest logging
Removed IConfiguration from ResultController constructor, now only using IMediator. Added logging for BadRequestException inner exceptions in ExceptionHandlingMiddleware for improved error diagnostics.
2026-01-21 22:24:41 +01:00
86f4e3141e Remove all 'fake' profile endpoints from controllers
Removed API endpoints for invoking, retrieving, and deleting RecActions and Results associated with fake/test profiles in RecActionController and ResultController. Only standard CRUD operations for real profiles are now exposed.
2026-01-21 12:06:19 +01:00
869ba9858f Update ProfileController to use CreatedAtAction in Post
Changed the Post method to return CreatedAtAction, providing a Location header linking to the new profile resource, in line with RESTful best practices.
2026-01-20 16:38:53 +01:00
5dee104377 Handle SQL exceptions in InsertActionProcedure test
Wrap InsertActionProcedure_runs_via_mediator in a try-catch block to handle SQL exceptions for duplicate key and foreign key violations, marking the test as passed with explanatory messages. Also, add EndpointId to the test data and improve assertion clarity. This increases test robustness against common DB constraint issues in integration testing.
2026-01-20 16:22:30 +01:00
36e1d5fad1 Update test assertions to allow any non-default int result
Previously, tests asserted that procedure results were exactly 0.
Now, assertions require only that results are not the default int
value, making the tests more flexible to non-zero outcomes.
2026-01-20 16:10:40 +01:00
304490d661 Add explicit @RC handling in SQL for delete/update procs
Updated DeleteObjectProcedureHandler and UpdateObjectProcedureHandler to declare, initialize, and select the @RC variable in their SQL command strings. This ensures the stored procedure return code is explicitly captured and returned by the query.
2026-01-20 15:39:54 +01:00
ff4ab9efe2 Handle NotFoundException gracefully in query tests
Updated ProfileQueryTests, RecActionQueryTests, and ResultQueryTests to catch NotFoundException and pass tests with an explanatory message when test data is missing or entities are not found. This improves test robustness and reduces false negatives due to unavailable test data. Also renamed a test in ResultQueryTests to reflect the new behavior.
2026-01-19 10:16:53 +01:00
470120e5e9 Add ProfileView entity mapping to RecDbContext
Mapped ProfileView to the VWREC_PROFILE view in the dbo schema.
Configured primary key and property-to-column mappings for all
relevant fields, enabling read access to profile data via EF Core.
2026-01-19 10:03:17 +01:00
ce35ef588f Add integration test project with MediatR procedure tests
Added ReC.Tests project with integration tests for stored procedure commands and queries via MediatR, covering endpoints, actions, results, and profiles. Introduced a test base for DI/configuration, helper for private property access, and updated the solution to include the new test suite. Tests use NUnit and cover both positive and negative scenarios.
2026-01-16 15:24:51 +01:00
df665e3b98 Remove ReC.Abstractions.Application from solution
Deleted the "ReC.Abstractions.Application" project from ReC.sln, including its project entry, build configurations, and nested project references.
2026-01-16 14:40:12 +01:00
4bc6df4d91 Revert "Refactor BaseCrudApi to use generic type parameters"
This reverts commit 5197ad1481.
2026-01-16 14:39:12 +01:00
383932e7e7 Remove [Table] attributes from DTO classes
Removed explicit table mapping attributes from EndpointAuthDto, ProfileDto, and RecActionDto to decouple DTOs from database schema definitions.
2026-01-16 13:17:44 +01:00
e3494d50b7 Add ReC.Abstractions.Application project to solution
Added new ReC.Abstractions.Application project targeting .NET 8.0, with implicit usings and nullable enabled. Updated solution file to include the project under the "core" folder and configured build settings for Debug and Release.
2026-01-16 13:06:37 +01:00
5197ad1481 Refactor BaseCrudApi to use generic type parameters
BaseCrudApi is now defined as BaseCrudApi<TCreate, TUpdate, TDelete>,
with each CRUD method using its respective type parameter. This
improves type safety and clarity for API payloads. Method signatures
and XML documentation have been updated accordingly.
2026-01-16 12:29:17 +01:00
dbfb7e7e47 Refactor API classes to use BaseCrudApi for CRUD ops
Introduce BaseCrudApi to encapsulate common CRUD logic for API resource classes. Refactor CommonApi, EndpointAuthApi, EndpointParamsApi, EndpointsApi, ProfileApi, RecActionApi, and ResultApi to inherit from BaseCrudApi, removing duplicated CRUD methods and constructors. This centralizes CRUD operations, reduces code duplication, and improves maintainability.
2026-01-16 11:53:14 +01:00
b9a40bb12e Refactor API method names for consistency and clarity
Renamed public API methods across ReC.Client.Api classes to use concise and uniform names (e.g., CreateAsync, UpdateAsync, DeleteAsync, GetAsync, InvokeAsync). Removed obsolete NETFRAMEWORK preprocessor blocks. Updated summary comment for Results property in ReCClient.cs. These changes improve naming consistency and code clarity throughout the client library.
2026-01-16 11:42:03 +01:00
8ddaf1d13e Update ResultApi to use "api/Result" endpoint paths
Replaced all occurrences of "api/OutRes" with "api/Result" in ResultApi.cs for GET, POST, PUT, and DELETE operations. This ensures all result-related API requests now target the correct "Result" endpoint.
2026-01-16 11:37:38 +01:00
14532a15bf Add NETFRAMEWORK-specific usings to API files
Added conditional using directives for System.Net.Http, System.Threading, and System.Threading.Tasks in multiple API files. These usings are now included only when targeting .NET Framework, improving compatibility and preventing unnecessary imports on other platforms.
2026-01-16 11:35:37 +01:00
0149a77f21 Add ReCClientHelpers for query and JSON request helpers
Introduced internal static class ReCClientHelpers with methods to build query strings from key/value pairs and to create JsonContent payloads for HTTP requests. Includes conditional compilation for .NET Framework compatibility and XML documentation for both methods. Added necessary using directives.
2026-01-16 11:34:19 +01:00
a17d260c6c Refactor ReCClient to use modular API group classes
Refactored ReCClient to expose grouped endpoint APIs as properties (e.g., RecActions, Results, Profiles, etc.), each handled by its own class. Removed direct endpoint methods from ReCClient and delegated them to these new API classes. Cleaned up using directives and improved code modularity for better maintainability and discoverability.
2026-01-16 11:34:06 +01:00
b639df0a39 Add ResultApi for HTTP access to output result endpoints
Introduced the ResultApi class in the ReC.Client.Api namespace to provide asynchronous methods for retrieving, creating, updating, and deleting output results via HTTP. The class uses an injected HttpClient, supports cancellation tokens, and leverages helper methods for query construction and JSON serialization.
2026-01-16 11:33:51 +01:00
0fddf5669f Add RecActionApi client for RecAction endpoint operations
Introduced RecActionApi in ReC.Client.Api to provide a typed API client for RecAction endpoints. Includes methods for invoking, retrieving, creating, updating, and deleting Rec actions, with support for cancellation tokens and helper-based serialization. All methods are documented with XML comments.
2026-01-16 11:33:36 +01:00
9c46b9f2da Add ProfileApi for profile CRUD operations via HttpClient
Introduced ProfileApi class in ReC.Client.Api to handle profile-related API endpoints. Supports retrieving, creating, updating, and deleting profiles using an injected HttpClient. Methods accept cancellation tokens and utilize helper methods for query building and JSON serialization. Includes XML documentation for all methods and parameters.
2026-01-16 11:32:44 +01:00
87d9769d9b Add EndpointsApi for CRUD operations on endpoints
Introduced EndpointsApi class in ReC.Client.Api to handle create, update, and delete operations for endpoint definitions via HTTP requests. Methods are generic, support cancellation tokens, and use a helper for JSON serialization of payloads.
2026-01-16 11:32:31 +01:00
19ecf104fa Add EndpointParamsApi for endpoint parameter operations
Introduced the EndpointParamsApi class in ReC.Client.Api to handle create, update, and delete operations for endpoint parameters via HTTP. Methods support generic payloads, cancellation tokens, and use JSON serialization helpers. Added necessary using directives for HTTP and threading.
2026-01-16 11:30:46 +01:00
5b5b034e78 Add EndpointAuthApi for endpoint authentication operations
Introduced the EndpointAuthApi class in ReC.Client.Api to handle endpoint authentication API interactions. This class provides async methods for creating, updating, and deleting endpoint authentication configurations using HttpClient, with support for cancellation tokens and JSON payload serialization. XML documentation is included for clarity.
2026-01-16 11:30:30 +01:00
5ebb3f72e3 Add CommonApi for generic HTTP object operations
Introduced the CommonApi class in the ReC.Client.Api namespace to provide generic methods for creating, updating, and deleting objects via HTTP. The class uses an injected HttpClient and supports asynchronous operations with optional cancellation tokens. All methods serialize payloads to JSON using ReCClientHelpers.ToJsonContent. XML documentation was added for clarity.
2026-01-16 11:29:54 +01:00
3cfecbf598 Rename OutResController to ResultController and update docs
Renamed the controller class to ResultController for clarity and added the ReC.Application.Results.Queries using directive. Updated class documentation and parameter descriptions to better reflect the controller's purpose.
2026-01-16 11:17:41 +01:00
4895b9c8f8 Refactor: move OutResults to Results namespace
Refactored all "Result" related command and query classes from ReC.Application.OutResults to ReC.Application.Results. Updated all relevant using statements and reorganized files accordingly. No functional changes; this improves project structure and clarity.
2026-01-16 11:13:06 +01:00
36fe78e152 Remove obsolete CreateResultViewCommand and handler
Deleted the CreateResultViewCommand.cs file, including the obsolete CreateResultViewCommand and CreateResultViewCommandHandler classes. These were previously used for creating ResultView entities, but are now replaced by related procedures or views. All associated code and using directives have been removed.
2026-01-16 11:09:02 +01:00
489180f5a1 Restructure solution folders in ReC.sln
Added "infrastructure", "presentation", and "core" solution folders. Moved projects into these new folders and updated the NestedProjects section to reflect the new organization. Adjusted parent GUIDs accordingly.
2026-01-16 11:07:19 +01:00
b65810bbbb Add XML docs to ResultType enum members
Added summary XML documentation to each member of the ResultType enum in the ReC.Client namespace, clarifying the purpose of Full, OnlyHeader, and OnlyBody options.
2026-01-16 11:05:17 +01:00
278fcfd75b Remove obsolete sync API methods from ReCClient
All synchronous (blocking) HTTP API methods have been removed from the ReCClient class, leaving only their asynchronous counterparts. These sync methods were previously marked as [Obsolete] and simply wrapped the async methods with .GetAwaiter().GetResult(). Additionally, the ToJsonContent method's return type was updated from HttpContent to JsonContent for improved type specificity.
2026-01-16 11:04:11 +01:00
41db75b183 Add TaskSyncExtensions for sync Task execution (.NET FW)
Introduced TaskSyncExtensions class with Sync extension methods to allow synchronous execution of Task and Task<TResult> instances. This enables blocking on async code and is conditionally compiled for .NET Framework builds.
2026-01-16 11:03:53 +01:00
f4a921e268 Adjust BuildQuery param types for .NET Framework compatibility
Use conditional compilation to set BuildQuery's tuple Value type
to object for .NET Framework and object? for other targets,
ensuring compatibility with nullable reference types across
different .NET versions.
2026-01-16 10:58:26 +01:00
cdb52dc6fd Add strongly-typed HTTP API methods to ReCClient
Introduce async/sync methods for all major API controllers in ReCClient, supporting GET, POST, PUT, and DELETE with JSON serialization. Add utility methods for query building and JSON content. Mark sync wrappers as [Obsolete]. Add System.Net.Http.Json dependency and supporting usings. Introduce ResultType enum. This greatly expands ReCClient's API coverage and usability.
2026-01-16 10:54:46 +01:00
f14f6c1f15 Rename GetProfile method to Get in ProfileController
Renamed the GetProfile method to Get in the ProfileController to standardize method naming. No changes were made to the method's functionality or parameters.
2026-01-16 10:48:42 +01:00
6a24719342 Remove obsolete MappingProfile classes from Endpoints/RecActions
Removed AutoMapper MappingProfile classes from ReC.Application.Endpoints and ReC.Application.RecActions. These profiles mapped command objects to DTOs with default values. The change reflects a move toward using procedures or views for these mappings.
2026-01-16 10:40:28 +01:00
631ab8cba5 Remove obsolete OutRes command/query validators
Deleted DeleteOutResCommandValidator and ReadOutResQueryValidator classes, which were marked as obsolete. These validators are no longer needed due to a shift toward using related procedures or views for validation.
2026-01-16 10:37:57 +01:00
872878b9d7 Remove obsolete OutRes and RecAction CQRS classes
Removed deprecated command, query, and mapping classes for OutRes and RecAction entities, including their handlers and AutoMapper profiles. These components were previously marked as obsolete and have been superseded by database procedures or views. This cleanup eliminates redundant code and enforces the use of the updated data access patterns.
2026-01-16 10:36:43 +01:00
5a30b0ece4 Remove ObtainEndpointCommand and its handler
Deleted the ObtainEndpointCommand, its handler, and related using directives. This command was previously used to obtain or create Endpoint entities by URI, but has been removed in favor of using a related procedure or view as indicated by its Obsolete attribute.
2026-01-16 10:36:10 +01:00
37200617ea Remove unused entity-to-DTO mappings from DtoMappingProfile
Cleaned up DtoMappingProfile by removing mappings for several domain entities (OutRes, Connection, Endpoint, EndpointAuth, EndpointParam, Profile, RecAction) to their DTOs. Only view-to-DTO mappings remain. Also removed unused using directives.
2026-01-16 10:35:21 +01:00
8ab66db1f2 Remove obsolete entity DbSets and configs from RecDbContext
Removed all DbSet properties and Fluent API configurations for obsolete domain entities (EndpointParam, OutRes, Connection, Endpoint, EndpointAuth, Profile, RecAction) from RecDbContext. These entities are now accessed via views, further enforcing the transition to view-based data access. Only view-based models remain configured in the context.
2026-01-16 10:29:27 +01:00
cbd52721ac Remove obsolete DbSets from IRecDbContext interface
Removed DbSet properties for entities marked as obsolete in IRecDbContext, including EndpointParam, OutRes, Connection, Endpoint, EndpointAuth, Profile, and RecAction. Retained only DbSets for views and query results to encourage use of views instead of direct entity access. This streamlines the interface and aligns with the intended data access pattern.
2026-01-16 10:24:09 +01:00
3003559d7a Remove obsolete navigation properties from RecActionView
Removed Root, Endpoint, EndpointAuth, and SqlConnection navigation properties from RecActionView, along with their [ForeignKey] and [Obsolete] attributes. This streamlines the data model by retaining only foreign key IDs and related string properties, reducing direct entity navigation in favor of using related procedures or views.
2026-01-16 10:21:50 +01:00
6cabdbb6a3 Remove obsolete entity classes in favor of views
Removed Connection, Endpoint, EndpointAuth, EndpointParam, OutRes, Profile, and RecAction classes, which represented database tables and were marked as obsolete. This cleanup supports the transition to using database views or an updated data access approach. All related code, including attributes and properties, has been deleted.
2026-01-16 10:20:22 +01:00
4dd54e206e Update EndpointAuth references to fully qualified name
Replaced unqualified EndpointAuth with Domain.Entities.EndpointAuth
in DtoMappingProfile and IRecDbContext to improve clarity and
avoid potential naming conflicts.
2026-01-16 10:18:40 +01:00
84cf5c8e4d Add EndpointsController for endpoint CRUD operations
Introduced EndpointsController with POST, PUT, and DELETE API endpoints for managing endpoints. Utilizes MediatR to handle insert, update, and delete procedures, and retrieves configuration values as needed. Includes proper routing and response type annotations.
2026-01-16 10:15:07 +01:00
84d6e7a511 Add EndpointParamsController for CRUD via procedures
Introduced EndpointParamsController to manage endpoint parameter records using stored procedures. The controller supports POST (insert), PUT (update), and DELETE operations, leverages MediatR for command handling, and uses dependency injection for configuration and mediator services. Endpoints are documented and specify response types.
2026-01-16 10:13:33 +01:00
89238cc2d1 Add EndpointAuthController with CRUD endpoints
Introduced EndpointAuthController to manage endpoint authentication records via MediatR and procedure-based commands. Added POST (insert), PUT (update), and DELETE (range delete) endpoints. Controller uses dependency injection and provides XML documentation for each action.
2026-01-16 10:12:11 +01:00
7b177f21c8 Remove ResultViewController and related API endpoints
Removed the entire ResultViewController.cs file, including all endpoints for result view queries and insertions. This eliminates support for fake profile IDs, result type filtering, and all related API logic from the codebase.
2026-01-16 10:08:47 +01:00
2deb235c8d Update Post to use CreatedAtAction with Location header
Refactored the Post method to return a 201 Created response using CreatedAtAction. This change adds a Location header pointing to the Get action with the relevant actionId, and includes both the new record's ID and actionId in the response body. This improves API usability and aligns with RESTful conventions.
2026-01-16 10:07:21 +01:00
c452724c9e Rename controller methods for consistency and clarity
Renamed CreateAction to Create and UpdateAction to Update in RecActionController to improve naming consistency. No changes to method logic or signatures.
2026-01-16 10:02:16 +01:00
13e65774cc Add procedure-based update and delete endpoints to controller
Refactored RecActionController to support update via a new PUT endpoint using UpdateActionProcedure. Changed DELETE endpoints to use DeleteActionProcedure payloads instead of command objects, removing obsolete attributes. Updated using statements for new procedure types and standardized payload handling for update and delete operations.
2026-01-16 10:01:08 +01:00
f2d2dc9a32 Add procedure-based CRUD endpoints to ProfileController
Extended ProfileController with POST, PUT, and DELETE endpoints for profile management using InsertProfileProcedure, UpdateProfileProcedure, and DeleteProfileProcedure. Added necessary using directives and XML documentation for each new endpoint.
2026-01-16 09:57:18 +01:00
459620e1dd Refactor update procedures: rename Guid to Id
Renamed all `Guid` properties and parameters to `Id` in update procedure interfaces, records, and handlers. This clarifies that the identifier is a numeric ID, not a GUID, and improves consistency across the codebase. All related method signatures and usages have been updated accordingly.
2026-01-16 09:53:50 +01:00
df2541108b Add POST/PUT endpoints for RESULT insert and update
Added POST endpoint to insert RESULT records and PUT endpoint to update RESULT records in OutResController. Integrated InsertResultProcedure and UpdateResultProcedure with MediatR, returning appropriate status codes. Updated using directives accordingly.
2026-01-16 09:51:30 +01:00
5dcd5313a5 Refactor EndpointParams procedures to Commands namespace
Moved Delete, Insert, and UpdateEndpointParamsProcedure classes from Common.Procedures to EndpointParams.Commands for better organization. Updated related imports in InsertObjectProcedure.cs and UpdateObjectProcedure.cs to reflect the new namespace. No functional changes to the procedures themselves.
2026-01-16 09:44:46 +01:00
ef9d725f59 Refactor endpoint procedures to Endpoints.Commands namespace
Moved Delete/Insert/UpdateEndpointProcedure classes from Common.Procedures to Endpoints.Commands namespace. Updated using directives accordingly for improved code organization and maintainability. No changes to class logic.
2026-01-16 09:41:19 +01:00
6f4ab073df Move EndpointAuth procedures to dedicated namespace
Refactored Delete/Insert/UpdateEndpointAuthProcedure records into the new ReC.Application.EndpointAuth.Commands namespace for better organization. Updated using statements in related files to ensure compatibility. No functional changes to the procedure records themselves.
2026-01-16 09:38:59 +01:00
f6b95331e1 Refactor procedure classes into feature-based namespaces
Moved Insert, Update, and Delete procedure records for Action, Profile, and Result entities from common namespaces to feature-specific namespaces. Updated all relevant using directives and controller references. No logic changes; this improves code organization and maintainability.
2026-01-16 09:34:41 +01:00
82de285891 Refactor OutResController to use DeleteResultProcedure
Refactored DELETE endpoints in OutResController to use the new DeleteResultProcedure payload instead of DeleteOutResCommand. Updated endpoints to call mediator.ExecuteDeleteProcedure and revised XML docs accordingly. Removed obsolete attributes and command references. The "fake" profile deletion now uses the procedure with the fake profile ID.
2026-01-16 09:18:15 +01:00
Developer 02
cbd86de3e8 Add handling for update and delete exceptions
Enhanced `ExceptionHandlingMiddleware` to handle
`UpdateObjectFailedException` and `DeleteObjectFailedException`.
Logs exception details, including serialized procedure data,
and sets HTTP status to `500 Internal Server Error`.
Improves error differentiation and response for specific failure cases.
2026-01-16 01:10:57 +01:00
Developer 02
a5160b35dd Add HTTP DELETE endpoint to CommonController
A new `DeleteObject` method has been added to the `CommonController` class to handle HTTP DELETE requests. This method is asynchronous and processes a `DeleteObjectProcedure` object passed in the request body using the `mediator.Send` function. The result is returned as an HTTP 200 OK response. The `[HttpDelete]` attribute has been applied to the method to designate it as a DELETE endpoint.
2026-01-16 01:08:30 +01:00
Developer 02
60e1627494 Add UpdateObject endpoint and enhance procedure support
Enhanced `CommonController` to support object operations:
- Added `using` statements for Insert, Update, and Delete procedures.
- Updated constructor to inject `IMediator` for request handling.
- Introduced `UpdateObject` endpoint with HTTP PUT support.
  - Processes `UpdateObjectProcedure` via MediatR.
  - Returns HTTP 200 OK with the result.
2026-01-16 01:08:20 +01:00
Developer 02
b9f08bc21c Add DeleteActionProcedure for ACTION entity deletion
Introduced a new `DeleteActionProcedure` record in the
`ReC.Application.Common.Procedures.DeleteProcedure` namespace.

This record implements the `IDeleteProcedure` interface and includes
the following properties:
- `Start`: Starting GUID/ID (inclusive).
- `End`: Ending GUID/ID (inclusive), defaults to `Start` if 0.
- `Force`: Allows deletion even if dependent RESULT data exists.

Added a `ToObjectProcedure` method to convert `DeleteActionProcedure`
to a `DeleteObjectProcedure` with the entity set to `"ACTION"`.
2026-01-16 01:06:56 +01:00
Developer 02
758616c95e Add DeleteEndpointAuthProcedure for endpoint deletion
Introduce the `DeleteEndpointAuthProcedure` record in the new
`ReC.Application.Common.Procedures.DeleteProcedure` namespace.
This record implements the `IDeleteProcedure` interface and
provides properties for specifying a range of GUID/IDs (`Start`
and `End`) and a `Force` flag to allow deletion despite dependent
data.

Add the `ToObjectProcedure` method to convert the record into a
`DeleteObjectProcedure` with the entity set to `"ENDPOINT_AUTH"`.
2026-01-16 01:06:45 +01:00
Developer 02
7376b49e38 Add DeleteEndpointParamsProcedure record
A new `DeleteEndpointParamsProcedure` record was introduced in the
`ReC.Application.Common.Procedures.DeleteProcedure` namespace. This
record implements the `IDeleteProcedure` interface and provides
properties for specifying a range of GUID/IDs (`Start` and `End`)
and a `Force` flag to allow deletion even with dependent data.

The record includes a `ToObjectProcedure` method to convert its
data into a `DeleteObjectProcedure` instance, setting the entity
to `"ENDPOINT_PARAMS"` and mapping the `Start`, `End`, and `Force`
properties.
2026-01-16 01:06:33 +01:00
Developer 02
b65c354ef0 Add DeleteEndpointProcedure for endpoint deletion
Introduced a new `DeleteEndpointProcedure` record in the
`ReC.Application.Common.Procedures.DeleteProcedure` namespace.

This record implements the `IDeleteProcedure` interface and includes
properties for specifying the start and end GUID/ID range, as well as
a `Force` flag to allow deletion even with dependent data.

Added a `ToObjectProcedure` method to convert the record into a
`DeleteObjectProcedure` with the entity set to "ENDPOINT".
2026-01-16 01:06:22 +01:00
Developer 02
38258a98c1 Add DeleteProfileProcedure for profile deletion logic
Introduce the `DeleteProfileProcedure` record in the new
`ReC.Application.Common.Procedures.DeleteProcedure` namespace.
This record implements the `IDeleteProcedure` interface and
provides properties for specifying a range of GUID/IDs
(`Start`, `End`) and a `Force` flag to allow deletion even
with dependent ACTION data.

Added a `ToObjectProcedure` method to convert
`DeleteProfileProcedure` instances into `DeleteObjectProcedure`
objects, setting the `Entity` to `"PROFILE"`.
2026-01-16 01:06:12 +01:00
Developer 02
7666708ab5 Add DeleteResultProcedure for RESULT entity deletion
Introduced a new `DeleteResultProcedure` record in the
`ReC.Application.Common.Procedures.DeleteProcedure` namespace.

This record implements the `IDeleteProcedure` interface and provides
properties for specifying a range of GUID/IDs (`Start` and `End`)
and a `Force` flag.

Added a `ToObjectProcedure` method to convert the record into a
`DeleteObjectProcedure` with the entity set to "RESULT".
2026-01-16 01:06:00 +01:00
Developer 02
d3b67bc429 Add IDeleteProcedure interface for delete operations
Introduced a new namespace `ReC.Application.Common.Procedures.DeleteProcedure` and added the `IDeleteProcedure` interface. This interface defines a `ToObjectProcedure` method, which returns a `DeleteObjectProcedure` object. These changes aim to standardize and encapsulate delete-related procedures in the application.
2026-01-16 01:05:46 +01:00
Developer 02
97f992aef5 Add DeleteObjectProcedure for entity deletion handling
Introduce `DeleteObjectProcedure` to encapsulate parameters for
delete operations, including entity, ID range, and force flag.
Add `DeleteObjectProcedureExtensions` for convenient invocation
via `ISender`. Implement `DeleteObjectProcedureHandler` to
execute the `[dbo].[PRREC_DELETE_OBJECT]` stored procedure
using dynamic SQL parameters.

Throw `DeleteObjectFailedException` on failure and ensure
`End` defaults to `Start` if unset. Add XML documentation
for improved code clarity. Include necessary `using`
directives for dependencies.
2026-01-16 01:05:28 +01:00
Developer 02
cc6f93ae1c Add DeleteObjectFailedException for delete operation errors
A new `DeleteObjectFailedException` class was introduced in the
`ReC.Application.Common.Exceptions` namespace to handle errors
related to failed delete operations.

This class includes:
- A `Procedure` property of type `DeleteObjectProcedure` to
  provide context about the failed operation.
- Three constructors to support different levels of detail
  (procedure only, procedure with a message, and procedure with
  a message and inner exception).

Additionally, a `using` directive for the
`ReC.Application.Common.Procedures.DeleteProcedure` namespace
was added to support the `DeleteObjectProcedure` type.
2026-01-16 01:05:10 +01:00
Developer 02
402990bd3c Add UpdateResultProcedure record for result updates
Introduced a new `UpdateResultProcedure` record in the
`ReC.Application.Common.Procedures.UpdateProcedure` namespace.
This record implements the `IUpdateProcedure` interface and
includes properties for `ActionId`, `StatusId`, `Header`, and
`Body`.

Added a `ToObjectProcedure` method to convert the record into
an `UpdateObjectProcedure` instance, setting the `Entity` to
"RESULT", and allowing optional tracking of changes via the
`ChangedBy` method.
2026-01-16 00:59:43 +01:00
Developer 02
43cdef4910 Add UpdateProfileProcedure record for profile updates
Introduced a new namespace `ReC.Application.Common.Procedures.UpdateProcedure`.
Added the `UpdateProfileProcedure` record implementing the `IUpdateProcedure` interface.
This record includes several nullable properties such as `Active`, `TypeId`, `Name`, `Description`, and others.
Added the `ToObjectProcedure` method to convert the record into an `UpdateObjectProcedure` instance, setting the entity to "PROFILE" and supporting optional change tracking via `ChangedBy`.
2026-01-16 00:59:33 +01:00
Developer 02
348a55fc60 Add UpdateEndpointProcedure record for endpoint updates
A new `UpdateEndpointProcedure` record was introduced in the
`ReC.Application.Common.Procedures.UpdateProcedure` namespace.
This record implements the `IUpdateProcedure` interface and
includes nullable properties `Active`, `Description`, and `Uri`.

The `ToObjectProcedure` method was added to convert the record
into an `UpdateObjectProcedure` instance, setting the `Entity`
to `"ENDPOINT"`, the `Guid` to the provided parameter, and the
`Endpoint` to the current instance. The method also supports
tracking changes via the optional `changedWho` parameter.
2026-01-16 00:59:23 +01:00
Developer 02
719bc9c941 Add UpdateEndpointParamsProcedure record
A new `UpdateEndpointParamsProcedure` record has been added under the `ReC.Application.Common.Procedures.UpdateProcedure` namespace. This record implements the `IUpdateProcedure` interface and includes nullable properties such as `Active`, `Description`, `GroupId`, `Sequence`, `Key`, and `Value`.

Additionally, a `ToObjectProcedure` method has been introduced, which converts the record into an `UpdateObjectProcedure` instance with the `Entity` set to `"ENDPOINT_PARAMS"`, and supports optional tracking of the user who made the change.
2026-01-16 00:59:10 +01:00
Developer 02
37381af042 Add UpdateEndpointAuthProcedure for endpoint updates
Introduce the `UpdateEndpointAuthProcedure` record in the
`ReC.Application.Common.Procedures.UpdateProcedure` namespace.

This class implements the `IUpdateProcedure` interface and includes
properties for managing endpoint authentication details such as
`Active`, `Description`, `TypeId`, `ApiKey`, `ApiValue`, `Token`,
`Username`, `Password`, `Domain`, and `Workstation`.

Add the `ToObjectProcedure` method to convert the procedure into
an `UpdateObjectProcedure` instance, setting the entity to
`ENDPOINT_AUTH` and allowing optional tracking of the user who
made the changes.
2026-01-16 00:58:55 +01:00
Developer 02
34fe996d91 Add UpdateActionProcedure record for update operations
Introduced a new `UpdateActionProcedure` record in the
`ReC.Application.Common.Procedures.UpdateProcedure` namespace.
This record implements the `IUpdateProcedure` interface and
includes several nullable properties such as `ProfileId`,
`Active`, `Sequence`, `EndpointId`, and others to support
update operations.

Added a `ToObjectProcedure` method to the record, which
creates and returns an `UpdateObjectProcedure` instance
with the entity set to "ACTION" and other relevant details.
This method also supports tracking changes via the `ChangedBy`
method.
2026-01-16 00:58:42 +01:00
Developer 02
252fc10243 Add IUpdateProcedure interface for update handling
Introduced a new namespace `ReC.Application.Common.Procedures.UpdateProcedure` and added the `IUpdateProcedure` interface. This interface defines a `ToObjectProcedure` method for converting to an `UpdateObjectProcedure`. The method accepts a `guid` parameter and an optional `changedWho` parameter to track changes. This addition establishes a contract for handling update procedures.
2026-01-16 00:58:26 +01:00
Developer 02
c8a9245b54 Add UpdateObjectProcedure for entity updates
Introduce `UpdateObjectProcedure` to handle updates for various
entities using a stored procedure. Implement MediatR-based
request/response pattern with `IRequest<int>` and add the
`UpdateObjectProcedureHandler` to execute the update logic.

Include an extension method `ExecuteUpdateProcedure` for
simplified execution of update requests. Add exception handling
via `UpdateObjectFailedException` to manage update failures.

Integrate dependencies on `DigitalData.Core.Abstraction.Application.Repository`,
`MediatR`, and `Microsoft.Data.SqlClient`. Ensure extensibility
and reliability in the update process.
2026-01-16 00:58:11 +01:00
Developer 02
c56bcc198e Add UpdateObjectFailedException for update operation errors
A new exception class, `UpdateObjectFailedException`, was added to the `ReC.Application.Common.Exceptions` namespace to handle failures during update operations.

The class includes a `Procedure` property of type `UpdateObjectProcedure` to store the associated procedure. It provides three constructors to support different levels of detail: one with just the procedure, one with a procedure and a custom message, and one with a procedure, a custom message, and an inner exception.

The `UpdateObjectProcedure` type is referenced from the `ReC.Application.Common.Procedures.UpdateProcedure` namespace, which is now included via a `using` directive.
2026-01-16 00:57:37 +01:00
Developer 02
538abec212 Add entity configurations and suppress obsolete warnings
Introduced entity configurations in `RecDbContext` for `Connection`, `Endpoint`, `EndpointAuth`, `EndpointParam`, `OutRes`, `Profile`, `ProfileView`, `RecAction`, and `RecActionView`. Mapped entities to corresponding tables or views with column configurations.

Added relationships:
- `Profile` to `Actions` (one-to-many).
- `RecAction` to `OutRes` (one-to-one with cascade delete).

Suppressed CS0618 warnings in `OnModelCreating` to handle obsolete members.
2026-01-15 23:59:13 +01:00
Developer 02
19666e649d Add obsolete DbSets and transition to Views in RecDbContext
Several new DbSet properties were added to the RecDbContext
class, marked as `[Obsolete("Use Views instead.")]`. These
include `EndpointParams`, `OutRes`, `Connections`, `Endpoints`,
`EndpointAuths`, `Profiles`, and `RecActions`.

Non-obsolete DbSets such as `RecActionViews`, `ProfileViews`,
`RecResultViews`, `HeaderQueryResults`, `BodyQueryResults`,
and `RecResults` were also added or retained.

These changes indicate a shift towards using Views for database
operations while maintaining backward compatibility with the
older DbSet properties.
2026-01-15 23:56:06 +01:00
Developer 02
1df9235036 Disambiguate Profile mapping in AutoMapper configuration
Updated the mapping configuration in `DtoMappingProfile` to use
the fully qualified name `Domain.Entities.Profile` for the
`Profile` entity. This change resolves potential ambiguity with
other classes named `Profile` in the codebase.
2026-01-15 23:54:28 +01:00
dd9c1c7ca2 Change default addedWho param to null in ToObjectProcedure
Updated ToObjectProcedure methods in Insert*Procedure records to use null as the default value for the addedWho parameter instead of "Rec.API". This affects InsertActionProcedure, InsertEndpointAuthProcedure, InsertEndpointParamsProcedure, InsertEndpointProcedure, and InsertProfileProcedure.
2026-01-14 17:14:17 +01:00
f9cc735318 Update AddedBy to default to "ReC.API" if no value provided
The AddedBy method in InsertObjectProcedure now accepts an optional parameter and sets AddedWho to "ReC.API" when no argument is supplied, ensuring a consistent default value.
2026-01-14 17:12:15 +01:00
2a8eb3c0ad Mark legacy DbSets as obsolete in IRecDbContext
Several entity-based DbSet properties in IRecDbContext are now marked with [Obsolete("Use Views instead.")], guiding developers to use view-based DbSets. The Profiles DbSet is now explicitly typed as Domain.Entities.Profile. No functional code was removed.
2026-01-14 17:10:55 +01:00
7a4cdb3d1f Support querying multiple profile views in ReadProfileView
Refactored ReadProfileViewQuery and handler to return multiple profiles as IEnumerable<ProfileViewDto>. Made Id parameter optional to allow fetching all profiles. Updated exception handling for empty results.
2026-01-14 16:30:41 +01:00
453b6d1813 Add ProfileController with MediatR GET endpoint
Introduced ProfileController to the API, implementing a GET endpoint that uses MediatR to handle ReadProfileViewQuery requests and return profile data. This supports CQRS and structured profile retrieval.
2026-01-14 16:23:00 +01:00
bd2b5ff62f Add ReadProfileViewQuery and handler for profile views
Introduce CQRS/MediatR query and handler to fetch ProfileView by Id,
optionally including related Actions. Uses repository and AutoMapper,
throws NotFoundException if profile is missing, and returns ProfileViewDto.
2026-01-14 16:12:54 +01:00
849de7a204 Replace CreateResultViewCommand with InsertResultProcedure
Updated ResultViewController to use InsertResultProcedure instead of CreateResultViewCommand. Changed import statements, method parameters, and response construction to reflect the new procedure type.
2026-01-14 15:31:16 +01:00
c7423fb6fd Refactor RecAction creation and remove fake endpoint
Refactored the CreateAction endpoint to use InsertActionProcedure and mediator.ExecuteInsertProcedure, replacing the obsolete CreateRecActionCommand. Removed the deprecated CreateFakeAction endpoint and all related code from RecActionController.
2026-01-14 15:25:40 +01:00
ad51e4b1eb Update OutResController to use ReadResultViewQuery
Switched GET endpoints to ReadResultViewQuery for modernized query handling. Removed [Obsolete] from the controller class, but added it to DELETE endpoints to mark them as deprecated. Updated "fake" profile GET endpoints to use the new query model and removed obsolete warnings from these methods.
2026-01-14 15:10:20 +01:00
df2ebe0cc2 Refactor to use ExecuteInsertProcedure for result insert
Replaced sender.Send and ToObjectProcedure with sender.ExecuteInsertProcedure, passing the InsertResultProcedure object, AddedWho config, and cancellation token directly. This simplifies and clarifies the result insertion process.
2026-01-14 15:00:22 +01:00
5c06f287ab Refactor insert procedure API and add extension method
Changed IInsertProcedure.ToObjectProcedure to accept an optional addedWho parameter. Introduced InsertObjectProcedureExtensions with an ExecuteInsertProcedure extension for ISender, defaulting addedWho to "Rec.API" if not specified.
2026-01-14 14:56:10 +01:00
c3e3a0377d Switch to InsertResultProcedure for result handling
Replaced CreateOutResCommand with InsertResultProcedure when sending results. Updated property names (StatusId instead of Status) and now use ToObjectProcedure to include AddedWho. Added InsertProcedure namespace import.
2026-01-14 14:40:30 +01:00
cf34a54170 Mark CreateResultViewCommand as obsolete, add handler
CreateResultViewCommand is now marked obsolete, advising use of related procedures or views instead. Added an obsolete CreateResultViewCommandHandler that delegates creation to the repository.
2026-01-14 14:36:30 +01:00
16f48f125b Refactor ToObjectProcedure default parameter handling
Changed ToObjectProcedure to use a nullable addedWho parameter and set "Rec.API" as a fallback within the method, ensuring consistent default behavior.
2026-01-14 14:32:17 +01:00
e3faa2f570 Mark entity classes as obsolete; add table attributes
Marked Connection, Endpoint, EndpointAuth, EndpointParam, OutRes, Profile, and RecAction classes as obsolete with [Obsolete("Use Views instead.")]. Added or updated [Table] attributes to specify database tables and schemas for each class. This signals a transition to using Views instead of these entities.
2026-01-14 13:41:52 +01:00
0d30b5ff87 Register default repository for TRecDbContext
Also register the default repository for TRecDbContext in dependency injection, ensuring both custom and default repositories are available for use.
2026-01-14 13:32:09 +01:00
563375f6e3 Refactor validator to use nested property accessors
Update InsertObjectProcedureValidator to reference nested properties (e.g., x.Action.ProfileId instead of x.ActionProfileId) throughout all entity validation rules. Adjust .When conditions accordingly to match the new data model structure with grouped sub-objects.
2026-01-14 13:17:10 +01:00
3864b0f68b Add CommonController with POST endpoint using MediatR
Introduced CommonController in the ReC.API.Controllers namespace with a POST endpoint at "api/common". The endpoint accepts an InsertObjectProcedure from the request body, sends it via MediatR, and returns a 201 Created response with the generated ID.
2026-01-14 13:13:23 +01:00
35e03269e7 Add DbSets for EndpointParam and view entities
Added DbSet properties for EndpointParam, RecActionView, and ProfileView to both IRecDbContext and RecDbContext. This enables querying and interaction with these entities and views in the database. Grouped the new DbSets under a #region for better code organization.
2026-01-14 12:00:36 +01:00
8b212d541e Refactor AddedWho assignment in InsertObjectProcedure
AddedWho is now set via the AddedBy method instead of direct assignment in object initializers. The property is made internal with a private setter to prevent external modification. All ToObjectProcedure methods in insert procedure records are updated to use AddedBy for consistency and encapsulation.
2026-01-14 11:51:42 +01:00
dd4cecc15d Rename InsertObjectFailedException.Request to Procedure
Updated ExceptionHandlingMiddleware to use the new Procedure property name when logging InsertObjectFailedException. Changed error title for BadRequestException to "Bad Procedure". Removed unused System.Text.Json using directive from InsertObjectProcedure.cs.
2026-01-14 09:54:19 +01:00
0dedb506e1 Handle InsertObjectFailedException in middleware
Add specific handling for InsertObjectFailedException in ExceptionHandlingMiddleware, including detailed logging and custom error response. Refactor InsertObjectFailedException to expose the request data via a public property for improved error reporting.
2026-01-14 09:51:40 +01:00
24f146ca26 Refactor InsertObjectFailedException for more context
InsertObjectFailedException now requires an InsertObjectProcedure
instance, improving error context. Exception throwing in
InsertObjectProcedureHandler updated to pass the procedure object
instead of just a message and serialized request.
2026-01-14 09:44:31 +01:00
2692553865 Add 'addedWho' param to insert procedures for auditing
Updated IInsertProcedure and all implementations to accept an optional 'addedWho' parameter in ToObjectProcedure, defaulting to "Rec.API". This enables tracking of the entity responsible for insert operations.
2026-01-14 09:37:17 +01:00
d90c2fab96 Refactor: split insert procedures into separate files
Refactored insert procedure-related classes by moving each record type (Action, Endpoint, EndpointAuth, Profile, Result, EndpointParams) and the IInsertProcedure interface into their own files under the new InsertProcedure namespace. Updated InsertObjectProcedureValidator to use the new namespace. This improves code organization, readability, and maintainability.
2026-01-14 09:26:27 +01:00
854e36e71f Refactor insert procedures with entity-specific records
Refactored insert procedure modeling by introducing the IInsertProcedure interface and entity-specific Insert[Entity]Procedure records. Each entity now has its own record with relevant properties and a ToObjectProcedure() method, improving type safety, clarity, and extensibility for insert operations. Updated InsertObjectProcedure to use these new types.
2026-01-14 09:18:52 +01:00
1d31f2aff9 Refactor InsertObjectProcedure to use nested records
Reorganized InsertObjectProcedure by grouping related properties into nested record types (Action, Endpoint, EndpointAuth, Profile, Result, EndpointParams) for better encapsulation and maintainability. Updated handler logic to use new structure and set AddedWhen to DateTime.UtcNow. Improved error logging and added System.Text.Json usage.
2026-01-12 16:47:57 +01:00
11206cf84f Throw exception on failed InsertObject identifier return
Replace default return of 0 with InsertObjectFailedException when the stored procedure does not return a valid identifier. Exception message includes serialized request for easier debugging. Added necessary imports for exception and JSON serialization.
2026-01-12 16:27:45 +01:00
b48ebd8e88 Refactor InsertObjectFailedException constructors
Refactored InsertObjectFailedException to use explicit constructors: parameterless, message-only, and message with inner exception. Removed constructors with optional parameters for clearer and more standard .NET exception handling.
2026-01-12 16:21:16 +01:00
12d17e0808 Add InsertObjectFailedException custom exception class
Introduced InsertObjectFailedException in the ReC.Application.Common.Exceptions namespace. This exception provides constructors for custom messages and inner exceptions, and is intended to signal failures during object insertion operations.
2026-01-12 16:16:52 +01:00
1dee3180d5 Refactor InsertObjectProcedure into entity-based regions
Organized properties into regions by target entity for clarity.
No functional changes; update improves code readability
and maintainability.
2026-01-12 16:07:52 +01:00
bd4046a6c1 Add InsertObjectProcedureValidator with entity-specific rules
Introduced InsertObjectProcedureValidator using FluentValidation to enforce required fields and string length constraints for InsertObjectProcedure. Validation rules are applied conditionally based on the Entity type, ensuring correct data for ACTION, ENDPOINT, PROFILE, RESULT, and ENDPOINT_PARAMS. Optional string fields also receive length checks.
2026-01-12 16:03:10 +01:00
af6f94c1ed Improve handling of stored procedure return value type
Check if return value is already a long before parsing its
string representation. This enhances robustness and efficiency
when the value is of the correct type.
2026-01-12 15:48:03 +01:00
7bfb56b664 Add InsertObjectProcedure MediatR command and handler
Introduces InsertObjectProcedure and its handler to support generic, parameterized insertion of various object types (ACTION, ENDPOINT, etc.) via a single stored procedure. The handler maps request properties to SQL parameters, executes the procedure, and returns the output GUID. This enables flexible and unified object creation through MediatR.
2026-01-12 15:19:54 +01:00
1a6eced316 Rename InsertObject to InsertObjectProcedure
Renamed the InsertObject MediatR request record to InsertObjectProcedure and moved it from InsertObject.cs to InsertObjectProcedure.cs. The structure and functionality remain unchanged; it still defines an Entity property with a default value of "ACTION".
2026-01-12 14:35:16 +01:00
c82749bcbf Add TODO comment for appsettings.json config in DbContext
A TODO comment was added above OnModelCreating in RecDbContext to note that configuration should be updated to use appsettings.json in the future. No functional changes were made.
2026-01-12 14:25:15 +01:00
e8fa149532 Move InsertObjectResult mapping to Fluent API
Removed [Column] attribute from InsertObjectResult and configured column mapping for NewObjectId in RecDbContext using the Fluent API. This centralizes entity mapping logic in the DbContext.
2026-01-12 14:20:41 +01:00
aaa7beb92a Add RecResults DbSet for InsertObjectResult entities
Added RecResults DbSet to IRecDbContext and RecDbContext to support managing InsertObjectResult entities. Updated IRecDbContext to include the new DbSet and ensured SaveChangesAsync is defined. Enables querying and persisting InsertObjectResult via EF Core.
2026-01-12 14:19:19 +01:00
5cce52ec27 Add ProfileViews and RecResultViews to IRecDbContext
Updated IRecDbContext to include DbSet properties for ProfileView and ResultView entities, enabling management of these collections within the context.
2026-01-12 14:17:34 +01:00
3f36f048b2 Add InsertObjectResult class for object insert results
Introduced InsertObjectResult in ReC.Domain.QueryOutput with a required NewObjectId property mapped to the "oGUID" column. This class will be used to represent the result of object insert operations. Added necessary using directive for data annotations.
2026-01-12 14:17:04 +01:00
92e8d9e778 Move query result classes to QueryOutput namespace
BodyQueryResult and HeaderQueryResult were relocated from ReC.Domain.Entities to ReC.Domain.QueryOutput. Updated all references in IRecDbContext and RecDbContext to use the new namespace.
2026-01-12 14:11:05 +01:00
2d04670fef Set default Entity to "ACTION" in InsertObject
Added XML summary to Entity property describing valid values.
Changed default value from null to "ACTION" for clarity.
2026-01-12 14:08:03 +01:00
c0085b4c18 Add InsertObject record for MediatR-based insert requests
Introduced InsertObject record in ReC.Application.Common.Procedures namespace. This record implements MediatR's IRequest<long> and includes a non-nullable Entity property to support object insertion operations.
2026-01-12 13:54:04 +01:00
59ea5e3e67 Update action invocation logic and remove EndpointAuth include
Changed invocation filter to use Results.Any() instead of Root.OutRes.
Removed eager loading of EndpointAuth from the query.
2026-01-12 13:26:54 +01:00
a9f2c4c2f7 Add Action and Profile relationships to Result entity
Added navigation properties and foreign key relationships from Result to Action (one-to-many) and Profile (many-to-one) entities using ActionId and ProfileId. This enhances entity associations in the data model.
2026-01-12 13:03:56 +01:00
001f4bf2c5 Configure one-to-many relationship for Profile-Actions
Added entity mapping to establish a one-to-many relationship between profiles and actions, with actions referencing their parent profile via the ProfileId foreign key.
2026-01-12 12:54:00 +01:00
bfe6c12ee0 Annotate ProfileView and add Actions property
ProfileView is now mapped to the VWREC_PROFILE table using the
[Table] attribute. Added an Actions property to support related
RecActionView collections.
2026-01-12 12:50:05 +01:00
e1260e49f0 Add one-to-many Action-Results relationship mapping
Added entity configuration to define a one-to-many relationship between Actions and Results. Each Action can have multiple Results, and each Result references its parent Action via the ActionId foreign key.
2026-01-12 12:47:21 +01:00
8b86eca838 Add obsolete navigation props to RecActionView class
Added Results, Root, Endpoint, EndpointAuth, and SqlConnection navigation properties to RecActionView, all marked as [Obsolete] to guide usage toward related procedures or views. Foreign key attributes applied where relevant.
2026-01-12 12:39:53 +01:00
fb12cb6c98 Refactor ResultView logic under OutResults namespace
Moved ReadResultViewQuery and its handler from ResultViews to OutResults namespace. Merged ResultView mapping profile into OutResults.MappingProfiles and removed the old MappingProfile class. Updated controller references accordingly for improved organization.
2026-01-12 11:32:39 +01:00
2635bfb223 Move CreateResultViewCommand to OutResults.Commands
Relocated CreateResultViewCommand and its handler from ResultViews.Commands to OutResults.Commands. Updated all namespace references and using directives accordingly. Modified the project file to include the new folder structure. No functional changes to the command or handler logic.
2026-01-12 11:30:46 +01:00
5245cd04ff Refactor: move RecActionViewQuery and MappingProfile
Moved ReadRecActionViewQuery and MappingProfile from RecActionViews to RecActions namespace. Updated all references and using directives accordingly. This organizational change consolidates related queries and mapping profiles for improved clarity and maintainability.
2026-01-12 11:27:51 +01:00
9d5334e7dc Move RecActionView commands to RecActions namespace
Refactored InvokeBatchRecActionViewsCommand and InvokeRecActionViewCommand to ReC.Application.RecActions.Commands. Updated related handlers, records, and extension methods to use the new namespace. Removed obsolete using statement in RecActionController. No functional changes; organizational update only.
2026-01-12 11:21:26 +01:00
e5bb61376a Mark ObtainEndpointCommand and mapping as obsolete
Mark ObtainEndpointCommand, its handler, and related mapping
profile as [Obsolete] with guidance to use the new procedure
or view-based approach. This deprecates the old command and
mapping logic in favor of updated patterns.
2026-01-12 11:07:17 +01:00
d3aa8c715b Mark fake OutRes endpoints and queries as [Obsolete]
Added [Obsolete] attribute to "fake" Get methods in OutResController, as well as to ReadOutResQuery, ReadOutResHandler, and ReadOutResQueryValidator. These components are now deprecated in favor of related procedures or views. No functional changes were made.
2026-01-12 11:02:27 +01:00
6720e66b23 Mark DeleteOutResCommand and related classes as obsolete
Added the [Obsolete("Use the related procedure or view.")] attribute to DeleteOutResCommand, its handler, and its validator to indicate these should no longer be used and to guide consumers toward the recommended procedure or view.
2026-01-12 11:00:21 +01:00
71470fc21d Mark CreateOutResCommandHandler and MappingProfiles obsolete
Both CreateOutResCommandHandler and the MappingProfiles constructor are now marked with [Obsolete] attributes, warning developers to use the related procedure or view instead. This signals that these components are deprecated and may be removed in the future.
2026-01-12 10:59:24 +01:00
9191ec4179 Update [Obsolete] messages to mention procedures or views
Updated [Obsolete] attribute messages across several commands,
handlers, and the mapping profile to clarify that related
procedures or views should be used instead. Added [Obsolete]
to CreateOutResCommand. No functional changes made.
2026-01-12 10:52:41 +01:00
e0d83c0a14 Mark RecAction commands and endpoints as obsolete
Added [Obsolete("Use the related procedure.")] to RecAction creation and deletion commands, handlers, mapping profile, and related controller endpoints to indicate deprecation. No functional changes were made.
2026-01-12 10:50:39 +01:00
03bcfb6fc9 refactor(ProfileView): move to Views directory 2026-01-12 10:36:39 +01:00
284ced6059 refactor(ResultView): move to Views directory 2026-01-12 10:34:27 +01:00
88c6e6d214 refactor(RecActionView): move to Views directory 2026-01-12 10:33:59 +01:00
6263848a0a Update DigitalData.Core.Infrastructure to v2.6.1
Upgraded the DigitalData.Core.Infrastructure NuGet package from version 2.6.0 to 2.6.1 in ReC.Infrastructure.csproj to include the latest fixes and improvements.
2026-01-12 10:26:23 +01:00
83f173fdc4 Update DigitalData.Core package versions in csproj files
Upgraded DigitalData.Core.Abstraction.Application to v1.6.0 in ReC.Application.csproj and DigitalData.Core.Infrastructure to v2.6.0 in ReC.Infrastructure.csproj. No other changes made.
2025-12-19 11:01:05 +01:00
7c687c0541 Remove Root property from ResultView entity
Removed the Root navigation property and its [ForeignKey("Id")] attribute from the ResultView class, as it is no longer needed. No other changes were made to the class structure.
2025-12-17 12:50:48 +01:00
48e9812224 Add handler for CreateResultViewCommand with repository
Added CreateResultViewCommandHandler to process creation of ResultView entities using IRepository<ResultView>. Also updated using directives to include necessary repository and entity namespaces.
2025-12-17 11:55:23 +01:00
1199c61ae8 Add AuthScopedFilter to set AddedWho via middleware
Introduce AuthScopedFilter to automatically set the AddedWho property on IAuthScoped commands using configuration, and register it globally for all controllers. Remove manual AddedWho assignment from ResultViewController. Make AddedWho nullable in AuthScope and IAuthScoped.
2025-12-17 11:52:59 +01:00
a55b51e504 Set AddedWho from config in Create; make property mutable
Changed AuthScope.AddedWho to be mutable (get; set;). In ResultViewController, set AddedWho from configuration in the Create action and throw an error if missing. Ensures AddedWho is always set and configuration issues are clearly reported.
2025-12-17 11:51:48 +01:00
752f781f54 Add AuthScopedValidator and register FluentValidation
Introduced AuthScopedValidator to enforce non-empty AddedWho on IAuthScoped entities with a clear error message. Registered all validators from the assembly in DI to enable automatic validation for authentication-scoped entities.
2025-12-17 11:37:58 +01:00
9b800dce20 Add POST endpoint to create ResultView in controller
Added a new HTTP POST action to ResultViewController that accepts a CreateResultViewCommand in the request body. On success, it returns a 201 Created response with a reference to the Get action for the created resource. This enables clients to create new ResultView entries via the API.
2025-12-17 11:17:14 +01:00
0fa1a418de Refactor validation behavior and update result view command
Refactored ValidationBehavior to use C# 12 primary constructors and file-scoped namespaces, simplifying the Handle method logic. Updated CreateResultViewCommand to implement IAuthScoped and IRequest, replaced AddedWho with a non-serialized Scope property, and added necessary usings and namespace.
2025-12-17 11:16:48 +01:00
cc54539aba Refactor scope handling with new IScoped and AuthScope
Introduce IScoped<TScope> interface for unified scope access and add AuthScope record. Remove legacy IScopedRequest interfaces to streamline scope management across the application.
2025-12-17 11:16:22 +01:00
ac4c4cb69a Add generic scoped request interfaces for MediatR support
Introduced IScopedRequestBase<TScope>, IScopedRequest<TScope>, and IScopedRequest<TScope, TResponse> interfaces to enable requests with scope information. These interfaces extend MediatR's IRequest types and enforce non-nullable scope types for improved request handling.
2025-12-17 10:41:04 +01:00
73ccb9e43b Add AutoMapper profile for CreateResultViewCommand mapping
Introduced a MappingProfile in ReC.Application.ResultViews to map CreateResultViewCommand to ResultView, setting AddedWhen to DateTime.UtcNow during mapping. Included necessary using directives.
2025-12-17 10:26:29 +01:00
b6ab59ae4a Add CreateResultViewCommand class for result view creation
Introduced CreateResultViewCommand in the ReC.Application.ResultViews.Commands namespace. This class includes properties for ActionId, StatusCode, Header, Body, and AddedWho, and will be used as a command or DTO for creating ResultView entities.
2025-12-17 10:13:33 +01:00
868e11ff62 Annotate ProfileView with table mapping attribute
Added [Table("VWREC_PROFILE", Schema = "dbo")] to ProfileView to specify its database table mapping. Included necessary using directive for DataAnnotations.Schema.
2025-12-17 09:39:25 +01:00
38d819adac Map ResultView to VWREC_RESULT with Table attribute
Explicitly map ResultView entity to "VWREC_RESULT" in the "dbo" schema using the [Table] attribute to ensure correct Entity Framework mapping.
2025-12-17 09:38:32 +01:00
9b3bb925f9 Refactor ResultType enum location and naming for clarity
Move ResultType enum to ReC.API.Models/ResultType.cs and update its values from Header/Body to OnlyHeader/OnlyBody for improved clarity. Update all controller usages and documentation to reflect these changes.
2025-12-17 09:35:24 +01:00
a92d57d9cf Return 404 for null body/header in result controllers
Previously, OutResController and ResultViewController returned HTTP 200 OK with an empty object when the response body or header was null. Now, they return HTTP 404 Not Found in these cases, providing more accurate HTTP status codes for missing resources. Non-null bodies or headers continue to return HTTP 200 OK with the deserialized content.
2025-12-17 09:31:58 +01:00
39fcee2b50 Add ResultViewController for output result retrieval
Introduce ResultViewController with endpoints to fetch output results by query, for fake/test profiles, and by action ID. Supports returning full, header, or body parts of results via a new ViewResultType enum. Utilizes MediatR and configuration injection for flexible and testable result access. Includes XML docs for improved API clarity.
2025-12-16 12:51:17 +01:00
d0597e28e8 Mark OutResController as obsolete with deprecation notice
Added [Obsolete] attribute to OutResController, indicating it is deprecated and will be removed in future versions. Developers are advised to use ResultViewController instead.
2025-12-15 17:13:05 +01:00
d8d77652ac Implement ReadResultViewQuery handler with filtering
Added ReadResultViewQueryHandler to support querying ResultView entities with optional filters (Id, ActionId, ProfileId). Integrated repository and AutoMapper, included error handling for no results, and improved using directives.
2025-12-15 17:11:23 +01:00
62612897bd Add ReadResultViewQuery and clean up DTO namespace
Introduced ReadResultViewQuery for querying result views with optional filters (Id, ActionId, ProfileId). Cleaned up ResultViewDto.cs by removing an unused using directive and reformatting the namespace declaration. Added necessary usings to support the new query.
2025-12-15 17:05:49 +01:00
99c50fb348 Make ResultViewDto immutable; add Root property
Refactored ResultViewDto to use init-only properties for immutability. Added a new Root property of type OutResDto?. Included a using directive for ReC.Domain.Entities to support the new property.
2025-12-15 17:02:11 +01:00
8aaf11f39d Add Root navigation property to ResultView with FK
Added OutRes? Root navigation property to ResultView, annotated with [ForeignKey("Id")], to establish an optional relationship to the OutRes entity. Also added the necessary using directive for DataAnnotations.Schema.
2025-12-15 17:01:35 +01:00
152189fefd Refactor: Rename RecResultViewDto to ResultViewDto
Replaced all usage of RecResultViewDto with ResultViewDto for improved naming consistency. Updated AutoMapper profile to map ResultView to ResultViewDto. Removed RecResultViewDto.cs and added ResultViewDto.cs with the same properties. No functional changes.
2025-12-15 16:57:05 +01:00
5a4b8427be Rename RecResultView entity to ResultView throughout codebase
Replaced RecResultView with ResultView, updating all references in DbContext, entity mapping, and AutoMapper profiles. Added the new ResultView class and removed the old RecResultView class. No changes to properties or structure.
2025-12-15 16:56:37 +01:00
e4a644a636 Add mappings for RecResultView and ProfileView DTOs
Added AutoMapper profile mappings for RecResultView to RecResultViewDto and ProfileView to ProfileViewDto to enable automatic entity-to-DTO conversions.
2025-12-15 16:55:45 +01:00
25c6c41b26 Add RecResultViewDto for result view data representation
Introduced RecResultViewDto in the ReC.Application.Common.Dto namespace. This DTO encapsulates properties for result entity view data, including related action and profile details, status, content, and audit information.
2025-12-15 16:53:25 +01:00
c672a10c97 Add navigation properties to RecResultView class
Added Action and Profile navigation properties to RecResultView, enabling direct access to related RecActionView and ProfileView data for improved object relationships and easier data retrieval in views or APIs.
2025-12-15 16:45:46 +01:00
1e21218f31 Make RecResultView properties nullable for flexibility
Changed ActionId, ProfileId, StatusCode, AddedWho, and AddedWhen
to nullable types in RecResultView to better support optional or
missing data scenarios. This improves compatibility with cases
where these fields may not always have values.
2025-12-15 16:43:39 +01:00
68e7ee54f9 Rename StatusId/Status to StatusCode/StatusName for clarity
Renamed the StatusId property to StatusCode and Status to StatusName in RecResultView. Updated RecDbContext mappings accordingly to maintain consistency and improve code clarity.
2025-12-15 16:42:01 +01:00
2a749267b3 Rename ResultHeader/ResultBody to Header/Body in RecResultView
Renamed the ResultHeader and ResultBody properties in the RecResultView class to Header and Body. Updated the RecDbContext entity configuration to map these new property names to the existing RESULT_HEADER and RESULT_BODY database columns. No changes were made to the database schema.
2025-12-15 16:35:15 +01:00
edc1de2034 Add RecResultView entity and DbSets to RecDbContext
Added DbSet properties for RecResultView and OutRes to RecDbContext. Configured entity mapping for RecResultView to the VWREC_RESULT view, including key and property-to-column mappings. This enables querying RecResultView and OutRes through the context.
2025-12-15 16:33:57 +01:00
ae79a60605 Add RecResultView entity for recommendation results
Introduced the RecResultView class in the ReC.Domain.Entities namespace. This class encapsulates properties related to recommendation results, including identifiers, status, result details, and audit metadata such as who added or changed the result and when.
2025-12-15 16:33:41 +01:00
6b8286a386 Move logging config to appsettings.Logging.json
Extracted "Logging" and "NLog" sections from appsettings.json into a new appsettings.Logging.json file. No changes were made to the logging settings themselves; this improves configuration separation and maintainability.
2025-12-15 16:15:01 +01:00
1fabc29e4f Auto-load appsettings.*.json files at startup
Dynamically adds all appsettings.*.json files in the root directory to the configuration, excluding appsettings.Development.json and appsettings.migration.json. This streamlines configuration management by automatically including relevant environment or custom config files.
2025-12-15 15:51:31 +01:00
56fb34d987 Update Status property mapping to STATUS_ID column
Changed the database column mapping for the Status property from "STATUS" to "STATUS_ID" to ensure correct association with the updated schema.
2025-12-15 15:31:30 +01:00
47ddde239e Update ProfileType mapping to PROFILE_TYPE_ID column
Changed the entity configuration to map the ProfileType property to the "PROFILE_TYPE_ID" column instead of "PROFILE_TYPE" for consistency with database schema.
2025-12-15 15:23:32 +01:00
5a4d2d8553 Update profile type fields to use ProfileType enum
Replaced byte/byte? with the strongly-typed ProfileType/ProfileType? enum for profile type fields in ProfileView and RecActionView. Added the necessary using directive for ReC.Domain.Constants. This improves type safety and code readability.
2025-12-15 15:07:47 +01:00
4e209e29fc Refactor ReadRecActionViewQuery for optional ProfileId
Refactored ReadRecActionViewQuery to remove base class inheritance and make ProfileId optional. Simplified query construction and filtering logic in the handler to conditionally filter by ProfileId only when provided. Removed unused constructors and methods for a cleaner API.
2025-12-15 15:07:26 +01:00
78aaea67e6 Refactor InvokeBatchRecActionViewsCommand structure
Decouple InvokeBatchRecActionViewsCommand from ReadRecActionQueryBase, making it a plain IRequest with an explicit ProfileId property. Update the extension and handler to use the new structure, improving clarity and separation of concerns.
2025-12-15 15:06:46 +01:00
98261f4e21 Refactor ProfileType to enum in RecActionViewDto
Changed ProfileType from string? to ProfileType? enum for improved type safety. Updated URI scheme assignment to use ToUriBuilderScheme(), centralizing scheme mapping logic.
2025-12-15 15:05:56 +01:00
aab8174500 Add ProfileType enum and extension for URI scheme
Introduced the ProfileType enum with Http and Https values in the ReC.Domain.Constants namespace. Added ProfileTypeExtensions with a ToUriBuilderScheme method to convert enum values to their lowercase string representations for URI building.
2025-12-15 15:05:10 +01:00
fb6d6af12b Change ProfileType to byte? in RecActionView
Updated the ProfileType property in RecActionView from string? to byte? to improve type safety and performance by using a numeric value instead of a string.
2025-12-15 14:37:57 +01:00
f82f4d2c65 Refactor RestType enum: remove Invalid, use byte
RestType now inherits from byte for efficiency. The Invalid value has been removed from the enum, and the IsValid extension method was updated to reflect this change by only checking for None.
2025-12-15 14:35:26 +01:00
90ee3f6a5d Explicitly set ErrorAction enum underlying type to byte
Changed ErrorAction enum declaration to use byte as its underlying type for improved memory efficiency and clarity.
2025-12-15 14:28:13 +01:00
a24ec1ab3e Set ApiKeyLocation enum underlying type to byte
Changed ApiKeyLocation enum to use byte as its underlying type instead of the default int, reducing memory usage and improving clarity for serialization scenarios.
2025-12-15 14:27:45 +01:00
b8c30d520e Set EndpointAuthType enum underlying type to byte
Explicitly define EndpointAuthType as a byte-based enum for improved memory efficiency and better interoperability with systems requiring a specific underlying type.
2025-12-15 14:22:13 +01:00
f69f323542 Make ProfileView string properties nullable
Changed several ProfileView properties (Type, Mandantor, ProfileName, LogLevel, Language, AddedWho) from non-nullable strings with default values to nullable strings. This allows these fields to be null instead of defaulting to an empty string.
2025-12-15 14:03:15 +01:00
3621820060 Refactor RecActionView Profile property; add ProfileViewDto
Changed RecActionView.Profile to use ProfileView type instead of Profile entity. Added new ProfileViewDto class to represent profile data as a DTO, including related properties and audit fields.
2025-12-15 14:01:11 +01:00
0e0f27c124 Rename ProfileGuid to Id in ProfileView and DbContext
Renamed the ProfileGuid property to Id in the ProfileView record.
Updated all DbContext mappings and primary key definitions to use
Id, while maintaining the mapping to the PROFILE_GUID column in
the database view.
2025-12-15 13:51:16 +01:00
5404530785 Add ProfileView entity mapped to VWREC_PROFILE view
Introduced the ProfileView entity as a record to represent data from the VWREC_PROFILE database view. Registered ProfileView in RecDbContext and configured its property mappings and primary key in OnModelCreating. This enables querying profile data via EF Core.
2025-12-15 13:51:00 +01:00
289b6109e4 Refactor DeleteRecActionsCommand to use RecAction entity
Switched from using RecActionDto to RecAction entity in the
DeleteRecActionsCommandHandler and updated repository types
and using statements accordingly. This aligns the handler
with domain-driven design and improves consistency.
2025-12-15 13:24:11 +01:00
679c065aaa Refactor CreateRecAction to use domain entities
Switched from using RecActionDto to RecAction domain entity in the CreateRecActionCommandHandler and updated relevant imports. This aligns the command handler with domain-driven design principles.
2025-12-15 13:23:56 +01:00
a02cac8778 Refactor handler to use AutoMapper and entity repository
Updated ObtainEndpointCommandHandler to depend on IRepository<Endpoint> and IMapper. Now maps Endpoint entities to EndpointDto using AutoMapper before returning, ensuring proper separation of concerns and DTO exposure.
2025-12-15 13:08:21 +01:00
42fd176fad Refactor RestType null check with pattern matching
Refactored the null check for action.RestType to use pattern matching, assigning it to a local variable restType if not null. Now throws DataIntegrityException if RestType is null, and uses restType for creating the HttpRequestMessage. This improves code clarity and ensures RestType is non-null in subsequent logic.
2025-12-15 12:53:43 +01:00
e529027587 Refactor HTTP method handling to use RestType enum
Replaced string-based HTTP method mapping with a strongly-typed RestType enum for improved type safety. Updated the ToHttpMethod extension to accept RestType, validate its value, and convert it to the appropriate HttpMethod. This reduces errors from invalid or misspelled method names.
2025-12-15 12:48:10 +01:00
cc2adab5e5 Rename ToHttpMethod to ToHttpMethodName in RestTypeExtensions
Renamed the extension method ToHttpMethod to ToHttpMethodName in the RestTypeExtensions class for improved clarity. The method's functionality remains unchanged; it still returns the uppercase string representation of the RestType enum value.
2025-12-15 12:47:59 +01:00
a0233fd876 Update RecActionViewDto: new props, type changes, renames
Renamed ProfileSequence to Sequence. Added display name properties for enums/types (e.g., EndpointAuthTypeName, RestTypeName, ErrorActionName). Added EndpointAuthApiKey. Changed RestType to enum and ErrorAction to nullable, with corresponding name properties.
2025-12-15 12:37:33 +01:00
0afe9870c0 Update RecActionView model and mapping for enums and names
Refactored RecActionView to use enum types for EndpointAuthType, ApiKeyLocation, and RestType, and added properties for their display names. Introduced ErrorAction and its display name. Updated RecDbContext mapping to treat RecActionView as a view with a key, map new enum ID and name fields, and align property mappings with the database view structure. This enhances clarity and supports both ID and human-readable values in the model.
2025-12-15 12:01:39 +01:00
784b4b1f05 Refactor to use DTOs instead of domain entities
Replaced references to ReC.Domain.Entities with ReC.Application.Common.Dto across multiple files to use DTOs in the application layer. Updated MappingProfile.cs namespace to ReC.Application.RecActionViews and adjusted using statements accordingly. These changes improve separation of concerns and ensure commands and mappings operate on DTOs rather than domain entities.
2025-12-15 11:58:43 +01:00
1634b4b7b1 Add RestType enum and extension methods for HTTP verbs
Introduced RestType enum in ReC.Domain.Constants to represent HTTP methods, including support for None and Invalid values. Added RestTypeExtensions with ToHttpMethod and IsValid methods to facilitate HTTP method handling and validation.
2025-12-15 11:40:44 +01:00
576b2d59d9 Update IRecDbContext to use domain model DbSet types
Changed DbSet properties in IRecDbContext from DTO types to their corresponding domain model classes for Connections, Endpoints, EndpointAuths, Profiles, and RecActions. This aligns the interface with the domain model and removes reliance on DTOs.
2025-12-15 11:30:48 +01:00
3da16ba640 Introduce DTOs and refactor app layer to use them
Added DTO classes for Connection, Endpoint, EndpointAuth, EndpointParam, Profile, and RecAction. Updated AutoMapper profiles, DbContext, and command handlers to use DTOs instead of domain entities. This decouples the application layer from the domain model, improving maintainability and flexibility. Cleaned up some using directives and file headers.
2025-12-12 15:16:38 +01:00
71a0220c3f Refactor: replace RecActionDto with RecActionViewDto
Standardize usage of RecActionViewDto across the codebase:
- Update pipeline behaviors, mapping profiles, and commands to use RecActionViewDto.
- Remove RecActionDto and introduce RecActionViewDto with equivalent properties and methods.
- Adjust query handlers and related interfaces to work with RecActionViewDto.
This clarifies DTO usage and aligns the model with domain intent.
2025-12-12 15:04:43 +01:00
f53603083a Refactor batch invocation to use RecActionViews
Replaced all batch RecAction logic with RecActionViews equivalents. Updated controller methods to call InvokeBatchRecActionView instead of InvokeBatchRecAction. Removed InvokeBatchRecActionsCommand and its handler, and added InvokeBatchRecActionViewsCommand with similar logic for RecActionViews. This shifts batch processing from RecActions to RecActionViews across the codebase.
2025-12-12 15:00:26 +01:00
92e7d44d3b Rename InvokeRecActionCommand to InvokeRecActionViewCommand
Refactored the RecAction invocation logic by renaming and moving InvokeRecActionCommand.cs to InvokeRecActionViewCommand.cs. Updated all class, record, and extension method names accordingly. The command handling logic remains unchanged, but all references and namespaces now reflect the new naming. Removed the old InvokeRecActionCommand.cs file.
2025-12-12 14:58:33 +01:00
f8211e9e9d Refactor RecAction query to use ReadRecActionViewQuery
Replaced ReadRecActionQuery and its handler with ReadRecActionViewQuery and ReadRecActionViewQueryHandler. Updated controller methods to use the new query type. This change clarifies and consolidates RecAction view query logic, improving code organization and domain alignment.
2025-12-12 14:52:11 +01:00
1782844543 Refactor RecAction commands to new namespace
Moved CreateRecActionCommand and DeleteRecActionsCommand (and their handlers) from ReCActionViews.Commands to ReCActions.Commands. Updated all references in controller and mapping profile. Removed old files to improve code organization. No changes to command logic.
2025-12-12 14:47:48 +01:00
2aa7cabcbd Refactor RecActions to RecActionViews namespaces
Renamed command and query files, namespaces, and usings from RecActions to RecActionViews for improved clarity and organization. Updated controller and mapping profile references accordingly. No changes to business logic or handler implementations.
2025-12-12 14:43:32 +01:00
28f35101f9 Refactor auth type to enum in RecActionDto and handler
Changed EndpointAuthType from string to enum in RecActionDto. Updated InvokeRecActionCommandHandler to use enum values in switch statements for improved type safety and maintainability.
2025-12-12 14:00:09 +01:00
f8c5502905 Refactor auth type properties to use strong enum type
Changed Type in EndpointAuth and EndpointAuthType in RecActionView from string? to EndpointAuthType? for improved type safety and clarity. This enforces the use of a specific enum for authentication types instead of plain strings.
2025-12-12 13:58:39 +01:00
961b87de3d Add EndpointAuthType enum for endpoint authentication types
Introduced the EndpointAuthType enum in the ReC.Domain.Constants namespace to represent various endpoint authentication methods, including NoAuth, ApiKey, BearerToken, JwtBearer, BasicAuth, DigestAuth, OAuth1, OAuth2, AwsSignature, and NtlmAuth. Each type is assigned a unique integer value for clear identification.
2025-12-12 13:57:52 +01:00
6b036f4f91 Add EF Core data annotations for table and FK mapping
Added [Table] and [ForeignKey] attributes to entity classes to explicitly map them to database tables/views and define relationships. Updated using directives as needed. Improves entity mapping clarity and robustness against schema changes.
2025-12-12 13:55:22 +01:00
46b7ae29cd Update IRecDbContext DbSets and add new entity sets
Changed DbSet properties to get/set in IRecDbContext, renamed Actions to RecActionViews for consistency, and added DbSets for Connections, Endpoints, EndpointAuths, Profiles, and RecActions. Updated RecDbContext implementation accordingly.
2025-12-12 13:42:49 +01:00
84e403f411 Refactor API key auth handling with switch and error check
Refactored the "API Key" authentication logic to use a switch
statement on the API key location, improving code clarity.
Added a default case to throw a DataIntegrityException for
unsupported API key locations, enhancing error handling.
2025-12-12 13:21:09 +01:00
3b77345aee Refactor API key location to use enum instead of string
Changed EndpointAuthApiKeyAddTo from string? to ApiKeyLocation? enum in RecActionDto for type safety. Updated related logic in InvokeRecActionCommandHandler to use the enum, and added the necessary using directive for ReC.Domain.Constants.
2025-12-12 13:17:54 +01:00
6d04a4afd1 Refactor API key location to use enum for type safety
Updated EndpointAuth and RecActionView to use the ApiKeyLocation enum for API key location properties instead of nullable strings. Added necessary using directives for ReC.Domain.Constants to support this change, improving type safety and code clarity.
2025-12-12 13:16:32 +01:00
1f250d55b0 Add ApiKeyLocation enum for API key placement options
Introduced the ApiKeyLocation enum in the ReC.Domain.Constants namespace to specify API key locations, supporting both Header and Query options.
2025-12-12 13:10:09 +01:00
c422de445d Refactor ErrorAction to use enum for type safety
Changed RecActionDto.ErrorAction from string to ErrorAction enum, updating all related logic to use enum values instead of strings. Added necessary using directives. This improves type safety, reduces risk of typos, and enhances maintainability.
2025-12-12 13:04:23 +01:00
f9a73fbe0c Refactor RecAction.ErrorAction to use enum type
Changed ErrorAction property in RecAction from string? to ErrorAction? enum for improved type safety. Added import for ReC.Domain.Constants to support the new type.
2025-12-12 13:02:17 +01:00
282ce3a0b7 Move ErrorAction enum to domain layer namespace
Changed ErrorAction enum namespace from ReC.Application.Common.Constants to ReC.Domain.Constants to better reflect its domain relevance. Removed the file's BOM. Enum members remain unchanged.
2025-12-12 13:00:24 +01:00
26f2da1313 Add ErrorAction enum for error handling actions
Introduced the ErrorAction enum in the ReC.Application.Common.Constants namespace with Stop and Continue values to standardize error response actions across the application.
2025-12-12 12:55:57 +01:00
bc700e2cd2 Refactor HTTP client name constant usage
Replaced the old HttpClientName constant in Constants.cs with a new Http.ClientName in the ReC.Application.Common.Constants namespace. Updated all references and using directives accordingly to improve code organization and maintainability.
2025-12-12 12:54:57 +01:00
ea5389df85 Remove Message property from OutRes and its EF mapping
The Message property was deleted from the OutRes class, and its corresponding Entity Framework mapping was removed from RecDbContext. This cleans up unused fields from both the model and database context configuration.
2025-12-12 12:51:43 +01:00
87e1bb9187 Refactor: centralize HTTP client naming with Constants
Introduced a Constants class to define a unique HttpClientName for HTTP client registration and usage. Updated DependencyInjection and InvokeRecActionCommandHandler to use this centralized name, improving consistency and reducing risk of name collisions.
2025-12-12 12:42:39 +01:00
68cc919bad Handle unsupported auth types with NotImplementedException
Throw NotImplementedException for unsupported authentication types
in InvokeRecActionCommandHandler, including details like ProfileId
and Id in the exception message for easier debugging. This prevents
silent failures when encountering unknown authentication methods.
2025-12-12 12:02:51 +01:00
374365d250 Refactor HttpClient usage and NTLM auth handling
Centralize HttpClient configuration using IHttpClientFactory with a named "Default" client. Move NTLM authentication logic from HttpClientHandler to request-level options, improving testability and aligning with .NET best practices.
2025-12-12 12:01:22 +01:00
8c79b3a156 Add support for multiple HTTP auth methods in REST actions
Expanded InvokeRecActionCommandHandler to support API Key, Bearer/JWT/OAuth2, Basic, and NTLM authentication schemes. Added necessary imports and logic for header/query manipulation and credential handling. Left placeholders for Digest, OAuth 1.0, and AWS Signature. Improves flexibility and robustness of outgoing HTTP requests.
2025-12-12 11:20:02 +01:00
0583d07f26 Add switch for endpoint auth types in action handler
Introduced a switch statement to handle various endpoint authentication types in InvokeRecActionCommandHandler. Cases for common auth methods have been added as placeholders, preparing the codebase for future implementation of specific authentication logic.
2025-12-12 10:50:49 +01:00
fd7744e94e Include EndpointAuth in RecActionView query results
Added .Include(act => act.EndpointAuth) to eagerly load the EndpointAuth navigation property when retrieving RecActionView entities. Also made a minor formatting adjustment to the check for empty action results.
2025-12-12 10:46:20 +01:00
a5aac1d0ec Add EndpointAuth navigation to RecActionView
Added EndpointAuth navigation property to RecActionView with [ForeignKey("EndpointAuthId")], enabling direct access to related authentication data via Entity Framework.
2025-12-12 10:34:26 +01:00
165152b7cf Merge branch 'master' of http://git.dd:3000/AppStd/ReC 2025-12-12 09:18:27 +01:00
f1f9e8d791 Bump version to 1.0.3-beta in ReC.API.csproj
Updated <Version>, <AssemblyVersion>, <FileVersion>, and <InformationalVersion> fields in the project file from 1.0.2-beta to 1.0.3-beta. No other changes were made.
2025-12-12 01:54:43 +01:00
35171add0c Add ErrorAction to RecActionDto and batch error handling
Introduce ErrorAction property to RecActionDto for per-action error handling. Update InvokeRecActionsCommandHandler to check invocation results and use ErrorAction to determine whether to continue or stop on failure, enabling configurable batch processing behavior.

Add ErrorAction to RecActionDto and batch error handling

Introduce ErrorAction property to RecActionDto for per-action error handling. Update InvokeRecActionsCommandHandler to check invocation results and use ErrorAction to determine whether to continue or stop processing on failure. This enables configurable error handling in batch action execution.
2025-12-12 01:52:35 +01:00
030dcf8b58 Add ErrorAction property to RecAction and DB mapping
Added the ErrorAction property to the RecAction class for specifying error handling actions. Updated RecDbContext to map this property to the ERROR_ACTION database column.
2025-12-12 01:47:43 +01:00
71411d4027 Update InvokeRecActionCommand to return success status
Changed InvokeRecActionCommand and its handler to return a boolean indicating success or failure, allowing consumers to check if the action was successful. This improves clarity and error handling in command execution.
2025-12-12 01:43:01 +01:00
da1b05347e Add Status and Message to CreateOutResCommand
CreateOutResCommand now includes optional Status and Message properties to capture additional result details. The HTTP response status code is recorded in Status when creating an output result in InvokeRecActionCommandHandler.
2025-12-12 01:32:09 +01:00
d932fb522c Add Status and Message columns to entity mapping
Added Status and Message properties to the entity configuration in RecDbContext.cs, mapping them to the STATUS and MESSAGE database columns. This allows the entity to store and retrieve these additional fields.
2025-12-12 01:28:29 +01:00
134a808633 Add Status, Message, and Header to OutRes class
Added three new nullable properties—Status (short), Message (string), and Header (string)—to the OutRes class to support additional metadata and state information.
2025-12-12 01:25:29 +01:00
Developer 02
00efb14f2c Merge branch 'feat/client' 2025-12-11 10:49:21 +01:00
fe40cd001f Update RecActionController routes and XML docs
Changed Invoke action to use profileId as a route parameter. Updated Get method XML docs to reflect use of ReadRecActionQuery from query string instead of profileId.
2025-12-10 16:41:51 +01:00
f96b73bf38 Update RecAction route and refactor EnsureEntity logic
Changed the RecActionController Invoke route to remove the {cmd}
parameter, now accepting POST requests at "invoke" only.
Refactored DbModelOptions.EnsureEntity<T> by removing the
entities dictionary conversion logic for virtual and non-virtual
entities.
2025-12-10 16:02:41 +01:00
c8f3b29329 Refactor EnsureEntity<T> for clarity and consistency
Renamed local variable from 'cluster' to 'entities' in EnsureEntity<T> and unified logic for selecting entity options. Improved readability by consistently using entities.TryGetValue and clearer variable naming.
2025-12-10 15:00:44 +01:00
07fca00344 Replace InvalidOperationException with custom config exception
Refactored DbModelOptions and EntityBaseOptions to throw DbModelConfigurationException instead of InvalidOperationException for configuration errors. Added necessary using directives for the new exception type to improve error clarity and specificity.
2025-12-10 14:54:20 +01:00
e4aec494c8 Add DbModelConfigurationException class to exceptions
Introduced DbModelConfigurationException in the ReC.Infrastructure.Exceptions namespace. This custom exception inherits from Exception and includes both a message constructor and a parameterless constructor for flexible error handling related to DB model configuration issues.
2025-12-10 14:53:48 +01:00
009bb623b5 Add EnsureEntity<T> method to DbModelOptions
Introduce EnsureEntity<T>(bool isVirtual) to validate and ensure entity options exist for a given type in either Entities or VirtualEntities. Throws an exception if options are missing.
2025-12-10 14:24:45 +01:00
f8e7f8c974 Switch DbModelOptions to use dictionaries for entities
Changed Entities and VirtualEntities from IEnumerable to Dictionary<string, T> in DbModelOptions, enabling direct access by string keys and improving lookup efficiency.
2025-12-10 13:56:41 +01:00
959b56c4bb Add ColumnNames property to EntityBaseOptions
Added a new ColumnNames property that exposes the column names from the ColumnMappings dictionary as an IEnumerable<string> in the EntityBaseOptions record. This provides convenient access to mapped column names alongside existing property name access.
2025-12-10 13:52:49 +01:00
5a56125444 Add PropertyNames property to EntityBaseOptions
Introduce PropertyNames as a read-only property exposing the keys of ColumnMappings. Refactor EnsureProperties to use PropertyNames for improved clarity and maintainability.
2025-12-10 13:52:16 +01:00
48039b8fd5 Rename ColumnName to ColumnMappings in EntityBaseOptions
Renamed the ColumnName property to ColumnMappings in the EntityBaseOptions record for improved clarity and consistency. Updated all internal references accordingly; no changes to logic or initialization.
2025-12-10 13:46:23 +01:00
31d1d9d171 Add EnsureProperties<T> for attribute-based config validation
Added EnsureProperties<T>() to EntityBaseOptions, enabling automatic validation of required properties marked with MustConfiguredAttribute via reflection. This reduces manual configuration and improves maintainability.
2025-12-10 13:43:58 +01:00
1419455b36 Add MustConfiguredAttribute and update project file
Added MustConfiguredAttribute for property usage in ReC.Domain.Attributes. Removed explicit "Attributes\" folder reference from ReC.Domain.csproj since the folder now contains code.
2025-12-10 13:32:42 +01:00
a6111cdc66 Rename Columns to ColumnName; add Attributes folder
Renamed the Columns property to ColumnName in EntityBaseOptions.cs,
updating all references accordingly. Added an "Attributes" folder
entry to ReC.Domain.csproj for future organization.
2025-12-10 13:30:47 +01:00
3caa6b9bd3 Add params overload for EnsureProperties in EntityBaseOptions
Added a public EnsureProperties method accepting a params string[] in EntityBaseOptions. This provides a more convenient way to ensure multiple properties are configured, internally delegating to the existing IEnumerable-based method.
2025-12-10 13:28:38 +01:00
fb08a45c57 Add EnsureProperties method to EntityBaseOptions
Introduce EnsureProperties to validate required property names
against the Columns dictionary, throwing an exception if any
are missing. This helps enforce configuration completeness.
2025-12-10 13:26:42 +01:00
0588ba33d8 Add DbModelOptions record for entity configuration
Introduced DbModelOptions in ReC.Infrastructure.Options to encapsulate collections of EntityOptions and VirtualEntityOptions, providing a structured way to configure entities and virtual entities. Both properties are initialized to empty collections by default.
2025-12-10 12:42:23 +01:00
535fdbb7b4 Refactor entity options to use shared base class
Introduce EntityBaseOptions with Columns property.
Update EntityOptions and VirtualEntityOptions to inherit from EntityBaseOptions for improved code reuse and consistency.
2025-12-10 12:32:34 +01:00
4206a962ff Refactor option records and improve namespace structure
Renamed Table to TableOptions and moved it, along with EntityOptions and VirtualEntityOptions, to the ReC.Infrastructure.Options.Shared namespace. Split records into separate files for better modularity and updated EntityOptions to reference TableOptions. This enhances code organization and naming consistency.
2025-12-10 12:24:39 +01:00
ae059e4416 Add VirtualEntityOptions and EntityOptions records
Introduced two new record types: VirtualEntityOptions (currently empty) and EntityOptions, which encapsulates a Table instance. These additions lay groundwork for future entity configuration options.
2025-12-10 12:16:50 +01:00
d771dbbc9e Add Table record in ReC.Infrastructure.Options namespace
Introduced a new Table record with Name and optional Schema properties under the ReC.Infrastructure.Options namespace. This addition provides a structured way to represent database table metadata.
2025-12-10 12:10:06 +01:00
73ffaaab08 Remove RecActionView entity and its database mapping
Deleted the RecActionView class, including all properties and data annotations, removing its mapping to the VWREC_ACTION view and related relationships from the codebase.
2025-12-10 12:06:20 +01:00
b79fcf936b Refactor RecAction mapping to Fluent API in DbContext
Moved all RecAction table and column mappings from data annotations in the entity class to Fluent API configuration in RecDbContext. Also consolidated the OutRes relationship setup into the new configuration block, removing redundant mapping code. RecAction is now a plain class without EF attributes.
2025-12-10 12:04:43 +01:00
674c14dd7c Refactor Profile mapping to use Fluent API in DbContext
Moved table and column mapping for Profile from data annotations
in the entity class to Fluent API configuration in RecDbContext.
Removes dependency on DataAnnotations in Profile.cs and
centralizes entity configuration in the DbContext.
2025-12-10 12:02:20 +01:00
b326e7e1b3 Refactor EndpointAuth EF mapping to Fluent API
Moved EF Core configuration for EndpointAuth from data annotations in the entity class to Fluent API in RecDbContext. This centralizes table, key, and property mappings, improving maintainability and consistency.
2025-12-10 12:00:21 +01:00
3f8ba7d76c Move Endpoint entity config to Fluent API in DbContext
Removed data annotations from Endpoint class and defined its table, key, and column mappings using Fluent API in RecDbContext. This centralizes entity configuration and improves code maintainability.
2025-12-10 11:58:42 +01:00
13346b610a Refactor Connection entity to use Fluent API mapping
Removed data annotations from Connection.cs and moved all table and column mapping to RecDbContext's OnModelCreating using the Fluent API. This centralizes entity configuration and removes dependencies on DataAnnotations in the entity class.
2025-12-10 11:56:14 +01:00
8bc9b85049 Move RawHeader column mapping to Fluent API config
Removed data annotation from HeaderQueryResult and mapped RawHeader to REQUEST_HEADER using Fluent API in RecDbContext. Centralizes entity configuration in the DbContext.
2025-12-10 11:50:55 +01:00
141e77f315 Remove EF Core data annotations from OutRes entity
Refactored the OutRes class to eliminate all Entity Framework Core data annotations, including table, key, column, and foreign key attributes. The entity now contains only property definitions, decoupling it from direct database schema mapping and allowing for alternative configuration or usage outside of EF Core.
2025-12-10 11:48:59 +01:00
f9a4d93495 Add OutRes entity mapping to RecDbContext
Configured OutRes entity to map to TBREC_OUT_RESULT table,
including primary key and property-to-column mappings.
2025-12-10 11:47:58 +01:00
79f771b3ea Remove EF Core data annotations from EndpointParam class
Refactored EndpointParam by removing all Entity Framework Core
data annotations and related using directives. The class now
contains only plain C# properties, with database mapping
responsibilities likely moved to Fluent API or another ORM
configuration approach.
2025-12-10 11:35:45 +01:00
8738c15804 Configure EndpointParam entity in RecDbContext
Added mapping for EndpointParam to TBREC_CFG_ENDPOINT_PARAMS table, including key and property-to-column configurations in OnModelCreating.
2025-12-10 11:34:28 +01:00
70c07c9595 Enhance RecActionView entity mapping in DbContext
Added explicit table and column mappings for RecActionView in DbContext. The entity is now mapped to the "VWREC_ACTION" table in the "dbo" schema, with each property aligned to its corresponding database column for improved accuracy.
2025-12-10 11:29:27 +01:00
96fe9c99da Move BodyQueryResult column mapping to Fluent API
Replaced data annotation with Fluent API configuration for BodyQueryResult.RawBody column mapping in RecDbContext, centralizing entity configuration and removing the [Column("REQUEST_BODY")] attribute from the entity class.
2025-12-10 11:05:09 +01:00
1e62a70866 Enable cascade delete for RecAction-OutRes relationship
Added DeleteBehavior.Cascade to the RecAction-OutRes one-to-one relationship in RecDbContext. Now, deleting a RecAction will also delete its related OutRes entity automatically. Previously, the delete behavior was not specified.
2025-12-10 10:38:55 +01:00
f4aa0b5965 Redirect root URL to Swagger UI; add required usings
Added several using directives to Program.cs for new dependencies. Updated HTTP pipeline to redirect requests from "/" to "/swagger" when Swagger is enabled, improving developer experience by automatically showing the API documentation.
2025-12-10 10:12:13 +01:00
1700fe978d Bump version to 1.0.2-beta in ReC.API.csproj
Updated project, assembly, and file versions to 1.0.2-beta/1.0.2.0 for new beta release. No other changes made.
2025-12-09 13:58:21 +01:00
37c88812e1 Simplify InvokeRecActionsCommandHandler dependencies and logic
Refactored InvokeRecActionsCommandHandler to only require ISender,
removing IServiceScopeFactory, IHttpClientFactory, and ILogger.
Replaced concurrent invocation and error handling with a simple
sequential loop, streamlining batch recommendation action execution.
2025-12-09 13:56:35 +01:00
34efb662ec Update version and assembly metadata to 1.0.1-beta
Updated `<Version>`, `<AssemblyVersion>`, and `<FileVersion>`
to reflect the minor version update from 1.0.0-beta to
1.0.1-beta. `<InformationalVersion>` remains unchanged.
2025-12-08 13:08:53 +01:00
dcbff90ed8 Remove 400 Bad Request response from Delete method
The `[ProducesResponseType(StatusCodes.Status400BadRequest)]`
attribute was removed from the `Delete` method in the
`OutResController` class. This change simplifies the API
response documentation, leaving only the `204 No Content`
response explicitly defined. The method is no longer expected
to return a `400 Bad Request` status code, reflecting a change
in behavior or a decision to streamline the API's response
documentation.
2025-12-08 11:52:58 +01:00
404a1c4793 Add Delete method for fake profile in OutResController
Added a new `Delete` method to the `OutResController` class to
delete all output results for a fake/test profile. The method
is accessible via the `DELETE /fake` route and supports
cancellation via a `CancellationToken`.

Included XML documentation for the method, describing its
purpose, parameters, and return type. Added `[ProducesResponseType]`
attributes to specify possible HTTP response codes: `204 No Content`
for success and `400 Bad Request` for invalid requests.

The method uses `IMediator` to send a `DeleteOutResCommand`
with a `ProfileId` obtained from the `IConfiguration` instance.
2025-12-08 11:50:30 +01:00
1cdd28738a Add Delete endpoint to OutResController
Added a new HTTP DELETE endpoint to the OutResController to allow deletion of output results based on criteria provided in the DeleteOutResCommand. The endpoint supports cancellation via a CancellationToken and returns a 204 No Content response on success. Updated using directives to include necessary namespaces for commands and queries. Added response type annotations for better API documentation.
2025-12-08 11:47:05 +01:00
45c7259ce8 Add XML documentation for DeleteOutResCommand and handler
Enhanced the `DeleteOutResCommand` and `DeleteOutResCommandHandler`
with detailed XML documentation comments. These comments describe
the purpose, usage, and deletion logic, including criteria based
on `ActionId` or `ProfileId`. Improved clarity for maintainers
and users of the codebase.
2025-12-08 11:45:17 +01:00
6d9985051e Add DeleteOutResCommandHandler for repository deletion
Updated using directives to include necessary dependencies for
repository and domain entities. Introduced the
DeleteOutResCommandHandler class to handle DeleteOutResCommand
requests. The handler uses IRepository<OutRes> to delete records
based on ActionId or ProfileId. Implemented asynchronous
deletion logic with support for cancellation tokens.
2025-12-08 11:42:08 +01:00
b9f5a3f10c Add DeleteOutResCommandValidator for input validation
Introduce DeleteOutResCommandValidator using FluentValidation
to enforce business rules for the DeleteOutResCommand. Added
a validation rule to ensure at least one of ActionId or
ProfileId is provided, with a descriptive error message if
neither is present. Included necessary using directives for
FluentValidation and the relevant namespace.
2025-12-08 11:28:43 +01:00
243cc97aa2 Add DeleteOutResCommand for CQRS pattern
Introduced the `DeleteOutResCommand` record in the `ReC.Application.OutResults.Commands` namespace. This command implements the `IRequest` interface from `MediatR` and includes two nullable properties: `ActionId` and `ProfileId`. Added the `MediatR` library to support the CQRS pattern.
2025-12-08 11:17:11 +01:00
bb43bfa064 Refactor property in RecActionView class
Replaced the `ProfileSequence` property with `Sequence` in the
`RecActionView` class. Updated the database column mapping
from `PROFILE_SEQUENCE` to `SEQUENCE`. This change aligns
with updates to the database schema and improves naming
consistency.
2025-12-08 11:04:52 +01:00
b4966585ae Enhance logging, Swagger, and XML documentation support 2025-12-08 10:44:15 +01:00
ae548d530f Enable Swagger via configuration setting
Added a condition in `Program.cs` to enable Swagger when the `UseSwagger` configuration value is `true`, in addition to the development environment.

Introduced a new `UseSwagger` setting in `appsettings.json` with a default value of `true`, allowing Swagger to be conditionally enabled in non-development environments. This improves flexibility for Swagger usage across different environments.
2025-12-08 10:07:57 +01:00
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
142 changed files with 4250 additions and 961 deletions

33
ReC.sln
View File

@@ -15,6 +15,21 @@ 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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "infrastructure", "infrastructure", "{3F88DACC-CEC0-4D9A-8BAA-37F67B02DC04}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presentation", "presentation", "{3D6EF9B9-D00D-432A-8477-067902B5CE8E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{2CEF945E-94D6-4273-9BE1-20B628CD0A57}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8353C9B1-CC4A-4097-A936-C06D4C618415}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReC.Tests", "tests\ReC.Tests\ReC.Tests.csproj", "{457ED5AC-F4A0-41C3-9758-4A3C272EDC11}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -41,16 +56,24 @@ Global
{DA3A6BDD-8045-478F-860B-D1F0EB97F02B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA3A6BDD-8045-478F-860B-D1F0EB97F02B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA3A6BDD-8045-478F-860B-D1F0EB97F02B}.Release|Any CPU.Build.0 = Release|Any CPU
{457ED5AC-F4A0-41C3-9758-4A3C272EDC11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{457ED5AC-F4A0-41C3-9758-4A3C272EDC11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{457ED5AC-F4A0-41C3-9758-4A3C272EDC11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{457ED5AC-F4A0-41C3-9758-4A3C272EDC11}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{420218AD-3C27-4003-9A84-36C92352F175} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{2917BEA4-6C70-40CD-BD46-57D4ADB40296} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{587A4D14-EFDA-4BE3-8912-D3AF84743079} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{109645F5-441D-476B-B7D2-FBEAA8EBAE14} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{DA3A6BDD-8045-478F-860B-D1F0EB97F02B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{420218AD-3C27-4003-9A84-36C92352F175} = {3D6EF9B9-D00D-432A-8477-067902B5CE8E}
{2917BEA4-6C70-40CD-BD46-57D4ADB40296} = {2CEF945E-94D6-4273-9BE1-20B628CD0A57}
{587A4D14-EFDA-4BE3-8912-D3AF84743079} = {3F88DACC-CEC0-4D9A-8BAA-37F67B02DC04}
{109645F5-441D-476B-B7D2-FBEAA8EBAE14} = {2CEF945E-94D6-4273-9BE1-20B628CD0A57}
{DA3A6BDD-8045-478F-860B-D1F0EB97F02B} = {3D6EF9B9-D00D-432A-8477-067902B5CE8E}
{3F88DACC-CEC0-4D9A-8BAA-37F67B02DC04} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{3D6EF9B9-D00D-432A-8477-067902B5CE8E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{2CEF945E-94D6-4273-9BE1-20B628CD0A57} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{457ED5AC-F4A0-41C3-9758-4A3C272EDC11} = {8353C9B1-CC4A-4097-A936-C06D4C618415}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F7B09104-4072-4635-9492-9C7C68D96ABD}

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,33 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class CommonController(IMediator mediator) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateObject([FromBody] InsertObjectProcedure procedure, CancellationToken cancel)
{
var id = await mediator.Send(procedure, cancel);
return StatusCode(StatusCodes.Status201Created, id);
}
[HttpPut]
public async Task<IActionResult> UpdateObject([FromBody] UpdateObjectProcedure procedure, CancellationToken cancel)
{
var result = await mediator.Send(procedure, cancel);
return Ok(result);
}
[HttpDelete]
public async Task<IActionResult> DeleteObject([FromBody] DeleteObjectProcedure procedure, CancellationToken cancel)
{
var result = await mediator.Send(procedure, cancel);
return Ok(result);
}
}

View File

@@ -0,0 +1,56 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.EndpointAuth.Commands;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class EndpointAuthController(IMediator mediator, IConfiguration config) : ControllerBase
{
/// <summary>
/// Inserts an endpoint authentication record via the ENDPOINT_AUTH insert procedure.
/// </summary>
/// <param name="procedure">InsertEndpointAuthProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The created ENDPOINT_AUTH identifier.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] InsertEndpointAuthProcedure procedure, CancellationToken cancel)
{
var id = await mediator.ExecuteInsertProcedure(procedure, config["AddedWho"], cancel);
return StatusCode(StatusCodes.Status201Created, id);
}
/// <summary>
/// Updates an endpoint authentication record via the ENDPOINT_AUTH update procedure.
/// </summary>
/// <param name="id">ENDPOINT_AUTH identifier to update.</param>
/// <param name="procedure">UpdateEndpointAuthProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Put([FromRoute] long id, [FromBody] UpdateEndpointAuthProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
/// <summary>
/// Deletes endpoint authentication records via the ENDPOINT_AUTH delete procedure for the specified id range.
/// </summary>
/// <param name="procedure">DeleteEndpointAuthProcedure payload (Start, End, Force).</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromBody] DeleteEndpointAuthProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
}

View File

@@ -0,0 +1,56 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.EndpointParams.Commands;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class EndpointParamsController(IMediator mediator, IConfiguration config) : ControllerBase
{
/// <summary>
/// Inserts endpoint parameter records via the ENDPOINT_PARAMS insert procedure.
/// </summary>
/// <param name="procedure">InsertEndpointParamsProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The created ENDPOINT_PARAMS identifier.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] InsertEndpointParamsProcedure procedure, CancellationToken cancel)
{
var id = await mediator.ExecuteInsertProcedure(procedure, config["AddedWho"], cancel);
return StatusCode(StatusCodes.Status201Created, id);
}
/// <summary>
/// Updates endpoint parameter records via the ENDPOINT_PARAMS update procedure.
/// </summary>
/// <param name="id">ENDPOINT_PARAMS identifier to update.</param>
/// <param name="procedure">UpdateEndpointParamsProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Put([FromRoute] long id, [FromBody] UpdateEndpointParamsProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
/// <summary>
/// Deletes endpoint parameter records via the ENDPOINT_PARAMS delete procedure for the specified id range.
/// </summary>
/// <param name="procedure">DeleteEndpointParamsProcedure payload (Start, End, Force).</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromBody] DeleteEndpointParamsProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
}

View File

@@ -0,0 +1,56 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Endpoints.Commands;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class EndpointsController(IMediator mediator, IConfiguration config) : ControllerBase
{
/// <summary>
/// Inserts an endpoint via the ENDPOINT insert procedure.
/// </summary>
/// <param name="procedure">InsertEndpointProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The created ENDPOINT identifier.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] InsertEndpointProcedure procedure, CancellationToken cancel)
{
var id = await mediator.ExecuteInsertProcedure(procedure, config["AddedWho"], cancel);
return StatusCode(StatusCodes.Status201Created, id);
}
/// <summary>
/// Updates an endpoint via the ENDPOINT update procedure.
/// </summary>
/// <param name="id">ENDPOINT identifier to update.</param>
/// <param name="procedure">UpdateEndpointProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Put([FromRoute] long id, [FromBody] UpdateEndpointProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
/// <summary>
/// Deletes endpoints via the ENDPOINT delete procedure for the specified id range.
/// </summary>
/// <param name="procedure">DeleteEndpointProcedure payload (Start, End, Force).</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromBody] DeleteEndpointProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
}

View File

@@ -0,0 +1,63 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Profile.Commands;
using ReC.Application.Profile.Queries;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ProfileController(IMediator mediator) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> Get([FromQuery] ReadProfileViewQuery query, CancellationToken cancel)
{
return Ok(await mediator.Send(query, cancel));
}
/// <summary>
/// Inserts a profile via the PROFILE insert procedure.
/// </summary>
/// <param name="procedure">InsertProfileProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The created profile identifier.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] InsertProfileProcedure procedure, CancellationToken cancel)
{
var id = await mediator.ExecuteInsertProcedure(procedure, cancel: cancel);
return CreatedAtAction(nameof(Get), new { id }, id);
}
/// <summary>
/// Updates a profile via the PROFILE update procedure.
/// </summary>
/// <param name="id">Profile identifier to update.</param>
/// <param name="procedure">UpdateProfileProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Put([FromRoute] long id, [FromBody] UpdateProfileProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
/// <summary>
/// Deletes profile records via the PROFILE delete procedure for the specified id range.
/// </summary>
/// <param name="procedure">DeleteProfileProcedure payload (Start, End, Force).</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromBody] DeleteProfileProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
}

View File

@@ -1,100 +1,83 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Models;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.RecActions.Commands;
using ReC.Application.RecActions.Queries;
using System.Text.Json;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class RecActionController(IMediator mediator) : ControllerBase
public class RecActionController(IMediator mediator, IConfiguration config) : ControllerBase
{
private const long FakeProfileId = 2;
/// <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/{profileId}")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> Invoke([FromRoute] int profileId, CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(profileId, cancel);
return Accepted();
}
[HttpPost("invoke/fake")]
public async Task<IActionResult> Invoke(CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(FakeProfileId, cancel);
await mediator.InvokeBatchRecActionView(profileId, cancel);
return Accepted();
}
#region CRUD
/// <summary>
/// Gets all RecActions for a given profile.
/// </summary>
/// <param name="query"></param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>A list of RecActions for the specified profile.</returns>
[HttpGet]
public async Task<IActionResult> Get([FromQuery] long profileId, CancellationToken cancel) => Ok(await mediator.Send(new ReadRecActionQuery()
{
ProfileId = profileId
}, cancel));
[HttpGet("fake")]
public async Task<IActionResult> Get(CancellationToken cancel) => Ok(await mediator.Send(new ReadRecActionQuery()
{
ProfileId = FakeProfileId
}, cancel));
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromQuery] ReadRecActionViewQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, 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]
public async Task<IActionResult> CreateAction([FromBody] CreateRecActionCommand command, CancellationToken cancel)
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Create([FromBody] InsertActionProcedure command, CancellationToken cancel)
{
await mediator.Send(command, cancel);
await mediator.ExecuteInsertProcedure(command, config["AddedWho"], cancel);
return CreatedAtAction(nameof(CreateAction), null);
return StatusCode(StatusCodes.Status201Created);
}
[HttpPost("fake")]
public async Task<IActionResult> CreateFakeAction(
CancellationToken cancel,
[FromBody] FakeRequest? request = null,
[FromQuery] string endpointUri = "https://jsonplaceholder.typicode.com/posts",
[FromQuery] string? endpointPath = "1",
[FromQuery] string type = "GET")
/// <summary>
/// Updates a RecAction via the ACTION update procedure.
/// </summary>
/// <param name="id">RecAction identifier to update.</param>
/// <param name="procedure">UpdateActionProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Update([FromRoute] long id, [FromBody] UpdateActionProcedure procedure, CancellationToken cancel)
{
if (endpointPath is not null)
endpointUri = new Uri(new Uri(endpointUri.TrimEnd('/') + "/"), endpointPath.TrimStart('/')).ToString();
var bodyJson = request?.Body is not null ? JsonSerializer.Serialize(request.Body, options: new() { WriteIndented = false }) : null;
var headerJson = request?.Header is not null ? JsonSerializer.Serialize(request.Header, options: new() { WriteIndented = false }) : null;
await mediator.Send(new CreateRecActionCommand()
{
ProfileId = FakeProfileId,
EndpointUri = endpointUri,
Type = type,
BodyQuery = $@"SELECT '{bodyJson ?? "NULL"}' AS REQUEST_BODY;",
HeaderQuery = headerJson is not null ? $@"SELECT '{headerJson}' AS REQUEST_HEADER;" : null,
Active = true,
EndpointAuthId = 4
}, cancel);
return CreatedAtAction(nameof(CreateFakeAction), null);
}
[HttpDelete]
public async Task<IActionResult> Delete([FromQuery] int profileId, CancellationToken cancel)
{
await mediator.Send(new DeleteRecActionsCommand()
{
ProfileId = FakeProfileId
}, cancel);
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
[HttpDelete("fake")]
public async Task<IActionResult> Delete(CancellationToken cancel)
/// <summary>
/// Deletes RecActions via the ACTION delete procedure for the specified id range.
/// </summary>
/// <param name="procedure">DeleteActionProcedure payload (Start, End, Force).</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([FromBody] DeleteActionProcedure procedure, CancellationToken cancel)
{
await mediator.Send(new DeleteRecActionsCommand()
{
ProfileId = FakeProfileId
}, cancel);
await mediator.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
#endregion CRUD

View File

@@ -0,0 +1,69 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.API.Models;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Results.Commands;
using ReC.Application.Results.Queries;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ResultController(IMediator mediator) : 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] ReadResultViewQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, cancel));
/// <summary>
/// Inserts a RESULT record via the insert procedure.
/// </summary>
/// <param name="procedure">InsertResultProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The created RESULT identifier.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] InsertResultProcedure procedure, CancellationToken cancel)
{
var id = await mediator.ExecuteInsertProcedure(procedure, cancel: cancel);
return CreatedAtAction(nameof(Get), new { actionId = procedure.ActionId }, new { id, procedure.ActionId });
}
/// <summary>
/// Updates a RESULT record via the update procedure.
/// </summary>
/// <param name="id">RESULT identifier to update.</param>
/// <param name="procedure">UpdateResultProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Put([FromRoute] long id, [FromBody] UpdateResultProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
/// <summary>
/// Deletes RESULT records via the delete procedure for the specified id range.
/// </summary>
/// <param name="procedure">DeleteResultProcedure payload (Start, End, Force).</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>No content on success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromBody] DeleteResultProcedure procedure, CancellationToken cancel)
{
await mediator.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
}

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,18 @@
using Microsoft.AspNetCore.Mvc.Filters;
using ReC.Application.Common.Interfaces;
namespace ReC.API.Middleware;
public class AuthScopedFilter(IConfiguration config) : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.ActionArguments.TryGetValue("command", out var command) && command is IAuthScoped authScopedCommand)
{
var addedWho = config["AddedWho"] ?? throw new InvalidOperationException("The required 'AddedWho' configuration is missing. Please contact a system administrator.");
authScopedCommand.Scope.AddedWho = addedWho;
}
await next();
}
}

View File

@@ -63,10 +63,17 @@ public class ExceptionHandlingMiddleware
switch (exception)
{
case BadRequestException badRequestEx:
if (badRequestEx.InnerException is not null)
{
logger.LogError(
badRequestEx.InnerException,
"BadRequestException inner exception captured.");
}
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
details = new()
{
Title = "Bad Request",
Title = "Bad Procedure",
Detail = badRequestEx.Message
};
break;
@@ -106,6 +113,57 @@ public class ExceptionHandlingMiddleware
};
break;
case InsertObjectFailedException insertFailedEx:
logger.LogError(
insertFailedEx,
"Insert operation failed during request processing. {procedure}",
JsonSerializer.Serialize(
insertFailedEx.Procedure,
options: new() { WriteIndented = true }
));
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
details = new()
{
Title = "Insert Operation Failed",
Detail = insertFailedEx.Message
};
break;
case UpdateObjectFailedException updateFailedEx:
logger.LogError(
updateFailedEx,
"Update operation failed during request processing. {procedure}",
JsonSerializer.Serialize(
updateFailedEx.Procedure,
options: new() { WriteIndented = true }
));
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
details = new()
{
Title = "Update Operation Failed",
Detail = updateFailedEx.Message
};
break;
case DeleteObjectFailedException deleteFailedEx:
logger.LogError(
deleteFailedEx,
"Delete operation failed during request processing. {procedure}",
JsonSerializer.Serialize(
deleteFailedEx.Procedure,
options: new() { WriteIndented = true }
));
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
details = new()
{
Title = "Delete Operation Failed",
Detail = deleteFailedEx.Message
};
break;
default:
logger.LogError(exception, "Unhandled exception occurred.");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
@@ -120,4 +178,4 @@ public class ExceptionHandlingMiddleware
if (details is not null)
await context.Response.WriteAsJsonAsync(details);
}
}
}

View File

@@ -0,0 +1,17 @@
namespace ReC.API.Models;
public enum ResultType
{
/// <summary>
/// Return the full result object.
/// </summary>
Full,
/// <summary>
/// Return only the header part of the result.
/// </summary>
OnlyHeader,
/// <summary>
/// Return only the body part of the result.
/// </summary>
OnlyBody
}

View File

@@ -1,56 +1,99 @@
using Microsoft.AspNetCore.Rewrite;
using Microsoft.EntityFrameworkCore;
using NLog;
using NLog.Web;
using ReC.API.Middleware;
using ReC.Application;
using ReC.Infrastructure;
using System.Reflection;
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((provider, opt) =>
builder.Logging.SetMinimumLevel(LogLevel.Trace);
if (!builder.Environment.IsDevelopment())
{
var cnnStr = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string is not found.");
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
var logger = provider.GetRequiredService<ILogger<RecDbContext>>();
opt.UseSqlServer(cnnStr)
.LogTo(log => logger.LogInformation("{log}", log), LogLevel.Trace)
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
var config = builder.Configuration;
Directory
.GetFiles(builder.Environment.ContentRootPath, "appsettings.*.json", SearchOption.TopDirectoryOnly)
.Where(file => Path.GetFileName(file) != $"appsettings.Development.json")
.Where(file => Path.GetFileName(file) != $"appsettings.migration.json")
.ToList()
.ForEach(file => config.AddJsonFile(file, true, true));
// Add services to the container.
builder.Services.AddRecServices(options =>
{
options.LuckyPennySoftwareLicenseKey = builder.Configuration["LuckyPennySoftwareLicenseKey"];
options.ConfigureRecActions(config.GetSection("RecAction"));
options.ConfigureSqlException(config.GetSection("SqlException"));
});
});
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();
});
});
builder.Services.AddControllers(options =>
{
options.Filters.Add<AuthScopedFilter>();
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
#pragma warning disable CS0618
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
#pragma warning restore CS0618
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || config.GetValue<bool>("UseSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI();
var rewriteOptions = new RewriteOptions().AddRedirect("^$", "swagger");
app.UseRewriter(rewriteOptions);
}
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,29 @@
<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>2.0.0-beta</Version>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<FileVersion>2.0.0.0</FileVersion>
<InformationalVersion>2.0.0-beta</InformationalVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</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

@@ -0,0 +1,59 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"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

@@ -1,10 +1,5 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"UseSwagger": true,
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL19\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
@@ -13,5 +8,11 @@
"RecAction": {
"MaxConcurrentInvocations": 5
},
"AddedWho": "ReC.API"
// Bad request SqlException numbers numbers can be updated at runtime; no restart required.
"SqlException": {
// https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlexception.number
"BadRequestSqlExceptionNumbers": [ 515, 547, 2601, 2627, 50000 ]
},
"AddedWho": "ReC.API",
"FakeProfileId": 2
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore;
namespace ReC.Application.Common.Behaviors;
public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionDto
where TRequest : RecActionViewDto
where TResponse : notnull
{
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
namespace ReC.Application.Common.Constants;
public static class Http
{
public static readonly string ClientName = "HttpClient-" + Guid.NewGuid().ToString();
}

View File

@@ -0,0 +1,32 @@
namespace ReC.Application.Common.Dto;
public record ConnectionDto
{
public short? Id { get; set; }
public string? Bezeichnung { get; set; }
public string? SqlProvider { get; set; }
public string? Server { get; set; }
public string? Datenbank { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? Bemerkung { get; set; }
public bool? Aktiv { get; set; }
public string? ErstelltWer { get; set; }
public DateTime? ErstelltWann { get; set; }
public string? GeandertWer { get; set; }
public DateTime? GeaendertWann { get; set; }
public bool? SysConnection { get; set; }
}

View File

@@ -1,5 +1,4 @@
using AutoMapper;
using ReC.Domain.Entities;
using ReC.Domain.Views;
namespace ReC.Application.Common.Dto;
@@ -7,6 +6,8 @@ public class DtoMappingProfile : AutoMapper.Profile
{
public DtoMappingProfile()
{
CreateMap<RecActionView, RecActionDto>();
CreateMap<RecActionView, RecActionViewDto>();
CreateMap<ResultView, ResultViewDto>();
CreateMap<ProfileView, ProfileViewDto>();
}
}

View File

@@ -0,0 +1,39 @@
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record EndpointAuthDto
{
public long? Id { get; set; }
public bool? Active { get; set; }
public string? Description { get; set; }
public EndpointAuthType? Type { get; set; }
public string? ApiKey { get; set; }
public string? ApiValue { get; set; }
public ApiKeyLocation? ApiKeyAddTo { get; set; }
public string? Token { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? Domain { get; set; }
public string? Workstation { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record EndpointDto
{
public long Id { get; set; }
public bool? Active { get; set; }
public string? Description { get; set; }
public string? Uri { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,47 +1,33 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
namespace ReC.Application.Common.Dto;
/// <summary>
/// Represents the TBREC_CFG_ENDPOINT_PARAMS table.
/// All properties are nullable to provide flexibility on the database side,
/// preventing breaking changes if columns are altered to be nullable in production.
/// </summary>
[Table("TBREC_CFG_ENDPOINT_PARAMS", Schema = "dbo")]
public class EndpointParam
public record EndpointParamDto
{
[Key]
[Column("GUID")]
public long? Id { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("DESCRIPTION")]
public string? Description { get; set; }
[Column("GROUP_ID")]
public short? GroupId { get; set; }
[Column("SEQUENCE")]
public byte? Sequence { get; set; }
[Column("KEY")]
public string? Key { get; set; }
[Column("VALUE")]
public string? Value { 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,30 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record ProfileDto
{
public long Id { get; set; }
public bool? Active { get; set; }
public string? Type { get; set; }
public string? Mandantor { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? LogLevel { get; set; }
public string? Language { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -0,0 +1,42 @@
namespace ReC.Application.Common.Dto;
public class ProfileViewDto
{
public long Id { get; init; }
public IEnumerable<RecActionViewDto>? RecActions { get; set; }
public bool Active { get; init; }
public byte TypeId { get; init; }
public string? Type { get; init; }
public string? Mandantor { get; init; }
public string? ProfileName { get; init; }
public string? Description { get; init; }
public byte LogLevelId { get; init; }
public string? LogLevel { get; init; }
public short LanguageId { get; init; }
public string? Language { get; init; }
public string? AddedWho { get; init; }
public DateTime AddedWhen { get; init; }
public string? ChangedWho { get; init; }
public DateTime? ChangedWhen { get; init; }
public DateTime? FirstRun { get; init; }
public DateTime? LastRun { get; init; }
public string? LastResult { get; init; }
}

View File

@@ -1,76 +1,53 @@
namespace ReC.Application.Common.Dto;
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
public record RecActionDto
{
public required long Id { get; init; }
public long? Id { get; set; }
public long? ProfileId { get; init; }
public long? ProfileId { get; set; }
public string? ProfileName { get; init; }
public ProfileDto? Profile { get; set; }
public string? ProfileType { get; init; }
public bool? Active { get; set; }
public byte? ProfileSequence { get; init; }
public byte? Sequence { get; set; }
public long? EndpointId { get; init; }
public long? EndpointId { get; set; }
public string? EndpointUri { get; init; }
public EndpointDto? Endpoint { get; set; }
public long? EndpointAuthId { get; init; }
public long? EndpointAuthId { get; set; }
public string? EndpointAuthType { get; init; }
public EndpointAuthDto? EndpointAuth { get; set; }
public string? EndpointAuthApiKey { get; init; }
public short? EndpointParamsId { get; set; }
public string? EndpointAuthApiValue { get; init; }
public short? SqlConnectionId { get; set; }
public string? EndpointAuthApiKeyAddTo { get; init; }
public ConnectionDto? SqlConnection { get; set; }
public string? EndpointAuthToken { get; init; }
public string? Type { get; set; }
public string? EndpointAuthUsername { get; init; }
public string? PreprocessingQuery { get; set; }
public string? EndpointAuthPassword { get; init; }
public string? HeaderQuery { get; set; }
public string? EndpointAuthDomain { get; init; }
public string? BodyQuery { get; set; }
public string? EndpointAuthWorkstation { get; init; }
public string? PostprocessingQuery { get; set; }
public short? EndpointParamsId { get; init; }
public ErrorAction? ErrorAction { get; set; }
public short? SqlConnectionId { get; init; }
public string? AddedWho { get; set; }
public string? SqlConnectionServer { get; init; }
public DateTime? AddedWhen { get; set; }
public string? SqlConnectionDb { get; init; }
public string? ChangedWho { get; set; }
public string? SqlConnectionUsername { get; init; }
public DateTime? ChangedWhen { get; set; }
public string? SqlConnectionPassword { get; init; }
public string? RestType { get; init; }
public string? PreprocessingQuery { get; init; }
public string? HeaderQuery { get; init; }
public Dictionary<string, string>? Headers { get; set; }
public string? BodyQuery { get; init; }
public string? Body { get; set; }
public string? PostprocessingQuery { get; init; }
public UriBuilder ToEndpointUriBuilder()
{
var builder = EndpointUri is null ? new UriBuilder() : new UriBuilder(EndpointUri);
builder.Port = -1;
if (ProfileType is not null)
builder.Scheme = ProfileType;
return builder;
}
public OutResDto? OutRes { get; set; }
}

View File

@@ -0,0 +1,88 @@
using ReC.Domain.Constants;
namespace ReC.Application.Common.Dto;
public record RecActionViewDto
{
public required long Id { get; init; }
public long? ProfileId { get; init; }
public string? ProfileName { get; init; }
public ProfileType? ProfileType { get; init; }
public byte? Sequence { get; init; }
public long? EndpointId { get; init; }
public string? EndpointUri { get; init; }
public long? EndpointAuthId { get; init; }
public EndpointAuthType EndpointAuthType { get; init; } = EndpointAuthType.NoAuth;
public string? EndpointAuthTypeName { get; init; }
public string? EndpointAuthApiKey { get; init; }
public string? EndpointAuthApiValue { get; init; }
public ApiKeyLocation? EndpointAuthApiKeyAddTo { get; init; }
public string? EndpointAuthApiKeyAddToName { get; init; }
public string? EndpointAuthToken { get; init; }
public string? EndpointAuthUsername { get; init; }
public string? EndpointAuthPassword { get; init; }
public string? EndpointAuthDomain { get; init; }
public string? EndpointAuthWorkstation { get; init; }
public short? EndpointParamsId { get; init; }
public short? SqlConnectionId { get; init; }
public string? SqlConnectionServer { get; init; }
public string? SqlConnectionDb { get; init; }
public string? SqlConnectionUsername { get; init; }
public string? SqlConnectionPassword { get; init; }
public RestType? RestType { get; init; }
public string? RestTypeName { get; init; }
public string? PreprocessingQuery { get; init; }
public string? HeaderQuery { get; init; }
public Dictionary<string, string>? Headers { get; set; }
public string? BodyQuery { get; init; }
public string? Body { get; set; }
public string? PostprocessingQuery { get; init; }
public ErrorAction? ErrorAction { get; init; }
public string? ErrorActionName { get; init; }
public UriBuilder ToEndpointUriBuilder()
{
var builder = EndpointUri is null ? new UriBuilder() : new UriBuilder(EndpointUri);
builder.Port = -1;
if (ProfileType is ProfileType type)
builder.Scheme = type.ToUriBuilderScheme();
return builder;
}
}

View File

@@ -0,0 +1,34 @@
namespace ReC.Application.Common.Dto;
public record ResultViewDto
{
public long Id { get; init; }
public OutResDto? Root { get; init; }
public long? ActionId { get; init; }
public RecActionViewDto? Action { get; init; }
public long? ProfileId { get; init; }
public ProfileViewDto? Profile { get; init; }
public string? ProfileName { get; init; }
public short? StatusCode { get; init; }
public string? StatusName { get; init; }
public string? Header { get; init; }
public string? Body { get; init; }
public string? AddedWho { get; init; }
public DateTime? AddedWhen { get; init; }
public string? ChangedWho { get; init; }
public DateTime? ChangedWhen { get; init; }
}

View File

@@ -0,0 +1,23 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Common.Exceptions;
public class DeleteObjectFailedException : Exception
{
public DeleteObjectProcedure Procedure { get; }
public DeleteObjectFailedException(DeleteObjectProcedure procedure) : base()
{
Procedure = procedure;
}
public DeleteObjectFailedException(DeleteObjectProcedure procedure, string? message) : base(message)
{
Procedure = procedure;
}
public DeleteObjectFailedException(DeleteObjectProcedure procedure, string? message, Exception? innerException) : base(message, innerException)
{
Procedure = procedure;
}
}

View File

@@ -0,0 +1,23 @@
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Common.Exceptions;
public class InsertObjectFailedException : Exception
{
public InsertObjectProcedure Procedure { get; }
public InsertObjectFailedException(InsertObjectProcedure procedure) : base()
{
Procedure = procedure;
}
public InsertObjectFailedException(InsertObjectProcedure procedure, string? message) : base(message)
{
Procedure = procedure;
}
public InsertObjectFailedException(InsertObjectProcedure procedure, string? message, Exception? innerException) : base(message, innerException)
{
Procedure = procedure;
}
}

View File

@@ -0,0 +1,23 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Common.Exceptions;
public class UpdateObjectFailedException : Exception
{
public UpdateObjectProcedure Procedure { get; }
public UpdateObjectFailedException(UpdateObjectProcedure procedure) : base()
{
Procedure = procedure;
}
public UpdateObjectFailedException(UpdateObjectProcedure procedure, string? message) : base(message)
{
Procedure = procedure;
}
public UpdateObjectFailedException(UpdateObjectProcedure procedure, string? message, Exception? innerException) : base(message, innerException)
{
Procedure = procedure;
}
}

View File

@@ -1,25 +1,28 @@
using System.Diagnostics.CodeAnalysis;
using ReC.Domain.Constants;
using System.Diagnostics.CodeAnalysis;
namespace ReC.Application.Common;
public static class HttpExtensions
{
private static readonly Dictionary<string, HttpMethod> _methods = new(StringComparer.OrdinalIgnoreCase)
private static readonly Dictionary<RestType, HttpMethod> _methods = new()
{
["GET"] = HttpMethod.Get,
["POST"] = HttpMethod.Post,
["PUT"] = HttpMethod.Put,
["DELETE"] = HttpMethod.Delete,
["PATCH"] = HttpMethod.Patch,
["HEAD"] = HttpMethod.Head,
["OPTIONS"] = HttpMethod.Options,
["TRACE"] = HttpMethod.Trace,
["CONNECT"] = HttpMethod.Connect
[RestType.Get] = HttpMethod.Get,
[RestType.Post] = HttpMethod.Post,
[RestType.Put] = HttpMethod.Put,
[RestType.Delete] = HttpMethod.Delete,
[RestType.Patch] = HttpMethod.Patch,
[RestType.Head] = HttpMethod.Head,
[RestType.Options] = HttpMethod.Options,
[RestType.Trace] = HttpMethod.Trace,
[RestType.Connect] = HttpMethod.Connect
};
public static HttpMethod ToHttpMethod(this string method) => _methods.TryGetValue(method, out var httpMethod)
? httpMethod
: new HttpMethod(method);
public static HttpMethod ToHttpMethod(this RestType method) => !method.IsValid()
? throw new ArgumentOutOfRangeException(nameof(method), $"The RestType value '{method}' is not valid.")
: _methods.TryGetValue(method, out var httpMethod)
? httpMethod
: new HttpMethod(method.ToHttpMethodName());
public static HttpRequestMessage ToHttpRequestMessage(this HttpMethod method, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri)
=> new(method, requestUri);

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace ReC.Application.Common.Interfaces;
public record AuthScope
{
public string? AddedWho { get; set; }
}
public interface IAuthScoped : IScoped<AuthScope>
{
public string? AddedWho => Scope.AddedWho;
}

View File

@@ -1,19 +1,24 @@
using Microsoft.EntityFrameworkCore;
using ReC.Domain.Entities;
using ReC.Domain.QueryOutput;
using ReC.Domain.Views;
namespace ReC.Application.Common.Interfaces;
public interface IRecDbContext
{
public DbSet<EndpointParam> EndpointParams { get; }
#region DbSets
public DbSet<RecActionView> RecActionViews { get; set; }
public DbSet<RecActionView> Actions { get; }
public DbSet<ProfileView> ProfileViews { get; set; }
public DbSet<OutRes> OutRes { get; }
public DbSet<ResultView> RecResultViews { get; set; }
public DbSet<HeaderQueryResult> HeaderQueryResults { get; }
public DbSet<HeaderQueryResult> HeaderQueryResults { get; set; }
public DbSet<BodyQueryResult> BodyQueryResults { get; }
public DbSet<BodyQueryResult> BodyQueryResults { get; set; }
public DbSet<InsertObjectResult> RecResults { get; set; }
#endregion DbSets
public Task<int> SaveChangesAsync(CancellationToken cancel = default);
}

View File

@@ -0,0 +1,10 @@
using MediatR;
using System.Text.Json.Serialization;
namespace ReC.Application.Common.Interfaces;
public interface IScoped<TScope> where TScope : notnull
{
[JsonIgnore]
public TScope Scope { get; }
}

View File

@@ -0,0 +1,6 @@
namespace ReC.Application.Common.Options;
public class SqlExceptionOptions
{
public HashSet<int> BadRequestSqlExceptionNumbers { get; set; } = [];
}

View File

@@ -0,0 +1,91 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Options;
namespace ReC.Application.Common.Procedures.DeleteProcedure;
public record DeleteObjectProcedure : IRequest<int>
{
/// <summary>
/// Target entity: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT
/// </summary>
public string Entity { get; set; } = null!;
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// If true, delete even if dependent data exists
/// </summary>
public bool Force { get; set; }
}
public static class DeleteObjectProcedureExtensions
{
public static Task<int> ExecuteDeleteProcedure(this ISender sender, IDeleteProcedure procedure, CancellationToken cancel = default)
{
return sender.Send(procedure.ToObjectProcedure(), cancel);
}
public static Task<int> ExecuteDeleteProcedure(this ISender sender, string entity, long start, long end = 0, bool force = false, CancellationToken cancel = default)
{
return sender.Send(new DeleteObjectProcedure
{
Entity = entity,
Start = start,
End = end,
Force = force
}, cancel);
}
}
public class DeleteObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlExceptionOptions> sqlExOpt) : IRequestHandler<DeleteObjectProcedure, int>
{
public async Task<int> Handle(DeleteObjectProcedure request, CancellationToken cancel)
{
var parameters = new[]
{
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
new SqlParameter("@pSTART", request.Start.ToString()),
new SqlParameter("@pEND", request.End.ToString()),
new SqlParameter("@pFORCE", (object?)request.Force ?? DBNull.Value)
};
try
{
var result = await repo.ExecuteQueryRawAsync(
"DECLARE @RC SMALLINT = 0; " +
"EXEC @RC = [dbo].[PRREC_DELETE_OBJECT] " +
"@pENTITY, @pSTART, @pEND, @pFORCE; " +
"SELECT @RC;",
parameters,
cancel);
// The stored procedure returns 0 on success, error codes > 0 on failure
if (result > 0)
{
throw new DeleteObjectFailedException(request, $"DeleteObject stored procedure failed with error code: {result}");
}
return result;
}
catch (SqlException ex)
{
if (sqlExOpt.CurrentValue.BadRequestSqlExceptionNumbers.Contains(ex.Number))
throw new BadRequestException(ex.Message, ex);
else
throw;
}
}
}

View File

@@ -0,0 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
public interface IDeleteProcedure
{
public DeleteObjectProcedure ToObjectProcedure();
}

View File

@@ -0,0 +1,6 @@
namespace ReC.Application.Common.Procedures.InsertProcedure;
public interface IInsertProcedure
{
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null);
}

View File

@@ -0,0 +1,150 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Options;
using ReC.Application.EndpointAuth.Commands;
using ReC.Application.EndpointParams.Commands;
using ReC.Application.Endpoints.Commands;
using ReC.Application.Profile.Commands;
using ReC.Application.RecActions.Commands;
using ReC.Application.Results.Commands;
namespace ReC.Application.Common.Procedures.InsertProcedure;
public record InsertObjectProcedure : IRequest<long>
{
/// <summary>
/// Target entity: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT
/// </summary>
public string Entity { get; set; } = null!;
internal string? AddedWho { get; private set; }
public InsertObjectProcedure AddedBy(string? addedWho = null)
{
AddedWho = addedWho ?? "ReC.API";
return this;
}
public InsertActionProcedure Action { get; set; } = new();
public InsertEndpointProcedure Endpoint { get; set; } = new();
public InsertEndpointAuthProcedure EndpointAuth { get; set; } = new();
public InsertProfileProcedure Profile { get; set; } = new();
public InsertResultProcedure Result { get; set; } = new();
public InsertEndpointParamsProcedure EndpointParams { get; set; } = new();
}
public static class InsertObjectProcedureExtensions
{
public static Task<long> ExecuteInsertProcedure(this ISender sender, IInsertProcedure procedure, string? addedWho = null, CancellationToken cancel = default)
{
return sender.Send(procedure.ToObjectProcedure(addedWho ?? "Rec.API"), cancel);
}
}
public class InsertObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlExceptionOptions> sqlExOpt) : IRequestHandler<InsertObjectProcedure, long>
{
public async Task<long> Handle(InsertObjectProcedure request, CancellationToken cancel)
{
var parameters = new[]
{
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
new SqlParameter("@pADDED_WHO", (object?)request.AddedWho ?? DBNull.Value),
new SqlParameter("@pADDED_WHEN", (object?)DateTime.UtcNow ?? DBNull.Value),
new SqlParameter("@pACTION_PROFILE_ID", (object?)request.Action.ProfileId ?? DBNull.Value),
new SqlParameter("@pACTION_ACTIVE", (object?)request.Action.Active ?? DBNull.Value),
new SqlParameter("@pACTION_SEQUENCE", (object?)request.Action.Sequence ?? DBNull.Value),
new SqlParameter("@pACTION_ENDPOINT_ID", (object?)request.Action.EndpointId ?? DBNull.Value),
new SqlParameter("@pACTION_ENDPOINT_AUTH_ID", (object?)request.Action.EndpointAuthId ?? DBNull.Value),
new SqlParameter("@pACTION_ENDPOINT_PARAMS_ID", (object?)request.Action.EndpointParamsId ?? DBNull.Value),
new SqlParameter("@pACTION_SQL_CONNECTION_ID", (object?)request.Action.SqlConnectionId ?? DBNull.Value),
new SqlParameter("@pACTION_TYPE_ID", (object?)(byte?)request.Action.TypeId ?? DBNull.Value),
new SqlParameter("@pACTION_PRE_SQL", (object?)request.Action.PreSql ?? DBNull.Value),
new SqlParameter("@pACTION_HEADER_SQL", (object?)request.Action.HeaderSql ?? DBNull.Value),
new SqlParameter("@pACTION_BODY_SQL", (object?)request.Action.BodySql ?? DBNull.Value),
new SqlParameter("@pACTION_POST_SQL", (object?)request.Action.PostSql ?? DBNull.Value),
new SqlParameter("@pACTION_ERROR_ACTION_ID", (object?)request.Action.ErrorActionId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_ACTIVE", (object?)request.Endpoint.Active ?? DBNull.Value),
new SqlParameter("@pENDPOINT_DESCRIPTION", (object?)request.Endpoint.Description ?? DBNull.Value),
new SqlParameter("@pENDPOINT_URI", (object?)request.Endpoint.Uri ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_ACTIVE", (object?)request.EndpointAuth.Active ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_DESCRIPTION", (object?)request.EndpointAuth.Description ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_TYPE_ID", (object?)request.EndpointAuth.TypeId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_API_KEY", (object?)request.EndpointAuth.ApiKey ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_API_VALUE", (object?)request.EndpointAuth.ApiValue ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_API_KEY_ADD_TO_ID", (object?)request.EndpointAuth.ApiKeyAddToId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_TOKEN", (object?)request.EndpointAuth.Token ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_USERNAME", (object?)request.EndpointAuth.Username ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_PASSWORD", (object?)request.EndpointAuth.Password ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_DOMAIN", (object?)request.EndpointAuth.Domain ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_WORKSTATION", (object?)request.EndpointAuth.Workstation ?? DBNull.Value),
new SqlParameter("@pPROFILE_ACTIVE", (object?)request.Profile.Active ?? DBNull.Value),
new SqlParameter("@pPROFILE_TYPE_ID", (object?)request.Profile.TypeId ?? DBNull.Value),
new SqlParameter("@pPROFILE_MANDANTOR", (object?)request.Profile.Mandantor ?? DBNull.Value),
new SqlParameter("@pPROFILE_NAME", (object?)request.Profile.Name ?? DBNull.Value),
new SqlParameter("@pPROFILE_DESCRIPTION", (object?)request.Profile.Description ?? DBNull.Value),
new SqlParameter("@pPROFILE_LOG_LEVEL_ID", (object?)request.Profile.LogLevelId ?? DBNull.Value),
new SqlParameter("@pPROFILE_LANGUAGE_ID", (object?)request.Profile.LanguageId ?? DBNull.Value),
new SqlParameter("@pRESULT_ACTION_ID", (object?)request.Result.ActionId ?? DBNull.Value),
new SqlParameter("@pRESULT_STATUS_ID", (object?)request.Result.StatusId ?? DBNull.Value),
new SqlParameter("@pRESULT_HEADER", (object?)request.Result.Header ?? DBNull.Value),
new SqlParameter("@pRESULT_BODY", (object?)request.Result.Body ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_ACTIVE", (object?)request.EndpointParams.Active ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_DESCRIPTION", (object?)request.EndpointParams.Description ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_GROUP_ID", (object?)request.EndpointParams.GroupId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_SEQUENCE", (object?)request.EndpointParams.Sequence ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_KEY", (object?)request.EndpointParams.Key ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_VALUE", (object?)request.EndpointParams.Value ?? DBNull.Value),
new SqlParameter
{
ParameterName = "@oGUID",
SqlDbType = System.Data.SqlDbType.BigInt,
Direction = System.Data.ParameterDirection.Output
}
};
try
{
await repo.ExecuteQueryRawAsync(
"EXEC [dbo].[PRREC_INSERT_OBJECT] " +
"@pENTITY, @pADDED_WHO, @pADDED_WHEN, " +
"@pACTION_PROFILE_ID, @pACTION_ACTIVE, @pACTION_SEQUENCE, @pACTION_ENDPOINT_ID, @pACTION_ENDPOINT_AUTH_ID, @pACTION_ENDPOINT_PARAMS_ID, @pACTION_SQL_CONNECTION_ID, @pACTION_TYPE_ID, @pACTION_PRE_SQL, @pACTION_HEADER_SQL, @pACTION_BODY_SQL, @pACTION_POST_SQL, @pACTION_ERROR_ACTION_ID, " +
"@pENDPOINT_ACTIVE, @pENDPOINT_DESCRIPTION, @pENDPOINT_URI, " +
"@pENDPOINT_AUTH_ACTIVE, @pENDPOINT_AUTH_DESCRIPTION, @pENDPOINT_AUTH_TYPE_ID, @pENDPOINT_AUTH_API_KEY, @pENDPOINT_AUTH_API_VALUE, @pENDPOINT_AUTH_API_KEY_ADD_TO_ID, @pENDPOINT_AUTH_TOKEN, @pENDPOINT_AUTH_USERNAME, @pENDPOINT_AUTH_PASSWORD, @pENDPOINT_AUTH_DOMAIN, @pENDPOINT_AUTH_WORKSTATION, " +
"@pPROFILE_ACTIVE, @pPROFILE_TYPE_ID, @pPROFILE_MANDANTOR, @pPROFILE_NAME, @pPROFILE_DESCRIPTION, @pPROFILE_LOG_LEVEL_ID, @pPROFILE_LANGUAGE_ID, " +
"@pRESULT_ACTION_ID, @pRESULT_STATUS_ID, @pRESULT_HEADER, @pRESULT_BODY, " +
"@pENDPOINT_PARAMS_ACTIVE, @pENDPOINT_PARAMS_DESCRIPTION, @pENDPOINT_PARAMS_GROUP_ID, @pENDPOINT_PARAMS_SEQUENCE, @pENDPOINT_PARAMS_KEY, @pENDPOINT_PARAMS_VALUE, " +
"@oGUID OUTPUT",
parameters,
cancel);
}
catch (SqlException ex)
{
if (sqlExOpt.CurrentValue.BadRequestSqlExceptionNumbers.Contains(ex.Number))
throw new BadRequestException(ex.Message, ex);
else
throw;
}
var guidParam = parameters.Last();
if (guidParam.Value != DBNull.Value)
if (guidParam.Value is long longValue)
return longValue;
else if (long.TryParse(guidParam.Value.ToString(), out var guid))
return guid;
throw new InsertObjectFailedException(request, "InsertObject stored procedure did not return a valid identifier.");
}
}

View File

@@ -0,0 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
public interface IUpdateProcedure
{
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null);
}

View File

@@ -0,0 +1,151 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Options;
using ReC.Application.EndpointAuth.Commands;
using ReC.Application.EndpointParams.Commands;
using ReC.Application.Endpoints.Commands;
using ReC.Application.Profile.Commands;
using ReC.Application.RecActions.Commands;
using ReC.Application.Results.Commands;
namespace ReC.Application.Common.Procedures.UpdateProcedure;
public record UpdateObjectProcedure : IRequest<int>
{
/// <summary>
/// Target entity: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT
/// </summary>
public string Entity { get; set; } = null!;
/// <summary>
/// Target GUID to update (required)
/// </summary>
public long Id { get; set; }
internal string? ChangedWho { get; private set; }
public UpdateObjectProcedure ChangedBy(string? changedWho = null)
{
ChangedWho = changedWho ?? "ReC.API";
return this;
}
public UpdateActionProcedure Action { get; set; } = new();
public UpdateEndpointProcedure Endpoint { get; set; } = new();
public UpdateEndpointAuthProcedure EndpointAuth { get; set; } = new();
public UpdateProfileProcedure Profile { get; set; } = new();
public UpdateResultProcedure Result { get; set; } = new();
public UpdateEndpointParamsProcedure EndpointParams { get; set; } = new();
}
public static class UpdateObjectProcedureExtensions
{
public static Task<int> ExecuteUpdateProcedure(this ISender sender, IUpdateProcedure procedure, long id, string? changedWho = null, CancellationToken cancel = default)
{
return sender.Send(procedure.ToObjectProcedure(id, changedWho ?? "ReC.API"), cancel);
}
}
public class UpdateObjectProcedureHandler(IRepository repo, IOptionsMonitor<SqlExceptionOptions> sqlExOpt) : IRequestHandler<UpdateObjectProcedure, int>
{
public async Task<int> Handle(UpdateObjectProcedure request, CancellationToken cancel)
{
var parameters = new[]
{
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
new SqlParameter("@pGUID", (object?)request.Id ?? DBNull.Value),
new SqlParameter("@pCHANGED_WHO", (object?)request.ChangedWho ?? DBNull.Value),
new SqlParameter("@pCHANGED_WHEN", (object?)DateTime.UtcNow ?? DBNull.Value),
new SqlParameter("@pACTION_PROFILE_ID", (object?)request.Action.ProfileId ?? DBNull.Value),
new SqlParameter("@pACTION_ACTIVE", (object?)request.Action.Active ?? DBNull.Value),
new SqlParameter("@pACTION_SEQUENCE", (object?)request.Action.Sequence ?? DBNull.Value),
new SqlParameter("@pACTION_ENDPOINT_ID", (object?)request.Action.EndpointId ?? DBNull.Value),
new SqlParameter("@pACTION_ENDPOINT_AUTH_ID", (object?)request.Action.EndpointAuthId ?? DBNull.Value),
new SqlParameter("@pACTION_ENDPOINT_PARAMS_ID", (object?)request.Action.EndpointParamsId ?? DBNull.Value),
new SqlParameter("@pACTION_SQL_CONNECTION_ID", (object?)request.Action.SqlConnectionId ?? DBNull.Value),
new SqlParameter("@pACTION_TYPE_ID", (object?)request.Action.TypeId ?? DBNull.Value),
new SqlParameter("@pACTION_PRE_SQL", (object?)request.Action.PreSql ?? DBNull.Value),
new SqlParameter("@pACTION_HEADER_SQL", (object?)request.Action.HeaderSql ?? DBNull.Value),
new SqlParameter("@pACTION_BODY_SQL", (object?)request.Action.BodySql ?? DBNull.Value),
new SqlParameter("@pACTION_POST_SQL", (object?)request.Action.PostSql ?? DBNull.Value),
new SqlParameter("@pACTION_ERROR_ACTION_ID", (object?)request.Action.ErrorActionId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_ACTIVE", (object?)request.Endpoint.Active ?? DBNull.Value),
new SqlParameter("@pENDPOINT_DESCRIPTION", (object?)request.Endpoint.Description ?? DBNull.Value),
new SqlParameter("@pENDPOINT_URI", (object?)request.Endpoint.Uri ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_ACTIVE", (object?)request.EndpointAuth.Active ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_DESCRIPTION", (object?)request.EndpointAuth.Description ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_TYPE_ID", (object?)request.EndpointAuth.TypeId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_API_KEY", (object?)request.EndpointAuth.ApiKey ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_API_VALUE", (object?)request.EndpointAuth.ApiValue ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_API_KEY_ADD_TO_ID", (object?)request.EndpointAuth.ApiKeyAddToId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_TOKEN", (object?)request.EndpointAuth.Token ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_USERNAME", (object?)request.EndpointAuth.Username ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_PASSWORD", (object?)request.EndpointAuth.Password ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_DOMAIN", (object?)request.EndpointAuth.Domain ?? DBNull.Value),
new SqlParameter("@pENDPOINT_AUTH_WORKSTATION", (object?)request.EndpointAuth.Workstation ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_ACTIVE", (object?)request.EndpointParams.Active ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_DESCRIPTION", (object?)request.EndpointParams.Description ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_GROUP_ID", (object?)request.EndpointParams.GroupId ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_SEQUENCE", (object?)request.EndpointParams.Sequence ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_KEY", (object?)request.EndpointParams.Key ?? DBNull.Value),
new SqlParameter("@pENDPOINT_PARAMS_VALUE", (object?)request.EndpointParams.Value ?? DBNull.Value),
new SqlParameter("@pPROFILE_ACTIVE", (object?)request.Profile.Active ?? DBNull.Value),
new SqlParameter("@pPROFILE_TYPE_ID", (object?)request.Profile.TypeId ?? DBNull.Value),
new SqlParameter("@pPROFILE_MANDANTOR", (object?)request.Profile.Mandantor ?? DBNull.Value),
new SqlParameter("@pPROFILE_NAME", (object?)request.Profile.Name ?? DBNull.Value),
new SqlParameter("@pPROFILE_DESCRIPTION", (object?)request.Profile.Description ?? DBNull.Value),
new SqlParameter("@pPROFILE_LOG_LEVEL_ID", (object?)request.Profile.LogLevelId ?? DBNull.Value),
new SqlParameter("@pPROFILE_LANGUAGE_ID", (object?)request.Profile.LanguageId ?? DBNull.Value),
new SqlParameter("@pPROFILE_FIRST_RUN", (object?)request.Profile.FirstRun ?? DBNull.Value),
new SqlParameter("@pPROFILE_LAST_RUN", (object?)request.Profile.LastRun ?? DBNull.Value),
new SqlParameter("@pPROFILE_LAST_RESULT", (object?)request.Profile.LastResult ?? DBNull.Value),
new SqlParameter("@pRESULT_ACTION_ID", (object?)request.Result.ActionId ?? DBNull.Value),
new SqlParameter("@pRESULT_STATUS_ID", (object?)request.Result.StatusId ?? DBNull.Value),
new SqlParameter("@pRESULT_HEADER", (object?)request.Result.Header ?? DBNull.Value),
new SqlParameter("@pRESULT_BODY", (object?)request.Result.Body ?? DBNull.Value)
};
try
{
var result = await repo.ExecuteQueryRawAsync(
"DECLARE @RC SMALLINT = 0; " +
"EXEC @RC = [dbo].[PRREC_UPDATE_OBJECT] " +
"@pENTITY, @pGUID, @pCHANGED_WHO, @pCHANGED_WHEN, " +
"@pACTION_PROFILE_ID, @pACTION_ACTIVE, @pACTION_SEQUENCE, @pACTION_ENDPOINT_ID, @pACTION_ENDPOINT_AUTH_ID, @pACTION_ENDPOINT_PARAMS_ID, @pACTION_SQL_CONNECTION_ID, @pACTION_TYPE_ID, @pACTION_PRE_SQL, @pACTION_HEADER_SQL, @pACTION_BODY_SQL, @pACTION_POST_SQL, @pACTION_ERROR_ACTION_ID, " +
"@pENDPOINT_ACTIVE, @pENDPOINT_DESCRIPTION, @pENDPOINT_URI, " +
"@pENDPOINT_AUTH_ACTIVE, @pENDPOINT_AUTH_DESCRIPTION, @pENDPOINT_AUTH_TYPE_ID, @pENDPOINT_AUTH_API_KEY, @pENDPOINT_AUTH_API_VALUE, @pENDPOINT_AUTH_API_KEY_ADD_TO_ID, @pENDPOINT_AUTH_TOKEN, @pENDPOINT_AUTH_USERNAME, @pENDPOINT_AUTH_PASSWORD, @pENDPOINT_AUTH_DOMAIN, @pENDPOINT_AUTH_WORKSTATION, " +
"@pENDPOINT_PARAMS_ACTIVE, @pENDPOINT_PARAMS_DESCRIPTION, @pENDPOINT_PARAMS_GROUP_ID, @pENDPOINT_PARAMS_SEQUENCE, @pENDPOINT_PARAMS_KEY, @pENDPOINT_PARAMS_VALUE, " +
"@pPROFILE_ACTIVE, @pPROFILE_TYPE_ID, @pPROFILE_MANDANTOR, @pPROFILE_NAME, @pPROFILE_DESCRIPTION, @pPROFILE_LOG_LEVEL_ID, @pPROFILE_LANGUAGE_ID, @pPROFILE_FIRST_RUN, @pPROFILE_LAST_RUN, @pPROFILE_LAST_RESULT, " +
"@pRESULT_ACTION_ID, @pRESULT_STATUS_ID, @pRESULT_HEADER, @pRESULT_BODY; " +
"SELECT @RC;",
parameters,
cancel);
// The stored procedure returns 0 on success, error codes > 0 on failure
if (result > 0)
{
throw new UpdateObjectFailedException(request, $"UpdateObject stored procedure failed with error code: {result}");
}
return result;
}
catch (SqlException ex)
{
if (sqlExOpt.CurrentValue.BadRequestSqlExceptionNumbers.Contains(ex.Number))
throw new BadRequestException(ex.Message, ex);
else
throw;
}
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using ReC.Application.Common.Interfaces;
namespace ReC.Application.Common.Validations;
public class AuthScopedValidator : AbstractValidator<IAuthScoped>
{
public AuthScopedValidator()
{
RuleFor(x => x.AddedWho)
.NotEmpty()
.WithMessage("The 'AddedWho' field is required. A missing value may indicate an API configuration issue. Please contact your system administrator for assistance.");
}
}

View File

@@ -0,0 +1,87 @@
using FluentValidation;
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Common.Validations;
public class InsertObjectProcedureValidator : AbstractValidator<InsertObjectProcedure>
{
public InsertObjectProcedureValidator()
{
// ENTITY must be one of the allowed values
RuleFor(x => x.Entity)
.NotEmpty()
.Must(e => e is "ACTION" or "ENDPOINT" or "ENDPOINT_AUTH" or "ENDPOINT_PARAMS" or "PROFILE" or "RESULT")
.WithMessage("ENTITY must be one of: ACTION, ENDPOINT, ENDPOINT_AUTH, ENDPOINT_PARAMS, PROFILE, RESULT.");
// ACTION validation
When(x => x.Entity == "ACTION", () =>
{
RuleFor(x => x.Action.ProfileId)
.NotNull()
.WithMessage("ACTION requires ActionProfileId (maps to @pACTION_PROFILE_ID).");
RuleFor(x => x.Action.EndpointId)
.NotNull()
.WithMessage("ACTION requires ActionEndpointId (maps to @pACTION_ENDPOINT_ID).");
});
// ENDPOINT validation
When(x => x.Entity == "ENDPOINT", () =>
{
RuleFor(x => x.Endpoint.Uri)
.NotEmpty()
.WithMessage("ENDPOINT requires EndpointUri (maps to @pENDPOINT_URI).")
.MaximumLength(2000);
});
// PROFILE validation
When(x => x.Entity == "PROFILE", () =>
{
RuleFor(x => x.Profile.Name)
.NotEmpty()
.WithMessage("PROFILE requires ProfileName (maps to @pPROFILE_NAME).")
.MaximumLength(50);
RuleFor(x => x.Profile.Mandantor)
.MaximumLength(50)
.When(x => x.Profile.Mandantor != null);
RuleFor(x => x.Profile.Description)
.MaximumLength(250)
.When(x => x.Profile.Description != null);
});
// RESULT validation
When(x => x.Entity == "RESULT", () =>
{
RuleFor(x => x.Result.ActionId)
.NotNull()
.WithMessage("RESULT requires ResultActionId (maps to @pRESULT_ACTION_ID).");
RuleFor(x => x.Result.StatusId)
.NotNull()
.WithMessage("RESULT requires ResultStatusId (maps to @pRESULT_STATUS_ID).");
});
// ENDPOINT_PARAMS validation
When(x => x.Entity == "ENDPOINT_PARAMS", () =>
{
RuleFor(x => x.EndpointParams.GroupId)
.NotNull()
.WithMessage("ENDPOINT_PARAMS requires EndpointParamsGroupId (maps to @pENDPOINT_PARAMS_GROUP_ID).");
});
// Simple length guards for some string fields (optional but cheap)
RuleFor(x => x.AddedWho)
.MaximumLength(50)
.When(x => x.AddedWho != null);
RuleFor(x => x.Endpoint.Description)
.MaximumLength(250)
.When(x => x.Endpoint.Description != null);
RuleFor(x => x.EndpointAuth.Description)
.MaximumLength(250)
.When(x => x.EndpointAuth.Description != null);
}
}

View File

@@ -1,10 +1,13 @@
using MediatR;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using ReC.Application.Common.Behaviors;
using ReC.Application.Common.Constants;
using ReC.Application.Common.Options;
using ReC.Application.Common.Procedures;
using System.Reflection;
using FluentValidation;
namespace ReC.Application;
@@ -35,7 +38,11 @@ public static class DependencyInjection
cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey;
});
services.AddHttpClient();
services.AddHttpClient(Http.ClientName)
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
UseDefaultCredentials = false
});
return services;
}
@@ -111,5 +118,23 @@ public static class DependencyInjection
return this;
}
#endregion ConfigureRecActions
#region ConfigureSqlException
public ConfigurationOptions ConfigureSqlException(Action<SqlExceptionOptions> configure)
{
_configActions.Enqueue(services => services.Configure(configure));
return this;
}
public ConfigurationOptions ConfigureSqlException(IConfiguration configuration)
{
_configActions.Enqueue(services =>
{
services.Configure<SqlExceptionOptions>(configuration);
});
return this;
}
#endregion ConfigureSqlException
}
}

View File

@@ -0,0 +1,32 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.EndpointAuth.Commands;
public record DeleteEndpointAuthProcedure : IDeleteProcedure
{
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// If true, delete even if dependent ACTION data exists
/// </summary>
public bool Force { get; set; }
public DeleteObjectProcedure ToObjectProcedure()
{
return new DeleteObjectProcedure
{
Entity = "ENDPOINT_AUTH",
Start = Start,
End = End,
Force = Force
};
}
}

View File

@@ -0,0 +1,27 @@
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.EndpointAuth.Commands;
public record InsertEndpointAuthProcedure : IInsertProcedure
{
public bool? Active { get; set; }
public string? Description { get; set; }
public byte? TypeId { get; set; }
public string? ApiKey { get; set; }
public string? ApiValue { get; set; }
public bool? ApiKeyAddToId { get; set; }
public string? Token { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? Domain { get; set; }
public string? Workstation { get; set; }
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null)
{
return new InsertObjectProcedure
{
Entity = "ENDPOINT_AUTH",
EndpointAuth = this
}.AddedBy(addedWho);
}
}

View File

@@ -0,0 +1,28 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.EndpointAuth.Commands;
public record UpdateEndpointAuthProcedure : IUpdateProcedure
{
public bool? Active { get; set; }
public string? Description { get; set; }
public byte? TypeId { get; set; }
public string? ApiKey { get; set; }
public string? ApiValue { get; set; }
public bool? ApiKeyAddToId { get; set; }
public string? Token { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? Domain { get; set; }
public string? Workstation { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ENDPOINT_AUTH",
Id = id,
EndpointAuth = this
}.ChangedBy(changedWho);
}
}

View File

@@ -0,0 +1,32 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.EndpointParams.Commands;
public record DeleteEndpointParamsProcedure : IDeleteProcedure
{
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// If true, delete even if dependent ACTION data exists
/// </summary>
public bool Force { get; set; }
public DeleteObjectProcedure ToObjectProcedure()
{
return new DeleteObjectProcedure
{
Entity = "ENDPOINT_PARAMS",
Start = Start,
End = End,
Force = Force
};
}
}

View File

@@ -0,0 +1,22 @@
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.EndpointParams.Commands;
public record InsertEndpointParamsProcedure : IInsertProcedure
{
public bool? Active { get; set; }
public string? Description { get; set; }
public short? GroupId { get; set; }
public byte? Sequence { get; set; }
public string? Key { get; set; }
public string? Value { get; set; }
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null)
{
return new InsertObjectProcedure
{
Entity = "ENDPOINT_PARAMS",
EndpointParams = this
}.AddedBy(addedWho);
}
}

View File

@@ -0,0 +1,23 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.EndpointParams.Commands;
public record UpdateEndpointParamsProcedure : IUpdateProcedure
{
public bool? Active { get; set; }
public string? Description { get; set; }
public short? GroupId { get; set; }
public byte? Sequence { get; set; }
public string? Key { get; set; }
public string? Value { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ENDPOINT_PARAMS",
Id = id,
EndpointParams = this
}.ChangedBy(changedWho);
}
}

View File

@@ -0,0 +1,32 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Endpoints.Commands;
public record DeleteEndpointProcedure : IDeleteProcedure
{
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// If true, delete even if dependent ACTION data exists
/// </summary>
public bool Force { get; set; }
public DeleteObjectProcedure ToObjectProcedure()
{
return new DeleteObjectProcedure
{
Entity = "ENDPOINT",
Start = Start,
End = End,
Force = Force
};
}
}

View File

@@ -0,0 +1,19 @@
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Endpoints.Commands;
public record InsertEndpointProcedure : IInsertProcedure
{
public bool? Active { get; set; }
public string? Description { get; set; }
public string? Uri { get; set; }
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null)
{
return new InsertObjectProcedure
{
Entity = "ENDPOINT",
Endpoint = this
}.AddedBy(addedWho);
}
}

View File

@@ -1,25 +0,0 @@
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,20 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Endpoints.Commands;
public record UpdateEndpointProcedure : IUpdateProcedure
{
public bool? Active { get; set; }
public string? Description { get; set; }
public string? Uri { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ENDPOINT",
Id = id,
Endpoint = this
}.ChangedBy(changedWho);
}
}

View File

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

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

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

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

View File

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

View File

@@ -0,0 +1,32 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Profile.Commands;
public record DeleteProfileProcedure : IDeleteProcedure
{
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// If true, delete even if dependent ACTION data exists
/// </summary>
public bool Force { get; set; }
public DeleteObjectProcedure ToObjectProcedure()
{
return new DeleteObjectProcedure
{
Entity = "PROFILE",
Start = Start,
End = End,
Force = Force
};
}
}

View File

@@ -0,0 +1,23 @@
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Profile.Commands;
public record InsertProfileProcedure : IInsertProcedure
{
public bool? Active { get; set; }
public byte? TypeId { get; set; }
public string? Mandantor { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public byte? LogLevelId { get; set; }
public short? LanguageId { get; set; }
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null)
{
return new InsertObjectProcedure
{
Entity = "PROFILE",
Profile = this
}.AddedBy(addedWho);
}
}

View File

@@ -0,0 +1,27 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Profile.Commands;
public record UpdateProfileProcedure : IUpdateProcedure
{
public bool? Active { get; set; }
public byte? TypeId { get; set; }
public string? Mandantor { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public byte? LogLevelId { get; set; }
public short? LanguageId { get; set; }
public DateTime? FirstRun { get; set; }
public DateTime? LastRun { get; set; }
public string? LastResult { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "PROFILE",
Id = id,
Profile = this
}.ChangedBy(changedWho);
}
}

View File

@@ -0,0 +1,36 @@
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.Views;
namespace ReC.Application.Profile.Queries;
public record ReadProfileViewQuery : IRequest<IEnumerable<ProfileViewDto>>
{
public long? Id { get; init; } = null;
public bool IncludeActions { get; init; } = true;
}
public class ReadProfileViewQueryHandler(IRepository<ProfileView> repo, IMapper mapper)
: IRequestHandler<ReadProfileViewQuery, IEnumerable<ProfileViewDto>>
{
public async Task<IEnumerable<ProfileViewDto>> Handle(ReadProfileViewQuery request, CancellationToken cancel)
{
var query = request.IncludeActions
? repo.Query.Include(p => p.Actions)
: repo.Query;
if (request.Id is long id)
query = query.Where(p => p.Id == id);
var profiles = await query.ToListAsync(cancel);
return profiles is null || profiles.Count == 0
? throw new NotFoundException($"Profile {request.Id} not found.")
: mapper.Map<IEnumerable<ProfileViewDto>>(profiles);
}
}

View File

@@ -8,9 +8,9 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="15.1.0" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.6.0" />
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.1" />
<PackageReference Include="FluentValidation" Version="12.1.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.0" />
<PackageReference Include="MediatR" Version="13.1.0" />

View File

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

View File

@@ -0,0 +1,32 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.RecActions.Commands;
public record DeleteActionProcedure : IDeleteProcedure
{
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// If true, delete even if dependent RESULT data exists
/// </summary>
public bool Force { get; set; }
public DeleteObjectProcedure ToObjectProcedure()
{
return new DeleteObjectProcedure
{
Entity = "ACTION",
Start = Start,
End = End,
Force = Force
};
}
}

View File

@@ -1,24 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Domain.Entities;
namespace ReC.Application.RecActions.Commands;
public class DeleteRecActionsCommand : IRequest
{
public long ProfileId { get; init; } = 2;
}
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

@@ -0,0 +1,30 @@
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Domain.Constants;
namespace ReC.Application.RecActions.Commands;
public record InsertActionProcedure : IInsertProcedure
{
public long? ProfileId { get; set; }
public bool? Active { get; set; }
public byte? Sequence { get; set; }
public long? EndpointId { get; set; }
public long? EndpointAuthId { get; set; }
public short? EndpointParamsId { get; set; }
public short? SqlConnectionId { get; set; }
public RestType? TypeId { get; set; }
public string? PreSql { get; set; }
public string? HeaderSql { get; set; }
public string? BodySql { get; set; }
public string? PostSql { get; set; }
public byte? ErrorActionId { get; set; }
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null)
{
return new InsertObjectProcedure
{
Entity = "ACTION",
Action = this
}.AddedBy(addedWho);
}
}

View File

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

View File

@@ -1,49 +0,0 @@
using MediatR;
using Microsoft.Extensions.Logging;
using ReC.Application.RecActions.Queries;
namespace ReC.Application.RecActions.Commands;
public record InvokeBatchRecActionsCommand : ReadRecActionQueryBase, IRequest;
public static class InvokeBatchRecActionsCommandExtensions
{
public static Task InvokeBatchRecAction(this ISender sender, long profileId, CancellationToken cancel = default)
=> sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId }, cancel);
}
public class InvokeRecActionsCommandHandler(ISender sender, 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 http = clientFactory.CreateClient();
using var semaphore = new SemaphoreSlim(5);
var tasks = actions.Select(async action =>
{
await semaphore.WaitAsync(cancel);
try
{
await sender.Send(action.ToInvokeCommand(), cancel);
}
catch(Exception ex)
{
logger?.LogError(
ex,
"Error invoking Rec action. ProfileId: {ProfileId}, Id: {Id}",
action.ProfileId,
action.Id
);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
}

View File

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

View File

@@ -0,0 +1,30 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.RecActions.Commands;
public record UpdateActionProcedure : IUpdateProcedure
{
public long? ProfileId { get; set; }
public bool? Active { get; set; }
public byte? Sequence { get; set; }
public long? EndpointId { get; set; }
public long? EndpointAuthId { get; set; }
public short? EndpointParamsId { get; set; }
public short? SqlConnectionId { get; set; }
public byte? TypeId { get; set; }
public string? PreSql { get; set; }
public string? HeaderSql { get; set; }
public string? BodySql { get; set; }
public string? PostSql { get; set; }
public byte? ErrorActionId { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ACTION",
Id = id,
Action = this
}.ChangedBy(changedWho);
}
}

View File

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

View File

@@ -1,36 +0,0 @@
using MediatR;
using DigitalData.Core.Abstraction.Application.Repository;
using ReC.Domain.Entities;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using DigitalData.Core.Exceptions;
using ReC.Application.Common.Dto;
namespace ReC.Application.RecActions.Queries;
public record ReadRecActionQueryBase
{
public long ProfileId { get; init; }
public ReadRecActionQuery ToReadQuery() => new(this);
}
public record ReadRecActionQuery : ReadRecActionQueryBase, IRequest<IEnumerable<RecActionDto>>
{
public ReadRecActionQuery(ReadRecActionQueryBase root) : base(root) { }
public ReadRecActionQuery() { }
}
public class ReadRecActionQueryHandler(IRepository<RecActionView> repo, IMapper mapper) : IRequestHandler<ReadRecActionQuery, IEnumerable<RecActionDto>>
{
public async Task<IEnumerable<RecActionDto>> Handle(ReadRecActionQuery request, CancellationToken cancel)
{
var actions = await repo.Where(x => x.ProfileId == request.ProfileId).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,39 @@
using MediatR;
using DigitalData.Core.Abstraction.Application.Repository;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using DigitalData.Core.Exceptions;
using ReC.Application.Common.Dto;
using ReC.Domain.Views;
namespace ReC.Application.RecActions.Queries;
public record ReadRecActionViewQuery : IRequest<IEnumerable<RecActionViewDto>>
{
public long? ProfileId { get; init; } = null;
public bool? Invoked { get; set; } = null;
}
public class ReadRecActionViewQueryHandler(IRepository<RecActionView> repo, IMapper mapper) : IRequestHandler<ReadRecActionViewQuery, IEnumerable<RecActionViewDto>>
{
public async Task<IEnumerable<RecActionViewDto>> Handle(ReadRecActionViewQuery request, CancellationToken cancel)
{
var query = repo.Query;
if (request.ProfileId is long profileId)
query = repo.Where(act => act.ProfileId == profileId);
if (request.Invoked is bool invoked)
query = invoked
? query.Where(act => act.Results!.Any())
: query.Where(act => !act.Results!.Any());
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<RecActionViewDto>>(actions);
}
}

View File

@@ -0,0 +1,32 @@
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Results.Commands;
public record DeleteResultProcedure : IDeleteProcedure
{
/// <summary>
/// Start GUID/ID (inclusive)
/// </summary>
public long Start { get; set; }
/// <summary>
/// End GUID/ID (inclusive). If 0, will be set to Start value.
/// </summary>
public long End { get; set; }
/// <summary>
/// Force parameter (not used for RESULT entity as it has no dependencies)
/// </summary>
public bool Force { get; set; }
public DeleteObjectProcedure ToObjectProcedure()
{
return new DeleteObjectProcedure
{
Entity = "RESULT",
Start = Start,
End = End,
Force = Force
};
}
}

View File

@@ -0,0 +1,20 @@
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Results.Commands;
public record InsertResultProcedure : IInsertProcedure
{
public long? ActionId { get; set; }
public short? StatusId { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public InsertObjectProcedure ToObjectProcedure(string? addedWho = null)
{
return new InsertObjectProcedure
{
Entity = "RESULT",
Result = this
}.AddedBy(addedWho ?? "Rec.API");
}
}

View File

@@ -0,0 +1,21 @@
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Results.Commands;
public record UpdateResultProcedure : IUpdateProcedure
{
public long? ActionId { get; set; }
public short? StatusId { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "RESULT",
Id = id,
Result = this
}.ChangedBy(changedWho);
}
}

View File

@@ -0,0 +1,49 @@
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.Views;
using System.Text.Json;
namespace ReC.Application.Results.Queries;
public record ReadResultViewQuery : IRequest<IEnumerable<ResultViewDto>>
{
public long? Id { get; init; } = null;
public long? ActionId { get; init; } = null;
public long? ProfileId { get; init; } = null;
}
public class ReadResultViewQueryHandler(IRepository<ResultView> repo, IMapper mapper) : IRequestHandler<ReadResultViewQuery, IEnumerable<ResultViewDto>>
{
public async Task<IEnumerable<ResultViewDto>> Handle(ReadResultViewQuery request, CancellationToken cancel)
{
var q = repo.Query;
if(request.Id is long id)
q = q.Where(rv => rv.Id == id);
if(request.ActionId is long actionId)
q = q.Where(rv => rv.ActionId == actionId);
if(request.ProfileId is long profileId)
q = q.Where(rv => rv.ProfileId == profileId);
var entities = await q.ToListAsync(cancel);
if (entities.Count == 0)
throw new NotFoundException($"No result views found for the given criteria. Criteria: {
JsonSerializer.Serialize(request, options: new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
})}"
);
return mapper.Map<IEnumerable<ResultViewDto>>(entities);
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides shared CRUD operations for API resources.
/// </summary>
public abstract class BaseCrudApi
{
/// <summary>
/// The HTTP client used to send requests.
/// </summary>
protected readonly HttpClient Http;
/// <summary>
/// The base resource path for the API endpoint.
/// </summary>
protected readonly string ResourcePath;
/// <summary>
/// Initializes a new instance of the <see cref="BaseCrudApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
/// <param name="resourcePath">The base resource path for the API endpoint.</param>
protected BaseCrudApi(HttpClient http, string resourcePath)
{
Http = http ?? throw new ArgumentNullException(nameof(http));
ResourcePath = resourcePath ?? throw new ArgumentNullException(nameof(resourcePath));
}
/// <summary>
/// Creates a resource.
/// </summary>
/// <typeparam name="T">The payload type.</typeparam>
/// <param name="payload">The payload to send.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The HTTP response message.</returns>
public Task<HttpResponseMessage> CreateAsync<T>(T payload, CancellationToken cancel = default)
=> Http.PostAsync(ResourcePath, ReCClientHelpers.ToJsonContent(payload), cancel);
/// <summary>
/// Updates a resource by identifier.
/// </summary>
/// <typeparam name="T">The payload type.</typeparam>
/// <param name="id">The resource identifier.</param>
/// <param name="payload">The payload to send.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The HTTP response message.</returns>
public Task<HttpResponseMessage> UpdateAsync<T>(long id, T payload, CancellationToken cancel = default)
=> Http.PutAsync($"{ResourcePath}/{id}", ReCClientHelpers.ToJsonContent(payload), cancel);
/// <summary>
/// Deletes resources with identifiers supplied in the payload.
/// </summary>
/// <typeparam name="T">The payload type containing identifiers.</typeparam>
/// <param name="payload">The payload to send.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The HTTP response message.</returns>
public Task<HttpResponseMessage> DeleteAsync<T>(T payload, CancellationToken cancel = default)
{
var request = new HttpRequestMessage(HttpMethod.Delete, ResourcePath)
{
Content = ReCClientHelpers.ToJsonContent(payload)
};
return Http.SendAsync(request, cancel);
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to common object endpoints.
/// </summary>
public class CommonApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="CommonApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public CommonApi(HttpClient http) : base(http, "api/Common")
{
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to endpoint authentication endpoints.
/// </summary>
public class EndpointAuthApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="EndpointAuthApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public EndpointAuthApi(HttpClient http) : base(http, "api/EndpointAuth")
{
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to endpoint parameter endpoints.
/// </summary>
public class EndpointParamsApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="EndpointParamsApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public EndpointParamsApi(HttpClient http) : base(http, "api/EndpointParams")
{
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to endpoint definitions.
/// </summary>
public class EndpointsApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="EndpointsApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public EndpointsApi(HttpClient http) : base(http, "api/Endpoints")
{
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to profile endpoints.
/// </summary>
public class ProfileApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="ProfileApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public ProfileApi(HttpClient http) : base(http, "api/Profile")
{
}
/// <summary>
/// Retrieves a profile by identifier.
/// </summary>
/// <param name="id">The profile identifier.</param>
/// <param name="includeActions">Whether to include related actions.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The HTTP response message.</returns>
public Task<HttpResponseMessage> GetAsync(long id, bool includeActions = false, CancellationToken cancel = default)
{
var query = ReCClientHelpers.BuildQuery(("Id", id), ("IncludeActions", includeActions));
return Http.GetAsync($"{ResourcePath}{query}", cancel);
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to RecAction endpoints.
/// </summary>
public class RecActionApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="RecActionApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public RecActionApi(HttpClient http) : base(http, "api/RecAction")
{
}
/// <summary>
/// Invokes a ReC action for the specified profile.
/// </summary>
/// <param name="profileId">The profile identifier.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns><see langword="true"/> if the request succeeds; otherwise, <see langword="false"/>.</returns>
public async Task<bool> InvokeAsync(int profileId, CancellationToken cancellationToken = default)
{
var resp = await Http.PostAsync($"{ResourcePath}/invoke/{profileId}", content: null, cancellationToken);
return resp.IsSuccessStatusCode;
}
/// <summary>
/// Retrieves Rec actions.
/// </summary>
/// <param name="profileId">Optional profile filter.</param>
/// <param name="invoked">Optional invoked filter.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The HTTP response message.</returns>
public Task<HttpResponseMessage> GetAsync(long? profileId = null, bool? invoked = null, CancellationToken cancel = default)
{
var query = ReCClientHelpers.BuildQuery(("ProfileId", profileId), ("Invoked", invoked));
return Http.GetAsync($"{ResourcePath}{query}", cancel);
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ReC.Client.Api
{
/// <summary>
/// Provides access to output result endpoints.
/// </summary>
public class ResultApi : BaseCrudApi
{
/// <summary>
/// Initializes a new instance of the <see cref="ResultApi"/> class.
/// </summary>
/// <param name="http">The HTTP client used for requests.</param>
public ResultApi(HttpClient http) : base(http, "api/Result")
{
}
/// <summary>
/// Retrieves results with optional filters.
/// </summary>
/// <param name="id">Optional result identifier.</param>
/// <param name="actionId">Optional action identifier.</param>
/// <param name="profileId">Optional profile identifier.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>The HTTP response message.</returns>
public Task<HttpResponseMessage> GetAsync(long? id = null, long? actionId = null, long? profileId = null, CancellationToken cancel = default)
{
var query = ReCClientHelpers.BuildQuery(("Id", id), ("ActionId", actionId), ("ProfileId", profileId));
return Http.GetAsync($"{ResourcePath}{query}", cancel);
}
}
}

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,37 @@
<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" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
</ItemGroup>
</Project>

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

@@ -0,0 +1,150 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using ReC.Client.Api;
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>
/// Provides access to RecAction endpoints.
/// </summary>
public RecActionApi RecActions { get; }
/// <summary>
/// Provides access to Result endpoints.
/// </summary>
public ResultApi Results { get; }
/// <summary>
/// Provides access to Profile endpoints.
/// </summary>
public ProfileApi Profiles { get; }
/// <summary>
/// Provides access to EndpointAuth endpoints.
/// </summary>
public EndpointAuthApi EndpointAuth { get; }
/// <summary>
/// Provides access to EndpointParams endpoints.
/// </summary>
public EndpointParamsApi EndpointParams { get; }
/// <summary>
/// Provides access to Endpoints endpoints.
/// </summary>
public EndpointsApi Endpoints { get; }
/// <summary>
/// Provides access to Common endpoints.
/// </summary>
public CommonApi Common { get; }
/// <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);
RecActions = new RecActionApi(_http);
Results = new ResultApi(_http);
Profiles = new ProfileApi(_http);
EndpointAuth = new EndpointAuthApi(_http);
EndpointParams = new EndpointParamsApi(_http);
Endpoints = new EndpointsApi(_http);
Common = new CommonApi(_http);
}
#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
}
/// <summary>
/// Specifies which part of the result to return for result view endpoints.
/// </summary>
public enum ResultType
{
/// <summary>
/// Returns both header and body.
/// </summary>
Full,
/// <summary>
/// Returns only the header portion of the result.
/// </summary>
OnlyHeader,
/// <summary>
/// Returns only the body portion of the result.
/// </summary>
OnlyBody
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http.Json;
#if NETFRAMEWORK
using System.Net.Http;
#endif
namespace ReC.Client
{
/// <summary>
/// Provides shared helpers for composing requests.
/// </summary>
internal static class ReCClientHelpers
{
#if NETFRAMEWORK
/// <summary>
/// Builds a query string from the provided key/value pairs, skipping null values.
/// </summary>
/// <param name="parameters">The key/value pairs to include in the query string.</param>
/// <returns>A query string beginning with '?', or an empty string if no values are provided.</returns>
public static string BuildQuery(params (string Key, object Value)[] parameters)
#else
/// <summary>
/// Builds a query string from the provided key/value pairs, skipping null values.
/// </summary>
/// <param name="parameters">The key/value pairs to include in the query string.</param>
/// <returns>A query string beginning with '?', or an empty string if no values are provided.</returns>
public static string BuildQuery(params (string Key, object? Value)[] parameters)
#endif
{
var parts = parameters
.Where(p => p.Value != null)
.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(Convert.ToString(p.Value, CultureInfo.InvariantCulture) ?? string.Empty)}");
var query = string.Join("&", parts);
return string.IsNullOrWhiteSpace(query) ? string.Empty : $"?{query}";
}
/// <summary>
/// Creates a JSON content payload from the provided object.
/// </summary>
/// <typeparam name="T">The type of the payload.</typeparam>
/// <param name="payload">The payload to serialize.</param>
/// <returns>A <see cref="JsonContent"/> instance ready for HTTP requests.</returns>
public static JsonContent ToJsonContent<T>(T payload) => JsonContent.Create(payload);
}
}

View File

@@ -0,0 +1,26 @@
#if NETFRAMEWORK
using System.Threading.Tasks;
#endif
namespace ReC.Client
{
/// <summary>
/// Provides synchronous wrappers for Task-based operations.
/// </summary>
public static class TaskSyncExtensions
{
/// <summary>
/// Blocks until the task completes and propagates any exception.
/// </summary>
/// <param name="task">The task to wait for.</param>
public static void Sync(this Task task) => task.ConfigureAwait(false).GetAwaiter().GetResult();
/// <summary>
/// Blocks until the task completes and returns its result, propagating any exception.
/// </summary>
/// <typeparam name="TResult">The type of the task result.</typeparam>
/// <param name="task">The task to wait for.</param>
/// <returns>The result of the completed task.</returns>
public static TResult Sync<TResult>(this Task<TResult> task) => task.ConfigureAwait(false).GetAwaiter().GetResult();
}
}

Some files were not shown because too many files have changed in this diff Show More