Compare commits

...

131 Commits

Author SHA1 Message Date
39fcee2b50 Add ResultViewController for output result retrieval
Introduce ResultViewController with endpoints to fetch output results by query, for fake/test profiles, and by action ID. Supports returning full, header, or body parts of results via a new ViewResultType enum. Utilizes MediatR and configuration injection for flexible and testable result access. Includes XML docs for improved API clarity.
2025-12-16 12:51:17 +01:00
d0597e28e8 Mark OutResController as obsolete with deprecation notice
Added [Obsolete] attribute to OutResController, indicating it is deprecated and will be removed in future versions. Developers are advised to use ResultViewController instead.
2025-12-15 17:13:05 +01:00
d8d77652ac Implement ReadResultViewQuery handler with filtering
Added ReadResultViewQueryHandler to support querying ResultView entities with optional filters (Id, ActionId, ProfileId). Integrated repository and AutoMapper, included error handling for no results, and improved using directives.
2025-12-15 17:11:23 +01:00
62612897bd Add ReadResultViewQuery and clean up DTO namespace
Introduced ReadResultViewQuery for querying result views with optional filters (Id, ActionId, ProfileId). Cleaned up ResultViewDto.cs by removing an unused using directive and reformatting the namespace declaration. Added necessary usings to support the new query.
2025-12-15 17:05:49 +01:00
99c50fb348 Make ResultViewDto immutable; add Root property
Refactored ResultViewDto to use init-only properties for immutability. Added a new Root property of type OutResDto?. Included a using directive for ReC.Domain.Entities to support the new property.
2025-12-15 17:02:11 +01:00
8aaf11f39d Add Root navigation property to ResultView with FK
Added OutRes? Root navigation property to ResultView, annotated with [ForeignKey("Id")], to establish an optional relationship to the OutRes entity. Also added the necessary using directive for DataAnnotations.Schema.
2025-12-15 17:01:35 +01:00
152189fefd Refactor: Rename RecResultViewDto to ResultViewDto
Replaced all usage of RecResultViewDto with ResultViewDto for improved naming consistency. Updated AutoMapper profile to map ResultView to ResultViewDto. Removed RecResultViewDto.cs and added ResultViewDto.cs with the same properties. No functional changes.
2025-12-15 16:57:05 +01:00
5a4b8427be Rename RecResultView entity to ResultView throughout codebase
Replaced RecResultView with ResultView, updating all references in DbContext, entity mapping, and AutoMapper profiles. Added the new ResultView class and removed the old RecResultView class. No changes to properties or structure.
2025-12-15 16:56:37 +01:00
e4a644a636 Add mappings for RecResultView and ProfileView DTOs
Added AutoMapper profile mappings for RecResultView to RecResultViewDto and ProfileView to ProfileViewDto to enable automatic entity-to-DTO conversions.
2025-12-15 16:55:45 +01:00
25c6c41b26 Add RecResultViewDto for result view data representation
Introduced RecResultViewDto in the ReC.Application.Common.Dto namespace. This DTO encapsulates properties for result entity view data, including related action and profile details, status, content, and audit information.
2025-12-15 16:53:25 +01:00
c672a10c97 Add navigation properties to RecResultView class
Added Action and Profile navigation properties to RecResultView, enabling direct access to related RecActionView and ProfileView data for improved object relationships and easier data retrieval in views or APIs.
2025-12-15 16:45:46 +01:00
1e21218f31 Make RecResultView properties nullable for flexibility
Changed ActionId, ProfileId, StatusCode, AddedWho, and AddedWhen
to nullable types in RecResultView to better support optional or
missing data scenarios. This improves compatibility with cases
where these fields may not always have values.
2025-12-15 16:43:39 +01:00
68e7ee54f9 Rename StatusId/Status to StatusCode/StatusName for clarity
Renamed the StatusId property to StatusCode and Status to StatusName in RecResultView. Updated RecDbContext mappings accordingly to maintain consistency and improve code clarity.
2025-12-15 16:42:01 +01:00
2a749267b3 Rename ResultHeader/ResultBody to Header/Body in RecResultView
Renamed the ResultHeader and ResultBody properties in the RecResultView class to Header and Body. Updated the RecDbContext entity configuration to map these new property names to the existing RESULT_HEADER and RESULT_BODY database columns. No changes were made to the database schema.
2025-12-15 16:35:15 +01:00
edc1de2034 Add RecResultView entity and DbSets to RecDbContext
Added DbSet properties for RecResultView and OutRes to RecDbContext. Configured entity mapping for RecResultView to the VWREC_RESULT view, including key and property-to-column mappings. This enables querying RecResultView and OutRes through the context.
2025-12-15 16:33:57 +01:00
ae79a60605 Add RecResultView entity for recommendation results
Introduced the RecResultView class in the ReC.Domain.Entities namespace. This class encapsulates properties related to recommendation results, including identifiers, status, result details, and audit metadata such as who added or changed the result and when.
2025-12-15 16:33:41 +01:00
6b8286a386 Move logging config to appsettings.Logging.json
Extracted "Logging" and "NLog" sections from appsettings.json into a new appsettings.Logging.json file. No changes were made to the logging settings themselves; this improves configuration separation and maintainability.
2025-12-15 16:15:01 +01:00
1fabc29e4f Auto-load appsettings.*.json files at startup
Dynamically adds all appsettings.*.json files in the root directory to the configuration, excluding appsettings.Development.json and appsettings.migration.json. This streamlines configuration management by automatically including relevant environment or custom config files.
2025-12-15 15:51:31 +01:00
56fb34d987 Update Status property mapping to STATUS_ID column
Changed the database column mapping for the Status property from "STATUS" to "STATUS_ID" to ensure correct association with the updated schema.
2025-12-15 15:31:30 +01:00
47ddde239e Update ProfileType mapping to PROFILE_TYPE_ID column
Changed the entity configuration to map the ProfileType property to the "PROFILE_TYPE_ID" column instead of "PROFILE_TYPE" for consistency with database schema.
2025-12-15 15:23:32 +01:00
5a4d2d8553 Update profile type fields to use ProfileType enum
Replaced byte/byte? with the strongly-typed ProfileType/ProfileType? enum for profile type fields in ProfileView and RecActionView. Added the necessary using directive for ReC.Domain.Constants. This improves type safety and code readability.
2025-12-15 15:07:47 +01:00
4e209e29fc Refactor ReadRecActionViewQuery for optional ProfileId
Refactored ReadRecActionViewQuery to remove base class inheritance and make ProfileId optional. Simplified query construction and filtering logic in the handler to conditionally filter by ProfileId only when provided. Removed unused constructors and methods for a cleaner API.
2025-12-15 15:07:26 +01:00
78aaea67e6 Refactor InvokeBatchRecActionViewsCommand structure
Decouple InvokeBatchRecActionViewsCommand from ReadRecActionQueryBase, making it a plain IRequest with an explicit ProfileId property. Update the extension and handler to use the new structure, improving clarity and separation of concerns.
2025-12-15 15:06:46 +01:00
98261f4e21 Refactor ProfileType to enum in RecActionViewDto
Changed ProfileType from string? to ProfileType? enum for improved type safety. Updated URI scheme assignment to use ToUriBuilderScheme(), centralizing scheme mapping logic.
2025-12-15 15:05:56 +01:00
aab8174500 Add ProfileType enum and extension for URI scheme
Introduced the ProfileType enum with Http and Https values in the ReC.Domain.Constants namespace. Added ProfileTypeExtensions with a ToUriBuilderScheme method to convert enum values to their lowercase string representations for URI building.
2025-12-15 15:05:10 +01:00
fb6d6af12b Change ProfileType to byte? in RecActionView
Updated the ProfileType property in RecActionView from string? to byte? to improve type safety and performance by using a numeric value instead of a string.
2025-12-15 14:37:57 +01:00
f82f4d2c65 Refactor RestType enum: remove Invalid, use byte
RestType now inherits from byte for efficiency. The Invalid value has been removed from the enum, and the IsValid extension method was updated to reflect this change by only checking for None.
2025-12-15 14:35:26 +01:00
90ee3f6a5d Explicitly set ErrorAction enum underlying type to byte
Changed ErrorAction enum declaration to use byte as its underlying type for improved memory efficiency and clarity.
2025-12-15 14:28:13 +01:00
a24ec1ab3e Set ApiKeyLocation enum underlying type to byte
Changed ApiKeyLocation enum to use byte as its underlying type instead of the default int, reducing memory usage and improving clarity for serialization scenarios.
2025-12-15 14:27:45 +01:00
b8c30d520e Set EndpointAuthType enum underlying type to byte
Explicitly define EndpointAuthType as a byte-based enum for improved memory efficiency and better interoperability with systems requiring a specific underlying type.
2025-12-15 14:22:13 +01:00
f69f323542 Make ProfileView string properties nullable
Changed several ProfileView properties (Type, Mandantor, ProfileName, LogLevel, Language, AddedWho) from non-nullable strings with default values to nullable strings. This allows these fields to be null instead of defaulting to an empty string.
2025-12-15 14:03:15 +01:00
3621820060 Refactor RecActionView Profile property; add ProfileViewDto
Changed RecActionView.Profile to use ProfileView type instead of Profile entity. Added new ProfileViewDto class to represent profile data as a DTO, including related properties and audit fields.
2025-12-15 14:01:11 +01:00
0e0f27c124 Rename ProfileGuid to Id in ProfileView and DbContext
Renamed the ProfileGuid property to Id in the ProfileView record.
Updated all DbContext mappings and primary key definitions to use
Id, while maintaining the mapping to the PROFILE_GUID column in
the database view.
2025-12-15 13:51:16 +01:00
5404530785 Add ProfileView entity mapped to VWREC_PROFILE view
Introduced the ProfileView entity as a record to represent data from the VWREC_PROFILE database view. Registered ProfileView in RecDbContext and configured its property mappings and primary key in OnModelCreating. This enables querying profile data via EF Core.
2025-12-15 13:51:00 +01:00
289b6109e4 Refactor DeleteRecActionsCommand to use RecAction entity
Switched from using RecActionDto to RecAction entity in the
DeleteRecActionsCommandHandler and updated repository types
and using statements accordingly. This aligns the handler
with domain-driven design and improves consistency.
2025-12-15 13:24:11 +01:00
679c065aaa Refactor CreateRecAction to use domain entities
Switched from using RecActionDto to RecAction domain entity in the CreateRecActionCommandHandler and updated relevant imports. This aligns the command handler with domain-driven design principles.
2025-12-15 13:23:56 +01:00
a02cac8778 Refactor handler to use AutoMapper and entity repository
Updated ObtainEndpointCommandHandler to depend on IRepository<Endpoint> and IMapper. Now maps Endpoint entities to EndpointDto using AutoMapper before returning, ensuring proper separation of concerns and DTO exposure.
2025-12-15 13:08:21 +01:00
42fd176fad Refactor RestType null check with pattern matching
Refactored the null check for action.RestType to use pattern matching, assigning it to a local variable restType if not null. Now throws DataIntegrityException if RestType is null, and uses restType for creating the HttpRequestMessage. This improves code clarity and ensures RestType is non-null in subsequent logic.
2025-12-15 12:53:43 +01:00
e529027587 Refactor HTTP method handling to use RestType enum
Replaced string-based HTTP method mapping with a strongly-typed RestType enum for improved type safety. Updated the ToHttpMethod extension to accept RestType, validate its value, and convert it to the appropriate HttpMethod. This reduces errors from invalid or misspelled method names.
2025-12-15 12:48:10 +01:00
cc2adab5e5 Rename ToHttpMethod to ToHttpMethodName in RestTypeExtensions
Renamed the extension method ToHttpMethod to ToHttpMethodName in the RestTypeExtensions class for improved clarity. The method's functionality remains unchanged; it still returns the uppercase string representation of the RestType enum value.
2025-12-15 12:47:59 +01:00
a0233fd876 Update RecActionViewDto: new props, type changes, renames
Renamed ProfileSequence to Sequence. Added display name properties for enums/types (e.g., EndpointAuthTypeName, RestTypeName, ErrorActionName). Added EndpointAuthApiKey. Changed RestType to enum and ErrorAction to nullable, with corresponding name properties.
2025-12-15 12:37:33 +01:00
0afe9870c0 Update RecActionView model and mapping for enums and names
Refactored RecActionView to use enum types for EndpointAuthType, ApiKeyLocation, and RestType, and added properties for their display names. Introduced ErrorAction and its display name. Updated RecDbContext mapping to treat RecActionView as a view with a key, map new enum ID and name fields, and align property mappings with the database view structure. This enhances clarity and supports both ID and human-readable values in the model.
2025-12-15 12:01:39 +01:00
784b4b1f05 Refactor to use DTOs instead of domain entities
Replaced references to ReC.Domain.Entities with ReC.Application.Common.Dto across multiple files to use DTOs in the application layer. Updated MappingProfile.cs namespace to ReC.Application.RecActionViews and adjusted using statements accordingly. These changes improve separation of concerns and ensure commands and mappings operate on DTOs rather than domain entities.
2025-12-15 11:58:43 +01:00
1634b4b7b1 Add RestType enum and extension methods for HTTP verbs
Introduced RestType enum in ReC.Domain.Constants to represent HTTP methods, including support for None and Invalid values. Added RestTypeExtensions with ToHttpMethod and IsValid methods to facilitate HTTP method handling and validation.
2025-12-15 11:40:44 +01:00
576b2d59d9 Update IRecDbContext to use domain model DbSet types
Changed DbSet properties in IRecDbContext from DTO types to their corresponding domain model classes for Connections, Endpoints, EndpointAuths, Profiles, and RecActions. This aligns the interface with the domain model and removes reliance on DTOs.
2025-12-15 11:30:48 +01:00
3da16ba640 Introduce DTOs and refactor app layer to use them
Added DTO classes for Connection, Endpoint, EndpointAuth, EndpointParam, Profile, and RecAction. Updated AutoMapper profiles, DbContext, and command handlers to use DTOs instead of domain entities. This decouples the application layer from the domain model, improving maintainability and flexibility. Cleaned up some using directives and file headers.
2025-12-12 15:16:38 +01:00
71a0220c3f Refactor: replace RecActionDto with RecActionViewDto
Standardize usage of RecActionViewDto across the codebase:
- Update pipeline behaviors, mapping profiles, and commands to use RecActionViewDto.
- Remove RecActionDto and introduce RecActionViewDto with equivalent properties and methods.
- Adjust query handlers and related interfaces to work with RecActionViewDto.
This clarifies DTO usage and aligns the model with domain intent.
2025-12-12 15:04:43 +01:00
f53603083a Refactor batch invocation to use RecActionViews
Replaced all batch RecAction logic with RecActionViews equivalents. Updated controller methods to call InvokeBatchRecActionView instead of InvokeBatchRecAction. Removed InvokeBatchRecActionsCommand and its handler, and added InvokeBatchRecActionViewsCommand with similar logic for RecActionViews. This shifts batch processing from RecActions to RecActionViews across the codebase.
2025-12-12 15:00:26 +01:00
92e7d44d3b Rename InvokeRecActionCommand to InvokeRecActionViewCommand
Refactored the RecAction invocation logic by renaming and moving InvokeRecActionCommand.cs to InvokeRecActionViewCommand.cs. Updated all class, record, and extension method names accordingly. The command handling logic remains unchanged, but all references and namespaces now reflect the new naming. Removed the old InvokeRecActionCommand.cs file.
2025-12-12 14:58:33 +01:00
f8211e9e9d Refactor RecAction query to use ReadRecActionViewQuery
Replaced ReadRecActionQuery and its handler with ReadRecActionViewQuery and ReadRecActionViewQueryHandler. Updated controller methods to use the new query type. This change clarifies and consolidates RecAction view query logic, improving code organization and domain alignment.
2025-12-12 14:52:11 +01:00
1782844543 Refactor RecAction commands to new namespace
Moved CreateRecActionCommand and DeleteRecActionsCommand (and their handlers) from ReCActionViews.Commands to ReCActions.Commands. Updated all references in controller and mapping profile. Removed old files to improve code organization. No changes to command logic.
2025-12-12 14:47:48 +01:00
2aa7cabcbd Refactor RecActions to RecActionViews namespaces
Renamed command and query files, namespaces, and usings from RecActions to RecActionViews for improved clarity and organization. Updated controller and mapping profile references accordingly. No changes to business logic or handler implementations.
2025-12-12 14:43:32 +01:00
28f35101f9 Refactor auth type to enum in RecActionDto and handler
Changed EndpointAuthType from string to enum in RecActionDto. Updated InvokeRecActionCommandHandler to use enum values in switch statements for improved type safety and maintainability.
2025-12-12 14:00:09 +01:00
f8c5502905 Refactor auth type properties to use strong enum type
Changed Type in EndpointAuth and EndpointAuthType in RecActionView from string? to EndpointAuthType? for improved type safety and clarity. This enforces the use of a specific enum for authentication types instead of plain strings.
2025-12-12 13:58:39 +01:00
961b87de3d Add EndpointAuthType enum for endpoint authentication types
Introduced the EndpointAuthType enum in the ReC.Domain.Constants namespace to represent various endpoint authentication methods, including NoAuth, ApiKey, BearerToken, JwtBearer, BasicAuth, DigestAuth, OAuth1, OAuth2, AwsSignature, and NtlmAuth. Each type is assigned a unique integer value for clear identification.
2025-12-12 13:57:52 +01:00
6b036f4f91 Add EF Core data annotations for table and FK mapping
Added [Table] and [ForeignKey] attributes to entity classes to explicitly map them to database tables/views and define relationships. Updated using directives as needed. Improves entity mapping clarity and robustness against schema changes.
2025-12-12 13:55:22 +01:00
46b7ae29cd Update IRecDbContext DbSets and add new entity sets
Changed DbSet properties to get/set in IRecDbContext, renamed Actions to RecActionViews for consistency, and added DbSets for Connections, Endpoints, EndpointAuths, Profiles, and RecActions. Updated RecDbContext implementation accordingly.
2025-12-12 13:42:49 +01:00
84e403f411 Refactor API key auth handling with switch and error check
Refactored the "API Key" authentication logic to use a switch
statement on the API key location, improving code clarity.
Added a default case to throw a DataIntegrityException for
unsupported API key locations, enhancing error handling.
2025-12-12 13:21:09 +01:00
3b77345aee Refactor API key location to use enum instead of string
Changed EndpointAuthApiKeyAddTo from string? to ApiKeyLocation? enum in RecActionDto for type safety. Updated related logic in InvokeRecActionCommandHandler to use the enum, and added the necessary using directive for ReC.Domain.Constants.
2025-12-12 13:17:54 +01:00
6d04a4afd1 Refactor API key location to use enum for type safety
Updated EndpointAuth and RecActionView to use the ApiKeyLocation enum for API key location properties instead of nullable strings. Added necessary using directives for ReC.Domain.Constants to support this change, improving type safety and code clarity.
2025-12-12 13:16:32 +01:00
1f250d55b0 Add ApiKeyLocation enum for API key placement options
Introduced the ApiKeyLocation enum in the ReC.Domain.Constants namespace to specify API key locations, supporting both Header and Query options.
2025-12-12 13:10:09 +01:00
c422de445d Refactor ErrorAction to use enum for type safety
Changed RecActionDto.ErrorAction from string to ErrorAction enum, updating all related logic to use enum values instead of strings. Added necessary using directives. This improves type safety, reduces risk of typos, and enhances maintainability.
2025-12-12 13:04:23 +01:00
f9a73fbe0c Refactor RecAction.ErrorAction to use enum type
Changed ErrorAction property in RecAction from string? to ErrorAction? enum for improved type safety. Added import for ReC.Domain.Constants to support the new type.
2025-12-12 13:02:17 +01:00
282ce3a0b7 Move ErrorAction enum to domain layer namespace
Changed ErrorAction enum namespace from ReC.Application.Common.Constants to ReC.Domain.Constants to better reflect its domain relevance. Removed the file's BOM. Enum members remain unchanged.
2025-12-12 13:00:24 +01:00
26f2da1313 Add ErrorAction enum for error handling actions
Introduced the ErrorAction enum in the ReC.Application.Common.Constants namespace with Stop and Continue values to standardize error response actions across the application.
2025-12-12 12:55:57 +01:00
bc700e2cd2 Refactor HTTP client name constant usage
Replaced the old HttpClientName constant in Constants.cs with a new Http.ClientName in the ReC.Application.Common.Constants namespace. Updated all references and using directives accordingly to improve code organization and maintainability.
2025-12-12 12:54:57 +01:00
ea5389df85 Remove Message property from OutRes and its EF mapping
The Message property was deleted from the OutRes class, and its corresponding Entity Framework mapping was removed from RecDbContext. This cleans up unused fields from both the model and database context configuration.
2025-12-12 12:51:43 +01:00
87e1bb9187 Refactor: centralize HTTP client naming with Constants
Introduced a Constants class to define a unique HttpClientName for HTTP client registration and usage. Updated DependencyInjection and InvokeRecActionCommandHandler to use this centralized name, improving consistency and reducing risk of name collisions.
2025-12-12 12:42:39 +01:00
68cc919bad Handle unsupported auth types with NotImplementedException
Throw NotImplementedException for unsupported authentication types
in InvokeRecActionCommandHandler, including details like ProfileId
and Id in the exception message for easier debugging. This prevents
silent failures when encountering unknown authentication methods.
2025-12-12 12:02:51 +01:00
374365d250 Refactor HttpClient usage and NTLM auth handling
Centralize HttpClient configuration using IHttpClientFactory with a named "Default" client. Move NTLM authentication logic from HttpClientHandler to request-level options, improving testability and aligning with .NET best practices.
2025-12-12 12:01:22 +01:00
8c79b3a156 Add support for multiple HTTP auth methods in REST actions
Expanded InvokeRecActionCommandHandler to support API Key, Bearer/JWT/OAuth2, Basic, and NTLM authentication schemes. Added necessary imports and logic for header/query manipulation and credential handling. Left placeholders for Digest, OAuth 1.0, and AWS Signature. Improves flexibility and robustness of outgoing HTTP requests.
2025-12-12 11:20:02 +01:00
0583d07f26 Add switch for endpoint auth types in action handler
Introduced a switch statement to handle various endpoint authentication types in InvokeRecActionCommandHandler. Cases for common auth methods have been added as placeholders, preparing the codebase for future implementation of specific authentication logic.
2025-12-12 10:50:49 +01:00
fd7744e94e Include EndpointAuth in RecActionView query results
Added .Include(act => act.EndpointAuth) to eagerly load the EndpointAuth navigation property when retrieving RecActionView entities. Also made a minor formatting adjustment to the check for empty action results.
2025-12-12 10:46:20 +01:00
a5aac1d0ec Add EndpointAuth navigation to RecActionView
Added EndpointAuth navigation property to RecActionView with [ForeignKey("EndpointAuthId")], enabling direct access to related authentication data via Entity Framework.
2025-12-12 10:34:26 +01:00
165152b7cf Merge branch 'master' of http://git.dd:3000/AppStd/ReC 2025-12-12 09:18:27 +01:00
f1f9e8d791 Bump version to 1.0.3-beta in ReC.API.csproj
Updated <Version>, <AssemblyVersion>, <FileVersion>, and <InformationalVersion> fields in the project file from 1.0.2-beta to 1.0.3-beta. No other changes were made.
2025-12-12 01:54:43 +01:00
35171add0c Add ErrorAction to RecActionDto and batch error handling
Introduce ErrorAction property to RecActionDto for per-action error handling. Update InvokeRecActionsCommandHandler to check invocation results and use ErrorAction to determine whether to continue or stop on failure, enabling configurable batch processing behavior.

