feat: 서비스 관리 API 구현 (#44)
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/45
This commit is contained in:
commit
3d793c652b
66
SPMS.API/Controllers/ServiceController.cs
Normal file
66
SPMS.API/Controllers/ServiceController.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using SPMS.Application.DTOs.Service;
|
||||
using SPMS.Application.Interfaces;
|
||||
using SPMS.Domain.Common;
|
||||
|
||||
namespace SPMS.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/in/service")]
|
||||
[ApiExplorerSettings(GroupName = "service")]
|
||||
[Authorize(Roles = "Super")]
|
||||
public class ServiceController : ControllerBase
|
||||
{
|
||||
private readonly IServiceManagementService _serviceManagementService;
|
||||
|
||||
public ServiceController(IServiceManagementService serviceManagementService)
|
||||
{
|
||||
_serviceManagementService = serviceManagementService;
|
||||
}
|
||||
|
||||
[HttpPost("list")]
|
||||
[SwaggerOperation(
|
||||
Summary = "서비스 목록 조회",
|
||||
Description = "등록된 서비스 목록을 조회합니다. 페이징, 검색, 상태 필터를 지원합니다.")]
|
||||
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<ServiceListResponseDto>))]
|
||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||
[SwaggerResponse(403, "권한 없음")]
|
||||
public async Task<IActionResult> GetListAsync([FromBody] ServiceListRequestDto request)
|
||||
{
|
||||
var result = await _serviceManagementService.GetListAsync(request);
|
||||
return Ok(ApiResponse<ServiceListResponseDto>.Success(result));
|
||||
}
|
||||
|
||||
[HttpPost("{serviceCode}")]
|
||||
[SwaggerOperation(
|
||||
Summary = "서비스 상세 조회",
|
||||
Description = "특정 서비스의 상세 정보를 조회합니다.")]
|
||||
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<ServiceResponseDto>))]
|
||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||
[SwaggerResponse(403, "권한 없음")]
|
||||
[SwaggerResponse(404, "서비스를 찾을 수 없음")]
|
||||
public async Task<IActionResult> GetByServiceCodeAsync([FromRoute] string serviceCode)
|
||||
{
|
||||
var result = await _serviceManagementService.GetByServiceCodeAsync(serviceCode);
|
||||
return Ok(ApiResponse<ServiceResponseDto>.Success(result));
|
||||
}
|
||||
|
||||
[HttpPost("{serviceCode}/status")]
|
||||
[SwaggerOperation(
|
||||
Summary = "서비스 상태 변경",
|
||||
Description = "서비스의 상태를 변경합니다. (Active: 0, Suspended: 1)")]
|
||||
[SwaggerResponse(200, "상태 변경 성공", typeof(ApiResponse<ServiceResponseDto>))]
|
||||
[SwaggerResponse(400, "잘못된 요청 또는 이미 해당 상태")]
|
||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||
[SwaggerResponse(403, "권한 없음")]
|
||||
[SwaggerResponse(404, "서비스를 찾을 수 없음")]
|
||||
public async Task<IActionResult> ChangeStatusAsync(
|
||||
[FromRoute] string serviceCode,
|
||||
[FromBody] ChangeServiceStatusRequestDto request)
|
||||
{
|
||||
var result = await _serviceManagementService.ChangeStatusAsync(serviceCode, request);
|
||||
return Ok(ApiResponse<ServiceResponseDto>.Success(result));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class ChangeServiceStatusRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "상태를 선택해주세요.")]
|
||||
[Range(0, 1, ErrorMessage = "상태는 Active(0) 또는 Suspended(1)만 가능합니다.")]
|
||||
public int Status { get; set; }
|
||||
}
|
||||
15
SPMS.Application/DTOs/Service/ServiceListRequestDto.cs
Normal file
15
SPMS.Application/DTOs/Service/ServiceListRequestDto.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class ServiceListRequestDto
|
||||
{
|
||||
[Range(1, int.MaxValue, ErrorMessage = "페이지 번호는 1 이상이어야 합니다.")]
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
[Range(1, 100, ErrorMessage = "페이지 크기는 1~100 사이여야 합니다.")]
|
||||
public int PageSize { get; set; } = 20;
|
||||
|
||||
public string? SearchKeyword { get; set; }
|
||||
public int? Status { get; set; }
|
||||
}
|
||||
21
SPMS.Application/DTOs/Service/ServiceListResponseDto.cs
Normal file
21
SPMS.Application/DTOs/Service/ServiceListResponseDto.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class ServiceListResponseDto
|
||||
{
|
||||
public List<ServiceSummaryDto> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
|
||||
}
|
||||
|
||||
public class ServiceSummaryDto
|
||||
{
|
||||
public string ServiceCode { get; set; } = string.Empty;
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string SubTier { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public int DeviceCount { get; set; }
|
||||
}
|
||||
25
SPMS.Application/DTOs/Service/ServiceResponseDto.cs
Normal file
25
SPMS.Application/DTOs/Service/ServiceResponseDto.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class ServiceResponseDto
|
||||
{
|
||||
public string ServiceCode { get; set; } = string.Empty;
|
||||
public string ServiceName { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public DateTime ApiKeyCreatedAt { get; set; }
|
||||
public string? ApnsBundleId { get; set; }
|
||||
public string? ApnsKeyId { get; set; }
|
||||
public string? ApnsTeamId { get; set; }
|
||||
public bool HasApnsKey { get; set; }
|
||||
public bool HasFcmCredentials { get; set; }
|
||||
public string? WebhookUrl { get; set; }
|
||||
public string? Tags { get; set; }
|
||||
public string SubTier { get; set; } = string.Empty;
|
||||
public DateTime? SubStartedAt { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public string CreatedByName { get; set; } = string.Empty;
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public int DeviceCount { get; set; }
|
||||
public List<string> AllowedIps { get; set; } = new();
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ public static class DependencyInjection
|
|||
// Application Services
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddScoped<IAccountService, AccountService>();
|
||||
services.AddScoped<IServiceManagementService, ServiceManagementService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
|
|||
10
SPMS.Application/Interfaces/IServiceManagementService.cs
Normal file
10
SPMS.Application/Interfaces/IServiceManagementService.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using SPMS.Application.DTOs.Service;
|
||||
|
||||
namespace SPMS.Application.Interfaces;
|
||||
|
||||
public interface IServiceManagementService
|
||||
{
|
||||
Task<ServiceListResponseDto> GetListAsync(ServiceListRequestDto request);
|
||||
Task<ServiceResponseDto> GetByServiceCodeAsync(string serviceCode);
|
||||
Task<ServiceResponseDto> ChangeStatusAsync(string serviceCode, ChangeServiceStatusRequestDto request);
|
||||
}
|
||||
153
SPMS.Application/Services/ServiceManagementService.cs
Normal file
153
SPMS.Application/Services/ServiceManagementService.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
using System.Linq.Expressions;
|
||||
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<ServiceListResponseDto> GetListAsync(ServiceListRequestDto request)
|
||||
{
|
||||
Expression<Func<Service, bool>>? 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<ServiceResponseDto> 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<ServiceResponseDto> 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);
|
||||
}
|
||||
|
||||
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<string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user