Skip to content

Conversation

@weeast1521
Copy link
Contributor

@weeast1521 weeast1521 commented Aug 16, 2025

📍 PR 타입 (하나 이상 선택)

  • 기능 추가
  • 버그 수정
  • 의존성, 환경 변수, 빌드 관련 코드 업데이트
  • 기타 사소한 수정

🏷️ 관련 이슈

Close #86

📌 개요

  • 스크랩 확인 기능 코드 수정
  • 좋아요 확인 기능 코드 수정

🔁 변경 사항

📸 스크린샷

✅ 체크리스트

  • 코드가 정상적으로 동작하는지 확인
  • PR에 적절한 라벨을 선택
  • 관련 테스트 코드 작성
  • 문서(README, Swagger 등) 업데이트

💡 추가 사항 (리뷰어가 참고해야 할 것)

Summary by CodeRabbit

  • New Features

    • Post details now include author, full content, like count, comment count, read status, timestamps, and your personalized liked/scraped status when signed in.
    • Post previews now display author and comment count.
  • Refactor

    • Firebase initialization updated to support path-based service account configuration with safer resource handling and clearer logging.

@weeast1521 weeast1521 self-assigned this Aug 16, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 16, 2025

Walkthrough

The 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

Cohort / File(s) Summary
Post retrieval API and DTOs
src/main/java/naughty/tuzamate/domain/post/controller/PostController.java, .../post/service/query/PostQueryService.java, .../post/service/query/PostQueryServiceImpl.java, .../post/dto/PostResDTO.java, .../post/converter/PostConverter.java
Controller getPost now takes PrincipalDetails and returns PostDTO. Service interface/impl updated to accept PrincipalDetails, compute liked/scraped via repositories, and return PostDTO. PostResDTO adds PostDTO and extends PostPreviewDTO with author/commentNum. Converter adds toPostDTO with liked/scraped flags.
Like/Scrap repositories
src/main/java/naughty/tuzamate/domain/postLike/repository/PostLikeRepository.java, .../postScrap/repository/PostScrapRepository.java
Added existsByPostIdAndUserId(Long, Long) methods for existence checks without loading entities.
Firebase configuration
src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java
Switched from Resource injection to path-based loading with helper getServiceAccountStream(), try-with-resources, and adjusted initialization/logging behavior.

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>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Update PostResDTO for single post with liked/scraped and fields (#86)
Modify PostController to accept PrincipalDetails and return updated DTO (#86)
Update PostService/Query to compute liked and scraped per user (#86)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Refactor Firebase service account loading and initialization (src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java) Not related to single post liked/scraped feature; unrelated infrastructure configuration change.

Possibly related PRs

Suggested labels

refactor

Poem

I thump my paw, approve the plan—
Now posts know if I’m a fan.
A like, a scrap, a carrot tap,
With author notes and comment map.
Config logs go hop, hop, hop—
Ready to fetch, and never stop! 🥕🐇

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#86

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 toPostPreviewDTO dereferences 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, principalDetails will 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 boardType and 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 73feb6f and f8ba6ca.

📒 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.

Comment on lines +3 to 10
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);
Copy link

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.

Comment on lines +34 to 46
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);
}
Copy link

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, principalDetails can be null, causing NPEs when calling getId().
  • 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/java

Length 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.java

Length 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.

Suggested change
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.

Comment on lines +28 to +41
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;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

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.

Suggested change
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.

@weeast1521 weeast1521 merged commit 4333c6d into dev Aug 16, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

♻️ [Refactor] : 단일 게시글 조회 좋아요, 스크랩 확인 기능 추가

2 participants