Skip to content

Commit

Permalink
Merge branch 'develop' into feat/#135-my_matching
Browse files Browse the repository at this point in the history
  • Loading branch information
happyjamy authored Feb 26, 2024
2 parents aed6fae + 5acd6c4 commit c98d24d
Show file tree
Hide file tree
Showing 49 changed files with 1,026 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.oeid.mogakgo.common.swagger.template;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

import io.oeid.mogakgo.core.properties.swagger.error.SwaggerChatErrorExamples;
import io.oeid.mogakgo.core.properties.swagger.error.SwaggerProjectErrorExamples;
import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples;
import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomDataRes;
import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomPublicRes;
import io.oeid.mogakgo.exception.dto.ErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import org.springframework.http.ResponseEntity;

@Tag(name = "Chat", description = "채팅 관련 API")
@SuppressWarnings("unused")
public interface ChatSwagger {

@Operation(summary = "채팅방 목록 조회", description = "사용자의 채팅방 목록을 조회하는 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "채팅방 목록 조회 성공"),
@ApiResponse(responseCode = "404", description = "요청한 유저가 존재하지 않음",
content = @Content(
mediaType = APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND)
))
})
ResponseEntity<List<ChatRoomPublicRes>> getChatRoomList(@Parameter(hidden = true) Long userId);

@Operation(summary = "채팅방 상세 조회", description = "채팅방의 상세 정보를 조회하는 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "채팅방 상세 조회 성공"),
@ApiResponse(responseCode = "404", description = "요청한 데이터가 유효하지 않음",
content = @Content(
mediaType = APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND),
@ExampleObject(name = "E030301", value = SwaggerProjectErrorExamples.PROJECT_NOT_FOUND),
@ExampleObject(name = "E110301", value = SwaggerChatErrorExamples.CHAT_ROOM_NOT_FOUND)
}
))
})
ResponseEntity<ChatRoomDataRes> getChatRoomDetailData(@Parameter(hidden = true) Long userId,
@Parameter(in = ParameterIn.PATH) String chatRoomId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples;
import io.oeid.mogakgo.domain.user.presentation.dto.req.UserSignUpApiRequest;
import io.oeid.mogakgo.domain.user.presentation.dto.req.UserSignUpApiReq;
import io.oeid.mogakgo.domain.user.presentation.dto.req.UserUpdateApiReq;
import io.oeid.mogakgo.domain.user.presentation.dto.res.UserDevelopLanguageApiRes;
import io.oeid.mogakgo.domain.user.presentation.dto.res.UserMatchingStatus;
import io.oeid.mogakgo.domain.user.presentation.dto.res.UserPublicApiResponse;
import io.oeid.mogakgo.domain.user.presentation.dto.res.UserSignUpApiResponse;
import io.oeid.mogakgo.domain.user.presentation.dto.res.UserUpdateApiRes;
import io.oeid.mogakgo.exception.dto.ErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down Expand Up @@ -43,7 +46,7 @@ public interface UserSwagger {
})
ResponseEntity<UserSignUpApiResponse> userSignUpApi(
@Parameter(hidden = true) Long userId,
UserSignUpApiRequest apiRequest);
UserSignUpApiReq apiRequest);

@Operation(summary = "회원 삭제", description = "회원을 삭제할 때 사용하는 API")
@ApiResponses(value = {
Expand Down Expand Up @@ -80,4 +83,31 @@ ResponseEntity<UserSignUpApiResponse> userSignUpApi(
})
ResponseEntity<List<UserDevelopLanguageApiRes>> userDevelopLanguageApi(
@Parameter(hidden = true) Long userId);

@Operation(summary = "매칭 상태 조회", description = "매칭 상태를 조회할 때 사용하는 API")
@ApiResponse(responseCode = "200", description = "매칭 상태 조회 성공")
ResponseEntity<UserMatchingStatus> userMatchingStatusApi(@Parameter(hidden = true) Long userId);

@Operation(summary = "회원 정보 수정", description = "회원 정보를 수정할 때 사용하는 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "회원 정보 수정 성공",
content = @Content(schema = @Schema(implementation = UserUpdateApiRes.class))),
@ApiResponse(responseCode = "400", description = "요청한 데이터가 유효하지 않음",
content = @Content(
mediaType = APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(name = "E020103", value = SwaggerUserErrorExamples.INVALID_USER_NAME),
@ExampleObject(name = "E020105", value = SwaggerUserErrorExamples.USER_WANTED_JOB_DUPLICATE),
@ExampleObject(name = "E020109", value = SwaggerUserErrorExamples.USER_AVATAR_URL_NOT_NULL)
})),
@ApiResponse(responseCode = "404", description = "해당 유저가 존재하지 않음",
content = @Content(
mediaType = APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class),
examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND))),
})
ResponseEntity<UserUpdateApiRes> userUpdateApi(@Parameter(hidden = true) Long userId,
UserUpdateApiReq request);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.oeid.mogakgo.core.configuration;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

private final WebSocketHandler webSocketHandler;

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/ws/chat").setAllowedOrigins("*");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.oeid.mogakgo.core.properties.swagger.error;

