feat: 운영자 관리 API 구현 (#134)
All checks were successful
SPMS_API/pipeline/head This commit looks good
All checks were successful
SPMS_API/pipeline/head This commit looks good
Reviewed-on: https://git.ipstein.myds.me/SPMS/SPMS_API/pulls/135
This commit is contained in:
commit
86f978633e
|
|
@ -92,4 +92,74 @@ public class AccountController : ControllerBase
|
||||||
await _accountService.DeleteAsync(adminCode);
|
await _accountService.DeleteAsync(adminCode);
|
||||||
return Ok(ApiResponse.Success());
|
return Ok(ApiResponse.Success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("operator/create")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "운영자 계정 생성 (이메일 링크)",
|
||||||
|
Description = "Super Admin이 운영자 계정을 생성합니다. 비밀번호 설정 이메일이 발송됩니다.")]
|
||||||
|
[SwaggerResponse(200, "생성 성공", typeof(ApiResponse<OperatorCreateResponseDto>))]
|
||||||
|
[SwaggerResponse(400, "잘못된 요청")]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
[SwaggerResponse(403, "권한 없음")]
|
||||||
|
[SwaggerResponse(409, "이메일 중복")]
|
||||||
|
public async Task<IActionResult> CreateOperatorAsync([FromBody] OperatorCreateRequestDto request)
|
||||||
|
{
|
||||||
|
var result = await _accountService.CreateOperatorAsync(request);
|
||||||
|
return Ok(ApiResponse<OperatorCreateResponseDto>.Success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("operator/delete")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "운영자 계정 삭제",
|
||||||
|
Description = "Super Admin이 운영자 계정을 삭제합니다. (Soft Delete) 자기 자신은 삭제할 수 없습니다.")]
|
||||||
|
[SwaggerResponse(200, "삭제 성공")]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
[SwaggerResponse(403, "권한 없음 / 자기 자신 삭제 불가")]
|
||||||
|
[SwaggerResponse(404, "운영자를 찾을 수 없음")]
|
||||||
|
public async Task<IActionResult> DeleteOperatorAsync([FromBody] OperatorDeleteRequestDto request)
|
||||||
|
{
|
||||||
|
var adminId = GetAdminId();
|
||||||
|
await _accountService.DeleteOperatorAsync(request.AdminCode, adminId);
|
||||||
|
return Ok(ApiResponse.Success());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("operator/list")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "운영자 목록 조회",
|
||||||
|
Description = "Super Admin이 운영자(Manager/User) 목록을 조회합니다. is_active 필터로 비활성 운영자도 조회 가능합니다.")]
|
||||||
|
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<OperatorListResponseDto>))]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
[SwaggerResponse(403, "권한 없음")]
|
||||||
|
public async Task<IActionResult> GetOperatorListAsync([FromBody] OperatorListRequestDto request)
|
||||||
|
{
|
||||||
|
var result = await _accountService.GetOperatorListAsync(request);
|
||||||
|
return Ok(ApiResponse<OperatorListResponseDto>.Success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("operator/password/reset")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "운영자 비밀번호 초기화",
|
||||||
|
Description = "Super Admin이 운영자의 비밀번호를 초기화합니다. 비밀번호 재설정 이메일이 발송됩니다.")]
|
||||||
|
[SwaggerResponse(200, "초기화 성공", typeof(ApiResponse<OperatorCreateResponseDto>))]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
[SwaggerResponse(403, "권한 없음")]
|
||||||
|
[SwaggerResponse(404, "운영자를 찾을 수 없음")]
|
||||||
|
public async Task<IActionResult> ResetOperatorPasswordAsync([FromBody] OperatorPasswordResetRequestDto request)
|
||||||
|
{
|
||||||
|
var result = await _accountService.ResetOperatorPasswordAsync(request.AdminCode);
|
||||||
|
return Ok(ApiResponse<OperatorCreateResponseDto>.Success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long GetAdminId()
|
||||||
|
{
|
||||||
|
var adminIdClaim = User.FindFirst("adminId")?.Value;
|
||||||
|
if (string.IsNullOrEmpty(adminIdClaim) || !long.TryParse(adminIdClaim, out var adminId))
|
||||||
|
{
|
||||||
|
throw new SPMS.Domain.Exceptions.SpmsException(
|
||||||
|
ErrorCodes.Unauthorized,
|
||||||
|
"인증 정보를 확인할 수 없습니다.",
|
||||||
|
401);
|
||||||
|
}
|
||||||
|
return adminId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
SPMS.Application/DTOs/Account/OperatorCreateRequestDto.cs
Normal file
20
SPMS.Application/DTOs/Account/OperatorCreateRequestDto.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class OperatorCreateRequestDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "이메일을 입력해주세요.")]
|
||||||
|
[EmailAddress(ErrorMessage = "올바른 이메일 형식이 아닙니다.")]
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "이름을 입력해주세요.")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Phone(ErrorMessage = "올바른 전화번호 형식이 아닙니다.")]
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "권한을 선택해주세요.")]
|
||||||
|
[Range(1, 2, ErrorMessage = "권한은 Manager(1) 또는 User(2)만 가능합니다.")]
|
||||||
|
public int Role { get; set; }
|
||||||
|
}
|
||||||
15
SPMS.Application/DTOs/Account/OperatorCreateResponseDto.cs
Normal file
15
SPMS.Application/DTOs/Account/OperatorCreateResponseDto.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class OperatorCreateResponseDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("admin_code")]
|
||||||
|
public string AdminCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("role")]
|
||||||
|
public int Role { get; set; }
|
||||||
|
}
|
||||||
11
SPMS.Application/DTOs/Account/OperatorDeleteRequestDto.cs
Normal file
11
SPMS.Application/DTOs/Account/OperatorDeleteRequestDto.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class OperatorDeleteRequestDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "운영자 코드를 입력해주세요.")]
|
||||||
|
[JsonPropertyName("admin_code")]
|
||||||
|
public string AdminCode { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
18
SPMS.Application/DTOs/Account/OperatorListRequestDto.cs
Normal file
18
SPMS.Application/DTOs/Account/OperatorListRequestDto.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class OperatorListRequestDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("page")]
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
|
||||||
|
[JsonPropertyName("size")]
|
||||||
|
public int Size { get; set; } = 20;
|
||||||
|
|
||||||
|
[JsonPropertyName("role")]
|
||||||
|
public int? Role { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_active")]
|
||||||
|
public bool? IsActive { get; set; }
|
||||||
|
}
|
||||||
37
SPMS.Application/DTOs/Account/OperatorListResponseDto.cs
Normal file
37
SPMS.Application/DTOs/Account/OperatorListResponseDto.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using SPMS.Application.DTOs.Notice;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class OperatorListResponseDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public List<OperatorItemDto> Items { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("pagination")]
|
||||||
|
public PaginationDto Pagination { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OperatorItemDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("admin_code")]
|
||||||
|
public string AdminCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("role")]
|
||||||
|
public int Role { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_active")]
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("last_login_at")]
|
||||||
|
public DateTime? LastLoginAt { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("created_at")]
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Account;
|
||||||
|
|
||||||
|
public class OperatorPasswordResetRequestDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "운영자 코드를 입력해주세요.")]
|
||||||
|
[JsonPropertyName("admin_code")]
|
||||||
|
public string AdminCode { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
@ -9,4 +9,8 @@ public interface IAccountService
|
||||||
Task<AccountResponseDto> GetByAdminCodeAsync(string adminCode);
|
Task<AccountResponseDto> GetByAdminCodeAsync(string adminCode);
|
||||||
Task<AccountResponseDto> UpdateAsync(string adminCode, UpdateAccountRequestDto request);
|
Task<AccountResponseDto> UpdateAsync(string adminCode, UpdateAccountRequestDto request);
|
||||||
Task DeleteAsync(string adminCode);
|
Task DeleteAsync(string adminCode);
|
||||||
|
Task<OperatorCreateResponseDto> CreateOperatorAsync(OperatorCreateRequestDto request);
|
||||||
|
Task DeleteOperatorAsync(string adminCode, long requestingAdminId);
|
||||||
|
Task<OperatorListResponseDto> GetOperatorListAsync(OperatorListRequestDto request);
|
||||||
|
Task<OperatorCreateResponseDto> ResetOperatorPasswordAsync(string adminCode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ public interface IEmailService
|
||||||
{
|
{
|
||||||
Task SendVerificationCodeAsync(string email, string code);
|
Task SendVerificationCodeAsync(string email, string code);
|
||||||
Task SendPasswordResetTokenAsync(string email, string token);
|
Task SendPasswordResetTokenAsync(string email, string token);
|
||||||
|
Task SendPasswordSetupTokenAsync(string email, string token);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using SPMS.Application.DTOs.Account;
|
using SPMS.Application.DTOs.Account;
|
||||||
|
using SPMS.Application.DTOs.Notice;
|
||||||
using SPMS.Application.Interfaces;
|
using SPMS.Application.Interfaces;
|
||||||
using SPMS.Domain.Common;
|
using SPMS.Domain.Common;
|
||||||
using SPMS.Domain.Entities;
|
using SPMS.Domain.Entities;
|
||||||
|
|
@ -13,11 +14,19 @@ public class AccountService : IAccountService
|
||||||
{
|
{
|
||||||
private readonly IAdminRepository _adminRepository;
|
private readonly IAdminRepository _adminRepository;
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly IEmailService _emailService;
|
||||||
|
private readonly ITokenStore _tokenStore;
|
||||||
|
|
||||||
public AccountService(IAdminRepository adminRepository, IUnitOfWork unitOfWork)
|
public AccountService(
|
||||||
|
IAdminRepository adminRepository,
|
||||||
|
IUnitOfWork unitOfWork,
|
||||||
|
IEmailService emailService,
|
||||||
|
ITokenStore tokenStore)
|
||||||
{
|
{
|
||||||
_adminRepository = adminRepository;
|
_adminRepository = adminRepository;
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
|
_emailService = emailService;
|
||||||
|
_tokenStore = tokenStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AccountResponseDto> CreateAsync(CreateAccountRequestDto request)
|
public async Task<AccountResponseDto> CreateAsync(CreateAccountRequestDto request)
|
||||||
|
|
@ -178,6 +187,165 @@ public class AccountService : IAccountService
|
||||||
await _unitOfWork.SaveChangesAsync();
|
await _unitOfWork.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<OperatorCreateResponseDto> CreateOperatorAsync(OperatorCreateRequestDto request)
|
||||||
|
{
|
||||||
|
// 1. 이메일 중복 검사
|
||||||
|
if (await _adminRepository.EmailExistsAsync(request.Email))
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.Conflict,
|
||||||
|
"이미 사용 중인 이메일입니다.",
|
||||||
|
409);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. AdminCode 생성
|
||||||
|
var adminCode = Guid.NewGuid().ToString("N")[..12].ToUpper();
|
||||||
|
|
||||||
|
// 3. Admin 엔티티 생성 (비밀번호 없이)
|
||||||
|
var admin = new Admin
|
||||||
|
{
|
||||||
|
AdminCode = adminCode,
|
||||||
|
Email = request.Email,
|
||||||
|
Password = string.Empty,
|
||||||
|
Name = request.Name,
|
||||||
|
Phone = request.Phone ?? string.Empty,
|
||||||
|
Role = (AdminRole)request.Role,
|
||||||
|
EmailVerified = false,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
IsDeleted = false
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. 저장
|
||||||
|
await _adminRepository.AddAsync(admin);
|
||||||
|
await _unitOfWork.SaveChangesAsync();
|
||||||
|
|
||||||
|
// 5. 비밀번호 설정 토큰 생성 및 저장
|
||||||
|
var token = Guid.NewGuid().ToString("N");
|
||||||
|
await _tokenStore.StoreAsync($"password_setup:{request.Email}", token, TimeSpan.FromHours(24));
|
||||||
|
|
||||||
|
// 6. 이메일 발송
|
||||||
|
await _emailService.SendPasswordSetupTokenAsync(request.Email, token);
|
||||||
|
|
||||||
|
return new OperatorCreateResponseDto
|
||||||
|
{
|
||||||
|
AdminCode = adminCode,
|
||||||
|
Email = request.Email,
|
||||||
|
Role = request.Role
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteOperatorAsync(string adminCode, long requestingAdminId)
|
||||||
|
{
|
||||||
|
var admin = await _adminRepository.GetByAdminCodeAsync(adminCode);
|
||||||
|
if (admin is null)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.NotFound,
|
||||||
|
"운영자를 찾을 수 없습니다.",
|
||||||
|
404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Super Admin 삭제 불가
|
||||||
|
if (admin.Role == AdminRole.Super)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.Forbidden,
|
||||||
|
"Super Admin은 삭제할 수 없습니다.",
|
||||||
|
403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 자기 자신 삭제 불가
|
||||||
|
if (admin.Id == requestingAdminId)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.Forbidden,
|
||||||
|
"자기 자신은 삭제할 수 없습니다.",
|
||||||
|
403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soft Delete
|
||||||
|
admin.IsDeleted = true;
|
||||||
|
admin.DeletedAt = DateTime.UtcNow;
|
||||||
|
admin.RefreshToken = null;
|
||||||
|
admin.RefreshTokenExpiresAt = null;
|
||||||
|
|
||||||
|
_adminRepository.Update(admin);
|
||||||
|
await _unitOfWork.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OperatorListResponseDto> GetOperatorListAsync(OperatorListRequestDto request)
|
||||||
|
{
|
||||||
|
AdminRole? roleFilter = request.Role.HasValue ? (AdminRole)request.Role.Value : null;
|
||||||
|
|
||||||
|
var (items, totalCount) = await _adminRepository.GetOperatorPagedAsync(
|
||||||
|
request.Page, request.Size, roleFilter, request.IsActive);
|
||||||
|
|
||||||
|
var totalPages = (int)Math.Ceiling((double)totalCount / request.Size);
|
||||||
|
|
||||||
|
return new OperatorListResponseDto
|
||||||
|
{
|
||||||
|
Items = items.Select(a => new OperatorItemDto
|
||||||
|
{
|
||||||
|
AdminCode = a.AdminCode,
|
||||||
|
Email = a.Email,
|
||||||
|
Name = a.Name,
|
||||||
|
Role = (int)a.Role,
|
||||||
|
IsActive = !a.IsDeleted,
|
||||||
|
LastLoginAt = a.LastLoginAt,
|
||||||
|
CreatedAt = a.CreatedAt
|
||||||
|
}).ToList(),
|
||||||
|
Pagination = new PaginationDto
|
||||||
|
{
|
||||||
|
Page = request.Page,
|
||||||
|
Size = request.Size,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
TotalPages = totalPages
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OperatorCreateResponseDto> ResetOperatorPasswordAsync(string adminCode)
|
||||||
|
{
|
||||||
|
var admin = await _adminRepository.GetByAdminCodeAsync(adminCode);
|
||||||
|
if (admin is null)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.NotFound,
|
||||||
|
"운영자를 찾을 수 없습니다.",
|
||||||
|
404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Super Admin 비밀번호 초기화 불가
|
||||||
|
if (admin.Role == AdminRole.Super)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.Forbidden,
|
||||||
|
"Super Admin의 비밀번호는 초기화할 수 없습니다.",
|
||||||
|
403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 세션 무효화
|
||||||
|
admin.RefreshToken = null;
|
||||||
|
admin.RefreshTokenExpiresAt = null;
|
||||||
|
|
||||||
|
_adminRepository.Update(admin);
|
||||||
|
await _unitOfWork.SaveChangesAsync();
|
||||||
|
|
||||||
|
// 비밀번호 재설정 토큰 생성 및 저장
|
||||||
|
var token = Guid.NewGuid().ToString("N");
|
||||||
|
await _tokenStore.StoreAsync($"password_reset:{admin.Email}", token, TimeSpan.FromHours(1));
|
||||||
|
|
||||||
|
// 이메일 발송
|
||||||
|
await _emailService.SendPasswordResetTokenAsync(admin.Email, token);
|
||||||
|
|
||||||
|
return new OperatorCreateResponseDto
|
||||||
|
{
|
||||||
|
AdminCode = admin.AdminCode,
|
||||||
|
Email = admin.Email,
|
||||||
|
Role = (int)admin.Role
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static AccountResponseDto MapToDto(Admin admin)
|
private static AccountResponseDto MapToDto(Admin admin)
|
||||||
{
|
{
|
||||||
return new AccountResponseDto
|
return new AccountResponseDto
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using SPMS.Domain.Entities;
|
using SPMS.Domain.Entities;
|
||||||
|
using SPMS.Domain.Enums;
|
||||||
|
|
||||||
namespace SPMS.Domain.Interfaces;
|
namespace SPMS.Domain.Interfaces;
|
||||||
|
|
||||||
|
|
@ -8,4 +9,6 @@ public interface IAdminRepository : IRepository<Admin>
|
||||||
Task<Admin?> GetByAdminCodeAsync(string adminCode);
|
Task<Admin?> GetByAdminCodeAsync(string adminCode);
|
||||||
Task<Admin?> GetByRefreshTokenAsync(string refreshToken);
|
Task<Admin?> GetByRefreshTokenAsync(string refreshToken);
|
||||||
Task<bool> EmailExistsAsync(string email, long? excludeId = null);
|
Task<bool> EmailExistsAsync(string email, long? excludeId = null);
|
||||||
|
Task<(IReadOnlyList<Admin> Items, int TotalCount)> GetOperatorPagedAsync(
|
||||||
|
int page, int size, AdminRole? roleFilter, bool? isActive);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SPMS.Domain.Entities;
|
using SPMS.Domain.Entities;
|
||||||
|
using SPMS.Domain.Enums;
|
||||||
using SPMS.Domain.Interfaces;
|
using SPMS.Domain.Interfaces;
|
||||||
|
|
||||||
namespace SPMS.Infrastructure.Persistence.Repositories;
|
namespace SPMS.Infrastructure.Persistence.Repositories;
|
||||||
|
|
@ -37,4 +38,43 @@ public class AdminRepository : Repository<Admin>, IAdminRepository
|
||||||
|
|
||||||
return await query.AnyAsync();
|
return await query.AnyAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<Admin> Items, int TotalCount)> GetOperatorPagedAsync(
|
||||||
|
int page, int size, AdminRole? roleFilter, bool? isActive)
|
||||||
|
{
|
||||||
|
IQueryable<Admin> query;
|
||||||
|
|
||||||
|
if (isActive.HasValue)
|
||||||
|
{
|
||||||
|
// IgnoreQueryFilters로 삭제된 운영자도 조회 가능
|
||||||
|
query = _context.Set<Admin>().IgnoreQueryFilters();
|
||||||
|
|
||||||
|
if (isActive.Value)
|
||||||
|
query = query.Where(a => !a.IsDeleted);
|
||||||
|
else
|
||||||
|
query = query.Where(a => a.IsDeleted);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 기본: 글로벌 필터 적용 (활성 운영자만)
|
||||||
|
query = _dbSet.AsQueryable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Super Admin 제외
|
||||||
|
query = query.Where(a => a.Role != AdminRole.Super);
|
||||||
|
|
||||||
|
// Role 필터
|
||||||
|
if (roleFilter.HasValue)
|
||||||
|
query = query.Where(a => a.Role == roleFilter.Value);
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
|
||||||
|
var items = await query
|
||||||
|
.OrderByDescending(a => a.CreatedAt)
|
||||||
|
.Skip((page - 1) * size)
|
||||||
|
.Take(size)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return (items, totalCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,12 @@ public class ConsoleEmailService : IEmailService
|
||||||
email, token);
|
email, token);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendPasswordSetupTokenAsync(string email, string token)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"[EMAIL] 비밀번호 설정 토큰 발송 → To: {Email}, Token: {Token}",
|
||||||
|
email, token);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user