Compare commits

...

202 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
Developer 02
3832351dd1 add Jenkinsfile
Some checks failed
AppStd/WorkFlow/pipeline/head There was a failure building this commit
2025-05-22 16:03:12 +02:00
Developer 02
99237cbecc refactor(IISProfile): Verzeichnis aktualisiert, um die Namenskonventionen für digitale Daten anzuwenden 2025-03-25 15:38:33 +01:00
Developer 02
cb2edffe91 chore: Separate IIS-Konfiguration für .Net 7 und 8 hinzufügen 2025-03-24 16:12:39 +01:00
Developer 02
4d3768248e chore: IIS-Veröffentlichungsprofile getrennt nach Framework hinzufügen 2025-03-13 12:06:56 +01:00
Developer 02
fb38bc1fd4 chore(API): Hinzufügen von .net 7-Unterstützung für API 2025-03-13 10:12:55 +01:00
Developer 02
10b557374d chore: Hinzufügen von .net 7-Unterstützung für Domäne, Infrastruktur und Anwendung 2025-03-13 10:04:40 +01:00
Developer 02
f266e6728f chore: Upgrade auf DigitalData.Auth.Client 1.3.3 2025-03-13 09:29:07 +01:00
Developer 02
3373fceef3 chore(API): Hochgestuft auf 1.1.0 2025-03-11 17:17:08 +01:00
Developer 02
f7eaa0f7de refactor: IssuerSigningKeyResolver wurde aktualisiert, um die Konfiguration über serviceProvider anstelle eines separaten öffentlichen Schlüssels zu ermöglichen. 2025-03-11 16:22:54 +01:00
Developer 02
d5b1ee41a0 fix: Arranged auth Authentication Scheme 2025-03-11 10:48:04 +01:00
Developer 02
c3f5d90b6a refactor(PlaceholderAuthController): Update auf Login nur über Body und ohne Cookie 2025-03-11 10:32:28 +01:00
Developer 02
753eb18b71 updated(AuthController): Aktualisiert, um als Platzhalter für auth api in swagger zu funktionieren.
- umbenennen PlaceholderAuthController
2025-03-11 10:17:27 +01:00
Developer 02
17d8373739 feat: JwtBearerEvents hinzugefügt, um Token aus Cookie oder Query-String lesen zu können 2025-03-11 09:36:32 +01:00
Developer 02
d6ccc10244 fix: Aktualisiertes Präfix für den Namen der NLog-Protokolldatei als workFlow.API 2025-03-10 15:47:58 +01:00
Developer 02
3dccf82710 fix: Aktualisiertes Präfix für den Namen der NLog-Protokolldatei als workFlow.API 2025-03-10 15:36:42 +01:00
Developer 02
c7d8b67ccb refactor Aktualisierte AuthPublicKey-Konfiguration, die getrennt von AuthClaimPrams konfiguriert werden kann, um sie als JWT-Barriere-Konfiguration zu verwenden 2025-03-10 13:53:15 +01:00
Developer 02
b76043fa24 chore: Aktualisierung von DigitalData.Core.Application und UserManager.Application auf 2.0.0
- Aktualisiert auf Dienste als aktuelle Core.Application
2025-03-10 10:53:40 +01:00
Developer 02
e28f4560d6 Refactor: Inline AuthHubClient Optionen durch Konfigurationsabschnitt ersetzen 2025-03-10 09:48:10 +01:00
Developer 02
97d5156bbb feat(auth): Integration von AuthHubClient und JWT-basierter Authentifizierung
- Abhängigkeit `DigitalData.Auth.Client` hinzugefügt
- `AuthHubClient` mit konfigurierbarem öffentlichen Schlüssel für Authentifizierung integriert
- Cookie-basierte Authentifizierung durch JWT-Bearer-Authentifizierung ersetzt
- Token-Validierung so konfiguriert, dass dynamisch auflösbare Signaturschlüssel verwendet werden
2025-03-07 16:10:10 +01:00
Developer 02
40cf8f3f10 chore: Konfigurierte Paket-ID, Version, Firma, Produkt und Titel 2024-10-29 14:50:18 +01:00
Developer 02
a325d07c6b refactor: AuthController-Methoden optimieren und Login-Logik verbessern
- AuthController aktualisiert, um eine klarere Struktur zu implementieren.
- Login-Methode vereinfacht, um die Benutzerauthentifizierung direkt zu behandeln.
- Neue LoginById-Methode für den Benutzerlogin über ID eingeführt.
- Fehlerprotokollierung und -behandlung in den Login-Methoden verbessert.
- Überflüssigen Code entfernt und Lesbarkeit verbessert.
- TODO für weitere Integration mit UserManager hinzugefügt.
2024-10-29 14:24:13 +01:00
Developer 02
69abd3afa2 feat(DIExtensions.AddAPIKeyAuth): if options.Key is null return true in default isValidKey function 2024-10-29 12:44:53 +01:00
Developer 02
cbdd6ee295 feat(APIKeyAuthOptions): Schlüsselattribut wird löschbar gemacht.
- isValidKey-Eintrag wird löschbar gemacht.
 - wenn der Schlüssel null ist und der X-API-Schlüssel nicht existiert, wird die Anfrage authirezred.
