Skip to content
This repository was archived by the owner on Jan 11, 2026. It is now read-only.

[REFACTOR] 2차 기획에 따른 대댓글 및 댓글 좋아요 로직 삭제#30

Merged
msk226 merged 2 commits intodevelopfrom
SPOT-304/refactor
Aug 9, 2025
Merged

[REFACTOR] 2차 기획에 따른 대댓글 및 댓글 좋아요 로직 삭제#30
msk226 merged 2 commits intodevelopfrom
SPOT-304/refactor

Conversation

@msk226
Copy link
Member

@msk226 msk226 commented Aug 9, 2025

#️⃣ 연관된 이슈


🔎 작업 내용

  • 2차 기획에 따른 대댓글 및 댓글 좋아요 로직 삭제
  • 담당 도메인에 대한 JPA 레포지토리 레이어 이동 (domain to infra)

📷 스크린샷 (선택)

작업한 결과물에 대한 간단한 스크린샷을 올려주세요.


💬리뷰 요구사항 (선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

Summary by CodeRabbit

  • Removed Features

    • Removed the ability to like or dislike post comments. Endpoints and related responses for comment likes/dislikes are no longer available.
  • Bug Fixes

    • Creating nested (child) comments is no longer supported; only top-level comments can be added.
  • Refactor

    • Updated internal package structure for improved organization. No impact on user-facing features.
  • Tests

    • Removed tests related to comment like/dislike functionality.

@msk226 msk226 self-assigned this Aug 9, 2025
@msk226 msk226 added the ♻️ refactor Code Refactoring label Aug 9, 2025
@msk226 msk226 linked an issue Aug 9, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Aug 9, 2025

Walkthrough

This change set restructures package and import paths throughout the codebase, moving various repositories, DTOs, and domain classes into more granular or infrastructure-specific packages, especially under .infrastructure.jpa and refined DTO subpackages. Several files related to comment-like/dislike functionality—including the controller, use case interface, implementation, DTO, and related tests—are removed. Internal logic for comment creation is simplified to only support top-level comments.

Changes

Cohort / File(s) Change Summary
Repository & DTO Package Refactoring
src/main/java/com/example/spot/**/*Repository.java, .../dto/request/**/*.java, .../dto/response/**/*.java, .../domain/**/*.java, .../infrastructure/**/*.java, .../application/**/*.java, .../presentation/controller/**/*.java, .../validator/**/*.java, src/test/java/com/example/spot/**/*Repository.java, etc.
Updated package declarations and import statements to move repositories to .infrastructure.jpa and DTOs to more specific subpackages. No logic changes.
Comment Like/Dislike Feature Removal
src/main/java/com/example/spot/comment/presentation/dto/CommentLikeResponse.java, src/main/java/com/example/spot/post/application/command/LikePostCommentUseCase.java, src/main/java/com/example/spot/post/application/command/impl/LikePostCommentUseCaseImpl.java, src/main/java/com/example/spot/post/presentation/controller/command/LikePostCommentController.java, src/test/java/com/example/spot/service/post/PostCommandServiceTest.java
Entire comment like/dislike feature deleted: DTO, use case interface, implementation, controller, and related tests.
Comment Creation Simplification
src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java
Internal logic for creating comments now only supports top-level comments; child/nested comment creation removed.
Formatting & Minor Refactors
src/main/java/com/example/spot/schedule/domain/repository/QuizRepository.java, .../validator/ExistQuizValidator.java, .../validator/ExistScheduleValidator.java, .../presentation/dto/response/comment/CommentDetailResponse.java, .../presentation/dto/response/post/PostSingleResponse.java, etc.
Minor formatting, such as splitting method parameters across lines, adjusting annotation formatting, and improving Javadoc readability. No behavioral changes.
Test Import Updates
src/test/java/com/example/spot/service/notification/NotificationCommandServiceTest.java, .../NotificationQueryServiceTest.java, .../service/post/GetPostUseCaseTest.java, .../service/schedule/AttendanceCommandServiceTest.java, etc.
Test imports updated to match new package structure. No test logic changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PostController
    participant ManagePostCommentUseCase
    participant PostRepository
    participant CommentRepository

    User->>PostController: POST /posts/{postId}/comments
    PostController->>ManagePostCommentUseCase: createComment(postId, memberId, request)
    ManagePostCommentUseCase->>PostRepository: findById(postId)
    ManagePostCommentUseCase->>CommentRepository: save(newTopLevelComment)
    ManagePostCommentUseCase->>PostRepository: update(post)
    ManagePostCommentUseCase-->>PostController: CommentCreateResponse
    PostController-->>User: API Response

    Note over ManagePostCommentUseCase: Only top-level comments supported (no parent/child logic)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

In the warren, code hops anew,
Old comment likes bid adieu.
Packages shuffled, imports align,
Only top-level carrots on the vine.
Tests and DTOs pruned with care—
Springtime tidying everywhere!
🐇✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SPOT-304/refactor

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
  • 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 src/utils.ts and explain its main purpose.
    • @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 comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

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

@msk226 msk226 merged commit cd8617f into develop Aug 9, 2025
1 of 2 checks passed
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: 10

🔭 Outside diff range comments (33)
src/main/java/com/example/spot/common/application/admin/AdminServiceImpl.java (1)

46-53: Guard against empty ID list to avoid executing no-op IN queries.

Skip delete calls when deletedMemberIds is empty. This prevents potential SQL generation issues for IN () and saves a round-trip.

         // Token 정리
-        refreshTokenRepository.deleteAllByMemberIdIn(deletedMemberIds);
+        if (deletedMemberIds.isEmpty()) {
+            return deletedMemberListDTO;
+        }
+        refreshTokenRepository.deleteAllByMemberIdIn(deletedMemberIds);

Additionally, if deletedMemberIds can be very large, consider chunking the IN-list (e.g., batches of 500–1000) to avoid exceeding DB parameter limits.

src/main/java/com/example/spot/member/infrastructure/jpa/StudyJoinReasonRepository.java (1)

1-15: Add @repository for consistency and exception translation

Other JPA repos (e.g., PreferredRegionRepository) use @repository. While optional, annotating consistently helps with component scanning clarity and Spring’s exception translation.

 package com.example.spot.member.infrastructure.jpa;

 import com.example.spot.member.domain.association.StudyJoinReason;
 import java.util.List;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;

-public interface StudyJoinReasonRepository extends JpaRepository<StudyJoinReason, Long> {
+@Repository
+public interface StudyJoinReasonRepository extends JpaRepository<StudyJoinReason, Long> {
src/main/java/com/example/spot/post/presentation/dto/request/post/PostLikeRequest.java (1)

1-17: Do not accept memberId from clients; derive from auth context

Including memberId in the request enables spoofing. Controllers/services should obtain the member from the authenticated principal.

 package com.example.spot.post.presentation.dto.request.post;

 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.Setter;

 @Getter
 @Setter
 @Builder
 public class PostLikeRequest {
-    @Schema(description = "멤버 ID", example = "1")
-    private Long memberId;
-
     @Schema(description = "게시글 ID", example = "1")
     private Long postId;
 }

Follow-up: I can provide controller/service adjustments to ignore request memberId and use SecurityContext. Want me to generate that patch?

src/main/java/com/example/spot/post/presentation/dto/response/post/PostHomeResponse.java (1)

9-18: Add missing @builder to PostHomeResponse for instantiation

PostHomeResponse uses a private all-args constructor but no factory or builder, making it impossible to create instances at runtime. To align with other DTOs, add Lombok’s @builder.

• File: src/main/java/com/example/spot/post/presentation/dto/response/post/PostHomeResponse.java
• Add import and annotation:

 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.Builder;

 @Getter
 @AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder
 public class PostHomeResponse {
     private PostBest5Response postBest5Response;
     private List<PostBoard5Response> postBoard5Responses;
     //공지
 }
src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java (2)

14-20: Add validation to prevent empty comments

Apply bean validation on content.

 package com.example.spot.post.presentation.dto.request.comment;

 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
+import javax.validation.constraints.NotBlank;

 @Getter
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
 public class CommentCreateRequest {
     @Schema(description = "댓글 내용입니다.",
             format = "string")
-    private String content;
+    @NotBlank
+    private String content;

21-26: Remove all parentCommentId references to complete nested-comment removal

Since nested comments (대댓글) are being removed, the parentCommentId property must be eliminated everywhere—not just in the request DTO—to prevent compilation and test failures.

Please remove or refactor its usage in:

  • src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java
  • src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java (field + toDTOwithParent builder)
  • src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java (parameter handling and comments)
  • src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java (field + mapping)
  • src/test/java/com/example/spot/service/post/PostCommandServiceTest.java (all assertions and fixtures referencing parentCommentId)

Example removal in the request DTO:

src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java
@@
-    @Schema(
-            description = "부모 댓글 ID (대댓글의 경우)",
-            example = "1"
-    )
-    private Long parentCommentId;
src/main/java/com/example/spot/member/application/impl/MemberInfoServiceImpl.java (1)

33-33: Avoid getById here; enforce domain-specific not-found handling

getById (a JPA reference getter) can defer throwing until field access and throws JPA-specific exceptions. Use findById(...).orElseThrow to throw your MemberHandler with the correct ErrorStatus.

Apply this diff inside the method:

-        Member member = memberRepository.getById(memberId);
+        Member member = memberRepository.findById(memberId)
+                .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND));

Add the missing import at the top of the file:

import com.example.spot.common.api.code.status.ErrorStatus;
src/main/java/com/example/spot/member/domain/validation/validator/ExistMemberValidator.java (1)

5-17: Decouple domain validator from infra implementation

This validator resides under member.domain.validation.* but depends on infrastructure.jpa.MemberRepository. That couples domain to infrastructure. Prefer depending on a domain-level port (e.g., MemberReaderPort), implemented in infra and injected via Spring. Alternatively, move the validator to application layer.

src/main/java/com/example/spot/study/application/StudyCommandServiceImpl.java (3)

252-257: Duplicate association: study.addMemberStudy(studyMember) called twice

This can create duplicate entries in collections or persistence layer (depending on mapping), leading to incorrect counts or constraint violations.

Apply this diff:

         study.addMemberStudy(studyMember);
         studyMemberRepository.save(studyMember);
-
-        study.addMemberStudy(studyMember);

273-278: Duplicate region-study association added twice

region.addRegionStudy and study.addRegionStudy are each followed by another study.addRegionStudy. Remove the duplicate call.

Apply this diff:

                     region.addRegionStudy(studyRegion);
                     study.addRegionStudy(studyRegion);
                     studyRegionRepository.save(studyRegion);
-
-                    study.addRegionStudy(studyRegion);

294-299: Duplicate study-theme association added twice

study.addStudyTheme(studyTheme) appears twice; keep one.

Apply this diff:

                     study.addStudyTheme(studyTheme);
                     theme.addStudyTheme(studyTheme);
                     studyThemeRepository.save(studyTheme);
-
-                    study.addStudyTheme(studyTheme);
src/main/java/com/example/spot/common/scheduler/PostSortLikesScheduler.java (1)

25-36: Apply DB-level limiting for top-5 posts instead of in-memory slicing

The custom Querydsl method findTopByOrderByLikeNumDesc() currently sorts all posts by like count and then you slice to five in Java. This will fetch every record before discarding all but the first five. We should push the limit down to the database:

• In src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostRepositoryImpl.java, modify the query chain to include .limit(5).

   public List<Post> findTopByOrderByLikeNumDesc() {
       return jpaQueryFactory
               .selectFrom(post)
               .leftJoin(post.likedPostList, like).fetchJoin()
               .groupBy(post.id)
               .orderBy(like.count().desc(), post.createdAt.desc())
+                .limit(5)
               .fetch();
   }

• In src/main/java/com/example/spot/common/scheduler/PostSortLikesScheduler.java, you can then drop the manual size check—simply iterate over all results (which will already be at most five):

-       List<Post> topByOrderByLikePosts = postRepository.findTopByOrderByLikeNumDesc();
-       int size = Math.min(topByOrderByLikePosts.size(), 5);
-       for (int i = 0; i < size; i++) {
+       List<Post> topByOrderByLikePosts = postRepository.findTopByOrderByLikeNumDesc();
+       for (int i = 0; i < topByOrderByLikePosts.size(); i++) {
          Post post = topByOrderByLikePosts.get(i);
          postScheduleLikesList.add(PostScheduleLikes.of(post, i + 1));
       }

This ensures only five records are ever retrieved from the DB, improving performance and reducing memory use.

src/main/java/com/example/spot/report/infrastructure/jpa/PostReportRepository.java (1)

1-9: Introduce domain ports for repository access to decouple the application layer

I see that services and use-cases across the application layer still import Spring Data repositories directly from infrastructure.jpa (e.g. ReportCommandServiceImpl, GetPostUseCaseImpl, StudyMemberQueryServiceImpl, etc.). To maintain clean layering, introduce domain/application-level “port” interfaces and have application services depend on those. Then build JPA “adapters” in infrastructure.jpa that implement each port by delegating to the Spring Data repositories.

• Define port interfaces in the domain or application module (e.g.
PostReportPort in com.example.spot.report.domain or com.example.spot.report.application.port)
– Similarly for MemberReportPort, StoryReportPort, etc.

• Update application services to depend on these ports instead of the Spring Data types.
– e.g. in ReportCommandServiceImpl, replace imports of
com.example.spot.report.infrastructure.jpa.PostReportRepository
with com.example.spot.report.application.port.PostReportPort.

• In infrastructure.jpa, implement each port as an adapter:

@Repository
public class SpringDataPostReportAdapter implements PostReportPort {
    private final PostReportRepository jpaRepo;
    // delegate methods…
}

This ensures that your domain/application layers remain framework-agnostic and only the infra package holds Spring Data details.

src/main/java/com/example/spot/notification/application/impl/GetNotificationUseCaseImpl.java (1)

67-69: Remove lossy int cast when computing counts

Casting Stream.count() (long) to int and back to long can overflow. Use count() directly.

-                .uncheckedNotificationCount((long) (int) notificationDTOs.stream()
-                        .filter(notificationDTO -> !notificationDTO.getIsChecked()).count())
+                .uncheckedNotificationCount(
+                        notificationDTOs.stream()
+                                .filter(notificationDTO -> !notificationDTO.getIsChecked())
+                                .count()
+                )

Apply the same change in both methods (Line 67-69 and Line 115-116).

Also applies to: 115-116

src/main/java/com/example/spot/study/application/StudyQueryServiceImpl.java (3)

306-308: Overly strict empty check blocks valid recommendations

Currently throws when either theme-based or region-based lists are empty. Users should still see recommendations if at least one source has results. Change || to &&.

-        if (preferRegionStudies.isEmpty() || preferThemeStudies.isEmpty()) {
+        if (preferRegionStudies.isEmpty() && preferThemeStudies.isEmpty()) {
             throw new StudyHandler(ErrorStatus._STUDY_IS_NOT_MATCH);
         }

579-581: Inefficient and incorrect region filtering (repeated queries, ignored loop variable)

You compute region but then stream over regions and repeatedly query using the same region variable, ignoring regionStudy. Query once for the selected region.

-        // 스터디 지역 조회
-        List<StudyRegion> regionStudies = regions.stream()
-                .flatMap(regionStudy -> studyRegionRepository.findAllByRegion(region).stream())
-                .toList();
+        // 스터디 지역 조회 (선택된 단일 지역으로 한 번만 조회)
+        List<StudyRegion> regionStudies = studyRegionRepository.findAllByRegion(region);

795-796: Filtered stream result is not used (no reassignment)

The filter has no effect because the result isn’t assigned. This likely intended to mirror the pattern used elsewhere (e.g., ongoing studies).

-        studies.stream().filter(study -> study.getStatus().equals(Status.ON)).toList();
+        studies = studies.stream()
+                .filter(study -> study.getStatus().equals(Status.ON))
+                .toList();
src/main/java/com/example/spot/todo/application/impl/ToggleToDoUseCaseImpl.java (1)

23-26: Writes under class-level @transactional(readOnly = true) — persistence may not flush.

checkToDoList performs writes (toDo.check(), notificationRepository.save(...), toDoRepository.save(...)) but the class is @Transactional(readOnly = true). Without overriding, JPA providers may skip flushes or optimize away writes.

Apply a method-level override:

 @Service
 @Transactional(readOnly = true)
 @RequiredArgsConstructor
 public class ToggleToDoUseCaseImpl implements ToggleToDoUseCase {

     @Override
+    @Transactional // override readOnly=true at class-level for this write operation
     public ToDoListUpdateResponseDTO checkToDoList(Long studyId, Long toDoListId) {

Optionally, consider batching notification writes for performance:

// build and saveAll(...) to reduce N roundtrips
List<Notification> notifications = members.stream()
    .map(m -> Notification.builder()
        .member(m)
        .notifierName(toDo.getMember().getName())
        .study(toDo.getStudy())
        .type(NotifyType.TO_DO_UPDATE)
        .isChecked(Boolean.FALSE)
        .build())
    .toList();
notificationRepository.saveAll(notifications);

Also applies to: 44-102

src/main/java/com/example/spot/auth/application/legacy/AuthServiceImpl.java (1)

145-148: Passwords are stored and compared in plain text — hash with PasswordEncoder.

Currently the server compares decrypted raw passwords and persists raw passwords (including temp passwords). This is a critical security flaw. Store only salted hashes and use passwordEncoder.matches(...) for verification.

Apply targeted diffs:

  1. Verify password on sign-in
-        String password = getDecryptedPassword(rsaId, signInDTO.getPassword());
-        if (!password.equals(member.getPassword())) {
+        String password = getDecryptedPassword(rsaId, signInDTO.getPassword());
+        if (!passwordEncoder.matches(password, member.getPassword())) {
             throw new MemberHandler(ErrorStatus._MEMBER_PASSWORD_NOT_MATCH);
         }
  1. Store hashed password on sign-up
-                .password(password)
+                .password(passwordEncoder.encode(password))
  1. Store hashed temporary password
-        member.setPassword(tempPw);
+        member.setPassword(passwordEncoder.encode(tempPw));

Add the encoder dependency and import (outside the selected lines):

// add field among constructor-injected dependencies
private final org.springframework.security.crypto.password.PasswordEncoder passwordEncoder;

Migration note:

  • Existing plain-text passwords will no longer match. Plan a one-time migration: either force password reset or perform a staged rollout that re-hashes on next successful login (detect plain vs hashed format and update).

Also applies to: 302-302, 396-400

src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentResponse.java (1)

1-19: Remove leftover nested-comment fields and mappings

We’ve still got several artifacts from the old nested-comment model that must be stripped out or refactored now that comments are flat:

• PostComment domain/entity (src/main/java/com/example/spot/post/domain/PostComment.java)
@joincolumn(name = "parent_id") and private PostComment parentComment
@onetomany(mappedBy = "parentComment") private List<PostComment> childrenComment

• Repository sorting (src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java)
postComment.parentComment.id.asc()

• DTOs
– CommentDetailResponse (src/.../CommentDetailResponse.java): private Long parentCommentId and its builder mapping
– CommentCreateResponse (src/.../CommentCreateResponse.java): private Long parentCommentId and toDTOwithParent
– CommentCreateRequest (src/.../CommentCreateRequest.java): private Long parentCommentId

• Controller & use-case layers
– ManagePostCommentController (src/.../ManagePostCommentController.java): docs/comments referencing parentCommentId
– ManagePostCommentUseCaseImpl (src/.../ManagePostCommentUseCaseImpl.java): .parentComment(null)

Please remove these fields, mappings, and related documentation so that no parentId, children, or depth-related code remains.

src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java (1)

43-56: Consolidate refresh-token deletion naming for clarity

Both deleteByMemberId(...) and deleteAllByMemberId(...) will delete all matching rows, but using two names is confusing. Choose one – we recommend deleteAllByMemberId(...) – and apply it everywhere.

Please update the following call sites:

  • src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java
    • line 43: refreshTokenRepository.deleteByMemberId(existing.getId());
    • line 55: refreshTokenRepository.deleteByMemberId(member.getId());
  • src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenService.java
    • line 69: refreshTokenRepository.deleteByMemberId(member.getId());
  • src/main/java/com/example/spot/auth/application/legacy/AuthServiceImpl.java
    • line 266: refreshTokenRepository.deleteByMemberId(member.getId());

Example diff:

- refreshTokenRepository.deleteByMemberId(member.getId());
+ refreshTokenRepository.deleteAllByMemberId(member.getId());

Optionally, once all callers use deleteAllByMemberId, remove the redundant deleteByMemberId(...) declaration from RefreshTokenRepository.

src/main/java/com/example/spot/post/domain/PostComment.java (2)

45-52: Nested comment relations remain while “대댓글” logic is being removed.

If nested comments are no longer supported, mark these as deprecated or remove them to avoid accidental usage and simplify persistence graphs.


29-35: Remove unused comment-like fields in PostComment
The likeNum, disLikeNum and likedPostCommentsList properties on PostComment are never read or written outside of the class itself—there are no service, controller, DTO or test references to comment-level likes/dislikes. We should remove them to keep the domain model clean and avoid accidental coupling.

• File to update:

  • src/main/java/com/example/spot/post/domain/PostComment.java (remove lines 29–35)

Proposed diff:

-    private int likeNum;
-
-    private int disLikeNum;
-
-    @OneToMany(mappedBy = "postComment")
-    @Builder.Default
-    private List<LikedPostComment> likedPostCommentsList = new ArrayList<>();
src/test/java/com/example/spot/service/notification/NotificationCommandServiceTest.java (1)

71-73: Bug: notification2 assigned twice; notification3 left null.

This will break tests that reference notification3.

Apply this fix:

-        this.notification2 = Notification.builder()
-                .id(3L).study(study).member(member).type(NotifyType.STUDY_APPLY).notifierName("Test")
-                .isChecked(true).build();
+        this.notification3 = Notification.builder()
+                .id(3L).study(study).member(member).type(NotifyType.STUDY_APPLY).notifierName("Test")
+                .isChecked(true).build();
src/test/java/com/example/spot/service/notification/NotificationQueryServiceTest.java (1)

63-66: Bug: notification2 assigned twice; notification3 left null.

Mirror the fix from the command service test to prevent NPEs or logic errors.

Apply this fix:

-        this.notification2 = Notification.builder()
-                .id(3L).study(study).member(member).type(NotifyType.STUDY_APPLY).notifierName("Test")
-                .isChecked(true).build();
+        this.notification3 = Notification.builder()
+                .id(3L).study(study).member(member).type(NotifyType.STUDY_APPLY).notifierName("Test")
+                .isChecked(true).build();
src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java (2)

63-67: Enforce DB-level uniqueness and handle concurrent scrapping

To prevent duplicate scraps under concurrent requests and remove the unnecessary saveAndFlush, please:

• In src/main/java/com/example/spot/post/domain/association/MemberScrap.java, add a unique constraint on (member_id, post_id):

 @Entity
+@Table(
+  name = "member_scrap",
+  uniqueConstraints = @UniqueConstraint(
+    name = "uk_member_scrap_member_post",
+    columnNames = {"member_id", "post_id"}
+  )
+)
 public class MemberScrap extends BaseEntity {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
     …
 }

• In src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java, replace the saveAndFlush call with a save wrapped in a try/catch for DataIntegrityViolationException. This preserves the pre-check for existing scraps (so tests stay green) and converts any DB unique-constraint violation into the domain error:

-        memberScrapRepository.saveAndFlush(memberScrap);
+        try {
+            memberScrapRepository.save(memberScrap);
+        } catch (DataIntegrityViolationException e) {
+            // handles race condition when two threads insert the same scrap
+            throw new PostHandler(ErrorStatus._POST_ALREADY_SCRAPPED);
+        }
         long scrapCount = memberScrapRepository.countByPostId(postId);

• Add the import if missing:

import org.springframework.dao.DataIntegrityViolationException;

120-127: Ensure deletePostIds is validated to prevent NPEs
The ScrapAllDeleteRequest currently allows a null or empty list, which will cause an NPE when you stream over request.getDeletePostIds(). Please add validation at the DTO or controller boundary.

Suggestions:

  • In ScrapAllDeleteRequest (src/main/java/com/example/spot/post/presentation/dto/request/post/ScrapAllDeleteRequest.java)
    • Annotate the field:
      @NotNull
      @NotEmpty
      @Schema(description = "삭제할 게시글 Id 리스트 입니다.")
      private List<Long> deletePostIds;
  • In ScrapPostController (src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java)
    • Mark the request parameter with @Valid:
      public ApiResponse<ScrapsPostDeleteResponse> cancelPostScraps(
          @Valid @RequestBody ScrapAllDeleteRequest request
      ) { … }
  • (Optional) Add a short-circuit check at the start of cancelPostScraps in ScrapPostUseCaseImpl to throw a clear exception if the list is null/empty.

Applying bean validation end-to-end will give clients immediate feedback and avoids unexpected NPEs in your service layer.

src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java (1)

55-57: Add @Valid to enforce request DTO constraints.

If ScrapAllDeleteRequest has bean validations (@NotNull/@notempty), ensure they’re triggered.

-    public ApiResponse<ScrapsPostDeleteResponse> deleteAllPostScrap(
-            @RequestBody ScrapAllDeleteRequest request) {
+    public ApiResponse<ScrapsPostDeleteResponse> deleteAllPostScrap(
+            @RequestBody @jakarta.validation.Valid ScrapAllDeleteRequest request) {
src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java (2)

21-27: Align query with “no nested comments” policy

If replies are removed, filter to top-level comments and simplify ordering. Current ordering by parentComment then createdAt implies reply hierarchy.

-                .where(
-                        postComment.post.id.eq(postId)
-                )
-                .orderBy(
-                        postComment.parentComment.id.asc().nullsFirst(),
-                        postComment.createdAt.asc()
-                )
+                .where(
+                        postComment.post.id.eq(postId),
+                        postComment.parentComment.isNull() // top-level only
+                )
+                .orderBy(
+                        postComment.createdAt.asc()
+                )

18-29: Remove lingering parentComment references in the post module

Several classes still reference the removed “replies” hierarchy. Please remove or refactor all of the following to align with the flat comment model:

• Domain model
• src/main/java/com/example/spot/post/domain/PostComment.java (fields parentComment, childrenComment)
• Controller
• src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java (parentCommentId path param and docs)
• DTOs
• src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java (parentCommentId)
• src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java (parentCommentId, comment.getParentComment())
• src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java (toDTOwithParent)
• Repository implementation
• src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java (orderBy postComment.parentComment.id)
• Use‐case
• src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java (.parentComment(null))
• Tests
• All tests in src/test/java/com/example/spot/service/post/** referencing parentCommentId or using PostComment.builder(parentComment…)

In PostCommentRepositoryImpl, for example, replace:

- .orderBy(
-     postComment.parentComment.id.asc().nullsFirst(),
-     postComment.createdAt.asc()
- )
+ .orderBy(postComment.createdAt.asc())

Ensure controllers, DTOs, use cases, domain classes, and tests no longer rely on parentComment.

src/test/java/com/example/spot/service/post/PostCommandServiceTest.java (3)

153-170: Dead stubbing for comment-like/dislike remains in setUp

All stubbings against likedPostCommentRepository are now dead code. Please delete them to avoid misleading future maintainers.

-        // LikedPostComment
-        when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedFalse(1L, 1L))
-                .thenReturn(Optional.empty());
-        when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedFalse(2L, 1L))
-                .thenReturn(Optional.of(member2LikedComment1));
-        when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedTrue(1L, 1L))
-                .thenReturn(Optional.of(member1LikedComment1));
-        when(likedPostCommentRepository.findByMemberIdAndPostCommentIdAndIsLikedTrue(2L, 1L))
-                .thenReturn(Optional.empty());
-        when(likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(1L)).thenReturn(1L);
-        when(likedPostCommentRepository.countByPostCommentIdAndIsLikedTrue(2L)).thenReturn(0L);
-        when(likedPostCommentRepository.countByPostCommentIdAndIsLikedFalse(1L)).thenReturn(1L);
-        when(likedPostCommentRepository.countByPostCommentIdAndIsLikedFalse(2L)).thenReturn(0L);
-        when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(1L, 1L)).thenReturn(true);
-        when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedTrue(2L, 1L)).thenReturn(false);
-        when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(1L, 1L)).thenReturn(false);
-        when(likedPostCommentRepository.existsByMemberIdAndPostCommentIdAndIsLikedFalse(2L, 1L)).thenReturn(true);

546-572: Child comment creation test contradicts the removal of nested comments

createComment_Child_Success should be removed or updated to assert failure (e.g., PostHandler) if 대댓글 creation is no longer allowed. Keeping it will either fail or encourage regressions.

Example update:

-    @Test
-    @DisplayName("댓글 작성 - 하위 댓글 (성공)")
-    void createComment_Child_Success() {
+    @Test
+    @DisplayName("댓글 작성 - 하위 댓글은 금지됨 (실패)")
+    void createComment_Child_Fail() {
...
-        CommentCreateResponse result = managePostCommentUseCase.createComment(postId, memberId, commentCreateRequest);
-        assertNotNull(result);
-        assertThat(result.getId()).isEqualTo(2L);
-        assertThat(result.getContent()).isEqualTo("댓글2");
-        assertThat(result.getWriter()).isEqualTo("회원2");
+        assertThrows(PostHandler.class,
+            () -> managePostCommentUseCase.createComment(postId, memberId, commentCreateRequest));
     }

79-90: Remove stale comment-like/dislike mocks from PostCommandServiceTest

It looks like the comment-like/dislike feature has been removed, but the test still declares and stubs its collaborators—please clean these up to reduce noise and avoid accidental coupling.

• In src/test/java/com/example/spot/service/post/PostCommandServiceTest.java:

  • Remove the LikedPostCommentRepository mock (around line 80) and all related when(likedPostCommentRepository…) stubs (lines 154–169).
  • Remove the GetLikedPostCommentUseCase mock (around line 89) and any related setup or invocations.

Updated diff might look like:

-    @Mock
-    private LikedPostCommentRepository likedPostCommentRepository;
...
-    @Mock
-    private GetLikedPostCommentUseCase getLikedPostCommentUseCase;

And delete all corresponding when(likedPostCommentRepository…) calls in your @BeforeEach setup.

♻️ Duplicate comments (3)
src/main/java/com/example/spot/post/application/query/impl/GetLikedPostUseCaseImpl.java (1)

4-4: Same layering concern as earlier

GetLikedPostUseCaseImpl now imports LikedPostRepository from infrastructure.jpa, coupling the application layer to infrastructure details. Please see the architectural advice left on PostScheduleRealTimeRepository.java.

src/main/java/com/example/spot/post/application/command/impl/LikePostUseCaseImpl.java (1)

7-14: Application layer now depends on infrastructure packages

All three repositories are referenced from infrastructure.jpa, repeating the layering leak noted earlier. Recommend injecting domain-level repository ports instead.

src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenService.java (1)

5-11: Layering breach repeated

JwtTokenService imports infra-specific repository interfaces, coupling auth logic to JPA. Refer to prior architectural comment for suggested remedy.

Comment on lines +49 to +56
// 부모 댓글 생성
PostComment comment = PostComment.builder()
.content(request.getContent())
.isAnonymous(request.isAnonymous())
.post(post)
.parentComment(null)
.member(member)
.build();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Behavior change: always creating top-level comments

This matches the PR goal (remove replies). Two follow-ups:

  • If CommentCreateRequest still contains any parentCommentId-like field, explicitly reject non-null/non-zero values to avoid silently ignoring client intent.
  • Update public API/docs/DTOs accordingly (controller docs already flagged).

I can propose DTO and controller changes if you confirm the request shape.

🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/application/command/impl/ManagePostCommentUseCaseImpl.java
around lines 49 to 56, the code always creates top-level comments by setting
parentComment to null. To prevent silently ignoring client intent, check if the
CommentCreateRequest contains a parentCommentId or similar field, and if so,
explicitly reject any non-null or non-zero values by throwing an exception or
returning an error. Also, update the DTOs and public API documentation to
reflect that replies are no longer supported.

package com.example.spot.post.application.query.impl;

import com.example.spot.comment.domain.association.LikedPostCommentRepository;
import com.example.spot.post.infrastructure.jpa.LikedPostCommentRepository;
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

Application should depend on a port, not infrastructure.jpa; also confirm keeping comment-like queries aligns with PR.

  • Importing ...infrastructure.jpa.LikedPostCommentRepository from application couples app to infra. Prefer a port interface with an infra adapter.
  • PR objective mentions removing comment-like logic. This use case still exposes like/dislike query endpoints. If the feature was removed, consider deleting this use case and repository; if read-only querying is intentionally retained, please confirm.

Check whether this use case is still referenced:


🏁 Script executed:

#!/bin/bash
rg -n -S 'GetLikedPostCommentUseCase|countByPostCommentIdAndIsLikedTrue|existsByMemberIdAndPostCommentId' --glob '!**/build/**' --glob '!**/target/**'

Length of output: 6891


Enforce hexagonal layering for comment-like queries & confirm PR intent

The GetLikedPostCommentUseCaseImpl still pulls in the JPA repository and is widely referenced, so either we introduce a domain port or remove this functionality altogether.

• In src/main/java/com/example/spot/post/application/query/impl/GetLikedPostCommentUseCaseImpl.java
– Replace direct import of
com.example.spot.post.infrastructure.jpa.LikedPostCommentRepository
with an application‐level port (e.g. LikedPostCommentPort).
• In src/main/java/com/example/spot/post/infrastructure/jpa/LikedPostCommentRepository.java
– Implement the new port interface instead of exposing repository methods directly to the application layer.
• References to this use case in:

  • src/main/java/com/example/spot/post/application/query/impl/GetPostUseCaseImpl.java (lines ~283–286)
  • src/test/java/com/example/spot/service/post/PostCommandServiceTest.java
  • src/test/java/com/example/spot/service/post/GetPostUseCaseTest.java

Also, the PR description suggests removing “comment-like” logic, but these read-only endpoints remain. If you intend to drop this feature, please delete the use case, its port, adapter, and all related tests. If instead you’re retaining read-only comment-like queries, introduce the port pattern above.

Comment on lines +1 to +3
package com.example.spot.post.infrastructure.querydsl;

import com.example.spot.post.domain.PostComment;
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

Custom repository wiring: verify Spring Data discovery of the Impl

With the custom interface in post.infrastructure.querydsl and the main repository likely in post.infrastructure.jpa, ensure:

  • PostCommentRepository extends PostCommentRepositoryCustom, and
  • PostCommentRepositoryImpl is in the same package (or a subpackage) as PostCommentRepository (typically post.infrastructure.jpa) per Spring Data naming rules.

Otherwise, the custom methods won’t be picked up at runtime.

Run to verify locations and wiring:


🏁 Script executed:

#!/bin/bash
echo "PostCommentRepository declaration (with package context):"
rg -n -A2 -B2 -S $'^\\s*public\\s+interface\\s+PostCommentRepository\\b'

echo
echo "Does PostCommentRepository extend the custom interface?"
rg -n -S 'interface\\s+PostCommentRepository\\s+extends[^\\{]*PostCommentRepositoryCustom'

echo
echo "PostCommentRepositoryImpl location (with package context):"
rg -n -A2 -B2 -S $'^\\s*public\\s+class\\s+PostCommentRepositoryImpl\\b' || true

Length of output: 1921


Custom repository wiring: relocate your implementation for Spring Data discovery

I confirmed that:

  • PostCommentRepository (in com.example.spot.post.infrastructure.jpa) correctly extends JpaRepository<PostComment, Long> and PostCommentRepositoryCustom.
  • PostCommentRepositoryImpl implements PostCommentRepositoryCustom but lives in
    com.example.spot.post.infrastructure.querydsl.impl.

Spring Data JPA only picks up custom impls named …Impl when they reside in the same package (or a subpackage) as the repository interface. To fix this, move your implementation class to align with the repository’s package, for example:

• From:
src/main/java/com/example/spot/post/infrastructure/querydsl/impl/PostCommentRepositoryImpl.java
• To:
src/main/java/com/example/spot/post/infrastructure/jpa/impl/PostCommentRepositoryImpl.java

This ensures Spring Data will wire your custom methods at runtime.

🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/infrastructure/querydsl/PostCommentRepositoryCustom.java
at the beginning of the file, the custom repository implementation
PostCommentRepositoryImpl is currently located in the package
com.example.spot.post.infrastructure.querydsl.impl, which prevents Spring Data
JPA from discovering it. To fix this, move the PostCommentRepositoryImpl.java
file from the querydsl.impl package to the jpa.impl package under
com.example.spot.post.infrastructure.jpa.impl, aligning it with the
PostCommentRepository interface package so Spring Data can wire the custom
implementation correctly at runtime.

Comment on lines 39 to 41
context.buildConstraintViolationWithTemplate(errorStatus.getMessage())
.addConstraintViolation();
.addConstraintViolation();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Bind validation message to the field for better UX

Attach the violation to "postId" so clients can render field-specific errors.

-            context.buildConstraintViolationWithTemplate(errorStatus.getMessage())
-                    .addConstraintViolation();
+            context.buildConstraintViolationWithTemplate(errorStatus.getMessage())
+                    .addPropertyNode("postId")
+                    .addConstraintViolation();
📝 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
context.buildConstraintViolationWithTemplate(errorStatus.getMessage())
.addConstraintViolation();
.addConstraintViolation();
}
context.buildConstraintViolationWithTemplate(errorStatus.getMessage())
.addPropertyNode("postId")
.addConstraintViolation();
}
🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/infrastructure/validator/ExistPostValidator.java
around lines 39 to 41, the validation error message is not bound to the specific
field "postId". Modify the constraint violation builder to add the violation
specifically to the "postId" property by calling addPropertyNode("postId")
before addConstraintViolation(), so that the error message is associated with
the correct field for better client-side error rendering.

Comment on lines 31 to +39
description = """
게시글 Id와 회원 Id를 받아 댓글을 생성합니다.

댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다.

익명 여부 선택할 수 있습니다.

생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0일 경우 null로 반환), 댓글 내용, 작성자를 반환합니다.
""")
게시글 Id와 회원 Id를 받아 댓글을 생성합니다.
댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다.
익명 여부 선택할 수 있습니다.
생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0일 경우 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

Update API docs: replies no longer supported

The @Operation description still mentions replies and parentCommentId. Align docs with the new behavior (only top-level comments).

Apply:

 @Operation(summary = "[게시판] 댓글 생성 API",
-        description = """
-                게시글 Id와 회원 Id를 받아 댓글을 생성합니다.
-                
-                댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다.
-                
-                익명 여부 선택할 수 있습니다.
-                
-                생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0일 경우 null로 반환), 댓글 내용, 작성자를 반환합니다.
-                """)
+        description = """
+                게시글 Id와 회원 Id를 받아 댓글을 생성합니다.
+                
+                현재는 대댓글(답글)을 지원하지 않으며, 생성되는 모든 댓글은 최상위 댓글입니다.
+                익명 여부를 선택할 수 있습니다.
+                
+                생성된 댓글의 고유 ID, 댓글 내용, 작성자를 반환합니다.
+                """)
📝 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
description = """
게시글 Id와 회원 Id를 받아 댓글을 생성합니다.
댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다.
익명 여부 선택할 있습니다.
생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0 경우 null로 반환), 댓글 내용, 작성자를 반환합니다.
""")
게시글 Id와 회원 Id를 받아 댓글을 생성합니다.
댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다.
익명 여부 선택할 있습니다.
생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0 경우 null로 반환), 댓글 내용, 작성자를 반환합니다.
""")
@Operation(summary = "[게시판] 댓글 생성 API",
description = """
게시글 Id와 회원 Id를 받아 댓글을 생성합니다.
현재는 대댓글(답글) 지원하지 않으며, 생성되는 모든 댓글은 최상위 댓글입니다.
익명 여부를 선택할 있습니다.
생성된 댓글의 고유 ID, 댓글 내용, 작성자를 반환합니다.
""")
🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java
around lines 31 to 39, the @Operation description still references replies and
parentCommentId, which are no longer supported. Update the description to remove
any mention of replies and parentCommentId, clarifying that only top-level
comments are created and returned, reflecting the current API behavior.

Comment on lines +32 to 34
allowableValues = {"ALL", "PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK",
"SPOT_ANNOUNCEMENT"}
)
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

Remove invalid "ALL" from allowableValues for update requests

"ALL" is a collection filter, not a post’s type. Leaving it here misleads clients and likely breaks Board.findByValue("ALL").

Apply this diff:

-            allowableValues = {"ALL", "PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK",
-                    "SPOT_ANNOUNCEMENT"}
+            allowableValues = {"PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK",
+                    "SPOT_ANNOUNCEMENT"}

Optional: for image uploads, document file type explicitly for OpenAPI (type: string, format: binary), or annotate via @Schema(type = "string", format = "binary") on MultipartFile.

Run to confirm "ALL" is not a Board constant and to prevent future drift:


🏁 Script executed:

#!/bin/bash
# Show Board enum and its constants
rg -n -A 40 $'enum\\s+Board\\b'
# Grep for ALL in Board enum context (should be absent)
rg -n -A 2 -B 2 $'enum\\s+Board\\b' | rg -n 'ALL' || echo "OK: 'ALL' not found in Board"

Length of output: 1832


Remove “ALL” from allowableValues in PostUpdateRequest

“ALL” is the collection‐filter constant, not a valid board type for an update. Exposing it in the update DTO may lead clients to send an unsupported value.

• File to update:
src/main/java/com/example/spot/post/presentation/dto/request/post/PostUpdateRequest.java
(around lines 32–34)

• Apply this diff:

-    @Schema(
-        description = "게시판 타입",
-        allowableValues = {"ALL", "PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK", "SPOT_ANNOUNCEMENT"}
-    )
+    @Schema(
+        description = "게시판 타입",
+        allowableValues = {"PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK", "SPOT_ANNOUNCEMENT"}
+    )

Optional: For any MultipartFile fields, add @Schema(type = "string", format = "binary") so OpenAPI correctly documents binary uploads.

🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/presentation/dto/request/post/PostUpdateRequest.java
around lines 32 to 34, remove "ALL" from the allowableValues array for the board
type validation annotation because "ALL" is not a valid board type for updates
and should not be exposed in the update DTO. Additionally, if there are any
MultipartFile fields in this class, add the annotation @Schema(type = "string",
format = "binary") to those fields to ensure OpenAPI correctly documents binary
file uploads.

Comment on lines +1 to +3
package com.example.spot.post.presentation.dto.response.comment;

import com.example.spot.comment.domain.PostComment;
import com.example.spot.post.domain.PostComment;
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

Package migration OK; remove nested-comment constructs to match PR objective.

Given 대댓글 removal, drop parentCommentId and toDTOwithParent(...) to avoid dead fields/APIs and confusion.

Proposed streamlined DTO (outside the changed lines):

package com.example.spot.post.presentation.dto.response.comment;

import com.example.spot.post.domain.PostComment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@AllArgsConstructor
public class CommentCreateResponse {
    @Schema(description = "댓글 ID입니다.", example = "1")
    private Long id;

    @Schema(description = "댓글 내용입니다.", example = "댓글 내용")
    private String content;

    @Schema(description = "작성자 이름입니다.", example = "작성자")
    private String writer;

    public static CommentCreateResponse toDTO(PostComment comment) {
        return CommentCreateResponse.builder()
            .id(comment.getId())
            .content(comment.getContent())
            .writer(comment.isAnonymous() ? "익명" : comment.getMember().getNickname())
            .build();
    }
}

Verify no remaining usages:

If you want, I can create a follow-up PR to remove these safely.


🏁 Script executed:

#!/bin/bash
rg -n -S 'toDTOwithParent|parentCommentId' --glob '!**/build/**' --glob '!**/target/**'

Length of output: 1785


Action Required: Remove Nested-Comment Constructs Across the Codebase

The ripgrep search shows that parentCommentId and toDTOwithParent are still referenced in several places. Before merging this PR, please remove these dead fields and methods and update the affected code and tests accordingly:

• src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java
– Drop the private Long parentCommentId; field
– Remove the toDTOwithParent(PostComment comment, Long parentCommentId) method

• src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentDetailResponse.java
– Remove private Long parentCommentId; and its mapping in the factory

• src/main/java/com/example/spot/post/presentation/dto/request/comment/CommentCreateRequest.java
– Remove private Long parentCommentId;

• src/main/java/com/example/spot/post/presentation/controller/command/ManagePostCommentController.java
– Eliminate any handling or Javadoc referring to parentCommentId

• src/test/java/com/example/spot/service/post/PostCommandServiceTest.java
– Update or remove the .parentCommentId(...) invocations at lines 531, 558, and 585

Here’s the revised CommentCreateResponse as an example of the streamlined DTO:

package com.example.spot.post.presentation.dto.response.comment;

import com.example.spot.post.domain.PostComment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@AllArgsConstructor
public class CommentCreateResponse {
    @Schema(description = "댓글 ID입니다.", example = "1")
    private Long id;

    @Schema(description = "댓글 내용입니다.", example = "댓글 내용")
    private String content;

    @Schema(description = "작성자 이름입니다.", example = "작성자")
    private String writer;

    public static CommentCreateResponse toDTO(PostComment comment) {
        return CommentCreateResponse.builder()
            .id(comment.getId())
            .content(comment.getContent())
            .writer(comment.isAnonymous() ? "익명" : comment.getMember().getNickname())
            .build();
    }
}

Please implement these removals and ensure all tests and controller behaviors are updated to reflect the absence of nested comments.

🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/presentation/dto/response/comment/CommentCreateResponse.java
lines 1 to 3 and related files, remove the private Long parentCommentId field
and the toDTOwithParent method from CommentCreateResponse. Similarly, remove
parentCommentId fields and related mappings from CommentDetailResponse and
CommentCreateRequest. In ManagePostCommentController, eliminate any handling or
documentation referring to parentCommentId. Finally, update
PostCommandServiceTest by removing or adjusting all .parentCommentId(...) calls
at lines 531, 558, and 585. Ensure all affected code and tests are updated to
reflect the removal of nested-comment constructs before merging.

@@ -1,4 +1,4 @@
package com.example.spot.post.presentation.dto.response;
package com.example.spot.post.presentation.dto.response.post;
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

Package move OK; verify all call sites and consider naming consistency.

  • Ensure all imports were updated to com.example.spot.post.presentation.dto.response.post.ScrapsPostDeleteResponse.
  • Naming nit: "ScrapsPostDeleteResponse" might read more naturally as "ScrapPostDeleteResponse" (singular).

Use this to confirm no stale imports remain:


🏁 Script executed:

#!/bin/bash
rg -n -S 'post\.presentation\.dto\.response\.ScrapsPostDeleteResponse|post\.presentation\.dto\.response\.post\.ScrapsPostDeleteResponse'

Length of output: 822


All imports updated; consider renaming class for consistency

  • Verified no stale imports remain—all call sites now reference
    com.example.spot.post.presentation.dto.response.post.ScrapsPostDeleteResponse in:

    • src/test/java/com/example/spot/service/post/PostCommandServiceTest.java
    • src/main/java/com/example/spot/post/presentation/controller/command/ScrapPostController.java
    • src/main/java/com/example/spot/post/application/command/ScrapPostUseCase.java
    • src/main/java/com/example/spot/post/application/command/impl/ScrapPostUseCaseImpl.java
  • Naming nit: for grammatical consistency, rename
    ScrapsPostDeleteResponseScrapPostDeleteResponse
    and update all imports accordingly.

🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/post/presentation/dto/response/post/ScrapsPostDeleteResponse.java
at line 1, rename the class from ScrapsPostDeleteResponse to
ScrapPostDeleteResponse to maintain grammatical consistency. After renaming the
class, update all import statements and references in the codebase, including
the specified test and implementation files, to use the new class name
ScrapPostDeleteResponse.

package com.example.spot.post.presentation.validator;

import com.example.spot.post.infrastructure.ExistPostValidator;
import com.example.spot.post.infrastructure.validator.ExistPostValidator;
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

Avoid presentation → infrastructure coupling for validator

The annotation in presentation imports a validator from infrastructure. This creates an inward dependency on infra, which is typically discouraged in layered/hexagonal setups. Prefer colocating ExistPostValidator with the annotation in presentation (or a shared validation module), or move both into a common module used by presentation and infra.

Run to verify if other annotations also couple to infrastructure.validator and that ExistPostValidator exists:

Also applies to: 15-15


🏁 Script executed:

#!/bin/bash
# 1) Find annotations whose validators live in infrastructure.validator
rg -nP '@Constraint\(\s*validatedBy\s*=\s*[^)]*infrastructure\.validator' -A 1

# 2) Ensure ExistPostValidator exists where the import points
fd -a ExistPostValidator.java

Length of output: 216


Avoid presentation → infrastructure coupling for validator

I’ve confirmed that the ExistPost annotation in the presentation layer imports its validator from the infrastructure module and that ExistPostValidator.java only lives under
src/main/java/com/example/spot/post/infrastructure/validator/ExistPostValidator.java. No other annotations reference infrastructure.validator.

To decouple layers, please:

  • Move ExistPostValidator into the presentation package alongside the annotation (e.g. src/main/java/com/example/spot/post/presentation/validator),
    or
  • Extract both the annotation and its validator into a shared validation module that’s consumable by presentation and infrastructure.

This will keep your presentation layer independent of infrastructure details.

🤖 Prompt for AI Agents
In src/main/java/com/example/spot/post/presentation/validator/ExistPost.java at
line 3, the ExistPost annotation imports its validator from the infrastructure
layer, causing unwanted coupling. To fix this, move the ExistPostValidator class
from the infrastructure.validator package into the presentation.validator
package alongside the ExistPost annotation, or alternatively, extract both the
annotation and its validator into a shared validation module that can be used by
both presentation and infrastructure layers. This will decouple the presentation
layer from infrastructure details.

import com.example.spot.common.security.utils.SecurityUtils;
import com.example.spot.member.domain.Member;
import com.example.spot.member.infrastructure.MemberRepository;
import com.example.spot.member.infrastructure.jpa.MemberRepository;
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

Avoid application → infrastructure coupling; depend on a domain/application port instead.

Importing com.example.spot.member.infrastructure.jpa.MemberRepository in the application layer couples it to infra. Prefer a domain/application-facing repository port (e.g., MemberRepository in domain or MemberReaderPort) with a JPA adapter in infra. If this coupling is intentional across the codebase, please confirm.

Run to gauge scope of infra dependencies from application/services:


🏁 Script executed:

#!/bin/bash
# Find application-layer code directly importing infra.jpa repositories.
rg -n -S 'com\.example\.spot\..*\.infrastructure\.jpa\..*Repository' --glob '!**/build/**' --glob '!**/target/**'

Length of output: 16834


Avoid direct application → infrastructure coupling

The PR correctly relocates all JPA repositories into the infrastructure module, but the application layer is still importing them (e.g. com.example.spot.member.infrastructure.jpa.MemberRepository). This breaks the intended hexagonal/clean-architecture layering. Instead:

  1. Define repository port interfaces in the domain or application layer (e.g. MemberReaderPort, MemberWriterPort, NotificationPort).
  2. Have each JPA repository in infrastructure.jpa implement the corresponding port.
  3. Update your services and use-case classes to depend on and inject the port interfaces—not the JPA classes.
  4. Adjust your Spring configuration (or component scanning) so that the JPA implementations are wired to the ports.

Affected locations (non-exhaustive):

  • All src/main/java/com/example/spot/**/application/** classes importing *.infrastructure.jpa.*Repository
  • Corresponding src/test/java/com/example/spot/** tests

Please refactor these layers to restore proper separation and avoid leaking infrastructure details into application logic.

🤖 Prompt for AI Agents
In
src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java
at line 9, the application layer is directly importing the infrastructure JPA
repository, which violates clean architecture principles. To fix this, define
repository port interfaces like MemberReaderPort in the domain or application
layer, have the JPA repository in infrastructure.jpa implement these interfaces,
and update the service to depend on and inject the port interfaces instead of
the JPA repository. Also, adjust Spring configuration to wire the JPA
implementations to the ports, ensuring the application layer does not directly
depend on infrastructure classes.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

♻️ refactor Code Refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 게시글 구조 관련 리팩토링

1 participant