Compare commits

...

92 Commits

Author SHA1 Message Date
c10f3c1b58 Bump to 1.2.1 2025-08-05 12:41:23 +02:00
b71e451121 feat(FileController): add control to get document 2025-08-04 17:50:58 +02:00
d4ea68fc0e feat(ObejctDto): add ControlsUpdates property 2025-08-04 17:25:43 +02:00
142a1a4faa refactor(PObejct): add ControlsUpdates property and create one to many relation 2025-08-04 17:24:07 +02:00
66fe515518 feat(PControlsUpdate): add mapping profile for dto 2025-08-04 17:20:09 +02:00
f54329ecd3 feat(PControlsUpdateDto): add dto of PControlsUpdate 2025-08-04 17:19:29 +02:00
9b7475bb56 feat(PControlsUpdate): add entity for TBMWF_PROFILE_CONTROLS_UPDATE table 2025-08-04 17:17:44 +02:00
1d0ded0e84 upg 1.2.0 2025-08-04 14:44:29 +02:00
9332a9161d fix: update ConfigMapping to configure with MappingOptions 2025-08-04 14:36:53 +02:00
6a04f36388 feat(UriBuilderExtensions.AppendPath): Ermöglicht das sichere Hinzufügen neuer Pfade zu UriBuilders.
- Implementiert für Resolver
2025-08-04 14:15:08 +02:00
581bd22c24 feat(MappingOptions): add to configure UriBuilderOptions factories.
- add WorkFlowServiceOptions for flexible configuration
2025-08-04 14:01:13 +02:00
eafdc17b70 refactor(DependencyInjection): update mediatRLicense to use from inputs 2025-08-04 11:15:58 +02:00
c952df5bb4 feat(TfFileDto): Icon durch IconUrl ersetzen und Mapping-Resolver hinzufügen 2025-08-04 11:01:25 +02:00
8c6202d7c0 feat(UriBuilderResolver): Umbenennen in TfFileUriBuilderResolver 2025-08-04 10:47:06 +02:00
288f8f98bd feat(UriBuilderResolver): update to handle nullable Url 2025-08-04 10:43:19 +02:00
659a402555 refactor(TfFileDto): made Url nullable 2025-08-04 10:24:54 +02:00
6288312c01 feat(ObjectStateDto): add TfFiles property 2025-08-03 12:29:57 +02:00
91679180ec feat(UriBuilderResolver): add to be able to dependencies.
- add UriBuilderFactory confoguration
 - inejct UriBuilderResolver as transient
2025-08-03 12:25:39 +02:00
bdc773d8ed add custom mapping for Url 2025-08-03 11:33:42 +02:00
cfbd0f013d fix property naming 2025-08-03 09:56:42 +02:00
d4b33d4b9a refactor(MappingProfile): add mapping for between TfFile and TfFileDto 2025-08-01 21:33:51 +02:00
4e5cb91967 feat(TfFile): create dto 2025-08-01 21:32:04 +02:00
b859391ab1 feat: Beziehung zwischen „ObjectState“ und „TfFiles“ hinzufügen 2025-08-01 16:02:03 +02:00
26300d8653 feat(TfFile): Entität mit Metadaten und Schema-Zuordnung hinzufügen
- Enthält detaillierte Schema-Zuordnung mit Validierungshinweisen für nullfähige Felder.
- Ermöglicht die Verfolgung von Dateimetadaten innerhalb des Workflow-Systems.
2025-08-01 15:51:12 +02:00
9d07b1e71c refactor: Beziehung zwischen „Object“ und „History“ hinzufügen. 2025-08-01 15:08:30 +02:00
ec975a2bc3 Add StateHistory property to PObject class
Introduces a new `StateHistory` property in the `PObject` class within `PObject.cs`. This property is decorated with the `[ForeignKey("ObjStateId")]` attribute and is of type `IEnumerable<PObjectStateHist>?`, allowing it to store a collection of `PObjectStateHist` objects. It is initialized to an empty array using `Array.Empty<PObjectStateHist>()`.
2025-08-01 14:04:15 +02:00
f10f5af541 Add mapping for PObjectStateHist and new DTO record
Introduced a mapping configuration in `MappingProfile` to map `PObjectStateHist` to `ObjectStateHistDto`, including the `Intl` property and an array of `Others`. Added a new record `ObjectStateHistDto` with properties for `Intl`, `Others`, `ChangedWho`, and `ChangedWhen`, initializing `Others` to an empty array of strings.
2025-08-01 14:00:26 +02:00
7d07fc58e9 Remove ToList() method from PObjectStateHist class
The `ToList()` method in the `PObjectStateHist` class has been removed. This method previously returned a list of strings, including the `IntlState` property of `State1` and other state properties (`State2`, `State3`, `State4`). Its removal indicates that this functionality is no longer needed or has been refactored.
2025-08-01 13:48:27 +02:00
7e82f688ad Remove ToList() method from PObjectState class
The `ToList()` method in the `PObjectState` class has been removed. This method previously returned a list of strings, including the `IntlState` property of `State1` and other state properties (`State2`, `State3`, `State4`). Its removal indicates that this functionality is no longer needed or has been refactored.
2025-08-01 13:47:50 +02:00
c325b2122b Add PObjectStateHist class for state history tracking
Introduces the `PObjectStateHist` class in the `WorkFlow.Domain.Entities` namespace, mapped to the `TBMWF_PROFILE_OBJ_STATE_HISTORY` table. The class includes properties with data annotations for validation and schema mapping, addressing nullable fields treated as required in application logic. A `ToList()` method is added to return state-related strings, enhancing functionality.
2025-08-01 13:47:22 +02:00
63adb51263 Refactor ButtonDto and ReadButtonRequest
Removed unused MediatR directive, added required ProfileId
property, and removed DialogNo from ButtonDto. Updated
ReadButtonRequest to inherit from IRequest<ButtonDto>
for MediatR integration.
2025-08-01 13:27:06 +02:00
363606dc61 Refactor DTOs and mappings for improved structure
- Removed `Id`, `Headlines`, and `States` from `ObjectDto`; added `State` property of type `ObjectStateDto?`.
- Completely removed `ProfileControlsTFDto` and `StateDto` classes.
- Removed `Id` from `ProfileDto`, keeping `TypeId` unchanged.
- Updated `MappingProfile` to remove mappings for `ProfileControlsTFDto` and `StateDto`, and changed mapping for `PControlsTF` to `PControlsTFDto`.
- Introduced new `ObjectStateDto` class with properties for `Intl`, `Others`, and a collection of `TFControls`.
- Added new `PControlsTFDto` class with various control attributes.
2025-08-01 13:19:10 +02:00
bc192e99a7 Refactor ObjectDto and update PObjectState relationships
Removed ObjStateId and Id from ObjectDto for simplification.
Updated foreign key in PObjectState from ObjStateId to StateId,
and modified TFControls to support nullable collections.
Adjusted WFDBContext configuration for clearer foreign key
relationships and improved maintainability of the database schema.
2025-08-01 12:53:56 +02:00
69d417616d Refactor repository structure in WorkFlow.Infrastructure
Removed ProfileObjRepository and migrated its functionality to PObjectRepository. Updated DependencyInjection.cs to register the new repository for dependency injection, improving clarity and organization while maintaining existing functionality.
2025-08-01 12:04:06 +02:00
a3cbe69fd6 Refactor data model: rename entities and update mappings
This commit includes a significant refactoring of the data model, renaming `ProfileObject` to `PObject` and `ProfileControlsTF` to `PControlsTF`.

Key changes:
- Updated `IProfileObjRepository` to reflect new entity names.
- Added `TFControls` property to `ObjectDto`.
- Adjusted `MappingProfile` for new entity mappings.
- Replaced `ProfileControlsTF` and `ProfileObjState` with `PControlsTF` and `PObjectState`.
- Updated `WFDBContext` with new DbSet properties for the renamed entities.
- Modified `ProfileObjRepository` to align with the new data structure.

These changes enhance clarity and maintainability across the application.
2025-08-01 12:02:34 +02:00
c2e8b335e0 Refactor ProfileControlsTFDto and update ProfileControlsTF
The `ProfileControlsTFDto` class has been changed from a record type to a class with auto-implemented properties, enhancing its structure and usability. Several properties have been renamed or removed, and new properties like `DialogNo`, `Text`, and `Icon` have been added.

