From 6879d5e1fd097f64285142ffc5b5dc8a7ce929b1 Mon Sep 17 00:00:00 2001 From: SEAN Date: Tue, 10 Feb 2026 11:27:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EB=AA=A9=EB=A1=9D/=EC=88=98=EC=A0=95=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#70)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ServiceTagsRequestDto, UpdateServiceTagsRequestDto, ServiceTagsResponseDto 생성 - IServiceManagementService에 GetTagsAsync, UpdateTagsAsync 추가 - ServiceManagementService에 태그 JSON 파싱/직렬화 로직 구현 - ServiceController에 POST tags/list, tags/update 엔드포인트 추가 - 태그 최대 10개 제한, 변경 없음 감지 Closes #70 --- SPMS.API/Controllers/ServiceController.cs | 29 ++++++++ .../DTOs/Service/ServiceTagsRequestDto.cs | 11 +++ .../DTOs/Service/ServiceTagsResponseDto.cs | 23 ++++++ .../Service/UpdateServiceTagsRequestDto.cs | 15 ++++ .../Interfaces/IServiceManagementService.cs | 4 ++ .../Services/ServiceManagementService.cs | 70 +++++++++++++++++++ 6 files changed, 152 insertions(+) create mode 100644 SPMS.Application/DTOs/Service/ServiceTagsRequestDto.cs create mode 100644 SPMS.Application/DTOs/Service/ServiceTagsResponseDto.cs create mode 100644 SPMS.Application/DTOs/Service/UpdateServiceTagsRequestDto.cs diff --git a/SPMS.API/Controllers/ServiceController.cs b/SPMS.API/Controllers/ServiceController.cs index 8af1835..6be0163 100644 --- a/SPMS.API/Controllers/ServiceController.cs +++ b/SPMS.API/Controllers/ServiceController.cs @@ -178,6 +178,35 @@ public class ServiceController : ControllerBase return Ok(ApiResponse.Success(result)); } + [HttpPost("tags/list")] + [SwaggerOperation( + Summary = "태그 목록 조회", + Description = "서비스에 등록된 태그 목록을 조회합니다.")] + [SwaggerResponse(200, "조회 성공", typeof(ApiResponse))] + [SwaggerResponse(401, "인증되지 않은 요청")] + [SwaggerResponse(403, "권한 없음")] + [SwaggerResponse(404, "서비스를 찾을 수 없음")] + public async Task GetTagsAsync([FromBody] ServiceTagsRequestDto request) + { + var result = await _serviceManagementService.GetTagsAsync(request); + return Ok(ApiResponse.Success(result)); + } + + [HttpPost("tags/update")] + [SwaggerOperation( + Summary = "태그 수정", + Description = "서비스의 태그 목록을 수정합니다. 최대 10개까지 등록 가능합니다.")] + [SwaggerResponse(200, "수정 성공", typeof(ApiResponse))] + [SwaggerResponse(400, "잘못된 요청 또는 변경 없음")] + [SwaggerResponse(401, "인증되지 않은 요청")] + [SwaggerResponse(403, "권한 없음")] + [SwaggerResponse(404, "서비스를 찾을 수 없음")] + public async Task UpdateTagsAsync([FromBody] UpdateServiceTagsRequestDto request) + { + var result = await _serviceManagementService.UpdateTagsAsync(request); + return Ok(ApiResponse.Success(result)); + } + [HttpPost("{serviceCode}/ip/list")] [SwaggerOperation( Summary = "IP 화이트리스트 조회", diff --git a/SPMS.Application/DTOs/Service/ServiceTagsRequestDto.cs b/SPMS.Application/DTOs/Service/ServiceTagsRequestDto.cs new file mode 100644 index 0000000..04ecdbd --- /dev/null +++ b/SPMS.Application/DTOs/Service/ServiceTagsRequestDto.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace SPMS.Application.DTOs.Service; + +public class ServiceTagsRequestDto +{ + [Required(ErrorMessage = "서비스 코드는 필수입니다.")] + [JsonPropertyName("service_code")] + public string ServiceCode { get; set; } = string.Empty; +} diff --git a/SPMS.Application/DTOs/Service/ServiceTagsResponseDto.cs b/SPMS.Application/DTOs/Service/ServiceTagsResponseDto.cs new file mode 100644 index 0000000..8346db5 --- /dev/null +++ b/SPMS.Application/DTOs/Service/ServiceTagsResponseDto.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace SPMS.Application.DTOs.Service; + +public class ServiceTagsResponseDto +{ + [JsonPropertyName("service_code")] + public string ServiceCode { get; set; } = string.Empty; + + public List Tags { get; set; } = new(); + + [JsonPropertyName("total_count")] + public int TotalCount { get; set; } +} + +public class TagItemDto +{ + [JsonPropertyName("tag_index")] + public int TagIndex { get; set; } + + [JsonPropertyName("tag_name")] + public string TagName { get; set; } = string.Empty; +} diff --git a/SPMS.Application/DTOs/Service/UpdateServiceTagsRequestDto.cs b/SPMS.Application/DTOs/Service/UpdateServiceTagsRequestDto.cs new file mode 100644 index 0000000..5743503 --- /dev/null +++ b/SPMS.Application/DTOs/Service/UpdateServiceTagsRequestDto.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace SPMS.Application.DTOs.Service; + +public class UpdateServiceTagsRequestDto +{ + [Required(ErrorMessage = "서비스 코드는 필수입니다.")] + [JsonPropertyName("service_code")] + public string ServiceCode { get; set; } = string.Empty; + + [Required(ErrorMessage = "태그 목록은 필수입니다.")] + [MaxLength(10, ErrorMessage = "태그는 최대 10개까지 등록할 수 있습니다.")] + public List Tags { get; set; } = new(); +} diff --git a/SPMS.Application/Interfaces/IServiceManagementService.cs b/SPMS.Application/Interfaces/IServiceManagementService.cs index 19afd94..007a9e4 100644 --- a/SPMS.Application/Interfaces/IServiceManagementService.cs +++ b/SPMS.Application/Interfaces/IServiceManagementService.cs @@ -15,6 +15,10 @@ public interface IServiceManagementService Task RegisterFcmCredentialsAsync(string serviceCode, FcmCredentialsRequestDto request); Task GetCredentialsAsync(string serviceCode); + // Tags + Task GetTagsAsync(ServiceTagsRequestDto request); + Task UpdateTagsAsync(UpdateServiceTagsRequestDto request); + // IP Whitelist Task GetIpListAsync(string serviceCode); Task AddIpAsync(string serviceCode, AddIpRequestDto request); diff --git a/SPMS.Application/Services/ServiceManagementService.cs b/SPMS.Application/Services/ServiceManagementService.cs index b70989b..1d8858a 100644 --- a/SPMS.Application/Services/ServiceManagementService.cs +++ b/SPMS.Application/Services/ServiceManagementService.cs @@ -454,6 +454,76 @@ public class ServiceManagementService : IServiceManagementService return response; } + public async Task GetTagsAsync(ServiceTagsRequestDto request) + { + var service = await _serviceRepository.GetByServiceCodeAsync(request.ServiceCode); + if (service is null) + { + throw new SpmsException( + ErrorCodes.NotFound, + "서비스를 찾을 수 없습니다.", + 404); + } + + return BuildTagsResponse(service); + } + + public async Task UpdateTagsAsync(UpdateServiceTagsRequestDto request) + { + var service = await _serviceRepository.GetByServiceCodeAsync(request.ServiceCode); + if (service is null) + { + throw new SpmsException( + ErrorCodes.NotFound, + "서비스를 찾을 수 없습니다.", + 404); + } + + var newTagsJson = JsonSerializer.Serialize(request.Tags); + if (newTagsJson == service.Tags) + { + throw new SpmsException( + ErrorCodes.NoChange, + "변경된 내용이 없습니다.", + 400); + } + + service.Tags = newTagsJson; + service.UpdatedAt = DateTime.UtcNow; + + _serviceRepository.Update(service); + await _unitOfWork.SaveChangesAsync(); + + return BuildTagsResponse(service); + } + + private static ServiceTagsResponseDto BuildTagsResponse(Service service) + { + var tagNames = new List(); + if (!string.IsNullOrEmpty(service.Tags)) + { + try + { + tagNames = JsonSerializer.Deserialize>(service.Tags) ?? new List(); + } + catch + { + // Invalid JSON, return empty + } + } + + return new ServiceTagsResponseDto + { + ServiceCode = service.ServiceCode, + Tags = tagNames.Select((name, index) => new TagItemDto + { + TagIndex = index, + TagName = name + }).ToList(), + TotalCount = tagNames.Count + }; + } + public async Task GetIpListAsync(string serviceCode) { var service = await _serviceRepository.GetByServiceCodeWithIpsAsync(serviceCode); -- 2.45.1