feat: restructure ProgramManager area and integrate Shared.Contracts

This commit is contained in:
2025-12-13 16:41:27 +03:30
parent b12b3b9eb8
commit f2293934d4
52 changed files with 134 additions and 735 deletions

View File

@@ -104,6 +104,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GozareshgirProgramManager.D
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GozareshgirProgramManager.Infrastructure", "ProgramManager\src\Infrastructure\GozareshgirProgramManager.Infrastructure\GozareshgirProgramManager.Infrastructure.csproj", "{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.Contracts", "Shared.Contracts\Shared.Contracts.csproj", "{08B234B6-783B-44E9-9961-4F97EAD16308}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -226,6 +228,10 @@ Global
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Release|Any CPU.Build.0 = Release|Any CPU
{08B234B6-783B-44E9-9961-4F97EAD16308}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08B234B6-783B-44E9-9961-4F97EAD16308}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08B234B6-783B-44E9-9961-4F97EAD16308}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08B234B6-783B-44E9-9961-4F97EAD16308}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj" />
</ItemGroup>

View File

@@ -15,6 +15,7 @@ using MediatR;
using PersianTools.Core;
using System.Runtime.InteropServices;
using Microsoft.EntityFrameworkCore;
using Shared.Contracts.Holidays;
namespace GozareshgirProgramManager.Application.Modules.Checkouts.Commands.CreateCheckout;
@@ -25,16 +26,18 @@ public class CreateOrEditCheckoutCommandHandler : IBaseCommandHandler<CreateOrEd
private readonly ISalaryPaymentSettingRepository _salaryPaymentSettingRepository;
private readonly ITaskSectionActivityRepository _taskSectionActivityRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IGozareshgirDbContext _gozareshgirDbContext;
private readonly IHolidayQueryService _holidayQueryService;
public CreateOrEditCheckoutCommandHandler(ICheckoutRepository checkoutRepository, IUnitOfWork unitOfWork, ISalaryPaymentSettingRepository salaryPaymentSettingRepository, ITaskSectionActivityRepository taskSectionActivityRepository, IGozareshgirDbContext gozareshgirDbContext)
public CreateOrEditCheckoutCommandHandler(ICheckoutRepository checkoutRepository, IUnitOfWork unitOfWork,
ISalaryPaymentSettingRepository salaryPaymentSettingRepository,
ITaskSectionActivityRepository taskSectionActivityRepository, IHolidayQueryService holidayQueryService)
{
_checkoutRepository = checkoutRepository;
_unitOfWork = unitOfWork;
_salaryPaymentSettingRepository = salaryPaymentSettingRepository;
_taskSectionActivityRepository = taskSectionActivityRepository;
_gozareshgirDbContext = gozareshgirDbContext;
_holidayQueryService = holidayQueryService;
}
public async Task<OperationResult> Handle(CreateOrEditCheckoutCommand request, CancellationToken cancellationToken)
@@ -182,9 +185,7 @@ public class CreateOrEditCheckoutCommandHandler : IBaseCommandHandler<CreateOrEd
var endMonth = Convert.ToInt32(endDate.Substring(5, 2));
var endDay = Convert.ToInt32(endDate.Substring(8, 2));
var persianEnd = new PersianDateTime(endYear, endMonth, endDay);
var holidays = await _gozareshgirDbContext.HolidayItems.Where(x=>x.Holidaydate >= start && x.Holidaydate <= end).ToListAsync();
var holidays = await _holidayQueryService.GetHolidaysInDates(start, end) ;
int mandatoryHours = 0;
for (var currentDay = persianStart; currentDay <= persianEnd; currentDay = currentDay.AddDays(1))
{

View File

@@ -1,11 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.LoginUser;
/// <summary>
/// دستور ورود کاربر به سیستم
/// </summary>
public record LoginUserCommand(long UserId) : IBaseCommand<LoginResponse>;

View File

@@ -1,98 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.LoginUser;
/// <summary>
/// Handler برای ورود کاربر به سیستم
/// </summary>
public class LoginUserCommandHandler : IRequestHandler<LoginUserCommand, OperationResult<LoginResponse>>
{
private readonly IUserRepository _userRepository;
private readonly IUserRefreshTokenRepository _refreshTokenRepository;
private readonly IAuthHelper _authHelper;
private readonly IUnitOfWork _unitOfWork;
public LoginUserCommandHandler(
IUserRepository userRepository,
IAuthHelper authHelper,
IUnitOfWork unitOfWork, IUserRefreshTokenRepository refreshTokenRepository)
{
_userRepository = userRepository;
_authHelper = authHelper;
_unitOfWork = unitOfWork;
_refreshTokenRepository = refreshTokenRepository;
}
public async Task<OperationResult<LoginResponse>> Handle(LoginUserCommand request, CancellationToken cancellationToken)
{
// اعتبارسنجی
if (request.UserId <= 0)
{
return OperationResult<LoginResponse>.Failure("شناسه کاربری معتبر نیست", ErrorType.BadRequest);
}
// یافتن کاربر
var user = await _userRepository.GetUserWithRolesByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return OperationResult<LoginResponse>.Failure("کاربر یافت نشد", ErrorType.NotFound);
}
// بررسی فعال بودن کاربر
if (!user.IsActive)
{
return OperationResult<LoginResponse>.Failure("حساب کاربری غیرفعال است", ErrorType.Unauthorized);
}
// تولید توکن‌ها با استفاده از AuthHelper
var roles = user.RoleUser
.Select(r => r.RoleId.ToString()).ToList();
var session = _authHelper.SignIn(
user.Id,
user.UserName,
user.FullName,
user.AccountId??0,
roles);
// دریافت اطلاعات درخواست با استفاده از AuthHelper
var ipAddress = _authHelper.GetClientIpAddress();
var userAgent = _authHelper.GetUserAgent();
// ذخیره Refresh Token در دیتابیس
//user.AddRefreshToken(refreshToken, refreshTokenExpiration, ipAddress, userAgent);
var refreshTokenEntity = new UserRefreshToken(
user.Id,
session.RefreshToken,
session.RefreshTokenExpiration,
ipAddress,
userAgent);
await _refreshTokenRepository.CreateAsync(refreshTokenEntity);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// ساخت پاسخ (RefreshToken به فرانت داده نمی‌شود)
var response = new LoginResponse
{
AccessToken = session.AccessToken,
ExpiresAt = session.AccessTokenExpiration,
UserId = user.Id,
FullName = user.FullName,
UserName = user.UserName,
Roles = user.RoleUser.Select(r => r.RoleId).ToList()
};
return OperationResult<LoginResponse>.Success(response);
}
}

