using System.Linq.Expressions; using System.Security.Cryptography; using SPMS.Application.DTOs.Service; 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 ServiceManagementService : IServiceManagementService { private readonly IServiceRepository _serviceRepository; private readonly IUnitOfWork _unitOfWork; public ServiceManagementService(IServiceRepository serviceRepository, IUnitOfWork unitOfWork) { _serviceRepository = serviceRepository; _unitOfWork = unitOfWork; } public async Task GetListAsync(ServiceListRequestDto request) { Expression>? predicate = null; // 검색어 필터 if (!string.IsNullOrWhiteSpace(request.SearchKeyword)) { var keyword = request.SearchKeyword; predicate = s => s.ServiceName.Contains(keyword) || s.ServiceCode.Contains(keyword); } // Status 필터 if (request.Status.HasValue) { var status = (ServiceStatus)request.Status.Value; if (predicate != null) { var basePredicate = predicate; predicate = s => basePredicate.Compile()(s) && s.Status == status; } else { predicate = s => s.Status == status; } } var (items, totalCount) = await _serviceRepository.GetPagedAsync( request.Page, request.PageSize, predicate, s => s.CreatedAt, descending: true); return new ServiceListResponseDto { Items = items.Select(MapToSummaryDto).ToList(), TotalCount = totalCount, Page = request.Page, PageSize = request.PageSize }; } public async Task GetByServiceCodeAsync(string serviceCode) { var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode); if (service is null) { throw new SpmsException( ErrorCodes.NotFound, "서비스를 찾을 수 없습니다.", 404); } // IP 목록 포함해서 다시 조회 var serviceWithIps = await _serviceRepository.GetByIdWithIpsAsync(service.Id); return MapToDto(serviceWithIps ?? service); } public async Task ChangeStatusAsync(string serviceCode, ChangeServiceStatusRequestDto request) { var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode); if (service is null) { throw new SpmsException( ErrorCodes.NotFound, "서비스를 찾을 수 없습니다.", 404); } var newStatus = (ServiceStatus)request.Status; // 이미 같은 상태면 변경 없음 if (service.Status == newStatus) { throw new SpmsException( ErrorCodes.NoChange, "이미 해당 상태입니다.", 400); } service.Status = newStatus; service.UpdatedAt = DateTime.UtcNow; _serviceRepository.Update(service); await _unitOfWork.SaveChangesAsync(); return MapToDto(service); } public async Task RefreshApiKeyAsync(string serviceCode) { var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode); if (service is null) { throw new SpmsException( ErrorCodes.NotFound, "서비스를 찾을 수 없습니다.", 404); } // 32~64자 랜덤 API Key 생성 (48자 = 36바이트 Base64) var randomBytes = new byte[36]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(randomBytes); } var newApiKey = Convert.ToBase64String(randomBytes); service.ApiKey = newApiKey; service.ApiKeyCreatedAt = DateTime.UtcNow; service.UpdatedAt = DateTime.UtcNow; _serviceRepository.Update(service); await _unitOfWork.SaveChangesAsync(); return new ApiKeyRefreshResponseDto { ServiceCode = service.ServiceCode, ApiKey = newApiKey, ApiKeyCreatedAt = service.ApiKeyCreatedAt }; } private static ServiceSummaryDto MapToSummaryDto(Service service) { return new ServiceSummaryDto { ServiceCode = service.ServiceCode, ServiceName = service.ServiceName, Description = service.Description, SubTier = service.SubTier.ToString(), Status = service.Status.ToString(), CreatedAt = service.CreatedAt, DeviceCount = service.Devices?.Count ?? 0 }; } private static ServiceResponseDto MapToDto(Service service) { return new ServiceResponseDto { ServiceCode = service.ServiceCode, ServiceName = service.ServiceName, Description = service.Description, ApiKey = service.ApiKey, ApiKeyCreatedAt = service.ApiKeyCreatedAt, ApnsBundleId = service.ApnsBundleId, ApnsKeyId = service.ApnsKeyId, ApnsTeamId = service.ApnsTeamId, HasApnsKey = !string.IsNullOrEmpty(service.ApnsPrivateKey), HasFcmCredentials = !string.IsNullOrEmpty(service.FcmCredentials), WebhookUrl = service.WebhookUrl, Tags = service.Tags, SubTier = service.SubTier.ToString(), SubStartedAt = service.SubStartedAt, Status = service.Status.ToString(), CreatedAt = service.CreatedAt, CreatedByName = service.CreatedByAdmin?.Name ?? string.Empty, UpdatedAt = service.UpdatedAt, DeviceCount = service.Devices?.Count ?? 0, AllowedIps = service.ServiceIps?.Select(ip => ip.IpAddress).ToList() ?? new List() }; } }