Compare commits

...

23 Commits

Author SHA1 Message Date
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
44 changed files with 508 additions and 368 deletions

View File

@ -5,6 +5,7 @@ VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1
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

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

@ -7,31 +7,11 @@ namespace WorkFlow.API.Controllers
[APIKeyAuth]
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);
public static bool TryGetUsername(this ClaimsPrincipal user, out string username)
{
var value = controller.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (value is null)
{
id = default;
return false;
}
if(int.TryParse(value, out int id_int))
{
id = id_int;
return true;
}
else
{
id = null;
return false;
}
}
public static bool TryGetUsername(this ControllerBase controller, out string username)
{
var value = controller.User.FindFirstValue(ClaimTypes.Name);
var value = user.FindFirstValue(ClaimTypes.Name);
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)
{
@ -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)
{
@ -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)
{

View File

@ -1,8 +1,8 @@
using Microsoft.AspNetCore.Authorization;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WorkFlow.API.Attributes;
using WorkFlow.Application.Contracts;
using WorkFlow.Domain.Entities;
using WorkFlow.Application.Profiles;
namespace WorkFlow.API.Controllers;
@ -12,56 +12,29 @@ namespace WorkFlow.API.Controllers;
[Authorize]
public class ProfileController : ControllerBase
{
public static readonly Profile Default = new()
{
Id = 1,
TypeId = 1,
Caption = "VA Freigabe",
Subtitle = "Freigabe in Rolle Verantwortlich",
CountObj = 2,
ForeColor = "Yellow",
BackColor = "Black",
Objects = new ProfileObject[]
{
new()
{
ObjStateId = 3,
ObjectId = 21601,
Headline1 = "Aveco Holding",
Headline2 = "Digital Data",
Subline1 = null,
Subline2 = "25 4711",
CmdCheckIn = ""
},
new()
{
ObjStateId = 4,
ObjectId = 21600,
Headline1 = "Aveco",
Headline2 = "Hammer AF",
Subline1 = null,
Subline2 = "456875",
CmdCheckIn = ""
}
}
};
private readonly ILogger<ProfileController> _logger;
private readonly IProfileService _service;
public ProfileController(ILogger<ProfileController> logger, IProfileService service)
private readonly IMediator _mediator;
public ProfileController(ILogger<ProfileController> logger, IMediator mediator)
{
_logger = logger;
_service = service;
_mediator = mediator;
}
[HttpGet]
[Authorize]
public IActionResult GetAsync()
public async Task<IActionResult> GetAsync()
{
try
{
return Ok(Default);
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)
{
@ -69,5 +42,4 @@ public class ProfileController : ControllerBase
return StatusCode(500);
}
}
}

View File

@ -35,15 +35,10 @@ public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTF
{
try
{
if (!this.TryGetUserId(out int? id))
if (!User.TryGetUserId(out var id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
return Unauthorized("Failed to retrieve user identity.");
}
return await _service.ReadAsync(
@ -71,16 +66,11 @@ public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTF
{
try
{
if (!this.TryGetUserId(out int? id))
if (!User.TryGetUserId(out var id))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (id is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
if (createDto.UserId != id)
return Unauthorized();
@ -100,16 +90,11 @@ public class ProfileControlsTFController : CRUDControllerBase<IProfileControlsTF
{
try
{
if (!this.TryGetUserId(out int? userId))
if (!User.TryGetUserId(out var userId))
{
logger.LogError("Authorization failed: User ID claim not found.");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to retrieve user identity.");
}
else if (userId is null)
{
logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
return StatusCode(StatusCodes.Status500InternalServerError, "Invalid user ID.");
}
return await _service.ReadByIdAsync(id).ThenAsync(
SuccessAsync: async pctf => pctf.UserId == userId ? await base.Delete(id) : Unauthorized(),

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@
</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="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
<PackageReference Include="NLog" Version="5.3.4" />
@ -30,6 +30,7 @@
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
<ProjectReference Include="..\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

@ -1,74 +1,10 @@
{
"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": {
@ -77,15 +13,5 @@
"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"
}
"MediatRLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg"
}

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

@ -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

@ -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

@ -0,0 +1,54 @@
using MediatR;
using WorkFlow.Application.Contracts.Repositories;
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 ReadProfile(int UserId, bool IncludeObject = true) : IRequest<Domain.Entities.Profile?>;
/// <summary>
/// Handles the <see cref="ReadProfile"/> request by retrieving the user profile
/// from the data store using the <see cref="IProfileRepository"/>.
/// </summary>
public class ReadProfileHandler : IRequestHandler<ReadProfile, Domain.Entities.Profile?>
{
private readonly IProfileRepository _profileRepository;
private readonly IProfileObjRepository _objRepository;
/// <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)
{
_profileRepository = profileRepository;
_objRepository = objRepository;
}
/// <summary>
/// Handles the <see cref="ReadProfile"/> 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<Domain.Entities.Profile?> Handle(ReadProfile 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);
return profile;
}
}
public static class ReadProfileExtensions
{
public static Task<Domain.Entities.Profile?> ReadProfileAsync(this IMediator mediator, int userId, bool includeObject = true)
=> mediator.Send(new ReadProfile(UserId: userId, IncludeObject: includeObject));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ public class Profile
public int? Id { get; set; }
[Column("TYPE_ID")]
public int? TypeId { get; set; }
public byte? TypeId { get; set; }
[Column("CAPTION")]
public string? Caption { get; set; }
@ -26,5 +26,5 @@ public class Profile
public string? BackColor { get; set; }
[NotMapped]
public IEnumerable<ProfileObject>? Objects;
public IEnumerable<ProfileObject>? Objects { get; set; }
}

View File

@ -1,18 +1,27 @@
namespace WorkFlow.Domain.Entities;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities;
public class ProfileObject
{
public int? ObjStateId { get; set; }
[Column("ObjStateID")]
public long? ObjStateId { get; set; }
public int? ObjectId { 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

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

View File

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

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

@ -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>();
return services;
}
}

View File

@ -1,15 +1,14 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories
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>
{
//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)
{
public ConfigRepository(WFDBContext dbContext) : base(dbContext, dbContext.Configs)
{
}
}
}

View File

@ -1,54 +1,53 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories
namespace WorkFlow.Infrastructure.Repositories;
public class ProfileControlsTFRepository : CRUDRepository<ProfileControlsTF, int, WFDBContext>, IProfileControlsTFRepository, ICRUDRepository<ProfileControlsTF, int>
{
public class ProfileControlsTFRepository : CRUDRepository<ProfileControlsTF, int, WFDBContext>, IProfileControlsTFRepository, ICRUDRepository<ProfileControlsTF, int>
public ProfileControlsTFRepository(WFDBContext dbContext) : base(dbContext, dbContext.ProfileControlsTFs)
{
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();
}
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);
}

View File

@ -1,8 +1,8 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories;

View File

@ -1,7 +1,55 @@
using WorkFlow.Infrastructure.Contracts;
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

@ -1,7 +1,7 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure;
using WorkFlow.Application.Contracts.Repositories;
using WorkFlow.Domain.Entities;
using WorkFlow.Infrastructure.Contracts;
namespace WorkFlow.Infrastructure.Repositories;

View File

@ -13,6 +13,8 @@ public class WFDBContext : DbContext, IUserManagerDbContext
public DbSet<ProfileControlsTF> ProfileControlsTFs { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<ProfileObject> Objects { get; set; }
public DbSet<ProfileObjState> ProfileObjStates { get; set; }

View File

@ -13,6 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
</ItemGroup>
</Project>