In the `ProfileControlsTF` class, the `Sequ` property has been updated to remove its default value assignment, maintaining its nullable byte type.
2025-08-01 11:10:24 +02:00
7ed86f18d7 Refactor ProfileControlsTF and ProfileObjState relationships
Updated property names and foreign key references in
ProfileControlsTF and ProfileObjState classes to improve
database relationship integrity. Added TFControls collection
to ProfileObjState and modified queries in ProfileObjRepository
to include related entities. Established relationships in
WFDBContext with cascade delete behavior.
2025-08-01 10:54:08 +02:00
2fd64cb616 Fix null reference issue in ReadAsync method
Updated the inclusion of the `States` property in the
`ReadAsync` method of `ProfileObjRepository` to check
for null values before accessing `State1`. This change
prevents potential null reference exceptions.
2025-08-01 10:07:30 +02:00
b89a69b0f3 Remove ProfileObjStateDto and update mappings
- Deleted the `ProfileObjStateDto` class and its `States` property.
- Removed mapping for `ProfileObjState` to `ProfileObjStateDto` in `MappingProfile`.
- Updated `States` mapping to handle null values by converting to a list or returning an empty array.
- Replaced `States` property in `ProfileObjState` with a `ToList()` method that aggregates state values.
2025-08-01 09:41:16 +02:00
bb29b1563a Refactor DTOs and MappingProfile for consistency
Standardized namespaces for DTO classes and redefined several records to include necessary properties. Removed mapping configurations from MappingProfile, indicating a potential refactor. Introduced ProfileObjStateDto with a States property and ensured ProfileControlsTFDto and StateDto are properly encapsulated.
2025-08-01 02:53:52 +02:00
ad023b01d3 Enhance state management in DTOs and entities
- Added `States` property to `ProfileObjStateDto` and `ObjectDto`.
- Updated `MappingProfile` to include new `States` mapping.
- Made `StateId` in `ProfileObjState` required and added `State1` navigation property.
- Changed `ProfileObject` to use `States` instead of `State`.
- Cleaned up `State` class structure and removed redundant namespaces.
- Updated `ProfileObjRepository` to fetch related state data.
2025-08-01 02:50:19 +02:00
7309b968fe Refactor ProfileObjState and ProfileObject relationships
Removed the State property from ProfileObjState and added
ChangedWho and ChangedWhen properties. Updated ProfileObject
to reference ProfileObjState instead of State. Modified
ProfileObjRepository to include the new State property
when querying ProfileObject instances.
2025-08-01 02:21:48 +02:00
1159f3f575 Update ProfileObjState class and DI registration 2025-08-01 02:13:44 +02:00
8f2261f0fa Refactor ProfileObjState properties and add documentation
- Changed properties from `init` to `set` for mutability.
- Updated `ProfileId`, `UserId`, `ObjId`, and `StateId` to nullable types.
- Added XML documentation for clarity on property usage.
- Reduced maximum length of `State2`, `State3`, and `State4` to 100 characters.
- Modified `AddedWho` to nullable with default value "SYS".
- Changed `AddedWhen` to nullable with default value of `DateTime.Now`.
- Introduced `ChangedWho` and `ChangedWhen` properties for tracking changes.
- Updated navigation properties to reflect new accessors.
2025-08-01 02:11:49 +02:00
c779dd4a47 Refactor ReadProfileHandler and add Buttons property
Updated `ReadProfileHandler` to assign buttons directly to the `profile` object before mapping to `ProfileDto`. This ensures the `Buttons` property is populated correctly. Added a new `[NotMapped]` property `Buttons` of type `IEnumerable<Button>?` to the `Profile` class to hold associated buttons.
2025-08-01 01:58:15 +02:00
709ebea097 Refactor profile handling and error management
- Updated `ProfileController` to always return `Ok(profile)` instead of `NotFound()`.
- Changed `ReadProfileQuery` to return a non-nullable `ProfileDto`, throwing `NotFoundException` if not found.
- Modified `ReadProfileHandler` to handle the new return type and throw exceptions appropriately.
- Adjusted `ReadProfileAsync` to align with the new non-nullable return type.
- Added dependency on `DigitalData.Core.Exceptions` for improved error handling.
2025-08-01 01:41:21 +02:00
63df235943 feat(ExceptionHandlingMiddleware): Add to handle exceptions by middleware 2025-07-30 17:16:10 +02:00
78f2788388 feat(API): add global exception handler 2025-07-30 15:55:19 +02:00
13acf6de08 refactor: remove legacy services and controllers 2025-07-30 15:39:30 +02:00
5466b35b95 feat(Repositories.DependencyInjection): add config, controlTf, state and button repositories with auto-mapper configurations. 2025-07-30 15:00:05 +02:00
d0e306b7e4 chore: update core libs 2025-07-30 14:31:30 +02:00
4fcc0a08b8 refactor(ObjectDto): Verschieben in das Verzeichnis Dto.
- Profil-Dienst entfernen
2025-07-30 13:32:56 +02:00
904536bd09 refactor(Profil): dto in den Ordner DTO verschieben.
- Ordner DTO in Dto umbenennen
2025-07-30 13:21:18 +02:00
6c0f39e3ed refactor(domain): Aktualisieren Sie die Entität „ProfileControlsTF“, damit sie dem neuen Schema entspricht
- Die Implementierung von IUnique<int> und zugehörige Navigationseigenschaften wurden entfernt
- Der Primärschlüsseltyp wurde von int zu long geändert
- Die Spaltentypen und -namen wurden aktualisiert, um sie an das Datenbankschema anzupassen
- Erforderliche Eigenschaften wurden in nullbare Typen mit Validierung auf Anwendungsebene konvertiert
- XML-Kommentare wurden hinzugefügt, um Designentscheidungen hinsichtlich der Nullbarkeit zu verdeutlichen
2025-07-30 11:01:29 +02:00
8ceaa9cb21 feat(ReadObject): created to handle objects.
- Add ObjectDto and mapping profile
2025-07-29 22:19:48 +02:00
27e4b4b2ef feat(ReadProfile): add logic to read buttons 2025-07-29 22:09:11 +02:00
82eb03b420 refactor(ReadProfile): update to read buttons 2025-07-29 21:49:21 +02:00
559127a931 feat(ReadButton): add dto and handler 2025-07-29 21:41:54 +02:00
Developer 02
87857862e4 feat(Button): create repositry 2025-07-29 19:55:20 +02:00
8655f78c8c feat(Button): add entity for TBMWF_PROF_BUTTONS table 2025-07-29 18:23:21 +02:00
168a4b0791 feat: add ReadProfileAsync extension method for IMediator 2025-07-25 17:50:38 +02:00
dd4cd1b39e feat(ReadProfile): Unterstützung für das bedingte Laden von Profilobjekten hinzugefügt
- Flag „IncludeObject” in ReadProfile-Anfrage eingeführt
- IProfileObjRepository in ReadProfileHandler eingefügt
- Handler aktualisiert, um Profilobjekte zu laden, wenn IncludeObject wahr ist
2025-07-25 17:42:58 +02:00
8eb8801c41 feat(DI): add method to inject IProfileObjRepository 2025-07-25 17:07:40 +02:00
eb7ed81cac feat(IProfileObjRepository): Schnittstelle von ProfileObjRepository erstellen und in ProfileObjRepository implementieren 2025-07-25 17:04:58 +02:00
b7c6477ec2 feat(repo): Hinzufügen von ProfileObjRepository zum Abrufen von Profilobjekten über eine SQL-Funktion 2025-07-25 17:01:11 +02:00
b7f9efa9b6 feat: füge Spaltenattribute zur Klasse ProfileObject hinzu 2025-07-25 16:48:29 +02:00
bf5566cefc Laden Sie umgebungsspezifische App-Einstellungsdateien mit Ausnahme von „Development“
- Listet die Dateien „appsettings.*.json“ im Stammverzeichnis des Inhalts auf
- Schließt „appsettings.Development.json“ vom Laden aus
- Fügt die verbleibenden JSON-Dateien zur Konfiguration hinzu
2025-07-25 16:26:05 +02:00
f8e51e02a0 refactor: disable api key 2025-07-25 15:30:37 +02:00
ad1fd3163e Refactoring (Controller): Aktualisierung zur Verwendung der aktuellen Version von TryGetUserId 2025-07-25 10:44:12 +02:00
bed5fae01c Refactoring: Vereinfachung der TryGetUserId-Methode und Umstellung des Erweiterungsziels auf ClaimsPrincipal
- TryGetUserId wurde so geändert, dass es direkt die Methode expression-bodied mit int.TryParse verwendet.
- Die Erweiterungsmethoden wurden aktualisiert, sodass sie nun statt auf ControllerBase auf ClaimsPrincipal abzielen, um eine bessere Abstraktion und Wiederverwendbarkeit zu erreichen.
- Andere Methoden wurden aus Gründen der Konsistenz unverändert gelassen.
2025-07-24 18:09:59 +02:00
a378862791 refactor(ProfileController): Aktualisierung, um JWT zum Abrufen der Benutzer-ID anstelle der Abfragezeichenfolge zu verwenden 2025-07-24 17:53:31 +02:00
dd2dbee037 update Auth.Client 2025-07-24 17:35:13 +02:00
b24f518bba fix(Profil): Typ von TypeId byte festgelegt 2025-07-24 16:46:16 +02:00
dd5babfdbe inject MediatRLicense 2025-07-24 15:41:29 +02:00
dc7da91872 feat(di): register MediatR with service collection 2025-07-24 14:09:20 +02:00
fe358623da feat(ProfileController): Hinzufügen von ProfileController mit GET-Endpunkt unter Verwendung von MediatR
- Implementiert die Aktion `GetAsync` zum Abrufen von Profildaten über MediatR
- Fügt die Attribute `[Authorize]` und `[APIKeyAuth]` für den gesicherten Zugriff hinzu
- Protokolliert Ausnahmen und gibt entsprechende HTTP-Statuscodes zurück
2025-07-24 13:41:44 +02:00
c08c5aacf3 feat: Lesevorgang für Benutzerprofil mittels MediatR und Repository implementiert
- ReadProfile-Request eingeführt, um Benutzerprofil anhand der UserId abzurufen
- ReadProfileHandler hinzugefügt, der das Profil aus dem IProfileRepository liest
- Asynchrone Verarbeitung mit Unterstützung für CancellationToken integriert
2025-07-24 13:22:12 +02:00
14f5c73d43 feat(profile): implement ReadProfile query with MediatR
Added ReadProfile query and its handler using MediatR pattern to retrieve user profile by user ID via IProfileRepository.
2025-07-24 11:40:20 +02:00
b25d4eb028 feat: add documentation comments 2025-07-24 11:28:29 +02:00
8c08beba4e feat(repository): Implementieren Sie ProfileRepository mit ReadAsync unter Verwendung von FNMWF_GET_PROFILES. 2025-07-24 11:24:20 +02:00
30bb3ffa11 chore: update dependency injection methods of repositories 2025-07-24 11:00:48 +02:00
a9faf74803 chore: update references of Contracts.Repositories 2025-07-24 10:56:02 +02:00
22e4b4f54f refactor(Contracts.Repositories): Verschieben Sie es in die Anwendungsschicht, um die Anforderungen einer sauberen Architektur zu erfüllen. 2025-07-24 10:35:30 +02:00
a954a24888 feat(Controller): Nicht erforderliche Post-, Put- und Delete-Methoden ignorieren 2025-07-23 16:20:46 +02:00
a78c117a47 feat: extend default Profile with sample ProfileObjects
- Added two sample `ProfileObject` instances to the static `Default` Profile
- Includes object metadata like ObjStateId, ObjectId, headlines, and sublines
- Enhances the default response of `GET /api/profile` for testing/demo purposes
2025-07-21 10:24:43 +02:00
07e16f8aca feat(domain): ProfileObject-Entität zur Repräsentation von Objekt-Metadaten hinzugefügt
ProfileObject-Klasse zu WorkFlow.Domain.Entities hinzugefügt mit folgenden Eigenschaften:
- ObjStateId
- ObjectId
- Headline1, Headline2
- Subline1, Subline2
- CmdCheckIn
2025-07-21 10:18:57 +02:00
0b70016ab6 refactor(controller): ProfileController vereinfacht und Standardprofil-Antwort hinzugefügt
- Basisklasse CRUDControllerBaseWithErrorHandling entfernt
- Statisches Standard-Profilobjekt für Test-/Demo-Zwecke eingeführt
- Generisches CRUD-Verhalten durch einfachen GET-Endpunkt ersetzt
- Fehlerbehandlung mit Logging und HTTP-500-Antwort verbessert
2025-07-21 10:14:11 +02:00
537891b8c5 refactor(ProfileService): CRUDService-Implementierung entfernen 2025-07-18 16:10:39 +02:00
f8be2d9f26 refactor(repository): simplify Profile and ProfileObjState repositories
- Removed inheritance from ICRUDRepository in IProfileRepository and related implementation
- Cleaned up ProfileRepository to no longer extend CRUDRepository
- Removed `profileActive` filter from IProfileObjStateRepository and implementation
- Adjusted Read and ReadAsync methods accordingly
2025-07-18 16:08:08 +02:00
547d723f47 refactor(Profile): simplify Profile entity and remove unused metadata
- Removed dependency on IUnique<int> interface
- Removed validation and database annotations like [Required], [Key]
- Renamed/updated column mappings and replaced required fields with nullable types
- Removed metadata fields such as AddedWho, AddedWhen, ChangedWho, ChangedWhen, etc.
- Cleaned up namespace and using directives
2025-07-18 15:44:49 +02:00
1fcdcf6c0a chore: alle Projekte in das Verzeichnis src verschieben 2025-07-18 14:48:28 +02:00
a5bffdf1ce chore(solution): move projects to src-solution folder 2025-07-18 14:44:31 +02:00
117 changed files with 1720 additions and 1464 deletions

