using System.Linq.Expressions; using SPMS.Application.DTOs.Account; using SPMS.Application.DTOs.Notice; using SPMS.Application.Interfaces; using SPMS.Domain.Common; using SPMS.Domain.Entities; using SPMS.Domain.Enums; using SPMS.Domain.Exceptions; using SPMS.Domain.Interfaces; namespace SPMS.Application.Services; public class AccountService : IAccountService { private readonly IAdminRepository _adminRepository; private readonly IUnitOfWork _unitOfWork; private readonly IEmailService _emailService; private readonly ITokenStore _tokenStore; public AccountService( IAdminRepository adminRepository, IUnitOfWork unitOfWork, IEmailService emailService, ITokenStore tokenStore) { _adminRepository = adminRepository; _unitOfWork = unitOfWork; _emailService = emailService; _tokenStore = tokenStore; } public async Task CreateAsync(CreateAccountRequestDto request) { // 1. 이메일 중복 검사 if (await _adminRepository.EmailExistsAsync(request.Email)) { throw new SpmsException( ErrorCodes.Conflict, "이미 사용 중인 이메일입니다.", 409); } // 2. AdminCode 생성 (UUID) var adminCode = Guid.NewGuid().ToString("N")[..12].ToUpper(); // 3. Admin 엔티티 생성 var admin = new Admin { AdminCode = adminCode, Email = request.Email, Password = BCrypt.Net.BCrypt.HashPassword(request.Password), 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. 응답 반환 return MapToDto(admin); } public async Task GetListAsync(AccountListRequestDto request) { // 필터 조건 생성 (Super Admin 제외) Expression> predicate = a => a.Role != AdminRole.Super; // 검색어 필터 if (!string.IsNullOrWhiteSpace(request.SearchKeyword)) { var keyword = request.SearchKeyword; predicate = a => a.Role != AdminRole.Super && (a.Name.Contains(keyword) || a.Email.Contains(keyword)); } // Role 필터 if (request.Role.HasValue) { var role = (AdminRole)request.Role.Value; var basePredicate = predicate; predicate = a => basePredicate.Compile()(a) && a.Role == role; } // 페이징 조회 var (items, totalCount) = await _adminRepository.GetPagedAsync( request.Page, request.PageSize, predicate, a => a.CreatedAt, descending: true); return new AccountListResponseDto { Items = items.Select(MapToDto).ToList(), TotalCount = totalCount, Page = request.Page, PageSize = request.PageSize }; } public async Task GetByAdminCodeAsync(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.NotFound, "운영자를 찾을 수 없습니다.", 404); } return MapToDto(admin); } public async Task UpdateAsync(string adminCode, UpdateAccountRequestDto request) { 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.Name = request.Name; admin.Phone = request.Phone ?? string.Empty; admin.Role = (AdminRole)request.Role; _adminRepository.Update(admin); await _unitOfWork.SaveChangesAsync(); return MapToDto(admin); } public async Task DeleteAsync(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); } // Soft Delete admin.IsDeleted = true; admin.DeletedAt = DateTime.UtcNow; admin.RefreshToken = null; admin.RefreshTokenExpiresAt = null; _adminRepository.Update(admin); await _unitOfWork.SaveChangesAsync(); } public async Task 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 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 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) { return new AccountResponseDto { AdminCode = admin.AdminCode, Email = admin.Email, Name = admin.Name, Phone = string.IsNullOrEmpty(admin.Phone) ? null : admin.Phone, Role = admin.Role.ToString(), EmailVerified = admin.EmailVerified, CreatedAt = admin.CreatedAt, LastLoginAt = admin.LastLoginAt }; } }