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 @@ -17,6 +17,8 @@
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/mentorings")
@RequiredArgsConstructor
Expand All @@ -43,6 +45,20 @@ public RsData<MentoringPagingResponse> getMentorings(
);
}

@GetMapping("/my")
@PreAuthorize("hasRole('MENTOR')")
@Operation(summary = "나의 멘토링 목록 조회", description = "나의 멘토링 목록을 조회합니다. 로그인한 멘토만 접근할 수 있습니다.")
public RsData<List<MentoringWithTagsDto>> getMyMentorings() {
Mentor mentor = memberStorage.findMentorByMember(rq.getActor());
List<MentoringWithTagsDto> resDto = mentoringService.getMyMentorings(mentor);

return new RsData<>(
"200",
"나의 멘토링 목록을 조회하였습니다.",
resDto
);
}

@GetMapping("/{mentoringId}")
@Operation(summary = "멘토링 상세 조회", description = "특정 멘토링을 상세 조회합니다.")
public RsData<MentoringResponse> getMentoring(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public interface MentoringRepository extends JpaRepository<Mentoring, Long>, MentoringRepositoryCustom {
List<Mentoring> findByMentorId(Long mentorId);
List<Mentoring> findByMentorIdOrderByIdDesc(Long mentorId);
Optional<Mentoring> findTopByOrderByIdDesc();
boolean existsByMentorIdAndTitle(Long mentorId, String title);
boolean existsByMentorIdAndTitleAndIdNot(Long mentorId, String title, Long MentoringId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public Page<MentoringWithTagsDto> getMentorings(String keyword, int page, int si
.map(MentoringWithTagsDto::from);
}

@Transactional(readOnly = true)
public List<MentoringWithTagsDto> getMyMentorings(Mentor mentor) {
return mentoringRepository.findByMentorIdOrderByIdDesc(mentor.getId())
.stream()
.map(MentoringWithTagsDto::from)
.toList();
}

@Transactional(readOnly = true)
public MentoringResponse getMentoring(Long mentoringId) {
Mentoring mentoring = mentoringStorage.findMentoring(mentoringId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ public class MentoringSessionController {
private final MentoringSessionManager mentoringSessionManager;
private final Rq rq;

@GetMapping("/{sessionId}/url")
@GetMapping("/url")
@Operation(summary = "세션참여 URL 발급", description = "세션 참여를 위한 URL을 발급합니다.")
public RsData<GetSessionUrlResponse> getSessionUrl(@PathVariable Long sessionId) {
public RsData<GetSessionUrlResponse> getSessionUrl(@RequestParam Long sessionId) {

GetSessionUrlResponse response = mentoringSessionManager.getSessionUrl(sessionId);
return new RsData<>("200", "요청완료", response);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.back.domain.mentoring.session.controller;

import com.back.domain.mentoring.session.dto.WebRtcSignalingMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;

import java.security.Principal;

@Slf4j
@Controller
@RequiredArgsConstructor
public class WebRtcController {

private final SimpMessageSendingOperations messagingTemplate;

@MessageMapping("/signal")
public void handleSignalingMessage(WebRtcSignalingMessage message, Principal principal) {
String senderUsername = principal.getName();

WebRtcSignalingMessage finalMessage = new WebRtcSignalingMessage(message.type(), senderUsername, message.to(), message.sessionId(), message.payload());

log.debug("[Signal] type: {}, from: {}, to: {}, sessionId: {}",
finalMessage.type(), finalMessage.from(), finalMessage.to(), finalMessage.sessionId());

// 'to' 필드가 있으면 특정 사용자에게, 없으면 세션 전체에 브로드캐스트
if (finalMessage.to() != null && !finalMessage.to().isEmpty()) {
String userSpecificTopic = "/topic/signal/user/" + finalMessage.to();
messagingTemplate.convertAndSend(userSpecificTopic, finalMessage);
} else {
messagingTemplate.convertAndSend("/topic/signal/room/" + finalMessage.sessionId(), finalMessage);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.back.domain.mentoring.session.dto;

public record WebRtcSignalingMessage(
String type,
String from,
String to,
String sessionId,
Object payload
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,39 @@ private ChatMessage(MentoringSession mentoringSession, Member sender, SenderRole
}

public static ChatMessage create(MentoringSession mentoringSession, Member sender, SenderRole senderRole, String content, MessageType type) {
if (mentoringSession == null) {
throw new IllegalArgumentException("MentoringSession은 null일 수 없습니다.");
}
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("Content는 null이거나 비어 있을 수 없습니다.");
}
if (type == null) {
throw new IllegalArgumentException("MessageType은 null일 수 없습니다.");
}

if (type == MessageType.SYSTEM) {
if (sender != null) {
throw new IllegalArgumentException("시스템 메시지의 sender는 null이어야 합니다.");
}
if (senderRole != SenderRole.SYSTEM) {
throw new IllegalArgumentException("시스템 메시지의 senderRole은 SYSTEM이어야 합니다.");
}
} else { // TEXT, IMAGE, FILE
if (sender == null) {
throw new IllegalArgumentException("일반 메시지의 sender는 null일 수 없습니다.");
}
if (senderRole != SenderRole.MENTOR && senderRole != SenderRole.MENTEE) {
throw new IllegalArgumentException("일반 메시지의 senderRole은 MENTOR 또는 MENTEE여야 합니다.");
}

boolean isParticipant = (mentoringSession.getReservation().getMentor().isMember(sender) ||
mentoringSession.getReservation().getMentee().isMember(sender));

if (!isParticipant) {
throw new IllegalArgumentException("메시지 발신자는 해당 멘토링 세션의 참여자가 아닙니다.");
}
}

return ChatMessage.builder()
.mentoringSession(mentoringSession)
.sender(sender)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ public class MentoringSession extends BaseEntity {
@OneToMany(mappedBy = "mentoringSession", cascade = CascadeType.ALL)
private List<ChatMessage> chatMessages = new ArrayList<>();

// 화면 공유, WebRTC 관련 필드 등 추가 가능

@Builder(access = AccessLevel.PRIVATE)
private MentoringSession(Reservation reservation) {
this.sessionUrl = java.util.UUID.randomUUID().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ public record MentorSlotRepetitionRequest(
@NotEmpty
List<DayOfWeek> daysOfWeek,

@Schema(description = "시작 시간", example = "HH:mm:ss")
@Schema(description = "시작 시간", example = "HH:mm")
@NotNull
@JsonFormat(pattern = "HH:mm:ss")
@JsonFormat(pattern = "HH:mm")
LocalTime startTime,

@Schema(description = "종료 시간", example = "HH:mm:ss")
@Schema(description = "종료 시간", example = "HH:mm")
@NotNull
@JsonFormat(pattern = "HH:mm:ss")
@JsonFormat(pattern = "HH:mm")
LocalTime endTime
){
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ public record MentorSlotRequest(
@NotNull
Long mentorId,

@Schema(description = "시작 일시", example = "yyyy-MM-ddTHH:mm:ss")
@Schema(description = "시작 일시", example = "yyyy-MM-ddTHH:mm")
@NotNull
LocalDateTime startDateTime,

@Schema(description = "종료 일시", example = "yyyy-MM-ddTHH:mm:ss")
@Schema(description = "종료 일시", example = "yyyy-MM-ddTHH:mm")
@NotNull
LocalDateTime endDateTime
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
@RestController
@RequestMapping("/news/{newsId}/comment")
@RequiredArgsConstructor
public class CommentController {
public class NewsCommentController {
private final NewsService newsService;
private final NewsCommentService newsCommentService;
private final Rq rq;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@ void returnEmptyPage() {
}
}

@Test
@DisplayName("나의 멘토링 목록 조회")
void getMentorings() {
// given
Mentoring mentoring2 = MentoringFixture.create(2L, mentor2);

List<Mentoring> mentorings = List.of(mentoring1);

when(mentoringRepository.findByMentorIdOrderByIdDesc(mentor1.getId()))
.thenReturn(mentorings);

// when
List<MentoringWithTagsDto> result = mentoringService.getMyMentorings(mentor1);

// then
assertThat(result).hasSize(1);
verify(mentoringRepository).findByMentorIdOrderByIdDesc(mentor1.getId());
}

@Nested
@DisplayName("멘토링 조회")
class Describe_getMentoring {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.back.domain.mentoring.session.entity;


import com.back.domain.member.member.entity.Member;
import com.back.fixture.MemberFixture;
import com.back.fixture.mentoring.MentoringSessionFixture;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

class ChatMessageTest {
@Test
@DisplayName("ChatMessage 생성 테스트")
void createChatMessageTest() {
// given
MentoringSession session = MentoringSessionFixture.createDefault();
Member sender = session.getReservation().getMentor().getMember();
String content = "Hello world";
SenderRole senderRole = SenderRole.MENTOR;
MessageType type = MessageType.TEXT;

// when
ChatMessage message = ChatMessage.create(session, sender, senderRole, content, type);

// then
assertThat(message.getMentoringSession()).isEqualTo(session);
assertThat(message.getSender()).isEqualTo(sender);
assertThat(message.getSenderRole()).isEqualTo(senderRole);
assertThat(message.getContent()).isEqualTo(content);
assertThat(message.getType()).isEqualTo(type);
assertThat(message.getTimestamp()).isNotNull();
assertThat(message.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now());
}

@Test
@DisplayName("시스템 메시지 생성 테스트")
void createSystemMessageTest() {
// given
MentoringSession session = MentoringSessionFixture.createDefault();
String content = "멘토링 세션이 시작되었습니다.";
MessageType type = MessageType.SYSTEM;
SenderRole senderRole = SenderRole.SYSTEM;
Member sender = null; // 시스템 메시지는 발신자가 없을 수 있습니다.

// when
ChatMessage message = ChatMessage.create(session, sender, senderRole, content, type);

// then
assertThat(message.getMentoringSession()).isEqualTo(session);
assertThat(message.getSender()).isNull();
assertThat(message.getSenderRole()).isEqualTo(senderRole);
assertThat(message.getContent()).isEqualTo(content);
assertThat(message.getType()).isEqualTo(type);
assertThat(message.getTimestamp()).isNotNull();
assertThat(message.getTimestamp()).isBeforeOrEqualTo(LocalDateTime.now());
}

@Nested
@DisplayName("ChatMessage 생성 유효성 검증")
class ValidationTests {

@Test
@DisplayName("MentoringSession이 null이면 예외가 발생한다")
void createWithNullSession_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(null, MemberFixture.createDefault(), SenderRole.MENTOR, "message", MessageType.TEXT));
}

@Test
@DisplayName("Content가 null이면 예외가 발생한다")
void createWithNullContent_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), MemberFixture.createDefault(), SenderRole.MENTOR, null, MessageType.TEXT));
}

@Test
@DisplayName("Content가 비어있으면 예외가 발생한다")
void createWithBlankContent_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), MemberFixture.createDefault(), SenderRole.MENTOR, " ", MessageType.TEXT));
}

@Test
@DisplayName("MessageType이 null이면 예외가 발생한다")
void createWithNullType_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), MemberFixture.createDefault(), SenderRole.MENTOR, "message", null));
}

@Test
@DisplayName("시스템 메시지에 sender가 있으면 예외가 발생한다")
void createSystemMessageWithSender_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), MemberFixture.createDefault(), SenderRole.SYSTEM, "system message", MessageType.SYSTEM));
}

@Test
@DisplayName("시스템 메시지의 senderRole이 SYSTEM이 아니면 예외가 발생한다")
void createSystemMessageWithInvalidRole_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), null, SenderRole.MENTOR, "system message", MessageType.SYSTEM));
}

@Test
@DisplayName("일반 메시지에 sender가 없으면 예외가 발생한다")
void createUserMessageWithNullSender_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), null, SenderRole.MENTOR, "user message", MessageType.TEXT));
}

@Test
@DisplayName("일반 메시지의 senderRole이 MENTOR나 MENTEE가 아니면 예외가 발생한다")
void createUserMessageWithInvalidRole_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(MentoringSessionFixture.createDefault(), MemberFixture.createDefault(), SenderRole.SYSTEM, "user message", MessageType.TEXT));
}

@Test
@DisplayName("메시지 발신자가 멘토링 참여자가 아니면 예외가 발생한다")
void createWithNonParticipantSender_shouldThrowException() {
// given
MentoringSession session = MentoringSessionFixture.createDefault();
Member nonParticipant = MemberFixture.create(99L, "[email protected]", "외부인", "password", Member.Role.MENTEE);

// when & then
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () ->
ChatMessage.create(session, nonParticipant, SenderRole.MENTOR, "some message", MessageType.TEXT));

assertThat(exception.getMessage()).isEqualTo("메시지 발신자는 해당 멘토링 세션의 참여자가 아닙니다.");
}
}
}
Loading