Add new domain models and interfaces for project management features

This commit is contained in:
2025-12-08 14:47:03 +03:30
parent b7a7fb01d7
commit 27e8a26ed8
295 changed files with 24896 additions and 26 deletions

View File

@@ -0,0 +1,5 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
public record CreateUserCommand(string FullName, string UserName, string Password, string Mobile, string? Email, long? AccountId, List<long> Roles) : IBaseCommand;

View File

@@ -0,0 +1,43 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.RoleUserAgg;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
public class CreateUserCommandHandler : IBaseCommandHandler<CreateUserCommand>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateUserCommandHandler(IUnitOfWork unitOfWork, IUserRepository userRepository)
{
_unitOfWork = unitOfWork;
_userRepository = userRepository;
}
public async Task<OperationResult> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
#region CustomValidation
if (_userRepository.Exists(x => x.FullName == request.FullName))
return OperationResult.Failure("نام و خانوادگی تکراری است");
if (_userRepository.Exists(x => x.UserName == request.UserName))
return OperationResult.Failure("نام کاربری تکراری است");
if (_userRepository.Exists(x=> !string.IsNullOrWhiteSpace(x.Mobile) && x.Mobile == request.Mobile))
return OperationResult.ValidationError("این شماره همراه قبلا به فرد دیگری اختصاص داده شده است");
if(request.AccountId == 0)
return OperationResult.Failure("آی دی اکانت، از سمت گزارشگیر صفر است");
#endregion
var userRoles = request.Roles.Where(x => x > 0).Select(x => new RoleUser(x)).ToList() ;
var create = new User(request.FullName, request.UserName, request.Password, request.Mobile,
request.Email, request?.AccountId, userRoles);
await _userRepository.CreateAsync(create);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
}

View File

@@ -0,0 +1,24 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
public class CreateUserCommandValidators : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidators()
{
RuleFor(x => x.FullName)
.NotEmpty()
.NotNull()
.WithMessage("نام و نام خانوادگی نمی تواند خالی باشد");
RuleFor(x => x.Mobile)
.NotNull().NotEmpty().WithMessage("شماره همراه نمی تواند خالی باشد");
RuleFor(x=>x.Mobile)
.Length(11).WithMessage("طول شماره همراه می بایست 11 رقم باشد");
RuleFor(x => x.UserName)
.NotEmpty().NotNull().WithMessage("نام کاربری نمیتوان خالی باشد");
}
}

View File

@@ -0,0 +1,34 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.RoleUserAgg;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.EditUser;
public class EditUserCommandHandler :IBaseCommandHandler<EditUserCommand>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public EditUserCommandHandler(IUserRepository userRepository, IUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(EditUserCommand request, CancellationToken cancellationToken)
{
var user = await _userRepository.GetByGozareshgirAccountId(request.AccountId);
if (user != null)
{
var userRoles = request.Roles.Where(x => x > 0).Select(x => new RoleUser(x)).ToList();
user.Edit(request.FullName, request.UserName, request.Mobile, userRoles, request.IsActive);
await _unitOfWork.SaveChangesAsync();
}
return OperationResult.Success();
}
}
public record EditUserCommand(string FullName, string UserName, string Mobile,long AccountId, List<long> Roles, bool IsActive) : IBaseCommand;

View File

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

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

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

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

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

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

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

@@ -0,0 +1,115 @@
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);
}
}