Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,30 +60,17 @@ public Response<AICourseItineraryResponse> getStoredAICourse(
@PathVariable Long aiCourseId) {
Optional<AICourse> 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<String, List<Place>> 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<DailyItineraryResponse> 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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
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;


public class AICourseConverter {

public static List<DailyItineraryResponse> toDailyItineraryResponseList(Map<String, List<Place>> courseMap) {
public static List<DailyItineraryResponse> toDailyItineraryResponseList(Map<String, List<Place>> courseMap,
String startDate) {
// 기존의 roomName과 totalRoomMember 정보를 제거하고 day, places만 포함하도록 함
return courseMap.entrySet().stream()
.map(entry -> DailyItineraryResponse.builder()
Expand All @@ -31,4 +34,25 @@ public static AICourseResponse toAICourseResponse(AICourse aiCourse) {
.roomId(aiCourse.getTripPlan().getRoom().getId())
.build();
}

public static AICourseItineraryResponse toAICourseItineraryResponse(AICourse aiCourse,
Map<String, List<Place>> courseMap) {
// TripPlan의 startDate를 "yyyy-MM-dd" 형식으로 변환
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String startDate = aiCourse.getTripPlan().getStartDate().format(formatter);

// dailyItineraries 생성 (각 DailyItineraryResponse에 startDate 포함)
List<DailyItineraryResponse> 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();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.umc.yeogi_gal_lae.api.aiCourse.dto;


import java.util.List;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -10,5 +9,6 @@
public class AICourseItineraryResponse {
private String roomName;
private int totalRoomMember;
private String startDate;
private List<DailyItineraryResponse> dailyItineraries;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
public interface FriendshipRepository extends JpaRepository<Friendship, Long> {
Optional<Friendship> findById(Long id);
List<Friendship> findByInviterIdOrInviteeId(Long inviterId, Long inviteeId);
Optional<Friendship> 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<Friendship> findByInviterIdAndInviteeIdBothWays(@Param("userId") Long userId, @Param("friendId") Long friendId);

// 친구 관계가 존재하는지 확인 (양방향)
boolean existsByInviterAndInvitee(User inviter, User invitee);

void deleteByInviterOrInvitee(User inviter, User invitee);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,27 @@ 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("자신은 친구 추가할 수 없습니다.");
}

// 이미 존재하는 친구 관계 확인 (양방향)
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);
Expand Down Expand Up @@ -131,13 +143,15 @@ private List<FriendListResponse> generateMockFriendList() {

@Transactional
public void deleteFriendship(Long userId, Long friendId) {
// 친구 관계 조회 (양방향 확인)
Friendship friendship = friendshipRepository.findByInviterIdAndInviteeId(userId, friendId)
.or(() -> friendshipRepository.findByInviterIdAndInviteeId(friendId, userId)) // 반대 방향도 확인
.orElseThrow(() -> new IllegalArgumentException("친구 관계가 아닙니다. "));
// 양방향 친구 관계를 모두 조회
List<Friendship> friendships = friendshipRepository.findByInviterIdAndInviteeIdBothWays(userId, friendId);

if (friendships.isEmpty()) {
throw new IllegalArgumentException("친구 관계가 아닙니다.");
}

// 친구 관계 삭제
friendshipRepository.delete(friendship);
// 모든 친구 관계 삭제
friendshipRepository.deleteAll(friendships);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/umc/yeogi_gal_lae/global/jwt/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down