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 @@ -35,8 +35,12 @@ public ResponseEntity<ApiResponse<QuestionResDTO.QuestionRoomResponse>> getQuest
// text/event-stream으로 연결을 유지하며, 댓글 생성 같은 목록 갱신 이벤트를 받는다.
// 인증 헤더가 필요하므로 프론트에서는 기본 EventSource 대신 fetch 기반 SSE 클라이언트로 구독한다.
@GetMapping(value = "/api/sessions/{sessionId}/questions/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribeQuestionEvents(@PathVariable Long sessionId) {
return questionService.subscribeQuestionEvents(sessionId);
public ResponseEntity<SseEmitter> subscribeQuestionEvents(@PathVariable Long sessionId) {
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.header("Cache-Control", "no-cache")
.header("X-Accel-Buffering", "no")
.body(questionService.subscribeQuestionEvents(sessionId));
}

// 질문 상세 조회
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,17 @@ public record CommentCreatedEvent(
) {
}

// 댓글 수정/삭제 시 SSE로 내려가는 목록 갱신 이벤트 응답
public record CommentUpdatedEvent(
String type,
Long sessionId,
Long questionId,
Boolean isResolved,
Integer commentCount,
List<PreviewCommentResponse> previewComments
) {
}

// O/X 클릭 직후 응답. selectedChoice가 null이면 같은 선택지를 다시 눌러 취소된 상태다.
public record UnderstandingResponseResult(
Long checkId,
Expand Down Expand Up @@ -243,6 +254,19 @@ public record QuestionCreatedEvent(
) {
}

// 좋아요, 해결 상태, 본문 수정, 삭제처럼 기존 질문의 상태가 바뀔 때 내려가는 SSE 이벤트
public record QuestionUpdatedEvent(
String type,
Long sessionId,
Long questionId,
String content,
Boolean isResolved,
Integer likeCount,
Boolean isDeleted,
LocalDateTime updatedAt
) {
}

// 운영진이 이해도 체크를 생성했을 때 SSE로 내려가는 이벤트.
// 같은 세션 질문방을 보고 있는 모든 클라이언트에게 전파된다.
public record UnderstandingCheckCreatedEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,21 @@ public void publishCommentCreated(Long sessionId, QuestionResDTO.CommentCreatedE
broadcast(sessionId, "comment-created", event);
}

// 댓글 수정/삭제 이벤트를 같은 세션 질문방을 구독 중인 모든 클라이언트에게 전파한다.
public void publishCommentUpdated(Long sessionId, QuestionResDTO.CommentUpdatedEvent event) {
broadcast(sessionId, "comment-updated", event);
}

// 질문 등록 이벤트를 같은 세션 질문방을 구독 중인 모든 클라이언트에게 전파한다.
public void publishQuestionCreated(Long sessionId, QuestionResDTO.QuestionCreatedEvent event) {
broadcast(sessionId, "question-created", event);
}

// 질문 상태 변경 이벤트를 같은 세션 질문방을 구독 중인 모든 클라이언트에게 전파한다.
public void publishQuestionUpdated(Long sessionId, QuestionResDTO.QuestionUpdatedEvent event) {
broadcast(sessionId, "question-updated", event);
}

// 이해도 체크 생성 이벤트를 같은 세션 질문방을 구독 중인 모든 클라이언트에게 전파한다.
public void publishUnderstandingCheckCreated(Long sessionId, QuestionResDTO.UnderstandingCheckCreatedEvent event) {
broadcast(sessionId, "understanding-check-created", event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ public QuestionResDTO.LikeRes toggleLike(Long questionId, Long userId) {
Question question = findQuestion(questionId);

// 이미 좋아요를 눌렀는지 확인
return questionLikeRepository.findByQuestionAndUser(question, loginUser)
QuestionResDTO.LikeRes result = questionLikeRepository.findByQuestionAndUser(question, loginUser)
.map(existingLike -> {
// 이미 눌렀으면 → 취소 (삭제 + likeCount -1)
questionLikeRepository.delete(existingLike);
Expand All @@ -298,6 +298,9 @@ public QuestionResDTO.LikeRes toggleLike(Long questionId, Long userId) {
question.increaseLikeCount();
return new QuestionResDTO.LikeRes(question.getId(), question.getLikeCount(), true);
});

publishQuestionUpdatedEventAfterCommit(question, false);
return result;
}

// 질문 수정
Expand All @@ -313,6 +316,8 @@ public QuestionResDTO.UpdateDeleteRes updateQuestion(

question.updateContent(request.getContent());

publishQuestionUpdatedEventAfterCommit(question, false);

return new QuestionResDTO.UpdateDeleteRes(
question.getId(), question.getContent(),
question.getUpdatedAt(), question.getDeletedAt()
Expand All @@ -328,6 +333,8 @@ public QuestionResDTO.UpdateDeleteRes deleteQuestion(Long questionId, Long userI

question.softDelete();

publishQuestionUpdatedEventAfterCommit(question, true);

return new QuestionResDTO.UpdateDeleteRes(
question.getId(), question.getContent(),
question.getUpdatedAt(), question.getDeletedAt()
Expand All @@ -347,6 +354,8 @@ public QuestionResDTO.CommentUpdateDeleteRes updateComment(

comment.updateContent(request.getContent());

publishCommentUpdatedEventAfterCommit(comment.getQuestion());

return new QuestionResDTO.CommentUpdateDeleteRes(
comment.getId(), comment.getContent(),
comment.getUpdatedAt(), comment.getDeletedAt()
Expand All @@ -362,6 +371,8 @@ public QuestionResDTO.CommentUpdateDeleteRes deleteComment(Long commentId, Long

comment.softDelete();

publishCommentUpdatedEventAfterCommit(comment.getQuestion());

return new QuestionResDTO.CommentUpdateDeleteRes(
comment.getId(), comment.getContent(),
comment.getUpdatedAt(), comment.getDeletedAt()
Expand All @@ -379,6 +390,8 @@ public QuestionResDTO.StatusUpdateRes updateQuestionStatus(Long questionId, Long
Question question = findQuestion(questionId);
question.markResolved();

publishQuestionUpdatedEventAfterCommit(question, false);

return new QuestionResDTO.StatusUpdateRes(
question.getId(), question.getIsResolved(), question.getUpdatedAt()
);
Expand Down Expand Up @@ -742,6 +755,32 @@ private void publishCommentCreatedEventAfterCommit(Question question) {
publishAfterCommit(() -> questionEventService.publishCommentCreated(sessionId, event));
}

private void publishCommentUpdatedEventAfterCommit(Question question) {
Long sessionId = question.getSession().getId();
Long questionId = question.getId();
List<Long> questionIds = List.of(questionId);

Map<Long, Integer> commentCounts = new HashMap<>();
questionCommentRepository.countByQuestionIds(questionIds)
.forEach(row -> commentCounts.put(row.getQuestionId(), Math.toIntExact(row.getCommentCount())));

Map<Long, List<QuestionResDTO.PreviewCommentResponse>> previewComments = new HashMap<>();
questionCommentRepository.findPreviewCommentsByQuestionIds(questionIds)
.forEach(row -> previewComments.computeIfAbsent(row.getQuestionId(), key -> new ArrayList<>())
.add(toPreviewCommentResponse(question, row)));

QuestionResDTO.CommentUpdatedEvent event = new QuestionResDTO.CommentUpdatedEvent(
"COMMENT_UPDATED",
sessionId,
questionId,
question.getIsResolved(),
commentCounts.getOrDefault(questionId, 0),
previewComments.getOrDefault(questionId, List.of())
);

publishAfterCommit(() -> questionEventService.publishCommentUpdated(sessionId, event));
}

private void publishQuestionCreatedEventAfterCommit(Question question) {
Long sessionId = question.getSession().getId();

Expand All @@ -759,6 +798,23 @@ private void publishQuestionCreatedEventAfterCommit(Question question) {
publishAfterCommit(() -> questionEventService.publishQuestionCreated(sessionId, event));
}

private void publishQuestionUpdatedEventAfterCommit(Question question, boolean isDeleted) {
Long sessionId = question.getSession().getId();

QuestionResDTO.QuestionUpdatedEvent event = new QuestionResDTO.QuestionUpdatedEvent(
"QUESTION_UPDATED",
sessionId,
question.getId(),
question.getContent(),
question.getIsResolved(),
question.getLikeCount(),
isDeleted,
question.getUpdatedAt()
);

publishAfterCommit(() -> questionEventService.publishQuestionUpdated(sessionId, event));
}

private void publishUnderstandingCheckCreatedEventAfterCommit(
Long sessionId, UnderstandingCheck check, int attendanceCount
) {
Expand Down
Loading
Loading