SPMS_API/SPMS.Application/Services/AccountService.cs
seonkyu.kim b6939c0fa9 feat: 운영자 계정 CRUD API 구현 (#42)
- AccountController: 운영자 CRUD 엔드포인트 (create, list, detail, update, delete)
- AccountService: 비즈니스 로직 구현
- Account DTOs: 요청/응답 DTO 5종
- ErrorCodes: Forbidden 코드 추가
- DI 등록

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 23:51:04 +09:00

196 lines
5.8 KiB
C#

using System.Linq.Expressions;
using SPMS.Application.DTOs.Account;
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;
public AccountService(IAdminRepository adminRepository, IUnitOfWork unitOfWork)
{
_adminRepository = adminRepository;
_unitOfWork = unitOfWork;
}
public async Task<AccountResponseDto> 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<AccountListResponseDto> GetListAsync(AccountListRequestDto request)
{
// 필터 조건 생성 (Super Admin 제외)
Expression<Func<Admin, bool>> 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<AccountResponseDto> 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<AccountResponseDto> 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();
}
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
};
}
}