public class SwaggerChatErrorExamples {

public static final String CHAT_ROOM_NOT_FOUND = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":404,\"code\":\"E110301\",\"message\":\"해당 채팅방이 존재하지 않습니다.\"}";

private SwaggerChatErrorExamples() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class SwaggerProjectErrorExamples {
public static final String PROJECT_NOT_FOUND = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":404,\"code\":\"E030301\",\"message\":\"해당 프로젝트가 존재하지 않습니다.\"}";
public static final String PROJECT_FORBIDDEN_OPERATION = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":403,\"code\":\"E030201\",\"message\":\"해당 프로젝트에 대한 권한이 없습니다.\"}";
public static final String PROJECT_DELETION_NOT_ALLOWED = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E030106\",\"message\":\"매칭 중이거나 대기중인 프로젝트는 삭제할 수 없습니다.\"}";
public static final String PROJECT_CANCEL_NOT_ALLOWED = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E030107\",\"message\":\"이미 취소 되었거나 종료된 프로젝트는 취소할 수 없습니다.\"}";
public static final String PROJECT_CANCEL_NOT_ALLOWED = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E030107\",\"message\":\"진행중인 프로젝트만 취소 할 수 있습니다.\"}";
public static final String PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":403,\"code\":\"E050201\",\"message\":\"해당 프로젝트 요청에 대한 권한이 없습니다.\"}";
public static final String INVALID_MATCHING_USER_TO_CREATE_PROJECT = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E030108\",\"message\":\"매칭이 진행 중인 유저는 프로젝트 생성을 할 수 없습니다.\"}";
public static final String ALREADY_EXIST_PROGRESS_PROJECT = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E030109\",\"message\":\"종료되지 않은 프로젝트 카드가 있으면 프로젝트 생성을 할 수 없습니다.\"}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class SwaggerUserErrorExamples {
public static final String INVALID_USER_NAME = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020103\",\"message\":\"유저 이름은 비어있을 수 없습니다.\"}";
public static final String USER_AVAILABLE_LIKE_COUNT_IS_ZERO = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020107\",\"message\":\"당일 사용 가능한 찔러보기 요청을 모두 소진했습니다.\"}";
public static final String USER_AVAILABLE_LIKE_COUNT_IS_FULL = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020108\",\"message\":\"당일 사용 가능한 찔러보기 최대 요청 횟수를 초과활 수 없습니다.\"}";

public static final String USER_AVATAR_URL_NOT_NULL = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020109\",\"message\":\"유저 프로필 이미지는 비어있을 수 없습니다.\"}";
private SwaggerUserErrorExamples() {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.oeid.mogakgo.domain.achievement.infrastructure;

import io.oeid.mogakgo.domain.achievement.domain.Achievement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AchievementJpaRepository extends JpaRepository<Achievement, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.oeid.mogakgo.domain.chat.application;

import io.oeid.mogakgo.domain.chat.application.dto.req.ChatRoomCreateReq;
import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomCreateRes;
import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomDataRes;
import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomPublicRes;
import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom;
import io.oeid.mogakgo.domain.chat.infrastructure.ChatRepository;
import io.oeid.mogakgo.domain.chat.infrastructure.ChatRoomRoomJpaRepository;
import io.oeid.mogakgo.domain.matching.exception.MatchingException;
import io.oeid.mogakgo.domain.project.domain.entity.Project;
import io.oeid.mogakgo.domain.project.exception.ProjectException;
import io.oeid.mogakgo.domain.project.infrastructure.ProjectJpaRepository;
import io.oeid.mogakgo.domain.user.application.UserCommonService;
import io.oeid.mogakgo.domain.user.domain.User;
import io.oeid.mogakgo.exception.code.ErrorCode404;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ChatService {

private final UserCommonService userCommonService;
private final ChatRoomRoomJpaRepository chatRoomRepository;
private final ChatRepository chatRepository;
private final ProjectJpaRepository projectRepository;

// 채팅방 리스트 조회
// TODO 마지막 채팅 기록 가져오기 구현
public List<ChatRoomPublicRes> findAllChatRoomByUserId(Long userId) {
userCommonService.getUserById(userId);
return chatRoomRepository.findAllChatRoomByUserId(userId);
}

// 채팅방 생성
@Transactional
public ChatRoomCreateRes createChatRoom(Long creatorId, ChatRoomCreateReq request) {
Project project = projectRepository.findById(request.getProjectId())
.orElseThrow(() -> new MatchingException(ErrorCode404.PROJECT_NOT_FOUND));
User creator = userCommonService.getUserById(creatorId);
User sender = userCommonService.getUserById(request.getSenderId());
ChatRoom chatRoom = chatRoomRepository.save(
ChatRoom.builder().project(project).creator(creator).sender(sender).build());
chatRepository.createCollection(chatRoom.getId());
return ChatRoomCreateRes.from(chatRoom);
}

// 채팅방 조회
public ChatRoomDataRes findAllChatInChatRoom(Long userId, String chatRoomId) {
var user = userCommonService.getUserById(userId);
var chatRoom = chatRoomRepository.findById(chatRoomId)
.orElseThrow(() -> new MatchingException(ErrorCode404.CHAT_ROOM_NOT_FOUND));
chatRoom.validateContainsUser(user);
var project = projectRepository.findById(chatRoom.getProject().getId())
.orElseThrow(() -> new ProjectException(ErrorCode404.PROJECT_NOT_FOUND));
var chatList = chatRepository.findAllByCollection(chatRoomId);
return ChatRoomDataRes.of(project.getMeetingInfo(), chatList);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.oeid.mogakgo.domain.chat.application;


import static io.oeid.mogakgo.exception.code.ErrorCode500.CHAT_WEB_SOCKET_ERROR;

import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage;
import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom;
import io.oeid.mogakgo.domain.chat.entity.enums.ChatStatus;
import io.oeid.mogakgo.domain.chat.exception.ChatException;
import io.oeid.mogakgo.domain.chat.infrastructure.ChatRepository;
import io.oeid.mogakgo.domain.chat.infrastructure.ChatRoomRoomJpaRepository;
import io.oeid.mogakgo.domain.chat.infrastructure.ChatRoomSessionRepository;
import io.oeid.mogakgo.exception.code.ErrorCode400;
import io.oeid.mogakgo.exception.code.ErrorCode404;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

@Slf4j
@RequiredArgsConstructor
@Service
public class ChatWebSocketService {

private final ChatRoomRoomJpaRepository chatRoomJpaRepository;
private final ChatRepository chatRepository;
private final ChatRoomSessionRepository chatRoomSessionRepository;

@Transactional(readOnly = true)
public ChatRoom findChatRoomById(String roomId) {
ChatRoom chatRoom = chatRoomJpaRepository.findById(roomId).orElseThrow(() -> new ChatException(ErrorCode404.CHAT_ROOM_NOT_FOUND));
if(chatRoom.getStatus().equals(ChatStatus.CLOSED)){
throw new ChatException(ErrorCode400.CHAT_ROOM_CLOSED);
}
return chatRoom;
}

public void saveChatMessage(ChatMessage chatMessage, String roomId) {
chatRepository.save(chatMessage, roomId);
}

public void closeChatRoom(String roomId) {
ChatRoom chatRoom = chatRoomJpaRepository.findById(roomId).orElseThrow(() -> new ChatException(ErrorCode404.CHAT_ROOM_NOT_FOUND));
chatRoomSessionRepository.removeRoom(chatRoom.getId());
chatRoom.closeChat();
}

public void addSessionToRoom(String roomId, WebSocketSession session) {
chatRoomSessionRepository.addSession(roomId, session);
}

public void removeSessionFromRoom(String roomId, WebSocketSession session) {
chatRoomSessionRepository.removeSession(roomId, session);
}

public void sendMessageToEachSocket(String roomId, TextMessage textMessage){
chatRoomSessionRepository.getSession(roomId).forEach(session -> {
try {
session.sendMessage(textMessage);
} catch (Exception e) {
log.error("sendMessageToEachSocket: {}", e.getMessage());
throw new ChatException(CHAT_WEB_SOCKET_ERROR);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.oeid.mogakgo.domain.chat.application.dto.req;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ChatRoomCreateReq {
private Long projectId;
private Long senderId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.oeid.mogakgo.domain.chat.application.dto.res;

import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom;
import io.oeid.mogakgo.domain.chat.entity.enums.ChatStatus;
import lombok.Getter;

@Getter
public class ChatRoomCreateRes {

private String roomId;
private Long projectId;
private Long creatorId;
private Long senderId;
private ChatStatus chatStatus;

private ChatRoomCreateRes(String roomId, Long projectId, Long creatorId, Long senderId,
ChatStatus chatStatus) {
this.roomId = roomId;
this.projectId = projectId;
this.creatorId = creatorId;
this.senderId = senderId;
this.chatStatus = chatStatus;
}

public static ChatRoomCreateRes from(ChatRoom chatRoom) {
return new ChatRoomCreateRes(chatRoom.getId(), chatRoom.getProject().getId(),
chatRoom.getCreator().getId(), chatRoom.getSender().getId(), chatRoom.getStatus());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.oeid.mogakgo.domain.chat.application.dto.res;

import io.oeid.mogakgo.domain.chat.application.vo.ChatData;
import io.oeid.mogakgo.domain.chat.application.vo.ChatRoomProjectInfo;
import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage;
import io.oeid.mogakgo.domain.project.domain.entity.vo.MeetingInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Schema(description = "채팅방 데이터 조회 응답")
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
public class ChatRoomDataRes {

private ChatRoomProjectInfo project;
private List<ChatData> data;

public static ChatRoomDataRes of(MeetingInfo meetingInfo, List<ChatMessage> data) {
ChatRoomProjectInfo project = new ChatRoomProjectInfo(meetingInfo.getMeetDetail(),
meetingInfo.getMeetStartTime(), meetingInfo.getMeetEndTime());
return new ChatRoomDataRes(project, data.stream().map(ChatData::from).toList());
}
}
Loading

0 comments on commit c98d24d

Please sign in to comment.