View File

@@ -1,11 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.RefreshUserToken;
/// <summary>
/// دستور تازه‌سازی توکن دسترسی کاربر
/// </summary>
public record RefreshUserTokenCommand() : IBaseCommand;

View File

@@ -1,86 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using Microsoft.EntityFrameworkCore;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.RefreshUserToken;
/// <summary>
/// Handler برای تازه‌سازی توکن دسترسی
/// </summary>
public class RefreshUserTokenCommandHandler : IBaseCommandHandler<RefreshUserTokenCommand>
{
private readonly IAuthHelper _authHelper;
private readonly IProgramManagerDbContext _context;
public RefreshUserTokenCommandHandler(
IAuthHelper authHelper,
IProgramManagerDbContext context)
{
_authHelper = authHelper;
_context = context;
}
public async Task<OperationResult> Handle(RefreshUserTokenCommand request, CancellationToken cancellationToken)
{
var refreshToken = _authHelper.GetRefreshTokenFromCookie();
// یافتن کاربر و Refresh Token فعال از دیتابیس
var user = await _context.Users
.Include(u => u.RefreshTokens)
.Include(u => u.RoleUser)
.FirstOrDefaultAsync(u => u.RefreshTokens.Any(r=>r.Token ==refreshToken), cancellationToken);
if (user == null)
{
return OperationResult<AccessTokenResponse>.Failure("کاربر یافت نشد", ErrorType.NotFound);
}
// بررسی فعال بودن کاربر
if (!user.IsActive)
{
return OperationResult<AccessTokenResponse>.Failure("حساب کاربری غیرفعال است", ErrorType.Unauthorized);
}
// پیدا کردن Refresh Token فعال
var activeRefreshToken = user.RefreshTokens
.FirstOrDefault(rt => rt.Token == refreshToken && rt.IsActive);
if (activeRefreshToken == null)
{
return OperationResult<AccessTokenResponse>.Failure(
"نشست شما منقضی شده است. لطفاً دوباره وارد شوید",
ErrorType.Unauthorized);
}
if (!activeRefreshToken.IsActive|| activeRefreshToken.IsRevoked||activeRefreshToken.IsExpired)
{
return OperationResult<AccessTokenResponse>.Failure(
"نشست شما منقضی شده است. لطفاً دوباره وارد شوید",
ErrorType.Unauthorized);
}
// تولید Access Token جدید با استفاده از AuthHelper
var roles = user.RoleUser.Select(r => r.RoleId.ToString()).ToList();
var newAccessToken = _authHelper.GenerateAccessToken(
user.Id,
user.UserName,
user.FullName,
user.AccountId,
roles);
var response = new AccessTokenResponse
{
AccessToken = newAccessToken,
ExpiresAt = DateTime.UtcNow.AddMinutes(30),
UserId = user.Id,
FullName = user.FullName,
UserName = user.UserName
};
return OperationResult<AccessTokenResponse>.Success(response);
}
}

