improvement: 수정/삭제/진단 계약 확장 (#218) #219
|
|
@ -72,7 +72,7 @@ public class ServiceController : ControllerBase
|
||||||
[HttpPost("update")]
|
[HttpPost("update")]
|
||||||
[SwaggerOperation(
|
[SwaggerOperation(
|
||||||
Summary = "서비스 수정",
|
Summary = "서비스 수정",
|
||||||
Description = "기존 서비스의 정보를 수정합니다. 서비스명, 설명, 웹훅 URL, 태그를 변경할 수 있습니다.")]
|
Description = "기존 서비스의 정보를 수정합니다. 서비스명, 설명, 웹훅 URL, 태그, 상태(Status)를 변경할 수 있습니다. Status 필드(0=Active, 1=Suspended)를 함께 전달하면 상태도 원자적으로 변경됩니다.")]
|
||||||
[SwaggerResponse(200, "수정 성공", typeof(ApiResponse<ServiceResponseDto>))]
|
[SwaggerResponse(200, "수정 성공", typeof(ApiResponse<ServiceResponseDto>))]
|
||||||
[SwaggerResponse(400, "변경된 내용 없음")]
|
[SwaggerResponse(400, "변경된 내용 없음")]
|
||||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
|
@ -192,10 +192,38 @@ public class ServiceController : ControllerBase
|
||||||
return Ok(ApiResponse.Success());
|
return Ok(ApiResponse.Success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("{serviceCode}/apns/delete")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "APNs 자격증명 삭제",
|
||||||
|
Description = "서비스에 등록된 APNs 자격증명(BundleId, KeyId, TeamId, PrivateKey, Certificate 등)을 모두 삭제합니다.")]
|
||||||
|
[SwaggerResponse(200, "삭제 성공", typeof(ApiResponse))]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
[SwaggerResponse(403, "권한 없음")]
|
||||||
|
[SwaggerResponse(404, "서비스 또는 자격증명을 찾을 수 없음")]
|
||||||
|
public async Task<IActionResult> DeleteApnsCredentialsAsync([FromRoute] string serviceCode)
|
||||||
|
{
|
||||||
|
await _serviceManagementService.DeleteApnsCredentialsAsync(serviceCode);
|
||||||
|
return Ok(ApiResponse.Success());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{serviceCode}/fcm/delete")]
|
||||||
|
[SwaggerOperation(
|
||||||
|
Summary = "FCM 자격증명 삭제",
|
||||||
|
Description = "서비스에 등록된 FCM Service Account JSON 자격증명을 삭제합니다.")]
|
||||||
|
[SwaggerResponse(200, "삭제 성공", typeof(ApiResponse))]
|
||||||
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
|
[SwaggerResponse(403, "권한 없음")]
|
||||||
|
[SwaggerResponse(404, "서비스 또는 자격증명을 찾을 수 없음")]
|
||||||
|
public async Task<IActionResult> DeleteFcmCredentialsAsync([FromRoute] string serviceCode)
|
||||||
|
{
|
||||||
|
await _serviceManagementService.DeleteFcmCredentialsAsync(serviceCode);
|
||||||
|
return Ok(ApiResponse.Success());
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("{serviceCode}/credentials")]
|
[HttpPost("{serviceCode}/credentials")]
|
||||||
[SwaggerOperation(
|
[SwaggerOperation(
|
||||||
Summary = "푸시 키 정보 조회",
|
Summary = "푸시 키 정보 조회",
|
||||||
Description = "서비스에 등록된 APNs/FCM 키의 메타 정보를 조회합니다. 민감 정보(Private Key)는 반환되지 않습니다.")]
|
Description = "서비스에 등록된 APNs/FCM 키의 메타 정보를 조회합니다. 민감 정보(Private Key)는 반환되지 않습니다. 각 플랫폼별 credentialStatus(ok/warn/error/none)와 statusReason 필드로 자격증명 상태 진단 결과를 제공합니다.")]
|
||||||
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<CredentialsResponseDto>))]
|
[SwaggerResponse(200, "조회 성공", typeof(ApiResponse<CredentialsResponseDto>))]
|
||||||
[SwaggerResponse(401, "인증되지 않은 요청")]
|
[SwaggerResponse(401, "인증되지 않은 요청")]
|
||||||
[SwaggerResponse(403, "권한 없음")]
|
[SwaggerResponse(403, "권한 없음")]
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,16 @@ public class ApnsCredentialsInfoDto
|
||||||
// p12 메타
|
// p12 메타
|
||||||
public bool HasCertificate { get; set; }
|
public bool HasCertificate { get; set; }
|
||||||
public DateTime? CertExpiresAt { get; set; }
|
public DateTime? CertExpiresAt { get; set; }
|
||||||
|
// 진단 상태
|
||||||
|
public string CredentialStatus { get; set; } = "none"; // ok|warn|error|none
|
||||||
|
public string? StatusReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FcmCredentialsInfoDto
|
public class FcmCredentialsInfoDto
|
||||||
{
|
{
|
||||||
public string? ProjectId { get; set; }
|
public string? ProjectId { get; set; }
|
||||||
public bool HasCredentials { get; set; }
|
public bool HasCredentials { get; set; }
|
||||||
|
// 진단 상태
|
||||||
|
public string CredentialStatus { get; set; } = "none"; // ok|none
|
||||||
|
public string? StatusReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,9 @@ public class UpdateServiceRequestDto
|
||||||
public string? WebhookUrl { get; set; }
|
public string? WebhookUrl { get; set; }
|
||||||
|
|
||||||
public string? Tags { get; set; }
|
public string? Tags { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 서비스 상태 (0: Active, 1: Suspended). 제공 시 상태도 함께 변경됩니다.
|
||||||
|
/// </summary>
|
||||||
|
public int? Status { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ public interface IServiceManagementService
|
||||||
Task RegisterApnsCredentialsAsync(string serviceCode, ApnsCredentialsRequestDto request);
|
Task RegisterApnsCredentialsAsync(string serviceCode, ApnsCredentialsRequestDto request);
|
||||||
Task RegisterFcmCredentialsAsync(string serviceCode, FcmCredentialsRequestDto request);
|
Task RegisterFcmCredentialsAsync(string serviceCode, FcmCredentialsRequestDto request);
|
||||||
Task<CredentialsResponseDto> GetCredentialsAsync(string serviceCode);
|
Task<CredentialsResponseDto> GetCredentialsAsync(string serviceCode);
|
||||||
|
Task DeleteApnsCredentialsAsync(string serviceCode);
|
||||||
|
Task DeleteFcmCredentialsAsync(string serviceCode);
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
Task<ServiceTagsResponseDto> GetTagsAsync(ServiceTagsRequestDto request);
|
Task<ServiceTagsResponseDto> GetTagsAsync(ServiceTagsRequestDto request);
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,17 @@ public class ServiceManagementService : IServiceManagementService
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status 변경
|
||||||
|
if (request.Status.HasValue)
|
||||||
|
{
|
||||||
|
var newStatus = (ServiceStatus)request.Status.Value;
|
||||||
|
if (service.Status != newStatus)
|
||||||
|
{
|
||||||
|
service.Status = newStatus;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasChanges)
|
if (!hasChanges)
|
||||||
{
|
{
|
||||||
throw new SpmsException(
|
throw new SpmsException(
|
||||||
|
|
@ -627,7 +638,7 @@ public class ServiceManagementService : IServiceManagementService
|
||||||
// APNs info (meta only, no private key)
|
// APNs info (meta only, no private key)
|
||||||
if (!string.IsNullOrEmpty(service.ApnsBundleId))
|
if (!string.IsNullOrEmpty(service.ApnsBundleId))
|
||||||
{
|
{
|
||||||
response.Apns = new ApnsCredentialsInfoDto
|
var apnsInfo = new ApnsCredentialsInfoDto
|
||||||
{
|
{
|
||||||
BundleId = service.ApnsBundleId,
|
BundleId = service.ApnsBundleId,
|
||||||
AuthType = service.ApnsAuthType,
|
AuthType = service.ApnsAuthType,
|
||||||
|
|
@ -637,6 +648,16 @@ public class ServiceManagementService : IServiceManagementService
|
||||||
HasCertificate = !string.IsNullOrEmpty(service.ApnsCertificate),
|
HasCertificate = !string.IsNullOrEmpty(service.ApnsCertificate),
|
||||||
CertExpiresAt = service.ApnsCertExpiresAt
|
CertExpiresAt = service.ApnsCertExpiresAt
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 상태 진단 — BuildIosSummary 로직 재활용
|
||||||
|
var iosSummary = BuildIosSummary(service);
|
||||||
|
if (iosSummary != null)
|
||||||
|
{
|
||||||
|
apnsInfo.CredentialStatus = iosSummary.CredentialStatus ?? "ok";
|
||||||
|
apnsInfo.StatusReason = iosSummary.StatusReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Apns = apnsInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FCM info (project_id only, no private key)
|
// FCM info (project_id only, no private key)
|
||||||
|
|
@ -661,13 +682,75 @@ public class ServiceManagementService : IServiceManagementService
|
||||||
response.Fcm = new FcmCredentialsInfoDto
|
response.Fcm = new FcmCredentialsInfoDto
|
||||||
{
|
{
|
||||||
ProjectId = projectId,
|
ProjectId = projectId,
|
||||||
HasCredentials = true
|
HasCredentials = true,
|
||||||
|
CredentialStatus = "ok"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteApnsCredentialsAsync(string serviceCode)
|
||||||
|
{
|
||||||
|
var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode);
|
||||||
|
if (service is null)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.NotFound,
|
||||||
|
"서비스를 찾을 수 없습니다.",
|
||||||
|
404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// APNs 자격증명이 없으면 에러
|
||||||
|
if (string.IsNullOrEmpty(service.ApnsBundleId))
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.NotFound,
|
||||||
|
"삭제할 APNs 자격증명이 없습니다.",
|
||||||
|
404);
|
||||||
|
}
|
||||||
|
|
||||||
|
service.ApnsBundleId = null;
|
||||||
|
service.ApnsKeyId = null;
|
||||||
|
service.ApnsTeamId = null;
|
||||||
|
service.ApnsPrivateKey = null;
|
||||||
|
service.ApnsAuthType = null;
|
||||||
|
service.ApnsCertificate = null;
|
||||||
|
service.ApnsCertPassword = null;
|
||||||
|
service.ApnsCertExpiresAt = null;
|
||||||
|
service.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
_serviceRepository.Update(service);
|
||||||
|
await _unitOfWork.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteFcmCredentialsAsync(string serviceCode)
|
||||||
|
{
|
||||||
|
var service = await _serviceRepository.GetByServiceCodeAsync(serviceCode);
|
||||||
|
if (service is null)
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.NotFound,
|
||||||
|
"서비스를 찾을 수 없습니다.",
|
||||||
|
404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FCM 자격증명이 없으면 에러
|
||||||
|
if (string.IsNullOrEmpty(service.FcmCredentials))
|
||||||
|
{
|
||||||
|
throw new SpmsException(
|
||||||
|
ErrorCodes.NotFound,
|
||||||
|
"삭제할 FCM 자격증명이 없습니다.",
|
||||||
|
404);
|
||||||
|
}
|
||||||
|
|
||||||
|
service.FcmCredentials = null;
|
||||||
|
service.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
_serviceRepository.Update(service);
|
||||||
|
await _unitOfWork.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ServiceTagsResponseDto> GetTagsAsync(ServiceTagsRequestDto request)
|
public async Task<ServiceTagsResponseDto> GetTagsAsync(ServiceTagsRequestDto request)
|
||||||
{
|
{
|
||||||
var service = await _serviceRepository.GetByServiceCodeAsync(request.ServiceCode);
|
var service = await _serviceRepository.GetByServiceCodeAsync(request.ServiceCode);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user