From 07d0a2e4342d12f624340d27a01dc9a4d8631fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=91=EC=A7=80=EC=9C=A4?= <155087951+jiyun921@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:58:10 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[refactor]=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=ED=95=99=EA=B3=BC=20=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#322)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feat]: 기계로봇자동차공학부, 재료공학과, 항공우주모빌리티공학과 추가 * [refactor]: 학과 목록 개수 수정 --- .../kuring/notice/domain/DepartmentName.java | 3 ++ .../engineering/AeroMobilityDept.java | 36 +++++++++++++++++++ .../deptinfo/engineering/MaterialDept.java | 36 +++++++++++++++++++ .../engineering/MechaRobotAutoDept.java | 36 +++++++++++++++++++ .../acceptance/CategoryAcceptanceTest.java | 2 +- 5 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/AeroMobilityDept.java create mode 100644 src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MaterialDept.java create mode 100644 src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MechaRobotAutoDept.java diff --git a/src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java b/src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java index ebfa8311f..2ef9eb11f 100644 --- a/src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java +++ b/src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java @@ -38,6 +38,9 @@ public enum DepartmentName { ADV_INDUSTRIAL("advanced_industrial", "aif", "신산업융합학과"), BIOLOGICAL("biological", "microbio", "생물공학과"), KBEAUTY("kbeauty_industry_fusion", "kbeauty", "K뷰티산업융합학과"), + MECHA_ROBOT_AUTO("mechanical_robot_automotive", "me", "기계로봇자동차공학부"), + MATERIAL("materials_science", "mse", "재료공학과"), + AERO_MOBILITY("aerospace_mobility", "aeroeng", "항공우주모빌리티공학과"), POLITICS("political_science", "kupol", "정치외교학과"), ECONOMICS("economics", "econ", "경제학과"), diff --git a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/AeroMobilityDept.java b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/AeroMobilityDept.java new file mode 100644 index 000000000..da8f1e08a --- /dev/null +++ b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/AeroMobilityDept.java @@ -0,0 +1,36 @@ +package com.kustacks.kuring.worker.scrap.deptinfo.engineering; + +import com.kustacks.kuring.worker.parser.notice.LatestPageNoticeHtmlParser; +import com.kustacks.kuring.worker.scrap.client.notice.LatestPageGraduateNoticeApiClient; +import com.kustacks.kuring.worker.scrap.client.notice.LatestPageNoticeApiClient; +import com.kustacks.kuring.worker.scrap.client.notice.property.LatestPageNoticeProperties; +import com.kustacks.kuring.worker.scrap.deptinfo.NoticeScrapInfo; +import com.kustacks.kuring.worker.scrap.deptinfo.RegisterDepartmentMap; +import com.kustacks.kuring.worker.scrap.deptinfo.StaffScrapInfo; + +import java.util.List; + +import static com.kustacks.kuring.notice.domain.DepartmentName.AERO_MOBILITY; + +@RegisterDepartmentMap(key = AERO_MOBILITY) +public class AeroMobilityDept extends EngineeringCollege { + + public AeroMobilityDept( + LatestPageNoticeApiClient latestPageNoticeApiClient, + LatestPageNoticeHtmlParser latestPageNoticeHtmlParser, + LatestPageNoticeProperties latestPageNoticeProperties, + LatestPageGraduateNoticeApiClient latestPageGraduateNoticeApiClient + ) { + super(); + this.noticeApiClient = latestPageNoticeApiClient; + this.htmlParser = latestPageNoticeHtmlParser; + this.latestPageNoticeProperties = latestPageNoticeProperties; + + List siteIds = List.of(11837); + this.staffScrapInfo = new StaffScrapInfo(AERO_MOBILITY.getHostPrefix(), siteIds); + this.noticeScrapInfo = new NoticeScrapInfo(AERO_MOBILITY.getHostPrefix(), 284); + this.departmentName = AERO_MOBILITY; + this.noticeGraduationInfo = new NoticeScrapInfo(AERO_MOBILITY.getHostPrefix(), 761); + this.latestPageGraduateNoticeApiClient = latestPageGraduateNoticeApiClient; + } +} diff --git a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MaterialDept.java b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MaterialDept.java new file mode 100644 index 000000000..b0029be99 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MaterialDept.java @@ -0,0 +1,36 @@ +package com.kustacks.kuring.worker.scrap.deptinfo.engineering; + +import com.kustacks.kuring.worker.parser.notice.LatestPageNoticeHtmlParser; +import com.kustacks.kuring.worker.scrap.client.notice.LatestPageGraduateNoticeApiClient; +import com.kustacks.kuring.worker.scrap.client.notice.LatestPageNoticeApiClient; +import com.kustacks.kuring.worker.scrap.client.notice.property.LatestPageNoticeProperties; +import com.kustacks.kuring.worker.scrap.deptinfo.NoticeScrapInfo; +import com.kustacks.kuring.worker.scrap.deptinfo.RegisterDepartmentMap; +import com.kustacks.kuring.worker.scrap.deptinfo.StaffScrapInfo; + +import java.util.List; + +import static com.kustacks.kuring.notice.domain.DepartmentName.MATERIAL; + +@RegisterDepartmentMap(key = MATERIAL) +public class MaterialDept extends EngineeringCollege { + + public MaterialDept( + LatestPageNoticeApiClient latestPageNoticeApiClient, + LatestPageNoticeHtmlParser latestPageNoticeHtmlParser, + LatestPageNoticeProperties latestPageNoticeProperties, + LatestPageGraduateNoticeApiClient latestPageGraduateNoticeApiClient + ) { + super(); + this.noticeApiClient = latestPageNoticeApiClient; + this.htmlParser = latestPageNoticeHtmlParser; + this.latestPageNoticeProperties = latestPageNoticeProperties; + + List siteIds = List.of(11785); + this.staffScrapInfo = new StaffScrapInfo(MATERIAL.getHostPrefix(), siteIds); + this.noticeScrapInfo = new NoticeScrapInfo(MATERIAL.getHostPrefix(), 5897); + this.departmentName = MATERIAL; + this.noticeGraduationInfo = new NoticeScrapInfo(MATERIAL.getHostPrefix(), 5899); + this.latestPageGraduateNoticeApiClient = latestPageGraduateNoticeApiClient; + } +} diff --git a/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MechaRobotAutoDept.java b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MechaRobotAutoDept.java new file mode 100644 index 000000000..0e4cf2f4d --- /dev/null +++ b/src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/engineering/MechaRobotAutoDept.java @@ -0,0 +1,36 @@ +package com.kustacks.kuring.worker.scrap.deptinfo.engineering; + +import com.kustacks.kuring.worker.parser.notice.LatestPageNoticeHtmlParser; +import com.kustacks.kuring.worker.scrap.client.notice.LatestPageGraduateNoticeApiClient; +import com.kustacks.kuring.worker.scrap.client.notice.LatestPageNoticeApiClient; +import com.kustacks.kuring.worker.scrap.client.notice.property.LatestPageNoticeProperties; +import com.kustacks.kuring.worker.scrap.deptinfo.NoticeScrapInfo; +import com.kustacks.kuring.worker.scrap.deptinfo.RegisterDepartmentMap; +import com.kustacks.kuring.worker.scrap.deptinfo.StaffScrapInfo; + +import java.util.List; + +import static com.kustacks.kuring.notice.domain.DepartmentName.MECHA_ROBOT_AUTO; + +@RegisterDepartmentMap(key = MECHA_ROBOT_AUTO) +public class MechaRobotAutoDept extends EngineeringCollege { + + public MechaRobotAutoDept( + LatestPageNoticeApiClient latestPageNoticeApiClient, + LatestPageNoticeHtmlParser latestPageNoticeHtmlParser, + LatestPageNoticeProperties latestPageNoticeProperties, + LatestPageGraduateNoticeApiClient latestPageGraduateNoticeApiClient + ) { + super(); + this.noticeApiClient = latestPageNoticeApiClient; + this.htmlParser = latestPageNoticeHtmlParser; + this.latestPageNoticeProperties = latestPageNoticeProperties; + + List siteIds = List.of(10048); + this.staffScrapInfo = new StaffScrapInfo(MECHA_ROBOT_AUTO.getHostPrefix(), siteIds); + this.noticeScrapInfo = new NoticeScrapInfo(MECHA_ROBOT_AUTO.getHostPrefix(), 403); + this.departmentName = MECHA_ROBOT_AUTO; + this.noticeGraduationInfo = new NoticeScrapInfo(MECHA_ROBOT_AUTO.getHostPrefix(), 763); + this.latestPageGraduateNoticeApiClient = latestPageGraduateNoticeApiClient; + } +} \ No newline at end of file diff --git a/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java b/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java index 60627b49a..f822ecd76 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java +++ b/src/test/java/com/kustacks/kuring/acceptance/CategoryAcceptanceTest.java @@ -60,7 +60,7 @@ void look_up_department_list() { var 학과_조회_요청_응답 = 학과_조회_요청(); // then - 학과_조회_응답_확인(학과_조회_요청_응답, 63); + 학과_조회_응답_확인(학과_조회_요청_응답, 66); } /** From 766b16c1926f326e286f38e4239eae2d5025acbc Mon Sep 17 00:00:00 2001 From: HanJu Kim <56250226+rlagkswn00@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:19:59 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Fix:=20=EB=8C=93=EA=B8=80=20=EA=B0=9C?= =?UTF-8?q?=EC=88=98=20=ED=91=9C=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: CommentCommandPort 부모 댓글의 답글 삭제 메서드 추가 * feat: CommentQueryRepository 부모 댓글의 답급 소프트 삭제 쿼리 추가 * feat: NoticeCommandService 댓글 삭제 시 답급도 같이 삭제하도록 수정 * feat: 댓글 삭제 정책에 맞게 데이터 정합성 유지를 위한 Flyway 쿼리 추가 * test: 댓글 삭제 시 답글 삭제하는 인수 테스트 추가 * test : 댓글 개수 테스트 로직 수정 * test : 소나큐브 이슈 대응(isZero 사용) --- .../CommentPersistenceAdapter.java | 5 ++ .../persistence/CommentQueryRepository.java | 2 + .../CommentQueryRepositoryImpl.java | 9 ++++ .../port/out/CommentCommandPort.java | 2 + .../service/NoticeCommandService.java | 2 + ...ment_destroyedAt_as_parent_destroyedAt.sql | 11 ++++ .../acceptance/NoticeAcceptanceTest.java | 50 ++++++++++++++++++- 7 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/db/migration/V251108__Update_comment_destroyedAt_as_parent_destroyedAt.sql diff --git a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentPersistenceAdapter.java index 309b7d84b..7dc1c85bd 100644 --- a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentPersistenceAdapter.java @@ -34,6 +34,11 @@ public void delete(Comment comment) { commentRepository.delete(comment); } + @Override + public void deleteByParentId(Long parentId) { + commentRepository.deleteByParentId(parentId); + } + @Override public Optional findComment(Long id) { return commentRepository.findReadModelById(id); diff --git a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepository.java b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepository.java index cd9026696..e57c8aac8 100644 --- a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepository.java @@ -13,4 +13,6 @@ public interface CommentQueryRepository { List findExcludeSubCommentByCursor(Long noticeId, String cursor, int size); List findSubCommentByIds(Long noticeId, Set parentCommentIds); + + void deleteByParentId(Long parentId); } diff --git a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepositoryImpl.java index 89905c55e..23a7cfdc5 100644 --- a/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/notice/adapter/out/persistence/CommentQueryRepositoryImpl.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.Set; @@ -86,6 +87,14 @@ public List findSubCommentByIds(Long noticeId, Set paren ).fetch(); } + @Override + public void deleteByParentId(Long parentId) { + queryFactory.update(comment) + .set(comment.destroyedAt, LocalDateTime.now()) + .where(comment.parentId.eq(parentId)) + .execute(); + } + private static BooleanExpression cursorId(String cursorId) { return cursorId == null ? null : comment.id.goe(Long.parseLong(cursorId)); } diff --git a/src/main/java/com/kustacks/kuring/notice/application/port/out/CommentCommandPort.java b/src/main/java/com/kustacks/kuring/notice/application/port/out/CommentCommandPort.java index 6bdbc2122..0c0d248e6 100644 --- a/src/main/java/com/kustacks/kuring/notice/application/port/out/CommentCommandPort.java +++ b/src/main/java/com/kustacks/kuring/notice/application/port/out/CommentCommandPort.java @@ -9,4 +9,6 @@ public interface CommentCommandPort { void createReply(Long userId, Long noticeId, Long parentId, String content); void delete(Comment comment); + + void deleteByParentId(Long parentId); } diff --git a/src/main/java/com/kustacks/kuring/notice/application/service/NoticeCommandService.java b/src/main/java/com/kustacks/kuring/notice/application/service/NoticeCommandService.java index 4d37172e2..720b2ac70 100644 --- a/src/main/java/com/kustacks/kuring/notice/application/service/NoticeCommandService.java +++ b/src/main/java/com/kustacks/kuring/notice/application/service/NoticeCommandService.java @@ -103,6 +103,8 @@ public void process(DeleteCommentCommand command) { commentCommandPort.delete(findComment); log.info("delete notice-comment, user{}, notice{}, comment{}", rootUser.getId(), findNotice.getId(), findComment.getId()); + + commentCommandPort.deleteByParentId(findComment.getId()); } private static boolean isNotCommentOwner(Comment findComment, RootUser findUser, NoticeDto findNotice) { diff --git a/src/main/resources/db/migration/V251108__Update_comment_destroyedAt_as_parent_destroyedAt.sql b/src/main/resources/db/migration/V251108__Update_comment_destroyedAt_as_parent_destroyedAt.sql new file mode 100644 index 000000000..3e79be986 --- /dev/null +++ b/src/main/resources/db/migration/V251108__Update_comment_destroyedAt_as_parent_destroyedAt.sql @@ -0,0 +1,11 @@ +-- 부모 댓글이 삭제되었으나(destroyed_at IS NOT NULL), 아직 Soft Delete 처리되지 않은 답글들을 업데이트합니다. + +UPDATE comment AS child + INNER JOIN comment AS parent +ON child.parent_id = parent.id + SET child.destroyed_at = parent.destroyed_at -- 답글의 Soft Delete 시간을 부모의 삭제 시간과 동일하게 설정 + +WHERE + child.parent_id IS NOT NULL -- 1. 이 답글이 루트 댓글이 아님 (parent_id가 NULL이 아님) + AND child.destroyed_at IS NULL -- 2. 답글이 아직 삭제되지 않았고 (destroyed_at IS NULL) + AND parent.destroyed_at IS NOT NULL -- 3. 부모 댓글은 이미 삭제된 상태인 경우 (destroyed_at IS NOT NULL) diff --git a/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java b/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java index 059256724..408d0ed38 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java +++ b/src/test/java/com/kustacks/kuring/acceptance/NoticeAcceptanceTest.java @@ -2,8 +2,7 @@ import com.kustacks.kuring.notice.domain.DepartmentName; import com.kustacks.kuring.support.IntegrationTestSupport; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.springframework.http.HttpStatus; import java.util.List; @@ -455,4 +454,51 @@ void comment_multiple_whitelist_words_test() { () -> assertThat(response2.statusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY.value()) ); } + + /** + * Given : 사전에 저장된 공지와 원댓글 3개, 답글 3개가 있다 + * When : 댓글을 삭제하면 + * Then : 댓글의 답글들도 같이 삭제된다. + */ + @DisplayName("[v2] 댓글 삭제 시 답글들도 함께 삭제된다.") + @Test + void delete_parent_comment_with_sub_comments() { + //given - 사용자 로그인 + String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); + + var 공지_조회_응답 = 공지사항_조회_요청("stu"); + var noticeId = 공지_조회_응답.jsonPath().getLong("data[0].id"); + + //댓글 추가 + 공지에_댓글_추가(noticeId, accessToken, "삭제될 원댓글"); + + //원댓글 ID 조회 + var 댓글_목록_응답 = 공지의_댓글_조회(noticeId, null, 10); + long parentCommentId = 댓글_목록_응답.jsonPath().getLong("data.comments[0].comment.id"); //3번째 추가. + + //답글 3개 추가 + 공지에_댓글_추가(noticeId, parentCommentId, accessToken, "답글 1"); + 공지에_댓글_추가(noticeId, parentCommentId, accessToken, "답글 2"); + 공지에_댓글_추가(noticeId, parentCommentId, accessToken, "답글 3"); + + var 삭제_전_응답 = 공지사항_조회_요청("stu"); + long 삭제_전_댓글_수 = 삭제_전_응답.jsonPath().getLong("data[0].commentCount"); + + //when - 댓글 삭제(댓글 1건 + 답글 3건 총 4건 삭제) + 댓글_삭제(accessToken, noticeId, parentCommentId); + + //then - 삭제 후 공지 목록 조회 + var 삭제_후_응답 = 공지사항_조회_요청("stu"); + long 삭제_후_댓글_수 = 삭제_후_응답.jsonPath().getLong("data[0].commentCount"); + + // 공지의 댓글 목록 조회 시에도 두건만 나와야 함(기존 6건 - 4건 = 2건) + var 삭제_후_댓글_목록 = 공지의_댓글_조회(noticeId, null, 10); + int 실제_조회된_댓글_수 = 삭제_후_댓글_목록.jsonPath().getList("data.comments").size(); + + assertAll( + () -> assertThat(삭제_전_댓글_수).isEqualTo(4), + () -> assertThat(삭제_후_댓글_수).isZero(), + () -> assertThat(실제_조회된_댓글_수).isZero() + ); + } }