View File

@ -1,20 +0,0 @@
using DigitalData.Core.API;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Config;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ConfigController : CRUDControllerBaseWithErrorHandling<IConfigService, ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>
{
public ConfigController(ILogger<ConfigController> logger, IConfigService service) : base(logger, service)
{
}
}

View File

@ -1,96 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers
{
[APIKeyAuth]
public static class ControllerExtensions
{
public static bool TryGetUserId(this ControllerBase controller, out int? id)
{
var value = controller.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (value is null)
{
id = default;
return false;
}
if(int.TryParse(value, out int id_int))
{
id = id_int;
return true;
}
else
{
id = null;
return false;
}
}
public static bool TryGetUsername(this ControllerBase controller, out string username)
{
var value = controller.User.FindFirstValue(ClaimTypes.Name);
if (value is null)
{
username = string.Empty;
return false;
}
else
{
username = value;
return true;
}
}
public static bool TryGetName(this ControllerBase controller, out string name)
{
var value = controller.User.FindFirstValue(ClaimTypes.Surname);
if (value is null)
{
name = string.Empty;
return false;
}
else
{
name = value;
return true;
}
}
public static bool TryGetPrename(this ControllerBase controller, out string prename)
{
var value = controller.User.FindFirstValue(ClaimTypes.GivenName);
if (value is null)
{
prename = string.Empty;
return false;
}
else
{
prename = value;
return true;
}
}
public static bool TryGetEmail(this ControllerBase controller, out string email)
{
var value = controller.User.FindFirstValue(ClaimTypes.Email);
if (value is null)
{
email = string.Empty;
return false;
}
else
{
email = value;
return true;
}
}
}
}

View File

@ -1,20 +0,0 @@
using DigitalData.Core.API;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Profile;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileController : CRUDControllerBaseWithErrorHandling<IProfileService, ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>
{
public ProfileController(ILogger<ProfileController> logger, IProfileService service) : base(logger, service)
{
}
}

View File