View File

@@ -1,11 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SignOutUser;
/// <summary>
/// دستور خروج کاربر از سیستم
/// </summary>
public record SignOutUserCommand(string RefreshToken) : IBaseCommand;

View File

@@ -1,68 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SignOutUser;
/// <summary>
/// Handler برای خروج کاربر از سیستم
/// </summary>
public class SignOutUserCommandHandler : IBaseCommandHandler<SignOutUserCommand>
{
private readonly IAuthHelper _authHelper;
private readonly IProgramManagerDbContext _context;
private readonly IUnitOfWork _unitOfWork;
public SignOutUserCommandHandler(
IAuthHelper _authHelper,
IProgramManagerDbContext context,
IUnitOfWork unitOfWork)
{
this._authHelper = _authHelper;
_context = context;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(SignOutUserCommand request, CancellationToken cancellationToken)
{
// دریافت UserId از Claims با استفاده از AuthHelper
var userId = _authHelper.GetCurrentUserId();
if (!userId.HasValue)
{
return OperationResult.Failure("کاربر احراز هویت نشده است", ErrorType.Unauthorized);
}
if (string.IsNullOrEmpty(request.RefreshToken))
{
return OperationResult.Failure("توکن تازه‌سازی یافت نشد", ErrorType.BadRequest);
}
// یافتن کاربر
var user = await _context.Users
.Include(u => u.RefreshTokens)
.FirstOrDefaultAsync(u => u.Id == userId.Value, cancellationToken);
if (user == null)
{
return OperationResult.Failure("کاربر یافت نشد", ErrorType.NotFound);
}
try
{
// لغو Refresh Token
user.RevokeRefreshToken(request.RefreshToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_authHelper.SignOut();
return OperationResult.Success();
}
catch (InvalidOperationException ex)
{
return OperationResult.Failure(ex.Message, ErrorType.BadRequest);
}
}
}

View File

@@ -1,10 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SsoLogin;
/// <summary>
/// دستور ورود از طریق SSO با استفاده از توکن JWT
/// </summary>
public record SsoLoginCommand(string Token) : IBaseCommand<LoginResponse>;

View File

@@ -1,115 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SsoLogin;
/// <summary>
/// Handler برای ورود از طریق SSO با استفاده از JWT Token
/// </summary>
public class SsoLoginCommandHandler : IRequestHandler<SsoLoginCommand, OperationResult<LoginResponse>>
{
private readonly IUserRepository _userRepository;
private readonly IUserRefreshTokenRepository _refreshTokenRepository;
private readonly IAuthHelper _authHelper;
private readonly IUnitOfWork _unitOfWork;
public SsoLoginCommandHandler(
IUserRepository userRepository,
IAuthHelper authHelper,
IUnitOfWork unitOfWork,
IUserRefreshTokenRepository refreshTokenRepository)
{
_userRepository = userRepository;
_authHelper = authHelper;
_unitOfWork = unitOfWork;
_refreshTokenRepository = refreshTokenRepository;
}
public async Task<OperationResult<LoginResponse>> Handle(SsoLoginCommand request, CancellationToken cancellationToken)
{
// اعتبارسنجی
if (string.IsNullOrWhiteSpace(request.Token))
{
return OperationResult<LoginResponse>.Failure("توکن SSO معتبر نیست", ErrorType.BadRequest);
}
// اعتبارسنجی توکن و استخراج Claims
var principal = _authHelper.ValidateToken(request.Token);
if (principal == null)
{
return OperationResult<LoginResponse>.Failure("توکن SSO نامعتبر یا منقضی شده است", ErrorType.Unauthorized);
}
// استخراج AccountId از Claims
var accountIdClaim = principal.FindFirst("AccountId")?.Value;
if (string.IsNullOrEmpty(accountIdClaim) || !long.TryParse(accountIdClaim, out var accountId))
{
return OperationResult<LoginResponse>.Failure("AccountId در توکن یافت نشد", ErrorType.BadRequest);
}
// یافتن کاربر بر اساس AccountId
var user = await _userRepository.GetByGozareshgirAccountId(accountId);
if (user == null)
{
return OperationResult<LoginResponse>.Failure("کاربر با AccountId مشخص شده یافت نشد", ErrorType.NotFound);
}
// بررسی فعال بودن کاربر
if (!user.IsActive)
{
return OperationResult<LoginResponse>.Failure("حساب کاربری غیرفعال است", ErrorType.Unauthorized);
}
// بارگذاری نقش‌های کاربر
user = await _userRepository.GetUserWithRolesByIdAsync(user.Id, cancellationToken);
if (user == null)
{
return OperationResult<LoginResponse>.Failure("خطا در بارگذاری اطلاعات کاربر", ErrorType.InternalServerError);
}
// تولید توکن‌های جدید برای کاربر
var roles = user.RoleUser
.Select(r => r.RoleId.ToString()).ToList();
var session = _authHelper.SignIn(
user.Id,
user.UserName,
user.FullName,
user.AccountId ?? 0,
roles);
// دریافت اطلاعات درخواست
var ipAddress = _authHelper.GetClientIpAddress();
var userAgent = _authHelper.GetUserAgent();
// ذخیره Refresh Token در دیتابیس
var refreshTokenEntity = new UserRefreshToken(
user.Id,
session.RefreshToken,
session.RefreshTokenExpiration,
ipAddress,
userAgent);
await _refreshTokenRepository.CreateAsync(refreshTokenEntity);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// ساخت پاسخ
var response = new LoginResponse
{
AccessToken = session.AccessToken,
ExpiresAt = session.AccessTokenExpiration,
UserId = user.Id,
FullName = user.FullName,
UserName = user.UserName,
Roles = user.RoleUser.Select(r => r.RoleId).ToList()
};
return OperationResult<LoginResponse>.Success(response);
}
}

View File

@@ -9,38 +9,38 @@ namespace GozareshgirProgramManager.Application._Common.Interfaces;
public interface IAuthHelper
{
// ==================== Token Generation ====================
LoginSession SignIn(long userId, string userName, string fullName, long accountId, List<string> roles);
/// <summary>
/// تولید Access Token
/// </summary>
string GenerateAccessToken(long userId, string userName, string fullName, long? accountId, List<string> roles);
// LoginSession SignIn(long userId, string userName, string fullName, long accountId, List<string> roles);
// /// <summary>
// /// تولید Access Token
// /// </summary>
// string GenerateAccessToken(long userId, string userName, string fullName, long? accountId, List<string> roles);
/// <summary>
/// تولید Refresh Token
/// </summary>
string GenerateRefreshToken();
// /// <summary>
// /// تولید Refresh Token
// /// </summary>
// string GenerateRefreshToken();
/// <summary>
/// دریافت تاریخ انقضای Refresh Token
/// </summary>
DateTime GetRefreshTokenExpiration();
// /// <summary>
// /// دریافت تاریخ انقضای Refresh Token
// /// </summary>
// DateTime GetRefreshTokenExpiration();
// ==================== Token Validation ====================
/// <summary>
/// اعتبارسنجی توکن و استخراج Claims
/// </summary>
ClaimsPrincipal? ValidateToken(string token);
//
// /// <summary>
// /// اعتبارسنجی توکن و استخراج Claims
// /// </summary>
// ClaimsPrincipal? ValidateToken(string token);
/// <summary>
/// اعتبارسنجی توکن منقضی شده (بدون چک زمان انقضا)
/// </summary>
ClaimsPrincipal? ValidateExpiredToken(string token);
// /// <summary>
// /// اعتبارسنجی توکن منقضی شده (بدون چک زمان انقضا)
// /// </summary>
// ClaimsPrincipal? ValidateExpiredToken(string token);
/// <summary>
/// استخراج UserId از توکن (حتی اگر منقضی شده باشد)
/// </summary>
long? GetUserIdFromToken(string token);
// /// <summary>
// /// استخراج UserId از توکن (حتی اگر منقضی شده باشد)
// /// </summary>
// long? GetUserIdFromToken(string token);
// ==================== HttpContext Helpers ====================
@@ -49,15 +49,11 @@ public interface IAuthHelper
/// </summary>
string? GetClientIpAddress();
/// <summary>
/// دریافت User Agent کاربر جاری
/// </summary>
string? GetUserAgent();
/// <summary>
/// دریافت Refresh Token از Cookie
/// </summary>
string? GetRefreshTokenFromCookie();
// /// <summary>
// /// دریافت Refresh Token از Cookie
// /// </summary>
// string? GetRefreshTokenFromCookie();
// ==================== Current User Claims ====================
@@ -71,39 +67,12 @@ public interface IAuthHelper
/// </summary>
long? GetCurrentUserId();
/// <summary>
/// دریافت نام کاربری جاری از Claims
/// </summary>
string? GetCurrentUserName();
/// <summary>
/// دریافت نام کامل کاربر جاری از Claims
/// </summary>
string? GetCurrentFullName();
/// <summary>
/// دریافت AccountId کاربر جاری از Claims
/// </summary>
long? GetCurrentAccountId();
/// <summary>
/// دریافت نقش‌های کاربر جاری از Claims
/// </summary>
List<string> GetCurrentUserRoles();
// ==================== Role Checking ====================
/// <summary>
/// بررسی دسترسی کاربر به نقش خاص
/// </summary>
bool HasRole(string roleName);
/// <summary>
/// بررسی دسترسی کاربر به یکی از نقش‌ها
/// </summary>
bool HasAnyRole(params string[] roleNames);
void SignOut();
}
public class LoginSession
{

View File

@@ -1,12 +0,0 @@
using GozareshgirProgramManager.Domain.HolidayAgg;
using GozareshgirProgramManager.Domain.HolidayItemAgg;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application._Common.Interfaces;
public interface IGozareshgirDbContext
{
DbSet<HolidayItem> HolidayItems { get; set; }
DbSet<Holiday> Holidays { get; set; }
}

View File

@@ -42,16 +42,7 @@ public static class DependencyInjection
// Register IAppDbContext
services.AddScoped<IProgramManagerDbContext>(provider =>
provider.GetRequiredService<ProgramManagerDbContext>());
#region GozareshgirDbContext
services.AddDbContext<GozareshgirDbContext>(x => x.UseSqlServer(configuration.GetConnectionString("GozareshgirDb")));
services.AddScoped<IGozareshgirDbContext>(provider =>
provider.GetRequiredService<GozareshgirDbContext>());
#endregion
// Unit of Work
services.AddScoped<IUnitOfWork, UnitOfWork>();

View File

@@ -1,22 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.HolidayAgg;
using GozareshgirProgramManager.Domain.HolidayItemAgg;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Infrastructure.Persistence.Context;
public class GozareshgirDbContext : DbContext, IGozareshgirDbContext
{
public GozareshgirDbContext(DbContextOptions<GozareshgirDbContext> options) : base(options)
{
}
public DbSet<HolidayItem> HolidayItems { get; set; } = null!;
public DbSet<Holiday> Holidays { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(GozareshgirDbContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}

View File

@@ -149,7 +149,7 @@ public class AuthHelper : IAuthHelper
/// </summary>
public long? GetCurrentUserId()
{
var userIdClaim = _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var userIdClaim = _httpContextAccessor.HttpContext?.User?.FindFirst("pm.userId")?.Value;
return long.TryParse(userIdClaim, out var userId) ? userId : null;
}

View File

@@ -1,143 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Users.Commands.LoginUser;
using GozareshgirProgramManager.Application.Modules.Users.Commands.RefreshUserToken;
using GozareshgirProgramManager.Application.Modules.Users.Commands.SignOutUser;
using GozareshgirProgramManager.Application.Modules.Users.Commands.SsoLogin;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
/// <summary>
/// کنترلر احراز هویت
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class AuthController :AdminBaseController
{
private readonly IAuthHelper _authHelper;
private readonly IMediator _mediator;
public AuthController(IAuthHelper authHelper, IMediator mediator)
{
_authHelper = authHelper;
_mediator = mediator;
}
/// <summary>
/// ورود به سیستم با شناسه کاربری
/// </summary>
/// <param name="request">شناسه کاربر</param>
/// <returns>فقط Access Token - Refresh Token در سرور ذخیره می‌شود</returns>
[HttpPost("login")]
[AllowAnonymous]
public async Task<ActionResult<OperationResult<LoginResponse>>> Login([FromBody] LoginByIdRequest request)
{
var command = new LoginUserCommand(request.UserId);
var result = await _mediator.Send(command);
return result;
}
/// <summary>
/// ورود به سیستم از طریق SSO با استفاده از توکن JWT
/// توکن JWT از query string دریافت می‌شود و Claims آن استخراج می‌شود
/// سپس کاربر بر اساس AccountId موجود در Claims لاگین می‌شود
/// </summary>
/// <param name="token">JWT Token از سیستم خارجی</param>
/// <returns>Access Token و اطلاعات کاربر</returns>
[HttpGet("sso-login")]
[AllowAnonymous]
public async Task<ActionResult<OperationResult<LoginResponse>>> SsoLogin([FromQuery] string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return BadRequest(OperationResult<LoginResponse>.Failure("توکن الزامی است", ErrorType.BadRequest));
}
var command = new SsoLoginCommand(token);
var result = await _mediator.Send(command);
return result;
}
/// <summary>
/// خروج از سیستم
/// </summary>
[HttpPost("signout")]
[Authorize]
public new async Task<ActionResult<OperationResult>> SignOut()
{
// دریافت Refresh Token از Header با استفاده از AuthHelper
var refreshToken = _authHelper.GetRefreshTokenFromCookie();
if (string.IsNullOrEmpty(refreshToken))
{
return OperationResult.Failure("توکن تازه‌سازی یافت نشد");
}
var command = new SignOutUserCommand(refreshToken);
var result = await _mediator.Send(command);
if (result.IsSuccess)
{
return Ok(result);
}
return StatusCode(result.ErrorType switch
{
ErrorType.Unauthorized => 401,
ErrorType.BadRequest => 400,
ErrorType.NotFound => 404,
_ => 500
}, result);
}
/// <summary>
/// تازه‌سازی توکن دسترسی
/// توکن منقضی شده را می‌گیرد و Access Token جدید برمی‌گرداند
/// Refresh Token از دیتابیس خوانده می‌شود و به فرانت داده نمی‌شود
/// </summary>
[HttpPost("refresh")]
[AllowAnonymous]
public async Task<ActionResult<OperationResult>> RefreshAccessToken()
{
var refreshTokenCommand = new RefreshUserTokenCommand();
var result = await _mediator.Send(refreshTokenCommand);
return result;
}
/// <summary>
/// دریافت اطلاعات کاربر جاری
/// </summary>
[HttpGet("current")]
public IActionResult GetCurrentUser()
{
if (!_authHelper.IsAuthenticated())
{
return Unauthorized(new { message = "کاربر احراز هویت نشده است" });
}
return Ok(new
{
userId = _authHelper.GetCurrentUserId(),
fullName= _authHelper.GetCurrentFullName(),
roles = _authHelper.GetCurrentUserRoles()
});
}
}
/// <summary>
/// درخواست ورود با شناسه کاربری
/// </summary>
public class LoginByIdRequest
{
public long UserId { get; set; }
}

View File

@@ -7,9 +7,9 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class CheckoutController : AdminBaseController
public class CheckoutController : ProgramManagerBaseController
{
private readonly IMediator _mediator;

View File

@@ -14,9 +14,9 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class ProjectController : AdminBaseController
public class ProjectController : ProgramManagerBaseController
{
private readonly IMediator _mediator;

View File

@@ -6,9 +6,9 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class RoleController : AdminBaseController
public class RoleController : ProgramManagerBaseController
{
private readonly IMediator _mediator;

View File

@@ -8,9 +8,9 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class SalaryPaymentSettingsController : AdminBaseController
public class SalaryPaymentSettingsController : ProgramManagerBaseController
{
private readonly IMediator _mediator;

View File

@@ -4,9 +4,9 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class SkillController:AdminBaseController
public class SkillController:ProgramManagerBaseController
{
private readonly IMediator _mediator;

View File

@@ -8,9 +8,9 @@ using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class UserController : AdminBaseController
public class UserController : ProgramManagerBaseController
{
private readonly IMediator _mediator;

View File

@@ -1,6 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@using Microsoft.EntityFrameworkCore
@using _0_Framework.Application
@model ServiceHost.Areas.Admin.Pages.Accounts.Account.IndexModel
@inject IAuthHelper _AuthHelper;

View File

@@ -1,5 +1,4 @@
@using Microsoft.CodeAnalysis.CSharp.Syntax
@model CompanyManagment.App.Contracts.Checkout.CreateCheckoutListViewModel
@model CompanyManagment.App.Contracts.Checkout.CreateCheckoutListViewModel
@{
var i = 1;
var b = 0;

View File

@@ -1,5 +1,4 @@
@using System.Security.Cryptography.X509Certificates
@model CompanyManagment.App.Contracts.Checkout.CheckoutViewModel
@model CompanyManagment.App.Contracts.Checkout.CheckoutViewModel
@{
var totalDays = Model.MonthlyRollCall?.Count ?? 0;
var rightSideDays = totalDays / 2;

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.Checkouts.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.ContractingParties.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.Contracts.IndexModel

View File

@@ -1,5 +1,4 @@
@using _0_Framework.Application
@model CompanyManagment.App.Contracts.Employee.CreateEmployee
@model CompanyManagment.App.Contracts.Employee.CreateEmployee
@{
var adminVersion = _0_Framework.Application.Version.AdminVersion;

View File

@@ -1,5 +1,4 @@
@using _0_Framework.Application
@model CompanyManagment.App.Contracts.Employee.CreateEmployee
@model CompanyManagment.App.Contracts.Employee.CreateEmployee
@{
var adminVersion = _0_Framework.Application.Version.AdminVersion;

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.Employees.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.Employers.IndexModel
@{

View File

@@ -1,7 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@using Microsoft.EntityFrameworkCore
@using CompanyManagment.App.Contracts.Holiday
@model ServiceHost.Areas.Admin.Pages.Company.Holidays.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@using CompanyManagment.App.Contracts.InsuranceList.Enums
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model CompanyManagment.App.Contracts.InsuranceList.InsuranceListConfirmOperation
@{

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.Jobs.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.MandatoryHours.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.Representative
@model ServiceHost.Areas.Admin.Pages.Company.Representative.IndexModel
@{

View File

@@ -1,6 +1,4 @@
@page
@using _0_Framework.Application.Enums
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model ServiceHost.Areas.Admin.Pages.Company.SmsResult.SmsSettingsModel
@Html.AntiForgeryToken()
@{

View File

@@ -1,6 +1,5 @@
@page
@using _0_Framework.Application
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model ServiceHost.Areas.Admin.Pages.Company.Workshops.EditWorkshopModel
@{
string adminVersion = _0_Framework.Application.Version.AdminVersion;

View File

@@ -1,5 +1,4 @@
@page
@using CompanyManagment.App.Contracts.PersonalContractingParty
@model ServiceHost.Areas.Admin.Pages.Company.Workshops.IndexModel
@{

View File

@@ -1,5 +1,4 @@
@using AccountManagement.Application.Contracts.Account
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model CompanyManagment.App.Contracts.Workshop.EditWorkshop
@{

View File

@@ -2,7 +2,6 @@
@page
@using _0_Framework.Application
@using AccountManagement.Application.Contracts.TicketAccessAccount
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model ServiceHost.Areas.Admin.Pages.IndexModel
@inject ITicketAccessAccountApplication TicketAccessAccount;
@inject IAuthHelper authHelper;

View File

@@ -1,6 +1,4 @@
@using Microsoft.AspNetCore.Razor.Language.Intermediate
@using _0_Framework.Application
@using Version = _0_Framework.Application.Version
@using Version = _0_Framework.Application.Version
@inject _0_Framework.Application.IAuthHelper AuthHelper;
@{

View File

@@ -1,6 +1,5 @@
@using _0_Framework.Application
@using AccountManagement.Domain.TicketAccessAccountAgg
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.Extensions.Options
@inject _0_Framework.Application.IAuthHelper AuthHelper;
@inject ITicketAccessAccountRepository TicketAccessAccount;

View File

@@ -1,4 +1,3 @@
@using ServiceHost
@namespace ServiceHost.Pages
@namespace ServiceHost.Areas.Admin.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, ServiceHost

View File

@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ServiceHost.BaseControllers;
[Authorize(Policy = "AdminArea")]
[Area("ProgramManager")]
[ApiExplorerSettings(GroupName = "ProgramManager")]
[Route("api/[area]/[controller]")]
public class ProgramManagerBaseController : ControllerBase
{
}

View File

@@ -231,36 +231,37 @@ builder.Services.AddSwaggerGen(options =>
options.SwaggerDoc("Admin", new OpenApiInfo { Title = "API - Admin", Version = "v1" });
options.SwaggerDoc("Client", new OpenApiInfo { Title = "API - Client", Version = "v1" });
options.SwaggerDoc("Camera", new OpenApiInfo { Title = "API - Camera", Version = "v1" });
options.SwaggerDoc("ProgramManager", new OpenApiInfo { Title = "API - ProgramManager", Version = "v1" });
options.DocInclusionPredicate((docName, apiDesc) =>
string.Equals(docName, apiDesc.GroupName, StringComparison.OrdinalIgnoreCase));
// اضافه کردن پشتیبانی از JWT در Swagger
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Description = "لطفاً 'Bearer [space] token' را وارد کنید."
});
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
// options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
// {
// Name = "Authorization",
// Type = SecuritySchemeType.ApiKey,
// Scheme = "Bearer",
// BearerFormat = "JWT",
// In = ParameterLocation.Header,
// Description = "لطفاً 'Bearer [space] token' را وارد کنید."
// });
//
// options.AddSecurityRequirement(new OpenApiSecurityRequirement
// {
// {
// new Microsoft.OpenApi.Models.OpenApiSecurityScheme
// {
// Reference = new Microsoft.OpenApi.Models.OpenApiReference
// {
// Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
// Id = "Bearer"
// }
// },
// Array.Empty<string>()
// }
// });
options.EnableAnnotations();
});
@@ -396,6 +397,8 @@ if (app.Environment.IsDevelopment())
options.SwaggerEndpoint("/swagger/Admin/swagger.json", "API - Admin");
options.SwaggerEndpoint("/swagger/Client/swagger.json", "API - Client");
options.SwaggerEndpoint("/swagger/Camera/swagger.json", "API - Camera");
options.SwaggerEndpoint("/swagger/ProgramManager/swagger.json", "API - ProgramManager");
});
}

View File

@@ -0,0 +1,8 @@
namespace Shared.Contracts.Holidays;
public record HolidayDto
{
public DateTime Holidaydate { get; private set; }
public long HolidayId { get; private set; }
public string HolidayYear { get; private set; }
}

View File

@@ -0,0 +1,6 @@
namespace Shared.Contracts.Holidays;
public interface IHolidayQueryService
{
Task<List<HolidayDto>> GetHolidaysInDates(DateTime startDate, DateTime endDate);
}

View File

@@ -0,0 +1,6 @@
namespace Shared.Contracts.PmUser;
public interface IPmUserCommandService
{
Task<(bool isSuccess, string pmUserDto)> Create();
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>