Compare commits

38 Commits

Author SHA1 Message Date
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
105 changed files with 963 additions and 564 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -17,4 +17,22 @@ public class ConfigController : CRUDControllerBaseWithErrorHandling<IConfigServi
public ConfigController(ILogger<ConfigController> logger, IConfigService service) : base(logger, service) public ConfigController(ILogger<ConfigController> logger, IConfigService service) : base(logger, service)
{ {
} }
[NonAction]
public override Task<IActionResult> Create(ConfigCreateDto createDto)
{
return base.Create(createDto);
}
[NonAction]
public override Task<IActionResult> Update(ConfigUpdateDto updateDto)
{
return base.Update(updateDto);
}
[NonAction]
public override Task<IActionResult> Delete([FromRoute] int id)
{
return base.Delete(id);
}
} }

View File

@@ -7,31 +7,11 @@ namespace WorkFlow.API.Controllers
[APIKeyAuth] [APIKeyAuth]
public static class ControllerExtensions public static class ControllerExtensions
{ {
public static bool TryGetUserId(this ControllerBase controller, out int? id) public static bool TryGetUserId(this ClaimsPrincipal user, out int id) => int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier), out id);
{
var value = controller.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (value is null) public static bool TryGetUsername(this ClaimsPrincipal user, out string username)
{ {
id = default; var value = user.FindFirstValue(ClaimTypes.Name);
return false;
}
if(int.TryParse(value, out int id_int))
{
id = id_int;
return true;
}
else
{
id = null;
return false;
}
}
public static bool TryGetUsername(this ControllerBase controller, out string username)
{
var value = controller.User.FindFirstValue(ClaimTypes.Name);
if (value is null) if (value is null)
{ {
@@ -45,9 +25,9 @@ namespace WorkFlow.API.Controllers
} }
} }
public static bool TryGetName(this ControllerBase controller, out string name) public static bool TryGetName(this ClaimsPrincipal user, out string name)
{ {
var value = controller.User.FindFirstValue(ClaimTypes.Surname); var value = user.FindFirstValue(ClaimTypes.Surname);
if (value is null) if (value is null)
{ {
@@ -61,9 +41,9 @@ namespace WorkFlow.API.Controllers
} }
} }
public static bool TryGetPrename(this ControllerBase controller, out string prename) public static bool TryGetPrename(this ClaimsPrincipal user, out string prename)
{ {
var value = controller.User.FindFirstValue(ClaimTypes.GivenName); var value = user.FindFirstValue(ClaimTypes.GivenName);
if (value is null) if (value is null)
{ {
@@ -77,9 +57,9 @@ namespace WorkFlow.API.Controllers
} }
} }
public static bool TryGetEmail(this ControllerBase controller, out string email) public static bool TryGetEmail(this ClaimsPrincipal user, out string email)
{ {
var value = controller.User.FindFirstValue(ClaimTypes.Email); var value = user.FindFirstValue(ClaimTypes.Email);
if (value is null) if (value is null)
{ {

View File

@@ -0,0 +1,45 @@
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()
{
try
{
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 profile is null ? NotFound() : Ok(profile);
}
catch (Exception ex)
{
_logger.LogError(ex, "{Message}", ex.Message);
return StatusCode(500);
}
}
}

View File

@@ -35,15 +35,10 @@ public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTF
{ {
try try
{ {
if (!this.TryGetUserId(out int? id)) if (!User.TryGetUserId(out var id))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return Unauthorized("Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
} }
return await _service.ReadAsync( return await _service.ReadAsync(
@@ -65,21 +60,17 @@ public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTF
} }
} }
[NonAction]
[HttpPost] [HttpPost]
public override async Task<IActionResult> Create([FromBody] ProfileControlsTFCreateDto createDto) public override async Task<IActionResult> Create([FromBody] ProfileControlsTFCreateDto createDto)
{ {
try try
{ {
if (!this.TryGetUserId(out int? id)) if (!User.TryGetUserId(out var id))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
} }
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id) if (createDto.UserId != id)
return Unauthorized(); return Unauthorized();
@@ -93,21 +84,17 @@ public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTF
} }
} }
[NonAction]
[HttpDelete] [HttpDelete]
public override async Task<IActionResult> Delete([FromRoute] int id) public override async Task<IActionResult> Delete([FromRoute] int id)
{ {
try try
{ {
if (!this.TryGetUserId(out int? userId)) if (!User.TryGetUserId(out var userId))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
} }
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync( return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(), SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),

View File

@@ -35,21 +35,16 @@ namespace WorkFlow.API.Controllers
{ {
try try
{ {
if (!this.TryGetUserId(out int? id)) if (!User.TryGetUserId(out var id))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return Unauthorized("Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
} }
return await _service.ReadAsync( return await _service.ReadAsync(
withProfile: withProfile, withUser: withUser, withState, withProfile: withProfile, withUser: withUser, withState,
userId: id, userId: id,
profileId: profileId, objId: objId, profileActive: profileActive) profileId: profileId, objId: objId)
.ThenAsync( .ThenAsync(
Success: pctf => pctf.Any() ? Ok(pctf) : NotFound(), Success: pctf => pctf.Any() ? Ok(pctf) : NotFound(),
Fail: IActionResult (msg, ntc) => Fail: IActionResult (msg, ntc) =>
@@ -70,16 +65,11 @@ namespace WorkFlow.API.Controllers
{ {
try try
{ {
if (!this.TryGetUserId(out int? id)) if (!User.TryGetUserId(out var id))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
} }
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id) if (createDto.UserId != id)
return Unauthorized(); return Unauthorized();
@@ -98,16 +88,11 @@ namespace WorkFlow.API.Controllers
{ {
try try
{ {
if (!this.TryGetUserId(out int? userId)) if (!User.TryGetUserId(out var userId))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
} }
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync( return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(), SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),

View File

@@ -17,4 +17,22 @@ public class StateController : CRUDControllerBaseWithErrorHandling<IStateService
public StateController(ILogger<StateController> logger, IStateService service) : base(logger, service) public StateController(ILogger<StateController> logger, IStateService service) : base(logger, service)
{ {
} }
[NonAction]
public override Task<IActionResult> Create(StateCreateDto createDto)
{
return base.Create(createDto);
}
[NonAction]
public override Task<IActionResult> Update(StateUpdateDto updateDto)
{
return base.Update(updateDto);
}
[NonAction]
public override Task<IActionResult> Delete([FromRoute] int id)
{
return base.Delete(id);
}
} }

View File

@@ -26,24 +26,19 @@ public class UserController : ControllerBase
{ {
try try
{ {
if (!this.TryGetUserId(out int? id)) if (!User.TryGetUserId(out var id))
{ {
logger.LogError("Authorization failed: User ID claim not found."); logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity."); return Unauthorized("Failed to retrieve user identity.");
} }
else if(id is int id_int)
return await userService.ReadByIdAsync(id_int).ThenAsync( return await userService.ReadByIdAsync(id).ThenAsync(
Success: Ok, Success: Ok,
Fail: IActionResult (msg, ntc) => Fail: IActionResult (msg, ntc) =>
{ {
logger.LogNotice(ntc); logger.LogNotice(ntc);
return NotFound(); return NotFound();
}); });
else
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using WorkFlow.API; using WorkFlow.API;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Abstractions.Security.Extensions;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized."); logger.Info("Logging initialized.");
@@ -26,14 +27,28 @@ try
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration; 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 // Add NLogger
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
if (!builder.Environment.IsDevelopment())
{
builder.Logging.ClearProviders(); builder.Logging.ClearProviders();
builder.Host.UseNLog(); builder.Host.UseNLog();
}
// Add services to the container // Add services to the container
var cnn_str = config.GetConnectionString("Default") ?? throw new("Default connection string not found."); var cnn_str = config.GetConnectionString("Default") ?? throw new("Default connection string not found.");
builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors()); builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors());
builder.Services.AddWorkFlow().AddUserManager<WFDBContext>(); 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).AddWorkFlowRepositories().AddUserManager<WFDBContext>();
builder.Services.AddCookieBasedLocalizer(); builder.Services.AddCookieBasedLocalizer();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions")); builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor() builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()

View File

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

View File

@@ -20,7 +20,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.3" /> <PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
<PackageReference Include="DigitalData.Core.API" Version="2.1.1" /> <PackageReference Include="DigitalData.Core.API" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
<PackageReference Include="NLog" Version="5.3.4" /> <PackageReference Include="NLog" Version="5.3.4" />
@@ -30,6 +30,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" /> <ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
<ProjectReference Include="..\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
{
"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,29 @@
using DigitalData.Core.Abstractions;
using MediatR;
namespace WorkFlow.Application.Buttons;
public record ButtonDto : IUnique<int>
{
public int Id { get; init; }
public required int ProfileId { get; init; }
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; }
}
public record ReadButtonRequest : IRequest<ButtonDto>;

View File

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

View File

@@ -0,0 +1,5 @@
namespace WorkFlow.Application.Contracts;
public interface IProfileService
{
}

View File

@@ -0,0 +1,9 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts.Repositories;
public interface IButtonRepository : ICRUDRepository<Button, int>
{
public Task<IEnumerable<Button>> ReadAllAsync(int profileId);
}

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts.Repositories;
/// <summary>
/// Repository for retrieving <see cref="ProfileObject"/> entities from the database.
/// </summary>
public interface IProfileObjRepository
{
/// <summary>
/// Retrieves the list of <see cref="ProfileObject"/> 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="ProfileObject"/> 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<ProfileObject>> ReadAsync(int userId, int profileId, CancellationToken cancel = default);
}

View File

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

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,8 @@
using DigitalData.Core.Abstractions.Infrastructure;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Application.Contracts.Repositories;
public interface IStateRepository : ICRUDRepository<State, int>
{
}

View File

@@ -16,6 +16,5 @@ namespace WorkFlow.Application.DTO.ProfileControlsTF
string AddedWho, string AddedWho,
DateTime AddedWhen, DateTime AddedWhen,
string? ChoiceList = null, string? ChoiceList = null,
ProfileDto? Profile = null,
UserReadDto? User = null); UserReadDto? User = null);
} }

View File

@@ -14,7 +14,6 @@ namespace WorkFlow.Application.DTO.ProfileObjState
string? State2 = null, string? State2 = null,
string? State3 = null, string? State3 = null,
string? State4 = null, string? State4 = null,
ProfileDto? Profile = null,
UserReadDto? User = null, UserReadDto? User = null,
StateDto? State = null); StateDto? State = null);
} }

View File

@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using WorkFlow.Application.Contracts;
using WorkFlow.Application.Services;
namespace WorkFlow.Application;
public static class DependencyInjection
{
public static IServiceCollection AddWorkFlowServices(this IServiceCollection services, Action<DIOptions>? options = null)
{
DIOptions diOptions = new();
options?.Invoke(diOptions);
services.AddAutoMapper(typeof(MappingProfile).Assembly);
services.TryAddScoped<IConfigService, ConfigService>();
services.TryAddScoped<IProfileControlsTFService, ProfileControlsTFService>();
services.TryAddScoped<IProfileObjStateService, ProfileObjStateService>();
services.TryAddScoped<IProfileService, ProfileService>();
services.TryAddScoped<IStateService, StateService>();
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly);
cfg.LicenseKey = diOptions.MediatRLicense;
});
return services;
}
public class DIOptions
{
public string MediatRLicense { get; set; } = string.Empty;
}
}

