From 13311a7fb300155043672107398ddb9e6b653abb Mon Sep 17 00:00:00 2001 From: margie1a <4987kk@naver.com> Date: Mon, 17 Feb 2025 19:54:50 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[refactor]:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/yeogi_gal_lae/global/error/ErrorStatus.java | 3 ++- src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtUtil.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/global/error/ErrorStatus.java b/src/main/java/com/umc/yeogi_gal_lae/global/error/ErrorStatus.java index 19f0a6e..7a312ca 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/global/error/ErrorStatus.java +++ b/src/main/java/com/umc/yeogi_gal_lae/global/error/ErrorStatus.java @@ -16,7 +16,8 @@ public enum ErrorStatus { // JWT 관련 에러 JWT_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "JWT_500", "JWT 토큰 생성 중 오류가 발생했습니다."), - JWT_INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "JWT_401", "유효하지 않은 JWT 토큰입니다."); + JWT_INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "JWT_401", "유효하지 않은 JWT 토큰입니다."), + JWT_EXPIRED_TOKEN(HttpStatus.BAD_REQUEST, "JWT_402", "만료된 JWT 토큰입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtUtil.java b/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtUtil.java index f621c78..09d5edf 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtUtil.java +++ b/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtUtil.java @@ -3,6 +3,7 @@ import com.umc.yeogi_gal_lae.global.error.AuthHandler; import com.umc.yeogi_gal_lae.global.error.ErrorStatus; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -63,6 +64,8 @@ public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token); return true; + } catch (ExpiredJwtException e) { + throw new AuthHandler(ErrorStatus.JWT_EXPIRED_TOKEN); } catch (JwtException e) { throw new AuthHandler(ErrorStatus.JWT_INVALID_TOKEN); } From bc0c14d6f08cc7069943bffb053ec4001dcb82f8 Mon Sep 17 00:00:00 2001 From: margie1a <4987kk@naver.com> Date: Mon, 17 Feb 2025 19:55:05 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[refactor]:=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/jwt/JwtAuthenticationFilter.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtAuthenticationFilter.java b/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtAuthenticationFilter.java index 6642bcd..5a7b844 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtAuthenticationFilter.java @@ -26,19 +26,28 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // 스웨거 및 로그인 관련 URL이면 필터 동작 X String requestURI = request.getRequestURI(); + + // 인증이 필요 없는 요청이면 필터를 통과시킴 if (isExcluded(requestURI)) { filterChain.doFilter(request, response); return; } + // JWT 토큰 확인 String token = resolveToken(request); if (token != null && jwtUtil.validateToken(token)) { String email = jwtUtil.extractEmail(token); + + // 현재 로그인한 사용자 정보 SecurityContext에 저장 JwtAuthenticationToken authentication = new JwtAuthenticationToken(email); SecurityContextHolder.getContext().setAuthentication(authentication); + + // Authorization 헤더가 없으면 자동으로 추가 + if (request.getHeader("Authorization") == null) { + request.setAttribute("Authorization", "Bearer " + token); + } } filterChain.doFilter(request, response); From 0fadd1d9cedff0ba0a16b3eaa698e6ff1264e46d Mon Sep 17 00:00:00 2001 From: GithubKangMin <158579562+GithubKangMin@users.noreply.github.com> Date: Tue, 18 Feb 2025 01:30:37 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[refactor]=20=EC=B9=9C=EA=B5=AC=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20-=20=EB=B3=B8=EC=9D=B8=EC=9D=B4=20=EB=B3=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80=20=EB=AA=BB=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/friendship/service/FriendshipService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java b/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java index 1b54a16..a036529 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java +++ b/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java @@ -55,14 +55,17 @@ public void acceptInvite(String token, String inviteeEmail) { User invitee = userRepository.findByEmail(inviteeEmail) .orElseThrow(() -> new IllegalArgumentException("Invitee not found")); + // 자신이 자신을 초대하는 경우 방지 + if (invite.getInviter().getId().equals(invitee.getId())) { + throw new IllegalArgumentException("자신은 친구 추가할 수 없습니다."); + } + Friendship friendship = Friendship.builder() .inviter(invite.getInviter()) // User 객체 직접 설정 .invitee(invitee) // 초대받은 User 객체 직접 설정 .status(FriendshipStatus.ACCEPT) .build(); - - friendshipRepository.save(friendship); // 새로운 친구 관계 저장 // 초대 정보 삭제 From 4719e83633bb90c05c526bdd9a67c1edd27b3923 Mon Sep 17 00:00:00 2001 From: GithubKangMin <158579562+GithubKangMin@users.noreply.github.com> Date: Tue, 18 Feb 2025 02:15:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[refactor]=20=EC=96=91=EB=B0=A9=ED=96=A5=20?= =?UTF-8?q?=EC=B9=9C=EA=B5=AC=20=EB=90=98=EC=96=B4=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=ED=95=9C=EB=B2=88=EC=97=90=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B9=9C=EA=B5=AC=20=EB=B6=88=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FriendshipRepository.java | 8 +++++- .../friendship/service/FriendshipService.java | 25 +++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/api/friendship/repository/FriendshipRepository.java b/src/main/java/com/umc/yeogi_gal_lae/api/friendship/repository/FriendshipRepository.java index 9934792..e1174f9 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/api/friendship/repository/FriendshipRepository.java +++ b/src/main/java/com/umc/yeogi_gal_lae/api/friendship/repository/FriendshipRepository.java @@ -11,7 +11,13 @@ public interface FriendshipRepository extends JpaRepository { Optional findById(Long id); List findByInviterIdOrInviteeId(Long inviterId, Long inviteeId); - Optional findByInviterIdAndInviteeId(Long inviterId, Long inviteeId); + + // inviterId와 inviteeId가 일치하는 Friendship 객체 조회 + @Query("SELECT f FROM Friendship f WHERE (f.inviter.id = :userId AND f.invitee.id = :friendId) OR (f.inviter.id = :friendId AND f.invitee.id = :userId)") + List findByInviterIdAndInviteeIdBothWays(@Param("userId") Long userId, @Param("friendId") Long friendId); + + // 친구 관계가 존재하는지 확인 (양방향) + boolean existsByInviterAndInvitee(User inviter, User invitee); void deleteByInviterOrInvitee(User inviter, User invitee); diff --git a/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java b/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java index a036529..f9b9e7f 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java +++ b/src/main/java/com/umc/yeogi_gal_lae/api/friendship/service/FriendshipService.java @@ -60,13 +60,22 @@ public void acceptInvite(String token, String inviteeEmail) { throw new IllegalArgumentException("자신은 친구 추가할 수 없습니다."); } + // 이미 존재하는 친구 관계 확인 (양방향) + boolean isAlreadyFriend = friendshipRepository.existsByInviterAndInvitee(invite.getInviter(), invitee) + || friendshipRepository.existsByInviterAndInvitee(invitee, invite.getInviter()); + + if (isAlreadyFriend) { + throw new IllegalArgumentException("이미 친구 관계가 존재합니다."); + } + + // 새로운 친구 관계 저장 Friendship friendship = Friendship.builder() .inviter(invite.getInviter()) // User 객체 직접 설정 .invitee(invitee) // 초대받은 User 객체 직접 설정 .status(FriendshipStatus.ACCEPT) .build(); - friendshipRepository.save(friendship); // 새로운 친구 관계 저장 + friendshipRepository.save(friendship); // 초대 정보 삭제 friendshipInviteRepository.delete(invite); @@ -134,13 +143,15 @@ private List generateMockFriendList() { @Transactional public void deleteFriendship(Long userId, Long friendId) { - // 친구 관계 조회 (양방향 확인) - Friendship friendship = friendshipRepository.findByInviterIdAndInviteeId(userId, friendId) - .or(() -> friendshipRepository.findByInviterIdAndInviteeId(friendId, userId)) // 반대 방향도 확인 - .orElseThrow(() -> new IllegalArgumentException("친구 관계가 아닙니다. ")); + // 양방향 친구 관계를 모두 조회 + List friendships = friendshipRepository.findByInviterIdAndInviteeIdBothWays(userId, friendId); + + if (friendships.isEmpty()) { + throw new IllegalArgumentException("친구 관계가 아닙니다."); + } - // 친구 관계 삭제 - friendshipRepository.delete(friendship); + // 모든 친구 관계 삭제 + friendshipRepository.deleteAll(friendships); } } \ No newline at end of file From 09ad67610e04c5dcb994fea427ef7e29fadae1d7 Mon Sep 17 00:00:00 2001 From: Gwanghyeon-k Date: Tue, 18 Feb 2025 14:10:59 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[feat]=20startDate=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/aiCourse/dto/AICourseItineraryResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/dto/AICourseItineraryResponse.java b/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/dto/AICourseItineraryResponse.java index 0f9381f..758d7e0 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/dto/AICourseItineraryResponse.java +++ b/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/dto/AICourseItineraryResponse.java @@ -1,6 +1,5 @@ package com.umc.yeogi_gal_lae.api.aiCourse.dto; - import java.util.List; import lombok.Builder; import lombok.Getter; @@ -10,5 +9,6 @@ public class AICourseItineraryResponse { private String roomName; private int totalRoomMember; + private String startDate; private List dailyItineraries; } From d72c39dcfd727e47529f9c2da70b51367bcb678f Mon Sep 17 00:00:00 2001 From: Gwanghyeon-k Date: Tue, 18 Feb 2025 14:12:13 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[feat]=20dto=20=EB=B3=80=ED=99=98=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiCourse/converter/AICourseConverter.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/converter/AICourseConverter.java b/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/converter/AICourseConverter.java index 7f703b3..9fba675 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/converter/AICourseConverter.java +++ b/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/converter/AICourseConverter.java @@ -1,10 +1,12 @@ package com.umc.yeogi_gal_lae.api.aiCourse.converter; import com.umc.yeogi_gal_lae.api.aiCourse.domain.AICourse; +import com.umc.yeogi_gal_lae.api.aiCourse.dto.AICourseItineraryResponse; import com.umc.yeogi_gal_lae.api.aiCourse.dto.AICourseResponse; import com.umc.yeogi_gal_lae.api.aiCourse.dto.DailyItineraryResponse; import com.umc.yeogi_gal_lae.api.place.converter.PlaceConverter; import com.umc.yeogi_gal_lae.api.place.domain.Place; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -12,7 +14,8 @@ public class AICourseConverter { - public static List toDailyItineraryResponseList(Map> courseMap) { + public static List toDailyItineraryResponseList(Map> courseMap, + String startDate) { // 기존의 roomName과 totalRoomMember 정보를 제거하고 day, places만 포함하도록 함 return courseMap.entrySet().stream() .map(entry -> DailyItineraryResponse.builder() @@ -31,4 +34,25 @@ public static AICourseResponse toAICourseResponse(AICourse aiCourse) { .roomId(aiCourse.getTripPlan().getRoom().getId()) .build(); } + + public static AICourseItineraryResponse toAICourseItineraryResponse(AICourse aiCourse, + Map> courseMap) { + // TripPlan의 startDate를 "yyyy-MM-dd" 형식으로 변환 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String startDate = aiCourse.getTripPlan().getStartDate().format(formatter); + + // dailyItineraries 생성 (각 DailyItineraryResponse에 startDate 포함) + List dailyItineraries = toDailyItineraryResponseList(courseMap, startDate); + + String roomName = aiCourse.getTripPlan().getRoom().getName(); + int totalRoomMember = (aiCourse.getTripPlan().getRoom().getRoomMembers() != null) + ? aiCourse.getTripPlan().getRoom().getRoomMembers().size() : 0; + + return AICourseItineraryResponse.builder() + .roomName(roomName) + .totalRoomMember(totalRoomMember) + .startDate(startDate) + .dailyItineraries(dailyItineraries) + .build(); + } } From 0e90c7d45a705ec6887023b691073ba870219828 Mon Sep 17 00:00:00 2001 From: Gwanghyeon-k Date: Tue, 18 Feb 2025 14:12:34 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[feat]=20aiCourse=20=EC=A1=B0=ED=9A=8C=20ap?= =?UTF-8?q?i=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AICourseController.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/controller/AICourseController.java b/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/controller/AICourseController.java index 4554ad8..1f693f9 100644 --- a/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/controller/AICourseController.java +++ b/src/main/java/com/umc/yeogi_gal_lae/api/aiCourse/controller/AICourseController.java @@ -5,7 +5,6 @@ import com.umc.yeogi_gal_lae.api.aiCourse.dto.AICourseIdResponse; import com.umc.yeogi_gal_lae.api.aiCourse.dto.AICourseItineraryResponse; import com.umc.yeogi_gal_lae.api.aiCourse.dto.AICourseResponse; -import com.umc.yeogi_gal_lae.api.aiCourse.dto.DailyItineraryResponse; import com.umc.yeogi_gal_lae.api.aiCourse.repository.AICourseRepository; import com.umc.yeogi_gal_lae.api.aiCourse.service.AICourseService; import com.umc.yeogi_gal_lae.api.place.domain.Place; @@ -61,30 +60,17 @@ public Response getStoredAICourse( @PathVariable Long aiCourseId) { Optional aiCourseOpt = aiCourseRepository.findById(aiCourseId); if (aiCourseOpt.isEmpty()) { - return Response.of(ErrorCode.NOT_FOUND, null); + return Response.of(ErrorCode.NOT_FOUND); } AICourse aiCourse = aiCourseOpt.get(); - // TripPlan 검증: aiCourse에 연결된 TripPlan의 id와 입력받은 tripPlanId가 동일해야 함 if (!aiCourse.getTripPlan().getId().equals(tripPlanId)) { - return Response.of(ErrorCode.NOT_FOUND, null); + return Response.of(ErrorCode.NOT_FOUND); } Map> courseMap = aiCourseService.getStoredAICourseById(aiCourseId); if (courseMap.isEmpty()) { - return Response.of(ErrorCode.NOT_FOUND, null); + return Response.of(ErrorCode.NOT_FOUND); } - // Room 정보 - String roomName = aiCourse.getTripPlan().getRoom().getName(); - int totalRoomMember = (aiCourse.getTripPlan().getRoom().getRoomMembers() != null) - ? aiCourse.getTripPlan().getRoom().getRoomMembers().size() : 0; - // dailyItineraries 변환 - List dailyItineraries = AICourseConverter.toDailyItineraryResponseList(courseMap); - - // 전체 응답 DTO 생성 - AICourseItineraryResponse responseDTO = AICourseItineraryResponse.builder() - .roomName(roomName) - .totalRoomMember(totalRoomMember) - .dailyItineraries(dailyItineraries) - .build(); + AICourseItineraryResponse responseDTO = AICourseConverter.toAICourseItineraryResponse(aiCourse, courseMap); return Response.of(SuccessCode.OK, responseDTO); }