Skip to content

Commit

Permalink
Merge pull request #46 from tukcomCD2024/backend/feature/25-amadeus-api
Browse files Browse the repository at this point in the history
[Backend] feat : Amadeus API - 항공편 조회
  • Loading branch information
Dayon-Hong authored Mar 10, 2024
2 parents 1899acd + c1216f9 commit d2e2bb0
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class Country {

private String imageUrl;

private String airportCode;
private String airportCode; // 공항 코드

private double latitude ; // 위도

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public interface CountryRepository extends JpaRepository<Country, Long> {
Country findIdByCity(String city); // 여행나라 이름으로 id 찾기

Country findCountryById(Long countryId); //
Country findAirportCodeByCity(String countryName); // 나라 이름으로 공항 코드 찾기

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.isp.backend.domain.flight.controller;

import com.amadeus.exceptions.ResponseException;
import com.isp.backend.domain.flight.service.FlightOfferService;
import com.isp.backend.domain.flight.dto.FlightSearchRequestDTO;
import com.isp.backend.global.security.CustomUserDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/bookings/flights")
@RequiredArgsConstructor
public class FlightOfferController {

@Autowired
private FlightOfferService flightOfferService;

/**
* 항공권 검색 API
*/
@GetMapping("/search")
public ResponseEntity<String> getFlightOffers(@AuthenticationPrincipal CustomUserDetails customUserDetails,
@RequestBody FlightSearchRequestDTO request) {
String memberUid = customUserDetails.getUsername();
try {
String flightOffersJson = flightOfferService.getFlightOffers(request);
return ResponseEntity.ok(flightOffersJson);
} catch (ResponseException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error occurred while fetching flight offers");
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.isp.backend.domain.flight.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class FlightSearchRequestDTO {

private String originCity;

private String destinationCity;

private String departureDate;

private String returnDate;

private int adults;

private int children;

private int max ;

private boolean nonStop ; // 직항 항공편 유무

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.isp.backend.domain.flight.mapper;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Service
public class FlightOfferProcessor {

/** 추출한 정보 json으로 반환 **/
public String processFlightOffers(String flightOffersJson) {
// JSON 형식의 항공편 정보 -> JsonObject로 변환
JsonArray flightOffersArray = new Gson().fromJson(flightOffersJson, JsonArray.class);

// 필요한 정보만 객체로 변환
JsonArray filteredFlightOffers = new JsonArray();
for (JsonElement flightOfferElement : flightOffersArray) {
JsonObject flightOffer = flightOfferElement.getAsJsonObject();
JsonObject filteredFlightOffer = filterFlightOffer(flightOffer);
filteredFlightOffers.add(filteredFlightOffer);
}

// 새로운 JsonArray를 JSON 형식으로 변환하여 반환
return filteredFlightOffers.toString();
}


/** 항공 정보에서 필요한 정보 추출 **/
public JsonObject filterFlightOffer(JsonObject flightOffer) {
JsonObject filteredFlightOffer = new JsonObject();
filteredFlightOffer.addProperty("id", flightOffer.get("id").getAsString());
filteredFlightOffer.addProperty("numberOfBookableSeats", flightOffer.get("numberOfBookableSeats").getAsInt());

// 여행 일정 - itineraries
JsonArray itineraries = flightOffer.getAsJsonArray("itineraries");
JsonArray filteredItineraries = new JsonArray();
for (JsonElement itineraryElement : itineraries) {
JsonObject itinerary = itineraryElement.getAsJsonObject();
JsonObject filteredItinerary = new JsonObject();

// duration 추출
formatDuration(itinerary, filteredItinerary);
// filteredItinerary.addProperty("duration", itinerary.get("duration").getAsString()); // 비행 시간


// segments 추출
JsonArray segments = itinerary.getAsJsonArray("segments");
JsonArray filteredSegments = new JsonArray();
for (JsonElement segmentElement : segments) {
JsonObject segment = segmentElement.getAsJsonObject();
JsonObject filteredSegment = new JsonObject();

// 출발편
filteredSegment.addProperty("departureIataCode", segment.getAsJsonObject("departure").get("iataCode").getAsString());
formatTime(segment.getAsJsonObject("departure").get("at").getAsString(), filteredSegment, "departureTime");
// 도착편
filteredSegment.addProperty("arrivalIataCode", segment.getAsJsonObject("arrival").get("iataCode").getAsString());
formatTime(segment.getAsJsonObject("arrival").get("at").getAsString(), filteredSegment, "arrivalTime");

filteredSegments.add(filteredSegment);
}
filteredItinerary.add("segments", filteredSegments);
filteredItineraries.add(filteredItinerary);
}
filteredFlightOffer.add("itineraries", filteredItineraries);

// 가격 정보 추출
JsonObject price = flightOffer.getAsJsonObject("price");
// filteredFlightOffer.addProperty("currency", price.get("currency").getAsString());
filteredFlightOffer.addProperty("totalPrice", price.get("total").getAsString());

return filteredFlightOffer;
}


/** 시간 변환 **/
private void formatTime(String timeStr, JsonObject filteredSegment, String propertyName) {
LocalDateTime time = LocalDateTime.parse(timeStr, DateTimeFormatter.ISO_DATE_TIME);
String formattedTime = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
filteredSegment.addProperty(propertyName, formattedTime);
}

/** duration 비행 시간 변환 **/
private void formatDuration(JsonObject itinerary, JsonObject filteredItinerary) {
String durationString = itinerary.get("duration").getAsString();
Duration duration = Duration.parse(durationString);
long hours = duration.toHours();
long minutes = duration.minusHours(hours).toMinutes();
String formattedDuration = String.format("%d:%02d", hours, minutes);
filteredItinerary.addProperty("duration", formattedDuration);
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.isp.backend.domain.flight.service;

import com.amadeus.Amadeus;
import com.amadeus.Params;
import com.amadeus.exceptions.ResponseException;
import com.amadeus.resources.FlightOfferSearch;
import com.google.gson.Gson;
import com.isp.backend.domain.country.entity.Country;
import com.isp.backend.domain.country.repository.CountryRepository;
import com.isp.backend.domain.flight.dto.FlightSearchRequestDTO;
import com.isp.backend.domain.flight.mapper.FlightOfferProcessor;
import com.isp.backend.global.exception.schedule.CountryNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

private final Amadeus amadeus;
private final CountryRepository countryRepository;
private final FlightOfferProcessor flightOfferProcessor ;


/**
* 항공편 조회
*/
public String getFlightOffers(FlightSearchRequestDTO request) throws ResponseException {

String originLocationCode = findAirportCode(request.getOriginCity());
String destinationLocationCode = findAirportCode(request.getDestinationCity());

FlightOfferSearch[] flightOffers = amadeus.shopping.flightOffersSearch.get(
Params.with("originLocationCode", originLocationCode)
.and("destinationLocationCode", destinationLocationCode)
.and("departureDate", request.getDepartureDate())
.and("returnDate", request.getReturnDate())
.and("adults", request.getAdults())
.and("children", request.getChildren())
.and("max", request.getMax())
.and("nonStop", request.isNonStop())
.and("currencyCode","KRW") // 원화 설정 - 추후 유저에게 입력받을 수 있게 변경
);

// FlightOfferSearch 배열을 JSON 문자열로 변환
Gson gson = new Gson();
String flightOffersJson = gson.toJson(flightOffers);
return flightOfferProcessor.processFlightOffers(flightOffersJson); // 원하는 정보만 조회
}


/** 공항 코드 찾기 **/
public String findAirportCode(String countryName) {
Country findCountry = countryRepository.findAirportCodeByCity(countryName);
if (findCountry == null) {
throw new CountryNotFoundException();
}
return findCountry.getAirportCode();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import com.isp.backend.domain.member.entity.Member;
import com.isp.backend.domain.schedule.entity.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
Schedule findByIdAndActivatedIsTrue(Long scheduleId);

List<Schedule> findByMemberAndActivatedIsTrue(Member findmember);
// List<Schedule> findByMemberAndActivatedIsTrue(Member findmember);
@Query("SELECT s FROM Schedule s WHERE s.member = :member AND s.activated = true ORDER BY s.updatedAt DESC")
List<Schedule> findSchedulesByMember(@Param("member") Member member);


}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public void saveSchedule(String uid, ScheduleSaveRequestDTO scheduleSaveRequestD
public List<ScheduleListResponseDTO> getScheduleList(String uid) {
Member findMember = validateUserCheck(uid);
// 내가 쓴 일정 불러오기
List<Schedule> scheduleList = scheduleRepository.findByMemberAndActivatedIsTrue(findMember);
List<Schedule> scheduleList = scheduleRepository.findSchedulesByMember(findMember);


return scheduleList.stream()
.map(scheduleMapper::toScheduleListResponseDTO)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.isp.backend.global.security;

import com.amadeus.Amadeus;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AmadeusConfig {

@Value("${api-key.amadeus.accessKey}")
private String apiKey;

@Value("${api-key.amadeus.secretKey}")
private String apiSecret;

@Bean
public Amadeus getAmadeus(){
return Amadeus
.builder(apiKey, apiSecret)
.build();
}

}
5 changes: 4 additions & 1 deletion backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ cloud:
auto: false

api-key:
chat-gpt: ${GPT_API_KEY}
chat-gpt: ${GPT_API_KEY}
amadeus:
accessKey: ${AMADEUS_ACCESS_KEY}
secretKey: ${AMADEUS_SECRET_KEY}

0 comments on commit d2e2bb0

Please sign in to comment.