View File

@@ -1,9 +1,12 @@
using AutoMapper; using AutoMapper;
using WorkFlow.Application.Buttons;
using WorkFlow.Application.DTO.Config; using WorkFlow.Application.DTO.Config;
using WorkFlow.Application.DTO.Profile; using WorkFlow.Application.DTO.Profile;
using WorkFlow.Application.DTO.ProfileControlsTF; using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Application.DTO.ProfileObjState; using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Application.DTO.State; using WorkFlow.Application.DTO.State;
using WorkFlow.Application.Objects;
using WorkFlow.Application.Profiles;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
namespace WorkFlow.Application namespace WorkFlow.Application
@@ -18,6 +21,13 @@ namespace WorkFlow.Application
CreateMap<ProfileControlsTF, ProfileControlsTFDto>(); CreateMap<ProfileControlsTF, ProfileControlsTFDto>();
CreateMap<ProfileObjState, ProfileObjStateDto>(); CreateMap<ProfileObjState, ProfileObjStateDto>();
CreateMap<State, StateDto>(); CreateMap<State, StateDto>();
CreateMap<Button, ButtonDto>();
CreateMap<ProfileObject, 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 }));
// Mapping create-DTO to entity // Mapping create-DTO to entity
CreateMap<ConfigCreateDto, Config>(); CreateMap<ConfigCreateDto, Config>();