Add ErrorAction to RecActionDto and batch error handling

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

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

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

Introduced a new `UseSwagger` setting in `appsettings.json` with a default value of `true`, allowing Swagger to be conditionally enabled in non-development environments. This improves flexibility for Swagger usage across different environments.
2025-12-08 10:07:57 +01:00
62 changed files with 1459 additions and 511 deletions

View File

@@ -1,10 +1,12 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.Application.OutResults.Commands;
using ReC.Application.OutResults.Queries;
namespace ReC.API.Controllers;
[Obsolete("This controller is deprecated and will be removed in future versions. Use the new ResultViewController instead.")]
[Route("api/[controller]")]
[ApiController]
public class OutResController(IMediator mediator, IConfiguration config) : ControllerBase
@@ -55,6 +57,34 @@ public class OutResController(IMediator mediator, IConfiguration config) : Contr
_ => Ok(res),
};
}
/// <summary>
/// Deletes output results based on the provided criteria.
/// </summary>
/// <param name="command">The command containing the deletion criteria, such as ActionId or ProfileId.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An empty response indicating success.</returns>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Delete([FromQuery] DeleteOutResCommand command, CancellationToken cancel)
{
await mediator.Send(command, cancel);
return NoContent();
}
/// <summary>
/// Deletes all output results for a fake/test profile.
/// </summary>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An empty response indicating success.</returns>
[HttpDelete("fake")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Delete(CancellationToken cancel)
{
await mediator.Send(new DeleteOutResCommand() { ProfileId = config.GetFakeProfileId() }, cancel);
return NoContent();
}
}
/// <summary>

