diff --git a/build.gradle b/build.gradle index cd91719..af71f94 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,13 @@ dependencies { // spring security implementation 'org.springframework.boot:spring-boot-starter-security' + + // apache commons + implementation 'org.apache.commons:commons-csv:1.8' + + // feign + implementation "org.springframework.cloud:spring-cloud-starter-openfeign:4.1.3" + implementation 'io.github.openfeign:feign-jackson:12.1' } diff --git a/src/main/java/hyu/erica/capstone/CapstoneApplication.java b/src/main/java/hyu/erica/capstone/CapstoneApplication.java index 11508a5..0043d10 100644 --- a/src/main/java/hyu/erica/capstone/CapstoneApplication.java +++ b/src/main/java/hyu/erica/capstone/CapstoneApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication @EnableJpaAuditing +@EnableFeignClients public class CapstoneApplication { public static void main(String[] args) { diff --git a/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java b/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java index 0abda79..1aa00a7 100644 --- a/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java +++ b/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java @@ -29,6 +29,9 @@ public enum ErrorStatus implements BaseErrorCode { _MEMBER_EMAIL_EXIST(HttpStatus.BAD_REQUEST, "COMMON4014", "이미 가입 된 이메일입니다. 다른 로그인 방식을 이용해주세요."), _RSA_ERROR(HttpStatus.BAD_REQUEST, "COMMON4015", "RSA 에러가 발생했습니다."), + // 파일 에러 + _FILE_INPUT_ERROR(HttpStatus.BAD_REQUEST, "FILE4000", "파일 입력 중 에러가 발생했습니다."), + //유저 에러 _USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER4040", "사용자를 찾을 수 없습니다."), _INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "USER4001", "비밀번호가 일치하지 않습니다."), @@ -38,6 +41,9 @@ public enum ErrorStatus implements BaseErrorCode { _STYLE_NOT_FOUND(HttpStatus.NOT_FOUND, "STYLE4040", "입력하신 여행 스타일을 찾을 수 없습니다."), _UNAUTHORIZED_USER(HttpStatus.FORBIDDEN, "STYLE4001", "해당 사용자는 권한이 없습니다."), + // 식당 관련 + _RESTAURANT_NOT_FOUND(HttpStatus.NOT_FOUND, "RESTAURANT4040", "입력하신 식당을 찾을 수 없습니다."), + ; diff --git a/src/main/java/hyu/erica/capstone/client/PlanClient.java b/src/main/java/hyu/erica/capstone/client/PlanClient.java new file mode 100644 index 0000000..8d26c93 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/client/PlanClient.java @@ -0,0 +1,17 @@ +package hyu.erica.capstone.client; + +import hyu.erica.capstone.web.dto.client.AttractionRequestDTO; +import hyu.erica.capstone.web.dto.client.RestaurantRequestDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "planApiClient", url = "${plan.api.url}") +public interface PlanClient { + + @GetMapping("/restaurants/search") + RestaurantRequestDTO getRestaurants(@RequestParam String query); + + @GetMapping("/attractions/search") + AttractionRequestDTO getAttractions(@RequestParam String query); +} diff --git a/src/main/java/hyu/erica/capstone/domain/Attraction.java b/src/main/java/hyu/erica/capstone/domain/Attraction.java index 5a9b567..4675762 100644 --- a/src/main/java/hyu/erica/capstone/domain/Attraction.java +++ b/src/main/java/hyu/erica/capstone/domain/Attraction.java @@ -1,5 +1,91 @@ package hyu.erica.capstone.domain; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@Builder +@DynamicUpdate +@DynamicInsert +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor public class Attraction { - // 추후 작성 예정 + + @Id + @Column(name = "UC_SEQ") + private Long ucSeq; + + @Column(name = "MAIN_TITLE") + private String mainTitle; + + @Column(name = "GUGUN_NM") + private String gugunNm; + + @Column(name = "LAT") + private Double lat; + + @Column(name = "LNG") + private Double lng; + + @Column(name = "PLACE") + private String place; + + @Column(name = "TITLE") + private String title; + + @Column(name = "SUBTITLE") + private String subtitle; + + @Column(name = "MAIN_PLACE") + private String mainPlace; + + @Column(name = "ADDR1") + private String addr1; + + @Column(name = "ADDR2") + private String addr2; + + @Column(name = "CNTCT_TEL") + private String cntctTel; + + @Column(name = "HOMEPAGE_URL") + private String homepageUrl; + + @Column(name = "TRFC_INFO", columnDefinition = "TEXT") + private String trfcInfo; + + @Column(name = "USAGE_DAY") + private String usageDay; + + @Column(name = "HLDY_INFO") + private String hldyInfo; + + @Column(name = "USAGE_DAY_WEEK_AND_TIME", columnDefinition = "TEXT") + private String usageDayWeekAndTime; + + @Column(name = "USAGE_AMOUNT") + private String usageAmount; + + @Column(name = "MIDDLE_SIZE_RM1") + private String middleSizeRm1; + + @Column(name = "MAIN_IMG_NORMAL") + private String mainImgNormal; + + @Column(name = "MAIN_IMG_THUMB") + private String mainImgThumb; + + @Lob + @Column(name = "ITEMCNTNTS") + private String itemcntnts; // ITEMCNTNTS (상세 설명) } diff --git a/src/main/java/hyu/erica/capstone/domain/Restaurant.java b/src/main/java/hyu/erica/capstone/domain/Restaurant.java index a5cfd5b..b59ab6e 100644 --- a/src/main/java/hyu/erica/capstone/domain/Restaurant.java +++ b/src/main/java/hyu/erica/capstone/domain/Restaurant.java @@ -1,5 +1,154 @@ package hyu.erica.capstone.domain; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + + +@Entity +@Table(name = "restaurant") // 테이블명 설정 +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class Restaurant { - // 추후 작성 예정 + + @Id + @Column(name = "RSTR_ID") + private Long rstrId; // 식당 고유 ID + + @Column(name = "RSTR_NM") + private String rstrNm; // 식당 이름 + + @Column(name = "RSTR_RDNMADR") + private String rstrRdnmAdr; // 도로명 주소 + + @Column(name = "RSTR_LNNO_ADRES") + private String rstrLnnoAdres; // 지번 주소 + + @Column(name = "RSTR_LA") + private Double rstrLa; // 위도 + + @Column(name = "RSTR_LO") + private Double rstrLo; // 경도 + + @Column(name = "RSTR_TELNO") + private String rstrTelNo; // 전화번호 + + @Column(name = "BSNS_STATM_BZCND_NM") + private String bsnsStatmBzcndNm; // 영업 상태 + + @Column(name = "BSNS_LCNC_NM") + private String bsnsLcncNm; // 업종명 + + @Column(name = "RSTR_INTRCN_CONT", columnDefinition = "TEXT") + private String rstrIntrcnCont; // 식당 소개 + + @Column(name = "AREA_NM") + private String areaNm; // 지역명 + + @Column(name = "PRDL_SEAT_CNT") + private Integer prdlSeatCnt; // 예상 좌석 수 + + @Column(name = "SEAT_CNT") + private Integer seatCnt; // 실제 좌석 수 + + @Column(name = "PRKG_POS_YN") + private String prkgPosYn; // 주차 가능 여부 (Y/N) + + @Column(name = "WIFI_OFR_YN") + private String wifiOfrYn; // 와이파이 제공 여부 (Y/N) + + @Column(name = "DCRN_YN") + private String dcrnYn; // 할인 여부 (Y/N) + + @Column(name = "PET_ENTRN_POSBL_YN") + private String petEntrnPosblYn; // 반려동물 동반 가능 여부 (Y/N) + + @Column(name = "FGGG_MENU_OFR_YN") + private String fgggMenuOfrYn; // 외국인 메뉴 제공 여부 (Y/N) + + @Column(name = "TLROM_INFO_CN") + private String tlromInfoCn; // 화장실 정보 + + @Column(name = "RESTDY_INFO_CN") + private String restdyInfoCn; // 휴무일 정보 + + @Column(name = "BSNS_TM_CN") + private String bsnsTmCn; // 영업시간 + + @Column(name = "HMDLV_SALE_YN") + private String hmdlvSaleYn; // 홈딜리버리 판매 여부 (Y/N) + + @Column(name = "DSBR_CVNTL_YN") + private String dsbrCvntlYn; // 장애인 편의시설 제공 여부 (Y/N) + + @Column(name = "DELV_SRVIC_YN") + private String delvSrvicYn; // 배달 서비스 제공 여부 (Y/N) + + @Column(name = "RSRV_MTHD_NM") + private String rsrvMthdNm; // 예약 방법 + + @Column(name = "ONLINE_RSRV_INFO_CN") + private String onlineRsrvInfoCn; // 온라인 예약 정보 + + @Column(name = "HMPG_URL") + private String hmpgUrl; // 홈페이지 URL + + @Column(name = "CRCMF_LDMARK_NM") + private String crcmfLdmarkNm; // 근처 랜드마크 이름 + + @Column(name = "CRCMF_LDMARK_LA") + private Double crcmfLdmarkLa; // 근처 랜드마크 위도 + + @Column(name = "CRCMF_LDMARK_LO") + private Double crcmfLdmarkLo; // 근처 랜드마크 경도 + + @Column(name = "CRCMF_LDMARK_DIST") + private Double crcmfLdmarkDist; // 근처 랜드마크 거리 + + @Column(name = "KIOSK_YN") + private String kioskYn; // 키오스크 여부 (Y/N) + + @Column(name = "MB_PMAMT_YN") + private String mbPmamtYn; // 모바일 결제 가능 여부 (Y/N) + + @Column(name = "SMORDER_YN") + private String smorderYn; // 스마트 오더 가능 여부 (Y/N) + + @Column(name = "REPRSNT_MENU_NM") + private String reprsntMenuNm; // 대표 메뉴 + + @Column(name = "AWARD_INFO_DSCRN") + private String awardInfoDscrn; // 수상 정보 + + @Column(name = "RTI_IDEX") + private Double rtiIdex; // RTI 지수 + + @Column(name = "ONLINE_CONV_PRGS_YN") + private String onlineConvPrgsYn; // 온라인 컨버전 진행 여부 (Y/N) + + @Column(name = "ACCPN_STTUS_IDEX") + private Double accpnSttusIdex; // 수용 상태 지수 + + @Column(name = "RATING_IDEX") + private Double ratingIdex; // 평점 지수 + + @Column(name = "TRPDVSR_GRAD") + private Double trpdvsrGrad; // 트립어드바이저 평점 + + @Column(name = "CTRIP_GRAD") + private Double ctripGrad; // 씨트립 평점 + + @Column(name = "NAVER_GRAD") + private Double naverGrad; // 네이버 평점 } diff --git a/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java b/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java index b62807f..4663bbd 100644 --- a/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java +++ b/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java @@ -32,7 +32,9 @@ public class PreferAttraction extends BaseEntity { @Id @GeneratedValue(strategy = IDENTITY) private Long id; - //private Attraction attraction; + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "attraction_id") + private Attraction attraction; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "user_id") diff --git a/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java b/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java index 9afa8c5..4d9da9c 100644 --- a/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java +++ b/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java @@ -32,7 +32,9 @@ public class PreferRestaurant extends BaseEntity { @Id @GeneratedValue(strategy = IDENTITY) private Long id; -// private Restaurant restaurant; + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "restaurant_id") + private Restaurant restaurant; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "user_id") diff --git a/src/main/java/hyu/erica/capstone/repository/AttractionRepository.java b/src/main/java/hyu/erica/capstone/repository/AttractionRepository.java new file mode 100644 index 0000000..73e34a3 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/repository/AttractionRepository.java @@ -0,0 +1,9 @@ +package hyu.erica.capstone.repository; + +import hyu.erica.capstone.domain.Attraction; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AttractionRepository extends JpaRepository { +} diff --git a/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java b/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java new file mode 100644 index 0000000..74a6b88 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java @@ -0,0 +1,9 @@ +package hyu.erica.capstone.repository; + +import hyu.erica.capstone.domain.mapping.PreferAttraction; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PreferAttractionRepository extends JpaRepository { +} diff --git a/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java b/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java new file mode 100644 index 0000000..c0663c7 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java @@ -0,0 +1,9 @@ +package hyu.erica.capstone.repository; + +import hyu.erica.capstone.domain.mapping.PreferRestaurant; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PreferRestaurantRepository extends JpaRepository { +} diff --git a/src/main/java/hyu/erica/capstone/repository/RestaurantRepository.java b/src/main/java/hyu/erica/capstone/repository/RestaurantRepository.java new file mode 100644 index 0000000..ba652c6 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/repository/RestaurantRepository.java @@ -0,0 +1,9 @@ +package hyu.erica.capstone.repository; + +import hyu.erica.capstone.domain.Restaurant; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RestaurantRepository extends JpaRepository { +} diff --git a/src/main/java/hyu/erica/capstone/service/CsvImportService.java b/src/main/java/hyu/erica/capstone/service/CsvImportService.java new file mode 100644 index 0000000..b172280 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/service/CsvImportService.java @@ -0,0 +1,109 @@ +package hyu.erica.capstone.service; + +import static java.lang.Double.parseDouble; + +import hyu.erica.capstone.api.code.status.ErrorStatus; +import hyu.erica.capstone.api.exception.GeneralException; +import hyu.erica.capstone.domain.Attraction; +import hyu.erica.capstone.domain.Restaurant; +import hyu.erica.capstone.repository.AttractionRepository; +import hyu.erica.capstone.repository.RestaurantRepository; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@Transactional +@RequiredArgsConstructor +public class CsvImportService { + + private final AttractionRepository attractionRepository; + private final RestaurantRepository restaurantRepository; + + + public void importAttraction(MultipartFile file) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8)); + CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT.withFirstRecordAsHeader())) { + + List touristSpots = new ArrayList<>(); + + for (CSVRecord record : csvParser) { + Attraction spot = Attraction.builder() + .ucSeq(Long.parseLong(record.get("UC_SEQ"))) + .mainTitle(record.get("MAIN_TITLE")) + .gugunNm(record.get("GUGUN_NM")) + .lat(parseDouble(record.get("LAT"))) + .lng(parseDouble(record.get("LNG"))) + .place(record.get("PLACE")) + .title(record.get("TITLE")) + .subtitle(record.get("SUBTITLE")) + .mainPlace(record.get("MAIN_PLACE")) + .addr1(record.get("ADDR1")) + .addr2(record.get("ADDR2")) + .cntctTel(record.get("CNTCT_TEL")) + .homepageUrl(record.get("HOMEPAGE_URL")) + .trfcInfo(trimString(record.get("TRFC_INFO"), 1000)) + .usageDay(record.get("USAGE_DAY")) + .hldyInfo(record.get("HLDY_INFO")) + .usageDayWeekAndTime(record.get("USAGE_DAY_WEEK_AND_TIME")) + .usageAmount(record.get("USAGE_AMOUNT")) + .middleSizeRm1(record.get("MIDDLE_SIZE_RM1")) + .mainImgNormal(record.get("MAIN_IMG_NORMAL")) + .mainImgThumb(record.get("MAIN_IMG_THUMB")) + .build(); + + touristSpots.add(spot); + } + attractionRepository.saveAll(touristSpots); + } catch (Exception e) { + e.printStackTrace(); + throw new GeneralException(ErrorStatus._FILE_INPUT_ERROR); + } + } + + public void importRestaurant(MultipartFile file) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8)); + CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT.withFirstRecordAsHeader())) { + + List batchList = new ArrayList<>(); + + for (CSVRecord record : csvParser) { + Restaurant restaurant = Restaurant.builder() + .rstrId(Long.parseLong(record.get("RSTR_ID"))) + .rstrNm(record.get("RSTR_NM")) + .rstrRdnmAdr(record.get("RSTR_RDNMADR")) + .rstrLnnoAdres(record.get("RSTR_LNNO_ADRES")) + .rstrLa(parseDouble(record.get("RSTR_LA"))) + .rstrLo(parseDouble(record.get("RSTR_LO"))) + .rstrTelNo(record.get("RSTR_TELNO")) + .bsnsStatmBzcndNm(record.get("BSNS_STATM_BZCND_NM")) + .bsnsLcncNm(record.get("BSNS_LCNC_NM")) + .rstrIntrcnCont(record.get("RSTR_INTRCN_CONT")) + .build(); + batchList.add(restaurant); + } + + restaurantRepository.saveAll(batchList); + + } catch (Exception e) { + throw new GeneralException(ErrorStatus._FILE_INPUT_ERROR); + } + } + + private String trimString(String value, int maxLength) { + if (value == null) return null; + return value.length() > maxLength ? value.substring(0, maxLength) : value; + } + + + +} diff --git a/src/main/java/hyu/erica/capstone/service/style/StyleCommandService.java b/src/main/java/hyu/erica/capstone/service/style/StyleCommandService.java index 831552a..98658c4 100644 --- a/src/main/java/hyu/erica/capstone/service/style/StyleCommandService.java +++ b/src/main/java/hyu/erica/capstone/service/style/StyleCommandService.java @@ -1,6 +1,7 @@ package hyu.erica.capstone.service.style; import hyu.erica.capstone.web.dto.style.request.UserStyleRequestDTO; +import hyu.erica.capstone.web.dto.style.response.UserStyleFinalResponseDTO; import hyu.erica.capstone.web.dto.style.response.UserStyleInitResponseDTO; import hyu.erica.capstone.web.dto.style.response.UserStyleResponseDTO; @@ -10,5 +11,5 @@ public interface StyleCommandService { UserStyleResponseDTO updateStyle(Long userId, Long styleId, UserStyleRequestDTO request); - + UserStyleFinalResponseDTO submitStyle(Long styleId, Long userId); } diff --git a/src/main/java/hyu/erica/capstone/service/style/StyleCommandServiceImpl.java b/src/main/java/hyu/erica/capstone/service/style/StyleCommandServiceImpl.java deleted file mode 100644 index d375afa..0000000 --- a/src/main/java/hyu/erica/capstone/service/style/StyleCommandServiceImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -package hyu.erica.capstone.service.style; - -import hyu.erica.capstone.api.code.status.ErrorStatus; -import hyu.erica.capstone.api.exception.GeneralException; -import hyu.erica.capstone.domain.Style; -import hyu.erica.capstone.domain.User; -import hyu.erica.capstone.domain.enums.City; -import hyu.erica.capstone.repository.StyleRepository; -import hyu.erica.capstone.repository.UserRepository; -import hyu.erica.capstone.web.dto.style.request.UserStyleRequestDTO; -import hyu.erica.capstone.web.dto.style.response.UserStyleInitResponseDTO; -import hyu.erica.capstone.web.dto.style.response.UserStyleResponseDTO; -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional -@RequiredArgsConstructor -public class StyleCommandServiceImpl implements StyleCommandService { - - private final UserRepository userRepository; - private final StyleRepository styleRepository; - - @Override - public UserStyleInitResponseDTO initStyle(Long userId) { - User user = userRepository.findById(userId).orElseThrow( () -> new GeneralException(ErrorStatus._USER_NOT_FOUND)); - - Style style = Style.builder() - .city(City.BUSAN) - .user(user) - .build(); - Style save = styleRepository.save(style); - - return UserStyleInitResponseDTO.of(userId, save.getId()); - } - - @Override - public UserStyleResponseDTO updateStyle(Long userId, Long styleId, UserStyleRequestDTO request) { - Style style = styleRepository.findById(styleId).orElseThrow( () -> new GeneralException(ErrorStatus._STYLE_NOT_FOUND)); - - if (!Objects.equals(style.getUser().getId(), userId)) - throw new GeneralException(ErrorStatus._UNAUTHORIZED_USER); - - style.updateStyle(request.startDate(), request.endDate(), request.preferActivity(), request.requirement()); - - Style save = styleRepository.save(style); - - return UserStyleResponseDTO.of(save); - } -} diff --git a/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java b/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java new file mode 100644 index 0000000..5de0ba6 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java @@ -0,0 +1,111 @@ +package hyu.erica.capstone.service.style.impl; + +import hyu.erica.capstone.api.code.status.ErrorStatus; +import hyu.erica.capstone.api.exception.GeneralException; +import hyu.erica.capstone.client.PlanClient; +import hyu.erica.capstone.domain.Restaurant; +import hyu.erica.capstone.domain.Style; +import hyu.erica.capstone.domain.User; +import hyu.erica.capstone.domain.enums.City; +import hyu.erica.capstone.domain.mapping.PreferRestaurant; +import hyu.erica.capstone.repository.AttractionRepository; +import hyu.erica.capstone.repository.PreferAttractionRepository; +import hyu.erica.capstone.repository.PreferRestaurantRepository; +import hyu.erica.capstone.repository.RestaurantRepository; +import hyu.erica.capstone.repository.StyleRepository; +import hyu.erica.capstone.repository.UserRepository; +import hyu.erica.capstone.service.style.StyleCommandService; +import hyu.erica.capstone.web.dto.client.RestaurantRequestDTO; +import hyu.erica.capstone.web.dto.style.request.UserStyleRequestDTO; +import hyu.erica.capstone.web.dto.style.response.UserStyleFinalResponseDTO; +import hyu.erica.capstone.web.dto.style.response.UserStyleInitResponseDTO; +import hyu.erica.capstone.web.dto.style.response.UserStyleResponseDTO; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class StyleCommandServiceImpl implements StyleCommandService { + + private final UserRepository userRepository; + private final StyleRepository styleRepository; + private final RestaurantRepository restaurantRepository; + private final AttractionRepository attractionRepository; + + private final PreferRestaurantRepository preferRestaurantRepository; + private final PreferAttractionRepository preferAttractionRepository; + + private final PlanClient planClient; + + @Override + public UserStyleInitResponseDTO initStyle(Long userId) { + User user = userRepository.findById(userId).orElseThrow( () -> new GeneralException(ErrorStatus._USER_NOT_FOUND)); + + Style style = Style.builder() + .city(City.BUSAN) + .user(user) + .build(); + Style save = styleRepository.save(style); + + return UserStyleInitResponseDTO.of(userId, save.getId()); + } + + @Override + public UserStyleResponseDTO updateStyle(Long userId, Long styleId, UserStyleRequestDTO request) { + Style style = styleRepository.findById(styleId).orElseThrow( () -> new GeneralException(ErrorStatus._STYLE_NOT_FOUND)); + + if (!Objects.equals(style.getUser().getId(), userId)) + throw new GeneralException(ErrorStatus._UNAUTHORIZED_USER); + + style.updateStyle(request.startDate(), request.endDate(), request.preferActivity(), request.requirement()); + + Style save = styleRepository.save(style); + + return UserStyleResponseDTO.of(save); + } + + @Override + public UserStyleFinalResponseDTO submitStyle(Long styleId, Long userId) { + Style style = styleRepository.findById(styleId).orElseThrow( () -> new GeneralException(ErrorStatus._STYLE_NOT_FOUND)); + User user = userRepository.findById(userId).orElseThrow( () -> new GeneralException(ErrorStatus._USER_NOT_FOUND)); + + StringBuilder sb = new StringBuilder(); + sb.append("여행 지역 : ").append(style.getCity().name()).append("\n") + .append("시작 날짜 : ").append(style.getStartDate()).append("\n") + .append("종료 날짜 : ").append(style.getEndDate()).append("\n") + .append("선호 활동 : ").append(style.getPreferActivity()).append("\n") + .append("추가 요구 사항 : ").append(style.getRequirement()); +// +// AttractionRequestDTO attractions = planClient.getAttractions(sb.toString()); + RestaurantRequestDTO restaurants = planClient.getRestaurants(sb.toString()); + + // TODO 매핑 테이블에 데이터 저장하기 + +// List restaurantIds = restaurants.restaurant_ids(); +// +// for (Long restaurantId : restaurantIds) { +// Restaurant restaurant = restaurantRepository.findById(restaurantId).orElseThrow( +// () -> new GeneralException(ErrorStatus._RESTAURANT_NOT_FOUND)); +// preferRestaurantRepository.save(PreferRestaurant.builder() +// .restaurant(restaurant) +// .user(user) +// .build()); +// } + +// List attractionIds = attractions.attraction_ids(); +// for (Long attractionId : attractionIds) { +// Attraction attraction = attractionRepository.findById(attractionId).orElseThrow( +// () -> new GeneralException(ErrorStatus._ATTRACTION_NOT_FOUND)); +// preferAttractionRepository.save(PreferAttraction.builder() +// .attraction(attraction) +// .user(user) +// .build()); +// } + + return UserStyleFinalResponseDTO.of(restaurants.restaurant_ids(), List.of()); + } +} diff --git a/src/main/java/hyu/erica/capstone/service/style/StyleQueryServiceImpl.java b/src/main/java/hyu/erica/capstone/service/style/impl/StyleQueryServiceImpl.java similarity index 92% rename from src/main/java/hyu/erica/capstone/service/style/StyleQueryServiceImpl.java rename to src/main/java/hyu/erica/capstone/service/style/impl/StyleQueryServiceImpl.java index 42a1ad3..68a9155 100644 --- a/src/main/java/hyu/erica/capstone/service/style/StyleQueryServiceImpl.java +++ b/src/main/java/hyu/erica/capstone/service/style/impl/StyleQueryServiceImpl.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.service.style; +package hyu.erica.capstone.service.style.impl; import hyu.erica.capstone.api.code.status.ErrorStatus; import hyu.erica.capstone.api.exception.GeneralException; @@ -6,6 +6,7 @@ import hyu.erica.capstone.domain.User; import hyu.erica.capstone.repository.StyleRepository; import hyu.erica.capstone.repository.UserRepository; +import hyu.erica.capstone.service.style.StyleQueryService; import hyu.erica.capstone.web.dto.style.response.UserStyleResponseDTO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/hyu/erica/capstone/web/controller/DefaultController.java b/src/main/java/hyu/erica/capstone/web/controller/DefaultController.java index 9e7ed2f..d1e1013 100644 --- a/src/main/java/hyu/erica/capstone/web/controller/DefaultController.java +++ b/src/main/java/hyu/erica/capstone/web/controller/DefaultController.java @@ -1,17 +1,57 @@ package hyu.erica.capstone.web.controller; +import hyu.erica.capstone.api.ApiResponse; +import hyu.erica.capstone.api.code.status.SuccessStatus; +import hyu.erica.capstone.service.CsvImportService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @Tag(name = "기본", description = "기본 API") +@RequestMapping("/api") @RestController +@RequiredArgsConstructor public class DefaultController { + private final CsvImportService importService; + @Operation(summary = "Health Check", description = "서버 상태 확인") @GetMapping("/health-check") public String healthCheck() { return "Hello, World!"; } + + @Operation(summary = "[서버 개발 용] 어트랙션 파일 업로드", description = """ + ### 어트랙션 파일을 업로드합니다. + + ### Request Body + - file: 어트랙션 파일 + + """) + @PostMapping("/upload/attractions") + public ApiResponse uploadAttractionFile(@RequestParam MultipartFile file) { + importService.importAttraction(file); + return ApiResponse.onSuccess(SuccessStatus._OK); + } + + @Operation(summary = "[서버 개발 용] 식당 파일 업로드", description = """ + ### 식당 파일을 업로드합니다. + + ### Request Body + - file: 식당 파일 + + """) + @PostMapping("/upload/restaurants") + public ApiResponse uploadRestaurantFile(@RequestParam MultipartFile file) { + importService.importRestaurant(file); + return ApiResponse.onSuccess(SuccessStatus._OK); + } + + } diff --git a/src/main/java/hyu/erica/capstone/web/controller/StyleController.java b/src/main/java/hyu/erica/capstone/web/controller/StyleController.java index 0d1bcce..5e0c56d 100644 --- a/src/main/java/hyu/erica/capstone/web/controller/StyleController.java +++ b/src/main/java/hyu/erica/capstone/web/controller/StyleController.java @@ -36,7 +36,7 @@ public class StyleController { - name : 회원 이름 - styleId : 스타일 ID (해당 스타일 ID로 선호 여행 스타일을 조회, 수정할 수 있습니다.) """) - @GetMapping("/") + @GetMapping("") public ApiResponse getUserPlanStyle() { return ApiResponse.of(SuccessStatus._OK, styleCommandService.initStyle(SecurityUtils.getCurrentUserId())); } @@ -77,5 +77,18 @@ public ApiResponse getUserPlanStyle(@PathVariable Long styleId) { return ApiResponse.of(SuccessStatus._OK, styleQueryService.getStyle(styleId, SecurityUtils.getCurrentUserId())); } + @Operation(summary = "[선호 여행 스타일 최종 제출]", description = """ + ### 선호 여행 스타일을 최종 제출합니다. \n + styleId를 통해 특정 선호 여행 스타일을 최종 제출할 수 있습니다. \n + + ### Response Body + - restaurantIds: 추천 식당 ID 리스트 + - attractionIds: 추천 관광지 ID 리스트 + """) + @PostMapping("/{styleId}/final") + public ApiResponse saveFinalUserPlanStyle(@PathVariable Long styleId) { + return ApiResponse.onSuccess(SuccessStatus._OK, styleCommandService.submitStyle(styleId, SecurityUtils.getCurrentUserId())); + } + } diff --git a/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java b/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java index cd0f0c5..fdda9d4 100644 --- a/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java +++ b/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java @@ -20,8 +20,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "[개발 전] 여행", description = "여행 관련 API") @CrossOrigin @RestController @RequestMapping("/api/trip-plans/{tripPlansId}") diff --git a/src/main/java/hyu/erica/capstone/web/controller/UserController.java b/src/main/java/hyu/erica/capstone/web/controller/UserController.java index b99b483..327ffbd 100644 --- a/src/main/java/hyu/erica/capstone/web/controller/UserController.java +++ b/src/main/java/hyu/erica/capstone/web/controller/UserController.java @@ -29,7 +29,7 @@ public class UserController { private final UserCommandService userCommandService; // 회원 가입 - @Operation(summary = "[회원 가입]", description = """ + @Operation(summary = "[회원 관련] 회원 가입", description = """ ### 회원 가입을 진행합니다. ### Request Body @@ -50,7 +50,7 @@ public ApiResponse signUp( return ApiResponse.onSuccess(SuccessStatus._OK, userCommandService.signUp(request)); } - @Operation(summary = "[이메일 중복 확인]", description = """ + @Operation(summary = "[회원 관련] 이메일 중복 확인 ", description = """ ### 이메일 중복을 확인합니다. ### Request Body @@ -62,7 +62,7 @@ public ApiResponse checkEmail(@RequestBody String email) { } // 로그인 - @Operation(summary = "[로그인]", description = """ + @Operation(summary = "[회원 관련] 로그인", description = """ ### 로그인을 진행합니다. 로그인 성공 시 access-token을 헤더에 포함하여 반환합니다. ### Request Body @@ -76,7 +76,7 @@ public ApiResponse login(@RequestBody SignInRequestDTO request, HttpServletRe } // 토큰 재발급 - @Operation(summary = "[토큰 재발급]", description = """ + @Operation(summary = "[회원 관련] 토큰 재발급", description = """ ### 토큰을 재발급합니다. refresh-token을 이용하여 access-token을 재발급합니다. ### Request Header @@ -89,7 +89,7 @@ public ApiResponse reissueToken() { // 마이페이지 - @Operation(summary = "[마이 페이지]", description = """ + @Operation(summary = "[회원 관련] 토큰 재발급", description = """ ### 마이페이지를 조회합니다. """) @GetMapping("/my-page") @@ -99,7 +99,7 @@ public ApiResponse myPage() { // 프로필 수정 - @Operation(summary = "[프로필 수정]", description = """ + @Operation(summary = "[회원 관련] 프로필 수정", description = """ ### 프로필을 수정합니다. ### Request Body diff --git a/src/main/java/hyu/erica/capstone/web/dto/client/AttractionRequestDTO.java b/src/main/java/hyu/erica/capstone/web/dto/client/AttractionRequestDTO.java new file mode 100644 index 0000000..a0e6c38 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/web/dto/client/AttractionRequestDTO.java @@ -0,0 +1,6 @@ +package hyu.erica.capstone.web.dto.client; + +import java.util.List; + +public record AttractionRequestDTO (String answer, List attraction_ids) { +} diff --git a/src/main/java/hyu/erica/capstone/web/dto/client/RestaurantRequestDTO.java b/src/main/java/hyu/erica/capstone/web/dto/client/RestaurantRequestDTO.java new file mode 100644 index 0000000..544c74c --- /dev/null +++ b/src/main/java/hyu/erica/capstone/web/dto/client/RestaurantRequestDTO.java @@ -0,0 +1,6 @@ +package hyu.erica.capstone.web.dto.client; + +import java.util.List; + +public record RestaurantRequestDTO(String answer, List restaurant_ids) { +} diff --git a/src/main/java/hyu/erica/capstone/web/dto/style/response/UserStyleFinalResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/style/response/UserStyleFinalResponseDTO.java new file mode 100644 index 0000000..f8fdadf --- /dev/null +++ b/src/main/java/hyu/erica/capstone/web/dto/style/response/UserStyleFinalResponseDTO.java @@ -0,0 +1,10 @@ +package hyu.erica.capstone.web.dto.style.response; + +import java.util.List; + +public record UserStyleFinalResponseDTO (List restaurant_ids, List attraction_ids) { + + public static UserStyleFinalResponseDTO of(List restaurant_ids, List attraction_ids) { + return new UserStyleFinalResponseDTO(restaurant_ids, attraction_ids); + } +}