diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0ac5882
--- /dev/null
+++ b/README.md
@@ -0,0 +1,239 @@
+# π FlipNote β Notification Service
+
+**FlipNote μλΉμ€μ μλ¦Ό λλ©μΈ λ°±μλ λ ν¬μ§ν 리μ
λλ€.**
+
+
+
+
+
+
+
+
+---
+
+## π λͺ©μ°¨
+
+- [μμνκΈ°](#μμνκΈ°)
+- [νκ²½ λ³μ](#νκ²½-λ³μ)
+- [μ€ν λ° λ°°ν¬](#μ€ν-λ°-λ°°ν¬)
+- [νλ‘μ νΈ κ΅¬μ‘°](#νλ‘μ νΈ-ꡬ쑰)
+
+---
+
+
+
+## π μμνκΈ°
+
+### μ¬μ μꡬμ¬ν
+
+- **Java** 21 μ΄μ
+- **Gradle** 8 μ΄μ
+- **MySQL** 8 μ΄μ
+- **RabbitMQ** 3 μ΄μ
+- Firebase νλ‘μ νΈ μμ± λ° μλΉμ€ κ³μ JSON λ°κΈ
+
+### μ€μΉ
+
+```bash
+# μμ‘΄μ± μ€μΉ λ° λΉλ
+./gradlew build -x test
+```
+
+---
+
+
+
+## π νκ²½ λ³μ
+
+`application.yaml`μμ μ°Έμ‘°νλ νκ²½ λ³μ λͺ©λ‘μ
λλ€.
+
+```text
+# βββ Database βββββββββββββββββββββββββββββββββββββββββββ
+DB_URL=jdbc:mysql://localhost:3306/flipnote_notification
+DB_USERNAME=
+DB_PASSWORD=
+
+# βββ JPA ββββββββββββββββββββββββββββββββββββββββββββββββ
+# create | create-drop | update | validate | none
+JPA_DDL_AUTO=validate
+
+# βββ RabbitMQ βββββββββββββββββββββββββββββββββββββββββββ
+RABBITMQ_HOST=localhost
+RABBITMQ_PORT=5672
+RABBITMQ_USERNAME=guest
+RABBITMQ_PASSWORD=guest
+
+# βββ Firebase βββββββββββββββββββββββββββββββββββββββββββ
+# μλΉμ€ κ³μ JSON μ 체 λ΄μ© (λ¬Έμμ΄)
+FIREBASE_SERVICE_ACCOUNT_JSON=
+
+# βββ Server βββββββββββββββββββββββββββββββββββββββββββββ
+SERVER_PORT=8086
+
+# βββ Async Thread Pool ββββββββββββββββββββββββββββββββββ
+ASYNC_CORE_POOL_SIZE=4
+ASYNC_MAX_POOL_SIZE=10
+ASYNC_QUEUE_CAPACITY=100
+
+# βββ Swagger ββββββββββββββββββββββββββββββββββββββββββββ
+SPRINGDOC_SERVER_URL=http://localhost:8086
+```
+
+> **β οΈ μ£Όμ**: νκ²½ λ³μ νμΌμ μ λ gitμ 컀λ°νμ§ λ§μΈμ. `.gitignore`μ ν¬ν¨λμ΄ μλμ§ λ°λμ νμΈνμΈμ.
+
+---
+
+
+
+## π₯οΈ μ€ν λ° λ°°ν¬
+
+### λ‘컬 κ°λ° μλ² μ€ν
+
+```bash
+./gradlew bootRun
+```
+
+κΈ°λ³Έμ μΌλ‘ `http://localhost:8086`μμ μ€νλ©λλ€.
+Swagger UIλ `http://localhost:8086/notifications/swagger-ui.html`μμ νμΈν μ μμ΅λλ€.
+
+### νλ‘λμ
λΉλ
+
+```bash
+./gradlew bootJar
+```
+
+`build/libs/notification-0.0.1-SNAPSHOT.jar` νμΌμ΄ μμ±λ©λλ€.
+
+### ν
μ€νΈ μ€ν
+
+```bash
+./gradlew test
+```
+
+### Docker μ΄λ―Έμ§ λΉλ λ° μ€ν
+
+```bash
+# μ΄λ―Έμ§ λΉλ
+docker build -t flipnote-notification .
+
+# 컨ν
μ΄λ μ€ν
+docker run -p 8086:8086 \
+ -e DB_URL=... \
+ -e FIREBASE_SERVICE_ACCOUNT_JSON=... \
+ flipnote-notification
+```
+
+### λ°°ν¬ (GitHub Actions)
+
+`main` λΈλμΉμ push μ GitHub Actionsκ° μλμΌλ‘ μλ κ³Όμ μ μ€νν©λλ€.
+
+**CI** (`push` / `pull_request` β `main`)
+1. JDK 21 μ€μΉ
+2. `./gradlew build -x test` β λΉλ κ²μ¦
+3. `./gradlew test` β ν
μ€νΈ μ€ν
+4. Dependency-Check β μ·¨μ½μ λΆμ 리ν¬νΈ μμ±
+
+**CD** (`push` β `main`)
+1. GitHub Container Registry(GHCR) λ‘κ·ΈμΈ
+2. Docker μ΄λ―Έμ§ λΉλ
+3. `ghcr.io/dungbik/flipnote-notification` μ΄λ―Έμ§ Push
+
+> λ°°ν¬μ νμν μν¬λ¦Ώ(`ORG_PAT`)μ GitHub Repository β Settings β Secrets and variables β Actionsμ λ±λ‘ν΄μΌ ν©λλ€.
+
+---
+
+
+
+## π νλ‘μ νΈ κ΅¬μ‘°
+
+- κ°λ΅ν λ²μ
+
+ ```text
+ src/main/java/flipnote/notification/
+ βββ domain/ # λλ©μΈ λ μ΄μ΄ (μν°ν°, λ ν¬μ§ν 리, μλ¬μ½λ)
+ βββ application/ # μ ν리μΌμ΄μ
λ μ΄μ΄ (μλΉμ€, 컀맨λ, κ²°κ³Ό κ°μ²΄)
+ βββ infrastructure/ # μΈνλΌ λ μ΄μ΄ (Firebase, RabbitMQ, μ€μ )
+ βββ interfaces/ # μΈν°νμ΄μ€ λ μ΄μ΄ (HTTP μ§μ
μ )
+ ```
+
+```text
+FlipNote-Notification/
+βββ src/
+β βββ main/
+β β βββ java/flipnote/notification/
+β β β βββ FlipNoteNotificationApplication.java
+β β β β
+β β β βββ domain/ # λλ©μΈ λ μ΄μ΄
+β β β β βββ common/ # λλ©μΈ 곡ν΅
+β β β β β βββ ErrorCode.java
+β β β β β βββ BizException.java
+β β β β β βββ CommonErrorCode.java
+β β β β β βββ BaseEntity.java
+β β β β βββ notification/ # μλ¦Ό λλ©μΈ
+β β β β β βββ Notification.java
+β β β β β βββ NotificationType.java
+β β β β β βββ NotificationRepository.java
+β β β β β βββ NotificationErrorCode.java
+β β β β βββ fcmtoken/ # FCM ν ν° λλ©μΈ
+β β β β βββ FcmToken.java
+β β β β βββ FcmTokenRepository.java
+β β β β
+β β β βββ application/ # μ ν리μΌμ΄μ
λ μ΄μ΄
+β β β β βββ dto/
+β β β β β βββ command/ # μλΉμ€ μ
λ ₯ 컀맨λ (κ²μ¦ μ΄λ
Έν
μ΄μ
μμ)
+β β β β β β βββ NotificationListCommand.java
+β β β β β βββ result/ # μλΉμ€ μΆλ ₯ κ²°κ³Ό κ°μ²΄ (νλ‘ν μ½ λ¬΄κ΄)
+β β β β β βββ FcmSendResult.java
+β β β β β βββ NotificationResult.java
+β β β β β βββ PagedResult.java
+β β β β βββ FcmSender.java # FCM μμλ°μ΄λ ν¬νΈ μΈν°νμ΄μ€
+β β β β βββ FcmTokenService.java
+β β β β βββ NotificationCommandService.java
+β β β β βββ NotificationQueryService.java
+β β β β
+β β β βββ infrastructure/ # μΈνλΌ λ μ΄μ΄
+β β β β βββ config/ # λ²μ© μ€μ
+β β β β β βββ AppConfig.java
+β β β β β βββ AsyncConfig.java
+β β β β β βββ AsyncProperties.java
+β β β β β βββ SwaggerConfig.java
+β β β β βββ persistence/ # JPA μ€μ λ° λ³νκΈ°
+β β β β β βββ JpaAuditingConfig.java
+β β β β β βββ MapToJsonConverter.java
+β β β β βββ fcm/ # Firebase Cloud Messaging μ΄λν°
+β β β β β βββ FirebaseConfig.java
+β β β β β βββ FirebaseFcmSender.java # FcmSender ꡬν체
+β β β β β βββ FcmErrorCode.java
+β β β β βββ messaging/ # RabbitMQ λ©μμ§ μ²λ¦¬
+β β β β βββ RabbitMQConfig.java
+β β β β βββ GroupInviteMessage.java
+β β β β βββ GroupInviteMessageListener.java
+β β β β βββ GroupJoinRequestMessage.java
+β β β β βββ GroupJoinRequestMessageListener.java
+β β β β
+β β β βββ interfaces/ # μΈν°νμ΄μ€ λ μ΄μ΄
+β β β βββ http/ # HTTP μ§μ
μ
+β β β βββ NotificationController.java
+β β β βββ NotificationControllerDocs.java
+β β β βββ dto/request/ # HTTP Request DTO (@Valid ν¬ν¨)
+β β β β βββ NotificationListRequest.java
+β β β β βββ TokenRegisterRequest.java
+β β β βββ common/ # HTTP κ³΅ν΅ μ νΈ
+β β β βββ ApiResponse.java
+β β β βββ ApiResponseAdvice.java
+β β β βββ CursorPagingRequest.java
+β β β βββ CursorPagingResponse.java
+β β β βββ GlobalExceptionHandler.java
+β β β βββ HttpHeaders.java
+β β β
+β β βββ resources/
+β β βββ application.yaml
+β β βββ messages.properties # μλ¦Ό λ©μμ§ ν
νλ¦Ώ
+β β
+β βββ test/
+β βββ java/flipnote/notification/
+β
+βββ Dockerfile
+βββ build.gradle.kts
+βββ settings.gradle.kts
+```
diff --git a/src/main/java/flipnote/notification/application/FcmSender.java b/src/main/java/flipnote/notification/application/FcmSender.java
new file mode 100644
index 0000000..ace35a4
--- /dev/null
+++ b/src/main/java/flipnote/notification/application/FcmSender.java
@@ -0,0 +1,10 @@
+package flipnote.notification.application;
+
+import java.util.List;
+
+import flipnote.notification.application.dto.result.FcmSendResult;
+
+public interface FcmSender {
+
+ FcmSendResult sendEachForMulticast(List tokens, String title, String body);
+}
diff --git a/src/main/java/flipnote/notification/application/service/FcmTokenService.java b/src/main/java/flipnote/notification/application/FcmTokenService.java
similarity index 63%
rename from src/main/java/flipnote/notification/application/service/FcmTokenService.java
rename to src/main/java/flipnote/notification/application/FcmTokenService.java
index e3ba37b..de48eb4 100644
--- a/src/main/java/flipnote/notification/application/service/FcmTokenService.java
+++ b/src/main/java/flipnote/notification/application/FcmTokenService.java
@@ -1,4 +1,4 @@
-package flipnote.notification.application.service;
+package flipnote.notification.application;
import java.util.Objects;
import java.util.Optional;
@@ -6,7 +6,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import flipnote.notification.application.dto.request.TokenRegisterRequest;
import flipnote.notification.domain.fcmtoken.FcmToken;
import flipnote.notification.domain.fcmtoken.FcmTokenRepository;
import lombok.RequiredArgsConstructor;
@@ -19,20 +18,20 @@ public class FcmTokenService {
private final FcmTokenRepository fcmTokenRepository;
@Transactional
- public void registerFcmToken(Long userId, TokenRegisterRequest req) {
- Optional existingToken = fcmTokenRepository.findByToken(req.token());
+ public void registerFcmToken(Long userId, String token) {
+ Optional existingToken = fcmTokenRepository.findByToken(token);
if (existingToken.isPresent()) {
- FcmToken token = existingToken.get();
+ FcmToken fcmToken = existingToken.get();
- if (Objects.equals(token.getUserId(), userId)) {
- token.updateLastUsedAt();
+ if (Objects.equals(fcmToken.getUserId(), userId)) {
+ fcmToken.updateLastUsedAt();
} else {
- fcmTokenRepository.deleteById(token.getId());
- saveFcmToken(userId, req.token());
+ fcmTokenRepository.deleteById(fcmToken.getId());
+ saveFcmToken(userId, token);
}
} else {
- saveFcmToken(userId, req.token());
+ saveFcmToken(userId, token);
}
}
diff --git a/src/main/java/flipnote/notification/application/service/NotificationCommandService.java b/src/main/java/flipnote/notification/application/NotificationCommandService.java
similarity index 63%
rename from src/main/java/flipnote/notification/application/service/NotificationCommandService.java
rename to src/main/java/flipnote/notification/application/NotificationCommandService.java
index f987e2d..c27dc6b 100644
--- a/src/main/java/flipnote/notification/application/service/NotificationCommandService.java
+++ b/src/main/java/flipnote/notification/application/NotificationCommandService.java
@@ -1,7 +1,6 @@
-package flipnote.notification.application.service;
+package flipnote.notification.application;
import java.time.LocalDateTime;
-import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -11,19 +10,14 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import com.google.firebase.messaging.BatchResponse;
-import com.google.firebase.messaging.FirebaseMessagingException;
-import com.google.firebase.messaging.SendResponse;
-
-import flipnote.notification.application.port.FcmSender;
-import flipnote.notification.common.exception.BizException;
-import flipnote.notification.common.exception.NotificationErrorCode;
+import flipnote.notification.application.dto.result.FcmSendResult;
+import flipnote.notification.domain.common.BizException;
import flipnote.notification.domain.fcmtoken.FcmToken;
import flipnote.notification.domain.fcmtoken.FcmTokenRepository;
import flipnote.notification.domain.notification.Notification;
+import flipnote.notification.domain.notification.NotificationErrorCode;
import flipnote.notification.domain.notification.NotificationRepository;
import flipnote.notification.domain.notification.NotificationType;
-import flipnote.notification.infrastructure.fcm.FcmErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -101,44 +95,20 @@ public void markAllNotificationsAsRead(Long userId) {
}
private void sendFcmNotification(Long userId, String body) {
- List infos = fcmTokenRepository.findByUserId(userId);
- if (infos.isEmpty()) {
+ List fcmTokens = fcmTokenRepository.findByUserId(userId);
+ if (fcmTokens.isEmpty()) {
log.warn("No FCM tokens for user {}", userId);
return;
}
- List tokens = infos.stream().map(FcmToken::getToken).toList();
- try {
- BatchResponse response = fcmSender.sendEachForMulticast(tokens, "μλ¦Ό", body);
-
- List validTokens = new ArrayList<>();
- List invalidTokens = new ArrayList<>();
- for (int i = 0; i < response.getResponses().size(); i++) {
- SendResponse res = response.getResponses().get(i);
- if (res.isSuccessful()) {
- validTokens.add(tokens.get(i));
- } else {
- String errorName = res.getException().getMessagingErrorCode().name();
- FcmErrorCode code = FcmErrorCode.from(errorName);
- if (code == FcmErrorCode.UNREGISTERED || code == FcmErrorCode.INVALID_ARGUMENT) {
- invalidTokens.add(tokens.get(i));
- }
- }
- }
+ List tokens = fcmTokens.stream().map(FcmToken::getToken).toList();
+ FcmSendResult result = fcmSender.sendEachForMulticast(tokens, "μλ¦Ό", body);
- if (!invalidTokens.isEmpty()) {
- fcmTokenRepository.deleteByUserIdAndTokenIn(userId, invalidTokens);
- }
- if (!validTokens.isEmpty()) {
- fcmTokenRepository.bulkUpdateLastUsedAt(validTokens, LocalDateTime.now());
- }
- } catch (FirebaseMessagingException e) {
- String errorName = e.getMessagingErrorCode() != null ? e.getMessagingErrorCode().name() : "INTERNAL";
- FcmErrorCode code = FcmErrorCode.from(errorName);
- if (code == FcmErrorCode.UNAVAILABLE) {
- throw new BizException(NotificationErrorCode.FCM_SERVER_UNAVAILABLE);
- }
- throw new BizException(NotificationErrorCode.FCM_INTERNAL_ERROR);
+ if (!result.invalidTokens().isEmpty()) {
+ fcmTokenRepository.deleteByUserIdAndTokenIn(userId, result.invalidTokens());
+ }
+ if (!result.validTokens().isEmpty()) {
+ fcmTokenRepository.bulkUpdateLastUsedAt(result.validTokens(), LocalDateTime.now());
}
}
diff --git a/src/main/java/flipnote/notification/application/service/NotificationQueryService.java b/src/main/java/flipnote/notification/application/NotificationQueryService.java
similarity index 58%
rename from src/main/java/flipnote/notification/application/service/NotificationQueryService.java
rename to src/main/java/flipnote/notification/application/NotificationQueryService.java
index 971a284..6d2a71b 100644
--- a/src/main/java/flipnote/notification/application/service/NotificationQueryService.java
+++ b/src/main/java/flipnote/notification/application/NotificationQueryService.java
@@ -1,4 +1,4 @@
-package flipnote.notification.application.service;
+package flipnote.notification.application;
import java.util.List;
import java.util.Locale;
@@ -8,9 +8,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import flipnote.notification.application.dto.request.NotificationListRequest;
-import flipnote.notification.application.dto.response.NotificationResponse;
-import flipnote.notification.common.response.CursorPagingResponse;
+import flipnote.notification.application.dto.command.NotificationListCommand;
+import flipnote.notification.application.dto.result.NotificationResult;
+import flipnote.notification.application.dto.result.PagedResult;
import flipnote.notification.domain.notification.Notification;
import flipnote.notification.domain.notification.NotificationRepository;
import lombok.RequiredArgsConstructor;
@@ -23,26 +23,23 @@ public class NotificationQueryService {
private final NotificationRepository notificationRepository;
private final MessageSource messageSource;
- public CursorPagingResponse getNotifications(Long userId, NotificationListRequest req) {
+ public PagedResult getNotifications(Long userId, NotificationListCommand command) {
List notifications = notificationRepository.findNotificationsByReceiverIdAndCursor(
- userId, req.getCursorId(), req.getGroupId(), req.getRead(), req.getPageRequest()
+ userId, command.cursorId(), command.groupId(), command.read(), command.getPageRequest()
);
- boolean hasNext = notifications.size() > req.getSize();
+ boolean hasNext = notifications.size() > command.size();
Long nextCursor = null;
if (hasNext) {
- notifications = notifications.subList(0, req.getSize());
+ notifications = notifications.subList(0, command.size());
nextCursor = notifications.get(notifications.size() - 1).getId();
}
- List content = notifications.stream()
- .map(notification -> {
- String message = buildMessage(notification);
- return NotificationResponse.of(notification, message);
- })
+ List content = notifications.stream()
+ .map(notification -> NotificationResult.of(notification, buildMessage(notification)))
.toList();
- return CursorPagingResponse.of(content, hasNext, nextCursor);
+ return PagedResult.of(content, hasNext, nextCursor);
}
private String buildMessage(Notification notification) {
diff --git a/src/main/java/flipnote/notification/application/dto/command/NotificationListCommand.java b/src/main/java/flipnote/notification/application/dto/command/NotificationListCommand.java
new file mode 100644
index 0000000..08997ae
--- /dev/null
+++ b/src/main/java/flipnote/notification/application/dto/command/NotificationListCommand.java
@@ -0,0 +1,16 @@
+package flipnote.notification.application.dto.command;
+
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+
+public record NotificationListCommand(
+ Long cursorId,
+ Long groupId,
+ Boolean read,
+ int size
+) {
+
+ public PageRequest getPageRequest() {
+ return PageRequest.of(0, size + 1, Sort.by(Sort.Direction.DESC, "id"));
+ }
+}
diff --git a/src/main/java/flipnote/notification/application/dto/request/NotificationListRequest.java b/src/main/java/flipnote/notification/application/dto/request/NotificationListRequest.java
deleted file mode 100644
index b00f01e..0000000
--- a/src/main/java/flipnote/notification/application/dto/request/NotificationListRequest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package flipnote.notification.application.dto.request;
-
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Sort;
-
-import flipnote.notification.common.response.CursorPagingRequest;
-import jakarta.validation.constraints.Min;
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class NotificationListRequest extends CursorPagingRequest {
-
- @Min(1)
- private Long groupId;
-
- private Boolean read;
-
- @Override
- public PageRequest getPageRequest() {
- return PageRequest.of(0, getSize(), Sort.by(Sort.Direction.DESC, "id"));
- }
-}
diff --git a/src/main/java/flipnote/notification/application/dto/result/FcmSendResult.java b/src/main/java/flipnote/notification/application/dto/result/FcmSendResult.java
new file mode 100644
index 0000000..7c70821
--- /dev/null
+++ b/src/main/java/flipnote/notification/application/dto/result/FcmSendResult.java
@@ -0,0 +1,9 @@
+package flipnote.notification.application.dto.result;
+
+import java.util.List;
+
+public record FcmSendResult(
+ List validTokens,
+ List invalidTokens
+) {
+}
diff --git a/src/main/java/flipnote/notification/application/dto/response/NotificationResponse.java b/src/main/java/flipnote/notification/application/dto/result/NotificationResult.java
similarity index 63%
rename from src/main/java/flipnote/notification/application/dto/response/NotificationResponse.java
rename to src/main/java/flipnote/notification/application/dto/result/NotificationResult.java
index 3aa9ce2..6595f0a 100644
--- a/src/main/java/flipnote/notification/application/dto/response/NotificationResponse.java
+++ b/src/main/java/flipnote/notification/application/dto/result/NotificationResult.java
@@ -1,4 +1,4 @@
-package flipnote.notification.application.dto.response;
+package flipnote.notification.application.dto.result;
import java.time.LocalDateTime;
import java.util.Map;
@@ -7,22 +7,18 @@
import flipnote.notification.domain.notification.Notification;
-public record NotificationResponse(
+public record NotificationResult(
Long notificationId,
Long groupId,
String message,
Map metadata,
boolean isRead,
-
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime readAt,
-
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime createdAt
) {
- public static NotificationResponse of(Notification notification, String message) {
- return new NotificationResponse(
+ public static NotificationResult of(Notification notification, String message) {
+ return new NotificationResult(
notification.getId(),
notification.getGroupId(),
message,
diff --git a/src/main/java/flipnote/notification/application/dto/result/PagedResult.java b/src/main/java/flipnote/notification/application/dto/result/PagedResult.java
new file mode 100644
index 0000000..cbab9bf
--- /dev/null
+++ b/src/main/java/flipnote/notification/application/dto/result/PagedResult.java
@@ -0,0 +1,14 @@
+package flipnote.notification.application.dto.result;
+
+import java.util.List;
+
+public record PagedResult(
+ List content,
+ boolean hasNext,
+ Long nextCursor
+) {
+
+ public static PagedResult of(List content, boolean hasNext, Long nextCursor) {
+ return new PagedResult<>(content, hasNext, hasNext ? nextCursor : null);
+ }
+}
diff --git a/src/main/java/flipnote/notification/application/port/FcmSender.java b/src/main/java/flipnote/notification/application/port/FcmSender.java
deleted file mode 100644
index f7e8558..0000000
--- a/src/main/java/flipnote/notification/application/port/FcmSender.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package flipnote.notification.application.port;
-
-import java.util.List;
-
-import com.google.firebase.messaging.BatchResponse;
-import com.google.firebase.messaging.FirebaseMessagingException;
-
-public interface FcmSender {
-
- BatchResponse sendEachForMulticast(
- List tokens,
- String title,
- String body
- ) throws FirebaseMessagingException;
-}
diff --git a/src/main/java/flipnote/notification/common/response/CursorPagingResponse.java b/src/main/java/flipnote/notification/common/response/CursorPagingResponse.java
deleted file mode 100644
index 2bdbf94..0000000
--- a/src/main/java/flipnote/notification/common/response/CursorPagingResponse.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package flipnote.notification.common.response;
-
-import java.util.List;
-import java.util.Objects;
-
-public record CursorPagingResponse(
- List content,
- boolean hasNext,
- String nextCursor,
- int size
-) {
-
- public static CursorPagingResponse of(List content, boolean hasNext, String nextCursor) {
- return new CursorPagingResponse<>(content, hasNext, hasNext ? nextCursor : null, content.size());
- }
-
- public static CursorPagingResponse of(List content, boolean hasNext, Long nextCursorId) {
- String nextCursor = Objects.toString(nextCursorId, null);
- return of(content, hasNext, nextCursor);
- }
-}
diff --git a/src/main/java/flipnote/notification/common/exception/BizException.java b/src/main/java/flipnote/notification/domain/common/BizException.java
similarity index 78%
rename from src/main/java/flipnote/notification/common/exception/BizException.java
rename to src/main/java/flipnote/notification/domain/common/BizException.java
index 5231a70..ccca01f 100644
--- a/src/main/java/flipnote/notification/common/exception/BizException.java
+++ b/src/main/java/flipnote/notification/domain/common/BizException.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common.exception;
+package flipnote.notification.domain.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/src/main/java/flipnote/notification/common/exception/CommonErrorCode.java b/src/main/java/flipnote/notification/domain/common/CommonErrorCode.java
similarity index 91%
rename from src/main/java/flipnote/notification/common/exception/CommonErrorCode.java
rename to src/main/java/flipnote/notification/domain/common/CommonErrorCode.java
index bad856f..d23da8c 100644
--- a/src/main/java/flipnote/notification/common/exception/CommonErrorCode.java
+++ b/src/main/java/flipnote/notification/domain/common/CommonErrorCode.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common.exception;
+package flipnote.notification.domain.common;
import org.springframework.http.HttpStatus;
diff --git a/src/main/java/flipnote/notification/common/exception/ErrorCode.java b/src/main/java/flipnote/notification/domain/common/ErrorCode.java
similarity index 66%
rename from src/main/java/flipnote/notification/common/exception/ErrorCode.java
rename to src/main/java/flipnote/notification/domain/common/ErrorCode.java
index ab86736..6ac44e0 100644
--- a/src/main/java/flipnote/notification/common/exception/ErrorCode.java
+++ b/src/main/java/flipnote/notification/domain/common/ErrorCode.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common.exception;
+package flipnote.notification.domain.common;
public interface ErrorCode {
diff --git a/src/main/java/flipnote/notification/domain/fcmtoken/FcmTokenRepository.java b/src/main/java/flipnote/notification/domain/fcmtoken/FcmTokenRepository.java
index 1ffe0f2..6f694c7 100644
--- a/src/main/java/flipnote/notification/domain/fcmtoken/FcmTokenRepository.java
+++ b/src/main/java/flipnote/notification/domain/fcmtoken/FcmTokenRepository.java
@@ -8,6 +8,7 @@
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
+import org.springframework.transaction.annotation.Transactional;
public interface FcmTokenRepository extends JpaRepository {
@@ -15,8 +16,12 @@ public interface FcmTokenRepository extends JpaRepository {
Optional findByToken(String token);
- void deleteByUserIdAndTokenIn(Long userId, List tokens);
+ @Transactional
+ @Modifying(clearAutomatically = true)
+ @Query("DELETE FROM FcmToken f WHERE f.userId = :userId AND f.token IN :tokens")
+ void deleteByUserIdAndTokenIn(@Param("userId") Long userId, @Param("tokens") List tokens);
+ @Transactional
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE FcmToken f SET f.lastUsedAt = :now WHERE f.token IN :tokens")
int bulkUpdateLastUsedAt(@Param("tokens") List tokens, @Param("now") LocalDateTime now);
diff --git a/src/main/java/flipnote/notification/domain/notification/Notification.java b/src/main/java/flipnote/notification/domain/notification/Notification.java
index 33acc69..2855c9f 100644
--- a/src/main/java/flipnote/notification/domain/notification/Notification.java
+++ b/src/main/java/flipnote/notification/domain/notification/Notification.java
@@ -46,9 +46,9 @@ public class Notification extends BaseEntity {
private Map metadata;
@Column(name = "is_read", nullable = false)
- boolean read;
+ private boolean read;
- LocalDateTime readAt;
+ private LocalDateTime readAt;
@Builder
public Notification(
diff --git a/src/main/java/flipnote/notification/common/exception/NotificationErrorCode.java b/src/main/java/flipnote/notification/domain/notification/NotificationErrorCode.java
similarity index 87%
rename from src/main/java/flipnote/notification/common/exception/NotificationErrorCode.java
rename to src/main/java/flipnote/notification/domain/notification/NotificationErrorCode.java
index 185e33e..89cfc34 100644
--- a/src/main/java/flipnote/notification/common/exception/NotificationErrorCode.java
+++ b/src/main/java/flipnote/notification/domain/notification/NotificationErrorCode.java
@@ -1,7 +1,8 @@
-package flipnote.notification.common.exception;
+package flipnote.notification.domain.notification;
import org.springframework.http.HttpStatus;
+import flipnote.notification.domain.common.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
diff --git a/src/main/java/flipnote/notification/infrastructure/config/AppConfig.java b/src/main/java/flipnote/notification/infrastructure/config/AppConfig.java
new file mode 100644
index 0000000..03ca193
--- /dev/null
+++ b/src/main/java/flipnote/notification/infrastructure/config/AppConfig.java
@@ -0,0 +1,25 @@
+package flipnote.notification.infrastructure.config;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer;
+import tools.jackson.databind.module.SimpleModule;
+
+@Configuration
+public class AppConfig {
+
+ @Bean
+ public JsonMapperBuilderCustomizer jacksonCustomizer() {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ return builder -> {
+ SimpleModule module = new SimpleModule();
+ module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+ builder.addModule(module);
+ };
+ }
+}
diff --git a/src/main/java/flipnote/notification/infrastructure/config/SwaggerConfig.java b/src/main/java/flipnote/notification/infrastructure/config/SwaggerConfig.java
index 7cff309..7ea0ab7 100644
--- a/src/main/java/flipnote/notification/infrastructure/config/SwaggerConfig.java
+++ b/src/main/java/flipnote/notification/infrastructure/config/SwaggerConfig.java
@@ -14,7 +14,7 @@
@Configuration
public class SwaggerConfig {
- @Value("${springdoc.server.url:http://localhost:8081}")
+ @Value("${springdoc.server.url:http://localhost:8086}")
private String serverUrl;
@Bean
diff --git a/src/main/java/flipnote/notification/infrastructure/fcm/FirebaseFcmSender.java b/src/main/java/flipnote/notification/infrastructure/fcm/FirebaseFcmSender.java
index fbcc7d0..0c546f4 100644
--- a/src/main/java/flipnote/notification/infrastructure/fcm/FirebaseFcmSender.java
+++ b/src/main/java/flipnote/notification/infrastructure/fcm/FirebaseFcmSender.java
@@ -1,5 +1,6 @@
package flipnote.notification.infrastructure.fcm;
+import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
@@ -9,18 +10,18 @@
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.MulticastMessage;
import com.google.firebase.messaging.Notification;
+import com.google.firebase.messaging.SendResponse;
-import flipnote.notification.application.port.FcmSender;
+import flipnote.notification.application.FcmSender;
+import flipnote.notification.application.dto.result.FcmSendResult;
+import flipnote.notification.domain.common.BizException;
+import flipnote.notification.domain.notification.NotificationErrorCode;
@Component
public class FirebaseFcmSender implements FcmSender {
@Override
- public BatchResponse sendEachForMulticast(
- List tokens,
- String title,
- String body
- ) throws FirebaseMessagingException {
+ public FcmSendResult sendEachForMulticast(List tokens, String title, String body) {
Notification notification = Notification.builder()
.setTitle(title)
.setBody(body)
@@ -30,6 +31,36 @@ public BatchResponse sendEachForMulticast(
.setNotification(notification)
.build();
- return FirebaseMessaging.getInstance().sendEachForMulticast(message);
+ try {
+ BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(message);
+ return toSendResult(tokens, response);
+ } catch (FirebaseMessagingException e) {
+ String errorName = e.getMessagingErrorCode() != null ? e.getMessagingErrorCode().name() : "INTERNAL";
+ FcmErrorCode code = FcmErrorCode.from(errorName);
+ if (code == FcmErrorCode.UNAVAILABLE) {
+ throw new BizException(NotificationErrorCode.FCM_SERVER_UNAVAILABLE);
+ }
+ throw new BizException(NotificationErrorCode.FCM_INTERNAL_ERROR);
+ }
+ }
+
+ private FcmSendResult toSendResult(List tokens, BatchResponse response) {
+ List validTokens = new ArrayList<>();
+ List invalidTokens = new ArrayList<>();
+
+ for (int i = 0; i < response.getResponses().size(); i++) {
+ SendResponse res = response.getResponses().get(i);
+ if (res.isSuccessful()) {
+ validTokens.add(tokens.get(i));
+ } else {
+ String errorName = res.getException().getMessagingErrorCode().name();
+ FcmErrorCode code = FcmErrorCode.from(errorName);
+ if (code == FcmErrorCode.UNREGISTERED || code == FcmErrorCode.INVALID_ARGUMENT) {
+ invalidTokens.add(tokens.get(i));
+ }
+ }
+ }
+
+ return new FcmSendResult(validTokens, invalidTokens);
}
}
diff --git a/src/main/java/flipnote/notification/application/dto/message/GroupInviteMessage.java b/src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessage.java
similarity index 61%
rename from src/main/java/flipnote/notification/application/dto/message/GroupInviteMessage.java
rename to src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessage.java
index d02a356..6b52292 100644
--- a/src/main/java/flipnote/notification/application/dto/message/GroupInviteMessage.java
+++ b/src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessage.java
@@ -1,4 +1,4 @@
-package flipnote.notification.application.dto.message;
+package flipnote.notification.infrastructure.messaging;
public record GroupInviteMessage(
Long groupId,
diff --git a/src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessageListener.java b/src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessageListener.java
index 8c03e6c..15683cd 100644
--- a/src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessageListener.java
+++ b/src/main/java/flipnote/notification/infrastructure/messaging/GroupInviteMessageListener.java
@@ -3,8 +3,7 @@
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
-import flipnote.notification.application.dto.message.GroupInviteMessage;
-import flipnote.notification.application.service.NotificationCommandService;
+import flipnote.notification.application.NotificationCommandService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
diff --git a/src/main/java/flipnote/notification/application/dto/message/GroupJoinRequestMessage.java b/src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessage.java
similarity index 73%
rename from src/main/java/flipnote/notification/application/dto/message/GroupJoinRequestMessage.java
rename to src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessage.java
index f6f8837..332597d 100644
--- a/src/main/java/flipnote/notification/application/dto/message/GroupJoinRequestMessage.java
+++ b/src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessage.java
@@ -1,4 +1,4 @@
-package flipnote.notification.application.dto.message;
+package flipnote.notification.infrastructure.messaging;
import java.util.List;
diff --git a/src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessageListener.java b/src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessageListener.java
index 6aa3587..adf4101 100644
--- a/src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessageListener.java
+++ b/src/main/java/flipnote/notification/infrastructure/messaging/GroupJoinRequestMessageListener.java
@@ -3,8 +3,7 @@
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
-import flipnote.notification.application.dto.message.GroupJoinRequestMessage;
-import flipnote.notification.application.service.NotificationCommandService;
+import flipnote.notification.application.NotificationCommandService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
diff --git a/src/main/java/flipnote/notification/infrastructure/config/JpaAuditingConfig.java b/src/main/java/flipnote/notification/infrastructure/persistence/JpaAuditingConfig.java
similarity index 77%
rename from src/main/java/flipnote/notification/infrastructure/config/JpaAuditingConfig.java
rename to src/main/java/flipnote/notification/infrastructure/persistence/JpaAuditingConfig.java
index d5382b4..52f84d3 100644
--- a/src/main/java/flipnote/notification/infrastructure/config/JpaAuditingConfig.java
+++ b/src/main/java/flipnote/notification/infrastructure/persistence/JpaAuditingConfig.java
@@ -1,4 +1,4 @@
-package flipnote.notification.infrastructure.config;
+package flipnote.notification.infrastructure.persistence;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
diff --git a/src/main/java/flipnote/notification/interfaces/rest/NotificationController.java b/src/main/java/flipnote/notification/interfaces/http/NotificationController.java
similarity index 68%
rename from src/main/java/flipnote/notification/interfaces/rest/NotificationController.java
rename to src/main/java/flipnote/notification/interfaces/http/NotificationController.java
index b8d0904..b2e7f07 100644
--- a/src/main/java/flipnote/notification/interfaces/rest/NotificationController.java
+++ b/src/main/java/flipnote/notification/interfaces/http/NotificationController.java
@@ -1,4 +1,4 @@
-package flipnote.notification.interfaces.rest;
+package flipnote.notification.interfaces.http;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -11,15 +11,14 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import flipnote.notification.application.dto.request.NotificationListRequest;
-import flipnote.notification.application.dto.request.TokenRegisterRequest;
-import flipnote.notification.application.dto.response.NotificationResponse;
-import flipnote.notification.application.service.FcmTokenService;
-import flipnote.notification.application.service.NotificationCommandService;
-import flipnote.notification.application.service.NotificationQueryService;
-import flipnote.notification.common.response.CursorPagingResponse;
-import flipnote.notification.common.HttpHeaders;
-import flipnote.notification.interfaces.rest.docs.NotificationControllerDocs;
+import flipnote.notification.application.FcmTokenService;
+import flipnote.notification.application.NotificationCommandService;
+import flipnote.notification.application.NotificationQueryService;
+import flipnote.notification.application.dto.result.NotificationResult;
+import flipnote.notification.interfaces.http.common.CursorPagingResponse;
+import flipnote.notification.interfaces.http.common.HttpHeaders;
+import flipnote.notification.interfaces.http.dto.request.NotificationListRequest;
+import flipnote.notification.interfaces.http.dto.request.TokenRegisterRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@@ -34,12 +33,13 @@ public class NotificationController implements NotificationControllerDocs {
@Override
@GetMapping
- public ResponseEntity> getNotifications(
+ public ResponseEntity> getNotifications(
@Valid @ModelAttribute NotificationListRequest req,
@RequestHeader(HttpHeaders.USER_ID) Long userId
) {
- CursorPagingResponse res
- = notificationQueryService.getNotifications(userId, req);
+ CursorPagingResponse res = CursorPagingResponse.from(
+ notificationQueryService.getNotifications(userId, req.toCommand())
+ );
return ResponseEntity.ok(res);
}
@@ -50,7 +50,7 @@ public ResponseEntity registerFcmToken(
@Valid @RequestBody TokenRegisterRequest req,
@RequestHeader(HttpHeaders.USER_ID) Long userId
) {
- fcmTokenService.registerFcmToken(userId, req);
+ fcmTokenService.registerFcmToken(userId, req.token());
return ResponseEntity.status(HttpStatus.CREATED).build();
}
diff --git a/src/main/java/flipnote/notification/interfaces/rest/docs/NotificationControllerDocs.java b/src/main/java/flipnote/notification/interfaces/http/NotificationControllerDocs.java
similarity index 62%
rename from src/main/java/flipnote/notification/interfaces/rest/docs/NotificationControllerDocs.java
rename to src/main/java/flipnote/notification/interfaces/http/NotificationControllerDocs.java
index 805a96b..7e2a9d6 100644
--- a/src/main/java/flipnote/notification/interfaces/rest/docs/NotificationControllerDocs.java
+++ b/src/main/java/flipnote/notification/interfaces/http/NotificationControllerDocs.java
@@ -1,11 +1,11 @@
-package flipnote.notification.interfaces.rest.docs;
+package flipnote.notification.interfaces.http;
import org.springframework.http.ResponseEntity;
-import flipnote.notification.application.dto.request.NotificationListRequest;
-import flipnote.notification.application.dto.request.TokenRegisterRequest;
-import flipnote.notification.application.dto.response.NotificationResponse;
-import flipnote.notification.common.response.CursorPagingResponse;
+import flipnote.notification.application.dto.result.NotificationResult;
+import flipnote.notification.interfaces.http.common.CursorPagingResponse;
+import flipnote.notification.interfaces.http.dto.request.NotificationListRequest;
+import flipnote.notification.interfaces.http.dto.request.TokenRegisterRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -13,7 +13,7 @@
public interface NotificationControllerDocs {
@Operation(summary = "μλ¦Ό λͺ©λ‘ μ‘°ν")
- ResponseEntity> getNotifications(
+ ResponseEntity> getNotifications(
NotificationListRequest req,
Long userId
);
diff --git a/src/main/java/flipnote/notification/common/response/ApiResponse.java b/src/main/java/flipnote/notification/interfaces/http/common/ApiResponse.java
similarity index 93%
rename from src/main/java/flipnote/notification/common/response/ApiResponse.java
rename to src/main/java/flipnote/notification/interfaces/http/common/ApiResponse.java
index c38be2f..73ec9cd 100644
--- a/src/main/java/flipnote/notification/common/response/ApiResponse.java
+++ b/src/main/java/flipnote/notification/interfaces/http/common/ApiResponse.java
@@ -1,11 +1,11 @@
-package flipnote.notification.common.response;
+package flipnote.notification.interfaces.http.common;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.validation.BindingResult;
-import flipnote.notification.common.exception.ErrorCode;
+import flipnote.notification.domain.common.ErrorCode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
diff --git a/src/main/java/flipnote/notification/common/response/ApiResponseAdvice.java b/src/main/java/flipnote/notification/interfaces/http/common/ApiResponseAdvice.java
similarity index 96%
rename from src/main/java/flipnote/notification/common/response/ApiResponseAdvice.java
rename to src/main/java/flipnote/notification/interfaces/http/common/ApiResponseAdvice.java
index 8f348bb..5fcd4c0 100644
--- a/src/main/java/flipnote/notification/common/response/ApiResponseAdvice.java
+++ b/src/main/java/flipnote/notification/interfaces/http/common/ApiResponseAdvice.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common.response;
+package flipnote.notification.interfaces.http.common;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
diff --git a/src/main/java/flipnote/notification/common/response/CursorPagingRequest.java b/src/main/java/flipnote/notification/interfaces/http/common/CursorPagingRequest.java
similarity index 95%
rename from src/main/java/flipnote/notification/common/response/CursorPagingRequest.java
rename to src/main/java/flipnote/notification/interfaces/http/common/CursorPagingRequest.java
index 92b3ecd..60b9f36 100644
--- a/src/main/java/flipnote/notification/common/response/CursorPagingRequest.java
+++ b/src/main/java/flipnote/notification/interfaces/http/common/CursorPagingRequest.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common.response;
+package flipnote.notification.interfaces.http.common;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
diff --git a/src/main/java/flipnote/notification/interfaces/http/common/CursorPagingResponse.java b/src/main/java/flipnote/notification/interfaces/http/common/CursorPagingResponse.java
new file mode 100644
index 0000000..3e79a80
--- /dev/null
+++ b/src/main/java/flipnote/notification/interfaces/http/common/CursorPagingResponse.java
@@ -0,0 +1,24 @@
+package flipnote.notification.interfaces.http.common;
+
+import java.util.List;
+import java.util.Objects;
+
+import flipnote.notification.application.dto.result.PagedResult;
+
+public record CursorPagingResponse(
+ List content,
+ boolean hasNext,
+ String nextCursor,
+ int size
+) {
+
+ public static CursorPagingResponse from(PagedResult pagedResult) {
+ String nextCursor = Objects.toString(pagedResult.nextCursor(), null);
+ return new CursorPagingResponse<>(
+ pagedResult.content(),
+ pagedResult.hasNext(),
+ nextCursor,
+ pagedResult.content().size()
+ );
+ }
+}
diff --git a/src/main/java/flipnote/notification/common/exception/GlobalExceptionHandler.java b/src/main/java/flipnote/notification/interfaces/http/common/GlobalExceptionHandler.java
similarity index 79%
rename from src/main/java/flipnote/notification/common/exception/GlobalExceptionHandler.java
rename to src/main/java/flipnote/notification/interfaces/http/common/GlobalExceptionHandler.java
index 6108fdc..2651114 100644
--- a/src/main/java/flipnote/notification/common/exception/GlobalExceptionHandler.java
+++ b/src/main/java/flipnote/notification/interfaces/http/common/GlobalExceptionHandler.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common.exception;
+package flipnote.notification.interfaces.http.common;
import java.util.List;
@@ -8,7 +8,9 @@
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
-import flipnote.notification.common.response.ApiResponse;
+import flipnote.notification.domain.common.BizException;
+import flipnote.notification.domain.common.CommonErrorCode;
+import flipnote.notification.domain.common.ErrorCode;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -48,11 +50,17 @@ public ResponseEntity>> handleValidatio
}
@ExceptionHandler(MissingServletRequestParameterException.class)
- public ResponseEntity handleMissingServletRequestParameter(
+ public ResponseEntity> handleMissingServletRequestParameter(
MissingServletRequestParameterException exception
) {
String missingParam = exception.getParameterName();
String message = String.format("νμ νλΌλ―Έν° '%s'κ° μμ΅λλ€.", missingParam);
- return ResponseEntity.badRequest().body(message);
+ return ResponseEntity
+ .badRequest()
+ .body(ApiResponse.builder()
+ .status(400)
+ .code(CommonErrorCode.INVALID_INPUT_VALUE.getCode())
+ .message(message)
+ .build());
}
}
diff --git a/src/main/java/flipnote/notification/common/HttpHeaders.java b/src/main/java/flipnote/notification/interfaces/http/common/HttpHeaders.java
similarity index 78%
rename from src/main/java/flipnote/notification/common/HttpHeaders.java
rename to src/main/java/flipnote/notification/interfaces/http/common/HttpHeaders.java
index c92d2c9..d0084de 100644
--- a/src/main/java/flipnote/notification/common/HttpHeaders.java
+++ b/src/main/java/flipnote/notification/interfaces/http/common/HttpHeaders.java
@@ -1,4 +1,4 @@
-package flipnote.notification.common;
+package flipnote.notification.interfaces.http.common;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
diff --git a/src/main/java/flipnote/notification/interfaces/http/dto/request/NotificationListRequest.java b/src/main/java/flipnote/notification/interfaces/http/dto/request/NotificationListRequest.java
new file mode 100644
index 0000000..0054d73
--- /dev/null
+++ b/src/main/java/flipnote/notification/interfaces/http/dto/request/NotificationListRequest.java
@@ -0,0 +1,21 @@
+package flipnote.notification.interfaces.http.dto.request;
+
+import flipnote.notification.application.dto.command.NotificationListCommand;
+import flipnote.notification.interfaces.http.common.CursorPagingRequest;
+import jakarta.validation.constraints.Min;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class NotificationListRequest extends CursorPagingRequest {
+
+ @Min(1)
+ private Long groupId;
+
+ private Boolean read;
+
+ public NotificationListCommand toCommand() {
+ return new NotificationListCommand(getCursorId(), groupId, read, getSize());
+ }
+}
diff --git a/src/main/java/flipnote/notification/application/dto/request/TokenRegisterRequest.java b/src/main/java/flipnote/notification/interfaces/http/dto/request/TokenRegisterRequest.java
similarity index 66%
rename from src/main/java/flipnote/notification/application/dto/request/TokenRegisterRequest.java
rename to src/main/java/flipnote/notification/interfaces/http/dto/request/TokenRegisterRequest.java
index 909fe0a..b279180 100644
--- a/src/main/java/flipnote/notification/application/dto/request/TokenRegisterRequest.java
+++ b/src/main/java/flipnote/notification/interfaces/http/dto/request/TokenRegisterRequest.java
@@ -1,4 +1,4 @@
-package flipnote.notification.application.dto.request;
+package flipnote.notification.interfaces.http.dto.request;
import jakarta.validation.constraints.NotEmpty;
diff --git a/src/main/java/flipnote/notification/interfaces/http/dto/response/.gitkeep b/src/main/java/flipnote/notification/interfaces/http/dto/response/.gitkeep
new file mode 100644
index 0000000..e69de29