@ -1,126 +0,0 @@
using DigitalData.Core.API;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTFService, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>
{
private readonly ILogger<ProfileControlsTFController> logger;
public ProfileControlsTFController(ILogger<ProfileControlsTFController> logger, IProfileControlsTFService service) : base(logger, service)
{
this.logger = logger;
}
[NonAction]
public override Task<IActionResult> GetAll() => base.GetAll();
[NonAction]
public override Task<IActionResult> Update(ProfileControlsTFUpdateDto updateDto) => base.Update(updateDto);
[HttpGet]
public async Task<IActionResult> GetAsync(
bool withProfile = true, bool withUser = false,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
try
{
if (!this.TryGetUserId(out int? id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadAsync(
withProfile: withProfile, withUser: withUser,
userId: id,
profileId: profileId, objId: objId, profileActive: profileActive)
.ThenAsync(
Success: pctf => pctf.Any() ? Ok(pctf) : NotFound(),
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpPost]
public override async Task<IActionResult> Create([FromBody] ProfileControlsTFCreateDto createDto)
{
try
{
if (!this.TryGetUserId(out int? id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id)
return Unauthorized();
return await base.Create(createDto);
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpDelete]
public override async Task<IActionResult> Delete([FromRoute] int id)
{
try
{
if (!this.TryGetUserId(out int? userId))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),
Fail: IActionResult (msg, ntc) =>
{
_logger.LogNotice(ntc);
return ntc.HasFlag(Flag.NotFound) ? NotFound() : StatusCode(StatusCodes.Status500InternalServerError);
});
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
}

View File

@ -1,127 +0,0 @@
using DigitalData.Core.API;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers
{
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileObjStateController : CRUDControllerBaseWithErrorHandling<IProfileObjStateService, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>
{
private readonly ILogger<ProfileObjStateController> logger;
public ProfileObjStateController(ILogger<ProfileObjStateController> logger, IProfileObjStateService service) : base(logger, service)
{
this.logger = logger;
}
[NonAction]
public override Task<IActionResult> GetAll() => base.GetAll();
[NonAction]
public override Task<IActionResult> Update(ProfileObjStateUpdateDto updateDto) => base.Update(updateDto);
[HttpGet]
public async Task<IActionResult> GetAsync(
bool withProfile = true, bool withUser = true, bool withState = true,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
try
{
if (!this.TryGetUserId(out int? id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadAsync(
withProfile: withProfile, withUser: withUser, withState,
userId: id,
profileId: profileId, objId: objId, profileActive: profileActive)
.ThenAsync(
Success: pctf => pctf.Any() ? Ok(pctf) : NotFound(),
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpPost]
public override async Task<IActionResult> Create([FromBody] ProfileObjStateCreateDto createDto)
{
try
{
if (!this.TryGetUserId(out int? id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id)
return Unauthorized();
return await base.Create(createDto);
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
[HttpDelete]
public override async Task<IActionResult> Delete([FromRoute] int id)
{
try
{
if (!this.TryGetUserId(out int? userId))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),
Fail: IActionResult (msg, ntc) =>
{
_logger.LogNotice(ntc);
return ntc.HasFlag(Flag.NotFound) ? NotFound() : StatusCode(StatusCodes.Status500InternalServerError);
});
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
}
}

View File

@ -1,20 +0,0 @@
using DigitalData.Core.API;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class StateController : CRUDControllerBaseWithErrorHandling<IStateService, StateCreateDto, StateDto, StateUpdateDto, State, int>
{
public StateController(ILogger<StateController> logger, IStateService service) : base(logger, service)
{
}
}

View File

@ -1,54 +0,0 @@
using DigitalData.Core.DTO;
using DigitalData.UserManager.Application.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class UserController : ControllerBase
{
private readonly ILogger<UserController> logger;
private readonly IUserService userService;
public UserController(ILogger<UserController> logger, IUserService userService)
{
this.logger = logger;
this.userService = userService;
}
[HttpGet]
public async Task<IActionResult> GetAsync()
{
try
{
if (!this.TryGetUserId(out int? id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if(id is int id_int)
return await userService.ReadByIdAsync(id_int).ThenAsync(
Success: Ok,
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
else
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
}
catch (Exception ex)
{
logger.LogError(ex, "An unexpected error occurred while processing the request: {Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred.");
}
}
}

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>4fb33592-ef0d-47c3-9cde-03b2ef12be00</ProjectGuid>
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\workFLOW\API\net7\$(Version)\workFLOW.API.zip</DesktopBuildPackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<DeployIisAppPath>WorkFlow.API</DeployIisAppPath>
<_TargetId>IISWebDeployPackage</_TargetId>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>4fb33592-ef0d-47c3-9cde-03b2ef12be00</ProjectGuid>
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\workFLOW\API\net8\$(Version)\workFLOW.API.zip</DesktopBuildPackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<DeployIisAppPath>WorkFlow.API</DeployIisAppPath>
<_TargetId>IISWebDeployPackage</_TargetId>
<TargetFramework>net7.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@ -1,91 +0,0 @@
{
"DiPMode": true,
"EnableSwagger": true,
"DisableAPIKeyAuth": false,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"NLog": {
"throwConfigExceptions": true,
"variables": {
"logDirectory": "E:\\LogFiles\\Digital Data\\workFlow.API",
"logFileNamePrefix": "${shortdate}-workFlow.API"
},
"targets": {
"infoLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
"maxArchiveDays": 30
},
"criticalLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
"maxArchiveDays": 30
}
},
// Trace, Debug, Info, Warn, Error and *Fatal*
"rules": [
{
"logger": "*",
"minLevel": "Info",
"maxLevel": "Warn",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Error",
"writeTo": "errorLogs"
},
{
"logger": "*",
"level": "Fatal",
"writeTo": "criticalLogs"
}
]
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"DirectorySearchOptions": {
"ServerName": "DD-VMP01-DC01",
"Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works",
"UserCacheExpirationDays": 1,
"CustomSearchFilters": {
"User": "(&(objectClass=user)(sAMAccountName=*))",
"Group": "(&(objectClass=group) (samAccountName=*))"
}
},
"APIKeyAuth": {
"Key": "ULbcOUiAXAoCXPviyCGtObZUGnrCHNgDmtNbQNpq5MOhB0EFQn18dObdQ93INNy8xIcnOPMJfEHqOotllELVrJ2R5AjqOfQszT2j00w215GanD3UiJGwFhwmdoNFsmNj",
"HeaderName": "X-API-Key",
"SwaggerDescription": "Required header for API key authentication. Enter a valid API key."
},
"OpenApiInfo": {
"Title": "WorkFlow API",
"Contact": {
"Email": "info-flow@digitaldata.works",
"Name": "Digital Data GmbH",
"Url": "https://digitaldata.works/"
}
},
"AuthClientParams": {
"Url": "https://localhost:7192/auth-hub",
"PublicKeys": [
{
"Issuer": "auth.digitaldata.works",
"Audience": "work-flow.digitaldata.works",
"Content": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QCd7dH/xOUITFZbitMa/xnh8a0LyL6ZBvSRAwkI9ceplTRSHJXoM1oB+xtjWE1kOuHVLe941Tm03szS4+/rHIm0Ejva/KKlv7sPFAHE/pWuoPS303vOHgI4HAFcuwywA8CghUWzaaK5LU/Hl8srWwxBHv5hKIUjJFJygeAIENvFOZ1gFbB3MPEC99PiPOwAmfl4tMQUmSsFyspl/RWVi7bTv26ZE+m3KPcWppmvmYjXlSitxRaySxnfFvpca/qWfd/uUUg2KWKtpAwWVkqr0qD9v3TyKSgHoGDsrFpwSx8qufUJSinmZ1u/0iKl6TXeHubYS4C4SUSVjOWXymI2ZQIDAQAB-----END PUBLIC KEY-----"
}
],
"RetryDelay": "00:00:05"
}
}

View File

@ -1,10 +0,0 @@
using DigitalData.Core.Abstractions.Application;
using WorkFlow.Application.DTO.Config;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts
{
public interface IConfigService : ICRUDService<ConfigCreateDto, ConfigDto, Config, int>
{
}
}

View File

@ -1,15 +0,0 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts
{
public interface IProfileControlsTFService : ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTF, int>
{
Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync(
bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null);
}
}

View File

@ -1,15 +0,0 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts
{
public interface IProfileObjStateService : ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjState, int>
{
Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync(
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null);
}
}

View File

@ -1,10 +0,0 @@
using DigitalData.Core.Abstractions.Application;
using WorkFlow.Application.DTO.Profile;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts
{
public interface IProfileService : ICRUDService<ProfileCreateDto, ProfileDto, Profile, int>
{
}
}

View File

@ -1,10 +0,0 @@
using DigitalData.Core.Abstractions.Application;
using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts
{
public interface IStateService : ICRUDService<StateCreateDto, StateDto, State, int>
{
}
}

View File

@ -1,25 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.Services;
using WorkFlow.Infrastructure;
namespace WorkFlow.Application
{
public static class DIExtensions
{
public static IServiceCollection AddWorkFlowServices(this IServiceCollection services)
{
services.AddAutoMapper(typeof(MappingProfile).Assembly);
services.TryAddScoped<IConfigService, ConfigService>();
services.TryAddScoped<IProfileControlsTFService, ProfileControlsTFService>();
services.TryAddScoped<IProfileObjStateService, ProfileObjStateService>();
services.TryAddScoped<IProfileService, ProfileService>();
services.TryAddScoped<IStateService, StateService>();
return services;
}
public static IServiceCollection AddWorkFlow(this IServiceCollection services) => services.AddWorkFlowRepositories().AddWorkFlowServices();
}
}

View File

@ -1,4 +0,0 @@
namespace WorkFlow.Application.DTO.Config
{
public record ConfigCreateDto(string Title, string String);
}

View File

@ -1,4 +0,0 @@
namespace WorkFlow.Application.DTO.Config
{
public record ConfigUpdateDto(string Title, string String) : BaseUpdateDto;
}

View File

@ -1,4 +0,0 @@
namespace WorkFlow.Application.DTO.Profile
{
public record ProfileCreateDto(string IntlName, int ExtId1, bool Active, byte TypeId) : BaseCreateDto;
}

View File

@ -1,12 +0,0 @@
namespace WorkFlow.Application.DTO.Profile
{
public record ProfileDto(int Id,
string IntlName,
int ExtId1,
bool Active,
byte TypeId,
string AddedWho,
DateTime AddedWhen,
string? ChangedWho = null,
DateTime? ChangedWhen = null);
}

View File

@ -1,4 +0,0 @@
namespace WorkFlow.Application.DTO.Profile
{
public record ProfileUpdateDto(string IntlName, int ExtId1, bool Active, byte TypeId) : BaseUpdateDto;
}

View File

@ -1,14 +0,0 @@
namespace WorkFlow.Application.DTO.ProfileControlsTF
{
public record ProfileControlsTFCreateDto(
int ProfileId,
int UserId,
long ObjId,
string ObjType,
string AttrName,
string CtrlType,
string CtrlCaption,
bool Mandatory,
bool ReadOnly,
string? ChoiceList = null) : BaseCreateDto;
}

View File

@ -1,21 +0,0 @@
using DigitalData.UserManager.Application.DTOs.User;
using WorkFlow.Application.DTO.Profile;
namespace WorkFlow.Application.DTO.ProfileControlsTF
{
public record ProfileControlsTFDto(int Id,
int ProfileId,
int UserId,
long ObjId,
string ObjType,
string AttrName,
string CtrlType,
string CtrlCaption,
bool Mandatory,
bool ReadOnly,
string AddedWho,
DateTime AddedWhen,
string? ChoiceList = null,
ProfileDto? Profile = null,
UserReadDto? User = null);
}

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Abstractions;
namespace WorkFlow.Application.DTO.ProfileControlsTF
{
/// <summary>
/// This Data Transfer Object (DTO) serves as a placeholder and does not support updates.
/// </summary>
public record ProfileControlsTFUpdateDto(int Id) : IUnique<int>;
}

View File

@ -1,11 +0,0 @@
namespace WorkFlow.Application.DTO.ProfileObjState
{
public record ProfileObjStateCreateDto(
int ProfileId,
int UserId,
long ObjId,
int StateId,
string? State2 = null,
string? State3 = null,
string? State4 = null) : BaseCreateDto;
}

View File

@ -1,20 +0,0 @@
using DigitalData.UserManager.Application.DTOs.User;
using WorkFlow.Application.DTO.Profile;
using WorkFlow.Application.DTO.State;
namespace WorkFlow.Application.DTO.ProfileObjState
{
public record ProfileObjStateDto(int Id,
int ProfileId,
int UserId,
long ObjId,
int StateId,
string AddedWho,
DateTime AddedWhen,
string? State2 = null,
string? State3 = null,
string? State4 = null,
ProfileDto? Profile = null,
UserReadDto? User = null,
StateDto? State = null);
}

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Abstractions;
namespace WorkFlow.Application.DTO.ProfileObjState
{
/// <summary>
/// This Data Transfer Object (DTO) serves as a placeholder and does not support updates.
/// </summary>
public record ProfileObjStateUpdateDto(int Id) : IUnique<int>;
}

View File

@ -1,4 +0,0 @@
namespace WorkFlow.Application.DTO.State
{
public record StateCreateDto(string IntlState) : BaseCreateDto;
}

View File

@ -1,4 +0,0 @@
namespace WorkFlow.Application.DTO.State
{
public record StateDto(int Id, string IntlState, string AddedWho, DateTime AddedWhen);
}

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Abstractions;
namespace WorkFlow.Application.DTO.State
{
/// <summary>
/// This Data Transfer Object (DTO) serves as a placeholder and does not support updates.
/// </summary>
public record StateUpdateDto(int Id) : IUnique<int>;
}

View File

@ -1,37 +0,0 @@
using AutoMapper;
using WorkFlow.Application.DTO.Config;
using WorkFlow.Application.DTO.Profile;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application
{
public class MappingProfile : AutoMapper.Profile
{
public MappingProfile()
{
// Mapping entity to DTO
CreateMap<Config, ConfigDto>();
CreateMap<Domain.Entities.Profile, ProfileDto>();
CreateMap<ProfileControlsTF, ProfileControlsTFDto>();
CreateMap<ProfileObjState, ProfileObjStateDto>();
CreateMap<State, StateDto>();
// Mapping create-DTO to entity
CreateMap<ConfigCreateDto, Config>();
CreateMap<ProfileCreateDto, Domain.Entities.Profile>();
CreateMap<ProfileControlsTFCreateDto, ProfileControlsTF>();
CreateMap<ProfileObjStateCreateDto, ProfileObjState>();
CreateMap<StateCreateDto, State>();
// Mapping update-DTO to entity
CreateMap<ConfigUpdateDto, Config>();
CreateMap<ProfileUpdateDto, Domain.Entities.Profile>();
CreateMap<ProfileControlsTFUpdateDto, ProfileControlsTF>();
CreateMap<ProfileObjStateUpdateDto, ProfileObjState>();
CreateMap<StateUpdateDto, State>();
}
}
}

View File

@ -1,17 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Config;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services;
public class ConfigService : CRUDService<IConfigRepository, ConfigCreateDto, ConfigDto, Config, int>,
IConfigService, ICRUDService<ConfigCreateDto, ConfigDto, Config, int>
{
public ConfigService(IConfigRepository repository, IMapper mapper) : base(repository, mapper)
{
}
}

View File

@ -1,35 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services
{
public class ProfileControlsTFService : CRUDService<IProfileControlsTFRepository, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTF, int>,
IProfileControlsTFService, ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTF, int>
{
public ProfileControlsTFService(IProfileControlsTFRepository repository, IMapper mapper) : base(repository, mapper)
{
}
public async Task<DataResult<IEnumerable<ProfileControlsTFDto>>> ReadAsync(
bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
var pctf_list = await _repository.ReadAsync(
isReadonly: true,
withProfile: withProfile, withUser: withUser,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive);
var pctf_dto_list = _mapper.Map<IEnumerable<ProfileControlsTFDto>>(pctf_list);
return Result.Success(pctf_dto_list);
}
}
}

View File

@ -1,34 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using DigitalData.Core.DTO;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services;
public class ProfileObjStateService : CRUDService<IProfileObjStateRepository, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjState, int>,
IProfileObjStateService, ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjState, int>
{
public ProfileObjStateService(IProfileObjStateRepository repository, IMapper mapper) : base(repository, mapper)
{
}
public async Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync(
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
{
var pos_list = await _repository.ReadAsync(
isReadonly: true,
withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive);
var post_dto_list = _mapper.Map<IEnumerable<ProfileObjStateDto>>(pos_list);
return Result.Success(post_dto_list);
}
}

View File

@ -1,16 +0,0 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Profile;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services;
public class ProfileService : CRUDService<IProfileRepository, ProfileCreateDto, ProfileDto, Profile, int>,
IProfileService, ICRUDService<ProfileCreateDto, ProfileDto, Profile, int>
{
public ProfileService(IProfileRepository repository, AutoMapper.IMapper mapper) : base(repository, mapper)
{
}
}

View File

@ -1,17 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Application.Services;
public class StateService : CRUDService<IStateRepository, StateCreateDto, StateDto, State, int>,
IStateService, ICRUDService<StateCreateDto, StateDto, State, int>
{
public StateService(IStateRepository repository, IMapper mapper) : base(repository, mapper)
{
}
}

View File

@ -1,44 +0,0 @@
using DigitalData.Core.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_PROFILE", Schema = "dbo")]
public class Profile : IUnique<int>
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("INTL_NAME", TypeName = "varchar(200)")]
public required string IntlName { get; init; }
[Required]
[Column("EXT_ID1")]
public required int ExtId1 { get; init; }
[Required]
[Column("ACTIVE")]
public required bool Active { get; init; }
[Required]
[Column("ADDED_WHO", TypeName = "varchar(30)")]
public required string AddedWho { get; init; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public required DateTime AddedWhen { get; init; }
[Column("CHANGED_WHO", TypeName = "varchar(30)")]
public string? ChangedWho { get; init; }
[Column("CHANGED_WHEN", TypeName = "datetime")]
public DateTime? ChangedWhen { get; init; }
[Required]
[Column("TYPE_ID")]
public required byte TypeId { get; init; }
}
}

View File

@ -1,68 +0,0 @@
using DigitalData.Core.Abstractions;
using DigitalData.UserManager.Domain.Entities;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_PROF_CONTROLS_TF", Schema = "dbo")]
public class ProfileControlsTF : IUnique<int>
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("MWF_PROFILE_ID")]
public required int ProfileId { get; init; }
[Required]
[Column("USR_ID")]
public required int UserId { get; init; }
[Required]
[Column("OBJ_ID")]
public required long ObjId { get; init; }
[Required]
[Column("OBJ_TYPE", TypeName = "varchar(10)")]
public required string ObjType { get; init; }
[Required]
[Column("ATTR_NAME", TypeName = "varchar(100)")]
public required string AttrName { get; init; }
[Required]
[Column("CTRL_TYPE", TypeName = "varchar(10)")]
public required string CtrlType { get; init; }
[Required]
[Column("CTRL_CAPTION", TypeName = "varchar(100)")]
public required string CtrlCaption { get; init; }
[Required]
[Column("MANDATORY")]
public required bool Mandatory { get; init; }
[Column("CHOICE_LIST", TypeName = "nvarchar(max)")]
public string? ChoiceList { get; init; }
[Required]
[Column("READ_ONLY")]
public required bool ReadOnly { get; init; }
[Required]
[Column("ADDED_WHO", TypeName = "varchar(100)")]
public required string AddedWho { get; init; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public required DateTime AddedWhen { get; init; }
[ForeignKey("ProfileId")]
public Profile? Profile { get; init; } = default;
[ForeignKey("UserId")]
public User? User { get; set; } = default;
}
}

View File

@ -1,57 +0,0 @@
using DigitalData.Core.Abstractions;
using DigitalData.UserManager.Domain.Entities;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_PROFILE_OBJ_STATE", Schema = "dbo")]
public class ProfileObjState : IUnique<int>
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("MWF_PROFILE_ID")]
public required int ProfileId { get; init; }
[Required]
[Column("USR_ID")]
public required int UserId { get; init; }
[Required]
[Column("OBJ_ID")]
public required long ObjId { get; init; }
[Required]
[Column("STATE_ID")]
public required int StateId { get; init; }
[Column("STATE2", TypeName = "nvarchar(3000)")]
public string? State2 { get; init; }
[Column("STATE3", TypeName = "nvarchar(3000)")]
public string? State3 { get; init; }
[Column("STATE4", TypeName = "nvarchar(3000)")]
public string? State4 { get; init; }
[Required]
[Column("ADDED_WHO", TypeName = "varchar(30)")]
public required string AddedWho { get; init; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public required DateTime AddedWhen { get; init; }
[ForeignKey("ProfileId")]
public Profile? Profile { get; init; } = null;
[ForeignKey("UserId")]
public User? User { get; init; } = null;
[ForeignKey("StateId")]
public State? State { get; init; } = null;
}
}

View File

@ -1,26 +0,0 @@
using DigitalData.Core.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_WF_STATE", Schema = "dbo")]
public class State : IUnique<int>
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("INTL_STATE", TypeName = "varchar(100)")]
public required string IntlState { get; init; }
[Required]
[Column("ADDED_WHO", TypeName = "varchar(30)")]
public required string AddedWho { get; init; }
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
public required DateTime AddedWhen { get; init; }
}
}

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Contracts
{
public interface IConfigRepository : ICRUDRepository<Config, int>
{
}
}

View File

@ -1,14 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Contracts
{
public interface IProfileControlsTFRepository : ICRUDRepository<ProfileControlsTF, int>
{
Task<IEnumerable<ProfileControlsTF>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = false,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null);
}
}

View File

@ -1,14 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Contracts
{
public interface IProfileObjStateRepository : ICRUDRepository<ProfileObjState, int>
{
Task<IEnumerable<ProfileObjState>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null);
}
}

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Contracts
{
public interface IProfileRepository : ICRUDRepository<Profile, int>
{
}
}

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Contracts
{
public interface IStateRepository : ICRUDRepository<State, int>
{
}
}

View File

@ -1,21 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using WorkFlow.Infrastructure.Contracts;
using WorkFlow.Infrastructure.Repositories;
namespace WorkFlow.Infrastructure
{
public static class DIExtensions
{
public static IServiceCollection AddWorkFlowRepositories(this IServiceCollection services)
{
services.TryAddScoped<IConfigRepository, ConfigRepository>();
services.TryAddScoped<IProfileControlsTFRepository, ProfileControlsTFRepository>();
services.TryAddScoped<IProfileObjStateRepository, ProfileObjStateRepository>();
services.TryAddScoped<IProfileRepository, ProfileRepository>();
services.TryAddScoped<IStateRepository, StateRepository>();
return services;
}
}
}

View File

@ -1,15 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories
{
//TODO: Make the db context type generic so that it can be used by other projects with different db contexts.
public class ConfigRepository : CRUDRepository<Config, int, WFDBContext>, IConfigRepository, ICRUDRepository<Config, int>
{
public ConfigRepository(WFDBContext dbContext) : base(dbContext, dbContext.Configs)
{
}
}
}

View File

@ -1,57 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories
{
public class ProfileControlsTFRepository : CRUDRepository<ProfileControlsTF, int, WFDBContext>, IProfileControlsTFRepository, ICRUDRepository<ProfileControlsTF, int>
{
public ProfileControlsTFRepository(WFDBContext dbContext) : base(dbContext, dbContext.ProfileControlsTFs)
{
}
protected override IQueryable<ProfileControlsTF> ReadOnly() => base.ReadOnly().Include(pctf => pctf.Profile).Include(pctf => pctf.User);
protected IQueryable<ProfileControlsTF> Read(bool isReadonly = false, bool withProfile = true, bool withUser = true, int? profileId = null, int? userId = null, string? username = null, int? objId = null, bool? profileActive = null)
{
var query = isReadonly ? _dbSet.AsNoTracking() : _dbSet.AsQueryable();
if (withProfile)
query = query.Include(pctf => pctf.Profile);
if (withUser)
query = query.Include(pctf => pctf.User);
if (profileId is not null)
query = query.Where(pctf => pctf.ProfileId == profileId);
if (userId is not null)
query = query.Where(pctf => pctf.UserId == userId);
if (username is null)
query = query.Where(pctf => pctf.User!.Username == username);
if (objId is not null)
query = query.Where(pctf => pctf.ObjId == objId);
if (profileActive is not null)
query = query.Where(pctf => pctf.Profile!.Active == profileActive);
return query;
}
public async Task<IEnumerable<ProfileControlsTF>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(
isReadonly: isReadonly,
withProfile: withProfile, withUser: withUser,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive)
.ToListAsync();
}
}

View File

@ -1,62 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories;
public class ProfileObjStateRepository : CRUDRepository<ProfileObjState, int, WFDBContext>, IProfileObjStateRepository, ICRUDRepository<ProfileObjState, int>
{
public ProfileObjStateRepository(WFDBContext dbContext) : base(dbContext, dbContext.ProfileObjStates)
{
}
protected override IQueryable<ProfileObjState> ReadOnly() => base.ReadOnly().Include(pos => pos.Profile).Include(pos => pos.State);
protected IQueryable<ProfileObjState> Read(bool isReadonly = false, bool withProfile = true, bool withUser = true, bool withState = true, int? profileId = null, int? userId = null, string? username = null, int? stateId = null, int? objId = null, bool? profileActive = null)
{
var query = isReadonly ? _dbSet.AsNoTracking() : _dbSet.AsQueryable();
if (withProfile)
query = query.Include(pctf => pctf.Profile);
if (withUser)
query = query.Include(pctf => pctf.User);
if (withState)
query = query.Include(pctf => pctf.State);
if (profileId is not null)
query = query.Where(pctf => pctf.ProfileId == profileId);
if (userId is not null)
query = query.Where(pctf => pctf.UserId == userId);
if (username is null)
query = query.Where(pctf => pctf.User!.Username == username);
if (stateId is null)
query = query.Where(pctf => pctf.State!.Id == stateId);
if (objId is not null)
query = query.Where(pctf => pctf.ObjId == objId);
if (profileActive is not null)
query = query.Where(pctf => pctf.Profile!.Active == profileActive);
return query;
}
public async Task<IEnumerable<ProfileObjState>> ReadAsync(
bool isReadonly = true,
bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null)
=> await Read(
isReadonly: isReadonly,
withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive)
.ToListAsync();
}

View File

@ -1,13 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories;
public class ProfileRepository : CRUDRepository<Profile, int, WFDBContext>, IProfileRepository, ICRUDRepository<Profile, int>
{
public ProfileRepository(WFDBContext dbContext) : base(dbContext, dbContext.Profiles)
{
}
}

View File

@ -1,13 +0,0 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories;
public class StateRepository : CRUDRepository<State, int, WFDBContext>, IStateRepository, ICRUDRepository<State, int>
{
public StateRepository(WFDBContext dbContext) : base(dbContext, dbContext.States)
{
}
}

View File

@ -3,13 +3,21 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlow.Domain", "WorkFlow.Domain\WorkFlow.Domain.csproj", "{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject
scripts\GetProfile.sql = scripts\GetProfile.sql
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlow.Infrastructure", "WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj", "{62526D0D-3365-4113-854A-3656191D7C63}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Application", "WorkFlow.Application\WorkFlow.Application.csproj", "{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.API", "src\WorkFlow.API\WorkFlow.API.csproj", "{2B724243-4C79-F3A4-EE25-B9A53C81464C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.API", "WorkFlow.API\WorkFlow.API.csproj", "{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Application", "src\WorkFlow.Application\WorkFlow.Application.csproj", "{F1B4AC83-5137-C20B-641C-1699B46007A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Domain", "src\WorkFlow.Domain\WorkFlow.Domain.csproj", "{92A11048-6B9C-374E-87A0-BD6D8F864B77}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Infrastructure", "src\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj", "{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -17,26 +25,32 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Release|Any CPU.Build.0 = Release|Any CPU
{62526D0D-3365-4113-854A-3656191D7C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62526D0D-3365-4113-854A-3656191D7C63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62526D0D-3365-4113-854A-3656191D7C63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62526D0D-3365-4113-854A-3656191D7C63}.Release|Any CPU.Build.0 = Release|Any CPU
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Release|Any CPU.Build.0 = Release|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Release|Any CPU.Build.0 = Release|Any CPU
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Release|Any CPU.Build.0 = Release|Any CPU
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Release|Any CPU.Build.0 = Release|Any CPU
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Release|Any CPU.Build.0 = Release|Any CPU
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2B724243-4C79-F3A4-EE25-B9A53C81464C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{F1B4AC83-5137-C20B-641C-1699B46007A0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{92A11048-6B9C-374E-87A0-BD6D8F864B77} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1ECB3995-5040-40BC-BC70-906E64BB4E01}
EndGlobalSection

4
scripts/GetProfile.sql Normal file
View File

@ -0,0 +1,4 @@
--PROFILES
select * from FNMWF_GET_PROFILES (1) --USER_ID
--PROFILE_OBJECTS
SELECT * FROM [FNMWF_GET_PROFILE_OBJECTS] (1,1) --USERID, PROFILE_ID

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,74 @@
using System.Security.Claims;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
public static class ControllerExtensions
{
public static bool TryGetUserId(this ClaimsPrincipal user, out int id) => int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier), out id);
public static bool TryGetUsername(this ClaimsPrincipal user, out string username)
{
var value = user.FindFirstValue(ClaimTypes.Name);
if (value is null)
{
username = string.Empty;
return false;
}
else
{
username = value;
return true;
}
}
public static bool TryGetName(this ClaimsPrincipal user, out string name)
{
var value = user.FindFirstValue(ClaimTypes.Surname);
if (value is null)
{
name = string.Empty;
return false;
}
else
{
name = value;
return true;
}
}
public static bool TryGetPrename(this ClaimsPrincipal user, out string prename)
{
var value = user.FindFirstValue(ClaimTypes.GivenName);
if (value is null)
{
prename = string.Empty;
return false;
}
else
{
prename = value;
return true;
}
}
public static bool TryGetEmail(this ClaimsPrincipal user, out string email)
{
var value = user.FindFirstValue(ClaimTypes.Email);
if (value is null)
{
email = string.Empty;
return false;
}
else
{
email = value;
return true;
}
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Web;
namespace WorkFlow.API.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class FileController : ControllerBase
{
[HttpGet("{path}")]
public IActionResult GetFile([FromRoute] string path, [FromQuery] bool icon = false)
{
string dPath = HttpUtility.UrlDecode(path);
byte[]? fileBytes = null;
string? contentType = null;
if (dPath == "docs/doc1.pdf" && !icon)
{
fileBytes = Convert.FromBase64String(Base64File.Doc1Base64);
contentType = "application/pdf";
}
else if (dPath == "icons/icon1.png" && icon)
{
fileBytes = Convert.FromBase64String(Base64File.Icon1Base64);
contentType = "image/png";
}
else
{
string fullPath = Path.Combine(AppContext.BaseDirectory, "files", dPath);
if (!System.IO.File.Exists(fullPath))
return NotFound();
fileBytes = System.IO.File.ReadAllBytes(fullPath);
contentType = GetContentType(fullPath);
}
return File(fileBytes, contentType ?? "application/octet-stream");
}
private string GetContentType(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
return ext switch
{
".pdf" => "application/pdf",
".png" => "image/png",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".txt" => "text/plain",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
_ => "application/octet-stream",
};
}
}

View File

@ -13,7 +13,7 @@ namespace WorkFlow.API.Controllers;
public class PlaceholderAuthController : ControllerBase
{
[HttpPost]
public IActionResult CreateTokenViaBody([FromBody] Login login)
public IActionResult CreateToken([FromBody] Login login)
{
throw new NotImplementedException();
}

View File

@ -0,0 +1,37 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Profiles;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProfileController : ControllerBase
{
private readonly ILogger<ProfileController> _logger;
private readonly IMediator _mediator;
public ProfileController(ILogger<ProfileController> logger, IMediator mediator)
{
_logger = logger;
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> GetAsync()
{
if (!User.TryGetUserId(out var userId))
{
_logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return Unauthorized("Failed to retrieve user identity.");
}
var profile = await _mediator.ReadProfileAsync(userId);
return Ok(profile);
}
}

View File

@ -0,0 +1,42 @@
using DigitalData.Core.Abstraction.Application.DTO;
using DigitalData.UserManager.Application.Contracts;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers;
[APIKeyAuth]
[Route("api/[controller]")]
[ApiController]
[Authorize]
[Obsolete("Use MediatR")]
public class UserController : ControllerBase
{
private readonly ILogger<UserController> logger;
private readonly IUserService userService;
public UserController(ILogger<UserController> logger, IUserService userService)
{
this.logger = logger;
this.userService = userService;
}
[HttpGet]
public async Task<IActionResult> GetAsync()
{
if (!User.TryGetUserId(out var id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return Unauthorized("Failed to retrieve user identity.");
}
return await userService.ReadByIdAsync(id).ThenAsync(
Success: Ok,
Fail: IActionResult (msg, ntc) =>
{
logger.LogNotice(ntc);
return NotFound();
});
}
}

View File

@ -0,0 +1,84 @@
using DigitalData.Core.Exceptions;
using System.Net;
using System.Text.Json;
namespace WorkFlow.API.Middleware;
//TODO: Fix and use DigitalData.Core.Exceptions.Middleware
/// <summary>
/// Middleware for handling exceptions globally in the application.
/// Captures exceptions thrown during the request pipeline execution,
/// logs them, and returns an appropriate HTTP response with a JSON error message.
/// </summary>
[Obsolete("Use DigitalData.Core.Exceptions.Middleware")]
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionHandlingMiddleware"/> class.
/// </summary>
/// <param name="next">The next middleware in the request pipeline.</param>
/// <param name="logger">The logger instance for logging exceptions.</param>
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
/// <summary>
/// Invokes the middleware to handle the HTTP request.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context); // Continue down the pipeline
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _logger);
}
}
/// <summary>
/// Handles exceptions by logging them and writing an appropriate JSON response.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <param name="exception">The exception that occurred.</param>
/// <param name="logger">The logger instance for logging the exception.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
{
context.Response.ContentType = "application/json";
string message;
switch (exception)
{
case BadRequestException badRequestEx:
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
message = badRequestEx.Message;
break;
case NotFoundException notFoundEx:
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
message = notFoundEx.Message;
break;
default:
logger.LogError(exception, "Unhandled exception occurred.");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
message = "An unexpected error occurred.";
break;
}
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
message
}));
}
}

View File

@ -1,22 +1,22 @@
using WorkFlow.Application;
using DigitalData.UserManager.Application;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Infrastructure;
using DigitalData.Core.API;
using DigitalData.Auth.Client;
using DigitalData.Core.Abstractions.Security.Extensions;
using DigitalData.Core.Application;
using DigitalData.UserManager.Application.DTOs.User;
using DigitalData.UserManager.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using WorkFlow.API.Models;
using Microsoft.OpenApi.Models;
using NLog;
using NLog.Web;
using WorkFlow.API;
using WorkFlow.API.Extensions;
using WorkFlow.API.Filters;
using Microsoft.OpenApi.Models;
using DigitalData.Auth.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using WorkFlow.API;
using Microsoft.Extensions.Options;
using DigitalData.Core.Abstractions.Security;
using WorkFlow.API.Middleware;
using WorkFlow.API.Models;
using WorkFlow.Application;
using WorkFlow.Infrastructure;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized.");
@ -26,22 +26,42 @@ try
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
Directory
.GetFiles(builder.Environment.ContentRootPath, "appsettings.*.json", SearchOption.TopDirectoryOnly)
.Where(file => Path.GetFileName(file) != $"appsettings.Development.json")
.ToList()
.ForEach(file => config.AddJsonFile(file, true, true));
// Add NLogger
builder.Logging.ClearProviders();
builder.Host.UseNLog();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
if (!builder.Environment.IsDevelopment())
{
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
// Add services to the container
var cnn_str = config.GetConnectionString("Default") ?? throw new("Default connection string not found.");
builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors());
builder.Services.AddWorkFlow().AddUserManager<WFDBContext>();
builder.Services.AddCookieBasedLocalizer();
var mediatRLicense = config["MediatRLicense"]
?? throw new InvalidOperationException(
"The 'MediatRLicense' configuration value is missing or empty." +
"Please ensure it is properly set in the configuration source.");
builder.Services.AddWorkFlowServices(opt =>
{
opt.MediatRLicense = mediatRLicense;
opt.ConfigMapping(config.GetSection("MappingOptions"));
}).AddWorkFlowRepositories();
builder.Services.AddUserManager<WFDBContext>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
{
Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object)
});
bool disableAPIKeyAuth = config.GetValue<bool>("DisableAPIKeyAuth") && builder.IsDevOrDiP();
bool disableAPIKeyAuth = config.GetValue<bool>("DisableAPIKeyAuth");
if (disableAPIKeyAuth)
builder.Services.AddAPIKeyAuth(new APIKeyAuthOptions());
else
@ -135,8 +155,10 @@ try
lazyProvider.Factory = () => app.Services;
app.UseMiddleware<ExceptionHandlingMiddleware>();
// Configure the HTTP request pipeline.
if (app.IsDevOrDiP() && app.Configuration.GetValue<bool>("EnableSwagger"))
if (app.Configuration.GetValue<bool>("EnableSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI();
@ -148,8 +170,6 @@ try
app.UseAuthorization();
app.UseCookieBasedLocalizer("de-DE");
app.MapControllers();
app.Run();

View File

@ -22,7 +22,7 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7120;http://localhost:5130",
"environmentVariables": {

View File

@ -5,12 +5,12 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>WorkFlow.API</PackageId>
<Version>1.1.0</Version>
<Version>1.2.1</Version>
<Company>Digital Data GmbH</Company>
<Product>WorkFlow.API</Product>
<Title>WorkFlow.API</Title>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
<AssemblyVersion>1.2.1</AssemblyVersion>
<FileVersion>1.2.1</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -20,16 +20,19 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.3" />
<PackageReference Include="DigitalData.Core.API" Version="2.1.1" />
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="UserManager" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
<ProjectReference Include="..\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,18 @@
{
"DisableAPIKeyAuth": true,
"APIKeyAuth": {
"Key": "ULbcOUiAXAoCXPviyCGtObZUGnrCHNgDmtNbQNpq5MOhB0EFQn18dObdQ93INNy8xIcnOPMJfEHqOotllELVrJ2R5AjqOfQszT2j00w215GanD3UiJGwFhwmdoNFsmNj",
"HeaderName": "X-API-Key",
"SwaggerDescription": "Required header for API key authentication. Enter a valid API key."
},
"AuthClientParams": {
"Url": "http://172.24.12.39:9090/auth-hub",
"RetryDelay": "00:00:05",
"PublicKeys": [
{
"Issuer": "auth.digitaldata.works",
"Audience": "work-flow.digitaldata.works"
}
]
}
}

View File

@ -0,0 +1,11 @@
{
"DirectorySearchOptions": {
"ServerName": "DD-VMP01-DC01",
"Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works",
"UserCacheExpirationDays": 1,
"CustomSearchFilters": {
"User": "(&(objectClass=user)(sAMAccountName=*))",
"Group": "(&(objectClass=group) (samAccountName=*))"
}
}
}

View File

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

View File

@ -0,0 +1,17 @@
{
"MappingOptions": {
"TfFileUri": {
"Scheme": "https",
"Host": "dd-gan.digitaldata.works",
"Port": 8443,
"Path": "api/file"
},
"TfFileIconUri": {
"Scheme": "https",
"Host": "dd-gan.digitaldata.works",
"Port": 8443,
"Path": "api/file",
"Query": "icon=true"
}
}
}

View File

@ -0,0 +1,17 @@
{
"DiPMode": true,
"EnableSwagger": true,
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"OpenApiInfo": {
"Title": "WorkFlow API",
"Contact": {
"Email": "info-flow@digitaldata.works",
"Name": "Digital Data GmbH",
"Url": "https://digitaldata.works/"
}
},
"MediatRLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg"
}

View File

@ -0,0 +1,20 @@
namespace WorkFlow.Application.Buttons;
public record ButtonDto
{
public byte? DialogNo { get; init; }
public string? BtnType { get; init; }
public string? Icon { get; init; }
public string? ForeColor { get; init; }
public string? BackColor { get; init; }
public string? Command { get; init; }
public string? DialogCommand { get; init; }
public string? ConfirmationText { get; init; }
}

View File

@ -0,0 +1,23 @@
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts.Repositories;
/// <summary>
/// Repository for retrieving <see cref="PObject"/> entities from the database.
/// </summary>
public interface IProfileObjRepository
{
/// <summary>
/// Retrieves the list of <see cref="PObject"/> associated with a given user ID and profile ID by calling a database function.
/// </summary>
/// <param name="userId">The unique identifier of the user whose profile is to be retrieved.</param>
/// <param name="profileId">The unique identifier of the profile whose object is to be retrieved.</param>
/// <param name="cancel">Propagates notification that operations should be canceled.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains the <see cref="PObject"/> object if found; otherwise, <c>null</c>.
/// </returns>
/// <remarks>
/// Logs an error if no profile is found, or if multiple profiles are returned, indicating potential data issues.
/// </remarks>
public Task<IEnumerable<PObject>> ReadAsync(int userId, int profileId, CancellationToken cancel = default);
}

View File

@ -0,0 +1,22 @@
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts.Repositories;
/// <summary>
/// Repository implementation for retrieving <see cref="Profile"/> entities from the database.
/// </summary>
public interface IProfileRepository
{
/// <summary>
/// Retrieves the <see cref="Profile"/> associated with a given user ID by calling a database function.
/// </summary>
/// <param name="userId">The unique identifier of the user whose profile is to be retrieved.</param>
/// <param name="cancel">Propagates notification that operations should be canceled.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains the <see cref="Profile"/> object if found; otherwise, <c>null</c>.
/// </returns>
/// <remarks>
/// Logs an error if no profile is found, or if multiple profiles are returned, indicating potential data issues.
/// </remarks>
Task<Profile?> ReadAsync(int userId, CancellationToken cancel = default);
}

View File

@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace WorkFlow.Application.DTO
namespace WorkFlow.Application.Dto
{
public record BaseCreateDto
{

View File

@ -1,9 +1,8 @@
using DigitalData.Core.Abstractions;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace WorkFlow.Application.DTO
namespace WorkFlow.Application.Dto
{
public record BaseUpdateDto : IUnique<int>
public record BaseUpdateDto
{
public required int Id { get; init; }

View File

@ -0,0 +1,52 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using WorkFlow.Application.Mapping;
namespace WorkFlow.Application;
public static class DependencyInjection
{
public static IServiceCollection AddWorkFlowServices(this IServiceCollection services, Action<WorkFlowServiceOptions>? options = null)
{
WorkFlowServiceOptions sOptions = new(services);
options?.Invoke(sOptions);
services.AddAutoMapper(typeof(MappingProfile).Assembly);
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly);
cfg.LicenseKey = sOptions.MediatRLicense;
});
if(!sOptions.IsMappingConfigured)
services.Configure<MappingOptions>(_ => { });
services.AddTransient<TfFileUriResolver>();
services.AddTransient<TfFileIconUriResolver>();
return services;
}
public class WorkFlowServiceOptions
{
private readonly IServiceCollection _services;
internal bool IsMappingConfigured { get; private set; } = false;
public WorkFlowServiceOptions(IServiceCollection services) => _services = services;
private void EnsureSingleMappingConfiguration(Action action)
{
if (IsMappingConfigured)
throw new InvalidOperationException("Mapping configuration has already been set.");
action();
IsMappingConfigured = true;
}
public void ConfigMapping(IConfiguration config) => EnsureSingleMappingConfiguration(() => _services.Configure<MappingOptions>(config));
public void ConfigMapping(Action<MappingOptions> options) => EnsureSingleMappingConfiguration(() => _services.Configure(options));
public string? MediatRLicense { get; set; }
}
}

View File

@ -1,4 +1,4 @@
namespace WorkFlow.Application.DTO.Config
namespace WorkFlow.Application.Dto
{
public record ConfigDto(int Id,
string Title,

View File

@ -0,0 +1,16 @@
namespace WorkFlow.Application.Dto;
public class ObjectDto
{
public IEnumerable<string> Headlines { get; set; } = Array.Empty<string>();
public IEnumerable<string> Sublines { get; set; } = Array.Empty<string>();
public string? CmdCheckIn { get; set; }
public ObjectStateDto? State { get; set; }
public IEnumerable<ObjectStateHistDto> StateHistories { get; set; } = Array.Empty<ObjectStateHistDto>();
public IEnumerable<PControlsUpdateDto>? ControlsUpdates { get; set; } = Array.Empty<PControlsUpdateDto>();
}

View File

@ -0,0 +1,15 @@
namespace WorkFlow.Application.Dto;
public record ObjectStateDto
{
public virtual string? Intl { get; set; }
/// <summary>
/// Holds state 2, 3 and 4 as a list of strings.
/// </summary>
public IEnumerable<string> Others { get; set; } = Array.Empty<string>();
public virtual IEnumerable<PControlsTFDto>? TFControls { get; set; }
public virtual IEnumerable<TfFileDto>? TfFiles { get; set; }
}

View File

@ -0,0 +1,15 @@
namespace WorkFlow.Application.Dto;
public record ObjectStateHistDto
{
public virtual string? Intl { get; set; }
/// <summary>
/// Holds state 2, 3 and 4 as a list of strings.
/// </summary>
public IEnumerable<string> Others { get; set; } = Array.Empty<string>();
public string? ChangedWho { get; set; }
public DateTime? ChangedWhen { get; set; }
}

View File

@ -0,0 +1,24 @@
namespace WorkFlow.Application.Dto;
public record PControlsTFDto
{
public byte? DialogNo { get; set; }
public string? AttrName { get; set; }
public string? CtrlType { get; set; }
public string? Caption { get; set; }
public string? Text { get; set; }
public string? Icon { get; set; }
public bool? Mandatory { get; set; }
public string? ChoiceList { get; set; }
public bool? ReadOnly { get; set; }
public byte? Sequ { get; set; }
}

View File

@ -0,0 +1,12 @@
namespace WorkFlow.Application.Dto;
public record PControlsUpdateDto
{
public string? AttrName { get; set; }
public string? AttrValue { get; set; }
public string? AddedWho { get; set; }
public DateTime? AddedWhen { get; set; }
}

View File

@ -0,0 +1,22 @@
using WorkFlow.Application.Buttons;
namespace WorkFlow.Application.Dto;
public class ProfileDto
{
public byte? TypeId { get; init; }
public string? Caption { get; init; }
public string? Subtitle { get; init; }
public int? CountObj { get; init; }
public string? ForeColor { get; init; }
public string? BackColor { get; init; }
public IEnumerable<ObjectDto> Objects { get; init; } = Array.Empty<ObjectDto>();
public IEnumerable<ButtonDto>? Buttons { get; set; } = Array.Empty<ButtonDto>();
}

View File

@ -0,0 +1,14 @@
namespace WorkFlow.Application.Dto;
public class TfFileDto
{
public string? Url { get; set; }
public string? Headline { get; set; }
public string? Subline { get; set; }
public string? Comment { get; set; }
public string? IconUrl { get; set; }
}

View File

@ -0,0 +1,52 @@
namespace WorkFlow.Application.Mapping;
public class MappingOptions
{
public UriBuilderOptions TfFileUri { get; set; } = new();
public UriBuilderOptions TfFileIconUri { get; set; } = new();
public class UriBuilderOptions
{
public string? Scheme { get; set; }
public string? Host { get; set; }
public int? Port { get; set; }
private string _path = "/";
public string Path
{
get => _path;
set => _path = value.Trim('/');
}
private string _query = string.Empty;
public string Query
{
get => _query;
set => _query = value.TrimStart('?');
}
public UriBuilder ToBuilder
{
get
{
var uriBuilder = new UriBuilder()
{
Scheme = Scheme ?? "http",
Host = Host ?? "localhost",
Path = Path,
Query = Query,
};
if (Port is int port)
uriBuilder.Port = port;
return uriBuilder;
}
}
}
}

View File

@ -0,0 +1,37 @@
using WorkFlow.Application.Buttons;
using WorkFlow.Application.Dto;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Mapping;
public class MappingProfile : AutoMapper.Profile
{
public MappingProfile()
{
CreateMap<Config, ConfigDto>();
CreateMap<Profile, ProfileDto>();
CreateMap<PControlsTF, PControlsTFDto>();
CreateMap<Button, ButtonDto>();
CreateMap<PObject, ObjectDto>()
.ForMember(dest => dest.Headlines, opt => opt.MapFrom(src => new[] { src.Headline1, src.Headline2 }))
.ForMember(dest => dest.Sublines, opt => opt.MapFrom(src => new[] { src.Subline1, src.Subline2 }));
CreateMap<PObjectState, ObjectStateDto>()
.ForMember(dest => dest.Intl, opt => opt.MapFrom(src => src.State1 != null ? src.State1.IntlState : null))
.ForMember(dest => dest.Others, opt => opt.MapFrom(src => new string?[] { src.State2, src.State3, src.State4 }));
CreateMap<PObjectStateHist, ObjectStateHistDto>()
.ForMember(dest => dest.Intl, opt => opt.MapFrom(src => src.State1 != null ? src.State1.IntlState : null))
.ForMember(dest => dest.Others, opt => opt.MapFrom(src => new string?[] { src.State2, src.State3, src.State4 }));
CreateMap<TfFile, TfFileDto>()
.ForMember(dest => dest.Url, opt => opt.MapFrom<TfFileUriResolver>())
.ForMember(dest => dest.IconUrl, opt => opt.MapFrom<TfFileIconUriResolver>());
CreateMap<PControlsUpdate, PControlsUpdateDto>();
}
}

View File

@ -0,0 +1,24 @@
using AutoMapper;
using Microsoft.Extensions.Options;
using System.Web;
using WorkFlow.Application.Dto;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Mapping;
public class TfFileIconUriResolver : IValueResolver<TfFile, TfFileDto, string?>
{
private readonly Func<UriBuilder> _uriBuilderFactory;
public TfFileIconUriResolver(IOptions<MappingOptions> options) => _uriBuilderFactory = () => options.Value.TfFileIconUri.ToBuilder;
public string? Resolve(TfFile source, TfFileDto destination, string? destMember, ResolutionContext context)
{
if(string.IsNullOrEmpty(source.Icon))
return null;
var builder = _uriBuilderFactory();
builder.AppendPath(source.Icon);
return builder.ToString();
}
}

View File

@ -0,0 +1,23 @@
using AutoMapper;
using Microsoft.Extensions.Options;
using WorkFlow.Application.Dto;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Mapping;
public class TfFileUriResolver : IValueResolver<TfFile, TfFileDto, string?>
{
private readonly Func<UriBuilder> _uriBuilderFactory;
public TfFileUriResolver(IOptions<MappingOptions> options) => _uriBuilderFactory = () => options.Value.TfFileUri.ToBuilder;
public string? Resolve(TfFile source, TfFileDto destination, string? destMember, ResolutionContext context)
{
if (string.IsNullOrEmpty(source.Path))
return null;
var builder = _uriBuilderFactory();
builder.AppendPath(source.Path);
return builder.ToString();
}
}

View File

@ -0,0 +1,14 @@
using System.Web;
namespace WorkFlow.Application.Mapping;
internal static class UriBuilderExtensions
{
public static void AppendPath(this UriBuilder uriBuilder, string path, bool encode = true)
{
if(encode)
path = HttpUtility.UrlEncode(path);
uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/', '\\')}/{path.Trim('/', '\\')}";
}
}

View File

@ -0,0 +1,71 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using MediatR;
using WorkFlow.Application.Buttons;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Application.Dto;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Profiles;
/// <summary>
/// Represents a request to read a user profile by their user ID.
/// </summary>
/// <param name="UserId">The ID of the user whose profile is being requested.</param>
public record ReadProfileQuery(int UserId, bool IncludeObject = true) : IRequest<ProfileDto>;
/// <summary>
/// Handles the <see cref="ReadProfileQuery"/> request by retrieving the user profile
/// from the data store using the <see cref="IProfileRepository"/>.
/// </summary>
public class ReadProfileHandler : IRequestHandler<ReadProfileQuery, ProfileDto>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfileObjRepository _objRepository;
private readonly IRepository<Button> _bttnRepository;
private readonly IMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="ReadProfileHandler"/> class.
/// </summary>
/// <param name="profileRepository">The profile repository used to access profile data.</param>
/// <param name="objRepository">The profile object repository used to access object data.</param>
public ReadProfileHandler(IProfileRepository profileRepository, IProfileObjRepository objRepository, IRepository<Button> buttonRepository, IMapper mapper)
{
_profileRepository = profileRepository;
_objRepository = objRepository;
_bttnRepository = buttonRepository;
_mapper = mapper;
}
/// <summary>
/// Handles the <see cref="ReadProfileQuery"/> request by retrieving the profile
/// corresponding to the specified user ID.
/// </summary>
/// <param name="request">The request containing the user ID.</param>
/// <param name="cancel">A cancellation token for the operation.</param>
/// <returns>The user profile if found; otherwise, <c>null</c>.</returns>
public async Task<ProfileDto> Handle(ReadProfileQuery request, CancellationToken cancel = default)
{
var profile = await _profileRepository.ReadAsync(request.UserId, cancel)
?? throw new NotFoundException();
if (request.IncludeObject && profile.Id is int profileId)
profile.Objects = await _objRepository.ReadAsync(request.UserId, profileId, cancel);
if (profile.Id is int pId)
profile.Buttons = await _bttnRepository.Read(b => b.ProfileId == pId).ToListAsync(cancel);
return _mapper.Map<ProfileDto>(profile);
}
}
public static class ReadProfileQueryExtensions
{
public static Task<ProfileDto> ReadProfileAsync(this IMediator mediator, int userId, bool includeObject = true)
=> mediator.Send(new ReadProfileQuery(UserId: userId, IncludeObject: includeObject));
}

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