Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
AnTaeho committed Dec 9, 2024
2 parents 7258986 + 9ce6448 commit e27a56a
Show file tree
Hide file tree
Showing 24 changed files with 342 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.palettee.archive.repository;

import com.palettee.archive.controller.dto.response.ColorCount;
import com.palettee.archive.controller.dto.response.*;
import com.palettee.archive.domain.*;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import java.util.*;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.repository.*;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.*;

public interface ArchiveRepository extends JpaRepository<Archive, Long>, ArchiveCustomRepository {

Expand All @@ -28,4 +27,6 @@ public interface ArchiveRepository extends JpaRepository<Archive, Long>, Archive
@Query("SELECT a.type AS type, COUNT(a) AS count FROM Archive a where a.id in :ids GROUP BY a.type")
List<ColorCount> countLikeArchiveByArchiveType(@Param("ids") List<Long> ids);

@Query("SELECT a FROM Archive a where a.user.id = :userId")
List<Archive> findAllByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@

public interface ChatUserRepository extends JpaRepository<ChatUser, Long> {
Optional<ChatUser> findByChatRoomAndUser(ChatRoom chatRoom, User user);
boolean existsByChatRoomAndUser(ChatRoom chatRoom, User user);
}
4 changes: 4 additions & 0 deletions src/main/java/com/palettee/chat/service/ChatUserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public void deleteChatUser(ChatRoom chatRoom, User user) {
chatUserRepository.delete(chatUser);
}

public boolean isExist(ChatRoom chatRoom, User user) {
return chatUserRepository.existsByChatRoomAndUser(chatRoom, user);
}

private ChatUser makeChatUser(ChatRoom chatRoom, User user) {
return ChatUser.builder()
.chatRoom(chatRoom)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.palettee.chat_room.controller.dto.request.ChatRoomCreateRequest;
import com.palettee.chat_room.controller.dto.response.ChatRoomResponse;
import com.palettee.chat_room.service.ChatRoomService;
import com.palettee.global.security.validation.UserUtils;
import com.palettee.user.domain.User;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
Expand All @@ -21,21 +23,24 @@ public class ChatRoomController {
// 채팅방 생성
@PostMapping
public ChatRoomResponse createChatRoom(@Valid @RequestBody ChatRoomCreateRequest chatRoomCreateRequest) {
return chatRoomService.saveChatRoom(chatRoomCreateRequest);
User contextUser = UserUtils.getContextUser();
return chatRoomService.saveChatRoom(chatRoomCreateRequest, contextUser);
}

// userId는 추후에 삭제할 예정
// 채팅방 참여
@PostMapping("/participation/{chatRoomId}/{userId}")
public void participateChatRoom(@PathVariable Long chatRoomId, @PathVariable Long userId) {
chatRoomService.participation(chatRoomId, userId);
@PostMapping("/participation/{chatRoomId}")
public void participateChatRoom(@PathVariable Long chatRoomId) {
User contextUser = UserUtils.getContextUser();
chatRoomService.participation(chatRoomId, contextUser);
}

// userId는 추후에 삭제할 예정
// 채팅방 나가기
@DeleteMapping("/leave/{chatRoomId}/{userId}")
public void leaveChatRoom(@PathVariable Long chatRoomId, @PathVariable Long userId) {
chatRoomService.leave(chatRoomId, userId);
@DeleteMapping("/leave/{chatRoomId}")
public void leaveChatRoom(@PathVariable Long chatRoomId) {
User contextUser = UserUtils.getContextUser();
chatRoomService.leave(chatRoomId, contextUser);
}

@GetMapping("/chats/{chatRoomId}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.palettee.chat_room.exception;

import com.palettee.global.exception.ErrorCode;
import com.palettee.global.exception.PaletteException;

public class DuplicateParticipationException extends PaletteException {
public static final PaletteException EXCEPTION = new DuplicateParticipationException();

public DuplicateParticipationException() {
super(ErrorCode.DUPLICATE_PARTICIPATION);
}
}
36 changes: 26 additions & 10 deletions src/main/java/com/palettee/chat_room/service/ChatRoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.palettee.chat_room.domain.ChatCategory;
import com.palettee.chat_room.domain.ChatRoom;
import com.palettee.chat_room.exception.ChatRoomNotFoundException;
import com.palettee.chat_room.exception.DuplicateParticipationException;
import com.palettee.chat_room.repository.ChatRoomRepository;
import com.palettee.notification.controller.dto.NotificationRequest;
import com.palettee.notification.domain.AlertType;
Expand All @@ -27,10 +28,16 @@ public class ChatRoomService {
private final NotificationService notificationService;

@Transactional
public ChatRoomResponse saveChatRoom(ChatRoomCreateRequest chatRoomCreateRequest) {
public ChatRoomResponse saveChatRoom(ChatRoomCreateRequest chatRoomCreateRequest,
User user) {
ChatRoom chatRoom = chatRoomCreateRequest.toEntityChatRoom();
ChatRoom savedChatRoom = chatRoomRepository.save(chatRoom);

User findUser = getUser(user.getId());
chatUserService.saveChatUser(savedChatRoom, findUser);

sendNotification(chatRoomCreateRequest, savedChatRoom);

return ChatRoomResponse.of(savedChatRoom);
}

Expand All @@ -54,17 +61,23 @@ private AlertType getType(ChatCategory chatCategory) {
}

@Transactional
public void participation(Long chatRoomId, Long userId) {
public void participation(Long chatRoomId, User user) {
ChatRoom chatRoom = getChatRoom(chatRoomId);
User user = getUser(userId);
chatUserService.saveChatUser(chatRoom, user);
User findUser = getUser(user.getId());

validDuplicateParticipation(chatRoom, findUser);

chatUserService.saveChatUser(chatRoom, findUser);
}

@Transactional
public void leave(Long chatRoomId, Long userId) {
public void leave(Long chatRoomId, User user) {
ChatRoom chatRoom = getChatRoom(chatRoomId);
User user = getUser(userId);
chatUserService.deleteChatUser(chatRoom, user);
User findUser = getUser(user.getId());

if(chatUserService.isExist(chatRoom, user)) {
chatUserService.deleteChatUser(chatRoom, findUser);
}
}

public ChatRoom getChatRoom(Long chatRoomId) {
Expand All @@ -73,10 +86,13 @@ public ChatRoom getChatRoom(Long chatRoomId) {
.orElseThrow(() -> ChatRoomNotFoundException.EXCEPTION);
}

/**
* 추후에 토큰에서 user 정보 꺼낼 예정
*/
private User getUser(Long userId) {
return userRepository.findById(userId).get();
}

private void validDuplicateParticipation(ChatRoom chatRoom, User user) {
if(chatUserService.isExist(chatRoom, user)) {
throw DuplicateParticipationException.EXCEPTION;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.palettee.gathering.repository;

import com.palettee.gathering.controller.dto.Response.*;
import com.palettee.portfolio.controller.dto.response.*;
import com.palettee.user.controller.dto.response.users.*;
import java.util.*;
import org.springframework.data.domain.*;

import java.util.List;

public interface GatheringRepositoryCustom {

CustomSliceResponse pageGathering(
Expand All @@ -33,7 +31,7 @@ CustomSliceResponse pageGathering(
* @param size 가져올 게더링 개수
* @param gatheringOffset 이전 조회에서 제공된 {@code nextGatheringId}
*/
GetUserGatheringResponse findGatheringsOnUserWithNoOffset(
GatheringPagingDTO findGatheringsOnUserWithNoOffset(
Long userId, int size,
Long gatheringOffset
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.palettee.gathering.repository;


import com.palettee.gathering.controller.dto.Response.GatheringResponse;
import static com.palettee.gathering.domain.QGathering.*;
import static com.palettee.gathering.domain.QPosition.*;
import static com.palettee.likes.domain.QLikes.*;
import static com.palettee.user.domain.QUser.*;

import com.palettee.gathering.controller.dto.Response.*;
import com.palettee.gathering.domain.Sort;
import com.palettee.gathering.domain.*;
import com.palettee.likes.domain.LikeType;
import com.palettee.portfolio.controller.dto.response.CustomSliceResponse;
import com.palettee.user.controller.dto.response.users.GetUserGatheringResponse;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static com.palettee.gathering.domain.QGathering.gathering;
import static com.palettee.gathering.domain.QPosition.position;
import static com.palettee.likes.domain.QLikes.likes;
import static com.palettee.user.domain.QUser.user;
import com.palettee.likes.domain.*;
import com.palettee.portfolio.controller.dto.response.*;
import com.palettee.user.controller.dto.response.users.*;
import com.querydsl.core.*;
import com.querydsl.core.types.dsl.*;
import com.querydsl.jpa.impl.*;
import java.util.*;
import java.util.stream.*;
import org.springframework.data.domain.*;
import org.springframework.stereotype.*;

@Repository
public class GatheringRepositoryImpl implements GatheringRepositoryCustom {
Expand Down Expand Up @@ -117,7 +116,7 @@ public CustomSliceResponse PageFindLikeGathering(Pageable pageable, Long userId,
* {@inheritDoc}
*/
@Override
public GetUserGatheringResponse findGatheringsOnUserWithNoOffset(
public GatheringPagingDTO findGatheringsOnUserWithNoOffset(
Long userId, int size,
Long gatheringOffset
) {
Expand All @@ -144,7 +143,7 @@ public GetUserGatheringResponse findGatheringsOnUserWithNoOffset(
.toList();
}

return GetUserGatheringResponse.of(
return GatheringPagingDTO.of(
searchResult, hasNext, nextOffset
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/palettee/global/configs/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public BypassUrlHolder bypassUrlHolder() {

// webSocket
.byPassable("/index.html")
.byPassable("/ws")
.byPassable("/ws/**")

// swagger
.byPassable("/api-test")
Expand All @@ -68,7 +68,8 @@ public BypassUrlHolder bypassUrlHolder() {

// 유저의 프로필, 아카이브, 게더링 조회
.conditionalByPassable("/user/{id}/profile", HttpMethod.GET)
.byPassable(HttpMethod.GET, "/user/{id}/archives", "/user/{id}/gatherings")
.byPassable(HttpMethod.GET,
"/user/{id}/archive-colors", "/user/{id}/archives", "/user/{id}/gatherings")

// 유저 제보 목록, 상세 내용, 댓글 조회
.byPassable(HttpMethod.GET, "/report", "/report/{reportId}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") // 클라이언트가 WebSocket 접속할 수 있는 엔드포인트
.setAllowedOriginPatterns("*"); // CORS 설정을 통해 모든 도메인에서 WebSocket 연결을 허용
.setAllowedOriginPatterns("*") // CORS 설정을 통해 모든 도메인에서 WebSocket 연결을 허용
.withSockJS();
registry.setErrorHandler(webSocketErrorHandler);
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/palettee/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public enum ErrorCode {

NOT_ARCHIVE_OWNER(400, "나의 아카이브가 아닙니다."),

CHAT_OVER_LENGTH(400, "채팅 가능 숫자를 넘었습니다."),
CHAT_IMAGE_NUMBER_OVER(400, "채팅 이미지 가능 숫자를 넘었습니다."),
DUPLICATE_PARTICIPATION(400, "중복 참여하였습니다."),

/* 401 UNAUTHORIZED : 인증되지 않은 사용자 */
EXPIRED_TOKEN(401, "토큰이 만료되었습니다."),
Expand Down
53 changes: 46 additions & 7 deletions src/main/java/com/palettee/global/handler/StompHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.palettee.chat.controller.dto.request.ChatRequest;
import com.palettee.global.handler.exception.ChatContentNullException;
import com.palettee.global.handler.exception.JSONMappingException;
import com.palettee.global.handler.exception.WrongSubPathException;
import com.palettee.chat.exception.ChatUserNotFoundException;
import com.palettee.chat.repository.ChatRepository;
import com.palettee.chat.service.ChatUserService;
import com.palettee.chat_room.domain.ChatRoom;
import com.palettee.chat_room.service.ChatRoomService;
import com.palettee.global.handler.exception.*;
import com.palettee.global.security.jwt.exceptions.ExpiredTokenException;
import com.palettee.global.security.jwt.exceptions.InvalidTokenException;
import com.palettee.global.security.jwt.exceptions.RoleMismatchException;
import com.palettee.global.security.jwt.utils.JwtUtils;
import com.palettee.user.domain.User;
import com.palettee.user.domain.UserRole;
import com.palettee.user.exception.UserNotFoundException;
import com.palettee.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
Expand All @@ -25,15 +31,19 @@
@Slf4j
public class StompHandler implements ChannelInterceptor {

private static final String TOPIC_CHAT_ENDPOINT = "/sub/chat";
private static final String TOPIC_CHAT_ENDPOINT = "/sub/chat/";
private final JwtUtils jwtUtils;
private final ObjectMapper objectMapper;
private final UserRepository userRepository;
private final ChatRoomService chatRoomService;
private final ChatUserService chatUserService;

@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
// STOMP 프로토콜 메시지의 헤더와 관련된 정보를 쉽게 다룰 수 있도록 도와주는 유틸리티 객체 (헤더: 명령(COMMAND), 구독 정보, 세션 정보 등)
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
String token = accessor.getFirstNativeHeader("Authorization");
String destination = accessor.getDestination();

if (StompCommand.CONNECT.equals(accessor.getCommand())) {
validateAuthorization(token);
Expand All @@ -43,9 +53,8 @@ public Message<?> preSend(Message<?> message, MessageChannel channel) {
}

else if(StompCommand.SUBSCRIBE.equals(accessor.getCommand())) {
String destination = accessor.getDestination();
validateDestination(destination);
log.info("destination = {}", destination);
validateParticipation(accessor, destination);
}

else if(StompCommand.SEND.equals(accessor.getCommand())) {
Expand Down Expand Up @@ -87,7 +96,6 @@ private void validateDestination(String destination) {

private void validateUserRole(String token) {
UserRole userRole = jwtUtils.getUserRoleFromAccessToken(token);
log.info("userRole = {}", userRole);

if (userRole.equals(UserRole.JUST_NEWBIE) || userRole.equals(UserRole.REAL_NEWBIE)) {
log.error("Role is mismatch");
Expand Down Expand Up @@ -116,5 +124,36 @@ private void validateContent(Message<?> message) {
log.error("채팅 내용 null 오류");
throw ChatContentNullException.EXCEPTION;
}

if(chatRequest.content() != null) {
if(chatRequest.content().length() > 500) {
log.error("채팅 내용 길이 Over 오류");
throw ChatContentOverLength.EXCEPTION;
}
}

if(chatRequest.imgUrls().size() > 3) {
log.error("채팅 이미지 개수 Over 오류");
throw ChatImageOverNumber.EXCEPTION;
}
}

private void validateParticipation(StompHeaderAccessor accessor, String destination) {
String email = (String) accessor.getSessionAttributes().get("email");
User user = getUser(email);

String chatRoomId = destination.substring(TOPIC_CHAT_ENDPOINT.length());
ChatRoom chatRoom = chatRoomService.getChatRoom(Long.valueOf(chatRoomId));

if(!chatUserService.isExist(chatRoom, user)) {
log.error("해당 채팅방에 참여자가 아닙니다.");
throw ChatUserNotFoundException.EXCEPTION;
}
}

private User getUser(String email) {
return userRepository
.findByEmail(email)
.orElseThrow(() -> UserNotFoundException.EXCEPTION);
}
}
Loading

0 comments on commit e27a56a

Please sign in to comment.