View File

@@ -0,0 +1,19 @@
using MediatR;
namespace WorkFlow.Application.Objects;
public class ObjectDto
{
public long? ObjStateId { get; set; }
public long? Id { get; set; }
public IEnumerable<string> Headlines { get; set; } = Array.Empty<string>();
public IEnumerable<string> Sublines { get; set; } = Array.Empty<string>();
public string? CmdCheckIn { get; set; }
}
public record ReadObjectRequest : IRequest
{
}

View File

@@ -0,0 +1,92 @@
using AutoMapper;
using MediatR;
using WorkFlow.Application.Buttons;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Application.Objects;
namespace WorkFlow.Application.Profiles;
public class ProfileDto
{
public int? Id { get; init; }
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>();
}
/// <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 ReadProfileRequest(int UserId, bool IncludeObject = true) : IRequest<ProfileDto?>;
/// <summary>
/// Handles the <see cref="ReadProfileRequest"/> request by retrieving the user profile
/// from the data store using the <see cref="IProfileRepository"/>.
/// </summary>
public class ReadProfileHandler : IRequestHandler<ReadProfileRequest, ProfileDto?>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfileObjRepository _objRepository;
private readonly IButtonRepository _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, IButtonRepository buttonRepository, IMapper mapper)
{
_profileRepository = profileRepository;
_objRepository = objRepository;
_bttnRepository = buttonRepository;
_mapper = mapper;
}
/// <summary>
/// Handles the <see cref="ReadProfileRequest"/> 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(ReadProfileRequest request, CancellationToken cancel = default)
{
var profile = await _profileRepository.ReadAsync(request.UserId, cancel);
if (request.IncludeObject && profile?.Id is int profileId)
profile.Objects = await _objRepository.ReadAsync(request.UserId, profileId, cancel);
var profileDto = _mapper.Map<ProfileDto>(profile);
if (profile?.Id is int pId)
{
var bttns = await _bttnRepository.ReadAllAsync(pId);
profileDto.Buttons = _mapper.Map<IEnumerable<ButtonDto>>(bttns);
}
return profileDto;
}
}
public static class ReadProfileExtensions
{
public static Task<ProfileDto?> ReadProfileAsync(this IMediator mediator, int userId, bool includeObject = true)
=> mediator.Send(new ReadProfileRequest(UserId: userId, IncludeObject: includeObject));
}