2024-10-29 12:23:10 +01:00
Developer 02
2c1abaaf32 feat(DisableAPIKeyAuth): Optionen als bool zu appsettings hinzugefügt. Konfiguriert mit app 2024-10-29 11:43:25 +01:00
Developer 02
8038ff74dd feat(Swagger): OpenApiInfo über appsettings konfiguriert. 2024-10-29 10:44:20 +01:00
Developer 02
edcf3781b7 feat(APIKeyAuthAttribute): Zu allen Controllern hinzugefügt 2024-10-29 10:21:10 +01:00
Developer 02
6ea053be36 feat(APIKeyAuthHeaderOpFilter): Implementierung der SwaggerGen.IOperationFilter-Schnittstelle, um das API-Schlüsselfeld hinzuzufügen.
- Eigenschaft swagger-description hinzugefügt
2024-10-29 10:00:06 +01:00
Developer 02
67a62d7311 feat(APIKeyAuthOptions): Datenmodell zur Konfiguration der Autorisierung mit API-Schlüssel erstellt.
- DI-Erweiterung hinzugefügt
2024-10-29 09:29:14 +01:00
Developer 02
e17875dad7 feat(API): Methode zur Injektion von Abhängigkeiten hinzugefügt, um API-Schlüssel-Filter hinzuzufügen 2024-10-28 16:58:50 +01:00
Developer 02
b460de4e37 feat: Attribut zur API-Schlüssel-Authentifizierung hinzufügen
- ApiKeyAuthAttribute hinzugefügt und mit ApiKeyAuthFilter verknüpft.
- Dieses Attribut wird verwendet, um die API-Schlüssel-Überprüfung in Controllern anzuwenden.
2024-10-28 16:48:58 +01:00
Developer 02
1ca336abe0 feat: API-Schlüssel-Authentifizierungsfilter implementieren
- ApiKeyAuthFilter hinzugefügt, um API-Schlüssel aus den Anforderungs-Headern zu validieren.
- Der Filter überprüft das Vorhandensein eines API-Schlüssels und dessen Gültigkeit, bevor die Anforderung autorisiert wird.
- Der Standardname des API-Schlüssel-Headers ist auf "X-API-Key" festgelegt.
2024-10-28 16:45:22 +01:00
Developer 02
6e4a575864 feat(API): NLogger hinzugefügt 2024-10-28 16:27:32 +01:00
Developer 02
d664adf000 refactor: Unnötige Verzeichnissuch-Cachinge-Prozesse entfernen 2024-10-28 16:13:55 +01:00
Developer 02
41151593fd feat: JWT-Service mit Unterstützung für UserReadDto-Ansprüche hinzufügen
- ToClaimDictionary-Erweiterungsmethode hinzugefügt, um die Ansprüche von UserReadDto in ein Wörterbuch zu konvertieren.
- JWT-Service konfiguriert, um Tokens mit Ansprüchen aus UserReadDto zu generieren.
- JWT-Service in die Authentifizierungskonfiguration integriert.
2024-10-25 14:51:50 +02:00
Developer 02
730b218eb5 feat: Erweiterungsmethode hinzugefügt, um UserReadDto in Claims-Liste zu konvertieren 2024-10-25 14:41:06 +02:00
Developer 02
ee99d40fb1 refactor(ProfileControlsTFCreateDto): Entfernte id. 2024-10-25 12:24:07 +02:00
Developer 02
364036b9e4 feat(API): Authentifizierungs-Cookie aktualisiert.
- ExpireTimeSpan als 1 Stunde zugewiesen.
 - SlidingExpiration als wahre Stunde zugewiesen.
 - Cookie-Name als 'AuthSession' zugewiesen.
2024-10-25 12:23:34 +02:00
Developer 02
27f68df6d7 Chore: Optionen zur Aktivierung von Swagger über die appsettings.json in der Produktion hinzugefügt. 2024-10-25 11:30:40 +02:00
Developer 02
02a7120413 feat(Model): LoginDto in Login umbenennen 2024-10-25 11:13:48 +02:00
Developer 02
d61140c349 feat(API): Verzeichnis-Suchdienst hinzugefügt 2024-10-25 10:58:02 +02:00
Developer 02
ef91358c96 feat(API): Cookie-basierter Lokalisierer hinzugefügt. 2024-10-25 10:54:06 +02:00
Developer 02
f2ab2a9759 feat(auth): Verbesserung der Login-Logik mit erweiterter Validierung und Fehlerbehandlung
- Überprüfungen hinzugefügt, um sicherzustellen, dass entweder 'UserId' oder 'Username' angegeben ist, jedoch nicht beide.
- Fehlermeldungen verbessert, um eine bessere Klarheit zu gewährleisten.
- Benutzerabfrage-Logik in der Login-Methode refaktoriert, um vorhandene Benutzerdaten nach Möglichkeit zu nutzen.
- Konsistente Protokollierung von Hinweisen und Fehlern für eine bessere Nachverfolgbarkeit sichergestellt.
2024-10-25 10:24:27 +02:00
Developer 02
0495dc10de feat: Eigenschaften zu LogInDto für Validierung und Null-Prüfungen hinzugefügt 2024-10-25 09:43:22 +02:00
Developer 02
9c41e7bb18 feat(API.Models): DTO erstellt, um sich sowohl über id-password als auch über username password anzumelden 2024-10-25 09:36:47 +02:00
Developer 02
65ad9e6da0 feat: Cookie-basierte Authentifizierung zur Anwendung hinzufügen
- `CookieAuthenticationDefaults.AuthenticationScheme` zur Benutzerauthentifizierung integriert.
- Cookie-Einstellungen konfiguriert, um die Sicherheit zu erhöhen:
  - `HttpOnly`-Flag gesetzt, um den Zugriff von clientseitigen Skripten zu verhindern.
  - `SecurePolicy` so eingestellt, dass Cookies nur über HTTPS-Anfragen gesendet werden.
  - `SameSite` auf `Strict` gesetzt, um CSRF-Angriffe zu mindern.
