diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bef5f2 --- /dev/null +++ b/README.md @@ -0,0 +1,234 @@ +# πŸ“’ FlipNote β€” Reaction Service + +**FlipNote μ„œλΉ„μŠ€μ˜ λ°˜μ‘(μ’‹μ•„μš”Β·λΆλ§ˆν¬) 도메인 λ°±μ—”λ“œ λ ˆν¬μ§€ν† λ¦¬μž…λ‹ˆλ‹€.** + +![Spring Boot](https://img.shields.io/badge/Spring_Boot_4-6DB33F?logo=springboot&logoColor=white) +![Java](https://img.shields.io/badge/Java_21-007396?logo=openjdk&logoColor=white) +![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white) +![RabbitMQ](https://img.shields.io/badge/RabbitMQ-FF6600?logo=rabbitmq&logoColor=white) +![Deploy](https://img.shields.io/badge/Deploy-GHCR%20%2B%20Docker-2496ED?logo=docker&logoColor=white) + +--- + +## πŸ“‘ λͺ©μ°¨ + +- [μ‹œμž‘ν•˜κΈ°](#μ‹œμž‘ν•˜κΈ°) +- [ν™˜κ²½ λ³€μˆ˜](#ν™˜κ²½-λ³€μˆ˜) +- [μ‹€ν–‰ 및 배포](#μ‹€ν–‰-및-배포) +- [ν”„λ‘œμ νŠΈ ꡬ쑰](#ν”„λ‘œμ νŠΈ-ꡬ쑰) + +--- + + + +## πŸš€ μ‹œμž‘ν•˜κΈ° + +### 사전 μš”κ΅¬μ‚¬ν•­ + +- **Java** 21 이상 +- **Gradle** 8 이상 +- **MySQL** 8 이상 +- **RabbitMQ** 3 이상 + +### μ„€μΉ˜ + +```bash +# μ˜μ‘΄μ„± μ„€μΉ˜ 및 λΉŒλ“œ +./gradlew build -x test +``` + +--- + + + +## πŸ” ν™˜κ²½ λ³€μˆ˜ + +`application.yml`μ—μ„œ μ°Έμ‘°ν•˜λŠ” ν™˜κ²½ λ³€μˆ˜ λͺ©λ‘μž…λ‹ˆλ‹€. 둜컬 μ‹€ν–‰ μ‹œ `.env` λ˜λŠ” IDE μ‹€ν–‰ ꡬ성에 μ•„λž˜ λ³€μˆ˜λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. + +```text +# ─── Database ─────────────────────────────────────────── +DB_URL=jdbc:mysql://localhost:3306/flipnote_reaction +DB_USERNAME= +DB_PASSWORD= +# create | create-drop | update | validate | none +DDL_AUTO=validate + +# ─── RabbitMQ ─────────────────────────────────────────── +RABBITMQ_HOST=localhost +RABBITMQ_PORT=5672 +RABBITMQ_USERNAME=guest +RABBITMQ_PASSWORD=guest + +# ─── gRPC ─────────────────────────────────────────────── +# Reaction μ„œλΉ„μŠ€ gRPC μ„œλ²„ 포트 (κΈ°λ³Έκ°’ 9093) +GRPC_SERVER_PORT=9093 +# CardSet μ„œλΉ„μŠ€ gRPC ν΄λΌμ΄μ–ΈνŠΈ μ£Όμ†Œ (κΈ°λ³Έκ°’ static://cardset-service:9095) +GRPC_CARDSET_ADDRESS=static://localhost:9095 +``` + +> **⚠️ 주의**: ν™˜κ²½ λ³€μˆ˜ νŒŒμΌμ€ μ ˆλŒ€ git에 μ»€λ°‹ν•˜μ§€ λ§ˆμ„Έμš”. `.gitignore`에 ν¬ν•¨λ˜μ–΄ μžˆλŠ”μ§€ λ°˜λ“œμ‹œ ν™•μΈν•˜μ„Έμš”. + +--- + + + +## πŸ–₯️ μ‹€ν–‰ 및 배포 + +### 둜컬 개발 μ„œλ²„ μ‹€ν–‰ + +```bash +./gradlew bootRun +``` + +기본적으둜 `http://localhost:8083`μ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€. +gRPC μ„œλ²„λŠ” 기본적으둜 `9093` ν¬νŠΈμ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€. + +### ν”„λ‘œλ•μ…˜ λΉŒλ“œ + +```bash +./gradlew bootJar +``` + +`build/libs/reaction-0.0.1-SNAPSHOT.jar` 파일이 μƒμ„±λ©λ‹ˆλ‹€. + +### ν…ŒμŠ€νŠΈ μ‹€ν–‰ + +```bash +./gradlew test +``` + +### Docker 이미지 λΉŒλ“œ 및 μ‹€ν–‰ + +```bash +# 이미지 λΉŒλ“œ +docker build -t flipnote-reaction . + +# μ»¨ν…Œμ΄λ„ˆ μ‹€ν–‰ +docker run -p 8083:8083 -p 9093:9093 \ + -e DB_URL=... \ + -e RABBITMQ_HOST=... \ + flipnote-reaction +``` + +### 배포 (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-reaction` 이미지 Push + +> 배포에 ν•„μš”ν•œ μ‹œν¬λ¦Ώ(`ORG_PAT`)은 GitHub Repository β†’ Settings β†’ Secrets and variables β†’ Actions에 등둝해야 ν•©λ‹ˆλ‹€. + +--- + + + +## πŸ“ ν”„λ‘œμ νŠΈ ꡬ쑰 + +- κ°„λž΅ν™” 버전 + + ```text + src/main/java/flipnote/reaction/ + β”œβ”€β”€ domain/ # 도메인 λ ˆμ΄μ–΄ (μ—”ν‹°ν‹°, λ ˆν¬μ§€ν† λ¦¬, 도메인 이벀트, μ—λŸ¬μ½”λ“œ) + β”œβ”€β”€ application/ # μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆμ΄μ–΄ (μ„œλΉ„μŠ€, 쑰회 μ»΄ν¬λ„ŒνŠΈ, Result 객체) + β”œβ”€β”€ infrastructure/ # 인프라 λ ˆμ΄μ–΄ (μ˜μ†μ„±, λ©”μ‹œμ§•, gRPC ν΄λΌμ΄μ–ΈνŠΈ) + └── interfaces/ # μΈν„°νŽ˜μ΄μŠ€ λ ˆμ΄μ–΄ (HTTP, gRPC μ§„μž…μ ) + ``` + +```text +FlipNote-Reaction/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ main/ +β”‚ β”‚ β”œβ”€β”€ java/flipnote/reaction/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ReactionApplication.java +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”œβ”€β”€ domain/ # 도메인 λ ˆμ΄μ–΄ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ common/ # 도메인 곡톡 +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ErrorCode.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BizException.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ CommonErrorCode.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── BaseEntity.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ bookmark/ # 뢁마크 도메인 +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Bookmark.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkTargetType.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkRepository.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkErrorCode.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── event/ # 뢁마크 도메인 이벀트 +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkAddedEvent.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── BookmarkRemovedEvent.java +β”‚ β”‚ β”‚ β”‚ └── like/ # μ’‹μ•„μš” 도메인 +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Like.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeTargetType.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeRepository.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeErrorCode.java +β”‚ β”‚ β”‚ β”‚ └── event/ # μ’‹μ•„μš” 도메인 이벀트 +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeAddedEvent.java +β”‚ β”‚ β”‚ β”‚ └── LikeRemovedEvent.java +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”œβ”€β”€ application/ # μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆμ΄μ–΄ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ common/ +β”‚ β”‚ β”‚ β”‚ β”‚ └── CardSetSummaryResult.java # 곡유 Result 객체 +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ bookmark/ +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkService.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkReader.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── BookmarkResult.java # μ„œλΉ„μŠ€ λ°˜ν™˜ Result +β”‚ β”‚ β”‚ β”‚ └── like/ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeService.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeReader.java +β”‚ β”‚ β”‚ β”‚ └── LikeResult.java # μ„œλΉ„μŠ€ λ°˜ν™˜ Result +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”œβ”€β”€ infrastructure/ # 인프라 λ ˆμ΄μ–΄ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ grpc/ # gRPC ν΄λΌμ΄μ–ΈνŠΈ +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ GrpcConfig.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── CardSetGrpcClient.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ messaging/ # λ©”μ‹œμ§€ λ°œν–‰ + μ„€μ • +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ RabbitMqConfig.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ReactionMessage.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ReactionEventPublisher.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── ReactionEventListener.java # @TransactionalEventListener +β”‚ β”‚ β”‚ β”‚ └── persistence/ # μ˜μ†μ„± μ–΄λŒ‘ν„° + JPA μ„€μ • +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AuditingConfig.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkRepositoryAdapter.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ SpringDataBookmarkRepository.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeRepositoryAdapter.java +β”‚ β”‚ β”‚ β”‚ └── SpringDataLikeRepository.java +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ └── interfaces/ # μΈν„°νŽ˜μ΄μŠ€ λ ˆμ΄μ–΄ +β”‚ β”‚ β”‚ β”œβ”€β”€ http/ # HTTP μ§„μž…μ  +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkController.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeController.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ common/ # 곡톡 응닡, μ˜ˆμ™Έ 처리 +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ApiResponse.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ ApiResponseAdvice.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ GlobalExceptionHandler.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ HeaderConstants.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ IdResponse.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ PagingRequest.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── PagingResponse.java +β”‚ β”‚ β”‚ β”‚ └── dto/request/ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkSearchRequest.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BookmarkTargetTypeRequest.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ LikeSearchRequest.java +β”‚ β”‚ β”‚ β”‚ └── LikeTargetTypeRequest.java +β”‚ β”‚ β”‚ └── grpc/ # gRPC μ„œλ²„ μ§„μž…μ  +β”‚ β”‚ β”‚ β”œβ”€β”€ ReactionGrpcService.java +β”‚ β”‚ β”‚ └── GrpcExceptionHandlerImpl.java +β”‚ β”‚ β”‚ +β”‚ β”‚ └── resources/ +β”‚ β”‚ └── application.yml +β”‚ β”‚ +β”‚ └── test/ +β”‚ └── java/flipnote/reaction/ +β”‚ +β”œβ”€β”€ Dockerfile +β”œβ”€β”€ build.gradle.kts +└── settings.gradle.kts +``` diff --git a/build.gradle.kts b/build.gradle.kts index 3a4f7ed..cdedd82 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,9 +2,9 @@ import com.google.protobuf.gradle.id plugins { java - id("org.springframework.boot") version "3.5.10" + id("org.springframework.boot") version "4.0.2" id("io.spring.dependency-management") version "1.1.7" - id("com.google.protobuf") version "0.9.4" + id("com.google.protobuf") version "0.9.5" } group = "flipnote" @@ -17,18 +17,11 @@ java { } } -configurations { - compileOnly { - extendsFrom(configurations.annotationProcessor.get()) - } -} - repositories { mavenCentral() } -val grpcVersion = "1.68.0" -val protocVersion = "4.28.2" +extra["springGrpcVersion"] = "1.0.2" dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") @@ -38,33 +31,39 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-amqp") // gRPC - implementation("io.grpc:grpc-netty-shaded:$grpcVersion") - implementation("io.grpc:grpc-protobuf:$grpcVersion") - implementation("io.grpc:grpc-stub:$grpcVersion") - implementation("com.google.protobuf:protobuf-java:$protocVersion") - implementation("javax.annotation:javax.annotation-api:1.3.2") + implementation("io.grpc:grpc-services") + implementation("org.springframework.grpc:spring-grpc-spring-boot-starter") compileOnly("org.projectlombok:lombok") runtimeOnly("com.mysql:mysql-connector-j") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.grpc:spring-grpc-test") testRuntimeOnly("com.h2database:h2") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } +dependencyManagement { + imports { + mavenBom("org.springframework.grpc:spring-grpc-dependencies:${property("springGrpcVersion")}") + } +} + protobuf { protoc { - artifact = "com.google.protobuf:protoc:$protocVersion" + artifact = "com.google.protobuf:protoc" } plugins { id("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" + artifact = "io.grpc:protoc-gen-grpc-java" } } generateProtoTasks { - all().forEach { task -> - task.plugins { - id("grpc") + all().forEach { + it.plugins { + id("grpc") { + option("@generated=omit") + } } } } diff --git a/src/main/java/flipnote/reaction/bookmark/service/BookmarkReader.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkReader.java similarity index 56% rename from src/main/java/flipnote/reaction/bookmark/service/BookmarkReader.java rename to src/main/java/flipnote/reaction/application/bookmark/BookmarkReader.java index aa74c36..6c401ac 100644 --- a/src/main/java/flipnote/reaction/bookmark/service/BookmarkReader.java +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkReader.java @@ -1,17 +1,16 @@ -package flipnote.reaction.bookmark.service; +package flipnote.reaction.application.bookmark; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import org.springframework.stereotype.Component; -import flipnote.reaction.bookmark.entity.Bookmark; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.exception.BookmarkErrorCode; -import flipnote.reaction.bookmark.repository.BookmarkRepository; -import flipnote.reaction.common.exception.BizException; +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkErrorCode; +import flipnote.reaction.domain.bookmark.BookmarkRepository; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.common.BizException; import lombok.RequiredArgsConstructor; @Component @@ -29,13 +28,11 @@ public boolean isBookmarked(BookmarkTargetType targetType, Long targetId, Long u return bookmarkRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); } - public Map areBookmarked(BookmarkTargetType targetType, List targetIds, Long userId) { - Set bookmarkedIds = bookmarkRepository.findByTargetTypeAndTargetIdInAndUserId(targetType, targetIds, userId) - .stream() - .map(Bookmark::getTargetId) - .collect(Collectors.toSet()); - + public Map areBookmarked(BookmarkTargetType targetType, List targetIds, long userId) { return targetIds.stream() - .collect(Collectors.toMap(id -> id, bookmarkedIds::contains)); + .collect(Collectors.toMap( + targetId -> targetId, + targetId -> bookmarkRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId) + )); } } diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java new file mode 100644 index 0000000..bbd9551 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkResult.java @@ -0,0 +1,22 @@ +package flipnote.reaction.application.bookmark; + +import java.time.LocalDateTime; + +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.bookmark.Bookmark; + +public record BookmarkResult( + String targetType, + Long targetId, + LocalDateTime bookmarkedAt, + CardSetSummaryResult cardSet +) { + public static BookmarkResult from(Bookmark bookmark, CardSetSummaryResult cardSet) { + return new BookmarkResult( + bookmark.getTargetType().name(), + bookmark.getTargetId(), + bookmark.getCreatedAt(), + cardSet + ); + } +} diff --git a/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java new file mode 100644 index 0000000..dfa0bf4 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/bookmark/BookmarkService.java @@ -0,0 +1,98 @@ +package flipnote.reaction.application.bookmark; + +import java.util.List; +import java.util.Map; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkErrorCode; +import flipnote.reaction.domain.bookmark.BookmarkRepository; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.bookmark.event.BookmarkAddedEvent; +import flipnote.reaction.domain.bookmark.event.BookmarkRemovedEvent; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; +import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; +import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class BookmarkService { + + private final BookmarkRepository bookmarkRepository; + private final BookmarkReader bookmarkReader; + private final ApplicationEventPublisher eventPublisher; + private final CardSetGrpcClient cardSetGrpcClient; + + @Transactional(noRollbackFor = DataIntegrityViolationException.class) + public Long addBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { + if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { + throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); + } + + if (bookmarkReader.isBookmarked(targetType, targetId, userId)) { + throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); + } + + Bookmark bookmark = Bookmark.builder() + .targetType(targetType) + .targetId(targetId) + .userId(userId) + .build(); + + try { + bookmarkRepository.save(bookmark); + } catch (DataIntegrityViolationException e) { + throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); + } + + eventPublisher.publishEvent(new BookmarkAddedEvent(targetType.name(), targetId, userId)); + + return bookmark.getId(); + } + + @Transactional + public void removeBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { + Bookmark bookmark = bookmarkReader.findByTargetAndUserId(targetType, targetId, userId); + bookmarkRepository.delete(bookmark); + + eventPublisher.publishEvent(new BookmarkRemovedEvent(targetType.name(), targetId, userId)); + } + + public Page getBookmarks(BookmarkTargetType targetType, Long userId, + BookmarkSearchRequest request) { + Page bookmarkPage = bookmarkRepository.findByTargetTypeAndUserId( + targetType, userId, request.getPageRequest() + ); + + List targetIds = bookmarkPage.getContent().stream() + .map(Bookmark::getTargetId) + .toList(); + + Map cardSetMap = fetchCardSets(targetIds, userId); + + return bookmarkPage.map(b -> BookmarkResult.from(b, cardSetMap.get(b.getTargetId()))); + } + + private Map fetchCardSets(List targetIds, Long userId) { + if (targetIds.isEmpty()) { + return Map.of(); + } + try { + return cardSetGrpcClient.getCardSetsByIds(targetIds, userId); + } catch (BizException e) { + log.warn("CardSet 쑰회 μ‹€νŒ¨ β€” cardSet 없이 λ°˜ν™˜ν•©λ‹ˆλ‹€: {}", e.getMessage()); + return Map.of(); + } + } +} diff --git a/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java b/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java new file mode 100644 index 0000000..42ff7d9 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/common/CardSetSummaryResult.java @@ -0,0 +1,13 @@ +package flipnote.reaction.application.common; + +public record CardSetSummaryResult( + Long id, + String name, + Long groupId, + String visibility, + String category, + String hashtag, + Long imageRefId, + Long cardCount +) { +} diff --git a/src/main/java/flipnote/reaction/like/service/LikeReader.java b/src/main/java/flipnote/reaction/application/like/LikeReader.java similarity index 57% rename from src/main/java/flipnote/reaction/like/service/LikeReader.java rename to src/main/java/flipnote/reaction/application/like/LikeReader.java index fc8c31b..e0501db 100644 --- a/src/main/java/flipnote/reaction/like/service/LikeReader.java +++ b/src/main/java/flipnote/reaction/application/like/LikeReader.java @@ -1,17 +1,16 @@ -package flipnote.reaction.like.service; +package flipnote.reaction.application.like; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import org.springframework.stereotype.Component; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.like.entity.Like; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.exception.LikeErrorCode; -import flipnote.reaction.like.repository.LikeRepository; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeErrorCode; +import flipnote.reaction.domain.like.LikeRepository; +import flipnote.reaction.domain.like.LikeTargetType; import lombok.RequiredArgsConstructor; @Component @@ -29,13 +28,11 @@ public boolean isLiked(LikeTargetType targetType, Long targetId, Long userId) { return likeRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); } - public Map areLiked(LikeTargetType targetType, List targetIds, Long userId) { - Set likedIds = likeRepository.findByTargetTypeAndTargetIdInAndUserId(targetType, targetIds, userId) - .stream() - .map(Like::getTargetId) - .collect(Collectors.toSet()); - + public Map areLiked(LikeTargetType targetType, List targetIds, long userId) { return targetIds.stream() - .collect(Collectors.toMap(id -> id, likedIds::contains)); + .collect(Collectors.toMap( + targetId -> targetId, + targetId -> likeRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId) + )); } } diff --git a/src/main/java/flipnote/reaction/application/like/LikeResult.java b/src/main/java/flipnote/reaction/application/like/LikeResult.java new file mode 100644 index 0000000..d0fc708 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/like/LikeResult.java @@ -0,0 +1,22 @@ +package flipnote.reaction.application.like; + +import java.time.LocalDateTime; + +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.like.Like; + +public record LikeResult( + String targetType, + Long targetId, + LocalDateTime likedAt, + CardSetSummaryResult cardSet +) { + public static LikeResult from(Like like, CardSetSummaryResult cardSet) { + return new LikeResult( + like.getTargetType().name(), + like.getTargetId(), + like.getCreatedAt(), + cardSet + ); + } +} diff --git a/src/main/java/flipnote/reaction/application/like/LikeService.java b/src/main/java/flipnote/reaction/application/like/LikeService.java new file mode 100644 index 0000000..df94047 --- /dev/null +++ b/src/main/java/flipnote/reaction/application/like/LikeService.java @@ -0,0 +1,97 @@ +package flipnote.reaction.application.like; + +import java.util.List; +import java.util.Map; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeErrorCode; +import flipnote.reaction.domain.like.LikeRepository; +import flipnote.reaction.domain.like.LikeTargetType; +import flipnote.reaction.domain.like.event.LikeAddedEvent; +import flipnote.reaction.domain.like.event.LikeRemovedEvent; +import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; +import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class LikeService { + + private final LikeRepository likeRepository; + private final LikeReader likeReader; + private final ApplicationEventPublisher eventPublisher; + private final CardSetGrpcClient cardSetGrpcClient; + + @Transactional(noRollbackFor = DataIntegrityViolationException.class) + public Long addLike(LikeTargetType targetType, Long targetId, Long userId) { + if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { + throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); + } + + if (likeReader.isLiked(targetType, targetId, userId)) { + throw new BizException(LikeErrorCode.ALREADY_LIKED); + } + + Like like = Like.builder() + .targetType(targetType) + .targetId(targetId) + .userId(userId) + .build(); + + try { + likeRepository.save(like); + } catch (DataIntegrityViolationException e) { + throw new BizException(LikeErrorCode.ALREADY_LIKED); + } + + eventPublisher.publishEvent(new LikeAddedEvent(targetType.name(), targetId, userId)); + + return like.getId(); + } + + @Transactional + public void removeLike(LikeTargetType targetType, Long targetId, Long userId) { + Like like = likeReader.findByTargetAndUserId(targetType, targetId, userId); + likeRepository.delete(like); + + eventPublisher.publishEvent(new LikeRemovedEvent(targetType.name(), targetId, userId)); + } + + public Page getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { + Page likePage = likeRepository.findByTargetTypeAndUserId( + targetType, userId, request.getPageRequest() + ); + + List targetIds = likePage.getContent().stream() + .map(Like::getTargetId) + .toList(); + + Map cardSetMap = fetchCardSets(targetIds, userId); + + return likePage.map(l -> LikeResult.from(l, cardSetMap.get(l.getTargetId()))); + } + + private Map fetchCardSets(List targetIds, Long userId) { + if (targetIds.isEmpty()) { + return Map.of(); + } + try { + return cardSetGrpcClient.getCardSetsByIds(targetIds, userId); + } catch (BizException e) { + log.warn("CardSet 쑰회 μ‹€νŒ¨ β€” cardSet 없이 λ°˜ν™˜ν•©λ‹ˆλ‹€: {}", e.getMessage()); + return Map.of(); + } + } +} diff --git a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java b/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java deleted file mode 100644 index 5034076..0000000 --- a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package flipnote.reaction.bookmark.model.response; - -import java.time.LocalDateTime; - -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.bookmark.entity.Bookmark; - -public record BookmarkResponse( - BookmarkTargetResponse target, - LocalDateTime bookmarkedAt -) { - public static BookmarkResponse from(Bookmark bookmark, CardSetSummary summary) { - return new BookmarkResponse( - new BookmarkTargetResponse( - bookmark.getTargetType().name(), - bookmark.getTargetId(), - summary != null ? summary.getGroupId() : null, - summary != null ? summary.getName() : null - ), - bookmark.getCreatedAt() - ); - } -} diff --git a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java b/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java deleted file mode 100644 index b62838f..0000000 --- a/src/main/java/flipnote/reaction/bookmark/model/response/BookmarkTargetResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package flipnote.reaction.bookmark.model.response; - -public record BookmarkTargetResponse( - String type, - Long id, - Long groupId, - String name -) { -} diff --git a/src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java b/src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java deleted file mode 100644 index ca7f7f7..0000000 --- a/src/main/java/flipnote/reaction/bookmark/service/BookmarkService.java +++ /dev/null @@ -1,98 +0,0 @@ -package flipnote.reaction.bookmark.service; - -import java.util.List; -import java.util.Map; - -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import cardset.Cardset; -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.bookmark.entity.Bookmark; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.exception.BookmarkErrorCode; -import flipnote.reaction.bookmark.model.request.BookmarkSearchRequest; -import flipnote.reaction.bookmark.model.response.BookmarkResponse; -import flipnote.reaction.bookmark.repository.BookmarkRepository; -import flipnote.reaction.common.config.RabbitMqConfig; -import flipnote.reaction.common.event.ReactionEventPublisher; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.common.exception.CommonErrorCode; -import flipnote.reaction.common.grpc.CardSetGrpcClient; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class BookmarkService { - - private final BookmarkRepository bookmarkRepository; - private final BookmarkReader bookmarkReader; - private final ReactionEventPublisher eventPublisher; - private final CardSetGrpcClient cardSetGrpcClient; - - @Transactional - public IdResponse addBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { - if (targetType == BookmarkTargetType.CARD_SET) { - if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { - throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); - } - } - - if (bookmarkReader.isBookmarked(targetType, targetId, userId)) { - throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); - } - - Bookmark bookmark = Bookmark.builder() - .targetType(targetType) - .targetId(targetId) - .userId(userId) - .build(); - - try { - bookmarkRepository.save(bookmark); - } catch (DataIntegrityViolationException e) { - throw new BizException(BookmarkErrorCode.ALREADY_BOOKMARKED); - } - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, "BOOKMARK_ADDED", - targetType.name(), targetId, userId); - - return IdResponse.from(bookmark.getId()); - } - - @Transactional - public void removeBookmark(BookmarkTargetType targetType, Long targetId, Long userId) { - Bookmark bookmark = bookmarkReader.findByTargetAndUserId(targetType, targetId, userId); - bookmarkRepository.delete(bookmark); - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, "BOOKMARK_REMOVED", - targetType.name(), targetId, userId); - } - - public PagingResponse getBookmarks(BookmarkTargetType targetType, Long userId, - BookmarkSearchRequest request) { - Page bookmarkPage = bookmarkRepository.findByTargetTypeAndUserId( - targetType, userId, request.getPageRequest() - ); - - List targetIds = bookmarkPage.getContent().stream() - .map(Bookmark::getTargetId) - .toList(); - - Map summaryMap = targetIds.isEmpty() - ? Map.of() - : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); - - Page responsePage = bookmarkPage.map( - bookmark -> BookmarkResponse.from(bookmark, summaryMap.get(bookmark.getTargetId())) - ); - return PagingResponse.from(responsePage); - } -} diff --git a/src/main/java/flipnote/reaction/common/event/ReactionEventPublisher.java b/src/main/java/flipnote/reaction/common/event/ReactionEventPublisher.java deleted file mode 100644 index 92729c0..0000000 --- a/src/main/java/flipnote/reaction/common/event/ReactionEventPublisher.java +++ /dev/null @@ -1,31 +0,0 @@ -package flipnote.reaction.common.event; - -import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.stereotype.Component; - -import flipnote.reaction.common.config.RabbitMqConfig; -import flipnote.reaction.common.model.event.ReactionMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Component -@RequiredArgsConstructor -public class ReactionEventPublisher { - - private final RabbitTemplate rabbitTemplate; - - public void publish(String routingKey, String eventType, - String targetType, Long targetId, Long userId) { - try { - rabbitTemplate.convertAndSend( - RabbitMqConfig.EXCHANGE, - routingKey, - new ReactionMessage(eventType, targetType, targetId, userId) - ); - } catch (Exception e) { - log.error("이벀트 λ°œν–‰ μ‹€νŒ¨: eventType={}, targetType={}, targetId={}, userId={}", - eventType, targetType, targetId, userId, e); - } - } -} diff --git a/src/main/java/flipnote/reaction/common/grpc/GrpcConfig.java b/src/main/java/flipnote/reaction/common/grpc/GrpcConfig.java deleted file mode 100644 index d131067..0000000 --- a/src/main/java/flipnote/reaction/common/grpc/GrpcConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package flipnote.reaction.common.grpc; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; - -@Configuration -public class GrpcConfig { - - @Bean - public ManagedChannel cardSetChannel( - @Value("${grpc.cardset.host}") String host, - @Value("${grpc.cardset.port}") int port - ) { - return ManagedChannelBuilder.forAddress(host, port) - .usePlaintext() - .build(); - } -} diff --git a/src/main/java/flipnote/reaction/common/grpc/GrpcServerConfig.java b/src/main/java/flipnote/reaction/common/grpc/GrpcServerConfig.java deleted file mode 100644 index 9fd49cc..0000000 --- a/src/main/java/flipnote/reaction/common/grpc/GrpcServerConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package flipnote.reaction.common.grpc; - -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.SmartLifecycle; -import org.springframework.stereotype.Component; - -import io.grpc.Server; -import io.grpc.ServerBuilder; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Component -public class GrpcServerConfig implements SmartLifecycle { - - private final Server server; - private boolean running; - - public GrpcServerConfig( - @Value("${grpc.server.port}") int port, - ReactionGrpcService reactionGrpcService - ) { - this.server = ServerBuilder.forPort(port) - .addService(reactionGrpcService) - .build(); - } - - @Override - public void start() { - try { - server.start(); - running = true; - log.info("gRPC server started on port {}", server.getPort()); - } catch (IOException e) { - throw new RuntimeException("Failed to start gRPC server", e); - } - } - - @Override - public void stop() { - if (server != null) { - server.shutdown(); - running = false; - log.info("gRPC server stopped"); - } - } - - @Override - public boolean isRunning() { - return running; - } -} diff --git a/src/main/java/flipnote/reaction/bookmark/entity/Bookmark.java b/src/main/java/flipnote/reaction/domain/bookmark/Bookmark.java similarity index 93% rename from src/main/java/flipnote/reaction/bookmark/entity/Bookmark.java rename to src/main/java/flipnote/reaction/domain/bookmark/Bookmark.java index cb6f125..eee6a1e 100644 --- a/src/main/java/flipnote/reaction/bookmark/entity/Bookmark.java +++ b/src/main/java/flipnote/reaction/domain/bookmark/Bookmark.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.entity; +package flipnote.reaction.domain.bookmark; -import flipnote.reaction.common.entity.BaseEntity; +import flipnote.reaction.domain.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/flipnote/reaction/bookmark/exception/BookmarkErrorCode.java b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkErrorCode.java similarity index 83% rename from src/main/java/flipnote/reaction/bookmark/exception/BookmarkErrorCode.java rename to src/main/java/flipnote/reaction/domain/bookmark/BookmarkErrorCode.java index e9eb2e3..f9a0d81 100644 --- a/src/main/java/flipnote/reaction/bookmark/exception/BookmarkErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkErrorCode.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.exception; +package flipnote.reaction.domain.bookmark; -import flipnote.reaction.common.exception.ErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java new file mode 100644 index 0000000..ee3769d --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkRepository.java @@ -0,0 +1,19 @@ +package flipnote.reaction.domain.bookmark; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BookmarkRepository { + + boolean existsByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); + + Optional findByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); + + Page findByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable); + + Bookmark save(Bookmark bookmark); + + void delete(Bookmark bookmark); +} diff --git a/src/main/java/flipnote/reaction/bookmark/entity/BookmarkTargetType.java b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkTargetType.java similarity index 51% rename from src/main/java/flipnote/reaction/bookmark/entity/BookmarkTargetType.java rename to src/main/java/flipnote/reaction/domain/bookmark/BookmarkTargetType.java index 1dfecab..60b01f9 100644 --- a/src/main/java/flipnote/reaction/bookmark/entity/BookmarkTargetType.java +++ b/src/main/java/flipnote/reaction/domain/bookmark/BookmarkTargetType.java @@ -1,4 +1,4 @@ -package flipnote.reaction.bookmark.entity; +package flipnote.reaction.domain.bookmark; public enum BookmarkTargetType { CARD_SET diff --git a/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java new file mode 100644 index 0000000..87421e2 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkAddedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.bookmark.event; + +public record BookmarkAddedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java new file mode 100644 index 0000000..34915b4 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/bookmark/event/BookmarkRemovedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.bookmark.event; + +public record BookmarkRemovedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/common/entity/BaseEntity.java b/src/main/java/flipnote/reaction/domain/common/BaseEntity.java similarity index 93% rename from src/main/java/flipnote/reaction/common/entity/BaseEntity.java rename to src/main/java/flipnote/reaction/domain/common/BaseEntity.java index 094ff68..07b0bc6 100644 --- a/src/main/java/flipnote/reaction/common/entity/BaseEntity.java +++ b/src/main/java/flipnote/reaction/domain/common/BaseEntity.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.entity; +package flipnote.reaction.domain.common; import java.time.LocalDateTime; diff --git a/src/main/java/flipnote/reaction/common/exception/BizException.java b/src/main/java/flipnote/reaction/domain/common/BizException.java similarity index 79% rename from src/main/java/flipnote/reaction/common/exception/BizException.java rename to src/main/java/flipnote/reaction/domain/common/BizException.java index db85b00..7f34bc2 100644 --- a/src/main/java/flipnote/reaction/common/exception/BizException.java +++ b/src/main/java/flipnote/reaction/domain/common/BizException.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.domain.common; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/flipnote/reaction/common/exception/CommonErrorCode.java b/src/main/java/flipnote/reaction/domain/common/CommonErrorCode.java similarity index 92% rename from src/main/java/flipnote/reaction/common/exception/CommonErrorCode.java rename to src/main/java/flipnote/reaction/domain/common/CommonErrorCode.java index 349b6f4..c3e21ff 100644 --- a/src/main/java/flipnote/reaction/common/exception/CommonErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/common/CommonErrorCode.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.domain.common; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/common/exception/ErrorCode.java b/src/main/java/flipnote/reaction/domain/common/ErrorCode.java similarity index 67% rename from src/main/java/flipnote/reaction/common/exception/ErrorCode.java rename to src/main/java/flipnote/reaction/domain/common/ErrorCode.java index 5841bc8..5bde461 100644 --- a/src/main/java/flipnote/reaction/common/exception/ErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/common/ErrorCode.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.domain.common; public interface ErrorCode { int getStatus(); diff --git a/src/main/java/flipnote/reaction/like/entity/Like.java b/src/main/java/flipnote/reaction/domain/like/Like.java similarity index 93% rename from src/main/java/flipnote/reaction/like/entity/Like.java rename to src/main/java/flipnote/reaction/domain/like/Like.java index 446662c..3863da9 100644 --- a/src/main/java/flipnote/reaction/like/entity/Like.java +++ b/src/main/java/flipnote/reaction/domain/like/Like.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.entity; +package flipnote.reaction.domain.like; -import flipnote.reaction.common.entity.BaseEntity; +import flipnote.reaction.domain.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/flipnote/reaction/like/exception/LikeErrorCode.java b/src/main/java/flipnote/reaction/domain/like/LikeErrorCode.java similarity index 83% rename from src/main/java/flipnote/reaction/like/exception/LikeErrorCode.java rename to src/main/java/flipnote/reaction/domain/like/LikeErrorCode.java index d69903b..f7733f3 100644 --- a/src/main/java/flipnote/reaction/like/exception/LikeErrorCode.java +++ b/src/main/java/flipnote/reaction/domain/like/LikeErrorCode.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.exception; +package flipnote.reaction.domain.like; -import flipnote.reaction.common.exception.ErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/flipnote/reaction/domain/like/LikeRepository.java b/src/main/java/flipnote/reaction/domain/like/LikeRepository.java new file mode 100644 index 0000000..8d32323 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/like/LikeRepository.java @@ -0,0 +1,19 @@ +package flipnote.reaction.domain.like; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface LikeRepository { + + boolean existsByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); + + Optional findByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); + + Page findByTargetTypeAndUserId(LikeTargetType targetType, Long userId, Pageable pageable); + + Like save(Like like); + + void delete(Like like); +} diff --git a/src/main/java/flipnote/reaction/like/entity/LikeTargetType.java b/src/main/java/flipnote/reaction/domain/like/LikeTargetType.java similarity index 51% rename from src/main/java/flipnote/reaction/like/entity/LikeTargetType.java rename to src/main/java/flipnote/reaction/domain/like/LikeTargetType.java index b23ce70..45e81ba 100644 --- a/src/main/java/flipnote/reaction/like/entity/LikeTargetType.java +++ b/src/main/java/flipnote/reaction/domain/like/LikeTargetType.java @@ -1,4 +1,4 @@ -package flipnote.reaction.like.entity; +package flipnote.reaction.domain.like; public enum LikeTargetType { CARD_SET diff --git a/src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java b/src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java new file mode 100644 index 0000000..01e0876 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/like/event/LikeAddedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.like.event; + +public record LikeAddedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java b/src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java new file mode 100644 index 0000000..2ac8e54 --- /dev/null +++ b/src/main/java/flipnote/reaction/domain/like/event/LikeRemovedEvent.java @@ -0,0 +1,8 @@ +package flipnote.reaction.domain.like.event; + +public record LikeRemovedEvent( + String targetType, + Long targetId, + Long userId +) { +} diff --git a/src/main/java/flipnote/reaction/common/grpc/CardSetGrpcClient.java b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java similarity index 67% rename from src/main/java/flipnote/reaction/common/grpc/CardSetGrpcClient.java rename to src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java index ab5104c..3749a79 100644 --- a/src/main/java/flipnote/reaction/common/grpc/CardSetGrpcClient.java +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/CardSetGrpcClient.java @@ -1,21 +1,19 @@ -package flipnote.reaction.common.grpc; +package flipnote.reaction.infrastructure.grpc; import java.util.List; import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.stereotype.Component; -import cardset.Cardset.CardSetSummary; import cardset.Cardset.GetCardSetsByIdsRequest; import cardset.Cardset.GetCardSetsByIdsResponse; import cardset.Cardset.IsCardSetViewableRequest; import cardset.Cardset.IsCardSetViewableResponse; import cardset.CardsetServiceGrpc; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.common.exception.CommonErrorCode; -import io.grpc.ManagedChannel; +import flipnote.reaction.application.common.CardSetSummaryResult; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; import io.grpc.StatusRuntimeException; import lombok.extern.slf4j.Slf4j; @@ -25,15 +23,15 @@ public class CardSetGrpcClient { private final CardsetServiceGrpc.CardsetServiceBlockingStub stub; - public CardSetGrpcClient(ManagedChannel cardSetChannel) { - this.stub = CardsetServiceGrpc.newBlockingStub(cardSetChannel); + public CardSetGrpcClient(CardsetServiceGrpc.CardsetServiceBlockingStub cardSetStub) { + this.stub = cardSetStub; } public boolean isCardSetViewable(Long cardSetId, Long userId) { try { IsCardSetViewableRequest request = IsCardSetViewableRequest.newBuilder() - .setCardSetId(cardSetId.intValue()) - .setUserId(userId.intValue()) + .setCardSetId(cardSetId) + .setUserId(userId) .build(); IsCardSetViewableResponse response = stub.isCardSetViewable(request); @@ -44,18 +42,27 @@ public boolean isCardSetViewable(Long cardSetId, Long userId) { } } - public Map getCardSetsByIds(List cardSetIds, Long userId) { + public Map getCardSetsByIds(List cardSetIds, Long userId) { try { GetCardSetsByIdsRequest request = GetCardSetsByIdsRequest.newBuilder() .addAllCardSetIds(cardSetIds) - .setUserId(userId.intValue()) + .setUserId(userId) .build(); GetCardSetsByIdsResponse response = stub.getCardSetsByIds(request); return response.getCardSetsList().stream() .collect(Collectors.toMap( cs -> (long) cs.getId(), - Function.identity() + cs -> new CardSetSummaryResult( + (long) cs.getId(), + cs.getName(), + (long) cs.getGroupId(), + cs.getVisibility(), + cs.getCategory(), + cs.getHashtag(), + (long) cs.getImageRefId(), + (long) cs.getCardCount() + ) )); } catch (StatusRuntimeException e) { log.error("gRPC call failed: GetCardSetsByIds, cardSetIds={}, userId={}", cardSetIds, userId, e); diff --git a/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java b/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java new file mode 100644 index 0000000..643448a --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/grpc/GrpcConfig.java @@ -0,0 +1,16 @@ +package flipnote.reaction.infrastructure.grpc; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.grpc.client.GrpcChannelFactory; + +import cardset.CardsetServiceGrpc; + +@Configuration +public class GrpcConfig { + + @Bean + public CardsetServiceGrpc.CardsetServiceBlockingStub cardSetStub(GrpcChannelFactory grpcChannelFactory) { + return CardsetServiceGrpc.newBlockingStub(grpcChannelFactory.createChannel("cardset")); + } +} diff --git a/src/main/java/flipnote/reaction/common/config/RabbitMqConfig.java b/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java similarity index 82% rename from src/main/java/flipnote/reaction/common/config/RabbitMqConfig.java rename to src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java index 563ee22..6c8cf81 100644 --- a/src/main/java/flipnote/reaction/common/config/RabbitMqConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/RabbitMqConfig.java @@ -1,7 +1,7 @@ -package flipnote.reaction.common.config; +package flipnote.reaction.infrastructure.messaging; import org.springframework.amqp.core.TopicExchange; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,6 +23,6 @@ public TopicExchange reactionExchange() { @Bean public MessageConverter jackson2JsonMessageConverter() { - return new Jackson2JsonMessageConverter(); + return new JacksonJsonMessageConverter(); } } diff --git a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java new file mode 100644 index 0000000..c6b0fb8 --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventListener.java @@ -0,0 +1,38 @@ +package flipnote.reaction.infrastructure.messaging; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import flipnote.reaction.domain.bookmark.event.BookmarkAddedEvent; +import flipnote.reaction.domain.bookmark.event.BookmarkRemovedEvent; +import flipnote.reaction.domain.like.event.LikeAddedEvent; +import flipnote.reaction.domain.like.event.LikeRemovedEvent; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class ReactionEventListener { + + private final ReactionEventPublisher eventPublisher; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onBookmarkAdded(BookmarkAddedEvent event) { + eventPublisher.bookmarkAdded(event.targetType(), event.targetId(), event.userId()); + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onBookmarkRemoved(BookmarkRemovedEvent event) { + eventPublisher.bookmarkRemoved(event.targetType(), event.targetId(), event.userId()); + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onLikeAdded(LikeAddedEvent event) { + eventPublisher.likeAdded(event.targetType(), event.targetId(), event.userId()); + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onLikeRemoved(LikeRemovedEvent event) { + eventPublisher.likeRemoved(event.targetType(), event.targetId(), event.userId()); + } +} diff --git a/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java new file mode 100644 index 0000000..a41670a --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionEventPublisher.java @@ -0,0 +1,44 @@ +package flipnote.reaction.infrastructure.messaging; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ReactionEventPublisher { + + private final RabbitTemplate rabbitTemplate; + + public void bookmarkAdded(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_ADDED, "BOOKMARK_ADDED", targetType, targetId, userId); + } + + public void bookmarkRemoved(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_BOOKMARK_REMOVED, "BOOKMARK_REMOVED", targetType, targetId, userId); + } + + public void likeAdded(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, "LIKE_ADDED", targetType, targetId, userId); + } + + public void likeRemoved(String targetType, Long targetId, Long userId) { + publish(RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, "LIKE_REMOVED", targetType, targetId, userId); + } + + private void publish(String routingKey, String eventType, String targetType, Long targetId, Long userId) { + try { + rabbitTemplate.convertAndSend( + RabbitMqConfig.EXCHANGE, + routingKey, + new ReactionMessage(eventType, targetType, targetId, userId) + ); + } catch (Exception e) { + log.error("이벀트 λ°œν–‰ μ‹€νŒ¨: eventType={}, targetType={}, targetId={}, userId={}", + eventType, targetType, targetId, userId, e); + } + } +} diff --git a/src/main/java/flipnote/reaction/common/model/event/ReactionMessage.java b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionMessage.java similarity index 67% rename from src/main/java/flipnote/reaction/common/model/event/ReactionMessage.java rename to src/main/java/flipnote/reaction/infrastructure/messaging/ReactionMessage.java index e55b025..015059d 100644 --- a/src/main/java/flipnote/reaction/common/model/event/ReactionMessage.java +++ b/src/main/java/flipnote/reaction/infrastructure/messaging/ReactionMessage.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.event; +package flipnote.reaction.infrastructure.messaging; public record ReactionMessage( String eventType, diff --git a/src/main/java/flipnote/reaction/common/config/AuditingConfig.java b/src/main/java/flipnote/reaction/infrastructure/persistence/AuditingConfig.java similarity index 78% rename from src/main/java/flipnote/reaction/common/config/AuditingConfig.java rename to src/main/java/flipnote/reaction/infrastructure/persistence/AuditingConfig.java index e930b9a..87a5431 100644 --- a/src/main/java/flipnote/reaction/common/config/AuditingConfig.java +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/AuditingConfig.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.config; +package flipnote.reaction.infrastructure.persistence; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java b/src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java new file mode 100644 index 0000000..bc63c24 --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/BookmarkRepositoryAdapter.java @@ -0,0 +1,45 @@ +package flipnote.reaction.infrastructure.persistence; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkRepository; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class BookmarkRepositoryAdapter implements BookmarkRepository { + + private final SpringDataBookmarkRepository springDataBookmarkRepository; + + @Override + public boolean existsByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId) { + return springDataBookmarkRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Optional findByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, + Long userId) { + return springDataBookmarkRepository.findByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Page findByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable) { + return springDataBookmarkRepository.findByTargetTypeAndUserId(targetType, userId, pageable); + } + + @Override + public Bookmark save(Bookmark bookmark) { + return springDataBookmarkRepository.save(bookmark); + } + + @Override + public void delete(Bookmark bookmark) { + springDataBookmarkRepository.delete(bookmark); + } +} diff --git a/src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java b/src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java new file mode 100644 index 0000000..ec643f0 --- /dev/null +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/LikeRepositoryAdapter.java @@ -0,0 +1,44 @@ +package flipnote.reaction.infrastructure.persistence; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeRepository; +import flipnote.reaction.domain.like.LikeTargetType; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class LikeRepositoryAdapter implements LikeRepository { + + private final SpringDataLikeRepository springDataLikeRepository; + + @Override + public boolean existsByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId) { + return springDataLikeRepository.existsByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Optional findByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId) { + return springDataLikeRepository.findByTargetTypeAndTargetIdAndUserId(targetType, targetId, userId); + } + + @Override + public Page findByTargetTypeAndUserId(LikeTargetType targetType, Long userId, Pageable pageable) { + return springDataLikeRepository.findByTargetTypeAndUserId(targetType, userId, pageable); + } + + @Override + public Like save(Like like) { + return springDataLikeRepository.save(like); + } + + @Override + public void delete(Like like) { + springDataLikeRepository.delete(like); + } +} diff --git a/src/main/java/flipnote/reaction/bookmark/repository/BookmarkRepository.java b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataBookmarkRepository.java similarity index 57% rename from src/main/java/flipnote/reaction/bookmark/repository/BookmarkRepository.java rename to src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataBookmarkRepository.java index 2ad22f3..769ef96 100644 --- a/src/main/java/flipnote/reaction/bookmark/repository/BookmarkRepository.java +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataBookmarkRepository.java @@ -1,22 +1,19 @@ -package flipnote.reaction.bookmark.repository; +package flipnote.reaction.infrastructure.persistence; -import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import flipnote.reaction.bookmark.entity.Bookmark; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; +import flipnote.reaction.domain.bookmark.Bookmark; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; -public interface BookmarkRepository extends JpaRepository { +interface SpringDataBookmarkRepository extends JpaRepository { boolean existsByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); Optional findByTargetTypeAndTargetIdAndUserId(BookmarkTargetType targetType, Long targetId, Long userId); Page findByTargetTypeAndUserId(BookmarkTargetType targetType, Long userId, Pageable pageable); - - List findByTargetTypeAndTargetIdInAndUserId(BookmarkTargetType targetType, List targetIds, Long userId); } diff --git a/src/main/java/flipnote/reaction/like/repository/LikeRepository.java b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataLikeRepository.java similarity index 59% rename from src/main/java/flipnote/reaction/like/repository/LikeRepository.java rename to src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataLikeRepository.java index 6003798..b189cb1 100644 --- a/src/main/java/flipnote/reaction/like/repository/LikeRepository.java +++ b/src/main/java/flipnote/reaction/infrastructure/persistence/SpringDataLikeRepository.java @@ -1,22 +1,19 @@ -package flipnote.reaction.like.repository; +package flipnote.reaction.infrastructure.persistence; -import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import flipnote.reaction.like.entity.Like; -import flipnote.reaction.like.entity.LikeTargetType; +import flipnote.reaction.domain.like.Like; +import flipnote.reaction.domain.like.LikeTargetType; -public interface LikeRepository extends JpaRepository { +interface SpringDataLikeRepository extends JpaRepository { boolean existsByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); Optional findByTargetTypeAndTargetIdAndUserId(LikeTargetType targetType, Long targetId, Long userId); Page findByTargetTypeAndUserId(LikeTargetType targetType, Long userId, Pageable pageable); - - List findByTargetTypeAndTargetIdInAndUserId(LikeTargetType targetType, List targetIds, Long userId); } diff --git a/src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java new file mode 100644 index 0000000..8f78899 --- /dev/null +++ b/src/main/java/flipnote/reaction/interfaces/grpc/GrpcExceptionHandlerImpl.java @@ -0,0 +1,52 @@ +package flipnote.reaction.interfaces.grpc; + +import org.springframework.grpc.server.exception.GrpcExceptionHandler; +import org.springframework.stereotype.Component; + +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.ErrorCode; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class GrpcExceptionHandlerImpl implements GrpcExceptionHandler { + + @Override + public StatusException handleException(Throwable t) { + if (t instanceof BizException e) { + ErrorCode errorCode = e.getErrorCode(); + log.warn("gRPC BizException: code={}, status={}, message={}", + errorCode.getCode(), errorCode.getStatus(), errorCode.getMessage()); + return toGrpcStatus(errorCode) + .withDescription(errorCode.getMessage()) + .asException(); + } + if (t instanceof StatusException e) { + log.warn("gRPC StatusException: status={}, description={}", + e.getStatus().getCode(), e.getStatus().getDescription()); + return e; + } + if (t instanceof StatusRuntimeException e) { + log.warn("gRPC StatusRuntimeException: status={}, description={}", + e.getStatus().getCode(), e.getStatus().getDescription()); + return e.getStatus().asException(e.getTrailers()); + } + log.error("gRPC Unhandled exception", t); + return Status.INTERNAL.withDescription("Internal server error").asException(); + } + + private Status toGrpcStatus(ErrorCode errorCode) { + return switch (errorCode.getStatus()) { + case 400 -> Status.INVALID_ARGUMENT; + case 401 -> Status.UNAUTHENTICATED; + case 403 -> Status.PERMISSION_DENIED; + case 404 -> Status.NOT_FOUND; + case 409 -> Status.ALREADY_EXISTS; + case 429 -> Status.RESOURCE_EXHAUSTED; + default -> Status.INTERNAL; + }; + } +} diff --git a/src/main/java/flipnote/reaction/common/grpc/ReactionGrpcService.java b/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java similarity index 63% rename from src/main/java/flipnote/reaction/common/grpc/ReactionGrpcService.java rename to src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java index 0be2c88..e599b2d 100644 --- a/src/main/java/flipnote/reaction/common/grpc/ReactionGrpcService.java +++ b/src/main/java/flipnote/reaction/interfaces/grpc/ReactionGrpcService.java @@ -1,67 +1,71 @@ -package flipnote.reaction.common.grpc; +package flipnote.reaction.interfaces.grpc; import java.util.List; import java.util.Map; -import org.springframework.stereotype.Component; +import org.springframework.grpc.server.service.GrpcService; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.service.BookmarkReader; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.service.LikeReader; +import flipnote.reaction.application.bookmark.BookmarkReader; +import flipnote.reaction.application.like.LikeReader; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.like.LikeTargetType; +import io.grpc.Status; import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; import reaction.Reaction.AreReactedRequest; import reaction.Reaction.AreReactedResponse; import reaction.Reaction.IsReactedRequest; import reaction.Reaction.IsReactedResponse; import reaction.ReactionServiceGrpc; -@Component +@GrpcService +@RequiredArgsConstructor public class ReactionGrpcService extends ReactionServiceGrpc.ReactionServiceImplBase { private final LikeReader likeReader; private final BookmarkReader bookmarkReader; - public ReactionGrpcService(LikeReader likeReader, BookmarkReader bookmarkReader) { - this.likeReader = likeReader; - this.bookmarkReader = bookmarkReader; - } - @Override public void isLiked(IsReactedRequest request, StreamObserver responseObserver) { - LikeTargetType targetType = LikeTargetType.valueOf(request.getTargetType()); + LikeTargetType targetType = parseTargetType(request.getTargetType(), LikeTargetType.class); boolean reacted = likeReader.isLiked(targetType, request.getTargetId(), request.getUserId()); - responseObserver.onNext(IsReactedResponse.newBuilder().setReacted(reacted).build()); responseObserver.onCompleted(); } @Override public void areLiked(AreReactedRequest request, StreamObserver responseObserver) { - LikeTargetType targetType = LikeTargetType.valueOf(request.getTargetType()); + LikeTargetType targetType = parseTargetType(request.getTargetType(), LikeTargetType.class); List targetIds = request.getTargetIdsList(); Map results = likeReader.areLiked(targetType, targetIds, request.getUserId()); - responseObserver.onNext(AreReactedResponse.newBuilder().putAllResults(results).build()); responseObserver.onCompleted(); } @Override public void isBookmarked(IsReactedRequest request, StreamObserver responseObserver) { - BookmarkTargetType targetType = BookmarkTargetType.valueOf(request.getTargetType()); + BookmarkTargetType targetType = parseTargetType(request.getTargetType(), BookmarkTargetType.class); boolean reacted = bookmarkReader.isBookmarked(targetType, request.getTargetId(), request.getUserId()); - responseObserver.onNext(IsReactedResponse.newBuilder().setReacted(reacted).build()); responseObserver.onCompleted(); } @Override public void areBookmarked(AreReactedRequest request, StreamObserver responseObserver) { - BookmarkTargetType targetType = BookmarkTargetType.valueOf(request.getTargetType()); + BookmarkTargetType targetType = parseTargetType(request.getTargetType(), BookmarkTargetType.class); List targetIds = request.getTargetIdsList(); Map results = bookmarkReader.areBookmarked(targetType, targetIds, request.getUserId()); - responseObserver.onNext(AreReactedResponse.newBuilder().putAllResults(results).build()); responseObserver.onCompleted(); } + + private > T parseTargetType(String value, Class enumClass) { + try { + return Enum.valueOf(enumClass, value); + } catch (IllegalArgumentException e) { + throw Status.INVALID_ARGUMENT + .withDescription("Invalid target type: " + value) + .asRuntimeException(); + } + } } diff --git a/src/main/java/flipnote/reaction/bookmark/controller/BookmarkController.java b/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java similarity index 66% rename from src/main/java/flipnote/reaction/bookmark/controller/BookmarkController.java rename to src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java index 599657f..f47e6c3 100644 --- a/src/main/java/flipnote/reaction/bookmark/controller/BookmarkController.java +++ b/src/main/java/flipnote/reaction/interfaces/http/BookmarkController.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.controller; +package flipnote.reaction.interfaces.http; -import static flipnote.reaction.common.constants.HeaderConstants.USER_ID; +import static flipnote.reaction.interfaces.http.common.HeaderConstants.USER_ID; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.model.request.BookmarkSearchRequest; -import flipnote.reaction.bookmark.model.request.BookmarkTargetTypeRequest; -import flipnote.reaction.bookmark.model.response.BookmarkResponse; -import flipnote.reaction.bookmark.service.BookmarkService; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; +import flipnote.reaction.application.bookmark.BookmarkResult; +import flipnote.reaction.application.bookmark.BookmarkService; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.interfaces.http.common.IdResponse; +import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.interfaces.http.dto.request.BookmarkSearchRequest; +import flipnote.reaction.interfaces.http.dto.request.BookmarkTargetTypeRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -35,7 +35,7 @@ public IdResponse addBookmark( @PathVariable Long targetId ) { BookmarkTargetType type = BookmarkTargetTypeRequest.from(targetType).toEntity(); - return bookmarkService.addBookmark(type, targetId, userId); + return IdResponse.from(bookmarkService.addBookmark(type, targetId, userId)); } @DeleteMapping("/{targetType}/{targetId}") @@ -49,12 +49,12 @@ public void removeBookmark( } @GetMapping("/{targetType}") - public PagingResponse getBookmarks( + public PagingResponse getBookmarks( @RequestHeader(USER_ID) Long userId, @PathVariable String targetType, @Valid @ModelAttribute BookmarkSearchRequest request ) { BookmarkTargetType type = BookmarkTargetTypeRequest.from(targetType).toEntity(); - return bookmarkService.getBookmarks(type, userId, request); + return PagingResponse.from(bookmarkService.getBookmarks(type, userId, request)); } } diff --git a/src/main/java/flipnote/reaction/like/controller/LikeController.java b/src/main/java/flipnote/reaction/interfaces/http/LikeController.java similarity index 66% rename from src/main/java/flipnote/reaction/like/controller/LikeController.java rename to src/main/java/flipnote/reaction/interfaces/http/LikeController.java index f14fc5d..d999545 100644 --- a/src/main/java/flipnote/reaction/like/controller/LikeController.java +++ b/src/main/java/flipnote/reaction/interfaces/http/LikeController.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.controller; +package flipnote.reaction.interfaces.http; -import static flipnote.reaction.common.constants.HeaderConstants.USER_ID; +import static flipnote.reaction.interfaces.http.common.HeaderConstants.USER_ID; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.model.request.LikeSearchRequest; -import flipnote.reaction.like.model.request.LikeTargetTypeRequest; -import flipnote.reaction.like.model.response.LikeResponse; -import flipnote.reaction.like.service.LikeService; +import flipnote.reaction.application.like.LikeResult; +import flipnote.reaction.application.like.LikeService; +import flipnote.reaction.domain.like.LikeTargetType; +import flipnote.reaction.interfaces.http.common.IdResponse; +import flipnote.reaction.interfaces.http.common.PagingResponse; +import flipnote.reaction.interfaces.http.dto.request.LikeSearchRequest; +import flipnote.reaction.interfaces.http.dto.request.LikeTargetTypeRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -35,7 +35,7 @@ public IdResponse addLike( @PathVariable Long targetId ) { LikeTargetType type = LikeTargetTypeRequest.from(targetType).toEntity(); - return likeService.addLike(type, targetId, userId); + return IdResponse.from(likeService.addLike(type, targetId, userId)); } @DeleteMapping("/{targetType}/{targetId}") @@ -49,12 +49,12 @@ public void removeLike( } @GetMapping("/{targetType}") - public PagingResponse getLikes( + public PagingResponse getLikes( @RequestHeader(USER_ID) Long userId, @PathVariable String targetType, @Valid @ModelAttribute LikeSearchRequest request ) { LikeTargetType type = LikeTargetTypeRequest.from(targetType).toEntity(); - return likeService.getLikes(type, userId, request); + return PagingResponse.from(likeService.getLikes(type, userId, request)); } } diff --git a/src/main/java/flipnote/reaction/common/model/response/ApiResponse.java b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponse.java similarity index 93% rename from src/main/java/flipnote/reaction/common/model/response/ApiResponse.java rename to src/main/java/flipnote/reaction/interfaces/http/common/ApiResponse.java index b8de86d..bd5cf0f 100644 --- a/src/main/java/flipnote/reaction/common/model/response/ApiResponse.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponse.java @@ -1,11 +1,11 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; import java.util.List; import java.util.stream.Collectors; import org.springframework.validation.BindingResult; -import flipnote.reaction.common.exception.ErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/flipnote/reaction/common/model/response/ApiResponseAdvice.java b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponseAdvice.java similarity index 96% rename from src/main/java/flipnote/reaction/common/model/response/ApiResponseAdvice.java rename to src/main/java/flipnote/reaction/interfaces/http/common/ApiResponseAdvice.java index 67d337b..1275e8d 100644 --- a/src/main/java/flipnote/reaction/common/model/response/ApiResponseAdvice.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/ApiResponseAdvice.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; diff --git a/src/main/java/flipnote/reaction/common/exception/GlobalExceptionHandler.java b/src/main/java/flipnote/reaction/interfaces/http/common/GlobalExceptionHandler.java similarity index 91% rename from src/main/java/flipnote/reaction/common/exception/GlobalExceptionHandler.java rename to src/main/java/flipnote/reaction/interfaces/http/common/GlobalExceptionHandler.java index 72cdc8c..8955786 100644 --- a/src/main/java/flipnote/reaction/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.exception; +package flipnote.reaction.interfaces.http.common; import java.util.List; @@ -9,7 +9,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import flipnote.reaction.common.model.response.ApiResponse; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.common.CommonErrorCode; +import flipnote.reaction.domain.common.ErrorCode; import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/src/main/java/flipnote/reaction/common/constants/HeaderConstants.java b/src/main/java/flipnote/reaction/interfaces/http/common/HeaderConstants.java similarity index 80% rename from src/main/java/flipnote/reaction/common/constants/HeaderConstants.java rename to src/main/java/flipnote/reaction/interfaces/http/common/HeaderConstants.java index d9a6331..3f979d9 100644 --- a/src/main/java/flipnote/reaction/common/constants/HeaderConstants.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/HeaderConstants.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.constants; +package flipnote.reaction.interfaces.http.common; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/flipnote/reaction/common/model/response/IdResponse.java b/src/main/java/flipnote/reaction/interfaces/http/common/IdResponse.java similarity index 69% rename from src/main/java/flipnote/reaction/common/model/response/IdResponse.java rename to src/main/java/flipnote/reaction/interfaces/http/common/IdResponse.java index 6240e46..4f3df70 100644 --- a/src/main/java/flipnote/reaction/common/model/response/IdResponse.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/IdResponse.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; public record IdResponse( Long id diff --git a/src/main/java/flipnote/reaction/common/model/request/PagingRequest.java b/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java similarity index 89% rename from src/main/java/flipnote/reaction/common/model/request/PagingRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java index dfe36c4..2dadcb4 100644 --- a/src/main/java/flipnote/reaction/common/model/request/PagingRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/PagingRequest.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.request; +package flipnote.reaction.interfaces.http.common; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -42,6 +42,6 @@ public Sort.Direction getOrder() { } public String getSortBy() { - return sortBy != null ? sortBy.toUpperCase() : null; + return sortBy; } } diff --git a/src/main/java/flipnote/reaction/common/model/response/PagingResponse.java b/src/main/java/flipnote/reaction/interfaces/http/common/PagingResponse.java similarity index 91% rename from src/main/java/flipnote/reaction/common/model/response/PagingResponse.java rename to src/main/java/flipnote/reaction/interfaces/http/common/PagingResponse.java index 304f4df..14c652d 100644 --- a/src/main/java/flipnote/reaction/common/model/response/PagingResponse.java +++ b/src/main/java/flipnote/reaction/interfaces/http/common/PagingResponse.java @@ -1,4 +1,4 @@ -package flipnote.reaction.common.model.response; +package flipnote.reaction.interfaces.http.common; import java.util.List; diff --git a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkSearchRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkSearchRequest.java similarity index 51% rename from src/main/java/flipnote/reaction/bookmark/model/request/BookmarkSearchRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkSearchRequest.java index 7b0d9b3..6942060 100644 --- a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkSearchRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkSearchRequest.java @@ -1,6 +1,6 @@ -package flipnote.reaction.bookmark.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.common.model.request.PagingRequest; +import flipnote.reaction.interfaces.http.common.PagingRequest; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkTargetTypeRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkTargetTypeRequest.java similarity index 64% rename from src/main/java/flipnote/reaction/bookmark/model/request/BookmarkTargetTypeRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkTargetTypeRequest.java index fba492d..93b9ec9 100644 --- a/src/main/java/flipnote/reaction/bookmark/model/request/BookmarkTargetTypeRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/BookmarkTargetTypeRequest.java @@ -1,8 +1,8 @@ -package flipnote.reaction.bookmark.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.bookmark.entity.BookmarkTargetType; -import flipnote.reaction.bookmark.exception.BookmarkErrorCode; -import flipnote.reaction.common.exception.BizException; +import flipnote.reaction.domain.bookmark.BookmarkErrorCode; +import flipnote.reaction.domain.bookmark.BookmarkTargetType; +import flipnote.reaction.domain.common.BizException; public enum BookmarkTargetTypeRequest { card_set; diff --git a/src/main/java/flipnote/reaction/like/model/request/LikeSearchRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeSearchRequest.java similarity index 50% rename from src/main/java/flipnote/reaction/like/model/request/LikeSearchRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeSearchRequest.java index 491bde9..229dc48 100644 --- a/src/main/java/flipnote/reaction/like/model/request/LikeSearchRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeSearchRequest.java @@ -1,6 +1,6 @@ -package flipnote.reaction.like.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.common.model.request.PagingRequest; +import flipnote.reaction.interfaces.http.common.PagingRequest; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/flipnote/reaction/like/model/request/LikeTargetTypeRequest.java b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeTargetTypeRequest.java similarity index 64% rename from src/main/java/flipnote/reaction/like/model/request/LikeTargetTypeRequest.java rename to src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeTargetTypeRequest.java index d5d459d..0e02093 100644 --- a/src/main/java/flipnote/reaction/like/model/request/LikeTargetTypeRequest.java +++ b/src/main/java/flipnote/reaction/interfaces/http/dto/request/LikeTargetTypeRequest.java @@ -1,8 +1,8 @@ -package flipnote.reaction.like.model.request; +package flipnote.reaction.interfaces.http.dto.request; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.exception.LikeErrorCode; +import flipnote.reaction.domain.common.BizException; +import flipnote.reaction.domain.like.LikeErrorCode; +import flipnote.reaction.domain.like.LikeTargetType; public enum LikeTargetTypeRequest { card_set; diff --git a/src/main/java/flipnote/reaction/interfaces/http/dto/response/.gitkeep b/src/main/java/flipnote/reaction/interfaces/http/dto/response/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/flipnote/reaction/like/model/response/LikeResponse.java b/src/main/java/flipnote/reaction/like/model/response/LikeResponse.java deleted file mode 100644 index c656567..0000000 --- a/src/main/java/flipnote/reaction/like/model/response/LikeResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package flipnote.reaction.like.model.response; - -import java.time.LocalDateTime; - -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.like.entity.Like; - -public record LikeResponse( - LikeTargetResponse target, - LocalDateTime likedAt -) { - public static LikeResponse from(Like like, CardSetSummary summary) { - return new LikeResponse( - new LikeTargetResponse( - like.getTargetType().name(), - like.getTargetId(), - summary != null ? summary.getGroupId() : null, - summary != null ? summary.getName() : null - ), - like.getCreatedAt() - ); - } -} diff --git a/src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java b/src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java deleted file mode 100644 index d3419dc..0000000 --- a/src/main/java/flipnote/reaction/like/model/response/LikeTargetResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package flipnote.reaction.like.model.response; - -public record LikeTargetResponse( - String type, - Long id, - Long groupId, - String name -) { -} diff --git a/src/main/java/flipnote/reaction/like/service/LikeService.java b/src/main/java/flipnote/reaction/like/service/LikeService.java deleted file mode 100644 index ed46aba..0000000 --- a/src/main/java/flipnote/reaction/like/service/LikeService.java +++ /dev/null @@ -1,96 +0,0 @@ -package flipnote.reaction.like.service; - -import java.util.List; -import java.util.Map; - -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import cardset.Cardset.CardSetSummary; -import flipnote.reaction.common.config.RabbitMqConfig; -import flipnote.reaction.common.event.ReactionEventPublisher; -import flipnote.reaction.common.exception.BizException; -import flipnote.reaction.common.exception.CommonErrorCode; -import flipnote.reaction.common.grpc.CardSetGrpcClient; -import flipnote.reaction.common.model.response.IdResponse; -import flipnote.reaction.common.model.response.PagingResponse; -import flipnote.reaction.like.entity.Like; -import flipnote.reaction.like.entity.LikeTargetType; -import flipnote.reaction.like.exception.LikeErrorCode; -import flipnote.reaction.like.model.request.LikeSearchRequest; -import flipnote.reaction.like.model.response.LikeResponse; -import flipnote.reaction.like.repository.LikeRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class LikeService { - - private final LikeRepository likeRepository; - private final LikeReader likeReader; - private final ReactionEventPublisher eventPublisher; - private final CardSetGrpcClient cardSetGrpcClient; - - @Transactional - public IdResponse addLike(LikeTargetType targetType, Long targetId, Long userId) { - if (targetType == LikeTargetType.CARD_SET) { - if (!cardSetGrpcClient.isCardSetViewable(targetId, userId)) { - throw new BizException(CommonErrorCode.TARGET_NOT_VIEWABLE); - } - } - - if (likeReader.isLiked(targetType, targetId, userId)) { - throw new BizException(LikeErrorCode.ALREADY_LIKED); - } - - Like like = Like.builder() - .targetType(targetType) - .targetId(targetId) - .userId(userId) - .build(); - - try { - likeRepository.save(like); - } catch (DataIntegrityViolationException e) { - throw new BizException(LikeErrorCode.ALREADY_LIKED); - } - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_LIKE_ADDED, "LIKE_ADDED", - targetType.name(), targetId, userId); - - return IdResponse.from(like.getId()); - } - - @Transactional - public void removeLike(LikeTargetType targetType, Long targetId, Long userId) { - Like like = likeReader.findByTargetAndUserId(targetType, targetId, userId); - likeRepository.delete(like); - - eventPublisher.publish(RabbitMqConfig.ROUTING_KEY_LIKE_REMOVED, "LIKE_REMOVED", - targetType.name(), targetId, userId); - } - - public PagingResponse getLikes(LikeTargetType targetType, Long userId, LikeSearchRequest request) { - Page likePage = likeRepository.findByTargetTypeAndUserId( - targetType, userId, request.getPageRequest() - ); - - List targetIds = likePage.getContent().stream() - .map(Like::getTargetId) - .toList(); - - Map summaryMap = targetIds.isEmpty() - ? Map.of() - : cardSetGrpcClient.getCardSetsByIds(targetIds, userId); - - Page responsePage = likePage.map( - like -> LikeResponse.from(like, summaryMap.get(like.getTargetId())) - ); - return PagingResponse.from(responsePage); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9984939..1e2a6c6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,17 +14,18 @@ spring: jpa: open-in-view: false hibernate: - ddl-auto: update + ddl-auto: ${DDL_AUTO:validate} properties: hibernate: dialect: org.hibernate.dialect.MySQLDialect - -grpc: - server: - port: ${GRPC_SERVER_PORT:9093} - cardset: - host: ${GRPC_CARDSET_HOST:cardset-service} - port: ${GRPC_CARDSET_PORT:9095} + grpc: + server: + port: ${GRPC_SERVER_PORT:9093} + client: + channels: + cardset: + address: ${GRPC_CARDSET_ADDRESS:static://cardset-service:9095} + negotiation-type: PLAINTEXT server: port: 8083 diff --git a/src/test/java/flipnote/reaction/config/TestGrpcConfig.java b/src/test/java/flipnote/reaction/config/TestGrpcConfig.java index f9846df..eaedda7 100644 --- a/src/test/java/flipnote/reaction/config/TestGrpcConfig.java +++ b/src/test/java/flipnote/reaction/config/TestGrpcConfig.java @@ -3,19 +3,13 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import flipnote.reaction.common.grpc.CardSetGrpcClient; -import io.grpc.ManagedChannel; +import flipnote.reaction.infrastructure.grpc.CardSetGrpcClient; import static org.mockito.Mockito.mock; @TestConfiguration public class TestGrpcConfig { - @Bean - public ManagedChannel cardSetChannel() { - return mock(ManagedChannel.class); - } - @Bean public CardSetGrpcClient cardSetGrpcClient() { return mock(CardSetGrpcClient.class); diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 0e1effb..62a9de7 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -15,10 +15,11 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.H2Dialect - -grpc: - server: - port: 0 - cardset: - host: localhost - port: 9090 + grpc: + server: + port: 0 + client: + channels: + cardset: + address: static://localhost:9090 + negotiation-type: PLAINTEXT