View File

@@ -4,7 +4,7 @@ using DigitalData.Core.Application;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.Config; using WorkFlow.Application.DTO.Config;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Application.Contracts.Repositories;
namespace WorkFlow.Application.Services; namespace WorkFlow.Application.Services;

View File

@@ -5,7 +5,7 @@ using DigitalData.Core.DTO;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileControlsTF; using WorkFlow.Application.DTO.ProfileControlsTF;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Application.Contracts.Repositories;
namespace WorkFlow.Application.Services namespace WorkFlow.Application.Services
{ {

View File

@@ -5,7 +5,7 @@ using DigitalData.Core.DTO;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.ProfileObjState; using WorkFlow.Application.DTO.ProfileObjState;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Application.Contracts.Repositories;
namespace WorkFlow.Application.Services; namespace WorkFlow.Application.Services;
@@ -19,13 +19,13 @@ public class ProfileObjStateService : CRUDService<IProfileObjStateRepository, Pr
public async Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync( public async Task<DataResult<IEnumerable<ProfileObjStateDto>>> ReadAsync(
bool withProfile = true, bool withUser = true, bool withState = true, bool withProfile = true, bool withUser = true, bool withState = true,
int? userId = null, string? username = null, int? userId = null, string? username = null,
int? profileId = null, int? objId = null, bool? profileActive = null) int? profileId = null, int? objId = null)
{ {
var pos_list = await _repository.ReadAsync( var pos_list = await _repository.ReadAsync(
isReadonly: true, isReadonly: true,
withProfile: withProfile, withUser: withUser, withState: withState, withProfile: withProfile, withUser: withUser, withState: withState,
userId: userId, username: username, userId: userId, username: username,
profileId: profileId, objId: objId, profileActive: profileActive); profileId: profileId, objId: objId);
var post_dto_list = _mapper.Map<IEnumerable<ProfileObjStateDto>>(pos_list); var post_dto_list = _mapper.Map<IEnumerable<ProfileObjStateDto>>(pos_list);

View File

@@ -0,0 +1,7 @@
using WorkFlow.Application.Contracts;
namespace WorkFlow.Application.Services;
public class ProfileService : IProfileService
{
}

View File

@@ -4,7 +4,7 @@ using DigitalData.Core.Application;
using WorkFlow.Application.Contracts; using WorkFlow.Application.Contracts;
using WorkFlow.Application.DTO.State; using WorkFlow.Application.DTO.State;
using WorkFlow.Domain.Entities; using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts; using WorkFlow.Application.Contracts.Repositories;
namespace WorkFlow.Application.Services; namespace WorkFlow.Application.Services;

View File

@@ -8,12 +8,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DigitalData.Core.Application" Version="3.2.0" /> <PackageReference Include="DigitalData.Core.Application" Version="3.2.0" />
<PackageReference Include="MediatR" Version="13.0.0" />
<PackageReference Include="UserManager.Application" Version="3.1.2" /> <PackageReference Include="UserManager.Application" Version="3.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" /> <ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
<ProjectReference Include="..\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,116 @@
using DigitalData.Core.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
[Table("TBMWF_PROF_BUTTONS")]
public class Button: IUnique<int>
{
[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

@@ -0,0 +1,30 @@
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<ProfileObject>? Objects { get; set; }
}

View File

@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
public class ProfileObject
{
[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; }
}

View File

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

View File

@@ -0,0 +1,17 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Repositories;
//TODO: Make the db context type generic so that it can be used by other projects with different db contexts.
public class ButtonRepository : CRUDRepository<Button, int, WFDBContext>, IButtonRepository, ICRUDRepository<Button, int>
{
public ButtonRepository(WFDBContext dbContext) : base(dbContext, dbContext.Buttons)
{
}
public async Task<IEnumerable<Button>> ReadAllAsync(int profileId) => await _dbSet.Where(b => b.ProfileId == profileId).ToListAsync();
}

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for retrieving <see cref="ProfileObject"/> entities from the database.
/// </summary>
public class ProfileObjRepository : IProfileObjRepository
{
private readonly WFDBContext _context;
/// <summary>
/// Initializes a new instance of the <see cref="ProfileObjRepository"/> class.
/// </summary>
/// <param name="context">The database context used for accessing profile data.</param>
public ProfileObjRepository(WFDBContext context)
{
_context = context;
}
/// <summary>
/// Retrieves the list of <see cref="ProfileObject"/> 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="ProfileObject"/> 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<ProfileObject>> ReadAsync(int userId, int profileId, CancellationToken cancel = default)
=> await _context.Objects
.FromSqlRaw("SELECT * FROM [FNMWF_GET_PROFILE_OBJECTS] ({0}, {1})", userId, profileId)
.ToListAsync(cancel);
}

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