diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbfc7c1 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +
+ +## [짤 제목 바로가기](https://memetitle.com) +
+ + +
+ + + +
+ +
+ +
+ +## 기술 스택 + +### 프론트엔드 + + + +### 백엔드 + + + +### 인프라 + + + +
+ +## 서비스 요청 흐름도 + + + +## 배포 과정 + + + +## DB ERD + + +## API 문서 + diff --git a/build.gradle b/build.gradle index 1bde523..45ec24c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '2.7.5' + id 'org.springframework.boot' version '3.2.7' id 'io.spring.dependency-management' version '1.1.4' } @@ -8,7 +8,7 @@ group = 'com.example' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '11' + sourceCompatibility = '17' } configurations { @@ -28,12 +28,18 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.5' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + // queryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' diff --git a/src/main/java/com/memetitle/auth/AdminMemberArgumentResolver.java b/src/main/java/com/memetitle/auth/AdminMemberArgumentResolver.java index af7cb82..464feda 100644 --- a/src/main/java/com/memetitle/auth/AdminMemberArgumentResolver.java +++ b/src/main/java/com/memetitle/auth/AdminMemberArgumentResolver.java @@ -1,10 +1,8 @@ package com.memetitle.auth; import com.memetitle.auth.dto.AdminMember; -import com.memetitle.auth.infrastructure.JwtProvider; import com.memetitle.global.exception.AuthException; import com.memetitle.global.exception.ErrorCode; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -12,7 +10,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Slf4j public class AdminMemberArgumentResolver implements HandlerMethodArgumentResolver { diff --git a/src/main/java/com/memetitle/auth/AuthHandlerInterceptor.java b/src/main/java/com/memetitle/auth/AuthHandlerInterceptor.java index efb471e..d3e1c4b 100644 --- a/src/main/java/com/memetitle/auth/AuthHandlerInterceptor.java +++ b/src/main/java/com/memetitle/auth/AuthHandlerInterceptor.java @@ -8,8 +8,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Optional; @Slf4j diff --git a/src/main/java/com/memetitle/auth/LoginMemberArgumentResolver.java b/src/main/java/com/memetitle/auth/LoginMemberArgumentResolver.java index f3bf1ce..f328854 100644 --- a/src/main/java/com/memetitle/auth/LoginMemberArgumentResolver.java +++ b/src/main/java/com/memetitle/auth/LoginMemberArgumentResolver.java @@ -10,7 +10,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @RequiredArgsConstructor public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { diff --git a/src/main/java/com/memetitle/auth/domain/RefreshToken.java b/src/main/java/com/memetitle/auth/domain/RefreshToken.java index 6d47443..e341bc8 100644 --- a/src/main/java/com/memetitle/auth/domain/RefreshToken.java +++ b/src/main/java/com/memetitle/auth/domain/RefreshToken.java @@ -4,9 +4,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; @Entity @Getter diff --git a/src/main/java/com/memetitle/auth/infrastructure/JwtProvider.java b/src/main/java/com/memetitle/auth/infrastructure/JwtProvider.java index 4d75955..48bddcc 100644 --- a/src/main/java/com/memetitle/auth/infrastructure/JwtProvider.java +++ b/src/main/java/com/memetitle/auth/infrastructure/JwtProvider.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Component; import javax.crypto.SecretKey; + import java.math.BigInteger; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; diff --git a/src/main/java/com/memetitle/auth/presentation/LoginController.java b/src/main/java/com/memetitle/auth/presentation/LoginController.java index 36aa734..7415276 100644 --- a/src/main/java/com/memetitle/auth/presentation/LoginController.java +++ b/src/main/java/com/memetitle/auth/presentation/LoginController.java @@ -6,11 +6,10 @@ import com.memetitle.auth.dto.response.TokenResponse; import com.memetitle.auth.service.LoginService; import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import static org.springframework.http.HttpHeaders.SET_COOKIE; diff --git a/src/main/java/com/memetitle/chat/config/CustomHandshakeInterceptor.java b/src/main/java/com/memetitle/chat/config/CustomHandshakeInterceptor.java new file mode 100644 index 0000000..f192338 --- /dev/null +++ b/src/main/java/com/memetitle/chat/config/CustomHandshakeInterceptor.java @@ -0,0 +1,39 @@ +package com.memetitle.chat.config; + +import com.memetitle.chat.domain.ChatRoom; +import com.memetitle.chat.repository.ChatRoomRepository; +import com.memetitle.global.exception.InvalidException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import java.util.Map; + +import static com.memetitle.global.exception.ErrorCode.NOT_FOUND_CHATROOM_ID; + +@Component +@RequiredArgsConstructor +public class CustomHandshakeInterceptor implements HandshakeInterceptor { + + private final ChatRoomRepository chatRoomRepository; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + String path = request.getURI().getPath(); + String roomId = path.split("/")[2]; + ChatRoom chatRoom = chatRoomRepository.findById(Long.parseLong(roomId)) + .orElseThrow(() -> new InvalidException(NOT_FOUND_CHATROOM_ID)); + + if (chatRoom.getMemberCount() == chatRoom.getMaxCapacity()) { + return false; + } + + return true; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {} +} diff --git a/src/main/java/com/memetitle/chat/config/WebSocketConfig.java b/src/main/java/com/memetitle/chat/config/WebSocketConfig.java new file mode 100644 index 0000000..1c81180 --- /dev/null +++ b/src/main/java/com/memetitle/chat/config/WebSocketConfig.java @@ -0,0 +1,35 @@ +package com.memetitle.chat.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +@RequiredArgsConstructor +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + private final WebSocketInterceptor webSocketInterceptor; + private final CustomHandshakeInterceptor customHandshakeInterceptor; + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws/{roomId}") // socket 연결 url + .setAllowedOrigins("https://memetitle.com", "http://localhost:3000") + .addInterceptors(customHandshakeInterceptor); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/sub"); // 구독 url(채팅방 참여=구독) + config.setApplicationDestinationPrefixes("/pub"); // 메세지 발행 prefixes 설정 + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(webSocketInterceptor); + } +} \ No newline at end of file diff --git a/src/main/java/com/memetitle/chat/config/WebSocketInterceptor.java b/src/main/java/com/memetitle/chat/config/WebSocketInterceptor.java new file mode 100644 index 0000000..8d9ce9e --- /dev/null +++ b/src/main/java/com/memetitle/chat/config/WebSocketInterceptor.java @@ -0,0 +1,54 @@ +package com.memetitle.chat.config; + +import com.memetitle.chat.repository.ChatRoomStorage; +import com.memetitle.chat.service.ChatService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.stereotype.Component; + +/** + * 채팅방 인원 관리용 인터셉터 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class WebSocketInterceptor implements ChannelInterceptor { + + private final ChatService chatService; + private final ChatRoomStorage chatRoomStorage; + + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.wrap(message); + + if (stompHeaderAccessor != null) { + StompCommand command = stompHeaderAccessor.getCommand(); + + switch (command) { + case SUBSCRIBE -> { + String simpDestination = (String) message.getHeaders().get("simpDestination"); + String roomId = simpDestination.split("/")[3]; + chatService.increaseMemberCount(Long.parseLong(roomId)); + + String sessionId = (String) message.getHeaders().get("simpSessionId"); + chatRoomStorage.storage(sessionId, Long.parseLong(roomId)); + + log.info("websocket subscribe"); + } + case DISCONNECT -> { + String sessionId = (String) message.getHeaders().get("simpSessionId"); + Long roomId = chatRoomStorage.getRoomId(sessionId); + chatService.decreaseMemberCount(roomId); + chatRoomStorage.delete(sessionId); + log.info("websocket disconnect"); + } + } + } + return message; + } +} diff --git a/src/main/java/com/memetitle/chat/domain/ChatRoom.java b/src/main/java/com/memetitle/chat/domain/ChatRoom.java new file mode 100644 index 0000000..1dae452 --- /dev/null +++ b/src/main/java/com/memetitle/chat/domain/ChatRoom.java @@ -0,0 +1,34 @@ +package com.memetitle.chat.domain; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public class ChatRoom { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Column(nullable = false) + private int memberCount; + + @Column(name = "max_capacity", nullable = false) + private int maxCapacity = 10; + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; +} diff --git a/src/main/java/com/memetitle/chat/dto/ChatRoomElement.java b/src/main/java/com/memetitle/chat/dto/ChatRoomElement.java new file mode 100644 index 0000000..91c6676 --- /dev/null +++ b/src/main/java/com/memetitle/chat/dto/ChatRoomElement.java @@ -0,0 +1,28 @@ +package com.memetitle.chat.dto; + +import com.memetitle.chat.domain.ChatRoom; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class ChatRoomElement { + + private Long id; + private String name; + private int memberCount; + private int maxCapacity; + private LocalDateTime createdAt; + + public static ChatRoomElement of(ChatRoom chatRoom) { + return ChatRoomElement.builder() + .id(chatRoom.getId()) + .name(chatRoom.getName()) + .memberCount(chatRoom.getMemberCount()) + .maxCapacity(chatRoom.getMaxCapacity()) + .createdAt(chatRoom.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/com/memetitle/chat/dto/request/ChatMessageRequest.java b/src/main/java/com/memetitle/chat/dto/request/ChatMessageRequest.java new file mode 100644 index 0000000..6b13f87 --- /dev/null +++ b/src/main/java/com/memetitle/chat/dto/request/ChatMessageRequest.java @@ -0,0 +1,19 @@ +package com.memetitle.chat.dto.request; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ChatMessageRequest { + + private String nickname; + private String message; + private String date; + + public ChatMessageRequest(String nickname, String message, String date) { + this.nickname = nickname; + this.message = message; + this.date = date; + } +} \ No newline at end of file diff --git a/src/main/java/com/memetitle/chat/dto/response/ChatMessageResponse.java b/src/main/java/com/memetitle/chat/dto/response/ChatMessageResponse.java new file mode 100644 index 0000000..ea0bbac --- /dev/null +++ b/src/main/java/com/memetitle/chat/dto/response/ChatMessageResponse.java @@ -0,0 +1,16 @@ +package com.memetitle.chat.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ChatMessageResponse { + + private String nickname; + private String message; + private Long roomId; + private String date; + +} + diff --git a/src/main/java/com/memetitle/chat/dto/response/ChatRoomsResponse.java b/src/main/java/com/memetitle/chat/dto/response/ChatRoomsResponse.java new file mode 100644 index 0000000..ff61a6e --- /dev/null +++ b/src/main/java/com/memetitle/chat/dto/response/ChatRoomsResponse.java @@ -0,0 +1,24 @@ +package com.memetitle.chat.dto.response; + +import com.memetitle.chat.domain.ChatRoom; +import com.memetitle.chat.dto.ChatRoomElement; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class ChatRoomsResponse { + private List chatRooms; + + public static ChatRoomsResponse ofChatRooms(List chatRooms) { + final List chatRoomElements = chatRooms.stream() + .map(ChatRoomElement::of) + .toList(); + + return ChatRoomsResponse.builder() + .chatRooms(chatRoomElements) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/memetitle/chat/presentation/ChatController.java b/src/main/java/com/memetitle/chat/presentation/ChatController.java new file mode 100644 index 0000000..536af13 --- /dev/null +++ b/src/main/java/com/memetitle/chat/presentation/ChatController.java @@ -0,0 +1,45 @@ +package com.memetitle.chat.presentation; + +import com.memetitle.chat.dto.request.ChatMessageRequest; +import com.memetitle.chat.dto.response.ChatMessageResponse; +import com.memetitle.chat.dto.response.ChatRoomsResponse; +import com.memetitle.chat.service.ChatService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class ChatController { + + private final ChatService chatService; + /** + * 클라이언트에서 메세지 요청시 : /pub/chat/message + * 채팅방 사람들에게 메세지 전달 : /sub/chat/messages + */ + @MessageMapping("/chat/{roomId}/message") + @SendTo("/sub/chat/{roomId}/messages") + public ResponseEntity receiveMessage( + @DestinationVariable final Long roomId, + @RequestBody final ChatMessageRequest chatMessageRequest + ) { + // 메시지를 해당 채팅방 구독자들에게 전송 + ChatMessageResponse chatMessageResponse = ChatMessageResponse.builder() + .nickname(chatMessageRequest.getNickname()) + .message(chatMessageRequest.getMessage()) + .roomId(roomId) + .date(chatMessageRequest.getDate()) + .build(); + return ResponseEntity.ok(chatMessageResponse); + } + + @GetMapping("/chat/rooms") + public ResponseEntity getChatRooms() { + return ResponseEntity.ok(chatService.getChatRooms()); + } +} diff --git a/src/main/java/com/memetitle/chat/repository/ChatRoomRepository.java b/src/main/java/com/memetitle/chat/repository/ChatRoomRepository.java new file mode 100644 index 0000000..6a575ac --- /dev/null +++ b/src/main/java/com/memetitle/chat/repository/ChatRoomRepository.java @@ -0,0 +1,17 @@ +package com.memetitle.chat.repository; + +import com.memetitle.chat.domain.ChatRoom; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +public interface ChatRoomRepository extends JpaRepository { + + @Modifying + @Query(value = "UPDATE ChatRoom cr set cr.memberCount = cr.memberCount + 1 where cr.id = :id") + void increaseMemberCount(Long id); + + @Modifying + @Query(value = "UPDATE ChatRoom cr set cr.memberCount = cr.memberCount - 1 where cr.id = :id") + void decreaseMemberCount(Long id); +} diff --git a/src/main/java/com/memetitle/chat/repository/ChatRoomStorage.java b/src/main/java/com/memetitle/chat/repository/ChatRoomStorage.java new file mode 100644 index 0000000..f2b2437 --- /dev/null +++ b/src/main/java/com/memetitle/chat/repository/ChatRoomStorage.java @@ -0,0 +1,26 @@ +package com.memetitle.chat.repository; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Repository +@Slf4j +public class ChatRoomStorage { + + private final ConcurrentMap chatRoomMembers = new ConcurrentHashMap<>(); + + public void storage(String sessionId, Long roomId) { + chatRoomMembers.put(sessionId, roomId); + } + + public Long getRoomId(String sessionId) { + return chatRoomMembers.get(sessionId); + } + + public void delete(String sessionId) { + chatRoomMembers.remove(sessionId); + } +} diff --git a/src/main/java/com/memetitle/chat/service/ChatService.java b/src/main/java/com/memetitle/chat/service/ChatService.java new file mode 100644 index 0000000..e7715dd --- /dev/null +++ b/src/main/java/com/memetitle/chat/service/ChatService.java @@ -0,0 +1,32 @@ +package com.memetitle.chat.service; + +import com.memetitle.chat.domain.ChatRoom; +import com.memetitle.chat.dto.response.ChatRoomsResponse; +import com.memetitle.chat.repository.ChatRoomRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class ChatService { + + private final ChatRoomRepository chatRoomRepository; + + public ChatRoomsResponse getChatRooms() { + + final List chatRooms = chatRoomRepository.findAll(); + return ChatRoomsResponse.ofChatRooms(chatRooms); + } + + public void increaseMemberCount(final Long roomId) { + chatRoomRepository.increaseMemberCount(roomId); + } + + public void decreaseMemberCount(final Long roomId) { + chatRoomRepository.decreaseMemberCount(roomId); + } +} diff --git a/src/main/java/com/memetitle/comment/domain/Comment.java b/src/main/java/com/memetitle/comment/domain/Comment.java index 88beb50..c198a0f 100644 --- a/src/main/java/com/memetitle/comment/domain/Comment.java +++ b/src/main/java/com/memetitle/comment/domain/Comment.java @@ -11,7 +11,7 @@ import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import javax.persistence.*; +import jakarta.persistence.*; import java.time.LocalDateTime; @Entity diff --git a/src/main/java/com/memetitle/comment/domain/CommentLike.java b/src/main/java/com/memetitle/comment/domain/CommentLike.java index dbe36c8..36a35a6 100644 --- a/src/main/java/com/memetitle/comment/domain/CommentLike.java +++ b/src/main/java/com/memetitle/comment/domain/CommentLike.java @@ -7,7 +7,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Getter diff --git a/src/main/java/com/memetitle/comment/dto/request/CommentCreateRequest.java b/src/main/java/com/memetitle/comment/dto/request/CommentCreateRequest.java index 0abfd73..cbcf280 100644 --- a/src/main/java/com/memetitle/comment/dto/request/CommentCreateRequest.java +++ b/src/main/java/com/memetitle/comment/dto/request/CommentCreateRequest.java @@ -3,7 +3,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @NoArgsConstructor() @Getter diff --git a/src/main/java/com/memetitle/comment/dto/request/CommentModifyRequest.java b/src/main/java/com/memetitle/comment/dto/request/CommentModifyRequest.java index c8fcfc2..8bafc22 100644 --- a/src/main/java/com/memetitle/comment/dto/request/CommentModifyRequest.java +++ b/src/main/java/com/memetitle/comment/dto/request/CommentModifyRequest.java @@ -3,7 +3,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @NoArgsConstructor() @Getter diff --git a/src/main/java/com/memetitle/comment/dto/response/CommentsResponse.java b/src/main/java/com/memetitle/comment/dto/response/CommentsResponse.java index c8eaac8..f4b6dc4 100644 --- a/src/main/java/com/memetitle/comment/dto/response/CommentsResponse.java +++ b/src/main/java/com/memetitle/comment/dto/response/CommentsResponse.java @@ -22,7 +22,7 @@ public class CommentsResponse { public static CommentsResponse ofComments(Page comments) { final List commentElement = comments.stream() .map(CommentElement::of) - .collect(Collectors.toList()); + .toList(); return CommentsResponse.builder() .comments(commentElement) @@ -36,7 +36,7 @@ public static CommentsResponse ofComments(Page comments) { public static CommentsResponse ofCommentDtos(Page comments) { final List commentElement = comments.stream() .map(CommentElement::of) - .collect(Collectors.toList()); + .toList(); return CommentsResponse.builder() .comments(commentElement) diff --git a/src/main/java/com/memetitle/comment/presentation/CommentController.java b/src/main/java/com/memetitle/comment/presentation/CommentController.java index b9fedee..5e5a7b3 100644 --- a/src/main/java/com/memetitle/comment/presentation/CommentController.java +++ b/src/main/java/com/memetitle/comment/presentation/CommentController.java @@ -12,7 +12,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.net.URI; import static org.springframework.data.domain.Sort.Direction.DESC; diff --git a/src/main/java/com/memetitle/comment/repository/CommentCustomRepository.java b/src/main/java/com/memetitle/comment/repository/CommentCustomRepository.java new file mode 100644 index 0000000..bf892f4 --- /dev/null +++ b/src/main/java/com/memetitle/comment/repository/CommentCustomRepository.java @@ -0,0 +1,13 @@ +package com.memetitle.comment.repository; + +import com.memetitle.comment.dto.CommentDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface CommentCustomRepository { + + Page findByTitleId(Long titleId, Pageable pageable); + + Page findByTitleId(Long memberId, Long titleId, Pageable pageable); + +} diff --git a/src/main/java/com/memetitle/comment/repository/CommentCustomRepositoryImpl.java b/src/main/java/com/memetitle/comment/repository/CommentCustomRepositoryImpl.java new file mode 100644 index 0000000..e5a62d4 --- /dev/null +++ b/src/main/java/com/memetitle/comment/repository/CommentCustomRepositoryImpl.java @@ -0,0 +1,119 @@ +package com.memetitle.comment.repository; + +import com.memetitle.comment.domain.Comment; +import com.memetitle.comment.domain.QComment; +import com.memetitle.comment.domain.QCommentLike; +import com.memetitle.comment.dto.CommentDto; +import com.memetitle.member.domain.QMember; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.PathBuilder; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@RequiredArgsConstructor +@Repository +public class CommentCustomRepositoryImpl implements CommentCustomRepository{ + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public Page findByTitleId(Long titleId, Pageable pageable) { + QComment comment = QComment.comment; + QMember member = QMember.member; + + // Main query + List content = jpaQueryFactory + .select(Projections.constructor(CommentDto.class, + comment.id, + comment.title.id, + comment.title.memeId, + comment.content, + comment.member.id, + comment.member.nickname, + comment.member.imgUrl, + comment.likeCount, + comment.createdAt, + Expressions.constant(false), + Expressions.constant(false) + )) + .from(comment) + .leftJoin(comment.member, member) + .where(comment.title.id.eq(titleId)) + .offset(pageable.getOffset()) + .orderBy(getOrderSpecifier(pageable.getSort()).stream().toArray(OrderSpecifier[]::new)) + .limit(pageable.getPageSize()) + .fetch(); + + // Count query + JPAQuery countQuery = jpaQueryFactory + .select(comment.count()) + .from(comment) + .where(comment.title.id.eq(titleId)); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } + + @Override + public Page findByTitleId(Long memberId, Long titleId, Pageable pageable) { + QComment comment = QComment.comment; + QCommentLike commentLike = QCommentLike.commentLike; + QMember member = QMember.member; + + List content = jpaQueryFactory + .select(Projections.constructor(CommentDto.class, + comment.id, + comment.title.id, + comment.title.memeId, + comment.content, + comment.member.id, + comment.member.nickname, + comment.member.imgUrl, + comment.likeCount, + comment.createdAt, + new CaseBuilder() + .when(commentLike.id.isNull()).then(false) + .otherwise(true).as("isLiked"), + new CaseBuilder() + .when(comment.member.id.eq(memberId)).then(true) + .otherwise(false).as("isOwner") + )) + .from(comment) + .leftJoin(comment.member, member) + .leftJoin(commentLike).on(comment.id.eq(commentLike.comment.id) + .and(commentLike.member.id.eq(memberId))) + .where(comment.title.id.eq(titleId)) + .offset(pageable.getOffset()) + .orderBy(getOrderSpecifier(pageable.getSort()).stream().toArray(OrderSpecifier[]::new)) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = jpaQueryFactory + .select(comment.count()) + .from(comment) + .where(comment.title.id.eq(titleId)); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } + + private List getOrderSpecifier(Sort sort) { + return sort.stream() + .map(order -> { + Order direction = order.isAscending() ? Order.ASC : Order.DESC; + PathBuilder orderByExpression = new PathBuilder(Comment.class, "comment"); + return new OrderSpecifier(direction, orderByExpression.get(order.getProperty())); + }) + .toList(); + } +} diff --git a/src/main/java/com/memetitle/comment/repository/CommentRepository.java b/src/main/java/com/memetitle/comment/repository/CommentRepository.java index 0523759..62bf489 100644 --- a/src/main/java/com/memetitle/comment/repository/CommentRepository.java +++ b/src/main/java/com/memetitle/comment/repository/CommentRepository.java @@ -9,19 +9,19 @@ import org.springframework.data.jpa.repository.Query; -public interface CommentRepository extends JpaRepository { +public interface CommentRepository extends JpaRepository, CommentCustomRepository{ - @Query(value = "SELECT new com.memetitle.comment.dto.CommentDto(c.id, c.title.id, c.title.memeId, c.content, c.member.id, c.member.nickname, c.member.imgUrl, c.likeCount, c.createdAt, false, false)" + - " from Comment c LEFT JOIN c.member m " + - " WHERE c.title.id = :titleId" - ,countQuery = "select count(c) from Comment c where c.title.id = :titleId") - Page findByTitleId(Long titleId, Pageable pageable); +// @Query(value = "SELECT new com.memetitle.comment.dto.CommentDto(c.id, c.title.id, c.title.memeId, c.content, c.member.id, c.member.nickname, c.member.imgUrl, c.likeCount, c.createdAt, false, false)" + +// " from Comment c LEFT JOIN c.member m " + +// " WHERE c.title.id = :titleId" +// ,countQuery = "select count(c) from Comment c where c.title.id = :titleId") +// Page findByTitleId(Long titleId, Pageable pageable); - @Query(value = "SELECT new com.memetitle.comment.dto.CommentDto(c.id, c.title.id, c.title.memeId, c.content, c.member.id, c.member.nickname, c.member.imgUrl, c.likeCount, c.createdAt, (CASE WHEN cl.id IS NULL THEN false ELSE true END), (CASE WHEN c.member.id = :memberId THEN true ELSE false END))" + - " from Comment c LEFT JOIN c.member m " + - " LEFT JOIN CommentLike cl ON c.id = cl.comment.id AND cl.member.id = :memberId" + - " WHERE c.title.id = :titleId") - Page findByTitleId(Long memberId, Long titleId, Pageable pageable); +// @Query(value = "SELECT new com.memetitle.comment.dto.CommentDto(c.id, c.title.id, c.title.memeId, c.content, c.member.id, c.member.nickname, c.member.imgUrl, c.likeCount, c.createdAt, (CASE WHEN cl.id IS NULL THEN false ELSE true END), (CASE WHEN c.member.id = :memberId THEN true ELSE false END))" + +// " from Comment c LEFT JOIN c.member m " + +// " LEFT JOIN CommentLike cl ON c.id = cl.comment.id AND cl.member.id = :memberId" + +// " WHERE c.title.id = :titleId") +// Page findByTitleId(Long memberId, Long titleId, Pageable pageable); @EntityGraph(attributePaths = {"member"}) Page findByMemberId(Long memberId, Pageable pageable); diff --git a/src/main/java/com/memetitle/global/aspect/LogAspect.java b/src/main/java/com/memetitle/global/aspect/LogAspect.java index c272e82..0554dca 100644 --- a/src/main/java/com/memetitle/global/aspect/LogAspect.java +++ b/src/main/java/com/memetitle/global/aspect/LogAspect.java @@ -2,13 +2,13 @@ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.*; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; @Aspect @Slf4j @@ -21,14 +21,16 @@ public void controller() { @AfterReturning(pointcut = "controller()", returning = "responseEntity") public void afterReturning(ResponseEntity responseEntity) { - HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); - if (request != null) { - HttpStatus statusCode = responseEntity.getStatusCode(); - if (statusCode != null) { - log.info("[Response sent: {} {} {}]", request.getMethod(), request.getRequestURI(), statusCode); - } else { - log.info("[Response sent: {} {}]", request.getMethod(), request.getRequestURI()); + try { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + if (request != null) { + HttpStatusCode statusCode = responseEntity.getStatusCode(); + if (statusCode != null) { + log.info("[Response sent: {} {} {}]", request.getMethod(), request.getRequestURI(), statusCode); + } else { + log.info("[Response sent: {} {}]", request.getMethod(), request.getRequestURI()); + } } - } + } catch (IllegalStateException e) {} } } diff --git a/src/main/java/com/memetitle/global/common/interceptor/PathMatcherInterceptor.java b/src/main/java/com/memetitle/global/common/interceptor/PathMatcherInterceptor.java index 8129be6..9645f49 100644 --- a/src/main/java/com/memetitle/global/common/interceptor/PathMatcherInterceptor.java +++ b/src/main/java/com/memetitle/global/common/interceptor/PathMatcherInterceptor.java @@ -2,8 +2,8 @@ import org.springframework.web.servlet.HandlerInterceptor; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public class PathMatcherInterceptor implements HandlerInterceptor { private final HandlerInterceptor handlerInterceptor; diff --git a/src/main/java/com/memetitle/global/config/QueryDSLConfig.java b/src/main/java/com/memetitle/global/config/QueryDSLConfig.java new file mode 100644 index 0000000..92a5761 --- /dev/null +++ b/src/main/java/com/memetitle/global/config/QueryDSLConfig.java @@ -0,0 +1,18 @@ +package com.memetitle.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class QueryDSLConfig { + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory(){ + return new JPAQueryFactory(entityManager); + } +} \ No newline at end of file diff --git a/src/main/java/com/memetitle/global/exception/ErrorCode.java b/src/main/java/com/memetitle/global/exception/ErrorCode.java index 89c57af..3a55c75 100644 --- a/src/main/java/com/memetitle/global/exception/ErrorCode.java +++ b/src/main/java/com/memetitle/global/exception/ErrorCode.java @@ -12,6 +12,7 @@ public enum ErrorCode { NOT_FOUND_MEME_ID(2001, "해당 밈을 찾을 수 없습니다."), NOT_FOUND_TITLE_ID(2002, "해당 제목을 찾을 수 없습니다."), NOT_FOUND_COMMENT_ID(2003, "해당 댓글을 찾을 수 없습니다."), + NOT_FOUND_CHATROOM_ID(2009, "해당 채팅방을 찾을 수 없습니다."), NOT_FOUND_TITLE_LIKE(2004, "해당 제목에 좋아요가 없습니다."), NOT_FOUND_COMMENT_LIKE(2005, "해당 댓글에 좋아요가 없습니다."), TITLE_ACCESS_DENIED(2012, "해당 제목에 대한 권한이 없습니다."), diff --git a/src/main/java/com/memetitle/global/exception/GlobalExceptionHandler.java b/src/main/java/com/memetitle/global/exception/GlobalExceptionHandler.java index c905c0d..16fdd91 100644 --- a/src/main/java/com/memetitle/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/memetitle/global/exception/GlobalExceptionHandler.java @@ -2,7 +2,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -27,7 +27,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { protected ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException e, HttpHeaders headers, - HttpStatus status, + HttpStatusCode status, WebRequest request ) { String errMessage = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage(); @@ -37,8 +37,8 @@ protected ResponseEntity handleMethodArgumentNotValid( .body(new ErrorResponse(INVALID_REQUEST.getCode(), errMessage)); } - @ExceptionHandler(MaxUploadSizeExceededException.class) - public ResponseEntity handleMaxUploadSizeExceededException(MaxUploadSizeExceededException ex) { + @Override + protected ResponseEntity handleMaxUploadSizeExceededException(MaxUploadSizeExceededException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { log.info("[Response sent: MaxUploadSizeExceededException - {}]", "파일 업로드 실패: 최대 업로드 크기를 초과했습니다"); return ResponseEntity.badRequest() @@ -68,4 +68,5 @@ protected ResponseEntity handleException(Exception e) { return ResponseEntity.badRequest() .body(new ErrorResponse(SERVER_ERROR)); } + } diff --git a/src/main/java/com/memetitle/global/filter/MDCLoggingFilter.java b/src/main/java/com/memetitle/global/filter/MDCLoggingFilter.java index ee5ffa9..045d677 100644 --- a/src/main/java/com/memetitle/global/filter/MDCLoggingFilter.java +++ b/src/main/java/com/memetitle/global/filter/MDCLoggingFilter.java @@ -6,8 +6,8 @@ import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.UUID; diff --git a/src/main/java/com/memetitle/member/domain/Member.java b/src/main/java/com/memetitle/member/domain/Member.java index 6d47bc6..4dcfb29 100644 --- a/src/main/java/com/memetitle/member/domain/Member.java +++ b/src/main/java/com/memetitle/member/domain/Member.java @@ -3,12 +3,11 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import javax.persistence.*; +import jakarta.persistence.*; import java.time.LocalDateTime; @Entity diff --git a/src/main/java/com/memetitle/member/dto/request/ProfileModifyRequest.java b/src/main/java/com/memetitle/member/dto/request/ProfileModifyRequest.java index 416443d..a0e77b3 100644 --- a/src/main/java/com/memetitle/member/dto/request/ProfileModifyRequest.java +++ b/src/main/java/com/memetitle/member/dto/request/ProfileModifyRequest.java @@ -4,8 +4,8 @@ import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; @NoArgsConstructor() @Getter diff --git a/src/main/java/com/memetitle/member/dto/response/RankingResponse.java b/src/main/java/com/memetitle/member/dto/response/RankingResponse.java index 072726c..db80d72 100644 --- a/src/main/java/com/memetitle/member/dto/response/RankingResponse.java +++ b/src/main/java/com/memetitle/member/dto/response/RankingResponse.java @@ -21,7 +21,7 @@ public class RankingResponse { public static RankingResponse ofRankDto(Page rankDtos) { final List rankingElements = rankDtos.stream() .map(RankingElement::of) - .collect(Collectors.toList()); + .toList(); return RankingResponse.builder() .ranks(rankingElements) diff --git a/src/main/java/com/memetitle/member/presentation/MemberController.java b/src/main/java/com/memetitle/member/presentation/MemberController.java index 9b79416..84e27f3 100644 --- a/src/main/java/com/memetitle/member/presentation/MemberController.java +++ b/src/main/java/com/memetitle/member/presentation/MemberController.java @@ -15,7 +15,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; +import jakarta.validation.Valid; import static org.springframework.data.domain.Sort.Direction.DESC; diff --git a/src/main/java/com/memetitle/meme/domain/Meme.java b/src/main/java/com/memetitle/meme/domain/Meme.java index a353e41..ef1ff37 100644 --- a/src/main/java/com/memetitle/meme/domain/Meme.java +++ b/src/main/java/com/memetitle/meme/domain/Meme.java @@ -4,11 +4,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; -import java.time.LocalDate; +import jakarta.persistence.*; + import java.time.LocalDateTime; -import static javax.persistence.EnumType.STRING; +import static jakarta.persistence.EnumType.STRING; @Entity @Getter diff --git a/src/main/java/com/memetitle/meme/domain/Title.java b/src/main/java/com/memetitle/meme/domain/Title.java index 2ae5228..ce5fd20 100644 --- a/src/main/java/com/memetitle/meme/domain/Title.java +++ b/src/main/java/com/memetitle/meme/domain/Title.java @@ -4,11 +4,10 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.BatchSize; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import javax.persistence.*; +import jakarta.persistence.*; import java.time.LocalDateTime; @Entity diff --git a/src/main/java/com/memetitle/meme/domain/TitleLike.java b/src/main/java/com/memetitle/meme/domain/TitleLike.java index 4821944..caaa628 100644 --- a/src/main/java/com/memetitle/meme/domain/TitleLike.java +++ b/src/main/java/com/memetitle/meme/domain/TitleLike.java @@ -7,7 +7,7 @@ import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Getter diff --git a/src/main/java/com/memetitle/meme/domain/TopTitle.java b/src/main/java/com/memetitle/meme/domain/TopTitle.java index 9f6e76f..f5c18a0 100644 --- a/src/main/java/com/memetitle/meme/domain/TopTitle.java +++ b/src/main/java/com/memetitle/meme/domain/TopTitle.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.*; +import jakarta.persistence.*; import java.time.LocalDateTime; @Entity diff --git a/src/main/java/com/memetitle/meme/dto/request/TitleCreateRequest.java b/src/main/java/com/memetitle/meme/dto/request/TitleCreateRequest.java index f545885..48dc92d 100644 --- a/src/main/java/com/memetitle/meme/dto/request/TitleCreateRequest.java +++ b/src/main/java/com/memetitle/meme/dto/request/TitleCreateRequest.java @@ -4,7 +4,7 @@ import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @NoArgsConstructor() @Getter diff --git a/src/main/java/com/memetitle/meme/dto/response/MemesResponse.java b/src/main/java/com/memetitle/meme/dto/response/MemesResponse.java index 22671f3..c3b8217 100644 --- a/src/main/java/com/memetitle/meme/dto/response/MemesResponse.java +++ b/src/main/java/com/memetitle/meme/dto/response/MemesResponse.java @@ -18,7 +18,7 @@ public class MemesResponse { public static MemesResponse ofMemes(Slice memes) { final List memeElements = memes.stream() .map(MemeElement::of) - .collect(Collectors.toList()); + .toList(); return MemesResponse.builder() .memes(memeElements) diff --git a/src/main/java/com/memetitle/meme/dto/response/TitlesResponse.java b/src/main/java/com/memetitle/meme/dto/response/TitlesResponse.java index 2a25d63..909dfa3 100644 --- a/src/main/java/com/memetitle/meme/dto/response/TitlesResponse.java +++ b/src/main/java/com/memetitle/meme/dto/response/TitlesResponse.java @@ -20,7 +20,7 @@ public class TitlesResponse { public static TitlesResponse ofTitles(Slice titles) { final List<TitleElement> titleElements = titles.stream() .map(TitleElement::of) - .collect(Collectors.toList()); + .toList(); return TitlesResponse.builder() .titles(titleElements) diff --git a/src/main/java/com/memetitle/meme/dto/response/TopTitlesResponse.java b/src/main/java/com/memetitle/meme/dto/response/TopTitlesResponse.java index c5071d2..4c2cc26 100644 --- a/src/main/java/com/memetitle/meme/dto/response/TopTitlesResponse.java +++ b/src/main/java/com/memetitle/meme/dto/response/TopTitlesResponse.java @@ -17,7 +17,7 @@ public class TopTitlesResponse { public static TopTitlesResponse ofTopTitles(List<TopTitle> topTitles) { final List<TitleElement> titleElements = topTitles.stream() .map(TitleElement::of) - .collect(Collectors.toList()); + .toList(); return TopTitlesResponse.builder() .titles(titleElements) diff --git a/src/main/java/com/memetitle/meme/presentation/TitleController.java b/src/main/java/com/memetitle/meme/presentation/TitleController.java index 656ae7d..4001d0b 100644 --- a/src/main/java/com/memetitle/meme/presentation/TitleController.java +++ b/src/main/java/com/memetitle/meme/presentation/TitleController.java @@ -12,7 +12,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.net.URI; import static org.springframework.data.domain.Sort.Direction.DESC; diff --git a/src/test/java/com/memetitle/auth/presentation/LoginControllerTest.java b/src/test/java/com/memetitle/auth/presentation/LoginControllerTest.java index 92754fc..31cb952 100644 --- a/src/test/java/com/memetitle/auth/presentation/LoginControllerTest.java +++ b/src/test/java/com/memetitle/auth/presentation/LoginControllerTest.java @@ -8,7 +8,6 @@ import com.memetitle.global.exception.ErrorCode; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -17,7 +16,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import javax.servlet.http.Cookie; +import jakarta.servlet.http.Cookie; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given;