diff --git a/src/main/java/in/koreatech/koin/domain/club/controller/ClubApi.java b/src/main/java/in/koreatech/koin/domain/club/controller/ClubApi.java index f687e7f65e..f4023040b4 100644 --- a/src/main/java/in/koreatech/koin/domain/club/controller/ClubApi.java +++ b/src/main/java/in/koreatech/koin/domain/club/controller/ClubApi.java @@ -158,8 +158,6 @@ ResponseEntity likeClubCancel( description = """ - authorId 확인하여 작성자 본인인 경우 삭제 버튼(x) 표시. - 닉네임은 존재 시 그대로 반환되며, 없는 경우 student의 익명 닉네임으로 반환. - - is_deleted 값이 false인 경우 "삭제된 댓글입니다"로 표현. - - is_admin 필드를 통해 관리자 댓글 여부를 알 수 있음. - 트리 구조는 대댓글 형태로 재귀적으로 구성됩니다. ```java @@ -192,7 +190,11 @@ ResponseEntity getQnas( } ) @Operation(summary = "특정 동아리의 QNA를 생성한다", - description = "parentId를 null 요청 시 첫 QNA, 부모 QNA의 id를 넣어서 요청하면 대댓글 형식으로 생성") + description = """ + 사용자의 경우 질문만 가능, 관리자의 경우 답변만 가능 + parentId를 null 요청 시 질문, 질문 QNA의 id를 넣어서 요청하면 답변 형식으로 생성 + """ + ) @PostMapping("/{clubId}/qna") ResponseEntity createQna( @RequestBody @Valid CreateQnaRequest request, @@ -212,8 +214,8 @@ ResponseEntity createQna( @Operation(summary = "특정 동아리의 QNA를 삭제한다", description = """ - 관리자는 모든 QNA 삭제 가능, 그 외에는 본인의 QNA만 삭제 가능 - - 부모 QNA(맨처음 QNA)인 경우, 그 아래 QNA들까지 모두 삭제 - - 자식 QNA인 경우, 삭제 시 삭제된 댓글입니다로만 표시하고 구조를 깨지 않음 + - 부모 QNA(질문 QNA)인 경우, 답변 QNA까지 모두 삭제 + - 자식 QNA(답변 QNA)인 경우, 답변 QNA만 삭제 """) @DeleteMapping("/{clubId}/qna/{qnaId}") ResponseEntity deleteQna( diff --git a/src/main/java/in/koreatech/koin/domain/club/dto/response/QnasResponse.java b/src/main/java/in/koreatech/koin/domain/club/dto/response/QnasResponse.java index 3577bbd43b..cdf15b9b4d 100644 --- a/src/main/java/in/koreatech/koin/domain/club/dto/response/QnasResponse.java +++ b/src/main/java/in/koreatech/koin/domain/club/dto/response/QnasResponse.java @@ -38,12 +38,6 @@ public record InnerQnaResponse( @Schema(description = "내용", example = "언제 모집하나요?", requiredMode = REQUIRED) String content, - @Schema(description = "삭제여부", example = "false", requiredMode = REQUIRED) - Boolean isDeleted, - - @Schema(description = "관리자여부", example = "true", requiredMode = REQUIRED) - Boolean isAdmin, - @Schema(description = "작성 일시", example = "2025.05.12 14:00", requiredMode = REQUIRED) @JsonFormat(pattern = "yyyy.MM.dd HH:mm") LocalDateTime createdAt, @@ -65,8 +59,6 @@ public static InnerQnaResponse from(ClubQna qna) { qna.getAuthor().getId(), nickname, qna.getContent(), - qna.getIsDeleted(), - qna.getIsAdmin(), qna.getCreatedAt(), children ); diff --git a/src/main/java/in/koreatech/koin/domain/club/model/ClubQna.java b/src/main/java/in/koreatech/koin/domain/club/model/ClubQna.java index 15e6bbd877..93b1972885 100644 --- a/src/main/java/in/koreatech/koin/domain/club/model/ClubQna.java +++ b/src/main/java/in/koreatech/koin/domain/club/model/ClubQna.java @@ -81,6 +81,11 @@ private ClubQna( this.isDeleted = isDeleted; } + public void removeChild(ClubQna child) { + children.remove(child); + child.parent = null; + } + public void delete() { isDeleted = true; } @@ -88,4 +93,14 @@ public void delete() { public boolean isRoot() { return parent == null; } + + public boolean isChild() { + return parent != null; + } + + public void detachFromParentIfChild() { + if (isChild()) { + parent.removeChild(this); + } + } } diff --git a/src/main/java/in/koreatech/koin/domain/club/service/ClubService.java b/src/main/java/in/koreatech/koin/domain/club/service/ClubService.java index 165270a4d6..27508b4137 100644 --- a/src/main/java/in/koreatech/koin/domain/club/service/ClubService.java +++ b/src/main/java/in/koreatech/koin/domain/club/service/ClubService.java @@ -67,7 +67,7 @@ public void createClubRequest(CreateClubRequest request, Integer studentId) { @Transactional public ClubResponse updateClub(Integer clubId, UpdateClubRequest request, Integer studentId) { Club club = clubRepository.getById(clubId); - isClubAdmin(clubId, studentId); + isClubManager(clubId, studentId); ClubCategory clubCategory = clubCategoryRepository.getById(request.clubCategoryId()); club.update(request.name(), request.imageUrl(), clubCategory, request.location(), request.description()); @@ -99,7 +99,7 @@ public ClubResponse updateClubIntroduction( Integer clubId, UpdateClubIntroductionRequest request, Integer studentId ) { Club club = clubRepository.getById(clubId); - isClubAdmin(clubId, studentId); + isClubManager(clubId, studentId); club.updateIntroduction(request.introduction()); List clubSNSs = club.getClubSNSs(); @@ -107,7 +107,7 @@ public ClubResponse updateClubIntroduction( return ClubResponse.from(club, clubSNSs); } - private void isClubAdmin(Integer clubId, Integer studentId) { + private void isClubManager(Integer clubId, Integer studentId) { if (!clubAdminRepository.existsByClubIdAndUserId(clubId, studentId)) { throw AuthorizationException.withDetail("studentId: " + studentId); } @@ -193,21 +193,26 @@ private ClubHotResponse getHotClubFromDBAndCache() { public void createQna(CreateQnaRequest request, Integer clubId, Integer studentId) { Club club = clubRepository.getById(clubId); Student student = studentRepository.getById(studentId); + boolean isManager = clubAdminRepository.existsByClubIdAndUserId(clubId, studentId); + boolean isQuestion = request.parentId() == null; + validateQnaCreateAuthorization(studentId, isQuestion, isManager); ClubQna parentQna = request.parentId() == null ? null : clubQnaRepository.getById(request.parentId()); - boolean isAdmin = clubAdminRepository.existsByClubIdAndUserId(clubId, studentId); - ClubQna qna = request.toClubQna(club, student, parentQna, isAdmin); + ClubQna qna = request.toClubQna(club, student, parentQna, isManager); clubQnaRepository.save(qna); } + private static void validateQnaCreateAuthorization(Integer studentId, boolean isQuestion, boolean isManager) { + if (isQuestion == isManager) { + throw AuthorizationException.withDetail("studentId: " + studentId); + } + } + @Transactional public void deleteQna(Integer clubId, Integer qnaId, Integer studentId) { ClubQna qna = clubQnaRepository.getById(qnaId); validateQnaDeleteAuthorization(clubId, qna, studentId); - if (qna.isRoot()) { - clubQnaRepository.delete(qna); - } else { - qna.delete(); - } + qna.detachFromParentIfChild(); + clubQnaRepository.delete(qna); } private void validateQnaDeleteAuthorization(Integer clubId, ClubQna qna, Integer studentId) {