Skip to content
Merged
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ dependencies {
// bucket4j
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'

// commons-lang3
implementation 'org.apache.commons:commons-lang3:3.18.0'
// caffeine
implementation 'com.github.ben-manes.caffeine:caffeine'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.oronaminc.join.answer.dto;

import com.oronaminc.join.global.dto.WriterDto;
import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
Expand All @@ -13,7 +14,7 @@ public record AnswerCreateResponse(
@Schema(description = "답변이 생성될 질문 ID")
Long questionId,
@Schema(description = "답변 생성/삭제/수정 상태", example = "CREATE")
String event,
EventType event,
@Schema(description = "답변 ID", example = "11")
Long answerId,
@Schema(description = "답변 내용", example = "답변입니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.oronaminc.join.answer.dto;

import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "답변 삭제 응답 DTO")
public record AnswerDeleteResponse(
Long answerId,
@Schema(description = "삭제 이벤트", example = "DELETE")
String event
EventType event
) {

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.oronaminc.join.answer.dto;

import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

Expand All @@ -8,7 +9,7 @@
public record AnswerUpdateResponse(
Long answerId,
@Schema(description = "수정 이벤트", example = "UPDATE")
String event,
EventType event,
@Schema(description = "수정된 내용", example = "수정된 답변입니다.")
String content

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.oronaminc.join.global.dto.WriterDto;
import com.oronaminc.join.member.domain.Member;
import com.oronaminc.join.question.domain.Question;
import com.oronaminc.join.websocket.common.EventType;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

Expand All @@ -17,7 +18,7 @@ public class AnswerMapper {
public static AnswerCreateResponse toAnswerCreateResponse(Answer answer) {
return AnswerCreateResponse.builder()
.questionId(answer.getQuestion().getId())
.event("CREATE")
.event(EventType.CREATE)
.answerId(answer.getId())
.content(answer.getContent())
.emojiCount(0)
Expand Down Expand Up @@ -51,7 +52,7 @@ public static Answer toEntity(Question question, Member member, AnswerRequest re
public static AnswerUpdateResponse toAnswerUpdateResponse(Answer answer) {
return AnswerUpdateResponse.builder()
.answerId(answer.getId())
.event("UPDATE")
.event(EventType.UPDATE)
.content(answer.getContent())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.oronaminc.join.emoji.dto;

import com.oronaminc.join.emoji.domain.TargetType;
import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "발표방/질문/답변 공감 생성/삭제 응답 DTO")
public record EmojiResponse(
@Schema(description = "이벤트 타입 (CREATE, DELETE)", example = "CREATE")
String event,
EventType event,
@Schema(description = "공감 대상 타입 (ROOM, QUESTION, ANSWER)", example = "ROOM")
TargetType targetType,
@Schema(description = "공감 대상 ID", example = "1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.oronaminc.join.member.service.MemberReader;
import com.oronaminc.join.question.service.QuestionReader;
import com.oronaminc.join.room.service.RoomReader;
import com.oronaminc.join.websocket.common.EventType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -46,7 +47,7 @@ public EmojiResponse createEmoji(Long memberId, EmojiRequest emojiRequest) {

emojiCount = incrementEmojiCount(targetType, targetId);

return new EmojiResponse("CREATE", targetType, targetId, emojiCount);
return new EmojiResponse(EventType.CREATE, targetType, targetId, emojiCount);

}

Expand All @@ -62,7 +63,7 @@ public EmojiResponse deleteEmoji(Long memberId, EmojiRequest emojiRequest) {
);
emojiCount = decrementEmojiCount(targetType, targetId);

return new EmojiResponse("DELETE", targetType, targetId, emojiCount);
return new EmojiResponse(EventType.DELETE, targetType, targetId, emojiCount);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public enum ErrorCode {
UNAUTHORIZED_LIMIT_PARTICIPANT("PARTICIPANT-005", "인원이 가득 차 참가할 수 없습니다.", UNAUTHORIZED),
UNAUTHORIZED_NOT_JOIN_ROOM("PARTICIPANT-005", "발표방에 참여하지 않았습니다. 먼저 참여해주세요.", UNAUTHORIZED),


FILE_UPLOAD_FAILED("FILE-001", "파일 업로드에 실패하였습니다.", INTERNAL_SERVER_ERROR),
NOT_FOUND_FILE("FILE-002", "존재하지 않는 파일입니다.", NOT_FOUND),
MOVEMENT_FILE_FAILED("FILE-003", "파일 이동이 실패하였습니다.", INTERNAL_SERVER_ERROR),
Expand All @@ -51,7 +50,6 @@ public enum ErrorCode {
UNAUTHORIZED_DELETE_ANSWER("ANSWER-005", "작성자 혹은 팀원, 발표자가 아니면 해당 댓글을 삭제할 수 없습니다.", UNAUTHORIZED),
TOO_MANY_REQUESTS_ANSWER("ANSWER-006", "잠시 후 다시 시도해주세요.", UNAUTHORIZED),


ACCESS_DENIED_SESSION("SESSION-1201", "접근 권한이 없습니다.", FORBIDDEN),
NOT_FOUND_SESSION("SESSION-1202", "세션이 유효하지 않습니다.", UNAUTHORIZED),
EXPIRED_SESSION("SESSION-1203", "세션이 만료되었습니다.", UNAUTHORIZED),
Expand All @@ -62,6 +60,7 @@ public enum ErrorCode {
SOCKET_BAD_REQUEST_PATH("SOCKET-1002", "경로가 유효하지 않습니다.", BAD_REQUEST),
SOCKET_BAD_REQUEST_MEMBER("SOCKET-1003", "회원이 유효하지 않습니다.", BAD_REQUEST),

STOMP_INVALID_DESTINATION("STOMP-001", "경로가 유효하지 않습니다.", BAD_REQUEST),

CONFLICT_EMOJI("EMOJI-001", "공감 처리 중 충돌이 발생했습니다.", CONFLICT),
NOT_FOUND_EMOJI("EMOJI-002", "해당 이모지가 존재하지 않습니다.", NOT_FOUND),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.oronaminc.join.global.exception;

import com.oronaminc.join.global.util.StringUtil;
import java.text.MessageFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;

Expand All @@ -8,5 +10,19 @@
public class ErrorException extends RuntimeException {

private final ErrorCode errorCode;
private final String errorMessage;

}
public ErrorException(ErrorCode errorCode) {
this(errorCode, null);
}

public static ErrorException of(ErrorCode errorCode, String message, Object... args) {
String errorMessage = createMessage(message, args);
return new ErrorException(errorCode, errorMessage);
}

public static String createMessage(String message, Object... args) {
return StringUtil.format(message, args);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class ExceptionAdvice {
public ResponseEntity<ErrorResponse> handleErrorException(ErrorException ex) {
ErrorCode errorCode = ex.getErrorCode();

log.error(errorCode.getMessage(), ex);
log.error("ErrorCode: {}, ErrorMessage: {}", ex.getErrorCode(), ex.getErrorMessage());

HttpStatus httpStatus = switch (errorCode.getErrorStatus()) {
case NOT_FOUND -> HttpStatus.NOT_FOUND;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

public interface ParticipantRepository extends JpaRepository<Participant, Long> {

@Query(
"SELECT COUNT(p) > 0 " +
"FROM Participant p " +
"WHERE p.room.id = :roomId AND p.member.id = :memberId"
)
@Query("""
select COUNT(p) > 0
from Participant p
where p.room.id = :roomId and p.member.id = :memberId
""")
boolean existsByRoomIdAndMemberId(@Param("roomId") Long roomId, @Param("memberId") Long memberId);

Optional<Participant> findByRoomIdAndParticipantType(Long roomId, ParticipantType participantType);
Expand All @@ -30,23 +30,23 @@ public interface ParticipantRepository extends JpaRepository<Participant, Long>
from Participant p
where p.member.id = :memberId
group by p.participantType
""")
""")
List<ParticipantCountDto> countByMemberIdGroupByParticipantType(Long memberId);

@Query("""
select p
from Participant p
join fetch p.room
where p.member.id = :memberId
""")
""")
Page<Participant> findByMemberId(Long memberId, Pageable pageable);

@Query("""
select p
from Participant p
join fetch p.room
where p.member.id = :memberId and p.participantType = :pType
""")
""")
Page<Participant> findByMemberIdAndParticipantType(Long memberId, ParticipantType pType,
Pageable pageable);

Expand All @@ -55,30 +55,30 @@ Page<Participant> findByMemberIdAndParticipantType(Long memberId, ParticipantTyp
from Participant p
join fetch p.room
where p.member.id = :memberId and p.participantType != :pType
""")
""")
Page<Participant> findByMemberIdAndParticipantTypeNot(Long memberId, ParticipantType pType,
Pageable pageable);

Optional<Participant> findByRoomIdAndMemberId( @Param("roomId") Long roomId, @Param("memberId") Long memberId );

@Query("""
SELECT CASE WHEN COUNT(p) > 0 THEN true ELSE false END
FROM Participant p
WHERE p.room.id = :roomId
AND p.member.id = :memberId
AND (p.participantType = com.oronaminc.join.participant.domain.ParticipantType.PRESENTER
OR p.participantType = com.oronaminc.join.participant.domain.ParticipantType.TEAM)
select case when COUNT(p) > 0 then true else false end
from Participant p
where p.room.id = :roomId
and p.member.id = :memberId
and (p.participantType = com.oronaminc.join.participant.domain.ParticipantType.PRESENTER
or p.participantType = com.oronaminc.join.participant.domain.ParticipantType.TEAM)
""")
boolean existsPresenterOrTeamByMemberId(@Param("roomId") Long roomId, @Param("memberId") Long memberId);

void deleteByRoomId(Long roomId);

@Query(value = """
SELECT COUNT(*)
FROM participant
WHERE room_id = :roomId
AND exited_at IS NOT NULL
AND TIMESTAMPDIFF(SECOND, created_at, exited_at) >= 30
select COUNT(*)
from participant
where room_id = :roomId
and exited_at is not null
and TIMESTAMPDIFF(SECOND, created_at, exited_at) >= 30
""", nativeQuery = true)
Long countParticipantsStayedOver30Seconds(@Param("roomId") Long roomId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand All @@ -30,7 +31,7 @@ public ResponseEntity<QuestionListResponse> getQuestions(
@RequestParam(required = false) Long lastEmojiCount,
@RequestParam(defaultValue = "10") int size,
@RequestParam Long memberId,
@RequestParam Long roomId
@PathVariable Long roomId
) {
Slice<QuestionAssembleResponse> result = questionService.getQuestions(
sort, lastId, lastEmojiCount, size, memberId, roomId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.oronaminc.join.global.dto.WriterDto;
import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Builder;
Expand All @@ -10,7 +11,7 @@
@Schema(description = "질문 생성 응답 DTO")
public record QuestionCreateResponse(
@Schema(description = "", example = "CREATE")
String event,
EventType event,
@Schema(description = "질문 ID", example = "11")
Long questionId,
@Schema(description = "질문 내용", example = "질문있습니다. 질문생성DTO가 맞나요?")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.oronaminc.join.question.dto;

import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

@Schema(description = "질문 삭제 응답 DTO")
public record QuestionDeleteResponse(
String event,
EventType event,
Long questionId
) {
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.oronaminc.join.question.dto;

import com.oronaminc.join.websocket.common.EventType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

@Builder
@Schema(description = "질문 수정 응답 DTO")
public record QuestionUpdateResponse(
String event,
EventType event,
Long questionId,
String content

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ public class QuestionService {
public Question create(Long roomId, Long memberId, QuestionRequest requestDto) {

Member member = memberReader.getById(memberId);

Room room = roomReader.getById(roomId);

participantService.validateParticipant(memberId, roomId);

Question question = QuestionMapper.toQuestion(room, member, requestDto);
Expand Down Expand Up @@ -86,12 +84,14 @@ public Question update(Long memberId, Long roomId, Long questionId, QuestionRequ

// 참여자가 아님
if (!participantReader.existsByRoomIdAndMemberId(roomId, memberId)) {
throw new ErrorException(ErrorCode.NOT_FOUND_PARTICIPANT);
throw ErrorException.of(ErrorCode.NOT_FOUND_PARTICIPANT,
"{}번 발표방에는 {}번 회원이 잠가 중이지 않습니다.", roomId, memberId);
}

// 작성자가 아님
if (!question.getMember().getId().equals(memberId)) {
throw new ErrorException(ErrorCode.UNAUTHORIZED_EDIT_QUESTION);
throw ErrorException.of(ErrorCode.UNAUTHORIZED_EDIT_QUESTION,
"{}번 회원은 {}번 질문을 수정할 권한이 없습니다.", memberId, questionId);
}

question.updateContent(request.content());
Expand All @@ -105,13 +105,15 @@ public Long delete(Long memberId, Long roomId, Long questionId) {

// 참여자가 아님
if (!participantReader.existsByRoomIdAndMemberId(roomId, memberId)) {
throw new ErrorException(ErrorCode.NOT_FOUND_PARTICIPANT);
throw ErrorException.of(ErrorCode.NOT_FOUND_PARTICIPANT,
"{}번 발표방에는 {}번 회원이 잠가 중이지 않습니다.", roomId, memberId);
}

// 관리자가 아님 && 작성자도 아님
if (!participantReader.existsPresenterOrTeamByMemberId(roomId, memberId)
&& !question.getMember().getId().equals(memberId)) {
throw new ErrorException(ErrorCode.UNAUTHORIZED_DELETE_QUESTION);
throw ErrorException.of(ErrorCode.UNAUTHORIZED_DELETE_QUESTION,
"{}번 회원은 {}번 질문을 삭제할 권한이 없습니다.", memberId, questionId);
}

answerService.deleteByQuestion(questionId);
Expand Down
Loading