From 5014f37157d8e3f4a810c5011425bb72b13aab8f Mon Sep 17 00:00:00 2001 From: si-zero Date: Wed, 26 Aug 2026 16:29:29 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=98=B5=EC=85=98=20=EC=9D=91=EB=8B=B5=20DTO=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/response/AdminNoticeOptionsResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/swyp/picke/domain/admin/dto/notification/response/AdminNoticeOptionsResponse.java diff --git a/src/main/java/com/swyp/picke/domain/admin/dto/notification/response/AdminNoticeOptionsResponse.java b/src/main/java/com/swyp/picke/domain/admin/dto/notification/response/AdminNoticeOptionsResponse.java new file mode 100644 index 0000000..ae906dc --- /dev/null +++ b/src/main/java/com/swyp/picke/domain/admin/dto/notification/response/AdminNoticeOptionsResponse.java @@ -0,0 +1,7 @@ +package com.swyp.picke.domain.admin.dto.notification.response; + +import java.util.List; + +public record AdminNoticeOptionsResponse( + List categories +) {} From 595a48b48fce6857439c45a0e7b8a3ca19bb2d50 Mon Sep 17 00:00:00 2001 From: si-zero Date: Wed, 26 Aug 2026 16:29:33 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=98=B5=EC=85=98=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminNotificationService.java | 11 +++++++++++ .../admin/service/AdminNotificationServiceTest.java | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/java/com/swyp/picke/domain/admin/service/AdminNotificationService.java b/src/main/java/com/swyp/picke/domain/admin/service/AdminNotificationService.java index 55445fc..d46663c 100644 --- a/src/main/java/com/swyp/picke/domain/admin/service/AdminNotificationService.java +++ b/src/main/java/com/swyp/picke/domain/admin/service/AdminNotificationService.java @@ -5,6 +5,7 @@ import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeDeliveryResultResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeDetailResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeListResponse; +import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeOptionsResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeSummaryResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeTargetCountResponse; import com.swyp.picke.domain.notification.entity.Notification; @@ -17,6 +18,7 @@ import com.swyp.picke.domain.notification.service.NotificationService; import com.swyp.picke.global.common.exception.CustomException; import com.swyp.picke.global.common.exception.ErrorCode; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; @@ -112,6 +114,15 @@ public AdminNoticeTargetCountResponse getTargetCount() { return new AdminNoticeTargetCountResponse(notificationDispatchService.countAdminNoticeTargets()); } + public AdminNoticeOptionsResponse getOptions() { + List categories = List.of( + NotificationCategory.CONTENT.name(), + NotificationCategory.NOTICE.name(), + NotificationCategory.EVENT.name() + ); + return new AdminNoticeOptionsResponse(categories); + } + private NotificationCategory normalizeCategory(NotificationCategory category) { if (category == null || category == NotificationCategory.ALL) { return null; diff --git a/src/test/java/com/swyp/picke/domain/admin/service/AdminNotificationServiceTest.java b/src/test/java/com/swyp/picke/domain/admin/service/AdminNotificationServiceTest.java index 621b5da..ad78960 100644 --- a/src/test/java/com/swyp/picke/domain/admin/service/AdminNotificationServiceTest.java +++ b/src/test/java/com/swyp/picke/domain/admin/service/AdminNotificationServiceTest.java @@ -144,4 +144,12 @@ void getTargetCount_returnsCount() { assertThat(response.targetCount()).isEqualTo(1200); } + + @Test + @DisplayName("공지 작성 시 선택 가능한 카테고리 목록을 조회한다") + void getOptions_returnsCreatableCategories() { + var response = adminNotificationService.getOptions(); + + assertThat(response.categories()).containsExactly("CONTENT", "NOTICE", "EVENT"); + } } From 13a9a8003544c857288d1db01165dd836fa5f8ad Mon Sep 17 00:00:00 2001 From: si-zero Date: Wed, 26 Aug 2026 16:29:38 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=98=B5=EC=85=98=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/AdminNotificationController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/swyp/picke/domain/admin/controller/AdminNotificationController.java b/src/main/java/com/swyp/picke/domain/admin/controller/AdminNotificationController.java index be0f213..b4d3b6b 100644 --- a/src/main/java/com/swyp/picke/domain/admin/controller/AdminNotificationController.java +++ b/src/main/java/com/swyp/picke/domain/admin/controller/AdminNotificationController.java @@ -6,6 +6,7 @@ import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeDeliveryResultResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeDetailResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeListResponse; +import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeOptionsResponse; import com.swyp.picke.domain.admin.dto.notification.response.AdminNoticeTargetCountResponse; import com.swyp.picke.domain.admin.service.AdminNotificationService; import com.swyp.picke.domain.notification.enums.NotificationCategory; @@ -44,6 +45,12 @@ public ApiResponse createNotice( return ApiResponse.onSuccess(adminNotificationService.createNotice(request)); } + @Operation(summary = "공지사항 작성 옵션 조회", description = "공지 작성 시 선택 가능한 category 목록을 내려준다.") + @GetMapping("/options") + public ApiResponse getOptions() { + return ApiResponse.onSuccess(adminNotificationService.getOptions()); + } + @Operation(summary = "공지사항 목록 조회") @GetMapping public ApiResponse getNotices( From e4ed6dafd287d3e12de57f3f1aa2ac359ee03897 Mon Sep 17 00:00:00 2001 From: si-zero Date: Wed, 26 Aug 2026 16:29:38 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20=EA=B3=B5=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=98=B5=EC=85=98=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api-specs/notification-api.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/api-specs/notification-api.md b/docs/api-specs/notification-api.md index 6b6dac0..e111310 100644 --- a/docs/api-specs/notification-api.md +++ b/docs/api-specs/notification-api.md @@ -345,6 +345,7 @@ | Method | Path | 설명 | |---|---|---| | `POST` | `/api/v1/admin/notices` | 공지사항/이벤트 작성 (즉시 전체 발송) | +| `GET` | `/api/v1/admin/notices/options` | 공지 작성 시 선택 가능한 `category` 목록 조회 | | `GET` | `/api/v1/admin/notices/target-count` | 지금 등록하면 몇 대의 디바이스에 발송되는지 미리 조회 | | `GET` | `/api/v1/admin/notices` | 공지 목록 조회 (`category`, `page`, `size` 쿼리) | | `GET` | `/api/v1/admin/notices/{noticeId}` | 공지 상세 조회 | @@ -371,6 +372,22 @@ 응답은 `AdminNoticeDetailResponse` (`notificationId`, `category`, `detailCode`, `title`, `body`, `referenceId`, `createdAt`). +**`GET /api/v1/admin/notices/options`** + +공지 작성 폼의 카테고리 선택지를 서버 enum 기준으로 내려줍니다. `ALL`은 목록 조회 필터용이라 작성 옵션에는 포함되지 않습니다. + +성공 응답 `200 OK`: + +```json +{ + "statusCode": 200, + "data": { + "categories": ["CONTENT", "NOTICE", "EVENT"] + }, + "error": null +} +``` + **`GET /api/v1/admin/notices/target-count`** `NOTICE`/`EVENT` 공지를 지금 등록하면 몇 대의 디바이스에 발송되는지 미리 조회합니다. `notifyAdminNotice`가 사용하는 것과 동일하게 '이벤트 및 소식 알림' 설정이 ON인 유저의 등록된 디바이스 수 기준입니다. `CONTENT` 카테고리 공지는 애초에 이 대상자 산정을 타지 않으므로 참고용으로만 사용하세요.