-
Notifications
You must be signed in to change notification settings - Fork 1
Refactor : 단일 게시글 조회 시 스크랩 및 좋아요 확인 가능한 코드로 수정 #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe getPost flow now accepts authenticated PrincipalDetails and returns a richer PostDTO. Service and converter were updated to include liked/scraped flags and author/comment counts. Repositories gained ID-based existence checks. FireBaseConfig was refactored to path-based service account loading with a new helper and try-with-resources. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Controller as PostController
participant Service as PostQueryService
participant LikeRepo as PostLikeRepository
participant ScrapRepo as PostScrapRepository
participant Converter as PostConverter
Client->>Controller: GET /posts/{postId} (PrincipalDetails)
Controller->>Service: getPost(postId, principal)
Service->>LikeRepo: existsByPostIdAndUserId(postId, principal.id)
Service->>ScrapRepo: existsByPostIdAndUserId(postId, principal.id)
Service->>Converter: toPostDTO(post, liked, scraped)
Service-->>Controller: PostDTO
Controller-->>Client: CustomResponse<PostDTO>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (7)
src/main/java/naughty/tuzamate/domain/postScrap/repository/PostScrapRepository.java (1)
17-17: Add DB uniqueness and supporting indexes for scale.
To enforce business rules and speed up per-user lookups:
- Add a unique constraint on (post_id, user_id) in post_scrap to prevent duplicates at the DB level.
- Add indexes on (user_id, id desc) for slice pagination and on (post_id, user_id) for exists checks.
src/main/java/naughty/tuzamate/domain/postLike/repository/PostLikeRepository.java (1)
16-16: Consider DB-level constraints and indexes for likes.
- Unique constraint on (post_id, user_id) in post_like to prevent duplicate likes.
- Indexes on (user_id, id desc) and (post_id, user_id) to optimize your slice queries and existence checks.
src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java (1)
17-32: Align naming with existing DTOs for consistency.
CommentResDTO uses writerName; here it’s author. Consider standardizing (author vs writerName) across DTOs to reduce cognitive load and client-side mapping conditionals.src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java (1)
43-51: Support URI schemes and robust absolute-path detection.
The current heuristic treats any value containing “:” as an absolute path (e.g., “classpath:...”), which misroutes classpath resources. Prefer explicit scheme handling and java.nio for absoluteness.Apply this diff:
- // 절대경로면 FileInputStream, 아니면 classpath - if (serviceAccountPath.startsWith("/") || serviceAccountPath.contains(":")) { - log.info("Using absolute path for Firebase service account: {}", serviceAccountPath); - return new FileInputStream(serviceAccountPath); - } else { - log.info("Using classpath resource for Firebase service account: {}", serviceAccountPath); - return new ClassPathResource(serviceAccountPath).getInputStream(); - } + // Support classpath:, file:, absolute path, or plain classpath-relative + if (serviceAccountPath.startsWith("classpath:")) { + String cp = serviceAccountPath.substring("classpath:".length()); + log.info("Using classpath resource for Firebase service account: {}", cp); + return new ClassPathResource(cp).getInputStream(); + } + if (serviceAccountPath.startsWith("file:")) { + String p = serviceAccountPath.substring("file:".length()); + log.info("Using file path for Firebase service account: {}", p); + return new FileInputStream(p); + } + java.nio.file.Path p = java.nio.file.Paths.get(serviceAccountPath); + if (p.isAbsolute()) { + log.info("Using absolute path for Firebase service account: {}", p.toAbsolutePath()); + return new FileInputStream(p.toFile()); + } + log.info("Using classpath resource for Firebase service account: {}", serviceAccountPath); + return new ClassPathResource(serviceAccountPath).getInputStream();And ensure these imports exist:
// Add if missing import java.nio.file.Path; import java.nio.file.Paths;src/main/java/naughty/tuzamate/domain/post/converter/PostConverter.java (1)
37-52: Avoid potential lazy-loading and N+1 on user/comments in conversion.
- Accessing post.getUser().getNickname() and post.getComments().size() can trigger extra queries if not fetch-joined and outside a read-only transaction.
- For commentNum, prefer a count query (e.g., commentRepository.countByPostId(post.getId())) to avoid loading the entire collection.
If not already done in the query layer, consider:
- Marking the read path @transactional(readOnly = true).
- Fetch-joining user in the post fetch for getPost.
- Replacing size() with a repository count method to avoid collection initialization.
src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java (1)
57-61: Potential N+1 when mapping previews (FYI)If
toPostPreviewDTOdereferences lazy associations (author/comment count), you may hit N+1 without fetch joins. Not a blocker for this PR, but worth a follow-up if observed in profiling.I can help add repository methods with fetch joins or projections to eliminate N+1 if needed.
src/main/java/naughty/tuzamate/domain/post/controller/PostController.java (1)
41-46: Confirm anonymous access and path variable usage for boardType
- If this endpoint is public,
principalDetailswill be null and currently triggers an NPE downstream. Ensure the service layer null-guards it (see service comment).- Route contains
{boardType}but it’s not validated/used. This can lead to confusing URLs and incorrect caching. Consider accepting@PathVariable BoardType boardTypeand verifying the post belongs to it (or remove it from the path).Example validation (outside this file’s changes), after adding
BoardType boardType:Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(GeneralErrorCode.NOT_FOUND_404)); if (post.getBoardType() != boardType) { throw new CustomException(GeneralErrorCode.NOT_FOUND_404); }Also, the return type changed to PostDTO. Please ensure API docs/clients are updated accordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (8)
src/main/java/naughty/tuzamate/domain/post/controller/PostController.java(1 hunks)src/main/java/naughty/tuzamate/domain/post/converter/PostConverter.java(1 hunks)src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java(1 hunks)src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryService.java(1 hunks)src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java(2 hunks)src/main/java/naughty/tuzamate/domain/postLike/repository/PostLikeRepository.java(1 hunks)src/main/java/naughty/tuzamate/domain/postScrap/repository/PostScrapRepository.java(1 hunks)src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
src/main/java/naughty/tuzamate/domain/post/converter/PostConverter.java (1)
src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java (1)
PostResDTO(8-70)
src/main/java/naughty/tuzamate/domain/postLike/repository/PostLikeRepository.java (2)
src/main/java/naughty/tuzamate/domain/postLike/entity/PostLike.java (1)
PostLike(9-38)src/main/java/naughty/tuzamate/domain/post/service/command/PostCommandServiceImpl.java (1)
Override(79-100)
src/main/java/naughty/tuzamate/domain/postScrap/repository/PostScrapRepository.java (1)
src/main/java/naughty/tuzamate/domain/postScrap/entity/PostScrap.java (1)
PostScrap(9-38)
src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryService.java (2)
src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java (1)
PostResDTO(8-70)src/main/java/naughty/tuzamate/domain/post/service/command/PostCommandService.java (4)
updatePost(12-12)deletePost(13-13)PostCommandService(9-18)createPost(11-11)
src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java (3)
src/main/java/naughty/tuzamate/global/apiPayload/CustomResponse.java (1)
Builder(15-74)src/main/java/naughty/tuzamate/domain/post/dto/PostReqDTO.java (2)
PostReqDTO(5-18)CreatePostRequestDTO(7-11)src/main/java/naughty/tuzamate/domain/comment/dto/CommentResDTO.java (2)
CommentPreviewDTO(16-26)CommentResDTO(8-46)
src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java (4)
src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java (1)
PostResDTO(8-70)src/main/java/naughty/tuzamate/auth/principal/PrincipalDetails.java (1)
RequiredArgsConstructor(13-66)src/main/java/naughty/tuzamate/domain/post/entity/Post.java (1)
Post(16-88)src/main/java/naughty/tuzamate/domain/post/service/command/PostCommandService.java (2)
updatePost(12-12)PostCommandService(9-18)
src/main/java/naughty/tuzamate/domain/post/controller/PostController.java (2)
src/main/java/naughty/tuzamate/domain/post/service/command/PostCommandService.java (2)
updatePost(12-12)deletePost(13-13)src/main/java/naughty/tuzamate/domain/post/service/command/PostCommandServiceImpl.java (1)
Override(43-63)
🔇 Additional comments (4)
src/main/java/naughty/tuzamate/domain/postScrap/repository/PostScrapRepository.java (1)
17-17: Good addition: ID-based existence check avoids entity loading overhead.
This derived query method is idiomatic Spring Data (nested property traversal) and fits the new per-user scraped flag flow.src/main/java/naughty/tuzamate/domain/postLike/repository/PostLikeRepository.java (1)
16-16: Looks good: efficient existence check by IDs.
This complements the entity-based variant and fits the liked flag resolution path.src/main/java/naughty/tuzamate/domain/post/dto/PostResDTO.java (1)
17-32: PostDTO is a clear fit for the single-post, user-context response.
Fields align with the PR goals (liked/scraped flags) and existing domain properties.src/main/java/naughty/tuzamate/domain/post/converter/PostConverter.java (1)
37-52: LGTM: Complete mapping for PostDTO including liked/scraped flags.
The mapping covers all new fields and keeps behavior consistent with PostPreviewDTO.
| import naughty.tuzamate.auth.principal.PrincipalDetails; | ||
| import naughty.tuzamate.domain.post.dto.PostResDTO; | ||
| import naughty.tuzamate.domain.post.enums.BoardType; | ||
|
|
||
| public interface PostQueryService { | ||
|
|
||
| PostResDTO.PostPreviewDTO getPost(Long postId); | ||
| PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails); | ||
| PostResDTO.PostPreviewListDTO getPostList(BoardType boardType, Long cursor, int size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Decouple query interface from security type and plan for null principals
Binding the query interface to PrincipalDetails tightens coupling and makes anonymous access error-prone. Prefer passing a nullable userId (or Optional) so the query layer doesn’t depend on auth internals. Also ensure null is handled gracefully for public reads.
Apply this minimal refactor at the interface level:
-import naughty.tuzamate.auth.principal.PrincipalDetails;
@@
- PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails);
+ PostResDTO.PostDTO getPost(Long postId, Long userId);To verify call sites were all updated after the signature change, run:
🏁 Script executed:
#!/bin/bash
# Find all `getPost(` usages and display surrounding context.
rg -nP -C3 --type=java '\bgetPost\s*\('Length of output: 8156
Decouple PostQueryService from PrincipalDetails
Bind the query layer only to a simple, nullable userId rather than the auth principal. This avoids leaking security internals into your service API and makes anonymous reads explicit.
Please update the following:
• PostQueryService.java
– Remove the PrincipalDetails import
– Change
PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails);to
PostResDTO.PostDTO getPost(Long postId, Long userId);• PostQueryServiceImpl.java
– Update the method signature to getPost(Long postId, Long userId)
– Replace all uses of principalDetails with userId (e.g. principalDetails.getMember().getId())
– Ensure you handle userId == null gracefully for public reads
• PostController.java
– In the getPost handler, pass the ID (or null) instead of the principal:
Long userId = principalDetails != null
? principalDetails.getMember().getId()
: null;
PostResDTO.PostDTO resDTO = postQueryService.getPost(postId, userId);🤖 Prompt for AI Agents
In
src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryService.java
lines 3-10, remove the PrincipalDetails import and change the getPost signature
from getPost(Long postId, PrincipalDetails principalDetails) to getPost(Long
postId, Long userId); update the interface accordingly. Then update
PostQueryServiceImpl.java to use getPost(Long postId, Long userId) and replace
all principalDetails usages with the userId value (e.g., replace
principalDetails.getMember().getId() with userId), ensuring code handles userId
== null for anonymous/public reads. Finally, update PostController.java getPost
handler to pass a Long userId (or null) extracted from PrincipalDetails
(principalDetails != null ? principalDetails.getMember().getId() : null) instead
of passing the principal object.
| public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) { | ||
| Post post = postRepository.findById(postId) | ||
| .orElseThrow(() -> new CustomException(GeneralErrorCode.NOT_FOUND_404)); | ||
|
|
||
| if (!post.isRead()) { | ||
| post.setIsRead(); | ||
| } | ||
|
|
||
| return PostConverter.toPostPreviewDTO(post); | ||
| boolean liked = postLikeRepository.existsByPostIdAndUserId(postId, principalDetails.getId()); | ||
| boolean scraped = postScrapRepository.existsByPostIdAndUserId(postId, principalDetails.getId()); | ||
|
|
||
| return PostConverter.toPostDTO(post, liked, scraped); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Handle null PrincipalDetails to avoid NPEs and decide on transactional semantics for isRead
- If this endpoint allows anonymous access,
principalDetailscan be null, causing NPEs when callinggetId(). - You’re mutating state (
post.setIsRead()) inside a GET without a transaction; unless another layer opens one, the change may not persist.
Suggested changes:
@@
- public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) {
+ @Transactional
+ public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) {
@@
- if (!post.isRead()) {
- post.setIsRead();
- }
+ if (!post.isRead()) {
+ post.setIsRead(); // persists due to @Transactional
+ }
@@
- boolean liked = postLikeRepository.existsByPostIdAndUserId(postId, principalDetails.getId());
- boolean scraped = postScrapRepository.existsByPostIdAndUserId(postId, principalDetails.getId());
+ Long userId = (principalDetails != null) ? principalDetails.getId() : null;
+ boolean liked = (userId != null) && postLikeRepository.existsByPostIdAndUserId(postId, userId);
+ boolean scraped = (userId != null) && postScrapRepository.existsByPostIdAndUserId(postId, userId);Verification/questions:
- Is GET meant to mutate
is_read? If not, remove that mutation or handle via a separate command endpoint/event. - Is the endpoint authenticated-only? If yes, ensure security config enforces it; if no, the null-guard above is required to prevent NPEs.
🏁 Script executed:
#!/bin/bash
# 1. Check if getPost is already annotated with @Transactional
rg -n "@Transactional" -C2 src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java
# 2. Locate all usages of getPost to find its controller and mapping
rg -nP --type java "getPost\s*\(" -C3
# 3. Inspect Spring Security configuration for anonymous access rules
rg -nP --type java "authorizeHttpRequests|authorizeRequests|permitAll|authenticated" -C5 src/main/javaLength of output: 10085
🏁 Script executed:
#!/bin/bash
# Locate the definition of allowUrls to see if GET /boards/**/posts/** is publicly accessible
rg -n "allowUrls" -C5 src/main/java/naughty/tuzamate/global/config/SecurityConfig.javaLength of output: 1118
Add @transactional to getPost and rely on enforced authentication for PrincipalDetails
- The getPost method in PostQueryServiceImpl.java (around line 34) isn’t annotated with @transactional, so calling post.setIsRead() on a detached entity won’t be persisted.
- SecurityConfig only permit-alls root, Swagger, and OAuth endpoints—everything else (including GET /boards/{boardType}/posts/{postId}) requires authentication—so principalDetails will never be null at runtime and a null-guard isn’t needed here.
Suggested diff (and import):
--- a/src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java
+++ b/src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java
@@
+import org.springframework.transaction.annotation.Transactional;
@@
- public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) {
+ @Transactional
+ public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) {
@@
if (!post.isRead()) {
post.setIsRead();
}Also confirm whether mutating the read flag belongs in a GET endpoint; if not, consider moving that logic to a dedicated command/API.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) { | |
| Post post = postRepository.findById(postId) | |
| .orElseThrow(() -> new CustomException(GeneralErrorCode.NOT_FOUND_404)); | |
| if (!post.isRead()) { | |
| post.setIsRead(); | |
| } | |
| return PostConverter.toPostPreviewDTO(post); | |
| boolean liked = postLikeRepository.existsByPostIdAndUserId(postId, principalDetails.getId()); | |
| boolean scraped = postScrapRepository.existsByPostIdAndUserId(postId, principalDetails.getId()); | |
| return PostConverter.toPostDTO(post, liked, scraped); | |
| } | |
| import org.springframework.transaction.annotation.Transactional; | |
| @Transactional | |
| public PostResDTO.PostDTO getPost(Long postId, PrincipalDetails principalDetails) { | |
| Post post = postRepository.findById(postId) | |
| .orElseThrow(() -> new CustomException(GeneralErrorCode.NOT_FOUND_404)); | |
| if (!post.isRead()) { | |
| post.setIsRead(); | |
| } | |
| boolean liked = postLikeRepository.existsByPostIdAndUserId(postId, principalDetails.getId()); | |
| boolean scraped = postScrapRepository.existsByPostIdAndUserId(postId, principalDetails.getId()); | |
| return PostConverter.toPostDTO(post, liked, scraped); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/naughty/tuzamate/domain/post/service/query/PostQueryServiceImpl.java
around lines 34 to 46, the method mutates the Post entity via post.setIsRead()
but is not annotated with @Transactional so the change on a detached entity will
not be persisted; annotate the getPost method with @Transactional (importing
org.springframework.transaction.annotation.Transactional) so the persistence
context remains open and setIsRead() is flushed, and leave out any null-guards
for PrincipalDetails since authentication is enforced by SecurityConfig; also
consider (and note to reviewers) whether flipping the read flag belongs in a GET
endpoint and move that mutation into a dedicated command/API if appropriate.
| try (InputStream serviceAccount = getServiceAccountStream()) { | ||
|
|
||
| FirebaseOptions options = FirebaseOptions.builder() | ||
| .setCredentials(GoogleCredentials.fromStream(serviceAccount)) | ||
| .build(); | ||
|
|
||
| // 앱이 이미 초기화되었는지 확인 (중복 초기화 방지) | ||
| if (FirebaseApp.getApps().isEmpty()) { | ||
| log.info("Successfully initialized firebase app"); | ||
| return FirebaseApp.initializeApp(options); | ||
| } else { | ||
| return FirebaseApp.getInstance(); | ||
| } | ||
| log.info(" Successfully initialized firebase app"); | ||
| return FirebaseApp.initializeApp(options); | ||
|
|
||
| } catch (IOException exception) { | ||
| log.error("Fail to initialize firebase app: {}", exception.getMessage(), exception); | ||
| // 초기화 실패 시 null 대신 예외를 던져서 애플리케이션이 문제를 인지하게 하는 것이 더 좋습니다. | ||
| throw new RuntimeException("Failed to initialize Firebase app.", exception); | ||
| log.error(" Fail to initialize firebase app: {}", exception.getMessage(), exception); | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Critical: Possible duplicate app initialization and null-returning @bean.
- Initializing without checking if a default FirebaseApp already exists can throw “FirebaseApp name [DEFAULT] already exists”.
- Returning null from a @bean will cause downstream bean creation failures (e.g., FirebaseMessaging) and is an anti-pattern.
Reintroduce the existence check and throw a runtime exception on failure instead of returning null.
Apply this diff:
- try (InputStream serviceAccount = getServiceAccountStream()) {
-
- FirebaseOptions options = FirebaseOptions.builder()
- .setCredentials(GoogleCredentials.fromStream(serviceAccount))
- .build();
-
- log.info(" Successfully initialized firebase app");
- return FirebaseApp.initializeApp(options);
-
- } catch (IOException exception) {
- log.error(" Fail to initialize firebase app: {}", exception.getMessage(), exception);
- return null;
- }
+ try (InputStream serviceAccount = getServiceAccountStream()) {
+ FirebaseOptions options = FirebaseOptions.builder()
+ .setCredentials(GoogleCredentials.fromStream(serviceAccount))
+ .build();
+
+ FirebaseApp app;
+ if (FirebaseApp.getApps().isEmpty()) {
+ app = FirebaseApp.initializeApp(options);
+ log.info("Successfully initialized Firebase app");
+ } else {
+ app = FirebaseApp.getInstance();
+ log.info("Reusing existing Firebase app");
+ }
+ return app;
+ } catch (IOException exception) {
+ log.error("Fail to initialize Firebase app: {}", exception.getMessage(), exception);
+ throw new IllegalStateException("Failed to initialize Firebase app", exception);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try (InputStream serviceAccount = getServiceAccountStream()) { | |
| FirebaseOptions options = FirebaseOptions.builder() | |
| .setCredentials(GoogleCredentials.fromStream(serviceAccount)) | |
| .build(); | |
| // 앱이 이미 초기화되었는지 확인 (중복 초기화 방지) | |
| if (FirebaseApp.getApps().isEmpty()) { | |
| log.info("Successfully initialized firebase app"); | |
| return FirebaseApp.initializeApp(options); | |
| } else { | |
| return FirebaseApp.getInstance(); | |
| } | |
| log.info(" Successfully initialized firebase app"); | |
| return FirebaseApp.initializeApp(options); | |
| } catch (IOException exception) { | |
| log.error("Fail to initialize firebase app: {}", exception.getMessage(), exception); | |
| // 초기화 실패 시 null 대신 예외를 던져서 애플리케이션이 문제를 인지하게 하는 것이 더 좋습니다. | |
| throw new RuntimeException("Failed to initialize Firebase app.", exception); | |
| log.error(" Fail to initialize firebase app: {}", exception.getMessage(), exception); | |
| return null; | |
| } | |
| } | |
| try (InputStream serviceAccount = getServiceAccountStream()) { | |
| FirebaseOptions options = FirebaseOptions.builder() | |
| .setCredentials(GoogleCredentials.fromStream(serviceAccount)) | |
| .build(); | |
| FirebaseApp app; | |
| if (FirebaseApp.getApps().isEmpty()) { | |
| app = FirebaseApp.initializeApp(options); | |
| log.info("Successfully initialized Firebase app"); | |
| } else { | |
| app = FirebaseApp.getInstance(); | |
| log.info("Reusing existing Firebase app"); | |
| } | |
| return app; | |
| } catch (IOException exception) { | |
| log.error("Fail to initialize Firebase app: {}", exception.getMessage(), exception); | |
| throw new IllegalStateException("Failed to initialize Firebase app", exception); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java around lines
28 to 41, the bean currently always calls FirebaseApp.initializeApp(options) and
returns null on IOException which can cause duplicate-initialization errors and
downstream bean failures; update it to first check FirebaseApp.getApps() or
FirebaseApp.getInstance() to return the existing default app if present, only
call FirebaseApp.initializeApp(options) when no default exists, and on
IOException (or any failure) throw a RuntimeException (wrapping the caught
exception) instead of returning null so the Spring context fails fast and avoids
creating dependent beans with a null FirebaseApp.
📍 PR 타입 (하나 이상 선택)
🏷️ 관련 이슈
Close #86
📌 개요
🔁 변경 사항
📸 스크린샷
✅ 체크리스트
💡 추가 사항 (리뷰어가 참고해야 할 것)
Summary by CodeRabbit
New Features
Refactor