Compare commits
125 Commits
41151593fd
...
feat/profi
| Author | SHA1 | Date | |
|---|---|---|---|
| c10f3c1b58 | |||
| b71e451121 | |||
| d4ea68fc0e | |||
| 142a1a4faa | |||
| 66fe515518 | |||
| f54329ecd3 | |||
| 9b7475bb56 | |||
| 1d0ded0e84 | |||
| 9332a9161d | |||
| 6a04f36388 | |||
| 581bd22c24 | |||
| eafdc17b70 | |||
| c952df5bb4 | |||
| 8c6202d7c0 | |||
| 288f8f98bd | |||
| 659a402555 | |||
| 6288312c01 | |||
| 91679180ec | |||
| bdc773d8ed | |||
| cfbd0f013d | |||
| d4b33d4b9a | |||
| 4e5cb91967 | |||
| b859391ab1 | |||
| 26300d8653 | |||
| 9d07b1e71c | |||
| ec975a2bc3 | |||
| f10f5af541 | |||
| 7d07fc58e9 | |||
| 7e82f688ad | |||
| c325b2122b | |||
| 63adb51263 | |||
| 363606dc61 | |||
| bc192e99a7 | |||
| 69d417616d | |||
| a3cbe69fd6 | |||
| c2e8b335e0 | |||
| 7ed86f18d7 | |||
| 2fd64cb616 | |||
| b89a69b0f3 | |||
| bb29b1563a | |||
| ad023b01d3 | |||
| 7309b968fe | |||
| 1159f3f575 | |||
| 8f2261f0fa | |||
| c779dd4a47 | |||
| 709ebea097 | |||
| 63df235943 | |||
| 78f2788388 | |||
| 13acf6de08 | |||
| 5466b35b95 | |||
| d0e306b7e4 | |||
| 4fcc0a08b8 | |||
| 904536bd09 | |||
| 6c0f39e3ed | |||
| 8ceaa9cb21 | |||
| 27e4b4b2ef | |||
| 82eb03b420 | |||
| 559127a931 | |||
|
|
87857862e4 | ||
| 8655f78c8c | |||
| 168a4b0791 | |||
| dd4cd1b39e | |||
| 8eb8801c41 | |||
| eb7ed81cac | |||
| b7c6477ec2 | |||
| b7f9efa9b6 | |||
| bf5566cefc | |||
| f8e51e02a0 | |||
| ad1fd3163e | |||
| bed5fae01c | |||
| a378862791 | |||
| dd2dbee037 | |||
| b24f518bba | |||
| dd5babfdbe | |||
| dc7da91872 | |||
| fe358623da | |||
| c08c5aacf3 | |||
| 14f5c73d43 | |||
| b25d4eb028 | |||
| 8c08beba4e | |||
| 30bb3ffa11 | |||
| a9faf74803 | |||
| 22e4b4f54f | |||
| a954a24888 | |||
| a78c117a47 | |||
| 07e16f8aca | |||
| 0b70016ab6 | |||
| 537891b8c5 | |||
| f8be2d9f26 | |||
| 547d723f47 | |||
| 1fcdcf6c0a | |||
| a5bffdf1ce | |||
|
|
3832351dd1 | ||
|
|
99237cbecc | ||
|
|
cb2edffe91 | ||
|
|
4d3768248e | ||
|
|
fb38bc1fd4 | ||
|
|
10b557374d | ||
|
|
f266e6728f | ||
|
|
3373fceef3 | ||
|
|
f7eaa0f7de | ||
|
|
d5b1ee41a0 | ||
|
|
c3f5d90b6a | ||
|
|
753eb18b71 | ||
|
|
17d8373739 | ||
|
|
d6ccc10244 | ||
|
|
3dccf82710 | ||
|
|
c7d8b67ccb | ||
|
|
b76043fa24 | ||
|
|
e28f4560d6 | ||
|
|
97d5156bbb | ||
|
|
40cf8f3f10 | ||
|
|
a325d07c6b | ||
|
|
69abd3afa2 | ||
|
|
cbdd6ee295 | ||
|
|
2c1abaaf32 | ||
|
|
8038ff74dd | ||
|
|
edcf3781b7 | ||
|
|
6ea053be36 | ||
|
|
67a62d7311 | ||
|
|
e17875dad7 | ||
|
|
b460de4e37 | ||
|
|
1ca336abe0 | ||
|
|
6e4a575864 | ||
|
|
d664adf000 |
@@ -1,154 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using DigitalData.UserManager.Application.Contracts;
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using DigitalData.UserManager.Application;
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using DigitalData.Core.DTO;
|
||||
using WorkFlow.API.Models;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class AuthController(IUserService userService, IGroupOfUserService gouService, IDirectorySearchService directorySearchService, IStringLocalizer<Resource> localizer, ILogger<AuthController> logger) : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService = userService;
|
||||
private readonly IGroupOfUserService _gouService = gouService;
|
||||
private readonly IDirectorySearchService _dirSearchService = directorySearchService;
|
||||
private readonly IStringLocalizer<Resource> _localizer = localizer;
|
||||
private readonly ILogger<AuthController> _logger = logger;
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("check")]
|
||||
public IActionResult CheckAuthentication()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(User.Identity?.IsAuthenticated ?? false);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message}", ex.Message);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] Login login)
|
||||
{
|
||||
try
|
||||
{
|
||||
var username = string.Empty;
|
||||
DataResult<UserReadDto>? uRes = null;
|
||||
|
||||
if(login.Username is not null && login.UserId is not null)
|
||||
return BadRequest("Invalid request: either 'UserId' or 'Username' must be provided, but not both.");
|
||||
else if(login.Username is not null)
|
||||
username = login.Username;
|
||||
else if(login.UserId is int userId)
|
||||
{
|
||||
uRes = await _userService.ReadByIdAsync(userId);
|
||||
if (!uRes.IsSuccess || uRes.Data is null)
|
||||
{
|
||||
return Unauthorized(uRes);
|
||||
}
|
||||
}
|
||||
else
|
||||
return BadRequest("Invalid request: either 'UserId' or 'Username' must be provided, but not both.");
|
||||
|
||||
bool isValid = _dirSearchService.ValidateCredentials(username, login.Password);
|
||||
|
||||
if (!isValid)
|
||||
return Unauthorized(Result.Fail().Message(_localizer[Key.UserNotFound]));
|
||||
|
||||
var gouMsg = await _gouService.HasGroup(username, "PM_USER", caseSensitive: false);
|
||||
if (!gouMsg.IsSuccess)
|
||||
return Unauthorized(Result.Fail().Message(_localizer[Key.UnauthorizedUser]));
|
||||
|
||||
//find the user
|
||||
uRes ??= await _userService.ReadByUsernameAsync(username);
|
||||
if (!uRes.IsSuccess || uRes.Data is null)
|
||||
{
|
||||
_logger.LogNotice(uRes.Notices);
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
UserReadDto user = uRes.Data;
|
||||
|
||||
// Create claimsIdentity
|
||||
var claimsIdentity = new ClaimsIdentity(user.ToClaimList(), CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
// Create authProperties
|
||||
var authProperties = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
AllowRefresh = true,
|
||||
ExpiresUtc = DateTime.UtcNow.AddMinutes(60)
|
||||
};
|
||||
|
||||
// Sign in
|
||||
await HttpContext.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(claimsIdentity),
|
||||
authProperties);
|
||||
|
||||
_dirSearchService.SetSearchRootCache(user.Username, login.Password);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message}", ex.Message);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("user")]
|
||||
public async Task<IActionResult> GetUserWithClaims()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Extract the username from the Name claim.
|
||||
string? username = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(username))
|
||||
return Unauthorized();
|
||||
|
||||
return await _userService.ReadByUsernameAsync(username)
|
||||
.ThenAsync(Ok, IActionResult (m, n) =>
|
||||
{
|
||||
_logger.LogNotice(n);
|
||||
return NotFound(Result.Fail().Message(_localizer[Key.UserNotFound]));
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message}", ex.Message);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
try
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Ok();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message}", ex.Message);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using DigitalData.Core.API;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.Config;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ConfigController(ILogger<ConfigController> logger, IConfigService service) : CRUDControllerBaseWithErrorHandling<IConfigService, ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>(logger, service)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using DigitalData.Core.API;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.Profile;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ProfileController(ILogger<ProfileController> logger, IProfileService service) : CRUDControllerBaseWithErrorHandling<IProfileService, ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>(logger, service)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using DigitalData.Core.API;
|
||||
using DigitalData.Core.DTO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.ProfileControlsTF;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ProfileControlsTFController(ILogger<ProfileControlsTFController> logger, IProfileControlsTFService service) : CRUDControllerBase<IProfileControlsTFService, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(logger, service)
|
||||
{
|
||||
[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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using DigitalData.Core.API;
|
||||
using DigitalData.Core.DTO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.ProfileObjState;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ProfileObjStateController(ILogger<ProfileObjStateController> logger, IProfileObjStateService service) : CRUDControllerBaseWithErrorHandling<IProfileObjStateService, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>(logger, service)
|
||||
{
|
||||
[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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using DigitalData.Core.API;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.State;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class StateController(ILogger<StateController> logger, IStateService service) : CRUDControllerBaseWithErrorHandling<IStateService, StateCreateDto, StateDto, StateUpdateDto, State, int>(logger, service)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using DigitalData.Core.DTO;
|
||||
using DigitalData.UserManager.Application.Contracts;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace WorkFlow.API.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class UserController(ILogger<UserController> logger, IUserService userService) : ControllerBase
|
||||
{
|
||||
[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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using WorkFlow.Application;
|
||||
using DigitalData.UserManager.Application;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WorkFlow.Infrastructure;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using DigitalData.Core.API;
|
||||
using DigitalData.Core.Application;
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using WorkFlow.API.Models;
|
||||
using System.Security.Claims;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var config = builder.Configuration;
|
||||
|
||||
// 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.ConfigureBySection<DirectorySearchOptions>();
|
||||
builder.Services.AddDirectorySearchService();
|
||||
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
|
||||
{
|
||||
Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object)
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only
|
||||
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
|
||||
options.LoginPath = "/api/auth/login";
|
||||
options.LogoutPath = "/api/auth/logout";
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // timeout.
|
||||
options.SlidingExpiration = true; //refreshes the expiration time on each request.
|
||||
options.Cookie.Name = "AuthSession";
|
||||
});
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// 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();
|
||||
@@ -1,18 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DigitalData.Core.API" Version="2.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"DiPMode": true,
|
||||
"EnableSwagger": true,
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"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=*))"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using WorkFlow.Application.DTO.Config;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Contracts
|
||||
{
|
||||
public interface IConfigService : ICRUDService<ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.DTO;
|
||||
using WorkFlow.Application.DTO.ProfileControlsTF;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Contracts
|
||||
{
|
||||
public interface IProfileControlsTFService : ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, 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);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.DTO;
|
||||
using WorkFlow.Application.DTO.ProfileObjState;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Contracts
|
||||
{
|
||||
public interface IProfileObjStateService : ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, 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);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using WorkFlow.Application.DTO.Profile;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Contracts
|
||||
{
|
||||
public interface IProfileService : ICRUDService<ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using WorkFlow.Application.DTO.State;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Contracts
|
||||
{
|
||||
public interface IStateService : ICRUDService<StateCreateDto, StateDto, StateUpdateDto, State, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.Config
|
||||
{
|
||||
public record ConfigCreateDto(string Title, string String);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.Config
|
||||
{
|
||||
public record ConfigUpdateDto(string Title, string String) : BaseUpdateDto;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.Profile
|
||||
{
|
||||
public record ProfileCreateDto(string IntlName, int ExtId1, bool Active, byte TypeId) : BaseCreateDto;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.Profile
|
||||
{
|
||||
public record ProfileDto(int Id,
|
||||
string IntlName,
|
||||
int ExtId1,
|
||||
bool Active,
|
||||
byte TypeId,
|
||||
string AddedWho,
|
||||
DateTime AddedWhen,
|
||||
string? ChangedWho = null,
|
||||
DateTime? ChangedWhen = null);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.Profile
|
||||
{
|
||||
public record ProfileUpdateDto(string IntlName, int ExtId1, bool Active, byte TypeId) : BaseUpdateDto;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.ProfileControlsTF
|
||||
{
|
||||
public record ProfileControlsTFCreateDto(
|
||||
int ProfileId,
|
||||
int UserId,
|
||||
long ObjId,
|
||||
string ObjType,
|
||||
string AttrName,
|
||||
string CtrlType,
|
||||
string CtrlCaption,
|
||||
bool Mandatory,
|
||||
bool ReadOnly,
|
||||
string? ChoiceList = null) : BaseCreateDto;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using WorkFlow.Application.DTO.Profile;
|
||||
|
||||
namespace WorkFlow.Application.DTO.ProfileControlsTF
|
||||
{
|
||||
public record ProfileControlsTFDto(int Id,
|
||||
int ProfileId,
|
||||
int UserId,
|
||||
long ObjId,
|
||||
string ObjType,
|
||||
string AttrName,
|
||||
string CtrlType,
|
||||
string CtrlCaption,
|
||||
bool Mandatory,
|
||||
bool ReadOnly,
|
||||
string AddedWho,
|
||||
DateTime AddedWhen,
|
||||
string? ChoiceList = null,
|
||||
ProfileDto? Profile = null,
|
||||
UserReadDto? User = null);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
|
||||
namespace WorkFlow.Application.DTO.ProfileControlsTF
|
||||
{
|
||||
/// <summary>
|
||||
/// This Data Transfer Object (DTO) serves as a placeholder and does not support updates.
|
||||
/// </summary>
|
||||
public record ProfileControlsTFUpdateDto(int Id) : IUnique<int>;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.ProfileObjState
|
||||
{
|
||||
public record ProfileObjStateCreateDto(
|
||||
int ProfileId,
|
||||
int UserId,
|
||||
long ObjId,
|
||||
int StateId,
|
||||
string? State2 = null,
|
||||
string? State3 = null,
|
||||
string? State4 = null) : BaseCreateDto;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using WorkFlow.Application.DTO.Profile;
|
||||
using WorkFlow.Application.DTO.State;
|
||||
|
||||
namespace WorkFlow.Application.DTO.ProfileObjState
|
||||
{
|
||||
public record ProfileObjStateDto(int Id,
|
||||
int ProfileId,
|
||||
int UserId,
|
||||
long ObjId,
|
||||
int StateId,
|
||||
string AddedWho,
|
||||
DateTime AddedWhen,
|
||||
string? State2 = null,
|
||||
string? State3 = null,
|
||||
string? State4 = null,
|
||||
ProfileDto? Profile = null,
|
||||
UserReadDto? User = null,
|
||||
StateDto? State = null);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
|
||||
namespace WorkFlow.Application.DTO.ProfileObjState
|
||||
{
|
||||
/// <summary>
|
||||
/// This Data Transfer Object (DTO) serves as a placeholder and does not support updates.
|
||||
/// </summary>
|
||||
public record ProfileObjStateUpdateDto(int Id) : IUnique<int>;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.State
|
||||
{
|
||||
public record StateCreateDto(string IntlState) : BaseCreateDto;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace WorkFlow.Application.DTO.State
|
||||
{
|
||||
public record StateDto(int Id, string IntlState, string AddedWho, DateTime AddedWhen);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
|
||||
namespace WorkFlow.Application.DTO.State
|
||||
{
|
||||
/// <summary>
|
||||
/// This Data Transfer Object (DTO) serves as a placeholder and does not support updates.
|
||||
/// </summary>
|
||||
public record StateUpdateDto(int Id) : IUnique<int>;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using AutoMapper;
|
||||
using WorkFlow.Application.DTO.Config;
|
||||
using WorkFlow.Application.DTO.Profile;
|
||||
using WorkFlow.Application.DTO.ProfileControlsTF;
|
||||
using WorkFlow.Application.DTO.ProfileObjState;
|
||||
using WorkFlow.Application.DTO.State;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application
|
||||
{
|
||||
public class MappingProfile : AutoMapper.Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
// Mapping entity to DTO
|
||||
CreateMap<Config, ConfigDto>();
|
||||
CreateMap<Domain.Entities.Profile, ProfileDto>();
|
||||
CreateMap<ProfileControlsTF, ProfileControlsTFDto>();
|
||||
CreateMap<ProfileObjState, ProfileObjStateDto>();
|
||||
CreateMap<State, StateDto>();
|
||||
|
||||
// Mapping create-DTO to entity
|
||||
CreateMap<ConfigCreateDto, Config>();
|
||||
CreateMap<ProfileCreateDto, Domain.Entities.Profile>();
|
||||
CreateMap<ProfileControlsTFCreateDto, ProfileControlsTF>();
|
||||
CreateMap<ProfileObjStateCreateDto, ProfileObjState>();
|
||||
CreateMap<StateCreateDto, State>();
|
||||
|
||||
// Mapping update-DTO to entity
|
||||
CreateMap<ConfigUpdateDto, Config>();
|
||||
CreateMap<ProfileUpdateDto, Domain.Entities.Profile>();
|
||||
CreateMap<ProfileControlsTFUpdateDto, ProfileControlsTF>();
|
||||
CreateMap<ProfileObjStateUpdateDto, ProfileObjState>();
|
||||
CreateMap<StateUpdateDto, State>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.Application;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.Config;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Application.Services
|
||||
{
|
||||
public class ConfigService(IConfigRepository repository, IMapper mapper)
|
||||
: CRUDService<IConfigRepository, ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>(repository, mapper),
|
||||
IConfigService, ICRUDService<ConfigCreateDto, ConfigDto, ConfigUpdateDto, Config, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.Application;
|
||||
using DigitalData.Core.DTO;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.ProfileControlsTF;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Application.Services
|
||||
{
|
||||
public class ProfileControlsTFService(IProfileControlsTFRepository repository, IMapper mapper)
|
||||
: CRUDService<IProfileControlsTFRepository, ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>(repository, mapper),
|
||||
IProfileControlsTFService, ICRUDService<ProfileControlsTFCreateDto, ProfileControlsTFDto, ProfileControlsTFUpdateDto, ProfileControlsTF, int>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.Application;
|
||||
using DigitalData.Core.DTO;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.ProfileObjState;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Application.Services
|
||||
{
|
||||
public class ProfileObjStateService(IProfileObjStateRepository repository, IMapper mapper)
|
||||
: CRUDService<IProfileObjStateRepository, ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>(repository, mapper),
|
||||
IProfileObjStateService, ICRUDService<ProfileObjStateCreateDto, ProfileObjStateDto, ProfileObjStateUpdateDto, ProfileObjState, int>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.Application;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.Profile;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Application.Services
|
||||
{
|
||||
public class ProfileService(IProfileRepository repository, AutoMapper.IMapper mapper)
|
||||
: CRUDService<IProfileRepository, ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>(repository, mapper),
|
||||
IProfileService, ICRUDService<ProfileCreateDto, ProfileDto, ProfileUpdateDto, Profile, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Application;
|
||||
using DigitalData.Core.Application;
|
||||
using WorkFlow.Application.Contracts;
|
||||
using WorkFlow.Application.DTO.State;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Application.Services
|
||||
{
|
||||
public class StateService(IStateRepository repository, IMapper mapper)
|
||||
: CRUDService<IStateRepository, StateCreateDto, StateDto, StateUpdateDto, State, int>(repository, mapper),
|
||||
IStateService, ICRUDService<StateCreateDto, StateDto, StateUpdateDto, State, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DigitalData.Core.Application" Version="2.0.0" />
|
||||
<PackageReference Include="UserManager.Application" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
|
||||
<ProjectReference Include="..\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,44 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WorkFlow.Domain.Entities
|
||||
{
|
||||
[Table("TBMWF_PROFILE", Schema = "dbo")]
|
||||
public class Profile : IUnique<int>
|
||||
{
|
||||
[Key]
|
||||
[Column("GUID")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("INTL_NAME", TypeName = "varchar(200)")]
|
||||
public required string IntlName { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("EXT_ID1")]
|
||||
public required int ExtId1 { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ACTIVE")]
|
||||
public required bool Active { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHO", TypeName = "varchar(30)")]
|
||||
public required string AddedWho { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
public required DateTime AddedWhen { get; init; }
|
||||
|
||||
[Column("CHANGED_WHO", TypeName = "varchar(30)")]
|
||||
public string? ChangedWho { get; init; }
|
||||
|
||||
[Column("CHANGED_WHEN", TypeName = "datetime")]
|
||||
public DateTime? ChangedWhen { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("TYPE_ID")]
|
||||
public required byte TypeId { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
using DigitalData.UserManager.Domain.Entities;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WorkFlow.Domain.Entities
|
||||
{
|
||||
[Table("TBMWF_PROF_CONTROLS_TF", Schema = "dbo")]
|
||||
public class ProfileControlsTF : IUnique<int>
|
||||
{
|
||||
[Key]
|
||||
[Column("GUID")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("MWF_PROFILE_ID")]
|
||||
public required int ProfileId { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("USR_ID")]
|
||||
public required int UserId { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("OBJ_ID")]
|
||||
public required long ObjId { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("OBJ_TYPE", TypeName = "varchar(10)")]
|
||||
public required string ObjType { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ATTR_NAME", TypeName = "varchar(100)")]
|
||||
public required string AttrName { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("CTRL_TYPE", TypeName = "varchar(10)")]
|
||||
public required string CtrlType { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("CTRL_CAPTION", TypeName = "varchar(100)")]
|
||||
public required string CtrlCaption { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("MANDATORY")]
|
||||
public required bool Mandatory { get; init; }
|
||||
|
||||
[Column("CHOICE_LIST", TypeName = "nvarchar(max)")]
|
||||
public string? ChoiceList { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("READ_ONLY")]
|
||||
public required bool ReadOnly { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHO", TypeName = "varchar(100)")]
|
||||
public required string AddedWho { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
public required DateTime AddedWhen { get; init; }
|
||||
|
||||
[ForeignKey("ProfileId")]
|
||||
public Profile? Profile { get; init; } = default;
|
||||
|
||||
[ForeignKey("UserId")]
|
||||
public User? User { get; set; } = default;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
using DigitalData.UserManager.Domain.Entities;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WorkFlow.Domain.Entities
|
||||
{
|
||||
[Table("TBMWF_PROFILE_OBJ_STATE", Schema = "dbo")]
|
||||
public class ProfileObjState : IUnique<int>
|
||||
{
|
||||
[Key]
|
||||
[Column("GUID")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("MWF_PROFILE_ID")]
|
||||
public required int ProfileId { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("USR_ID")]
|
||||
public required int UserId { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("OBJ_ID")]
|
||||
public required long ObjId { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("STATE_ID")]
|
||||
public required int StateId { get; init; }
|
||||
|
||||
[Column("STATE2", TypeName = "nvarchar(3000)")]
|
||||
public string? State2 { get; init; }
|
||||
|
||||
[Column("STATE3", TypeName = "nvarchar(3000)")]
|
||||
public string? State3 { get; init; }
|
||||
|
||||
[Column("STATE4", TypeName = "nvarchar(3000)")]
|
||||
public string? State4 { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHO", TypeName = "varchar(30)")]
|
||||
public required string AddedWho { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
public required DateTime AddedWhen { get; init; }
|
||||
|
||||
[ForeignKey("ProfileId")]
|
||||
public Profile? Profile { get; init; } = null;
|
||||
|
||||
[ForeignKey("UserId")]
|
||||
public User? User { get; init; } = null;
|
||||
|
||||
[ForeignKey("StateId")]
|
||||
public State? State { get; init; } = null;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WorkFlow.Domain.Entities
|
||||
{
|
||||
[Table("TBMWF_WF_STATE", Schema = "dbo")]
|
||||
public class State : IUnique<int>
|
||||
{
|
||||
[Key]
|
||||
[Column("GUID")]
|
||||
public int Id { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("INTL_STATE", TypeName = "varchar(100)")]
|
||||
public required string IntlState { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHO", TypeName = "varchar(30)")]
|
||||
public required string AddedWho { get; init; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
public required DateTime AddedWhen { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Contracts
|
||||
{
|
||||
public interface IConfigRepository : ICRUDRepository<Config, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Contracts
|
||||
{
|
||||
public interface IProfileObjStateRepository : ICRUDRepository<ProfileObjState, int>
|
||||
{
|
||||
Task<IEnumerable<ProfileObjState>> ReadAsync(
|
||||
bool isReadonly = true,
|
||||
bool withProfile = true, bool withUser = true, bool withState = true,
|
||||
int? userId = null, string? username = null,
|
||||
int? profileId = null, int? objId = null, bool? profileActive = null);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Contracts
|
||||
{
|
||||
public interface IProfileRepository : ICRUDRepository<Profile, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Contracts
|
||||
{
|
||||
public interface IStateRepository : ICRUDRepository<State, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Repositories
|
||||
{
|
||||
//TODO: Make the db context type generic so that it can be used by other projects with different db contexts.
|
||||
public class ConfigRepository(WFDBContext dbContext) : CRUDRepository<Config, int, WFDBContext>(dbContext, dbContext.Configs), IConfigRepository, ICRUDRepository<Config, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Repositories
|
||||
{
|
||||
public class ProfileControlsTFRepository(WFDBContext dbContext) : CRUDRepository<ProfileControlsTF, int, WFDBContext>(dbContext, dbContext.ProfileControlsTFs), IProfileControlsTFRepository, ICRUDRepository<ProfileControlsTF, int>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Repositories
|
||||
{
|
||||
public class ProfileObjStateRepository(WFDBContext dbContext) : CRUDRepository<ProfileObjState, int, WFDBContext>(dbContext, dbContext.ProfileObjStates), IProfileObjStateRepository, ICRUDRepository<ProfileObjState, int>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
|
||||
namespace WorkFlow.Infrastructure.Repositories
|
||||
{
|
||||
public class ProfileRepository(WFDBContext dbContext) : CRUDRepository<Profile, int, WFDBContext>(dbContext, dbContext.Profiles), IProfileRepository, ICRUDRepository<Profile, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using DigitalData.Core.Abstractions.Infrastructure;
|
||||
using DigitalData.Core.Infrastructure;
|
||||
using WorkFlow.Domain.Entities;
|
||||
using WorkFlow.Infrastructure.Contracts;
|
||||
namespace WorkFlow.Infrastructure.Repositories
|
||||
{
|
||||
public class StateRepository(WFDBContext dbContext) : CRUDRepository<State, int, WFDBContext>(dbContext, dbContext.States), IStateRepository, ICRUDRepository<State, int>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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(DbContextOptions options) : DbContext(options), 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; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
//configure model builder for user manager tables
|
||||
modelBuilder.ConfigureUserManager();
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.0.0" />
|
||||
<PackageReference Include="UserManager.Infrastructure" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WorkFlow.Domain\WorkFlow.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
54
WorkFlow.sln
54
WorkFlow.sln
@@ -3,13 +3,21 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34622.214
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlow.Domain", "WorkFlow.Domain\WorkFlow.Domain.csproj", "{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\GetProfile.sql = scripts\GetProfile.sql
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkFlow.Infrastructure", "WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj", "{62526D0D-3365-4113-854A-3656191D7C63}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Application", "WorkFlow.Application\WorkFlow.Application.csproj", "{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.API", "src\WorkFlow.API\WorkFlow.API.csproj", "{2B724243-4C79-F3A4-EE25-B9A53C81464C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.API", "WorkFlow.API\WorkFlow.API.csproj", "{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Application", "src\WorkFlow.Application\WorkFlow.Application.csproj", "{F1B4AC83-5137-C20B-641C-1699B46007A0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Domain", "src\WorkFlow.Domain\WorkFlow.Domain.csproj", "{92A11048-6B9C-374E-87A0-BD6D8F864B77}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkFlow.Infrastructure", "src\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj", "{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -17,26 +25,32 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{71E9264E-A2F0-4E5A-B010-8E4618C0C6AC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{62526D0D-3365-4113-854A-3656191D7C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{62526D0D-3365-4113-854A-3656191D7C63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{62526D0D-3365-4113-854A-3656191D7C63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{62526D0D-3365-4113-854A-3656191D7C63}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5700B5DD-D17E-4E17-ADE5-48C95A0CC364}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FB33592-EF0D-47C3-9CDE-03B2EF12BE00}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B724243-4C79-F3A4-EE25-B9A53C81464C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1B4AC83-5137-C20B-641C-1699B46007A0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{92A11048-6B9C-374E-87A0-BD6D8F864B77}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{2B724243-4C79-F3A4-EE25-B9A53C81464C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{F1B4AC83-5137-C20B-641C-1699B46007A0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{92A11048-6B9C-374E-87A0-BD6D8F864B77} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{94F2D67D-649E-63D2-A3BF-0BEC98C2D7E6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1ECB3995-5040-40BC-BC70-906E64BB4E01}
|
||||
EndGlobalSection
|
||||
|
||||
4
scripts/GetProfile.sql
Normal file
4
scripts/GetProfile.sql
Normal 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
|
||||
14
src/WorkFlow.API/Attributes/APIKeyAuthAttribute.cs
Normal file
14
src/WorkFlow.API/Attributes/APIKeyAuthAttribute.cs
Normal 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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/WorkFlow.API/Base64File.cs
Normal file
12
src/WorkFlow.API/Base64File.cs
Normal file
File diff suppressed because one or more lines are too long
74
src/WorkFlow.API/Controllers/ControllerExtensions.cs
Normal file
74
src/WorkFlow.API/Controllers/ControllerExtensions.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Security.Claims;
|
||||
using WorkFlow.API.Attributes;
|
||||
|
||||
namespace WorkFlow.API.Controllers;
|
||||
|
||||
[APIKeyAuth]
|
||||
public static class ControllerExtensions
|
||||
{
|
||||
public static bool TryGetUserId(this ClaimsPrincipal user, out int id) => int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier), out id);
|
||||
|
||||
public static bool TryGetUsername(this ClaimsPrincipal user, out string username)
|
||||
{
|
||||
var value = user.FindFirstValue(ClaimTypes.Name);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
username = string.Empty;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
username = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetName(this ClaimsPrincipal user, out string name)
|
||||
{
|
||||
var value = user.FindFirstValue(ClaimTypes.Surname);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
name = string.Empty;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetPrename(this ClaimsPrincipal user, out string prename)
|
||||
{
|
||||
var value = user.FindFirstValue(ClaimTypes.GivenName);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
prename = string.Empty;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
prename = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetEmail(this ClaimsPrincipal user, out string email)
|
||||
{
|
||||
var value = user.FindFirstValue(ClaimTypes.Email);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
email = string.Empty;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
email = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/WorkFlow.API/Controllers/FileController.cs
Normal file
57
src/WorkFlow.API/Controllers/FileController.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Web;
|
||||
|
||||
namespace WorkFlow.API.Controllers;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class FileController : ControllerBase
|
||||
{
|
||||
[HttpGet("{path}")]
|
||||
public IActionResult GetFile([FromRoute] string path, [FromQuery] bool icon = false)
|
||||
{
|
||||
string dPath = HttpUtility.UrlDecode(path);
|
||||
|
||||
byte[]? fileBytes = null;
|
||||
string? contentType = null;
|
||||
|
||||
if (dPath == "docs/doc1.pdf" && !icon)
|
||||
{
|
||||
fileBytes = Convert.FromBase64String(Base64File.Doc1Base64);
|
||||
contentType = "application/pdf";
|
||||
}
|
||||
else if (dPath == "icons/icon1.png" && icon)
|
||||
{
|
||||
fileBytes = Convert.FromBase64String(Base64File.Icon1Base64);
|
||||
contentType = "image/png";
|
||||
}
|
||||
else
|
||||
{
|
||||
string fullPath = Path.Combine(AppContext.BaseDirectory, "files", dPath);
|
||||
if (!System.IO.File.Exists(fullPath))
|
||||
return NotFound();
|
||||
|
||||
fileBytes = System.IO.File.ReadAllBytes(fullPath);
|
||||
contentType = GetContentType(fullPath);
|
||||
}
|
||||
|
||||
return File(fileBytes, contentType ?? "application/octet-stream");
|
||||
}
|
||||
|
||||
private string GetContentType(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path).ToLowerInvariant();
|
||||
return ext switch
|
||||
{
|
||||
".pdf" => "application/pdf",
|
||||
".png" => "image/png",
|
||||
".jpg" => "image/jpeg",
|
||||
".jpeg" => "image/jpeg",
|
||||
".txt" => "text/plain",
|
||||
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
}
|
||||
}
|
||||
24
src/WorkFlow.API/Controllers/PlaceholderAuthController.cs
Normal file
24
src/WorkFlow.API/Controllers/PlaceholderAuthController.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using WorkFlow.API.Models;
|
||||
using WorkFlow.API.Attributes;
|
||||
|
||||
namespace WorkFlow.API.Controllers;
|
||||
|
||||
//TODO: implement up-to-date AuthController in UserManager
|
||||
[APIKeyAuth]
|
||||
[Route("api/Auth")]
|
||||
[ApiController]
|
||||
[Tags("Auth")]
|
||||
public class PlaceholderAuthController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult CreateToken([FromBody] Login login)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[HttpGet("check")]
|
||||
[Authorize]
|
||||
public IActionResult Check() => Ok();
|
||||
}
|
||||
37
src/WorkFlow.API/Controllers/ProfileController.cs
Normal file
37
src/WorkFlow.API/Controllers/ProfileController.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.API.Attributes;
|
||||
using WorkFlow.Application.Profiles;
|
||||
|
||||
namespace WorkFlow.API.Controllers;
|
||||
|
||||
[APIKeyAuth]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ProfileController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ProfileController> _logger;
|
||||
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public ProfileController(ILogger<ProfileController> logger, IMediator mediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAsync()
|
||||
{
|
||||
if (!User.TryGetUserId(out var userId))
|
||||
{
|
||||
_logger.LogError("Invalid user ID: Retrieved ID is null or not an integer.");
|
||||
return Unauthorized("Failed to retrieve user identity.");
|
||||
}
|
||||
|
||||
var profile = await _mediator.ReadProfileAsync(userId);
|
||||
return Ok(profile);
|
||||
}
|
||||
}
|
||||
42
src/WorkFlow.API/Controllers/UserController.cs
Normal file
42
src/WorkFlow.API/Controllers/UserController.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using DigitalData.UserManager.Application.Contracts;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorkFlow.API.Attributes;
|
||||
|
||||
namespace WorkFlow.API.Controllers;
|
||||
|
||||
[APIKeyAuth]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Obsolete("Use MediatR")]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<UserController> logger;
|
||||
private readonly IUserService userService;
|
||||
|
||||
public UserController(ILogger<UserController> logger, IUserService userService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAsync()
|
||||
{
|
||||
if (!User.TryGetUserId(out var id))
|
||||
{
|
||||
logger.LogError("Authorization failed: User ID claim not found.");
|
||||
return Unauthorized("Failed to retrieve user identity.");
|
||||
}
|
||||
|
||||
return await userService.ReadByIdAsync(id).ThenAsync(
|
||||
Success: Ok,
|
||||
Fail: IActionResult (msg, ntc) =>
|
||||
{
|
||||
logger.LogNotice(ntc);
|
||||
return NotFound();
|
||||
});
|
||||
}
|
||||
}
|
||||
22
src/WorkFlow.API/Extensions/DIExtensions.cs
Normal file
22
src/WorkFlow.API/Extensions/DIExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/WorkFlow.API/Filters/APIKeyAuthFilter.cs
Normal file
22
src/WorkFlow.API/Filters/APIKeyAuthFilter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
42
src/WorkFlow.API/Filters/APIKeyAuthHeaderOpFilter.cs
Normal file
42
src/WorkFlow.API/Filters/APIKeyAuthHeaderOpFilter.cs
Normal 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
10
src/WorkFlow.API/Jenkinsfile
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'dotnet build'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/WorkFlow.API/LazyServiceProvider.cs
Normal file
18
src/WorkFlow.API/LazyServiceProvider.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
84
src/WorkFlow.API/Middleware/ExceptionHandlingMiddleware.cs
Normal file
84
src/WorkFlow.API/Middleware/ExceptionHandlingMiddleware.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace WorkFlow.API.Middleware;
|
||||
|
||||
//TODO: Fix and use DigitalData.Core.Exceptions.Middleware
|
||||
/// <summary>
|
||||
/// Middleware for handling exceptions globally in the application.
|
||||
/// Captures exceptions thrown during the request pipeline execution,
|
||||
/// logs them, and returns an appropriate HTTP response with a JSON error message.
|
||||
/// </summary>
|
||||
[Obsolete("Use DigitalData.Core.Exceptions.Middleware")]
|
||||
public class ExceptionHandlingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExceptionHandlingMiddleware"/> class.
|
||||
/// </summary>
|
||||
/// <param name="next">The next middleware in the request pipeline.</param>
|
||||
/// <param name="logger">The logger instance for logging exceptions.</param>
|
||||
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the middleware to handle the HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context of the current request.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context); // Continue down the pipeline
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleExceptionAsync(context, ex, _logger);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles exceptions by logging them and writing an appropriate JSON response.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context of the current request.</param>
|
||||
/// <param name="exception">The exception that occurred.</param>
|
||||
/// <param name="logger">The logger instance for logging the exception.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
string message;
|
||||
|
||||
switch (exception)
|
||||
{
|
||||
case BadRequestException badRequestEx:
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
message = badRequestEx.Message;
|
||||
break;
|
||||
|
||||
case NotFoundException notFoundEx:
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
message = notFoundEx.Message;
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.LogError(exception, "Unhandled exception occurred.");
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
message = "An unexpected error occurred.";
|
||||
break;
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(new
|
||||
{
|
||||
message
|
||||
}));
|
||||
}
|
||||
}
|
||||
11
src/WorkFlow.API/Models/APIKeyAuthOptions.cs
Normal file
11
src/WorkFlow.API/Models/APIKeyAuthOptions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
12
src/WorkFlow.API/Models/AuthTokenKeys.cs
Normal file
12
src/WorkFlow.API/Models/AuthTokenKeys.cs
Normal 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";
|
||||
}
|
||||
@@ -5,13 +5,14 @@ namespace WorkFlow.API.Models
|
||||
{
|
||||
public static class ModelExtensions
|
||||
{
|
||||
public static List<Claim> ToClaimList(this UserReadDto user) => [
|
||||
public static List<Claim> ToClaimList(this UserReadDto user) => new()
|
||||
{
|
||||
new (ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new (ClaimTypes.Name, user.Username),
|
||||
new (ClaimTypes.Surname, user.Name ?? ""),
|
||||
new (ClaimTypes.GivenName, user.Prename ?? ""),
|
||||
new (ClaimTypes.Email, user.Email ?? "")
|
||||
];
|
||||
};
|
||||
|
||||
public static Dictionary<string, object> ToClaimDictionary(this UserReadDto user) => user.ToClaimList().ToDictionary(claim => claim.Type, claim => (object) claim.Value);
|
||||
}
|
||||
181
src/WorkFlow.API/Program.cs
Normal file
181
src/WorkFlow.API/Program.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using DigitalData.Auth.Client;
|
||||
using DigitalData.Core.Abstractions.Security.Extensions;
|
||||
using DigitalData.Core.Application;
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using DigitalData.UserManager.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using NLog;
|
||||
using NLog.Web;
|
||||
using WorkFlow.API;
|
||||
using WorkFlow.API.Extensions;
|
||||
using WorkFlow.API.Filters;
|
||||
using WorkFlow.API.Middleware;
|
||||
using WorkFlow.API.Models;
|
||||
using WorkFlow.Application;
|
||||
using WorkFlow.Infrastructure;
|
||||
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
logger.Info("Logging initialized.");
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var config = builder.Configuration;
|
||||
|
||||
Directory
|
||||
.GetFiles(builder.Environment.ContentRootPath, "appsettings.*.json", SearchOption.TopDirectoryOnly)
|
||||
.Where(file => Path.GetFileName(file) != $"appsettings.Development.json")
|
||||
.ToList()
|
||||
.ForEach(file => config.AddJsonFile(file, true, true));
|
||||
|
||||
// Add NLogger
|
||||
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
|
||||
if (!builder.Environment.IsDevelopment())
|
||||
{
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Host.UseNLog();
|
||||
}
|
||||
|
||||
// Add services to the container
|
||||
var cnn_str = config.GetConnectionString("Default") ?? throw new("Default connection string not found.");
|
||||
builder.Services.AddDbContext<WFDBContext>(options => options.UseSqlServer(cnn_str).EnableDetailedErrors());
|
||||
var mediatRLicense = config["MediatRLicense"]
|
||||
?? throw new InvalidOperationException(
|
||||
"The 'MediatRLicense' configuration value is missing or empty." +
|
||||
"Please ensure it is properly set in the configuration source.");
|
||||
builder.Services.AddWorkFlowServices(opt =>
|
||||
{
|
||||
opt.MediatRLicense = mediatRLicense;
|
||||
opt.ConfigMapping(config.GetSection("MappingOptions"));
|
||||
}).AddWorkFlowRepositories();
|
||||
|
||||
builder.Services.AddUserManager<WFDBContext>();
|
||||
|
||||
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
|
||||
builder.Services.AddJWTService<UserReadDto>(user => new SecurityTokenDescriptor()
|
||||
{
|
||||
Claims = user.ToClaimList().ToDictionary(claim => claim.Type, claim => claim.Value as object)
|
||||
});
|
||||
|
||||
bool disableAPIKeyAuth = config.GetValue<bool>("DisableAPIKeyAuth");
|
||||
if (disableAPIKeyAuth)
|
||||
builder.Services.AddAPIKeyAuth(new APIKeyAuthOptions());
|
||||
else
|
||||
if (config.GetSection("APIKeyAuth").Get<APIKeyAuthOptions>() is APIKeyAuthOptions options)
|
||||
builder.Services.AddAPIKeyAuth(options);
|
||||
else
|
||||
throw new("The API Key Authorization configuration is not available in the app settings, even though the app is not in development or DiP mode and API Key Authorization is not disabled.");
|
||||
|
||||
var lazyProvider = new LazyServiceProvider();
|
||||
|
||||
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
|
||||
|
||||
builder.Services.AddControllers();
|
||||
|
||||
var authTokenKeys = config.GetSection(nameof(AuthTokenKeys)).Get<AuthTokenKeys>() ?? new();
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(opt =>
|
||||
{
|
||||
opt.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
|
||||
{
|
||||
var clientParams = lazyProvider.GetRequiredService<IOptions<ClientParams>>()?.Value;
|
||||
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
|
||||
return new List<SecurityKey>() { publicKey.SecurityKey };
|
||||
},
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = authTokenKeys.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = authTokenKeys.Audience,
|
||||
};
|
||||
|
||||
opt.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
// if there is no token read related cookie or query string
|
||||
if (context.Token is null) // if there is no token
|
||||
{
|
||||
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
|
||||
context.Token = cookieToken;
|
||||
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
|
||||
context.Token = queryStrToken;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(setupAct =>
|
||||
{
|
||||
setupAct.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
|
||||
setupAct.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
|
||||
if (!disableAPIKeyAuth)
|
||||
setupAct.OperationFilter<APIKeyAuthHeaderOpFilter>();
|
||||
|
||||
if (config.GetSection("OpenApiInfo").Get<OpenApiInfo>() is OpenApiInfo openApiInfo)
|
||||
setupAct.SwaggerDoc(openApiInfo?.Version ?? "v1", openApiInfo);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
lazyProvider.Factory = () => app.Services;
|
||||
|
||||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Configuration.GetValue<bool>("EnableSwagger"))
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Stopped program because of exception.");
|
||||
throw;
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5130",
|
||||
"environmentVariables": {
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
8
src/WorkFlow.API/WFKey.cs
Normal file
8
src/WorkFlow.API/WFKey.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
38
src/WorkFlow.API/WorkFlow.API.csproj
Normal file
38
src/WorkFlow.API/WorkFlow.API.csproj
Normal file
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PackageId>WorkFlow.API</PackageId>
|
||||
<Version>1.2.1</Version>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>WorkFlow.API</Product>
|
||||
<Title>WorkFlow.API</Title>
|
||||
<AssemblyVersion>1.2.1</AssemblyVersion>
|
||||
<FileVersion>1.2.1</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfile - Copy.pubxml" />
|
||||
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet7.pubxml" />
|
||||
<_WebToolingArtifacts Remove="Properties\PublishProfiles\IISProfileNet8.pubxml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
|
||||
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.0.0" />
|
||||
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.14" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageReference Include="UserManager" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WorkFlow.Application\WorkFlow.Application.csproj" />
|
||||
<ProjectReference Include="..\WorkFlow.Infrastructure\WorkFlow.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
src/WorkFlow.API/appsettings.Auth.json
Normal file
18
src/WorkFlow.API/appsettings.Auth.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
11
src/WorkFlow.API/appsettings.LDAP.json
Normal file
11
src/WorkFlow.API/appsettings.LDAP.json
Normal 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=*))"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/WorkFlow.API/appsettings.Logging.json
Normal file
51
src/WorkFlow.API/appsettings.Logging.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
17
src/WorkFlow.API/appsettings.Mapping.json
Normal file
17
src/WorkFlow.API/appsettings.Mapping.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"MappingOptions": {
|
||||
"TfFileUri": {
|
||||
"Scheme": "https",
|
||||
"Host": "dd-gan.digitaldata.works",
|
||||
"Port": 8443,
|
||||
"Path": "api/file"
|
||||
},
|
||||
"TfFileIconUri": {
|
||||
"Scheme": "https",
|
||||
"Host": "dd-gan.digitaldata.works",
|
||||
"Port": 8443,
|
||||
"Path": "api/file",
|
||||
"Query": "icon=true"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/WorkFlow.API/appsettings.json
Normal file
17
src/WorkFlow.API/appsettings.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"DiPMode": true,
|
||||
"EnableSwagger": true,
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
|
||||
},
|
||||
"OpenApiInfo": {
|
||||
"Title": "WorkFlow API",
|
||||
"Contact": {
|
||||
"Email": "info-flow@digitaldata.works",
|
||||
"Name": "Digital Data GmbH",
|
||||
"Url": "https://digitaldata.works/"
|
||||
}
|
||||
},
|
||||
"MediatRLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg"
|
||||
}
|
||||
20
src/WorkFlow.Application/Buttons/ReadButton.cs
Normal file
20
src/WorkFlow.Application/Buttons/ReadButton.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace WorkFlow.Application.Buttons;
|
||||
|
||||
public record ButtonDto
|
||||
{
|
||||
public byte? DialogNo { get; init; }
|
||||
|
||||
public string? BtnType { get; init; }
|
||||
|
||||
public string? Icon { get; init; }
|
||||
|
||||
public string? ForeColor { get; init; }
|
||||
|
||||
public string? BackColor { get; init; }
|
||||
|
||||
public string? Command { get; init; }
|
||||
|
||||
public string? DialogCommand { get; init; }
|
||||
|
||||
public string? ConfirmationText { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for retrieving <see cref="PObject"/> entities from the database.
|
||||
/// </summary>
|
||||
public interface IProfileObjRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the list of <see cref="PObject"/> associated with a given user ID and profile ID by calling a database function.
|
||||
/// </summary>
|
||||
/// <param name="userId">The unique identifier of the user whose profile is to be retrieved.</param>
|
||||
/// <param name="profileId">The unique identifier of the profile whose object is to be retrieved.</param>
|
||||
/// <param name="cancel">Propagates notification that operations should be canceled.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation. The task result contains the <see cref="PObject"/> object if found; otherwise, <c>null</c>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Logs an error if no profile is found, or if multiple profiles are returned, indicating potential data issues.
|
||||
/// </remarks>
|
||||
public Task<IEnumerable<PObject>> ReadAsync(int userId, int profileId, CancellationToken cancel = default);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WorkFlow.Application.DTO
|
||||
namespace WorkFlow.Application.Dto
|
||||
{
|
||||
public record BaseCreateDto
|
||||
{
|
||||
@@ -1,9 +1,8 @@
|
||||
using DigitalData.Core.Abstractions;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WorkFlow.Application.DTO
|
||||
namespace WorkFlow.Application.Dto
|
||||
{
|
||||
public record BaseUpdateDto : IUnique<int>
|
||||
public record BaseUpdateDto
|
||||
{
|
||||
public required int Id { get; init; }
|
||||
|
||||
52
src/WorkFlow.Application/DependencyInjection.cs
Normal file
52
src/WorkFlow.Application/DependencyInjection.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using WorkFlow.Application.Mapping;
|
||||
|
||||
namespace WorkFlow.Application;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddWorkFlowServices(this IServiceCollection services, Action<WorkFlowServiceOptions>? options = null)
|
||||
{
|
||||
WorkFlowServiceOptions sOptions = new(services);
|
||||
options?.Invoke(sOptions);
|
||||
|
||||
services.AddAutoMapper(typeof(MappingProfile).Assembly);
|
||||
services.AddMediatR(cfg =>
|
||||
{
|
||||
cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly);
|
||||
cfg.LicenseKey = sOptions.MediatRLicense;
|
||||
});
|
||||
|
||||
if(!sOptions.IsMappingConfigured)
|
||||
services.Configure<MappingOptions>(_ => { });
|
||||
|
||||
services.AddTransient<TfFileUriResolver>();
|
||||
services.AddTransient<TfFileIconUriResolver>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public class WorkFlowServiceOptions
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
|
||||
internal bool IsMappingConfigured { get; private set; } = false;
|
||||
|
||||
public WorkFlowServiceOptions(IServiceCollection services) => _services = services;
|
||||
|
||||
private void EnsureSingleMappingConfiguration(Action action)
|
||||
{
|
||||
if (IsMappingConfigured)
|
||||
throw new InvalidOperationException("Mapping configuration has already been set.");
|
||||
action();
|
||||
IsMappingConfigured = true;
|
||||
}
|
||||
|
||||
public void ConfigMapping(IConfiguration config) => EnsureSingleMappingConfiguration(() => _services.Configure<MappingOptions>(config));
|
||||
|
||||
public void ConfigMapping(Action<MappingOptions> options) => EnsureSingleMappingConfiguration(() => _services.Configure(options));
|
||||
|
||||
public string? MediatRLicense { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace WorkFlow.Application.DTO.Config
|
||||
namespace WorkFlow.Application.Dto
|
||||
{
|
||||
public record ConfigDto(int Id,
|
||||
string Title,
|
||||
16
src/WorkFlow.Application/Dto/ObjectDto.cs
Normal file
16
src/WorkFlow.Application/Dto/ObjectDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public class ObjectDto
|
||||
{
|
||||
public IEnumerable<string> Headlines { get; set; } = Array.Empty<string>();
|
||||
|
||||
public IEnumerable<string> Sublines { get; set; } = Array.Empty<string>();
|
||||
|
||||
public string? CmdCheckIn { get; set; }
|
||||
|
||||
public ObjectStateDto? State { get; set; }
|
||||
|
||||
public IEnumerable<ObjectStateHistDto> StateHistories { get; set; } = Array.Empty<ObjectStateHistDto>();
|
||||
|
||||
public IEnumerable<PControlsUpdateDto>? ControlsUpdates { get; set; } = Array.Empty<PControlsUpdateDto>();
|
||||
}
|
||||
15
src/WorkFlow.Application/Dto/ObjectStateDto.cs
Normal file
15
src/WorkFlow.Application/Dto/ObjectStateDto.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public record ObjectStateDto
|
||||
{
|
||||
public virtual string? Intl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds state 2, 3 and 4 as a list of strings.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Others { get; set; } = Array.Empty<string>();
|
||||
|
||||
public virtual IEnumerable<PControlsTFDto>? TFControls { get; set; }
|
||||
|
||||
public virtual IEnumerable<TfFileDto>? TfFiles { get; set; }
|
||||
}
|
||||
15
src/WorkFlow.Application/Dto/ObjectStateHistDto.cs
Normal file
15
src/WorkFlow.Application/Dto/ObjectStateHistDto.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public record ObjectStateHistDto
|
||||
{
|
||||
public virtual string? Intl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds state 2, 3 and 4 as a list of strings.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Others { get; set; } = Array.Empty<string>();
|
||||
|
||||
public string? ChangedWho { get; set; }
|
||||
|
||||
public DateTime? ChangedWhen { get; set; }
|
||||
}
|
||||
24
src/WorkFlow.Application/Dto/PControlsTFDto.cs
Normal file
24
src/WorkFlow.Application/Dto/PControlsTFDto.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public record PControlsTFDto
|
||||
{
|
||||
public byte? DialogNo { get; set; }
|
||||
|
||||
public string? AttrName { get; set; }
|
||||
|
||||
public string? CtrlType { get; set; }
|
||||
|
||||
public string? Caption { get; set; }
|
||||
|
||||
public string? Text { get; set; }
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public bool? Mandatory { get; set; }
|
||||
|
||||
public string? ChoiceList { get; set; }
|
||||
|
||||
public bool? ReadOnly { get; set; }
|
||||
|
||||
public byte? Sequ { get; set; }
|
||||
}
|
||||
12
src/WorkFlow.Application/Dto/PControlsUpdateDto.cs
Normal file
12
src/WorkFlow.Application/Dto/PControlsUpdateDto.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public record PControlsUpdateDto
|
||||
{
|
||||
public string? AttrName { get; set; }
|
||||
|
||||
public string? AttrValue { get; set; }
|
||||
|
||||
public string? AddedWho { get; set; }
|
||||
|
||||
public DateTime? AddedWhen { get; set; }
|
||||
}
|
||||
22
src/WorkFlow.Application/Dto/ProfileDto.cs
Normal file
22
src/WorkFlow.Application/Dto/ProfileDto.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using WorkFlow.Application.Buttons;
|
||||
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public class ProfileDto
|
||||
{
|
||||
public byte? TypeId { get; init; }
|
||||
|
||||
public string? Caption { get; init; }
|
||||
|
||||
public string? Subtitle { get; init; }
|
||||
|
||||
public int? CountObj { get; init; }
|
||||
|
||||
public string? ForeColor { get; init; }
|
||||
|
||||
public string? BackColor { get; init; }
|
||||
|
||||
public IEnumerable<ObjectDto> Objects { get; init; } = Array.Empty<ObjectDto>();
|
||||
|
||||
public IEnumerable<ButtonDto>? Buttons { get; set; } = Array.Empty<ButtonDto>();
|
||||
}
|
||||
14
src/WorkFlow.Application/Dto/TfFileDto.cs
Normal file
14
src/WorkFlow.Application/Dto/TfFileDto.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace WorkFlow.Application.Dto;
|
||||
|
||||
public class TfFileDto
|
||||
{
|
||||
public string? Url { get; set; }
|
||||
|
||||
public string? Headline { get; set; }
|
||||
|
||||
public string? Subline { get; set; }
|
||||
|
||||
public string? Comment { get; set; }
|
||||
|
||||
public string? IconUrl { get; set; }
|
||||
}
|
||||
52
src/WorkFlow.Application/Mapping/MappingOptions.cs
Normal file
52
src/WorkFlow.Application/Mapping/MappingOptions.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
public class MappingOptions
|
||||
{
|
||||
public UriBuilderOptions TfFileUri { get; set; } = new();
|
||||
|
||||
public UriBuilderOptions TfFileIconUri { get; set; } = new();
|
||||
|
||||
public class UriBuilderOptions
|
||||
{
|
||||
public string? Scheme { get; set; }
|
||||
|
||||
public string? Host { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
private string _path = "/";
|
||||
|
||||
public string Path
|
||||
{
|
||||
get => _path;
|
||||
set => _path = value.Trim('/');
|
||||
}
|
||||
|
||||
private string _query = string.Empty;
|
||||
|
||||
public string Query
|
||||
{
|
||||
get => _query;
|
||||
set => _query = value.TrimStart('?');
|
||||
}
|
||||
|
||||
public UriBuilder ToBuilder
|
||||
{
|
||||
get
|
||||
{
|
||||
var uriBuilder = new UriBuilder()
|
||||
{
|
||||
Scheme = Scheme ?? "http",
|
||||
Host = Host ?? "localhost",
|
||||
Path = Path,
|
||||
Query = Query,
|
||||
};
|
||||
|
||||
if (Port is int port)
|
||||
uriBuilder.Port = port;
|
||||
|
||||
return uriBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/WorkFlow.Application/Mapping/MappingProfile.cs
Normal file
37
src/WorkFlow.Application/Mapping/MappingProfile.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using WorkFlow.Application.Buttons;
|
||||
using WorkFlow.Application.Dto;
|
||||
using WorkFlow.Domain.Entities;
|
||||
|
||||
namespace WorkFlow.Application.Mapping;
|
||||
|
||||
public class MappingProfile : AutoMapper.Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<Config, ConfigDto>();
|
||||
|
||||
CreateMap<Profile, ProfileDto>();
|
||||
|
||||
CreateMap<PControlsTF, PControlsTFDto>();
|
||||
|
||||
CreateMap<Button, ButtonDto>();
|
||||
|
||||
CreateMap<PObject, ObjectDto>()
|
||||
.ForMember(dest => dest.Headlines, opt => opt.MapFrom(src => new[] { src.Headline1, src.Headline2 }))
|
||||
.ForMember(dest => dest.Sublines, opt => opt.MapFrom(src => new[] { src.Subline1, src.Subline2 }));
|
||||
|
||||
CreateMap<PObjectState, ObjectStateDto>()
|
||||
.ForMember(dest => dest.Intl, opt => opt.MapFrom(src => src.State1 != null ? src.State1.IntlState : null))
|
||||
.ForMember(dest => dest.Others, opt => opt.MapFrom(src => new string?[] { src.State2, src.State3, src.State4 }));
|
||||
|
||||
CreateMap<PObjectStateHist, ObjectStateHistDto>()
|
||||
.ForMember(dest => dest.Intl, opt => opt.MapFrom(src => src.State1 != null ? src.State1.IntlState : null))
|
||||
.ForMember(dest => dest.Others, opt => opt.MapFrom(src => new string?[] { src.State2, src.State3, src.State4 }));
|
||||
|
||||
CreateMap<TfFile, TfFileDto>()
|
||||
.ForMember(dest => dest.Url, opt => opt.MapFrom<TfFileUriResolver>())
|
||||
.ForMember(dest => dest.IconUrl, opt => opt.MapFrom<TfFileIconUriResolver>());
|
||||
|
||||
CreateMap<PControlsUpdate, PControlsUpdateDto>();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user