feat: IP 화이트리스트 관리 API 구현 (#50)
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/51
This commit is contained in:
commit
9762052dd6
|
|
@ -125,4 +125,52 @@ public class ServiceController : ControllerBase
|
|||
var result = await _serviceManagementService.GetCredentialsAsync(serviceCode);
|
||||
return Ok(ApiResponse<CredentialsResponseDto>.Success(result));
|
||||
}
|
||||
|
||||
[HttpPost("{serviceCode}/ip/list")]
|
||||
[SwaggerOperation(
|
||||
Summary = "IP 화이트리스트 조회",
|
||||
Description = "서비스에 등록된 IP 화이트리스트 목록을 조회합니다.")]
|
||||
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<IpListResponseDto>))]
|
||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||
[SwaggerResponse(403, "권한 없음")]
|
||||
[SwaggerResponse(404, "서비스를 찾을 수 없음")]
|
||||
public async Task<IActionResult> GetIpListAsync([FromRoute] string serviceCode)
|
||||
{
|
||||
var result = await _serviceManagementService.GetIpListAsync(serviceCode);
|
||||
return Ok(ApiResponse<IpListResponseDto>.Success(result));
|
||||
}
|
||||
|
||||
[HttpPost("{serviceCode}/ip/add")]
|
||||
[SwaggerOperation(
|
||||
Summary = "IP 추가",
|
||||
Description = "서비스의 IP 화이트리스트에 새 IP를 추가합니다. IPv4 형식만 지원합니다.")]
|
||||
[SwaggerResponse(200, "추가 성공", typeof(ApiResponse<ServiceIpDto>))]
|
||||
[SwaggerResponse(400, "잘못된 요청 (유효하지 않은 IP 형식)")]
|
||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||
[SwaggerResponse(403, "권한 없음")]
|
||||
[SwaggerResponse(404, "서비스를 찾을 수 없음")]
|
||||
[SwaggerResponse(409, "이미 등록된 IP")]
|
||||
public async Task<IActionResult> AddIpAsync(
|
||||
[FromRoute] string serviceCode,
|
||||
[FromBody] AddIpRequestDto request)
|
||||
{
|
||||
var result = await _serviceManagementService.AddIpAsync(serviceCode, request);
|
||||
return Ok(ApiResponse<ServiceIpDto>.Success(result));
|
||||
}
|
||||
|
||||
[HttpPost("{serviceCode}/ip/delete")]
|
||||
[SwaggerOperation(
|
||||
Summary = "IP 삭제",
|
||||
Description = "서비스의 IP 화이트리스트에서 IP를 삭제합니다.")]
|
||||
[SwaggerResponse(200, "삭제 성공", typeof(ApiResponse))]
|
||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||
[SwaggerResponse(403, "권한 없음")]
|
||||
[SwaggerResponse(404, "서비스 또는 IP를 찾을 수 없음")]
|
||||
public async Task<IActionResult> DeleteIpAsync(
|
||||
[FromRoute] string serviceCode,
|
||||
[FromBody] DeleteIpRequestDto request)
|
||||
{
|
||||
await _serviceManagementService.DeleteIpAsync(serviceCode, request);
|
||||
return Ok(ApiResponse.Success());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
SPMS.Application/DTOs/Service/AddIpRequestDto.cs
Normal file
12
SPMS.Application/DTOs/Service/AddIpRequestDto.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class AddIpRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "IP 주소는 필수입니다.")]
|
||||
[RegularExpression(
|
||||
@"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
|
||||
ErrorMessage = "유효한 IPv4 주소 형식이 아닙니다.")]
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
}
|
||||
9
SPMS.Application/DTOs/Service/DeleteIpRequestDto.cs
Normal file
9
SPMS.Application/DTOs/Service/DeleteIpRequestDto.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class DeleteIpRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "IP ID는 필수입니다.")]
|
||||
public long IpId { get; set; }
|
||||
}
|
||||
14
SPMS.Application/DTOs/Service/IpListResponseDto.cs
Normal file
14
SPMS.Application/DTOs/Service/IpListResponseDto.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace SPMS.Application.DTOs.Service;
|
||||
|
||||
public class IpListResponseDto
|
||||
{
|
||||
public string ServiceCode { get; set; } = string.Empty;
|
||||
public List<ServiceIpDto> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceIpDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -11,4 +11,9 @@ public interface IServiceManagementService
|
|||
Task RegisterApnsCredentialsAsync(string serviceCode, ApnsCredentialsRequestDto request);
|
||||
Task RegisterFcmCredentialsAsync(string serviceCode, FcmCredentialsRequestDto request);
|
||||
Task<CredentialsResponseDto> GetCredentialsAsync(string serviceCode);
|
||||
|
||||
// IP Whitelist
|
||||
Task<IpListResponseDto> GetIpListAsync(string serviceCode);
|
||||
Task<ServiceIpDto> AddIpAsync(string serviceCode, AddIpRequestDto request);
|
||||
Task DeleteIpAsync(string serviceCode, DeleteIpRequestDto request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,6 +279,90 @@ public class ServiceManagementService : IServiceManagementService
|
|||
return response;
|
||||
}
|
||||
|
||||
public async Task<IpListResponseDto> GetIpListAsync(string serviceCode)
|
||||
{
|
||||
var service = await _serviceRepository.GetByServiceCodeWithIpsAsync(serviceCode);
|
||||
if (service is null)
|
||||
{
|
||||
throw new SpmsException(
|
||||
ErrorCodes.NotFound,
|
||||
"서비스를 찾을 수 없습니다.",
|
||||
404);
|
||||
}
|
||||
|
||||
return new IpListResponseDto
|
||||
{
|
||||
ServiceCode = service.ServiceCode,
|
||||
Items = service.ServiceIps.Select(ip => new ServiceIpDto
|
||||
{
|
||||
Id = ip.Id,
|
||||
IpAddress = ip.IpAddress
|
||||
}).ToList(),
|
||||
TotalCount = service.ServiceIps.Count
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<ServiceIpDto> AddIpAsync(string serviceCode, AddIpRequestDto request)
|
||||
{
|
||||
var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode);
|
||||
if (service is null)
|
||||
{
|
||||
throw new SpmsException(
|
||||
ErrorCodes.NotFound,
|
||||
"서비스를 찾을 수 없습니다.",
|
||||
404);
|
||||
}
|
||||
|
||||
// Check for duplicate IP
|
||||
var exists = await _serviceRepository.ServiceIpExistsAsync(service.Id, request.IpAddress);
|
||||
if (exists)
|
||||
{
|
||||
throw new SpmsException(
|
||||
ErrorCodes.Conflict,
|
||||
"이미 등록된 IP 주소입니다.",
|
||||
409);
|
||||
}
|
||||
|
||||
var serviceIp = new ServiceIp
|
||||
{
|
||||
ServiceId = service.Id,
|
||||
IpAddress = request.IpAddress
|
||||
};
|
||||
|
||||
await _serviceRepository.AddServiceIpAsync(serviceIp);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
return new ServiceIpDto
|
||||
{
|
||||
Id = serviceIp.Id,
|
||||
IpAddress = serviceIp.IpAddress
|
||||
};
|
||||
}
|
||||
|
||||
public async Task DeleteIpAsync(string serviceCode, DeleteIpRequestDto request)
|
||||
{
|
||||
var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode);
|
||||
if (service is null)
|
||||
{
|
||||
throw new SpmsException(
|
||||
ErrorCodes.NotFound,
|
||||
"서비스를 찾을 수 없습니다.",
|
||||
404);
|
||||
}
|
||||
|
||||
var serviceIp = await _serviceRepository.GetServiceIpByIdAsync(request.IpId);
|
||||
if (serviceIp is null || serviceIp.ServiceId != service.Id)
|
||||
{
|
||||
throw new SpmsException(
|
||||
ErrorCodes.NotFound,
|
||||
"IP 주소를 찾을 수 없습니다.",
|
||||
404);
|
||||
}
|
||||
|
||||
_serviceRepository.DeleteServiceIp(serviceIp);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static ServiceSummaryDto MapToSummaryDto(Service service)
|
||||
{
|
||||
return new ServiceSummaryDto
|
||||
|
|
|
|||
|
|
@ -8,5 +8,12 @@ public interface IServiceRepository : IRepository<Service>
|
|||
Task<Service?> GetByServiceCodeAsync(string serviceCode);
|
||||
Task<Service?> GetByApiKeyAsync(string apiKey);
|
||||
Task<Service?> GetByIdWithIpsAsync(long id);
|
||||
Task<Service?> GetByServiceCodeWithIpsAsync(string serviceCode);
|
||||
Task<IReadOnlyList<Service>> GetByStatusAsync(ServiceStatus status);
|
||||
|
||||
// ServiceIp methods
|
||||
Task<ServiceIp?> GetServiceIpByIdAsync(long ipId);
|
||||
Task<bool> ServiceIpExistsAsync(long serviceId, string ipAddress);
|
||||
Task AddServiceIpAsync(ServiceIp serviceIp);
|
||||
void DeleteServiceIp(ServiceIp serviceIp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,25 @@ public class ServiceRepository : Repository<Service>, IServiceRepository
|
|||
.Include(s => s.ServiceIps)
|
||||
.FirstOrDefaultAsync(s => s.Id == id);
|
||||
|
||||
public async Task<Service?> GetByServiceCodeWithIpsAsync(string serviceCode)
|
||||
=> await _dbSet
|
||||
.Include(s => s.ServiceIps)
|
||||
.FirstOrDefaultAsync(s => s.ServiceCode == serviceCode);
|
||||
|
||||
public async Task<IReadOnlyList<Service>> GetByStatusAsync(ServiceStatus status)
|
||||
=> await _dbSet.Where(s => s.Status == status).ToListAsync();
|
||||
|
||||
// ServiceIp methods
|
||||
public async Task<ServiceIp?> GetServiceIpByIdAsync(long ipId)
|
||||
=> await _context.Set<ServiceIp>().FirstOrDefaultAsync(ip => ip.Id == ipId);
|
||||
|
||||
public async Task<bool> ServiceIpExistsAsync(long serviceId, string ipAddress)
|
||||
=> await _context.Set<ServiceIp>()
|
||||
.AnyAsync(ip => ip.ServiceId == serviceId && ip.IpAddress == ipAddress);
|
||||
|
||||
public async Task AddServiceIpAsync(ServiceIp serviceIp)
|
||||
=> await _context.Set<ServiceIp>().AddAsync(serviceIp);
|
||||
|
||||
public void DeleteServiceIp(ServiceIp serviceIp)
|
||||
=> _context.Set<ServiceIp>().Remove(serviceIp);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user