Compare commits

...

56 Commits

Author SHA1 Message Date
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
83 changed files with 1547 additions and 1024 deletions

28
ReC.sln
View File

@@ -20,6 +20,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
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
@@ -46,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}

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

@@ -1,5 +1,9 @@
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;
@@ -9,8 +13,51 @@ namespace ReC.API.Controllers;
public class ProfileController(IMediator mediator) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetProfile([FromQuery] ReadProfileViewQuery query, CancellationToken cancel)
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 StatusCode(StatusCodes.Status201Created, 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,7 +1,9 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
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;
@@ -71,7 +73,7 @@ public class RecActionController(IMediator mediator, IConfiguration config) : Co
/// <returns>An HTTP 201 Created response.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> CreateAction([FromBody] InsertActionProcedure command, CancellationToken cancel)
public async Task<IActionResult> Create([FromBody] InsertActionProcedure command, CancellationToken cancel)
{
await mediator.ExecuteInsertProcedure(command, config["AddedWho"], cancel);
@@ -79,33 +81,48 @@ public class RecActionController(IMediator mediator, IConfiguration config) : Co
}
/// <summary>
/// Deletes all RecActions associated with a specific profile.
/// Updates a RecAction via the ACTION update procedure.
/// </summary>
/// <param name="cmd"></param>
/// <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>An HTTP 204 No Content response upon successful deletion.</returns>
[HttpDelete]
/// <returns>No content on success.</returns>
[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Obsolete("Use the related procedure.")]
public async Task<IActionResult> Delete([FromQuery] DeleteRecActionsCommand cmd, CancellationToken cancel)
public async Task<IActionResult> Update([FromRoute] long id, [FromBody] UpdateActionProcedure procedure, CancellationToken cancel)
{
await mediator.Send(cmd, cancel);
await mediator.ExecuteUpdateProcedure(procedure, id, cancel: cancel);
return NoContent();
}
/// <summary>
/// Deletes all RecActions for a fake/test profile.
/// 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.ExecuteDeleteProcedure(procedure, cancel);
return NoContent();
}
/// <summary>
/// Deletes RecActions for a fake/test profile via the ACTION delete procedure.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An HTTP 204 No Content response upon successful deletion.</returns>
[HttpDelete("fake")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Obsolete("Use the related procedure.")]
public async Task<IActionResult> Delete(CancellationToken cancel)
{
await mediator.Send(new DeleteRecActionsCommand()
await mediator.ExecuteDeleteProcedure(new DeleteActionProcedure
{
ProfileId = config.GetFakeProfileId()
Start = config.GetFakeProfileId(),
End = config.GetFakeProfileId(),
Force = false
}, cancel);
return NoContent();

View File

@@ -2,14 +2,17 @@
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.API.Models;
using ReC.Application.OutResults.Commands;
using ReC.Application.OutResults.Queries;
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 OutResController(IMediator mediator, IConfiguration config) : ControllerBase
public class ResultController(IMediator mediator, IConfiguration config) : ControllerBase
{
/// <summary>
/// Gets output results based on the provided query parameters.
@@ -57,34 +60,67 @@ public class OutResController(IMediator mediator, IConfiguration config) : Contr
_ => Ok(res),
};
}
/// <summary>
/// Deletes output results based on the provided criteria.
/// Inserts a RESULT record via the insert procedure.
/// </summary>
/// <param name="command">The command containing the deletion criteria, such as ActionId or ProfileId.</param>
/// <param name="procedure">InsertResultProcedure payload.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An empty response indicating success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Obsolete("Use the related procedure or view.")]
public async Task<IActionResult> Delete([FromQuery] DeleteOutResCommand command, CancellationToken cancel)
/// <returns>The created RESULT identifier.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody] InsertResultProcedure procedure, CancellationToken cancel)
{
await mediator.Send(command, 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 all output results for a fake/test profile.
/// 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();
}
/// <summary>
/// Deletes RESULT records for a fake/test profile via the delete procedure.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An empty response indicating success.</returns>
/// <returns>No content on success.</returns>
[HttpDelete("fake")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Obsolete("Use the related procedure or view.")]
public async Task<IActionResult> Delete(CancellationToken cancel)
{
await mediator.Send(new DeleteOutResCommand() { ProfileId = config.GetFakeProfileId() }, cancel);
await mediator.ExecuteDeleteProcedure(new DeleteResultProcedure
{
Start = config.GetFakeProfileId(),
End = config.GetFakeProfileId(),
Force = false
}, cancel);
return NoContent();
}
}

View File

@@ -1,49 +0,0 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.API.Models;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.OutResults.Queries;
namespace ReC.API.Controllers;
[Route("api/[controller]")]
[ApiController]
public class ResultViewController(IMediator mediator, IConfiguration config) : ControllerBase
{
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromQuery] ReadResultViewQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, cancel));
[HttpGet("fake")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get(CancellationToken cancel) => Ok(await mediator.Send(new ReadResultViewQuery()
{
ProfileId = config.GetFakeProfileId()
}, cancel));
[HttpGet("fake/{actionId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromRoute] long actionId, CancellationToken cancel, ResultType resultType = ResultType.Full)
{
var res = (await mediator.Send(new ReadResultViewQuery()
{
ProfileId = config.GetFakeProfileId(),
ActionId = actionId
}, cancel)).First();
return resultType switch
{
ResultType.OnlyBody => res.Body is null ? NotFound() : Ok(res.Body.JsonToDynamic()),
ResultType.OnlyHeader => res.Header is null ? NotFound() : Ok(res.Header.JsonToDynamic()),
_ => Ok(res),
};
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] InsertResultProcedure procedure, CancellationToken cancel)
{
await mediator.Send(procedure, cancel);
return CreatedAtAction(nameof(Get), new { actionId = procedure.ActionId }, procedure);
}
}

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

@@ -1,5 +1,4 @@
using ReC.Domain.Entities;
using ReC.Domain.Views;
using ReC.Domain.Views;
namespace ReC.Application.Common.Dto;
@@ -8,16 +7,6 @@ public class DtoMappingProfile : AutoMapper.Profile
public DtoMappingProfile()
{
CreateMap<RecActionView, RecActionViewDto>();
CreateMap<OutRes, OutResDto>();
CreateMap<Connection, ConnectionDto>();
CreateMap<EndpointAuth, EndpointAuthDto>();
CreateMap<Endpoint, EndpointDto>();
CreateMap<EndpointParam, EndpointParamDto>();
CreateMap<Domain.Entities.Profile, ProfileDto>();
CreateMap<RecAction, RecActionDto>();
CreateMap<ResultView, ResultViewDto>();
CreateMap<ProfileView, ProfileViewDto>();
}

View File

@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
[Table("TBREC_CFG_ENDPOINT_AUTH")]
public record EndpointAuthDto
{
public long? Id { get; set; }

View File

@@ -2,7 +2,6 @@
namespace ReC.Application.Common.Dto;
[Table("TBREC_CFG_PROFILE", Schema = "dbo")]
public record ProfileDto
{
public long Id { get; set; }

View File

@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
[Table("TBREC_CFG_ACTION")]
public record RecActionDto
{
public long? Id { get; set; }

View File

@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using ReC.Domain.Entities;
using ReC.Domain.QueryOutput;
using ReC.Domain.Views;
@@ -8,37 +7,16 @@ namespace ReC.Application.Common.Interfaces;
public interface IRecDbContext
{
#region DbSets
[Obsolete("Use Views instead.")]
public DbSet<EndpointParam> EndpointParams { get; set; }
public DbSet<RecActionView> RecActionViews { get; set; }
public DbSet<ProfileView> ProfileViews { get; set; }
public DbSet<ResultView> RecResultViews { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<OutRes> OutRes { get; set; }
public DbSet<HeaderQueryResult> HeaderQueryResults { get; set; }
public DbSet<BodyQueryResult> BodyQueryResults { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<Connection> Connections { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<Endpoint> Endpoints { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<EndpointAuth> EndpointAuths { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<Domain.Entities.Profile> Profiles { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<RecAction> RecActions { get; set; }
public DbSet<InsertObjectResult> RecResults { get; set; }
#endregion DbSets

View File

@@ -2,6 +2,11 @@
using MediatR;
using Microsoft.Data.SqlClient;
using ReC.Application.Common.Exceptions;
using ReC.Application.EndpointAuth.Commands;
using ReC.Application.EndpointParams.Commands;
using ReC.Application.Endpoints.Commands;
using ReC.Application.Results.Commands;
using ReC.Application.Profile.Commands;
namespace ReC.Application.Common.Procedures.InsertProcedure;

View File

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

View File

@@ -2,6 +2,12 @@ using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using Microsoft.Data.SqlClient;
using ReC.Application.Common.Exceptions;
using ReC.Application.EndpointAuth.Commands;
using ReC.Application.EndpointParams.Commands;
using ReC.Application.Endpoints.Commands;
using ReC.Application.Results.Commands;
using ReC.Application.Profile.Commands;
using ReC.Application.RecActions.Commands;
namespace ReC.Application.Common.Procedures.UpdateProcedure;
@@ -15,7 +21,7 @@ public record UpdateObjectProcedure : IRequest<int>
/// <summary>
/// Target GUID to update (required)
/// </summary>
public long Guid { get; set; }
public long Id { get; set; }
internal string? ChangedWho { get; private set; }
@@ -35,9 +41,9 @@ public record UpdateObjectProcedure : IRequest<int>
public static class UpdateObjectProcedureExtensions
{
public static Task<int> ExecuteUpdateProcedure(this ISender sender, IUpdateProcedure procedure, long guid, string? changedWho = null, CancellationToken cancel = default)
public static Task<int> ExecuteUpdateProcedure(this ISender sender, IUpdateProcedure procedure, long id, string? changedWho = null, CancellationToken cancel = default)
{
return sender.Send(procedure.ToObjectProcedure(guid, changedWho ?? "ReC.API"), cancel);
return sender.Send(procedure.ToObjectProcedure(id, changedWho ?? "ReC.API"), cancel);
}
}
@@ -48,7 +54,7 @@ public class UpdateObjectProcedureHandler(IRepository repo) : IRequestHandler<Up
var parameters = new[]
{
new SqlParameter("@pENTITY", request.Entity ?? (object)DBNull.Value),
new SqlParameter("@pGUID", (object?)request.Guid ?? 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),

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.EndpointAuth.Commands;
public record DeleteEndpointAuthProcedure : IDeleteProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.EndpointAuth.Commands;
public record InsertEndpointAuthProcedure : IInsertProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.EndpointAuth.Commands;
public record UpdateEndpointAuthProcedure : IUpdateProcedure
{
@@ -14,12 +16,12 @@ public record UpdateEndpointAuthProcedure : IUpdateProcedure
public string? Domain { get; set; }
public string? Workstation { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long guid, string? changedWho = null)
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ENDPOINT_AUTH",
Guid = guid,
Id = id,
EndpointAuth = this
}.ChangedBy(changedWho);
}

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.EndpointParams.Commands;
public record DeleteEndpointParamsProcedure : IDeleteProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.EndpointParams.Commands;
public record InsertEndpointParamsProcedure : IInsertProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.EndpointParams.Commands;
public record UpdateEndpointParamsProcedure : IUpdateProcedure
{
@@ -9,12 +11,12 @@ public record UpdateEndpointParamsProcedure : IUpdateProcedure
public string? Key { get; set; }
public string? Value { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long guid, string? changedWho = null)
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ENDPOINT_PARAMS",
Guid = guid,
Id = id,
EndpointParams = this
}.ChangedBy(changedWho);
}

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Endpoints.Commands;
public record DeleteEndpointProcedure : IDeleteProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Endpoints.Commands;
public record InsertEndpointProcedure : IInsertProcedure
{

View File

@@ -1,29 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Application.Common.Dto;
using ReC.Domain.Entities;
namespace ReC.Application.Endpoints.Commands;
[Obsolete("Use the related procedure or view.")]
public class ObtainEndpointCommand : IRequest<EndpointDto>
{
public string Uri { get; init; } = null!;
}
[Obsolete("Use the related procedure or view.")]
public class ObtainEndpointCommandHandler(IRepository<Endpoint> repo, IMapper mapper) : IRequestHandler<ObtainEndpointCommand, EndpointDto>
{
public async Task<EndpointDto> Handle(ObtainEndpointCommand request, CancellationToken cancel)
{
var endpoint = await repo.Where(e => e.Uri == request.Uri).FirstOrDefaultAsync(cancel);
if (endpoint is not null)
return mapper.Map<EndpointDto>(endpoint);
endpoint = await repo.CreateAsync(request, cancel);
return mapper.Map<EndpointDto>(endpoint);
}
}

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Endpoints.Commands;
public record UpdateEndpointProcedure : IUpdateProcedure
{
@@ -6,12 +8,12 @@ public record UpdateEndpointProcedure : IUpdateProcedure
public string? Description { get; set; }
public string? Uri { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long guid, string? changedWho = null)
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ENDPOINT",
Guid = guid,
Id = id,
Endpoint = this
}.ChangedBy(changedWho);
}

View File

@@ -1,17 +0,0 @@
using ReC.Application.Endpoints.Commands;
using ReC.Application.Common.Dto;
namespace ReC.Application.Endpoints;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfile : AutoMapper.Profile
{
[Obsolete("Use the related procedure or view.")]
public MappingProfile()
{
CreateMap<ObtainEndpointCommand, EndpointDto>()
.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,30 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults.Commands;
[Obsolete("Use the related procedure or view.")]
public class CreateOutResCommand : IRequest
{
public required long ActionId { get; set; }
public short? Status { get; set; }
public string? Message { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
public string? AddedWho { get; set; }
}
[Obsolete("Use the related procedure or view.")]
public class CreateOutResCommandHandler(IRepository<OutRes> repo) : IRequestHandler<CreateOutResCommand>
{
public Task Handle(CreateOutResCommand request, CancellationToken cancel)
{
return repo.CreateAsync(request, cancel);
}
}

View File

@@ -1,28 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using ReC.Application.Common.Interfaces;
using ReC.Domain.Views;
using System.Text.Json.Serialization;
namespace ReC.Application.OutResults.Commands;
[Obsolete("Use the related procedure or view.")]
public class CreateResultViewCommand : IAuthScoped, IRequest
{
public required long ActionId { get; set; }
public required short StatusCode { get; set; }
public string? Header { get; set; }
public string? Body { get; set; }
[JsonIgnore]
public AuthScope Scope { get; } = new();
}
[Obsolete("Use the related procedure or view.")]
public class CreateResultViewCommandHandler(IRepository<ResultView> repo) : IRequestHandler<CreateResultViewCommand>
{
public Task Handle(CreateResultViewCommand request, CancellationToken cancel) => repo.CreateAsync(request, cancel);
}

View File

@@ -1,48 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults.Commands;
/// <summary>
/// Represents the command to delete output results based on specified criteria.
/// </summary>
/// <remarks>
/// Deletion can be performed by providing either an <see cref="ActionId"/> or a <see cref="ProfileId"/>.
/// At least one of these properties must be set for the operation to proceed.
/// </remarks>
[Obsolete("Use the related procedure or view.")]
public record DeleteOutResCommand : IRequest
{
/// <summary>
/// Gets the unique identifier for the action whose output results should be deleted.
/// </summary>
public long? ActionId { get; init; }
/// <summary>
/// Gets the unique identifier for the profile whose associated action's output results should be deleted.
/// </summary>
public long? ProfileId { get; init; }
}
/// <summary>
/// Handles the execution of the <see cref="DeleteOutResCommand"/>.
/// </summary>
[Obsolete("Use the related procedure or view.")]
public class DeleteOutResCommandHandler(IRepository<OutRes> repo) : IRequestHandler<DeleteOutResCommand>
{
/// <summary>
/// Processes the delete command by removing matching <see cref="OutRes"/> entities from the repository.
/// </summary>
/// <param name="request">The command containing the deletion criteria.</param>
/// <param name="cancel">A cancellation token that can be used to cancel the work.</param>
/// <returns>A task that represents the asynchronous delete operation.</returns>
/// <remarks>
/// The handler deletes records where <c>OutRes.ActionId</c> matches <see cref="DeleteOutResCommand.ActionId"/>
/// or where the associated <c>Action.ProfileId</c> matches <see cref="DeleteOutResCommand.ProfileId"/>.
/// </remarks>
public Task Handle(DeleteOutResCommand request, CancellationToken cancel)
{
return repo.DeleteAsync(x => x.ActionId == request.ActionId || x.Action!.ProfileId == request.ProfileId, cancel);
}
}

View File

@@ -1,15 +0,0 @@
using FluentValidation;
namespace ReC.Application.OutResults.Commands;
[Obsolete("Use the related procedure or view.")]
public class DeleteOutResCommandValidator : AbstractValidator<DeleteOutResCommand>
{
public DeleteOutResCommandValidator()
{
RuleFor(x => x)
.Must(x => x.ActionId.HasValue || x.ProfileId.HasValue)
.WithMessage("At least one of ActionId or ProfileId must be provided.")
.WithName("Identifier");
}
}

View File

@@ -1,20 +0,0 @@
using ReC.Application.OutResults.Commands;
using ReC.Domain.Entities;
using ReC.Domain.Views;
namespace ReC.Application.OutResults;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfiles : AutoMapper.Profile
{
[Obsolete("Use the related procedure or view.")]
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"));
CreateMap<CreateResultViewCommand, ResultView>()
.ForMember(e => e.AddedWhen, exp => exp.MapFrom(cmd => DateTime.UtcNow));
}
}

View File

@@ -1,39 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Application.Common.Dto;
using ReC.Domain.Entities;
namespace ReC.Application.OutResults.Queries;
[Obsolete("Use the related procedure or view.")]
public record ReadOutResQuery : IRequest<IEnumerable<OutResDto>>
{
public long? ProfileId { get; init; }
public long? ActionId { get; init; }
}
[Obsolete("Use the related procedure or view.")]
public class ReadOutResHandler(IRepository<OutRes> repo, IMapper mapper) : IRequestHandler<ReadOutResQuery, IEnumerable<OutResDto>>
{
public async Task<IEnumerable<OutResDto>> Handle(ReadOutResQuery request, CancellationToken cancel)
{
var q = repo.Query;
if(request.ActionId is long actionId)
q = q.Where(res => res.ActionId == actionId);
if(request.ProfileId is long profileId)
q = q.Where(res => res.Action!.ProfileId == profileId);
var resList = await q.ToListAsync(cancel);
if (resList.Count == 0)
throw new NotFoundException();
return mapper.Map<IEnumerable<OutResDto>>(resList);
}
}

View File

@@ -1,15 +0,0 @@
using FluentValidation;
namespace ReC.Application.OutResults.Queries;
[Obsolete("Use the related procedure or view.")]
public class ReadOutResQueryValidator : AbstractValidator<ReadOutResQuery>
{
public ReadOutResQueryValidator()
{
RuleFor(x => x)
.Must(x => x.ActionId.HasValue || x.ProfileId.HasValue)
.WithMessage("At least one of ActionId or ProfileId must be provided.")
.WithName("Identifier");
}
}

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Profile.Commands;
public record DeleteProfileProcedure : IDeleteProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Profile.Commands;
public record InsertProfileProcedure : IInsertProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Profile.Commands;
public record UpdateProfileProcedure : IUpdateProcedure
{
@@ -13,12 +15,12 @@ public record UpdateProfileProcedure : IUpdateProcedure
public DateTime? LastRun { get; set; }
public string? LastResult { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long guid, string? changedWho = null)
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "PROFILE",
Guid = guid,
Id = id,
Profile = this
}.ChangedBy(changedWho);
}

View File

@@ -1,47 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using ReC.Domain.Entities;
using ReC.Application.Endpoints.Commands;
namespace ReC.Application.RecActions.Commands;
[Obsolete("Use the related procedure or view.")]
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; }
}
[Obsolete("Use the related procedure or view.")]
public class CreateRecActionCommandHandler(ISender sender, IRepository<RecAction> repo) : IRequestHandler<CreateRecActionCommand>
{
public async Task Handle(CreateRecActionCommand request, CancellationToken cancel)
{
if(request.EndpointId is null)
if(request.EndpointUri is string endpointUri)
{
var endpoint = await sender.Send(new ObtainEndpointCommand { Uri = endpointUri }, cancel);
request.EndpointId = endpoint.Id;
}
else
throw new BadRequestException("Either EndpointId or EndpointUri must be provided.");
await repo.CreateAsync(request, cancel);
}
}

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.RecActions.Commands;
public record DeleteActionProcedure : IDeleteProcedure
{

View File

@@ -1,26 +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;
[Obsolete("Use the related procedure or view.")]
public class DeleteRecActionsCommand : IRequest
{
public required long ProfileId { get; init; }
}
[Obsolete("Use the related procedure or view.")]
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

@@ -5,7 +5,7 @@ using ReC.Application.Common.Constants;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.OutResults.Commands;
using ReC.Application.Results.Commands;
using ReC.Domain.Constants;
using System.Net;
using System.Net.Http.Headers;

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.RecActions.Commands;
public record UpdateActionProcedure : IUpdateProcedure
{
@@ -16,12 +18,12 @@ public record UpdateActionProcedure : IUpdateProcedure
public string? PostSql { get; set; }
public byte? ErrorActionId { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long guid, string? changedWho = null)
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "ACTION",
Guid = guid,
Id = id,
Action = this
}.ChangedBy(changedWho);
}

View File

@@ -1,17 +0,0 @@
using ReC.Application.Common.Dto;
using ReC.Application.RecActions.Commands;
namespace ReC.Application.RecActions;
// TODO: update to inject AddedWho from the current host/user contex
public class MappingProfile : AutoMapper.Profile
{
[Obsolete("Use the related procedure or view.")]
public MappingProfile()
{
CreateMap<CreateRecActionCommand, RecActionDto>()
.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,4 +1,6 @@
namespace ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.DeleteProcedure;
namespace ReC.Application.Results.Commands;
public record DeleteResultProcedure : IDeleteProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
namespace ReC.Application.Results.Commands;
public record InsertResultProcedure : IInsertProcedure
{

View File

@@ -1,4 +1,6 @@
namespace ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Application.Results.Commands;
public record UpdateResultProcedure : IUpdateProcedure
{
@@ -7,12 +9,12 @@ public record UpdateResultProcedure : IUpdateProcedure
public string? Header { get; set; }
public string? Body { get; set; }
public UpdateObjectProcedure ToObjectProcedure(long guid, string? changedWho = null)
public UpdateObjectProcedure ToObjectProcedure(long id, string? changedWho = null)
{
return new UpdateObjectProcedure
{
Entity = "RESULT",
Guid = guid,
Id = id,
Result = this
}.ChangedBy(changedWho);
}

View File

@@ -7,7 +7,7 @@ using ReC.Application.Common.Dto;
using ReC.Domain.Views;
using System.Text.Json;
namespace ReC.Application.OutResults.Queries;
namespace ReC.Application.Results.Queries;
public record ReadResultViewQuery : IRequest<IEnumerable<ResultViewDto>>
{

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

@@ -30,8 +30,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<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>

View File

@@ -1,11 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
#if NETFRAMEWORK
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
#endif
using ReC.Client.Api;
namespace ReC.Client
{
@@ -21,6 +17,41 @@ namespace ReC.Client
/// </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>
@@ -28,37 +59,13 @@ namespace ReC.Client
public ReCClient(IHttpClientFactory httpClientFactory)
{
_http = httpClientFactory.CreateClient(ClientName);
}
/// <summary>
/// Asynchronously invokes a ReC action for a specific profile.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/RecAction/invoke/{profileId}</c> endpoint.
/// </remarks>
/// <param name="profileId">The ID of the profile to invoke the action for.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation. The task result is <see langword="true"/> if the request was successful; otherwise, <see langword="false"/>.</returns>
public async Task<bool> InvokeRecActionAsync(int profileId, CancellationToken cancellationToken = default)
{
var resp = await _http.PostAsync($"api/RecAction/invoke/{profileId}", content: null, cancellationToken);
return resp.IsSuccessStatusCode;
}
/// <summary>
/// Synchronously invokes a ReC action for a specific profile.
/// </summary>
/// <remarks>
/// This method sends a POST request to the <c>api/RecAction/invoke/{profileId}</c> endpoint.
/// This is the synchronous version of <see cref="InvokeRecActionAsync(int, CancellationToken)"/>.
/// </remarks>
/// <param name="profileId">The ID of the profile to invoke the action for.</param>
/// <returns><see langword="true"/> if the request was successful; otherwise, <see langword="false"/>.</returns>
[Obsolete("Use InvokeRecActionAsync instead to avoid potential deadlocks and improve performance.")]
public bool InvokeRecAction(int profileId)
{
var resp = _http.PostAsync($"api/RecAction/invoke/{profileId}", content: null).GetAwaiter().GetResult();
return resp.IsSuccessStatusCode;
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
@@ -121,4 +128,23 @@ namespace ReC.Client
}
#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();
}
}

View File

@@ -1,36 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Obsolete("Use Views instead.")]
[Table("TBDD_CONNECTION")]
public class Connection
{
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,24 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Obsolete("Use Views instead.")]
[Table("TBREC_CFG_ENDPOINT")]
public class Endpoint
{
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,41 +0,0 @@
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Obsolete("Use Views instead.")]
[Table("TBREC_CFG_ENDPOINT_AUTH")]
public class EndpointAuth
{
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

@@ -1,35 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
/// <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>
[Obsolete("Use Views instead.")]
[Table("TBREC_CFG_ENDPOINT_PARAMS", Schema = "dbo")]
public class EndpointParam
{
public long? Id { get; set; }
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 string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,28 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Obsolete("Use Views instead.")]
[Table("TBREC_OUT_RESULT", Schema = "dbo")]
public class OutRes
{
public long? Id { get; set; }
public long? ActionId { get; set; }
public RecAction? Action { get; set; }
public short? Status { get; set; }
public string? Header { get; set; }
public string? Body { 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,32 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Obsolete("Use Views instead.")]
[Table("TBREC_CFG_PROFILE", Schema = "dbo")]
public class Profile
{
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

@@ -1,55 +0,0 @@
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Obsolete("Use Views instead.")]
[Table("TBREC_CFG_ACTION")]
public class RecAction
{
public long? Id { get; set; }
public long? ProfileId { get; set; }
public Profile? Profile { get; set; }
public bool? Active { get; set; }
public byte? Sequence { get; set; }
public long? EndpointId { get; set; }
public Endpoint? Endpoint { get; set; }
public long? EndpointAuthId { get; set; }
public EndpointAuth? EndpointAuth { get; set; }
public short? EndpointParamsId { get; set; }
public short? SqlConnectionId { get; set; }
public Connection? SqlConnection { get; set; }
public string? Type { get; set; }
public string? PreprocessingQuery { get; set; }
public string? HeaderQuery { get; set; }
public string? BodyQuery { get; set; }
public string? PostprocessingQuery { get; set; }
public ErrorAction? ErrorAction { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
public OutRes? OutRes { get; set; }
}

View File

@@ -1,5 +1,4 @@
using ReC.Domain.Constants;
using ReC.Domain.Entities;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Views;
@@ -19,10 +18,6 @@ public class RecActionView
public required long Id { get; set; }
[ForeignKey("Id")]
[Obsolete("Use the related procedure or view.")]
public RecAction? Root { get; set; }
public long? ProfileId { get; set; }
[ForeignKey("ProfileId")]
@@ -36,18 +31,10 @@ public class RecActionView
public long? EndpointId { get; set; }
[ForeignKey("EndpointId")]
[Obsolete("Use the related procedure or view.")]
public Endpoint? Endpoint { get; set; }
public string? EndpointUri { get; set; }
public long? EndpointAuthId { get; set; }
[ForeignKey("EndpointAuthId")]
[Obsolete("Use the related procedure or view.")]
public EndpointAuth? EndpointAuth { get; set; }
public EndpointAuthType? EndpointAuthType { get; set; }
public string? EndpointAuthTypeName { get; set; }
@@ -74,10 +61,6 @@ public class RecActionView
public short? SqlConnectionId { get; set; }
[ForeignKey("SqlConnectionId")]
[Obsolete("Use the related procedure or view.")]
public Connection? SqlConnection { get; set; }
public string? SqlConnectionServer { get; set; }
public string? SqlConnectionDb { get; set; }

View File

@@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using ReC.Application.Common.Interfaces;
using ReC.Domain.Entities;
using ReC.Domain.QueryOutput;
using ReC.Domain.Views;
@@ -9,37 +8,16 @@ namespace ReC.Infrastructure;
public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(options), IRecDbContext
{
#region DB Sets
[Obsolete("Use Views instead.")]
public DbSet<EndpointParam> EndpointParams { get; set; }
public DbSet<RecActionView> RecActionViews { get; set; }
public DbSet<ProfileView> ProfileViews { get; set; }
public DbSet<ResultView> RecResultViews { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<OutRes> OutRes { get; set; }
public DbSet<HeaderQueryResult> HeaderQueryResults { get; set; }
public DbSet<BodyQueryResult> BodyQueryResults { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<Connection> Connections { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<Endpoint> Endpoints { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<EndpointAuth> EndpointAuths { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<Profile> Profiles { get; set; }
[Obsolete("Use Views instead.")]
public DbSet<RecAction> RecActions { get; set; }
public DbSet<InsertObjectResult> RecResults { get; set; }
#endregion DB Sets
@@ -48,213 +26,6 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
{
base.OnModelCreating(modelBuilder);
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<Connection>(b =>
{
b.ToTable("TBDD_CONNECTION");
b.HasKey(e => e.Id);
b.Property(e => e.Id)
.HasColumnName("GUID")
.ValueGeneratedOnAdd();
b.Property(e => e.Bezeichnung).HasColumnName("BEZEICHNUNG");
b.Property(e => e.SqlProvider).HasColumnName("SQL_PROVIDER");
b.Property(e => e.Server).HasColumnName("SERVER");
b.Property(e => e.Datenbank).HasColumnName("DATENBANK");
b.Property(e => e.Username).HasColumnName("USERNAME");
b.Property(e => e.Password).HasColumnName("PASSWORD");
b.Property(e => e.Bemerkung).HasColumnName("BEMERKUNG");
b.Property(e => e.Aktiv).HasColumnName("AKTIV");
b.Property(e => e.ErstelltWer).HasColumnName("ERSTELLTWER");
b.Property(e => e.ErstelltWann).HasColumnName("ERSTELLTWANN");
b.Property(e => e.GeandertWer).HasColumnName("GEANDERTWER");
b.Property(e => e.GeaendertWann).HasColumnName("GEAENDERTWANN");
b.Property(e => e.SysConnection).HasColumnName("SYS_CONNECTION");
});
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<Endpoint>(b =>
{
b.ToTable("TBREC_CFG_ENDPOINT");
b.HasKey(e => e.Id);
b.Property(e => e.Id)
.HasColumnName("GUID")
.ValueGeneratedOnAdd();
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.Description).HasColumnName("DESCRIPTION");
b.Property(e => e.Uri).HasColumnName("URI");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
});
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<EndpointAuth>(b =>
{
b.ToTable("TBREC_CFG_ENDPOINT_AUTH");
b.HasKey(e => e.Id);
b.Property(e => e.Id)
.HasColumnName("GUID")
.ValueGeneratedOnAdd();
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.Description).HasColumnName("DESCRIPTION");
b.Property(e => e.Type).HasColumnName("TYPE");
b.Property(e => e.ApiKey).HasColumnName("API_KEY");
b.Property(e => e.ApiValue).HasColumnName("API_VALUE");
b.Property(e => e.ApiKeyAddTo).HasColumnName("API_KEY_ADD_TO");
b.Property(e => e.Token).HasColumnName("TOKEN");
b.Property(e => e.Username).HasColumnName("USERNAME");
b.Property(e => e.Password).HasColumnName("PASSWORD");
b.Property(e => e.Domain).HasColumnName("DOMAIN");
b.Property(e => e.Workstation).HasColumnName("WORKSTATION");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
});
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<EndpointParam>(b =>
{
b.ToTable("TBREC_CFG_ENDPOINT_PARAMS", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id).HasColumnName("GUID");
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.Description).HasColumnName("DESCRIPTION");
b.Property(e => e.GroupId).HasColumnName("GROUP_ID");
b.Property(e => e.Sequence).HasColumnName("SEQUENCE");
b.Property(e => e.Key).HasColumnName("KEY");
b.Property(e => e.Value).HasColumnName("VALUE");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
});
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<OutRes>(b =>
{
b.ToTable("TBREC_OUT_RESULT", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id)
.HasColumnName("GUID")
.ValueGeneratedOnAdd();
b.Property(e => e.ActionId).HasColumnName("ACTION_ID");
b.Property(e => e.Status).HasColumnName("STATUS_ID");
b.Property(e => e.Header).HasColumnName("RESULT_HEADER");
b.Property(e => e.Body).HasColumnName("RESULT_BODY");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
});
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<Profile>(b =>
{
b.ToTable("TBREC_CFG_PROFILE", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id)
.HasColumnName("GUID")
.ValueGeneratedOnAdd();
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.Type).HasColumnName("TYPE");
b.Property(e => e.Mandantor).HasColumnName("MANDANTOR");
b.Property(e => e.Name).HasColumnName("PROFILE_NAME");
b.Property(e => e.Description).HasColumnName("DESCRIPTION");
b.Property(e => e.LogLevel).HasColumnName("LOG_LEVEL");
b.Property(e => e.Language).HasColumnName("LANGUAGE");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
});
#pragma warning restore CS0618 // Type or member is obsolete
modelBuilder.Entity<ProfileView>(b =>
{
b.ToView("VWREC_PROFILE", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id).HasColumnName("PROFILE_GUID");
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.TypeId).HasColumnName("TYPE_ID");
b.Property(e => e.Type).HasColumnName("TYPE");
b.Property(e => e.Mandantor).HasColumnName("MANDANTOR");
b.Property(e => e.ProfileName).HasColumnName("PROFILE_NAME");
b.Property(e => e.Description).HasColumnName("DESCRIPTION");
b.Property(e => e.LogLevelId).HasColumnName("LOG_LEVEL_ID");
b.Property(e => e.LogLevel).HasColumnName("LOG_LEVEL");
b.Property(e => e.LanguageId).HasColumnName("LANGUAGE_ID");
b.Property(e => e.Language).HasColumnName("LANGUAGE");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
b.Property(e => e.FirstRun).HasColumnName("FIRST_RUN");
b.Property(e => e.LastRun).HasColumnName("LAST_RUN");
b.Property(e => e.LastResult).HasColumnName("LAST_RESULT");
b.HasMany(e => e.Actions)
.WithOne(a => a.Profile)
.HasForeignKey(a => a.ProfileId);
});
#pragma warning disable CS0618 // Type or member is obsolete
modelBuilder.Entity<RecAction>(b =>
{
b.ToTable("TBREC_CFG_ACTION");
b.HasKey(e => e.Id);
b.Property(e => e.Id).HasColumnName("GUID");
b.Property(e => e.ProfileId).HasColumnName("PROFILE_ID");
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.Sequence).HasColumnName("SEQUENCE");
b.Property(e => e.EndpointId).HasColumnName("ENDPOINT_ID");
b.Property(e => e.EndpointAuthId).HasColumnName("ENDPOINT_AUTH_ID");
b.Property(e => e.EndpointParamsId).HasColumnName("ENDPOINT_PARAMS_ID");
b.Property(e => e.SqlConnectionId).HasColumnName("SQL_CONNECTION_ID");
b.Property(e => e.Type).HasColumnName("TYPE");
b.Property(e => e.PreprocessingQuery).HasColumnName("PREPROCESSING_QUERY");
b.Property(e => e.HeaderQuery).HasColumnName("HEADER_QUERY");
b.Property(e => e.BodyQuery).HasColumnName("BODY_QUERY");
b.Property(e => e.PostprocessingQuery).HasColumnName("POSTPROCESSING_QUERY");
b.Property(e => e.ErrorAction).HasColumnName("ERROR_ACTION");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
b.HasOne(act => act.OutRes)
.WithOne(res => res.Action)
.HasForeignKey<OutRes>(res => res.ActionId)
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore CS0618 // Type or member is obsolete
modelBuilder.Entity<RecActionView>(b =>
{
b.ToView("VWREC_ACTION", "dbo");
@@ -299,6 +70,30 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
.HasForeignKey(r => r.ActionId);
});
modelBuilder.Entity<ProfileView>(b =>
{
b.ToView("VWREC_PROFILE", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id).HasColumnName("PROFILE_GUID");
b.Property(e => e.Active).HasColumnName("ACTIVE");
b.Property(e => e.TypeId).HasColumnName("TYPE_ID");
b.Property(e => e.Type).HasColumnName("TYPE");
b.Property(e => e.Mandantor).HasColumnName("MANDANTOR");
b.Property(e => e.ProfileName).HasColumnName("PROFILE_NAME");
b.Property(e => e.Description).HasColumnName("DESCRIPTION");
b.Property(e => e.LogLevelId).HasColumnName("LOG_LEVEL_ID");
b.Property(e => e.LogLevel).HasColumnName("LOG_LEVEL");
b.Property(e => e.LanguageId).HasColumnName("LANGUAGE_ID");
b.Property(e => e.Language).HasColumnName("LANGUAGE");
b.Property(e => e.AddedWho).HasColumnName("ADDED_WHO");
b.Property(e => e.AddedWhen).HasColumnName("ADDED_WHEN");
b.Property(e => e.ChangedWho).HasColumnName("CHANGED_WHO");
b.Property(e => e.ChangedWhen).HasColumnName("CHANGED_WHEN");
b.Property(e => e.FirstRun).HasColumnName("FIRST_RUN");
b.Property(e => e.LastRun).HasColumnName("LAST_RUN");
b.Property(e => e.LastResult).HasColumnName("LAST_RESULT");
});
modelBuilder.Entity<ResultView>(b =>
{
b.ToView("VWREC_RESULT", "dbo");

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.EndpointAuth.Commands;
using ReC.Tests.Application;
namespace ReC.Tests.Application.EndpointAuth;
[TestFixture]
public class EndpointAuthProcedureTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task InsertEndpointAuthProcedure_runs_via_mediator()
{
var procedure = new InsertEndpointAuthProcedure { Active = true, Description = "auth", TypeId = 1, ApiKey = "key", ApiValue = "value" };
var objectProc = procedure.ToObjectProcedure("ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task UpdateEndpointAuthProcedure_runs_via_mediator()
{
var procedure = new UpdateEndpointAuthProcedure { Active = false, Description = "auth-update", TypeId = 2 };
var objectProc = procedure.ToObjectProcedure(15, "ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task DeleteEndpointAuthProcedure_runs_via_mediator()
{
var procedure = new DeleteEndpointAuthProcedure { Start = 3, End = 4, Force = false };
var objectProc = procedure.ToObjectProcedure();
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.EndpointParams.Commands;
using ReC.Tests.Application;
namespace ReC.Tests.Application.EndpointParams;
[TestFixture]
public class EndpointParamsProcedureTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task InsertEndpointParamsProcedure_runs_via_mediator()
{
var procedure = new InsertEndpointParamsProcedure { Active = true, Description = "param", GroupId = 1, Sequence = 1, Key = "k", Value = "v" };
var objectProc = procedure.ToObjectProcedure("ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task UpdateEndpointParamsProcedure_runs_via_mediator()
{
var procedure = new UpdateEndpointParamsProcedure { Active = false, Description = "param-update", GroupId = 2, Sequence = 2, Key = "k2", Value = "v2" };
var objectProc = procedure.ToObjectProcedure(25, "ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task DeleteEndpointParamsProcedure_runs_via_mediator()
{
var procedure = new DeleteEndpointParamsProcedure { Start = 5, End = 6, Force = true };
var objectProc = procedure.ToObjectProcedure();
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.Endpoints.Commands;
using ReC.Tests.Application;
namespace ReC.Tests.Application.Endpoints;
[TestFixture]
public class EndpointProcedureTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task InsertEndpointProcedure_runs_via_mediator()
{
var procedure = new InsertEndpointProcedure { Active = true, Description = "desc", Uri = "http://example" };
var objectProc = procedure.ToObjectProcedure("ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task UpdateEndpointProcedure_runs_via_mediator()
{
var procedure = new UpdateEndpointProcedure { Active = false, Description = "updated", Uri = "http://updated" };
var objectProc = procedure.ToObjectProcedure(12, "ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task DeleteEndpointProcedure_runs_via_mediator()
{
var procedure = new DeleteEndpointProcedure { Start = 1, End = 2, Force = true };
var objectProc = procedure.ToObjectProcedure();
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,58 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
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.Tests.Application;
namespace ReC.Tests.Application.Procedures;
[TestFixture]
public class ProcedureExecutionTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task ExecuteInsertProcedure_runs_with_addedWho()
{
var procedure = new InsertProfileProcedure { Name = "name" };
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.ExecuteInsertProcedure(procedure, "ReC.Tests");
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task ExecuteUpdateProcedure_runs_with_changedWho()
{
var procedure = new UpdateProfileProcedure { Name = "updated" };
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.ExecuteUpdateProcedure(procedure, 123, "ReC.Tests");
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task ExecuteDeleteProcedure_runs()
{
var procedure = new DeleteProfileProcedure { Start = 1, End = 2, Force = true };
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.ExecuteDeleteProcedure(procedure);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
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.Tests.Application;
namespace ReC.Tests.Application.Profile;
[TestFixture]
public class ProfileProcedureTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task InsertProfileProcedure_runs_via_mediator()
{
var procedure = new InsertProfileProcedure { Active = true, TypeId = 1, Name = "name", Mandantor = "man" };
var objectProc = procedure.ToObjectProcedure("ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task UpdateProfileProcedure_runs_via_mediator()
{
var procedure = new UpdateProfileProcedure { Active = false, TypeId = 2, Name = "updated", Mandantor = "man2" };
var objectProc = procedure.ToObjectProcedure(45, "ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task DeleteProfileProcedure_runs_via_mediator()
{
var procedure = new DeleteProfileProcedure { Start = 9, End = 10, Force = false };
var objectProc = procedure.ToObjectProcedure();
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,51 @@
using System.Linq;
using System.Threading.Tasks;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.Profile.Queries;
using ReC.Tests.Application;
namespace ReC.Tests.Application.Profile;
[TestFixture]
public class ProfileQueryTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task ReadProfileViewQuery_returns_profile_from_database()
{
var profileId = Configuration.GetValue<long?>("FakeProfileId");
Assert.That(profileId, Is.Not.Null.And.GreaterThan(0), "FakeProfileId must be configured in appsettings.json");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
try
{
var profiles = await sender.Send(new ReadProfileViewQuery
{
Id = profileId,
IncludeActions = false
});
var profile = profiles.Single();
Assert.That(profile.Id, Is.EqualTo(profileId));
Assert.That(profile.ProfileName, Is.Not.Null.And.Not.Empty);
Assert.That(profile.Active, Is.True);
}
catch (NotFoundException)
{
Assert.Pass("NotFound is acceptable when profile does not exist");
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.Common.Procedures.DeleteProcedure;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
using ReC.Application.RecActions.Commands;
using ReC.Tests.Application;
namespace ReC.Tests.Application.RecActions;
[TestFixture]
public class RecActionProcedureTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task InsertActionProcedure_runs_via_mediator()
{
var procedure = new InsertActionProcedure { ProfileId = 1, Active = true, Sequence = 1 };
var objectProc = procedure.ToObjectProcedure("ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task UpdateActionProcedure_runs_via_mediator()
{
var procedure = new UpdateActionProcedure { ProfileId = 2, Active = false, Sequence = 2 };
var objectProc = procedure.ToObjectProcedure(35, "ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task DeleteActionProcedure_runs_via_mediator()
{
var procedure = new DeleteActionProcedure { Start = 7, End = 8, Force = true };
var objectProc = procedure.ToObjectProcedure();
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,62 @@
using System.Linq;
using System.Threading.Tasks;
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.RecActions.Queries;
using ReC.Tests.Application;
namespace ReC.Tests.Application.RecActions;
[TestFixture]
public class RecActionQueryTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task ReadRecActionViewQuery_returns_actions_for_profile()
{
var profileId = Configuration.GetValue<long?>("FakeProfileId");
Assert.That(profileId, Is.Not.Null.And.GreaterThan(0), "FakeProfileId must be configured in appsettings.json");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
try
{
var actions = await sender.Send(new ReadRecActionViewQuery
{
ProfileId = profileId
});
Assert.That(actions, Is.Not.Empty);
Assert.That(actions.All(a => a.ProfileId == profileId));
}
catch (NotFoundException)
{
Assert.Pass("NotFound is acceptable when test data is unavailable");
}
}
[Test]
public void ReadRecActionViewQuery_with_unknown_profile_throws_not_found()
{
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var invalidProfileId = long.MaxValue;
Assert.ThrowsAsync<NotFoundException>(async () =>
await sender.Send(new ReadRecActionViewQuery
{
ProfileId = invalidProfileId
}));
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ReC.Application;
using ReC.Application.Common.Options;
using ReC.Infrastructure;
namespace ReC.Tests.Application;
public abstract class RecApplicationTestBase : IDisposable
{
protected RecApplicationTestBase()
{
Configuration = BuildConfiguration();
ServiceProvider = BuildServiceProvider(Configuration);
}
protected IConfiguration Configuration { get; }
protected IServiceProvider ServiceProvider { get; }
private static IConfiguration BuildConfiguration()
{
var appSettingsPath = LocateApiAppSettings();
return new ConfigurationBuilder()
.AddJsonFile(appSettingsPath, optional: false, reloadOnChange: false)
.Build();
}
private static IServiceProvider BuildServiceProvider(IConfiguration configuration)
{
var services = new ServiceCollection();
services.AddSingleton(configuration);
services.AddRecServices(options =>
{
options.LuckyPennySoftwareLicenseKey = configuration["LuckyPennySoftwareLicenseKey"];
options.ConfigureRecActions(configuration.GetSection("RecAction"));
});
services.AddRecInfrastructure(opt =>
{
opt.ConfigureDbContext((_, builder) => builder.UseSqlServer(configuration.GetConnectionString("Default")));
});
return services.BuildServiceProvider();
}
private static string LocateApiAppSettings()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
var candidate = Path.Combine(current.FullName, "src", "ReC.API", "appsettings.json");
if (File.Exists(candidate))
return candidate;
current = current.Parent;
}
throw new FileNotFoundException("Could not locate src/ReC.API/appsettings.json from test base directory.");
}
public void Dispose()
{
if (ServiceProvider is IDisposable disposable)
disposable.Dispose();
}
}

View File

@@ -0,0 +1,61 @@
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
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.Tests.Application;
namespace ReC.Tests.Application.Results;
[TestFixture]
public class ResultProcedureTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task InsertResultProcedure_runs_via_mediator()
{
var procedure = new InsertResultProcedure { ActionId = 1, StatusId = 200, Header = "h", Body = "b" };
var objectProc = procedure.ToObjectProcedure("ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.GreaterThan(0));
}
[Test]
public async Task UpdateResultProcedure_runs_via_mediator()
{
var procedure = new UpdateResultProcedure { ActionId = 2, StatusId = 500, Header = "h2", Body = "b2" };
var objectProc = procedure.ToObjectProcedure(55, "ReC.Tests");
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
[Test]
public async Task DeleteResultProcedure_runs_via_mediator()
{
var procedure = new DeleteResultProcedure { Start = 11, End = 12, Force = false };
var objectProc = procedure.ToObjectProcedure();
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var result = await sender.Send(objectProc);
Assert.That(result, Is.EqualTo(0));
}
}

View File

@@ -0,0 +1,42 @@
using DigitalData.Core.Exceptions;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using ReC.Application.Results.Queries;
using ReC.Tests.Application;
namespace ReC.Tests.Application.Results;
[TestFixture]
public class ResultQueryTests : RecApplicationTestBase
{
private (ISender Sender, IServiceScope Scope) CreateScopedSender()
{
var scope = ServiceProvider.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
return (sender, scope);
}
[Test]
public async Task ReadResultViewQuery_with_unknown_action_allows_not_found()
{
var (sender, scope) = CreateScopedSender();
using var _ = scope;
var invalidActionId = long.MaxValue;
try
{
var results = await sender.Send(new ReadResultViewQuery
{
ActionId = invalidActionId
});
Assert.That(results, Is.Empty);
}
catch (NotFoundException)
{
Assert.Pass("NotFound is acceptable for unknown action");
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Reflection;
using ReC.Application.Common.Procedures.InsertProcedure;
using ReC.Application.Common.Procedures.UpdateProcedure;
namespace ReC.Tests.Application.Shared;
internal static class ProcedureObjectHelper
{
public static string? GetAddedWho(InsertObjectProcedure procedure)
{
return (string?)typeof(InsertObjectProcedure)
.GetProperty("AddedWho", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?
.GetValue(procedure);
}
public static string? GetChangedWho(UpdateObjectProcedure procedure)
{
return (string?)typeof(UpdateObjectProcedure)
.GetProperty("ChangedWho", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?
.GetValue(procedure);
}
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ReC.Application\ReC.Application.csproj" />
<ProjectReference Include="..\..\src\ReC.Infrastructure\ReC.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\ReC.Domain\ReC.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>