- Benutzerdefinierte Anmelde-(`/api/auth/login`) und Abmeldepfade (`/api/auth/logout`) definiert.
2024-10-25 01:45:17 +02:00
Developer 02
0ef327a059 feat: ProfileObjStateController für verbesserte CRUD-Funktionalität aktualisiert
- `GetAsync`-Methode mit zusätzlichen Filteroptionen für Profil-, Benutzer- und Zustandsdetails erweitert.
- Verbesserte Autorisierungsprüfungen mit detaillierter Fehlerprotokollierung bei fehlenden oder ungültigen Benutzer-ID-Ansprüchen.
- Identitätsprüfung in den Create- und Delete-Methoden hinzugefügt, um unbefugten Zugriff zu verhindern.
- Fehlerbehandlung und Antwort verfeinert für robustere serverseitige Verarbeitung.
2024-10-25 01:43:14 +02:00
Developer 02
2a9e0a8f17 feat(ProfileControlsTFController): HttpDelete-Attribut zur Delete-Methode hinzugefügt 2024-10-25 01:39:32 +02:00
Developer 02
d6aac0b400 refactor: ProfileControlsTFController aktualisiert, um CRUD-Operationen zu verbessern
- Create-, Update- und Delete-Methoden verfeinert, um eine bessere Validierung der Benutzeridentität zu gewährleisten
- Autorisierungsprüfungen für benutzerbezogene Operationen basierend auf Claims hinzugefügt
- Verbesserte Fehlerbehandlung und Protokollierung für detaillierteres Feedback
- Fehlerbehandlungs-Basisklasse entfernt, Übergang zu direkten CRUD-Methoden
2024-10-25 01:38:16 +02:00
Developer 02
6d25f8d3bd refactor: ProfileControlsTFController mit benutzerdefinierter GetAsync-Methode erweitert
- Neue GetAsync-Methode hinzugefügt, um komplexe Filter mit optionalen Parametern zu unterstützen
- Verbesserte Fehlerprotokollierung und Validierung für die Extraktion der Benutzeridentität
- Benutzer-ID in den Service-Schichtenoperationen integriert
- Basismethode GetAll entfernt, um eine bessere Kontrolle über das Datenabrufen zu gewährleisten
2024-10-25 01:07:59 +02:00
Developer 02
eb45c6aefa feat(UserConroller): Added method to get authorized user. 2024-10-25 00:39:05 +02:00
Developer 02
79167a7f9d refactor(AuthController): Verwendeter Primär-Konstruktor 2024-10-24 22:11:41 +02:00
Developer 02
2a81f33340 feat: Erweiterungsmethoden zum Extrahieren von Benutzerdetails in ControllerExtensions hinzugefügt
- Methoden implementiert, um Benutzer-ID, Benutzernamen, Nachnamen, Vornamen und E-Mail aus den Claims zu extrahieren
- Null-Überprüfungen und Parsing-Logik bereitgestellt, um eine gültige Extraktion sicherzustellen
2024-10-24 22:10:02 +02:00
Developer 02
4e5a68fa89 feat: robuste Authentifizierungslogik in AuthController implementiert
- Login-Endpunkt mit claims-basierter Identität und Cookie-Authentifizierung hinzugefügt
- Gruppenvalidierungslogik mit Autorisierungsprüfungen integriert
- Fehlerbehandlung und Authentifizierungsfluss mit Logging versehen
- Logout-Funktionalität mit ordnungsgemäßem Cookie-Abmeldungsprozess bereitgestellt
2024-10-24 21:04:16 +02:00
Developer 02
2d2f35c972 refactor(API): Ersetzte CRUDControllerBase mit CRUDControllerBaseWithErrorHandling auf allen Controllern. 2024-10-24 20:50:42 +02:00
Developer 02
c606fe4480 feat(API): StateController initialisiert. 2024-10-24 20:46:37 +02:00
Developer 02
fa26fad600 feat(API): ProfileObjStateController initialisiert. 2024-10-24 20:45:42 +02:00
Developer 02
6da7f33437 feat(API): ProfileControlsTF initialisiert. 2024-10-24 20:43:37 +02:00
Developer 02
6fcddfc7b9 feat(API): ProfileController initialisiert. 2024-10-24 20:42:37 +02:00
Developer 02
ae59ffe73b feat(API): ConfigController initialisiert. 2024-10-24 20:40:27 +02:00
Developer 02
94a2d414d3 feat(API): Dienste Work Flow und User Manager hinzugefügt. 2024-10-24 20:25:26 +02:00
Developer 02
c7f1be7c58 feat(WFDBContext): Implementiert UserManagerDbContext. 2024-10-24 20:16:08 +02:00
Developer 02
70d7ed7415 feat(API): DB-Kontext zu den Diensten hinzugefügt 2024-10-24 20:02:16 +02:00
Developer 02
0351f8733d feat(API): Erforderliche Abhängigkeiten hinzugefügt. 2024-10-24 19:10:06 +02:00
Developer 02
26b57e5475 feat(API): Initalisiert. 2024-10-24 19:08:35 +02:00
Developer 02
2c66112d4d feat(ProfileObjStateService): ReadAsync-Methode als Schnittstellenimplementierung erstellt. 2024-10-24 18:57:39 +02:00
Developer 02
5327249f5e feat(ProfileControlsTFService): ReadAsync-Methode als Schnittstellenimplementierung erstellt. 2024-10-24 15:40:23 +02:00
Developer 02
f300b640a2 refactor: Ersetzt 'ProfileControlsTFRepository' durch 'IProfileControlsTFRepository'. 2024-10-24 15:27:10 +02:00
Developer 02
6a062045bb refactor: Ersetzt 'ProfileControlsTFRepository' durch 'IProfileControlsTFRepository'. 2024-10-24 15:26:39 +02:00
Developer 02
22f69589c9 refactor: Ersetzte 'usrId' durch 'userId'. 2024-10-24 15:22:32 +02:00
Developer 02
ca94368d0b feat(ProfileObjStateRepository): Zwei asynchrone Lesemethoden wurden zusammengeführt. 2024-10-24 15:15:41 +02:00
Developer 02
05701c10d2 feat(ProfileControlsTFRepository): Zwei asynchrone Lesemethoden wurden zusammengeführt. 2024-10-24 15:04:46 +02:00
Developer 02
1b21ccecf3 feat(DIExtensions): Methoden zur Injektion von Workflow-Diensten und mit TryAddScoped-Methode erstellt.
- Umwandlung von AddScoped in TryAddScoped in der Methode AddWorkFlowRepositories.
 - Methode mit dem Namen AddWorkFlow erstellt, um Dienste und Repositories zusammen hinzuzufügen