View File

@@ -3,7 +3,8 @@ using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.API.Models;
using ReC.Application.RecActions.Commands;
using ReC.Application.RecActions.Queries;
using ReC.Application.RecActionViews.Commands;
using ReC.Application.RecActionViews.Queries;
using System.Text.Json;
namespace ReC.API.Controllers;
@@ -18,11 +19,11 @@ public class RecActionController(IMediator mediator, IConfiguration config) : Co
/// <param name="profileId">The ID of the profile.</param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>An HTTP 202 Accepted response indicating the process has been started.</returns>
[HttpPost("invoke/{cmd}")]
[HttpPost("invoke/{profileId}")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> Invoke([FromRoute] int profileId, CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(profileId, cancel);
await mediator.InvokeBatchRecActionView(profileId, cancel);
return Accepted();
}
@@ -35,7 +36,7 @@ public class RecActionController(IMediator mediator, IConfiguration config) : Co
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> Invoke(CancellationToken cancel)
{
await mediator.InvokeBatchRecAction(config.GetFakeProfileId(), cancel);
await mediator.InvokeBatchRecActionView(config.GetFakeProfileId(), cancel);
return Accepted();
}
@@ -43,12 +44,12 @@ public class RecActionController(IMediator mediator, IConfiguration config) : Co
/// <summary>
/// Gets all RecActions for a given profile.
/// </summary>
/// <param name="profileId">The ID of the profile.</param>
/// <param name="query"></param>
/// <param name="cancel">A token to cancel the operation.</param>
/// <returns>A list of RecActions for the specified profile.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get([FromQuery] ReadRecActionQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, cancel));
public async Task<IActionResult> Get([FromQuery] ReadRecActionViewQuery query, CancellationToken cancel) => Ok(await mediator.Send(query, cancel));
/// <summary>
/// Gets all RecActions for a fake/test profile.
@@ -58,7 +59,7 @@ public class RecActionController(IMediator mediator, IConfiguration config) : Co
/// <returns>A list of RecActions for the fake profile.</returns>
[HttpGet("fake")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get(CancellationToken cancel, [FromQuery] bool invoked = false) => Ok(await mediator.Send(new ReadRecActionQuery()
public async Task<IActionResult> Get(CancellationToken cancel, [FromQuery] bool invoked = false) => Ok(await mediator.Send(new ReadRecActionViewQuery()
{
ProfileId = config.GetFakeProfileId(),
Invoked = invoked

View File

@@ -0,0 +1,40 @@
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ReC.API.Extensions;
using ReC.Application.ResultViews.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.Body => res.Body is null ? Ok(new object { }) : Ok(res.Body.JsonToDynamic()),
ResultType.Header => res.Header is null ? Ok(new object { }) : Ok(res.Header.JsonToDynamic()),
_ => Ok(res),
};
}
}

View File

@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Rewrite;
using Microsoft.EntityFrameworkCore;
using NLog;
using NLog.Web;
using ReC.API.Middleware;
using ReC.Application;
using ReC.Infrastructure;
using System.Reflection;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
@@ -23,6 +25,13 @@ try
var config = builder.Configuration;
Directory
.GetFiles(builder.Environment.ContentRootPath, "appsettings.*.json", SearchOption.TopDirectoryOnly)
.Where(file => Path.GetFileName(file) != $"appsettings.Development.json")
.Where(file => Path.GetFileName(file) != $"appsettings.migration.json")
.ToList()
.ForEach(file => config.AddJsonFile(file, true, true));
// Add services to the container.
builder.Services.AddRecServices(options =>
{
@@ -48,7 +57,12 @@ try
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(c =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
@@ -57,10 +71,13 @@ try
#pragma warning restore CS0618
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
if (app.Environment.IsDevelopment() || config.GetValue<bool>("UseSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI();
var rewriteOptions = new RewriteOptions().AddRedirect("^$", "swagger");
app.UseRewriter(rewriteOptions);
}
app.UseHttpsRedirection();

View File

@@ -10,13 +10,15 @@
<Product>ReC.API</Product>
<PackageIcon>Assets\icon.ico</PackageIcon>
<PackageTags>digital data rest-caller rec api</PackageTags>
<Version>1.0.0-beta</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0-beta</InformationalVersion>
<Version>1.0.3-beta</Version>
<AssemblyVersion>1.0.3.0</AssemblyVersion>
<FileVersion>1.0.3.0</FileVersion>
<InformationalVersion>1.0.3-beta</InformationalVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />

View File

@@ -0,0 +1,59 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"NLog": {
"throwConfigExceptions": true,
"variables": {
"logDirectory": "E:\\LogFiles\\Digital Data\\Rec.API",
"logFileNamePrefix": "${shortdate}.Rec.API"
},
"targets": {
"infoLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"warningLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Warning.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
"maxArchiveDays": 30
},
"criticalLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
"maxArchiveDays": 30
}
},
"rules": [
{
"logger": "*",
"level": "Info",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Warn",
"writeTo": "warningLogs"
},
{
"logger": "*",
"level": "Error",
"writeTo": "errorLogs"
},
{
"logger": "*",
"level": "Fatal",
"writeTo": "criticalLogs"
}
]
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,18 @@ public class DtoMappingProfile : AutoMapper.Profile
{
public DtoMappingProfile()
{
CreateMap<RecActionView, RecActionDto>();
CreateMap<RecActionView, RecActionViewDto>();
CreateMap<OutRes, OutResDto>();
CreateMap<Connection, ConnectionDto>();
CreateMap<EndpointAuth, EndpointAuthDto>();
CreateMap<Endpoint, EndpointDto>();
CreateMap<EndpointParam, EndpointParamDto>();
CreateMap<Profile, ProfileDto>();
CreateMap<RecAction, RecActionDto>();
CreateMap<ResultView, ResultViewDto>();
CreateMap<ProfileView, ProfileViewDto>();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
/// <summary>
/// Represents the TBREC_CFG_ENDPOINT_PARAMS table.
/// All properties are nullable to provide flexibility on the database side,
/// preventing breaking changes if columns are altered to be nullable in production.
/// </summary>
public record EndpointParamDto
{
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

@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Application.Common.Dto;
[Table("TBREC_CFG_PROFILE", Schema = "dbo")]
public record ProfileDto
{
public long Id { get; set; }
public bool? Active { get; set; }
public string? Type { get; set; }
public string? Mandantor { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? LogLevel { get; set; }
public string? Language { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,15 +5,25 @@ namespace ReC.Application.Common.Interfaces;
public interface IRecDbContext
{
public DbSet<EndpointParam> EndpointParams { get; }
public DbSet<EndpointParam> EndpointParams { get; set; }
public DbSet<RecActionView> Actions { get; }
public DbSet<RecActionView> RecActionViews { get; set; }
public DbSet<OutRes> OutRes { get; }
public DbSet<OutRes> OutRes { get; set; }
public DbSet<HeaderQueryResult> HeaderQueryResults { get; }
public DbSet<HeaderQueryResult> HeaderQueryResults { get; set; }
public DbSet<BodyQueryResult> BodyQueryResults { get; }
public DbSet<BodyQueryResult> BodyQueryResults { get; set; }
public DbSet<Connection> Connections { get; set; }
public DbSet<Endpoint> Endpoints { get; set; }
public DbSet<EndpointAuth> EndpointAuths { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<RecAction> RecActions { get; set; }
public Task<int> SaveChangesAsync(CancellationToken cancel = default);
}

View File

@@ -1,10 +1,11 @@
using MediatR;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ReC.Application.Common.Behaviors;
using ReC.Application.Common.Constants;
using ReC.Application.Common.Options;
using System.Reflection;
using FluentValidation;
namespace ReC.Application;
@@ -35,7 +36,11 @@ public static class DependencyInjection
cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey;
});
services.AddHttpClient();
services.AddHttpClient(Http.ClientName)
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
UseDefaultCredentials = false
});
return services;
}

View File

@@ -1,25 +1,27 @@
using DigitalData.Core.Abstraction.Application.Repository;
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;
public class ObtainEndpointCommand : IRequest<Endpoint>
public class ObtainEndpointCommand : IRequest<EndpointDto>
{
public string Uri { get; init; } = null!;
}
public class ObtainEndpointCommandHandler(IRepository<Endpoint> repo) : IRequestHandler<ObtainEndpointCommand, Endpoint>
public class ObtainEndpointCommandHandler(IRepository<Endpoint> repo, IMapper mapper) : IRequestHandler<ObtainEndpointCommand, EndpointDto>
{
public async Task<Endpoint> Handle(ObtainEndpointCommand request, CancellationToken cancel)
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 endpoint;
return mapper.Map<EndpointDto>(endpoint);
endpoint = await repo.CreateAsync(request, cancel);
return endpoint;
return mapper.Map<EndpointDto>(endpoint);
}
}

View File

@@ -1,5 +1,5 @@
using ReC.Application.Endpoints.Commands;
using ReC.Domain.Entities;
using ReC.Application.Common.Dto;
namespace ReC.Application.Endpoints;
@@ -8,7 +8,7 @@ public class MappingProfile : AutoMapper.Profile
{
public MappingProfile()
{
CreateMap<ObtainEndpointCommand, Endpoint>()
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

@@ -8,6 +8,10 @@ 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; }

View File

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

@@ -0,0 +1,14 @@
using FluentValidation;
namespace ReC.Application.OutResults.Commands;
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

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

View File

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

View File

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

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

View File

@@ -1,8 +1,8 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using ReC.Application.Endpoints.Commands;
using ReC.Domain.Entities;
using ReC.Application.Endpoints.Commands;
namespace ReC.Application.RecActions.Commands;

View File

@@ -1,52 +0,0 @@
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ReC.Application.RecActions.Queries;
namespace ReC.Application.RecActions.Commands;
public record InvokeBatchRecActionsCommand : ReadRecActionQueryBase, IRequest;
public static class InvokeBatchRecActionsCommandExtensions
{
public static Task InvokeBatchRecAction(this ISender sender, long profileId, CancellationToken cancel = default)
=> sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId }, cancel);
}
public class InvokeRecActionsCommandHandler(ISender sender, IServiceScopeFactory scopeFactory, IHttpClientFactory clientFactory, ILogger<InvokeRecActionsCommandHandler>? logger = null) : IRequestHandler<InvokeBatchRecActionsCommand>
{
public async Task Handle(InvokeBatchRecActionsCommand request, CancellationToken cancel)
{
var actions = await sender.Send(request.ToReadQuery(q => q.Invoked = false), cancel);
var http = clientFactory.CreateClient();
using var semaphore = new SemaphoreSlim(5);
var tasks = actions.Select(async action =>
{
await semaphore.WaitAsync(cancel);
try
{
using var scope = scopeFactory.CreateScope();
var sender = scope.ServiceProvider.GetRequiredService<ISender>();
await sender.Send(action.ToInvokeCommand(), cancel);
}
catch(Exception ex)
{
logger?.LogError(
ex,
"Error invoking Rec action. ProfileId: {ProfileId}, Id: {Id}",
action.ProfileId,
action.Id
);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
namespace ReC.Domain.Attributes;
[AttributeUsage(AttributeTargets.Property)]
public class MustConfiguredAttribute : Attribute
{
}

View File

@@ -0,0 +1,7 @@
namespace ReC.Domain.Constants;
public enum ApiKeyLocation : byte
{
Header = 0,
Query = 1
}

View File

@@ -0,0 +1,15 @@
namespace ReC.Domain.Constants;
public enum EndpointAuthType : byte
{
NoAuth = 0,
ApiKey = 1,
BearerToken = 2,
JwtBearer = 3,
BasicAuth = 4,
DigestAuth = 5,
OAuth1 = 6,
OAuth2 = 7,
AwsSignature = 8,
NtlmAuth = 9
}

View File

@@ -0,0 +1,7 @@
namespace ReC.Domain.Constants;
public enum ErrorAction : byte
{
Stop = 0,
Continue = 1,
}

View File

@@ -0,0 +1,12 @@
namespace ReC.Domain.Constants;
public enum ProfileType : byte
{
Http = 1,
Https = 2
}
public static class ProfileTypeExtensions
{
public static string ToUriBuilderScheme(this ProfileType profileType) => profileType.ToString().ToLower();
}

View File

@@ -0,0 +1,28 @@
namespace ReC.Domain.Constants;
public enum RestType : byte
{
None = 0,
Get = 1,
Post = 2,
Put = 3,
Patch = 4,
Delete = 5,
Head = 6,
Options = 7,
Connect = 8,
Trace = 9,
}
public static class RestTypeExtensions
{
public static string ToHttpMethodName(this RestType restType)
{
return restType.ToString().ToUpper();
}
public static bool IsValid(this RestType restType)
{
return restType != RestType.None;
}
}

View File

@@ -1,9 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
namespace ReC.Domain.Entities;
public class BodyQueryResult
{
[Column("REQUEST_BODY")]
public string? RawBody { get; init; }
}
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
@@ -11,37 +10,25 @@ namespace ReC.Domain.Entities;
[Table("TBREC_CFG_ENDPOINT_PARAMS", Schema = "dbo")]
public class EndpointParam
{
[Key]
[Column("GUID")]
public long? Id { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("DESCRIPTION")]
public string? Description { get; set; }
[Column("GROUP_ID")]
public short? GroupId { get; set; }
[Column("SEQUENCE")]
public byte? Sequence { get; set; }
[Column("KEY")]
public string? Key { get; set; }
[Column("VALUE")]
public string? Value { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,9 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
namespace ReC.Domain.Entities;
public class HeaderQueryResult
{
[Column("REQUEST_HEADER")]
public string? RawHeader { get; init; }
}

View File

@@ -1,37 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
[Table("TBREC_OUT_RESULT", Schema = "dbo")]
public class OutRes
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public long? Id { get; set; }
[Column("ACTION_ID")]
public long? ActionId { get; set; }
[ForeignKey("ActionId")]
public RecAction? Action { get; set; }
[Column("RESULT_HEADER")]
public short? Status { get; set; }
public string? Header { get; set; }
[Column("RESULT_BODY")]
public string? Body { get; set; }
[Column("ADDED_WHO")]
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
}

View File

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

View File

@@ -0,0 +1,42 @@
using ReC.Domain.Constants;
namespace ReC.Domain.Entities;
public record ProfileView
{
public long Id { get; init; }
public bool Active { get; init; }
public ProfileType TypeId { get; init; }
public string? Type { get; init; }
public string? Mandantor { get; init; }
public string? ProfileName { get; init; }
public string? Description { get; init; }
public byte LogLevelId { get; init; }
public string? LogLevel { get; init; }
public short LanguageId { get; init; }
public string? Language { get; init; }
public string? AddedWho { get; init; }
public DateTime AddedWhen { get; init; }
public string? ChangedWho { get; init; }
public DateTime? ChangedWhen { get; init; }
public DateTime? FirstRun { get; init; }
public DateTime? LastRun { get; init; }
public string? LastResult { get; init; }
}

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations;
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
@@ -6,68 +6,48 @@ namespace ReC.Domain.Entities;
[Table("TBREC_CFG_ACTION")]
public class RecAction
{
[Key]
[Column("GUID")]
public long? Id { get; set; }
[Column("PROFILE_ID")]
public long? ProfileId { get; set; }
[ForeignKey("ProfileId")]
public Profile? Profile { get; set; }
[Column("ACTIVE")]
public bool? Active { get; set; }
[Column("SEQUENCE")]
public byte? Sequence { get; set; }
[Column("ENDPOINT_ID")]
public long? EndpointId { get; set; }
[ForeignKey("EndpointId")]
public Endpoint? Endpoint { get; set; }
[Column("ENDPOINT_AUTH_ID")]
public long? EndpointAuthId { get; set; }
[ForeignKey("EndpointAuthId")]
public EndpointAuth? EndpointAuth { get; set; }
[Column("ENDPOINT_PARAMS_ID")]
public short? EndpointParamsId { get; set; }
[Column("SQL_CONNECTION_ID")]
public short? SqlConnectionId { get; set; }
[ForeignKey("SqlConnectionId")]
public Connection? SqlConnection { get; set; }
[Column("TYPE")]
public string? Type { get; set; }
[Column("PREPROCESSING_QUERY")]
public string? PreprocessingQuery { get; set; }
[Column("HEADER_QUERY")]
public string? HeaderQuery { get; set; }
[Column("BODY_QUERY")]
public string? BodyQuery { get; set; }
[Column("POSTPROCESSING_QUERY")]
public string? PostprocessingQuery { get; set; }
[Column("ADDED_WHO")]
public ErrorAction? ErrorAction { get; set; }
public string? AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
public OutRes? OutRes { get; set; }

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations;
using ReC.Domain.Constants;
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
@@ -14,110 +14,84 @@ namespace ReC.Domain.Entities;
[Table("VWREC_ACTION", Schema = "dbo")]
public class RecActionView
{
[Column("ACTION_ID")]
public required long Id { get; set; }
[ForeignKey("Id")]
public RecAction? Root { get; set; }
[Column("PROFILE_ID")]
public long? ProfileId { get; set; }
[ForeignKey("ProfileId")]
public Profile? Profile { get; set; }
public ProfileView? Profile { get; set; }
[Column("PROFILE_NAME")]
[MaxLength(100)]
public string? ProfileName { get; set; }
[Column("PROFILE_TYPE")]
[MaxLength(20)]
public string? ProfileType { get; set; }
public ProfileType? ProfileType { get; set; }
[Column("PROFILE_SEQUENCE")]
public byte? ProfileSequence { get; set; }
public byte? Sequence { get; set; }
[Column("ENDPOINT_ID")]
public long? EndpointId { get; set; }
[Column("ENDPOINT_URI")]
[MaxLength(4000)]
[ForeignKey("EndpointId")]
public Endpoint? Endpoint { get; set; }
public string? EndpointUri { get; set; }
[Column("ENDPOINT_AUTH_ID")]
public long? EndpointAuthId { get; set; }
[Column("ENDPOINT_AUTH_TYPE")]
[MaxLength(50)]
public string? EndpointAuthType { get; set; }
[ForeignKey("EndpointAuthId")]
public EndpointAuth? EndpointAuth { get; set; }
public EndpointAuthType? EndpointAuthType { get; set; }
public string? EndpointAuthTypeName { get; set; }
[Column("ENDPOINT_AUTH_API_KEY")]
[MaxLength(300)]
public string? EndpointAuthApiKey { get; set; }
[Column("ENDPOINT_AUTH_API_VALUE")]
[MaxLength(300)]
public string? EndpointAuthApiValue { get; set; }
[Column("ENDPOINT_AUTH_API_KEY_ADD_TO")]
[MaxLength(20)]
public string? EndpointAuthApiKeyAddTo { get; set; }
public ApiKeyLocation? EndpointAuthApiKeyAddTo { get; set; }
public string? EndpointAuthApiKeyAddToName { get; set; }
[Column("ENDPOINT_AUTH_TOKEN")]
[MaxLength(300)]
public string? EndpointAuthToken { get; set; }
[Column("ENDPOINT_AUTH_USERNAME")]
[MaxLength(200)]
public string? EndpointAuthUsername { get; set; }
[Column("ENDPOINT_AUTH_PASSWORD")]
[MaxLength(200)]
public string? EndpointAuthPassword { get; set; }
[Column("ENDPOINT_AUTH_DOMAIN")]
[MaxLength(100)]
public string? EndpointAuthDomain { get; set; }
[Column("ENDPOINT_AUTH_WORKSTATION")]
[MaxLength(100)]
public string? EndpointAuthWorkstation { get; set; }
[Column("ENDPOINT_PARAMS_ID")]
public short? EndpointParamsId { get; set; }
[Column("SQL_CONNECTION_ID")]
public short? SqlConnectionId { get; set; }
[Column("SQL_CONNECTION_SERVER")]
[MaxLength(150)]
[ForeignKey("SqlConnectionId")]
public Connection? SqlConnection { get; set; }
public string? SqlConnectionServer { get; set; }
[Column("SQL_CONNECTION_DB")]
[MaxLength(100)]
public string? SqlConnectionDb { get; set; }
[Column("SQL_CONNECTION_USERNAME")]
[MaxLength(100)]
public string? SqlConnectionUsername { get; set; }
[Column("SQL_CONNECTION_PASSWORD")]
[MaxLength(100)]
public string? SqlConnectionPassword { get; set; }
[Column("REST_TYPE")]
[MaxLength(20)]
public string? RestType { get; set; }
public RestType? RestType { get; set; }
public string? RestTypeName { get; set; }
[Column("PREPROCESSING_QUERY")]
public string? PreprocessingQuery { get; set; }
[Column("HEADER_QUERY")]
public string? HeaderQuery { get; set; }
[Column("BODY_QUERY")]
public string? BodyQuery { get; set; }
[Column("POSTPROCESSING_QUERY")]
public string? PostprocessingQuery { get; set; }
public ErrorAction? ErrorAction { get; set; }
public string? ErrorActionName { get; set; }
}

View File

@@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace ReC.Domain.Entities;
public class ResultView
{
public long Id { get; set; }
[ForeignKey("Id")]
public OutRes? Root { get; set; }
public long? ActionId { get; set; }
public RecActionView? Action { get; set; }
public long? ProfileId { get; set; }
public ProfileView? Profile { get; set; }
public string? ProfileName { get; set; }
public short? StatusCode { get; set; }
public string? StatusName { 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

@@ -0,0 +1,12 @@
namespace ReC.Infrastructure.Exceptions;
public class DbModelConfigurationException : Exception
{
public DbModelConfigurationException(string message) : base(message)
{
}
public DbModelConfigurationException()
{
}
}

View File

@@ -0,0 +1,23 @@
using ReC.Infrastructure.Exceptions;
using ReC.Infrastructure.Options.Shared;
namespace ReC.Infrastructure.Options;
public record DbModelOptions
{
public Dictionary<string, EntityOptions> Entities { get; init; } = [];
public Dictionary<string, VirtualEntityOptions> VirtualEntities { get; init; } = [];
public void EnsureEntity<T>(bool isVirtual)
{
var entities = isVirtual
? VirtualEntities.ToDictionary(kvp => kvp.Key, kvp => kvp.Value as EntityBaseOptions)
: Entities.ToDictionary(kvp => kvp.Key, kvp => kvp.Value as EntityBaseOptions);
if(entities.TryGetValue(nameof(T), out var entityOptions))
entityOptions.EnsureProperties<T>();
else
throw new DbModelConfigurationException($"Entity options for type '{typeof(T).FullName}' not found.");
}
}

View File

@@ -0,0 +1,33 @@
using ReC.Domain.Attributes;
using ReC.Infrastructure.Exceptions;
namespace ReC.Infrastructure.Options.Shared;
public record EntityBaseOptions()
{
public Dictionary<string, string> ColumnMappings { get; init; } = [];
public IEnumerable<string> PropertyNames => ColumnMappings.Select(col => col.Key);
public IEnumerable<string> ColumnNames => ColumnMappings.Select(col => col.Value);
public void EnsureProperties(IEnumerable<string> propertyNames)
{
var missingProperties = propertyNames.Except(PropertyNames).ToList();
if (missingProperties.Count != 0)
throw new DbModelConfigurationException($"The following properties are not configured: {string.Join(", ", missingProperties)}");
}
public void EnsureProperties(params string[] propertyNames)
=> EnsureProperties(propertyNames.AsEnumerable());
public void EnsureProperties<T>()
{
var propertyNames = typeof(T)
.GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(MustConfiguredAttribute)))
.Select(prop => prop.Name);
EnsureProperties(propertyNames);
}
}

View File

@@ -0,0 +1,3 @@
namespace ReC.Infrastructure.Options.Shared;
public record EntityOptions(TableOptions Table) : EntityBaseOptions;

View File

@@ -0,0 +1,3 @@
namespace ReC.Infrastructure.Options.Shared;
public record TableOptions(string Name, string? Schema = null);

View File

@@ -0,0 +1,3 @@
namespace ReC.Infrastructure.Options.Shared;
public record VirtualEntityOptions : EntityBaseOptions;

View File

@@ -8,7 +8,11 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
{
public DbSet<EndpointParam> EndpointParams { get; set; }
public DbSet<RecActionView> Actions { get; set; }
public DbSet<RecActionView> RecActionViews { get; set; }
public DbSet<ProfileView> ProfileViews { get; set; }
public DbSet<ResultView> RecResultViews { get; set; }
public DbSet<OutRes> OutRes { get; set; }
@@ -30,15 +34,265 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<RecActionView>().HasNoKey();
modelBuilder.Entity<Connection>(b =>
{
b.ToTable("TBDD_CONNECTION");
modelBuilder.Entity<HeaderQueryResult>().HasNoKey();
b.HasKey(e => e.Id);
modelBuilder.Entity<BodyQueryResult>().HasNoKey();
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");
});
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");
});
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");
});
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");
});
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");
});
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");
});
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<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);
});
modelBuilder.Entity<RecActionView>(b =>
{
b.ToView("VWREC_ACTION", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id).HasColumnName("ACTION_GUID");
b.Property(e => e.ProfileId).HasColumnName("PROFILE_ID");
b.Property(e => e.ProfileName).HasColumnName("PROFILE_NAME");
b.Property(e => e.ProfileType).HasColumnName("PROFILE_TYPE_ID");
b.Property(e => e.Sequence).HasColumnName("SEQUENCE");
b.Property(e => e.EndpointId).HasColumnName("ENDPOINT_ID");
b.Property(e => e.EndpointUri).HasColumnName("ENDPOINT_URI");
b.Property(e => e.EndpointAuthId).HasColumnName("ENDPOINT_AUTH_ID");
b.Property(e => e.EndpointAuthType).HasColumnName("ENDPOINT_AUTH_TYPE_ID");
b.Property(e => e.EndpointAuthTypeName).HasColumnName("ENDPOINT_AUTH_TYPE");
b.Property(e => e.EndpointAuthApiKey).HasColumnName("ENDPOINT_AUTH_API_KEY");
b.Property(e => e.EndpointAuthApiValue).HasColumnName("ENDPOINT_AUTH_API_VALUE");
b.Property(e => e.EndpointAuthApiKeyAddTo).HasColumnName("ENDPOINT_AUTH_API_KEY_ADD_TO_ID");
b.Property(e => e.EndpointAuthApiKeyAddToName).HasColumnName("ENDPOINT_AUTH_API_KEY_ADD_TO");
b.Property(e => e.EndpointAuthToken).HasColumnName("ENDPOINT_AUTH_TOKEN");
b.Property(e => e.EndpointAuthUsername).HasColumnName("ENDPOINT_AUTH_USERNAME");
b.Property(e => e.EndpointAuthPassword).HasColumnName("ENDPOINT_AUTH_PASSWORD");
b.Property(e => e.EndpointAuthDomain).HasColumnName("ENDPOINT_AUTH_DOMAIN");
b.Property(e => e.EndpointAuthWorkstation).HasColumnName("ENDPOINT_AUTH_WORKSTATION");
b.Property(e => e.EndpointParamsId).HasColumnName("ENDPOINT_PARAMS_ID");
b.Property(e => e.SqlConnectionId).HasColumnName("SQL_CONNECTION_ID");
b.Property(e => e.SqlConnectionServer).HasColumnName("SQL_CONNECTION_SERVER");
b.Property(e => e.SqlConnectionDb).HasColumnName("SQL_CONNECTION_DB");
b.Property(e => e.SqlConnectionUsername).HasColumnName("SQL_CONNECTION_USERNAME");
b.Property(e => e.SqlConnectionPassword).HasColumnName("SQL_CONNECTION_PASSWORD");
b.Property(e => e.RestType).HasColumnName("REST_TYPE_ID");
b.Property(e => e.RestTypeName).HasColumnName("REST_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_ID");
b.Property(e => e.ErrorActionName).HasColumnName("ERROR_ACTION");
});
modelBuilder.Entity<ResultView>(b =>
{
b.ToView("VWREC_RESULT", "dbo");
b.HasKey(e => e.Id);
b.Property(e => e.Id).HasColumnName("RESULT_GUID");
b.Property(e => e.ActionId).HasColumnName("ACTION_ID");
b.Property(e => e.ProfileId).HasColumnName("PROFILE_ID");
b.Property(e => e.ProfileName).HasColumnName("PROFILE_NAME");
b.Property(e => e.StatusCode).HasColumnName("STATUS_ID");
b.Property(e => e.StatusName).HasColumnName("STATUS");
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");
});
modelBuilder.Entity<HeaderQueryResult>(b =>
{
b.HasNoKey();
b.Property(e => e.RawHeader).HasColumnName("REQUEST_HEADER");
});
modelBuilder.Entity<BodyQueryResult>(b =>
{
b.HasNoKey();
b.Property(e => e.RawBody).HasColumnName("REQUEST_BODY");
});
modelBuilder.Entity<RecAction>()
.HasOne(act => act.OutRes)
.WithOne(res => res.Action)
.HasForeignKey<OutRes>(res => res.ActionId);
}
}