diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java b/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java index 3561d8d..e783b15 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java @@ -44,10 +44,10 @@ public ResponseEntity> getQue public ResponseEntity> createQuestion( @PathVariable Long sessionId, @RequestBody QuestionReqDTO.CreateReq request, - @AuthenticationPrincipal Integer userId + @AuthenticationPrincipal Long userId ) { return ResponseUtil.success(QuestionSuccessCode.QUESTION_CREATED, - questionService.createQuestion(sessionId, request, Long.valueOf(userId))); + questionService.createQuestion(sessionId, request, userId)); } // 댓글/대댓글 등록 @@ -73,6 +73,29 @@ public ResponseEntity> toggleLike( questionService.toggleLike(questionId, userId)); } + // 질문 수정 + // PATCH /api/questions/{questionId}/modify + @PatchMapping("/api/questions/{questionId}/modify") + public ResponseEntity> updateQuestion( + @PathVariable Long questionId, + @RequestBody QuestionReqDTO.UpdateReq request, + @AuthenticationPrincipal Long userId + ) { + return ResponseUtil.success(QuestionSuccessCode.QUESTION_UPDATED, + questionService.updateQuestion(questionId, request, userId)); + } + + // 질문 삭제 + // DELETE /api/questions/{questionId} + @DeleteMapping("/api/questions/{questionId}") + public ResponseEntity> deleteQuestion( + @PathVariable Long questionId, + @AuthenticationPrincipal Long userId + ) { + return ResponseUtil.success(QuestionSuccessCode.QUESTION_DELETED, + questionService.deleteQuestion(questionId, userId)); + } + // 이해도 체크 생성 // POST /api/sessions/{sessionId}/understanding-checks @PostMapping("/api/sessions/{sessionId}/understanding-checks") diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionReqDTO.java b/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionReqDTO.java index ca6b46b..a902d6b 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionReqDTO.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionReqDTO.java @@ -13,6 +13,13 @@ public static class CreateReq { private String content; } + // 질문 수정 요청 + @Getter + @NoArgsConstructor + public static class UpdateReq { + private String content; + } + // 댓글/대댓글 등록 요청 // parentCommentId가 null이면 일반 댓글, 값이 있으면 대댓글 @Getter diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionResDTO.java b/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionResDTO.java index c2804eb..3dcdc0a 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionResDTO.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/dto/QuestionResDTO.java @@ -50,6 +50,16 @@ public record LikeRes( ) { } + // 질문 수정/삭제 응답 (형태가 동일해서 하나로 공유) + // deletedAt에 값이 있으면 삭제된 상태 + public record UpdateDeleteRes( + Long id, + String content, + LocalDateTime updatedAt, + LocalDateTime deletedAt + ) { + } + // 질문 상세 응답 public record QuestionDetailResponse( Long questionId, diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/entity/Question.java b/backend/src/main/java/com/example/Piroin/project/domain/question/entity/Question.java index a099370..6168ec5 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/entity/Question.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/entity/Question.java @@ -67,4 +67,16 @@ public void decreaseLikeCount() { } this.updatedAt = LocalDateTime.now(); } + + // 질문 내용 수정 + public void updateContent(String content) { + this.content = content; + this.updatedAt = LocalDateTime.now(); + } + + // 질문 소프트 삭제 (DB에서 실제로 지우지 않고 deleted_at에 시각 기록) + public void softDelete() { + this.deletedAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/exception/code/QuestionSuccessCode.java b/backend/src/main/java/com/example/Piroin/project/domain/question/exception/code/QuestionSuccessCode.java index 94d5f4c..2a1bd5d 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/exception/code/QuestionSuccessCode.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/exception/code/QuestionSuccessCode.java @@ -11,8 +11,10 @@ public enum QuestionSuccessCode implements BaseCode { QUESTION_ROOM_OK(HttpStatus.OK, "QUESTION200_1", "질문 방 조회에 성공했습니다."), QUESTION_DETAIL_OK(HttpStatus.OK, "QUESTION200_2", "질문 상세 조회에 성공했습니다."), - LIKE_TOGGLED(HttpStatus.OK, "QUESTION200_3", "좋아요가 처리되었습니다."), // ← 추가 + LIKE_TOGGLED(HttpStatus.OK, "QUESTION200_3", "좋아요가 처리되었습니다."), UNDERSTANDING_RESPONSE_OK(HttpStatus.OK, "QUESTION200_4", "이해도 응답이 반영되었습니다."), + QUESTION_UPDATED(HttpStatus.OK, "QUESTION200_5", "질문이 수정되었습니다."), + QUESTION_DELETED(HttpStatus.OK, "QUESTION200_6", "질문이 삭제되었습니다."), QUESTION_CREATED(HttpStatus.CREATED, "QUESTION201_1", "질문이 등록되었습니다."), COMMENT_CREATED(HttpStatus.CREATED, "QUESTION201_2", "댓글이 등록되었습니다."), UNDERSTANDING_CHECK_CREATED(HttpStatus.CREATED, "QUESTION201_3", "이해도 체크가 생성되었습니다."); diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java b/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java index 44d3324..a4c5f65 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java @@ -237,6 +237,40 @@ public QuestionResDTO.LikeRes toggleLike(Long questionId, Long userId) { }); } + // 질문 수정 + @Transactional + public QuestionResDTO.UpdateDeleteRes updateQuestion( + Long questionId, + QuestionReqDTO.UpdateReq request, + Long userId + ) { + User loginUser = findLoginUser(userId); + Question question = findQuestion(questionId); + validateQuestionOwner(question, loginUser); + + question.updateContent(request.getContent()); + + return new QuestionResDTO.UpdateDeleteRes( + question.getId(), question.getContent(), + question.getUpdatedAt(), question.getDeletedAt() + ); + } + + // 질문 삭제 + @Transactional + public QuestionResDTO.UpdateDeleteRes deleteQuestion(Long questionId, Long userId) { + User loginUser = findLoginUser(userId); + Question question = findQuestion(questionId); + validateQuestionOwner(question, loginUser); + + question.softDelete(); + + return new QuestionResDTO.UpdateDeleteRes( + question.getId(), question.getContent(), + question.getUpdatedAt(), question.getDeletedAt() + ); + } + // 이해도 체크 생성 @Transactional public QuestionResDTO.UnderstandingCheckCreateResponse createUnderstandingCheck( @@ -320,6 +354,12 @@ private void validateCheckBelongsToSession(UnderstandingCheck check, StudySessio } } + private void validateQuestionOwner(Question question, User loginUser) { + if (!question.getUser().getId().equals(loginUser.getId())) { + throw new QuestionException(HttpStatus.FORBIDDEN, "본인의 질문만 수정/삭제할 수 있습니다."); + } + } + private UnderstandResChoice applyUnderstandingResponse( UnderstandingCheck check, User loginUser, UnderstandResChoice requestedChoice ) {