2024-10-23 17:42:19 +02:00
Developer 02
56344afdc8 feat(Anwendung): Crud-Dienste für Config, ProfileControlsTF, ProfileObjState, Profile und State erstellt.
- Implementierte zugehörige Schnittstellen.
2024-10-23 17:27:00 +02:00
Developer 02
dbbe07405b feat(Application): Crud-Service-Schnittstellen für Config, ProfileControlsTF, ProfileObjState, Profile und State erstellt.
- IUnique-Schnittstellen für ProfileControlsTFUpdateDto, ProfileObjStateUpdateDto und StateUpdateDto implementiert.
2024-10-23 17:04:36 +02:00
Developer 02
908dd6f170 feat(BaseUpdateDto): Implementierte IUniqe-Schnittstelle 2024-10-23 16:47:40 +02:00
Developer 02
dfb3dc3d08 feat: Profil von Auto-Mapper erstellt. 2024-10-23 16:42:19 +02:00
Developer 02
9f175bc4e9 feat(State): Erstellen, Lesen und Aktualisieren von DTOs unter Verwendung von Basis-DTOs zum Aktualisieren und Erstellen. 2024-10-23 16:32:58 +02:00
Developer 02
1453f9adb1 feat(ProfileObjState: Erstellen, Lesen und Aktualisieren von DTOs unter Verwendung von Basis-DTOs zum Aktualisieren und Erstellen. 2024-10-23 16:32:39 +02:00
Developer 02
21956cfc16 feat(ProfileControlsTF): Erstellen, Lesen und Aktualisieren von DTOs unter Verwendung von Basis-DTOs zum Aktualisieren und Erstellen. 2024-10-23 15:45:29 +02:00
Developer 02
1e6d247817 feat(Profile): Erstellen, Lesen und Aktualisieren von DTOs unter Verwendung von Basis-DTOs zum Aktualisieren und Erstellen. 2024-10-23 15:10:37 +02:00
Developer 02
adc33bfee1 feat(Config): Erstellen, Lesen und Aktualisieren von DTOs unter Verwendung von Basis-DTOs zum Aktualisieren und Erstellen. 2024-10-23 14:57:15 +02:00
Developer 02
5ce6958122 feat: BaseUpdateDto zur Verfolgung von Aktualisierungen hinzufügen
- BaseUpdateDto eingeführt, um Metadaten zur Aktualisierung zu kapseln.
- Eigenschaften umfassen ChangedWho und ChangedWhen, beide mit JsonIgnore markiert, um von der Serialisierung ausgeschlossen zu werden.
2024-10-23 14:36:23 +02:00
Developer 02
23e2267d00 feat: BaseCreateDto zur Verfolgung von Erstellung hinzufügen
- BaseCreateDto eingeführt, um Metadaten zur Erstellung zu kapseln.
- Eigenschaften umfassen AddedWho und AddedWhen, beide mit JsonIgnore markiert, um von der Serialisierung ausgeschlossen zu werden.
2024-10-23 14:34:52 +02:00
Developer 02
bc9ac273ea feat: Initalisiertes WorkFlow.Application Projekt mit Abhängigkeiten. 2024-10-23 14:19:55 +02:00
Developer 02
b0896c214f feat: Überladungen der Read-Methode für ProfileObjStateRepository hinzufügen
- Überladene ReadAsync-Methoden in ProfileObjStateRepository implementiert, um Filterung nach Benutzer und Status zu ermöglichen.
- Die Abfrageeffizienz durch Einbeziehung verwandter Entitäten und Anwendung bedingter Filter verbessert.
2024-10-23 14:12:04 +02:00
Developer 02
e1f0d611e5 ProfileObjStateRepository mit Read-Methode erweitern
- Neue Read-Methode zur ProfileObjStateRepository hinzugefügt, um flexiblere Abfragen zu ermöglichen.
- Die Methode unterstützt optionale Parameter zur Filterung nach Profil, Benutzer, Zustand und Objekt-ID.
- Verbesserte Datenabrufmöglichkeiten durch Einbeziehung verwandter Entitäten wie Profil und Zustand.
2024-10-23 14:00:55 +02:00
Developer 02
8ff6bbf93f refactor(ProfileObjState): Benutzer-Eigenschaft hinzugefügt, abhängig von Fremdschlüssel UsrId 2024-10-23 13:51:17 +02:00
Developer 02
650b23def9 refactor(ProfileControlsTFRepository): Überschriebene ReadOnly-Methode, um Profile und ProfileObjState Entitäten standardmäßig einzuschließen. 2024-10-23 13:40:12 +02:00
Developer 02
859f0631f0 refactor(repository): ReadAsync-Methode aktualisieren, um readonly-Parameter zu unterstützen 2024-10-23 13:36:50 +02:00
Developer 02
31bf58919d refactor(ProfileControlsTFRepository): Überschriebene ReadOnly-Methode, um Profile und User Entitäten standardmäßig einzuschließen. 2024-10-23 13:25:57 +02:00
Developer 02
480dcce051 feat(repository): Überladung der ReadAsync-Methode mit Username- und usrId-Filter in ProfileControlsTFRepository hinzugefügt
- Überladung von `ReadAsync` hinzugefügt, um die Filterung nach `usrId` und `username` zu unterstützen.
- `ReadAsync` aktualisiert, um die Einzelabfrage mit `FirstOrDefaultAsync` zu ermöglichen.
- Abfrageflexibilität verbessert, indem Benutzer- und Profilfilter in beiden asynchronen Methoden zugelassen werden.
2024-10-23 13:19:31 +02:00
Developer 02
e0877f5990 feat(repository): Async-Read-Methode und Username-Filter in ProfileControlsTFRepository hinzugefügt
- `ReadAsync`-Methode für asynchrone Abfrageausführung hinzugefügt.
- `username`-Filter zur `Read`-Methode hinzugefügt.
- Filterlogik in der `Read`-Methode aktualisiert, um die `username`-Bedingung einzuschließen.
2024-10-23 13:11:27 +02:00
Developer 02
845f7fe729 feat(repository): Abfragefunktionalität mit Filtern in ProfileControlsTFRepository hinzugefügt
- Methode `Read` mit optionalen Filtern für `Profile`, `User`, `profileId`, `usrId`, `objId` und `profileActive` in `ProfileControlsTFRepository` hinzugefügt.
- `AsNoTracking` für schreibgeschützte Abfragen eingeführt.
- `Include` für verwandte `Profile` und `User` Entitäten in der Abfrage hinzugefügt.
2024-10-23 13:02:11 +02:00
Developer 02
a7081d3f74 feat(WfState): Wf-Präfix aus Entität, Repository und DbSet entfernt 2024-10-23 11:49:20 +02:00
Developer 02
e1ec8c581c feat(ProfileObjState.cs): Mwf-Präfix entfernt 2024-10-23 11:47:14 +02:00
Developer 02
c76f9d1709 feat(ProfControlsTf.cs): Umbenennung der Domäne, des Repository und des dbset in ProfileControlsTF 2024-10-23 11:44:11 +02:00
Developer 02
4bcac51473 feat(ProfControlsTf.cs): Removed the Mwf prefix from property names. 2024-10-23 11:28:27 +02:00
Developer 02
1f4c7589d0 feat(Config.cs): Umbenennung von ConfTitle in Title und ConfString in String 2024-10-23 11:25:39 +02:00
Developer 02
8290699b2f feat(Infrastructure): erstellt DIExtensions.
- AddWorkFlowRepositories-Methode hinzugefügt, um Repositories über Schnittstellen einzubinden
2024-10-23 11:11:09 +02:00
Developer 02
f611847e2a feat(Contracts): implementiert alle Repository-Schnittstellen mit CRUDRepository 2024-10-23 11:07:37 +02:00
Developer 02
3c5df5bc6a feat(Contracts): IWfStateRepository als eine Implementierung von ICRUDRepository erstellt. 2024-10-23 10:45:01 +02:00
Developer 02
3f70bdc8f7 feat(Contracts): IProfileObjStateRepository als eine Implementierung von ICRUDRepository erstellt. 2024-10-23 10:44:05 +02:00
Developer 02
f0b182fb94 feat(Contracts): IProfileRepository als eine Implementierung von ICRUDRepository erstellt 2024-10-23 10:42:46 +02:00
Developer 02
3d35c1ab21 feat(Contracts): IProfControlsTfRepository als eine Implementierung von ICRUDRepository erstellt 2024-10-23 10:41:25 +02:00
Developer 02
16f694eb67 feat(Contracts): IConfigRepository als eine Implementierung von ICRUDRepository erstellt 2024-10-23 10:39:52 +02:00
Developer 02
5dab28d99d feat(Entitäten): IUnique-Schnittstelle, die auf jede Entität angewendet wird. 2024-10-23 10:35:33 +02:00
Developer 02
1df7858423 refactor(ProfileObjState): Umbenennen der Eigenschaft 'Profile' in 'MwfProfile' 2024-10-23 10:31:34 +02:00
Developer 02
cb5a6afde0 feat(ProfileObjState): State Entität mit StateId-foreign-key hinzugefügt. 2024-10-23 10:26:50 +02:00
Developer 02
d4d6c29225 feat(ProfileObjState): Profil Entität mit MwfProfileId-foreign-key hinzugefügt. 2024-10-23 10:26:15 +02:00
Developer 02
dd15b520c1 feat(ProfControlsTf): User Entität mit UsrId-foreign-key hinzugefügt.
- UserManager.Domain baget-package hinzugefügt
2024-10-23 10:20:59 +02:00
72 changed files with 2258 additions and 225 deletions

View File

@@ -1,63 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_PROF_CONTROLS_TF", Schema = "dbo")]
public class ProfControlsTf
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("MWF_PROFILE_ID")]
public required int MwfProfileId { get; init; }
[Required]
[Column("USR_ID")]
public required int UsrId { 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("MwfProfileId")]
public Profile? MwfProfile { get; init; } = default;
}
}

