Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,6 @@ ResponseEntity<Void> likeClubCancel(
description = """
- authorId 확인하여 작성자 본인인 경우 삭제 버튼(x) 표시.
- 닉네임은 존재 시 그대로 반환되며, 없는 경우 student의 익명 닉네임으로 반환.
- is_deleted 값이 false인 경우 "삭제된 댓글입니다"로 표현.
- is_admin 필드를 통해 관리자 댓글 여부를 알 수 있음.
- 트리 구조는 대댓글 형태로 재귀적으로 구성됩니다.

```java
Expand Down Expand Up @@ -192,7 +190,11 @@ ResponseEntity<QnasResponse> getQnas(
}
)
@Operation(summary = "특정 동아리의 QNA를 생성한다",
description = "parentId를 null 요청 시 첫 QNA, 부모 QNA의 id를 넣어서 요청하면 대댓글 형식으로 생성")
description = """
사용자의 경우 질문만 가능, 관리자의 경우 답변만 가능
parentId를 null 요청 시 질문, 질문 QNA의 id를 넣어서 요청하면 답변 형식으로 생성
"""
)
@PostMapping("/{clubId}/qna")
ResponseEntity<Void> createQna(
@RequestBody @Valid CreateQnaRequest request,
Expand All @@ -212,8 +214,8 @@ ResponseEntity<Void> createQna(
@Operation(summary = "특정 동아리의 QNA를 삭제한다",
description = """
- 관리자는 모든 QNA 삭제 가능, 그 외에는 본인의 QNA만 삭제 가능
- 부모 QNA(맨처음 QNA)인 경우, 그 아래 QNA들까지 모두 삭제
- 자식 QNA인 경우, 삭제 시 삭제된 댓글입니다로만 표시하고 구조를 깨지 않음
- 부모 QNA(질문 QNA)인 경우, 답변 QNA까지 모두 삭제
- 자식 QNA(답변 QNA)인 경우, 답변 QNA만 삭제
""")
@DeleteMapping("/{clubId}/qna/{qnaId}")
ResponseEntity<Void> deleteQna(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -65,8 +59,6 @@ public static InnerQnaResponse from(ClubQna qna) {
qna.getAuthor().getId(),
nickname,
qna.getContent(),
qna.getIsDeleted(),
qna.getIsAdmin(),
qna.getCreatedAt(),
children
);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/in/koreatech/koin/domain/club/model/ClubQna.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,26 @@ private ClubQna(
this.isDeleted = isDeleted;
}

public void removeChild(ClubQna child) {
children.remove(child);
child.parent = null;
}

public void delete() {
isDeleted = true;
}

public boolean isRoot() {
return parent == null;
}

public boolean isChild() {
return parent != null;
}

public void detachFromParentIfChild() {
if (isChild()) {
parent.removeChild(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -99,15 +99,15 @@ 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<ClubSNS> clubSNSs = club.getClubSNSs();

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);
}
Expand Down Expand Up @@ -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) {
Expand Down