chore: alle Projekte in das Verzeichnis src verschieben

This commit is contained in:
2025-07-18 14:48:28 +02:00
parent a5bffdf1ce
commit 1fcdcf6c0a
76 changed files with 28 additions and 72 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,161 @@
using WorkFlow.Application;
using DigitalData.UserManager.Application;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Infrastructure;
using DigitalData.Core.API;
using DigitalData.Core.Application;
using DigitalData.UserManager.Application.DTOs.User;
using Microsoft.IdentityModel.Tokens;
using WorkFlow.API.Models;
using NLog;
using NLog.Web;
using WorkFlow.API.Extensions;
using WorkFlow.API.Filters;
using Microsoft.OpenApi.Models;
using DigitalData.Auth.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using WorkFlow.API;
using Microsoft.Extensions.Options;
using DigitalData.Core.Abstractions.Security;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized.");
try
{
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
// Add NLogger
builder.Logging.ClearProviders();
builder.Host.UseNLog();
// Add services to the container
var cnn_str = config.GetConnectionString("Default") ?? throw new("Default connection string not found.");
builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors());
builder.Services.AddWorkFlow().AddUserManager<WFDBContext>();
builder.Services.AddCookieBasedLocalizer();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
{
Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object)
});
bool disableAPIKeyAuth = config.GetValue<bool>("DisableAPIKeyAuth") && builder.IsDevOrDiP();
if (disableAPIKeyAuth)
builder.Services.AddAPIKeyAuth(new APIKeyAuthOptions());
else
if (config.GetSection("APIKeyAuth").Get<APIKeyAuthOptions>() is APIKeyAuthOptions options)
builder.Services.AddAPIKeyAuth(options);
else
throw new("The API Key Authorization configuration is not available in the app settings, even though the app is not in development or DiP mode and API Key Authorization is not disabled.");
var lazyProvider = new LazyServiceProvider();
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
builder.Services.AddControllers();
var authTokenKeys = config.GetSection(nameof(AuthTokenKeys)).Get<AuthTokenKeys>() ?? new();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
var clientParams = lazyProvider.GetRequiredService<IOptions<ClientParams>>()?.Value;
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(setupAct =>
{
setupAct.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer"
});
setupAct.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
if (!disableAPIKeyAuth)
setupAct.OperationFilter<APIKeyAuthHeaderOpFilter>();
if (config.GetSection("OpenApiInfo").Get<OpenApiInfo>() is OpenApiInfo openApiInfo)
setupAct.SwaggerDoc(openApiInfo?.Version ?? "v1", openApiInfo);
});
var app = builder.Build();
lazyProvider.Factory = () => app.Services;
// Configure the HTTP request pipeline.
if (app.IsDevOrDiP() && app.Configuration.GetValue<bool>("EnableSwagger"))
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseCookieBasedLocalizer("de-DE");
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
logger.Error(ex, "Stopped program because of exception.");
throw;
}

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>WorkFlow.API</PackageId>
<Version>1.1.0</Version>
<Company>Digital Data GmbH</Company>
<Product>WorkFlow.API</Product>
<Title>WorkFlow.API</Title>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfile - Copy.pubxml" />
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet7.pubxml" />
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet8.pubxml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.3" />
<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" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,91 @@
{
"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

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

View File

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

View File

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

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

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

View File

@@ -0,0 +1,25 @@
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,13 @@
using System.Text.Json.Serialization;
namespace WorkFlow.Application.DTO
{
public record BaseCreateDto
{
[JsonIgnore]
public required string AddedWho { get; set; } = "UNKNOWN";
[JsonIgnore]
public required DateTime AddedWhen = DateTime.Now;
}
}

View File

@@ -0,0 +1,16 @@
using DigitalData.Core.Abstractions;
using System.Text.Json.Serialization;
namespace WorkFlow.Application.DTO
{
public record BaseUpdateDto : IUnique<int>
{
public required int Id { get; init; }
[JsonIgnore]
public required string ChangedWho { get; set; } = "UNKNOWN";
[JsonIgnore]
public required DateTime ChangedWhen = DateTime.Now;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DigitalData.Core.Application" Version="3.2.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

@@ -0,0 +1,36 @@
using DigitalData.Core.Abstractions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace WorkFlow.Domain.Entities
{
[Table("TBMWF_CONFIG", Schema = "dbo")]
public class Config : IUnique<int>
{
[Key]
[Column("GUID")]
public int Id { get; init; }
[Required]
[Column("CONF_TITLE", TypeName = "varchar(100)")]
public required string Title { get; init; }
[Required]
[Column("CONF_STRING", TypeName = "varchar(900)")]
public required string String { 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; }
}
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

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

View File

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

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

View File

@@ -0,0 +1,46 @@
using DigitalData.UserManager.Domain.Entities;
using DigitalData.UserManager.Infrastructure;
using DigitalData.UserManager.Infrastructure.Contracts;
using Microsoft.EntityFrameworkCore;
using WorkFlow.Domain.Entities;
namespace WorkFlow.Infrastructure;
public class WFDBContext : DbContext, IUserManagerDbContext
{
public DbSet<Config> Configs { get; set; }
public DbSet<ProfileControlsTF> ProfileControlsTFs { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<ProfileObjState> ProfileObjStates { get; set; }
public DbSet<State> States { get; set; }
public DbSet<GroupOfUser> GroupOfUsers { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<ModuleOfUser> ModuleOfUsers { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserRep> UserReps { get; set; }
public DbSet<ClientUser> ClientUsers { get; set; }
public WFDBContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//configure model builder for user manager tables
modelBuilder.ConfigureUserManager();
base.OnModelCreating(modelBuilder);
}
}

View File

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