View File

@@ -1,43 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_PROFILE", Schema = "dbo")]
public class Profile
{
[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,46 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_PROFILE_OBJ_STATE", Schema = "dbo")]
public class ProfileObjState
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("MWF_PROFILE_ID")]
public required int MwfProfileId { get; init; }
[Required]
[Column("USR_ID")]
public required int UsrId { 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; }
}
}

View File

@@ -1,25 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_WF_STATE", Schema = "dbo")]
public class WfState
{
[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,18 +0,0 @@
using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure
{
public class WFDBContext(DbContextOptions options) : DbContext(options)
{
public DbSet<Config> Configs { get; set; }
public DbSet<ProfControlsTf> ProfControlsTf { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<ProfileObjState> ProfileObjStates { get; set; }
public DbSet<WfState> WfStates { get; set; }
}
}

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,9 +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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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.API", "src\WorkFlow.API\WorkFlow.API.csproj", "{2B724243-4C79-F3A4-EE25-B9A53C81464C}"
EndProject
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
@@ -13,18 +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
{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

View File

@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Filters;
namespace WorkFlow.API.Attributes
{
//TODO: move APIKeyAuthAttribute to Core.API
public class APIKeyAuthAttribute : ServiceFilterAttribute
{
public APIKeyAuthAttribute()
: base(typeof(APIKeyAuthFilter))
{
}
}
}

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

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using WorkFlow.API.Models;
using WorkFlow.API.Attributes;
namespace WorkFlow.API.Controllers;
//TODO: implement up-to-date AuthController in UserManager
[APIKeyAuth]
[Route("api/Auth")]
[ApiController]
[Tags("Auth")]
public class PlaceholderAuthController : ControllerBase
{
[HttpPost]
public IActionResult CreateToken([FromBody] Login login)
{
throw new NotImplementedException();
}
[HttpGet("check")]
[Authorize]
public IActionResult Check() => Ok();
}

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,22 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using WorkFlow.API.Filters;
using WorkFlow.API.Models;
namespace WorkFlow.API.Extensions
{
public static class DIExtensions
{
public static IServiceCollection AddAPIKeyAuth(this IServiceCollection services, Func<string?, bool> isValidKey, string headerName = "X-API-Key")
=> services.AddSingleton<APIKeyAuthFilter>(provider => new(isValidKey: isValidKey, headerName: headerName));
public static IServiceCollection AddAPIKeyAuth(this IServiceCollection services, APIKeyAuthOptions options, bool configureOptions = true)
{
if(configureOptions)
services.TryAddSingleton(Options.Create(options));
return services.AddAPIKeyAuth(isValidKey: key => options.Key is null || options.Key == key, headerName: options.HeaderName);
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
namespace WorkFlow.API.Filters;
public class APIKeyAuthFilter : IAuthorizationFilter
{
private readonly Func<string?, bool> isValidKey;
private readonly string headerName;
public APIKeyAuthFilter(Func<string?, bool> isValidKey, string headerName = "X-API-Key")
{
this.isValidKey = isValidKey;
this.headerName = headerName;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!isValidKey(context.HttpContext.Request.Headers[headerName]))
context.Result = new UnauthorizedResult();
}
}

View File

@@ -0,0 +1,42 @@
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using WorkFlow.API.Models;
namespace WorkFlow.API.Filters;
public class APIKeyAuthHeaderOpFilter : IOperationFilter
{
private readonly APIKeyAuthOptions apiKeyAuthOptions;
private readonly IWebHostEnvironment environment;
public APIKeyAuthHeaderOpFilter(IOptions<APIKeyAuthOptions> options, IWebHostEnvironment environment)
{
this.environment = environment;
apiKeyAuthOptions = options.Value;
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var param = new OpenApiParameter
{
Name = apiKeyAuthOptions.HeaderName,
In = ParameterLocation.Header,
Required = true,
AllowEmptyValue = false,
Schema = new OpenApiSchema
{
Type = "string"
}
};
if(environment.IsDevelopment())
param.Schema.Default = new OpenApiString(apiKeyAuthOptions.Key);
if (apiKeyAuthOptions.SwaggerDescription is not null)
param.Description = apiKeyAuthOptions.SwaggerDescription;
operation.Parameters.Add(param);
}
}

10
src/WorkFlow.API/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,10 @@
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'dotnet build'
}
}
}
}

View File

@@ -0,0 +1,18 @@
namespace WorkFlow.API;
public class LazyServiceProvider : IServiceProvider
{
private Lazy<IServiceProvider>? _serviceProvider;
public Func<IServiceProvider> Factory
{
set => _serviceProvider = new(value);
}
public object? GetService(Type serviceType)
{
if (_serviceProvider is null)
throw new InvalidOperationException("GetService cannot be called before _serviceProvider is set.");
return _serviceProvider.Value.GetService(serviceType);
}
}

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

@@ -0,0 +1,11 @@
namespace WorkFlow.API.Models
{
public class APIKeyAuthOptions
{
public string? Key { get; init; } = null;
public string HeaderName { get; init; } = "X-API-Key";
public string? SwaggerDescription { get; init; } = null;
}
}

View File

@@ -0,0 +1,12 @@
namespace WorkFlow.API.Models;
public class AuthTokenKeys
{
public string Cookie { get; init; } = "AuthToken";
public string QueryString { get; init; } = "AuthToken";
public string Issuer { get; init; } = "auth.digitaldata.works";
public string Audience { get; init; } = "work-flow.digitaldata.works";
}

View File

@@ -0,0 +1,4 @@
namespace WorkFlow.API.Models
{
public record Login(int? UserId, string? Username, string Password);
}

View File

@@ -0,0 +1,19 @@
using DigitalData.UserManager.Application.DTOs.User;
using System.Security.Claims;
namespace WorkFlow.API.Models
{
public static class ModelExtensions
{
public static List<Claim> ToClaimList(this UserReadDto user) => new()
{
new (ClaimTypes.NameIdentifier, user.Id.ToString()),
new (ClaimTypes.Name, user.Username),
new (ClaimTypes.Surname, user.Name ?? ""),
new (ClaimTypes.GivenName, user.Prename ?? ""),
new (ClaimTypes.Email, user.Email ?? "")
};
public static Dictionary<string, object> ToClaimDictionary(this UserReadDto user) => user.ToClaimList().ToDictionary(claim => claim.Type, claim => (object) claim.Value);
}
}

181
src/WorkFlow.API/Program.cs Normal file
View File

@@ -0,0 +1,181 @@
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 Microsoft.OpenApi.Models;
using NLog;
using NLog.Web;
using WorkFlow.API;
using WorkFlow.API.Extensions;
using WorkFlow.API.Filters;
using WorkFlow.API.Middleware;
using WorkFlow.API.Models;
using WorkFlow.Application;
using WorkFlow.Infrastructure;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized.");
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.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());
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");
if (disableAPIKeyAuth)
builder.Services.AddAPIKeyAuth(new APIKeyAuthOptions());
else
if (config.GetSection("APIKeyAuth").Get<APIKeyAuthOptions>() is APIKeyAuthOptions options)
builder.Services.AddAPIKeyAuth(options);
else
throw new("The API Key Authorization configuration is not available in the app settings, even though the app is not in development or DiP mode and API Key Authorization is not disabled.");
var lazyProvider = new LazyServiceProvider();
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
builder.Services.AddControllers();
var authTokenKeys = config.GetSection(nameof(AuthTokenKeys)).Get<AuthTokenKeys>() ?? new();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
var clientParams = lazyProvider.GetRequiredService<IOptions<ClientParams>>()?.Value;
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(setupAct =>
{
setupAct.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer"
});
setupAct.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
if (!disableAPIKeyAuth)
setupAct.OperationFilter<APIKeyAuthHeaderOpFilter>();
if (config.GetSection("OpenApiInfo").Get<OpenApiInfo>() is OpenApiInfo openApiInfo)
setupAct.SwaggerDoc(openApiInfo?.Version ?? "v1", openApiInfo);
});
var app = builder.Build();
lazyProvider.Factory = () => app.Services;
app.UseMiddleware<ExceptionHandlingMiddleware>();
// Configure the HTTP request pipeline.
if (app.Configuration.GetValue<bool>("EnableSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
logger.Error(ex, "Stopped program because of exception.");
throw;
}

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:56180",
"sslPort": 44397
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5130",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7120;http://localhost:5130",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace WorkFlow.API
{
public static class WFKey
{
public static readonly string WrongPassword = nameof(WrongPassword);
public static readonly string UserNotFoundOrWrongPassword = nameof(UserNotFoundOrWrongPassword);
}
}

View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>WorkFlow.API</PackageId>
<Version>1.2.1</Version>
<Company>Digital Data GmbH</Company>
<Product>WorkFlow.API</Product>
<Title>WorkFlow.API</Title>
<AssemblyVersion>1.2.1</AssemblyVersion>
<FileVersion>1.2.1</FileVersion>
</PropertyGroup>
<ItemGroup>
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfile - Copy.pubxml" />
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet7.pubxml" />
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet8.pubxml" />
</ItemGroup>
<ItemGroup>
<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,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

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

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace WorkFlow.Application.Dto
{
public record BaseCreateDto
{
[JsonIgnore]
public required string AddedWho { get; set; } = "UNKNOWN";
[JsonIgnore]
public required DateTime AddedWhen = DateTime.Now;
}
}

View File

@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace WorkFlow.Application.Dto
{
public record BaseUpdateDto
{
public required int Id { get; init; }
[JsonIgnore]
public required string ChangedWho { get; set; } = "UNKNOWN";
[JsonIgnore]
public required DateTime ChangedWhen = DateTime.Now;
}
}

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

@@ -0,0 +1,10 @@
namespace WorkFlow.Application.Dto
{
public record ConfigDto(int Id,
string Title,
string String,
string AddedWho,
DateTime AddedWhen,
string? ChangedWho = null,
DateTime? ChangedWhen = null);
}

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));
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.Application" Version="3.3.4" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.0.1" />
<PackageReference Include="MediatR" Version="13.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
<PackageReference Include="UserManager" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,115 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_PROF_BUTTONS")]
public class Button
{
[Key]
[Column("GUID", TypeName = "int")]
public int Id { get; set; }
[Required]
[Column("MWF_PROFILE_ID", TypeName = "int")]
public required int ProfileId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("DIALOG_NO", TypeName = "tinyint")]
public byte? DialogNo { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("BTN_TYPE", TypeName = "nvarchar(20)")]
public string? BtnType { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("TEXT", TypeName = "nvarchar(500)")]
public string? Text { get; set; }
[Column("ICON", TypeName = "nvarchar(100)")]
public string? Icon { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("FORE_COLOR", TypeName = "nvarchar(100)")]
public string? ForeColor { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("BACK_COLOR", TypeName = "nvarchar(100)")]
public string? BackColor { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("COMMAND", TypeName = "nvarchar(max)")]
public string? Command { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("DIALOG_COMMAND", TypeName = "nvarchar(max)")]
public string? DialogCommand { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("CONFIRMATION_TEXT", TypeName = "nvarchar(250)")]
public string? ConfirmationText { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHO", TypeName = "nvarchar(100)")]
public string? AddedWho { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime? AddedWhen { get; set; }
[Column("CHANGED_WHO", TypeName = "nvarchar(100)")]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN", TypeName = "datetime")]
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -12,11 +12,11 @@ namespace WorkFlow.Domain.Entities
[Required]
[Column("CONF_TITLE", TypeName = "varchar(100)")]
public required string ConfTitle { get; init; }
public required string Title { get; init; }
[Required]
[Column("CONF_STRING", TypeName = "varchar(900)")]
public required string ConfString { get; init; }
public required string String { get; init; }
[Required]
[Column("ADDED_WHO", TypeName = "varchar(30)")]

View File

@@ -0,0 +1,94 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_PROF_CONTROLS_TF", Schema = "dbo")]
public class PControlsTF
{
[Key]
[Column("GUID", TypeName = "bigint")]
public long Id { get; set; }
[Required]
[Column("OBJ_STATE_ID", TypeName = "bigint")]
public long ObjStateId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("DIALOG_NO", TypeName = "tinyint")]
public byte? DialogNo { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ATTR_NAME", TypeName = "nvarchar(100)")]
public string? AttrName { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("CTRL_TYPE", TypeName = "nvarchar(10)")]
public string? CtrlType { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("CAPTION", TypeName = "nvarchar(100)")]
public string? Caption { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("TEXT", TypeName = "nvarchar(500)")]
public string? Text { get; set; }
[Column("ICON", TypeName = "nvarchar(100)")]
public string? Icon { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("MANDATORY", TypeName = "bit")]
public bool? Mandatory { get; set; }
[Column("CHOICE_LIST", TypeName = "nvarchar(max)")]
public string? ChoiceList { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("READ_ONLY", TypeName = "bit")]
public bool? ReadOnly { get; set; }
[Column("SEQU", TypeName = "tinyint")]
public byte? Sequ { get; set; }
[Column("ADDED_WHO", TypeName = "nvarchar(100)")]
public required string AddedWho { get; set; }
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime AddedWhen { get; set; }
}

View File

@@ -0,0 +1,69 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_PROFILE_CONTROLS_UPDATE")]
public class PControlsUpdate
{
[Column("GUID", TypeName = "bigint")]
public long Id { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("MWF_PROFILE_ID", TypeName = "int")]
public int ProfileId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("USR_ID", TypeName = "int")]
public int? UsrId { get; set; }
[Required]
[Column("OBJ_ID", TypeName = "bigint")]
public long ObjId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ATTR_NAME", TypeName = "nvarchar(100)")]
public string? AttrName { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ATTR_VALUE", TypeName = "nvarchar(3000)")]
public string? AttrValue { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHO", TypeName = "nvarchar(30)")]
public string? AddedWho { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime? AddedWhen { get; set; }
}

View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
public class PObject
{
[Column("ObjStateID")]
public long? ObjStateId { get; set; }
[Column("ObjectID")]
public long? Id { get; set; }
[Column("Headline1")]
public string? Headline1 { get; set; }
[Column("Headline2")]
public string? Headline2 { get; set; }
[Column("Subline1")]
public string? Subline1 { get; set; }
[Column("Subline2")]
public string? Subline2 { get; set; }
[Column("CMD_CheckIn")]
public string? CmdCheckIn { get; set; }
[ForeignKey("ObjStateId")]
public virtual PObjectState? State { get; set; }
public virtual IEnumerable<PObjectStateHist>? StateHistories { get; set; }
public virtual IEnumerable<PControlsUpdate>? ControlsUpdates { get; set; }
}

View File

@@ -0,0 +1,88 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_PROFILE_OBJ_STATE", Schema = "dbo")]
public class PObjectState
{
[Key]
[Column("GUID", TypeName = "bigint")]
public long Id { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("MWF_PROFILE_ID", TypeName = "int")]
public int? ProfileId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("USR_ID", TypeName = "int")]
public int? UserId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("OBJ_ID", TypeName = "bigint")]
public long? ObjectId { get; set; }
[Required]
[Column("STATE_ID", TypeName = "int")]
public int StateId { get; set; }
[Column("STATE2", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? State2 { get; set; }
[Column("STATE3", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? State3 { get; set; }
[Column("STATE4", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? State4 { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHO", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? AddedWho { get; set; } = "SYS";
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime? AddedWhen { get; set; } = DateTime.Now;
[Column("CHANGED_WHO", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN", TypeName = "datetime")]
public DateTime? ChangedWhen { get; set; }
[ForeignKey("StateId")]
public virtual State? State1 { get; set; }
public virtual IEnumerable<PControlsTF>? TFControls { get; set; }
public virtual IEnumerable<TfFile>? TfFiles { get; set; }
}

View File

@@ -0,0 +1,65 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_PROFILE_OBJ_STATE_HISTORY", Schema = "dbo")]
public class PObjectStateHist
{
[Key]
[Column("GUID", TypeName = "bigint")]
public long Id { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("MWF_PROFILE_ID", TypeName = "int")]
public int? ProfileId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("USR_ID", TypeName = "int")]
public int? UserId { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("OBJ_ID", TypeName = "bigint")]
public long? ObjectId { get; set; }
[Required]
[Column("STATE_ID", TypeName = "int")]
public int StateId { get; set; }
[Column("STATE2", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? State2 { get; set; }
[Column("STATE3", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? State3 { get; set; }
[Column("STATE4", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? State4 { get; set; }
[Column("CHANGED_WHO", TypeName = "nvarchar(100)")]
[StringLength(100)]
public string? ChangedWho { get; set; }
[Column("CHANGED_WHEN", TypeName = "datetime")]
public DateTime? ChangedWhen { get; set; }
[ForeignKey("StateId")]
public virtual State? State1 { get; set; }
}

View File

@@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
public class Profile
{
[Column("PROFILE_ID")]
public int? Id { get; set; }
[Column("TYPE_ID")]
public byte? TypeId { get; set; }
[Column("CAPTION")]
public string? Caption { get; set; }
[Column("SUBTITLE")]
public string? Subtitle { get; set; }
[Column("COUNTOBJ")]
public int? CountObj { get; set; }
[Column("FORE_COLOR")]
public string? ForeColor { get; set; }
[Column("BACK_COLOR")]
public string? BackColor { get; set; }
[NotMapped]
public IEnumerable<PObject>? Objects { get; set; }
[NotMapped]
public IEnumerable<Button>? Buttons { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_WF_STATE", Schema = "dbo")]
public class State
{
[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

@@ -0,0 +1,60 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_TF_FILES")]
public class TfFile
{
[Key]
[Column("GUID", TypeName = "bigint")]
public long Id { get; set; }
[Required]
[Column("OBJ_STATE_ID", TypeName = "bigint")]
public long ObjStateId { get; set; }
[Required]
[StringLength(512)]
[Column("F_FAPTH", TypeName = "nvarchar(512)")]
public string Path { get; set; } = null!;
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("HEADLINE", TypeName = "nvarchar(100)")]
public string? Headline { get; set; }
[StringLength(100)]
[Column("SUBLINE", TypeName = "nvarchar(100)")]
public string? Subline { get; set; }
[StringLength(250)]
[Column("COMMENT", TypeName = "nvarchar(250)")]
public string? Comment { get; set; }
[StringLength(100)]
[Column("ICON", TypeName = "nvarchar(100)")]
public string? Icon { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHO", TypeName = "nvarchar(100)")]
public string? AddedWho { get; set; }
/// <summary>
/// Although this field is marked as <c>nullable</c> in the database schema to allow
/// for greater flexibility during database-level operations or migrations, it is
/// treated as <c>required</c> and <c>not null</c> within the application logic.
/// Validation should be enforced at the application level to ensure a value is provided.
/// </summary>
[Column("ADDED_WHEN", TypeName = "datetime")]
public DateTime? AddedWhen { get; set; }
}

View File

@@ -1,9 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="UserManager" Version="1.1.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,27 @@
using DigitalData.Core.Infrastructure;
using DigitalData.Core.Infrastructure.AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Repositories;
namespace WorkFlow.Infrastructure;
public static class DependencyInjection
{
public static IServiceCollection AddWorkFlowRepositories(this IServiceCollection services)
{
services.TryAddScoped<IProfileRepository, ProfileRepository>();
services.TryAddScoped<IProfileObjRepository, PObjectRepository>();
services.AddDbRepository<WFDBContext, Config>(c => c.Configs).UseAutoMapper();
services.AddDbRepository<WFDBContext, PControlsTF>(c => c.ProfileControlsTFs).UseAutoMapper();
services.AddDbRepository<WFDBContext, State>(c => c.States).UseAutoMapper();
services.AddDbRepository<WFDBContext, Button>(c => c.Buttons).UseAutoMapper();
services.AddDbRepository<WFDBContext, State>(c => c.States).UseAutoMapper();
services.AddDbRepository<WFDBContext, PObjectState>(c => c.ProfileObjStates).UseAutoMapper();
return services;
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for retrieving <see cref="PObject"/> entities from the database.
/// </summary>
public class PObjectRepository : IProfileObjRepository
{
private readonly WFDBContext _context;
/// <summary>
/// Initializes a new instance of the <see cref="PObjectRepository"/> class.
/// </summary>
/// <param name="context">The database context used for accessing profile data.</param>
public PObjectRepository(WFDBContext context)
{
_context = context;
}
/// <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 async Task<IEnumerable<PObject>> ReadAsync(int userId, int profileId, CancellationToken cancel = default)
=> await _context.Objects
.FromSqlRaw("SELECT * FROM [FNMWF_GET_PROFILE_OBJECTS] ({0}, {1})", userId, profileId)
.Include(obj => obj.State).ThenInclude(objState => objState != null ? objState.State1 : null)
.Include(obj => obj.State).ThenInclude(objState => objState != null ? objState.TFControls : null)
.Include(obj => obj.State).ThenInclude(objState => objState != null ? objState.TfFiles : null)
.Include(obj => obj.StateHistories!).ThenInclude(hist => hist != null ? hist.State1 : null)
.Include(obj => obj.ControlsUpdates)
.ToListAsync(cancel);
}

View File

@@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for retrieving <see cref="Profile"/> entities from the database.
/// </summary>
public class ProfileRepository : IProfileRepository
{
private readonly ILogger<ProfileRepository> _logger;
private readonly WFDBContext _context;
/// <summary>
/// Initializes a new instance of the <see cref="ProfileRepository"/> class.
/// </summary>
/// <param name="context">The database context used for accessing profile data.</param>
/// <param name="logger">The logger instance used for logging repository operations.</param>
public ProfileRepository(WFDBContext context, ILogger<ProfileRepository> logger)
{
_logger = logger;
_context = context;
}
/// <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>
public async Task<Profile?> ReadAsync(int userId, CancellationToken cancel = default)
{
var profiles = await _context.Profiles
.FromSqlRaw("SELECT * FROM FNMWF_GET_PROFILES ({0})", userId)
.ToListAsync(cancel);
if (profiles == null || profiles.Count == 0)
{
_logger.LogError("No profile record was found associated with user ID {userId}.", userId);
}
else if (profiles.Count > 1)
{
_logger.LogError("Multiple profile records were found for user ID {userId}, which may indicate a data integrity issue.", userId);
}
return profiles?.FirstOrDefault();
}
}

View File

@@ -0,0 +1,72 @@
using DigitalData.UserManager.Domain.Entities;
using DigitalData.UserManager.Infrastructure;
using DigitalData.UserManager.Infrastructure.Contracts;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure;
public class WFDBContext : DbContext, IUserManagerDbContext
{
public DbSet<Config> Configs { get; set; }
public DbSet<PControlsTF> ProfileControlsTFs { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<PObject> Objects { get; set; }
public DbSet<PObjectState> ProfileObjStates { get; set; }
public DbSet<State> States { get; set; }
public DbSet<GroupOfUser> GroupOfUsers { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<ModuleOfUser> ModuleOfUsers { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserRep> UserReps { get; set; }
public DbSet<ClientUser> ClientUsers { get; set; }
public DbSet<Button> Buttons { get; set; }
public WFDBContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ConfigureUserManager();
modelBuilder.Entity<PObjectState>()
.HasMany(objState => objState.TFControls)
.WithOne()
.HasForeignKey(control => control.ObjStateId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<PObjectState>()
.HasMany(objState => objState.TfFiles)
.WithOne()
.HasForeignKey(file => file.ObjStateId);
modelBuilder.Entity<PObject>()
.HasMany(p => p.StateHistories)
.WithOne()
.HasForeignKey(hist => hist.ObjectId);
modelBuilder.Entity<PObject>()
.HasMany(obj => obj.ControlsUpdates)
.WithOne()
.HasForeignKey(cu => cu.ObjId)
.OnDelete(DeleteBehavior.Cascade);
base.OnModelCreating(modelBuilder);
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.1.1" />
<PackageReference Include="DigitalData.Core.Infrastructure.AutoMapper" Version="1.0.3" />
<PackageReference Include="UserManager" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
</ItemGroup>
</Project>