Conversation
WalkthroughThis 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 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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
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 unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
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
deletedMemberIdsis empty. This prevents potential SQL generation issues forIN ()and saves a round-trip.// Token 정리 - refreshTokenRepository.deleteAllByMemberIdIn(deletedMemberIds); + if (deletedMemberIds.isEmpty()) { + return deletedMemberListDTO; + } + refreshTokenRepository.deleteAllByMemberIdIn(deletedMemberIds);Additionally, if
deletedMemberIdscan 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 translationOther 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 contextIncluding 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 instantiationPostHomeResponse 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 commentsApply 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 removalSince nested comments (대댓글) are being removed, the
parentCommentIdproperty 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 +
toDTOwithParentbuilder)- 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: AvoidgetByIdhere; enforce domain-specific not-found handling
getById(a JPA reference getter) can defer throwing until field access and throws JPA-specific exceptions. UsefindById(...).orElseThrowto throw yourMemberHandlerwith the correctErrorStatus.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 implementationThis validator resides under
member.domain.validation.*but depends oninfrastructure.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 twiceThis 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.addRegionStudyandstudy.addRegionStudyare each followed by anotherstudy.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 slicingThe 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 layerI 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” ininfrastructure.jpathat implement each port by delegating to the Spring Data repositories.• Define port interfaces in the domain or application module (e.g.
–PostReportPortincom.example.spot.report.domainorcom.example.spot.report.application.port)
– Similarly forMemberReportPort,StoryReportPort, etc.• Update application services to depend on these ports instead of the Spring Data types.
– e.g. inReportCommandServiceImpl, replace imports of
com.example.spot.report.infrastructure.jpa.PostReportRepository
withcom.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 countsCasting 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 recommendationsCurrently 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
regionbut then stream overregionsand repeatedly query using the sameregionvariable, ignoringregionStudy. 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.
checkToDoListperforms 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:
- 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); }
- Store hashed password on sign-up
- .password(password) + .password(passwordEncoder.encode(password))
- 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 mappingsWe’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") andprivate 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 parentCommentIdand its builder mapping
– CommentCreateResponse (src/.../CommentCreateResponse.java):private Long parentCommentIdand 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 clarityBoth 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
ThelikeNum,disLikeNumandlikedPostCommentsListproperties onPostCommentare 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:notification2assigned twice;notification3left 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:notification2assigned twice;notification3left 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 scrappingTo 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 thesaveAndFlushcall with asavewrapped in a try/catch forDataIntegrityViolationException. 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: EnsuredeletePostIdsis validated to prevent NPEs
TheScrapAllDeleteRequestcurrently allows anullor empty list, which will cause an NPE when you stream overrequest.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
cancelPostScrapsinScrapPostUseCaseImplto 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” policyIf 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 moduleSeveral 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 setUpAll 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 commentscreateComment_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 PostCommandServiceTestIt 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@BeforeEachsetup.
♻️ Duplicate comments (3)
src/main/java/com/example/spot/post/application/query/impl/GetLikedPostUseCaseImpl.java (1)
4-4: Same layering concern as earlier
GetLikedPostUseCaseImplnow importsLikedPostRepositoryfrominfrastructure.jpa, coupling the application layer to infrastructure details. Please see the architectural advice left onPostScheduleRealTimeRepository.java.src/main/java/com/example/spot/post/application/command/impl/LikePostUseCaseImpl.java (1)
7-14: Application layer now depends on infrastructure packagesAll 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
JwtTokenServiceimports infra-specific repository interfaces, coupling auth logic to JPA. Refer to prior architectural comment for suggested remedy.
| // 부모 댓글 생성 | ||
| PostComment comment = PostComment.builder() | ||
| .content(request.getContent()) | ||
| .isAnonymous(request.isAnonymous()) | ||
| .post(post) | ||
| .parentComment(null) | ||
| .member(member) | ||
| .build(); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Behavior change: always creating top-level comments
This matches the PR goal (remove replies). Two follow-ups:
- If
CommentCreateRequeststill contains anyparentCommentId-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; |
There was a problem hiding this comment.
💡 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.LikedPostCommentRepositoryfrom 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.javasrc/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.
| package com.example.spot.post.infrastructure.querydsl; | ||
|
|
||
| import com.example.spot.post.domain.PostComment; |
There was a problem hiding this comment.
💡 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' || trueLength of output: 1921
Custom repository wiring: relocate your implementation for Spring Data discovery
I confirmed that:
PostCommentRepository(incom.example.spot.post.infrastructure.jpa) correctly extendsJpaRepository<PostComment, Long>andPostCommentRepositoryCustom.PostCommentRepositoryImplimplementsPostCommentRepositoryCustombut 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.
| context.buildConstraintViolationWithTemplate(errorStatus.getMessage()) | ||
| .addConstraintViolation(); | ||
| .addConstraintViolation(); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| description = """ | ||
| 게시글 Id와 회원 Id를 받아 댓글을 생성합니다. | ||
|
|
||
| 댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다. | ||
|
|
||
| 익명 여부 선택할 수 있습니다. | ||
|
|
||
| 생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0일 경우 null로 반환), 댓글 내용, 작성자를 반환합니다. | ||
| """) | ||
| 게시글 Id와 회원 Id를 받아 댓글을 생성합니다. | ||
| 댓글일 경우 parentCommentId는 0이고, 대댓글일 경우 부모댓글 parentCommentId를 받습니다. | ||
| 익명 여부 선택할 수 있습니다. | ||
| 생성된 댓글의 고유 ID와 부모댓글 ID(parentCommentId가 0일 경우 null로 반환), 댓글 내용, 작성자를 반환합니다. | ||
| """) |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| allowableValues = {"ALL", "PASS_EXPERIENCE", "INFORMATION_SHARING", "COUNSELING", "JOB_TALK", "FREE_TALK", | ||
| "SPOT_ANNOUNCEMENT"} | ||
| ) |
There was a problem hiding this comment.
💡 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.
| package com.example.spot.post.presentation.dto.response.comment; | ||
|
|
||
| import com.example.spot.comment.domain.PostComment; | ||
| import com.example.spot.post.domain.PostComment; |
There was a problem hiding this comment.
💡 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; | |||
There was a problem hiding this comment.
💡 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.ScrapsPostDeleteResponsein:- 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
ScrapsPostDeleteResponse→ScrapPostDeleteResponse
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; |
There was a problem hiding this comment.
💡 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.javaLength 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
ExistPostValidatorinto 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; |
There was a problem hiding this comment.
💡 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:
- Define repository port interfaces in the domain or application layer (e.g.
MemberReaderPort,MemberWriterPort,NotificationPort). - Have each JPA repository in
infrastructure.jpaimplement the corresponding port. - Update your services and use-case classes to depend on and inject the port interfaces—not the JPA classes.
- 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.
#️⃣ 연관된 이슈
🔎 작업 내용
📷 스크린샷 (선택)
💬리뷰 요구사항 (선택)
Summary by CodeRabbit
Removed Features
Bug Fixes
Refactor
Tests