diff --git a/src/main/java/swyp/swyp6_team7/location/dao/CountryDao.java b/src/main/java/swyp/swyp6_team7/location/dao/CountryDao.java new file mode 100644 index 00000000..0c14da88 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/dao/CountryDao.java @@ -0,0 +1,67 @@ +package swyp.swyp6_team7.location.dao; + +import org.springframework.dao.DuplicateKeyException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import swyp.swyp6_team7.location.domain.Country; +import swyp.swyp6_team7.location.domain.Continent; + +import java.util.Optional; + +@Repository +public class CountryDao { + private final JdbcTemplate jdbcTemplate; + + public CountryDao(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void addCountry(Country country) { + if (isCountryExists(country.getCountryName())) { + System.out.println("Duplicate country: " + country.getCountryName()); + return; + } + + String sql = "INSERT INTO countries (country_name, latitude, longitude, continent) VALUES (?, ?, ?, ?)"; + try { + jdbcTemplate.update(sql, + country.getCountryName(), + country.getLatitude(), + country.getLongitude(), + country.getContinent().name()); + } catch (DuplicateKeyException e) { + System.out.println("Already exists: " + country.getCountryName()); + } + } + + public boolean isCountryExists(String countryName) { + String sql = "SELECT COUNT(*) FROM countries WHERE country_name = ?"; + Integer count = jdbcTemplate.queryForObject(sql, new Object[]{countryName}, Integer.class); + return count != null && count > 0; + } + + public Optional findByCountryName(String countryName) { + String sql = "SELECT * FROM countries WHERE country_name = ?"; + return jdbcTemplate.query(sql, new Object[]{countryName}, rs -> { + if (rs.next()) { + Country country = new Country(); + country.setId(rs.getInt("country_id")); + country.setCountryName(rs.getString("country_name")); + country.setLatitude(rs.getDouble("latitude")); + country.setLongitude(rs.getDouble("longitude")); + country.setContinent(Continent.valueOf(rs.getString("continent"))); + return Optional.of(country); + } + return Optional.empty(); + }); + } + + public Country insertCountry(String countryName, Continent continent) { + String sql = "INSERT INTO countries (country_name, continent) VALUES (?, ?)"; + jdbcTemplate.update(sql, countryName, continent.name()); + + return findByCountryName(countryName).orElseThrow(() -> + new IllegalStateException("Country insert failed: " + countryName)); + } + +} diff --git a/src/main/java/swyp/swyp6_team7/location/dao/LocationDao.java b/src/main/java/swyp/swyp6_team7/location/dao/LocationDao.java index b94fbade..3ef9dec9 100644 --- a/src/main/java/swyp/swyp6_team7/location/dao/LocationDao.java +++ b/src/main/java/swyp/swyp6_team7/location/dao/LocationDao.java @@ -3,6 +3,7 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; @@ -14,29 +15,48 @@ public LocationDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - public void addCity(Location location) { - if (location == null || location.getLocationName() == null) { - throw new IllegalArgumentException("Invalid location data: location or locationName is null"); + public void addCity(Location location, Country country) { + String sql = "INSERT INTO locations (location_name, location_type, country_id) VALUES (?, ?, ?)"; + try { + jdbcTemplate.update(sql, + location.getLocationName(), + location.getLocationType().name(), + country.getId()); + } catch (DuplicateKeyException e) { + System.out.println("중복으로 삽입 불가 : " + location.getLocationName()); } + } - // 중복 데이터 체크 - if (isLocationExists(location.getLocationName(), location.getLocationType())) { - System.out.println("Duplicate entry found for: " + location.getLocationName()); - return; // 이미 존재하는 경우 삽입하지 않음 - } + public boolean existsByLocationName(String locationName) { + String sql = "SELECT COUNT(*) FROM locations WHERE location_name = ?"; + Integer count = jdbcTemplate.queryForObject(sql, new Object[]{locationName}, Integer.class); + return count != null && count > 0; + } - String sql = "INSERT INTO locations (location_name, location_type) VALUES (?, ?)"; - try { - jdbcTemplate.update(sql, location.getLocationName(), location.getLocationType().name()); - } catch (DuplicateKeyException e) { - System.out.println("Duplicate entry found for: " + location.getLocationName()); + public void updateLocationWithCountry(String locationName, String countryName, Integer countryId) { + String locationType = countryName.equals("대한민국") ? "DOMESTIC" : "INTERNATIONAL"; + + String sql = "UPDATE locations " + + "SET country_id = ?, location_type = ? " + + "WHERE location_name = ?"; + + int rows = jdbcTemplate.update(sql, countryId, locationType, locationName); + + if (rows > 0) { + //System.out.println(locationName + " 업데이트 완료 - country_id: " + countryId + ", type: " + locationType); + } else { + //System.out.println(locationName + " 은(는) 업데이트 대상 아님"); } } - public boolean isLocationExists(String locationName, LocationType locationType) { - // locationName과 locationType을 기준으로 DB에 해당 레코드가 있는지 체크합니다. - String sql = "SELECT COUNT(*) FROM locations WHERE location_name = ? AND location_type = ?"; - int count = jdbcTemplate.queryForObject(sql, new Object[]{locationName, locationType.name()}, Integer.class); - return count > 0; + public void updateCountryIdForMatchingLocationName(String countryName, Integer countryId) { + String sql = "UPDATE locations SET country_id = ?, location_type = ? WHERE location_name = ?"; + String type = countryName.equals("대한민국") ? "DOMESTIC" : "INTERNATIONAL"; + + int rows = jdbcTemplate.update(sql, countryId, type, countryName); + if (rows > 0) { + //System.out.println("국가 location 업데이트됨 → " + countryName); + } } + } diff --git a/src/main/java/swyp/swyp6_team7/location/domain/Continent.java b/src/main/java/swyp/swyp6_team7/location/domain/Continent.java new file mode 100644 index 00000000..153e32cd --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/domain/Continent.java @@ -0,0 +1,32 @@ +package swyp.swyp6_team7.location.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor +public enum Continent { + ASIA("아시아"), + EUROPE("유럽"), + NORTH_AMERICA("북아메리카"), + SOUTH_AMERICA("남아메리카"), + AFRICA("아프리카"), + OCEANIA("오세아니아"), + ANTARCTICA("남극"); + + private final String description; + + public static Continent fromString(String value) { + return Arrays.stream(values()) + .filter(e -> e.description.equals(value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("지원되지 않는 Continent입니다: " + value)); + } + + @Override + public String toString() { + return description; + } +} diff --git a/src/main/java/swyp/swyp6_team7/location/domain/Country.java b/src/main/java/swyp/swyp6_team7/location/domain/Country.java new file mode 100644 index 00000000..c45c1626 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/domain/Country.java @@ -0,0 +1,32 @@ +package swyp.swyp6_team7.location.domain; + +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Setter +@Entity +@AllArgsConstructor +@NoArgsConstructor +@Table(name ="countries") +public class Country { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "country_id") + private Integer id; + + @Column(name = "country_name",nullable = false, unique = true) + private String countryName; + + private Double latitude; + private Double longitude; + + @Enumerated(EnumType.STRING) + private Continent continent; + + @Builder + public Country(String countryName, Continent continent) { + this.countryName = countryName; + this.continent = continent; + } +} diff --git a/src/main/java/swyp/swyp6_team7/location/domain/Location.java b/src/main/java/swyp/swyp6_team7/location/domain/Location.java index b0209ea8..51ee00ec 100644 --- a/src/main/java/swyp/swyp6_team7/location/domain/Location.java +++ b/src/main/java/swyp/swyp6_team7/location/domain/Location.java @@ -21,9 +21,16 @@ public class Location { @Column(name = "location_type") private LocationType locationType; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "country_id", nullable = false) + private Country country; + + + @Builder - public Location(String locationName,LocationType locationType) { + public Location(String locationName,LocationType locationType, Country country) { this.locationName = locationName; this.locationType = locationType; + this.country = country; } } diff --git a/src/main/java/swyp/swyp6_team7/location/init/CountryInitializer.java b/src/main/java/swyp/swyp6_team7/location/init/CountryInitializer.java new file mode 100644 index 00000000..03c9f6d8 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/init/CountryInitializer.java @@ -0,0 +1,37 @@ +package swyp.swyp6_team7.location.init; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; +import swyp.swyp6_team7.location.dao.CountryDao; +import swyp.swyp6_team7.location.domain.Country; +import swyp.swyp6_team7.location.parser.CountryParser; +import swyp.swyp6_team7.location.reader.CsvReader; + +import java.io.InputStream; +import java.util.List; + + +@Component +@RequiredArgsConstructor +public class CountryInitializer { + private final CsvReader csvReader; + private final CountryParser countryParser; + private final CountryDao countryDao; + + @PostConstruct + public void initCountries() { + try { + InputStream input = new ClassPathResource("cities/countries.csv").getInputStream(); + List countries = csvReader.readByLine(input, countryParser, null); + countries.stream() + .filter(country -> country != null) + .forEach(countryDao::addCountry); + System.out.println("countries.csv 적재 완료 (" + countries.size() + "건)"); + } catch (Exception e) { + System.err.println("countries.csv 적재 실패: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/swyp/swyp6_team7/location/init/LocationInitializer.java b/src/main/java/swyp/swyp6_team7/location/init/LocationInitializer.java new file mode 100644 index 00000000..c6e855fc --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/init/LocationInitializer.java @@ -0,0 +1,22 @@ +package swyp.swyp6_team7.location.init; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import swyp.swyp6_team7.location.service.LocationService; + +@Component +@RequiredArgsConstructor +public class LocationInitializer { + private final LocationService locationService; + + @PostConstruct + public void initLocations() { + try { + locationService.loadAllLocations(); + } catch (Exception e) { + System.err.println("Location 초기 데이터 적재 실패: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/swyp/swyp6_team7/location/parser/CityParser.java b/src/main/java/swyp/swyp6_team7/location/parser/CityParser.java index b918f5fd..1343fb4d 100644 --- a/src/main/java/swyp/swyp6_team7/location/parser/CityParser.java +++ b/src/main/java/swyp/swyp6_team7/location/parser/CityParser.java @@ -4,7 +4,10 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; +import swyp.swyp6_team7.location.dao.CountryDao; import swyp.swyp6_team7.location.dao.LocationDao; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; @@ -18,55 +21,53 @@ public class CityParser implements Parser { private final LocationDao locationDao; + private final CountryDao countryDao; @Override public Location parse(String line, LocationType locationType) { - String[] columns = line.split(","); - - String location; - - switch (locationType) { - case DOMESTIC: - location = columns[3].trim(); // 소분류 (시) - // '시' 제거 로직 추가 - if (location.endsWith("시")) { - location = location.substring(0, location.length() - 1); - } - break; + // foreign: id,continent,country,city,city_en + // korea: id,country,region,city - case INTERNATIONAL: - location = columns[2].trim(); // 중분류 (나라) + String[] columns = line.split(","); + if (columns.length < 4) return null; - // 중복 체크 로직 추가 - if (locationDao.isLocationExists(location, locationType)) { - // 이미 해당 나라가 DB에 있는 경우, 중복 추가 방지 - System.out.println("Location already exists: " + location); - return null; // null 반환으로 추가하지 않음 - } - break; + String countryName; + String cityName; + Continent continent = Continent.ASIA; // 기본값 국내 데이터 파싱에 사용 - default: - throw new IllegalArgumentException("지원되지 않는 LocationType입니다: " + locationType); + if (locationType == LocationType.DOMESTIC) { + countryName = columns[1].trim(); // 대한민국 + cityName = columns[3].trim().replace("시", ""); // 서울시 → 서울 + } else { + countryName = columns[2].trim(); // 예: 미국 + cityName = columns[3].trim(); // 예: 뉴욕 + try { + continent = Continent.fromString(columns[1].trim()); + } catch (Exception ignored) { + // 유럽/아시아 같은 애매한 값 무시 + } } - return new Location(location, locationType); - } + final Continent finalContinent = continent; - public void parseAndSave(String resourcePath, LocationType locationType) { - Resource resource = new ClassPathResource(resourcePath); - try (InputStream inputStream = resource.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { + // country 조회 또는 신규 생성 + Country country = countryDao.findByCountryName(countryName) + .orElseGet(() -> countryDao.insertCountry(countryName, finalContinent)); - String line; - br.readLine(); // 첫 번째 행 건너뜀 (헤더) - while ((line = br.readLine()) != null) { - Location locationObject = parse(line, locationType); - if (locationObject != null) { - locationDao.addCity(locationObject); - } - } - } catch (IOException e) { - e.printStackTrace(); + locationDao.updateCountryIdForMatchingLocationName(country.getCountryName(), country.getId()); + + // 이미 존재하면 업데이트, 없으면 삽입 + if (locationDao.existsByLocationName(cityName)) { + locationDao.updateLocationWithCountry(cityName, country.getCountryName(), country.getId()); + } else { + Location location = Location.builder() + .locationName(cityName) + .locationType(locationType) + .country(country) + .build(); + locationDao.addCity(location, country); } + return null; + } } diff --git a/src/main/java/swyp/swyp6_team7/location/parser/CountryParser.java b/src/main/java/swyp/swyp6_team7/location/parser/CountryParser.java new file mode 100644 index 00000000..cf4ca2de --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/parser/CountryParser.java @@ -0,0 +1,37 @@ +package swyp.swyp6_team7.location.parser; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import swyp.swyp6_team7.location.dao.CountryDao; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; +import swyp.swyp6_team7.location.domain.LocationType; + +@Component +@RequiredArgsConstructor +public class CountryParser implements Parser { + private final CountryDao countryDao; + + @Override + public Country parse(String line, LocationType locationType) { + String[] parts = line.split(","); + if (parts.length < 4) return null; + + String countryName = parts[0].trim(); + double latitude = Double.parseDouble(parts[1].trim()); + double longitude = Double.parseDouble(parts[2].trim()); + String continentStr = parts[3].trim(); + + if (countryDao.isCountryExists(countryName)) return null; + + Continent continent = Continent.fromString(continentStr); + + Country country = new Country(); + country.setCountryName(countryName); + country.setLatitude(latitude); + country.setLongitude(longitude); + country.setContinent(continent); + + return country; + } +} diff --git a/src/main/java/swyp/swyp6_team7/location/repository/CountryRepository.java b/src/main/java/swyp/swyp6_team7/location/repository/CountryRepository.java new file mode 100644 index 00000000..33f6adab --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/repository/CountryRepository.java @@ -0,0 +1,11 @@ +package swyp.swyp6_team7.location.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import swyp.swyp6_team7.location.domain.Country; + +import java.util.Optional; + +public interface CountryRepository extends JpaRepository { + boolean existsByCountryName(String countryName); + Optional findByCountryName(String countryName); +} diff --git a/src/main/java/swyp/swyp6_team7/location/service/CountryService.java b/src/main/java/swyp/swyp6_team7/location/service/CountryService.java new file mode 100644 index 00000000..451d825e --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/location/service/CountryService.java @@ -0,0 +1,27 @@ +package swyp.swyp6_team7.location.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; +import swyp.swyp6_team7.location.repository.CountryRepository; + +@Service +@RequiredArgsConstructor +public class CountryService { + private final CountryRepository countryRepository; + + @Transactional + public Country InsertCountry(String countryName, Continent defaultContinent) { + return countryRepository.findByCountryName(countryName) + .orElseGet(() -> { + Country country = new Country(); + country.setCountryName(countryName); + country.setContinent(defaultContinent); + country.setLatitude(null); // 후처리 가능 + country.setLongitude(null); + return countryRepository.save(country); + }); + } +} diff --git a/src/main/java/swyp/swyp6_team7/location/service/LocationService.java b/src/main/java/swyp/swyp6_team7/location/service/LocationService.java index ceea4499..ecff74db 100644 --- a/src/main/java/swyp/swyp6_team7/location/service/LocationService.java +++ b/src/main/java/swyp/swyp6_team7/location/service/LocationService.java @@ -5,6 +5,7 @@ import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import swyp.swyp6_team7.location.dao.LocationDao; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; import swyp.swyp6_team7.location.parser.CityParser; @@ -24,13 +25,7 @@ public class LocationService { public void importCities(InputStream inputStream, LocationType locationType) throws IOException { List cities = csvReader.readByLine(inputStream, cityParser, locationType); - cities.forEach(city -> { - if (city != null && city.getLocationName() != null && !city.getLocationName().trim().isEmpty()) { - locationDao.addCity(city); - } else { - System.out.println("Invalid location data: " + city); - } - }); + System.out.println("location 데이터 적재 완료: " + cities.size() + "건"); } public void loadAllLocations() throws IOException { diff --git a/src/main/java/swyp/swyp6_team7/member/entity/Users.java b/src/main/java/swyp/swyp6_team7/member/entity/Users.java index 08bd0133..e511571a 100644 --- a/src/main/java/swyp/swyp6_team7/member/entity/Users.java +++ b/src/main/java/swyp/swyp6_team7/member/entity/Users.java @@ -71,6 +71,9 @@ public class Users { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private Set tagPreferences; // user_tagpreferences 참조 + @Column(name = "total_distance") + private Double totalDistance = 0.0; + @Builder public Users(Integer userNumber, String userEmail, String userPw, String userName, Gender userGender, AgeGroup userAgeGroup, Set preferredTags) { this.userNumber = userNumber; diff --git a/src/main/java/swyp/swyp6_team7/member/repository/UserBlockReportRepository.java b/src/main/java/swyp/swyp6_team7/member/repository/UserBlockReportRepository.java index aea62191..152a82b3 100644 --- a/src/main/java/swyp/swyp6_team7/member/repository/UserBlockReportRepository.java +++ b/src/main/java/swyp/swyp6_team7/member/repository/UserBlockReportRepository.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Repository; import swyp.swyp6_team7.member.entity.UserBlockReport; +import java.time.LocalDateTime; import java.util.List; @Repository @@ -11,4 +12,8 @@ public interface UserBlockReportRepository extends JpaRepository findAllByReportedUserNumberOrderByRegTs(int userNumber); List findAllByReportedUserNumberAndReporterUserNumber(int reportedUserNumber, int reporterUserNumber); + + int countByReportedUserNumber(Integer reportedUserNumber); + int countByReportedUserNumberAndRegTsAfter(Integer reportedUserNumber, LocalDateTime since); + } diff --git a/src/main/java/swyp/swyp6_team7/member/service/MemberBlockService.java b/src/main/java/swyp/swyp6_team7/member/service/MemberBlockService.java index 81588809..c45626b2 100644 --- a/src/main/java/swyp/swyp6_team7/member/service/MemberBlockService.java +++ b/src/main/java/swyp/swyp6_team7/member/service/MemberBlockService.java @@ -15,6 +15,7 @@ import swyp.swyp6_team7.member.repository.UserBlockRepository; import swyp.swyp6_team7.member.repository.UserRepository; import swyp.swyp6_team7.notification.service.UserBlockWarnNotificationService; +import swyp.swyp6_team7.profile.dto.UserReportStats; import java.time.LocalDate; import java.time.LocalDateTime; @@ -258,4 +259,17 @@ public String getTempToken(Users user) { return tempToken; } + + public UserReportStats getUserReportStats(int targetUserNumber) { + int totalReports = userBlockReportRepository.countByReportedUserNumber(targetUserNumber); + + LocalDateTime oneWeekAgo = LocalDateTime.now().minusDays(7); + int recentReportCount = userBlockReportRepository.countByReportedUserNumberAndRegTsAfter(targetUserNumber, oneWeekAgo); + + boolean recentlyReported = recentReportCount > 0; + + + return new UserReportStats(recentlyReported, totalReports,recentReportCount); + } + } diff --git a/src/main/java/swyp/swyp6_team7/member/service/MemberService.java b/src/main/java/swyp/swyp6_team7/member/service/MemberService.java index 06a9024b..59b39754 100644 --- a/src/main/java/swyp/swyp6_team7/member/service/MemberService.java +++ b/src/main/java/swyp/swyp6_team7/member/service/MemberService.java @@ -287,4 +287,14 @@ private Users findUserById(Integer userNumber) { return userRepository.findById(userNumber) .orElseThrow(() -> new IllegalArgumentException("해당 회원이 존재하지 않습니다.")); } + + @Transactional + public void updateUserTravelDistance(Integer userNumber, double travelDistance) { + Users user = userRepository.findById(userNumber) + .orElseThrow(() -> new RuntimeException("사용자 없음")); + user.setTotalDistance(travelDistance); // 누적 저장 + } + + + } diff --git a/src/main/java/swyp/swyp6_team7/profile/controller/ProfileController.java b/src/main/java/swyp/swyp6_team7/profile/controller/ProfileController.java index 87a05edf..9f0f63b2 100644 --- a/src/main/java/swyp/swyp6_team7/profile/controller/ProfileController.java +++ b/src/main/java/swyp/swyp6_team7/profile/controller/ProfileController.java @@ -9,7 +9,7 @@ import swyp.swyp6_team7.global.utils.auth.RequireUserNumber; import swyp.swyp6_team7.member.entity.Users; import swyp.swyp6_team7.profile.dto.ProfileUpdateRequest; -import swyp.swyp6_team7.profile.dto.ProfileViewResponse; +import swyp.swyp6_team7.profile.dto.response.ProfileViewResponse; import swyp.swyp6_team7.profile.service.ProfileService; import java.util.Optional; @@ -43,7 +43,7 @@ public ApiResponse updateProfile( } } - //프로필 조회 (이름, 이메일, 연령대, 성별, 선호 태그, 자기소개) + //내 프로필 조회 @GetMapping("/me") public ApiResponse viewProfile( @RequireUserNumber Integer userNumber @@ -56,7 +56,19 @@ public ApiResponse viewProfile( throw new MoingApplicationException("사용자를 찾을 수 없음"); } - return ApiResponse.success(new ProfileViewResponse(userOpt.get())); + // 실제 계산 로직이 없으므로, 0 또는 기본값으로 설정 + double travelDistance = 0.0; + int visitedCountryCount = 0; + int travelBadgeCount = 0; + + // DTO 생성자에 맞춰서 값 전달 + ProfileViewResponse response = new ProfileViewResponse( + userOpt.get(), + visitedCountryCount, + travelBadgeCount + ); + + return ApiResponse.success(response); } catch (IllegalArgumentException e) { String errorMessage = e.getMessage() != null ? e.getMessage() : "잘못된 요청입니다."; log.warn("Invalid request: {}", errorMessage); @@ -66,4 +78,6 @@ public ApiResponse viewProfile( throw e; } } + + } diff --git a/src/main/java/swyp/swyp6_team7/profile/controller/TargetUserProfileController.java b/src/main/java/swyp/swyp6_team7/profile/controller/TargetUserProfileController.java new file mode 100644 index 00000000..0252f1bb --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/controller/TargetUserProfileController.java @@ -0,0 +1,40 @@ +package swyp.swyp6_team7.profile.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import swyp.swyp6_team7.global.exception.MoingApplicationException; +import swyp.swyp6_team7.global.utils.api.ApiResponse; +import swyp.swyp6_team7.global.utils.auth.RequireUserNumber; +import swyp.swyp6_team7.profile.dto.response.TargetUserProfileResponse; +import swyp.swyp6_team7.profile.service.TargetUserProfileService; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/api/users") +public class TargetUserProfileController { + private final TargetUserProfileService targetUserProfileService; + // 상대방 프로필 조회 + @GetMapping("/{targetUserNumber}/profile") + public ApiResponse viewOtherUserProfile( + @RequireUserNumber Integer userNumber, + @PathVariable("targetUserNumber") Integer targetUserNumber + ){ + try { + log.info("상대방 프로필 조회 요청 - 요청자 userNumber: {}, 대상 otherUserNumber: {}", userNumber, targetUserNumber); + TargetUserProfileResponse response = targetUserProfileService.getOtherUserProfile(targetUserNumber); + return ApiResponse.success(response); + } catch (IllegalArgumentException e) { + String errorMessage = e.getMessage() != null ? e.getMessage() : "잘못된 요청입니다."; + log.warn("Invalid request: {}", errorMessage); + throw new MoingApplicationException(errorMessage); + } catch (Exception e) { + log.error("Error fetching other user profile ", e); + throw e; + } + } +} diff --git a/src/main/java/swyp/swyp6_team7/profile/controller/UserTravelListController.java b/src/main/java/swyp/swyp6_team7/profile/controller/UserTravelListController.java new file mode 100644 index 00000000..b0e0fec6 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/controller/UserTravelListController.java @@ -0,0 +1,59 @@ +package swyp.swyp6_team7.profile.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; +import swyp.swyp6_team7.global.utils.api.ApiResponse; +import swyp.swyp6_team7.global.utils.auth.RequireUserNumber; +import swyp.swyp6_team7.profile.dto.response.VisitedCountryLogResponse; +import swyp.swyp6_team7.profile.service.VisitedCountryLogService; +import swyp.swyp6_team7.travel.dto.response.TravelListResponseDto; +import swyp.swyp6_team7.travel.service.TravelAppliedService; +import swyp.swyp6_team7.travel.service.TravelListService; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/users") +public class UserTravelListController { + private final TravelListService travelListService; + private final TravelAppliedService travelAppliedService; + private final VisitedCountryLogService visitedCountryLogService; + + // 상대방의 만든 여행 목록 조회 + @GetMapping("/{targetUserNumber}/created-travels") + public ApiResponse> getTargetUserCreatedTravels( + @PathVariable("targetUserNumber") Integer targetUserNumber, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "5") int size, + @RequireUserNumber Integer userNumber + ){ + Pageable pageable = PageRequest.of(page, size); + Page createdTravelList = travelListService.getTravelListByUser(targetUserNumber, pageable); + + return ApiResponse.success(createdTravelList); + } + + // 상대방의 참가한 여행 목록 조회 + @GetMapping("/{targetUserNumber}/applied-travels") + public ApiResponse> getTargetUserAppliedTravels( + @PathVariable("targetUserNumber") Integer targetUserNumber, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "5") int size, + @RequireUserNumber Integer userNumber + ){ + Pageable pageable = PageRequest.of(page, size); + Page appliedTravelList = travelAppliedService.getAppliedTripsByUser(targetUserNumber, pageable); + + return ApiResponse.success(appliedTravelList); + } + + // 상대방의 방문한 여행 로그 조회 + @GetMapping("/{targetUserNumber}/visited-countries") + public ApiResponse getVisitedCountries( + @PathVariable("targetUserNumber") Integer targetUserNumber, + @RequireUserNumber Integer userNumber) { + return ApiResponse.success(visitedCountryLogService.getVisitedCountriesByUser(targetUserNumber)); + } +} diff --git a/src/main/java/swyp/swyp6_team7/profile/dto/UserReportStats.java b/src/main/java/swyp/swyp6_team7/profile/dto/UserReportStats.java new file mode 100644 index 00000000..e0c569f7 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/dto/UserReportStats.java @@ -0,0 +1,12 @@ +package swyp.swyp6_team7.profile.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UserReportStats { + private boolean recentlyReported; // 최근 7일 내 신고당함 여부 + private int totalReportCount; // 누적 신고 당한 횟수 + private int recentReportCount; // 최근 7내 신고당한 횟수 +} diff --git a/src/main/java/swyp/swyp6_team7/profile/dto/ProfileViewResponse.java b/src/main/java/swyp/swyp6_team7/profile/dto/response/ProfileViewResponse.java similarity index 50% rename from src/main/java/swyp/swyp6_team7/profile/dto/ProfileViewResponse.java rename to src/main/java/swyp/swyp6_team7/profile/dto/response/ProfileViewResponse.java index 76c81a3a..b766756b 100644 --- a/src/main/java/swyp/swyp6_team7/profile/dto/ProfileViewResponse.java +++ b/src/main/java/swyp/swyp6_team7/profile/dto/response/ProfileViewResponse.java @@ -1,29 +1,47 @@ -package swyp.swyp6_team7.profile.dto; +package swyp.swyp6_team7.profile.dto.response; import lombok.Getter; -import lombok.Setter; import swyp.swyp6_team7.member.entity.Users; import swyp.swyp6_team7.tag.domain.UserTagPreference; +import java.time.format.DateTimeFormatter; import java.util.Set; @Getter -@Setter public class ProfileViewResponse { private String email; private String name; private String gender; + private String userRegDate; private String ageGroup; private String[] preferredTags; private boolean userSocialTF; - public ProfileViewResponse(Users user) { + private Double travelDistance; // 계산된 여행 거리 + private Integer visitedCountryCount; // 방문한 국가 개수 + private Integer travelBadgeCount; // 획득한 여행 뱃지 개수 + + public ProfileViewResponse(Users user, + Integer visitedCountryCount, + Integer travelBadgeCount) { this.email = user.getUserEmail(); this.name = user.getUserName(); this.gender = user.getUserGender().name(); this.ageGroup = user.getUserAgeGroup().getValue(); this.userSocialTF = user.getUserSocialTF(); + this.travelDistance = user.getTotalDistance() != null ? travelDistance : 0.0; + this.visitedCountryCount = visitedCountryCount != null ? visitedCountryCount : 0; + this.travelBadgeCount = travelBadgeCount != null ? travelBadgeCount : 0; + + // userRegDate 포매팅 + if (user.getUserRegDate() != null) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 M월"); + this.userRegDate = user.getUserRegDate().format(formatter); + } else { + this.userRegDate = ""; + } + // 태그 목록을 가져와서 배열로 변환 Set tagPreferences = user.getTagPreferences(); if (tagPreferences != null && !tagPreferences.isEmpty()) { diff --git a/src/main/java/swyp/swyp6_team7/profile/dto/response/TargetUserProfileResponse.java b/src/main/java/swyp/swyp6_team7/profile/dto/response/TargetUserProfileResponse.java new file mode 100644 index 00000000..3af0a8b9 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/dto/response/TargetUserProfileResponse.java @@ -0,0 +1,71 @@ +package swyp.swyp6_team7.profile.dto.response; + +import lombok.Getter; +import swyp.swyp6_team7.member.entity.Users; +import swyp.swyp6_team7.tag.domain.UserTagPreference; + +import java.time.format.DateTimeFormatter; +import java.util.Set; + +@Getter +public class TargetUserProfileResponse { + // 기본 정보 + private String name; + private String userRegDate; // 예: "2025년 2월" + private String ageGroup; + private String[] preferredTags; + // 프로필 이미지 + private String profileImageUrl; + + // 추가 정보 + private Integer createdTravelCount; // 생성한 여행 수 + private Integer participatedTravelCount; // 참가한 여행 수 + private Double travelDistance; // 계산된 여행 거리 + private Integer visitedCountryCount; // 방문한 국가 수 + //TODO + private Integer travelBadgeCount; // 획득한 여행 뱃지 수 + + private Boolean recentlyReported; // 최근 신고 여부 + private Integer totalReportCount; // 누적 신고 횟수 + private Integer recentReportCount; // 최근 신고 횟수 + + public TargetUserProfileResponse(Users user, + String profileImageUrl, + Integer createdTravelCount, + Integer participatedTravelCount, + Integer visitedCountryCount, + Integer travelBadgeCount, + Boolean recentlyReported, + Integer totalReportCount, + Integer recentReportCount) { + this.name = user.getUserName(); + this.ageGroup = user.getUserAgeGroup().getValue(); + this.profileImageUrl = profileImageUrl; + this.createdTravelCount = createdTravelCount != null ? createdTravelCount : 0; + this.participatedTravelCount = participatedTravelCount != null ? participatedTravelCount : 0; + this.travelDistance = user.getTotalDistance(); + this.visitedCountryCount = visitedCountryCount != null ? visitedCountryCount : 0; + this.travelBadgeCount = travelBadgeCount != null ? travelBadgeCount : 0; + this.recentlyReported = recentlyReported != null ? recentlyReported : false; + this.totalReportCount = totalReportCount != null ? totalReportCount : 0; + this.recentReportCount = recentReportCount != null ? recentReportCount : 0; + + if (user.getUserRegDate() != null) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 M월"); + this.userRegDate = user.getUserRegDate().format(formatter); + } else { + this.userRegDate = ""; + } + + Set tagPreferences = user.getTagPreferences(); + if (tagPreferences != null && !tagPreferences.isEmpty()) { + this.preferredTags = tagPreferences.stream() + .map(preference -> preference.getTag().getName()) + .toArray(String[]::new); + } else { + this.preferredTags = new String[0]; + } + } + + +} diff --git a/src/main/java/swyp/swyp6_team7/profile/dto/response/VisitedCountryLogResponse.java b/src/main/java/swyp/swyp6_team7/profile/dto/response/VisitedCountryLogResponse.java new file mode 100644 index 00000000..786fc9bc --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/dto/response/VisitedCountryLogResponse.java @@ -0,0 +1,36 @@ +package swyp.swyp6_team7.profile.dto.response; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import swyp.swyp6_team7.location.domain.Continent; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +@Getter +@Builder +@AllArgsConstructor +public class VisitedCountryLogResponse { + private Integer userNumber; + private Integer visitedCountriesCount; + // 대륙별 (국가명, 시작일자) 목록 + private Map> internationalLogs; + private List domesticLogs; + + @Getter + @Builder + @AllArgsConstructor + public static class CountryVisits { + private String countryName; + private List visitDates; + } + + @Getter + @Builder + @AllArgsConstructor + public static class DomesticVisit { + private String locationName; + private List visitDates; + } +} diff --git a/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogCustomRepository.java b/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogCustomRepository.java new file mode 100644 index 00000000..2c090ca3 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogCustomRepository.java @@ -0,0 +1,13 @@ +package swyp.swyp6_team7.profile.repository; + +import com.querydsl.core.Tuple; + +import java.util.List; + +public interface VisitedCountryLogCustomRepository { + List findVisitedCountriesWithContinentByUser(Integer userNumber); + List findVisitedCountriesWithStartDate(Integer userNumber); + List findInternationalVisits(Integer userNumber); + List findDomesticVisits(Integer userNumber); + List findAllUserNumbersWithTravelLog(); +} diff --git a/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogCustomRepositoryImpl.java b/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogCustomRepositoryImpl.java new file mode 100644 index 00000000..3390efd2 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogCustomRepositoryImpl.java @@ -0,0 +1,177 @@ +package swyp.swyp6_team7.profile.repository; + +import com.querydsl.core.Tuple; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import swyp.swyp6_team7.companion.domain.QCompanion; +import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.domain.QCountry; +import swyp.swyp6_team7.location.domain.QLocation; +import swyp.swyp6_team7.travel.domain.QTravel; + +import java.time.LocalDate; +import java.util.*; + +@Repository +@RequiredArgsConstructor +public class VisitedCountryLogCustomRepositoryImpl implements VisitedCountryLogCustomRepository { + + private final JPAQueryFactory queryFactory; + + QTravel travel = QTravel.travel; + QLocation location = QLocation.location; + QCountry country = QCountry.country; + QCompanion companion = QCompanion.companion; + + @Override + public List findVisitedCountriesWithContinentByUser(Integer userNumber) { + LocalDate today = LocalDate.now(); + + // 내가 개설한 여행 중 종료된 것 + List createdTravels = queryFactory + .select(location.country.countryName, location.country.continent, travel.startDate) + .from(travel) + .join(travel.location, location) + .where( + travel.userNumber.eq(userNumber), + travel.endDate.before(today) + ) + .distinct() + .fetch(); + + // 내가 companion으로 참가한 여행 중 종료된 것 + List participatedTravels = queryFactory + .select(location.country.countryName, location.country.continent, travel.startDate) + .from(companion) + .join(companion.travel, travel) + .join(travel.location, location) + .where( + companion.userNumber.eq(userNumber), + travel.endDate.before(today) + ) + .distinct() + .fetch(); + + Set result = new LinkedHashSet<>(); + result.addAll(createdTravels); + result.addAll(participatedTravels); + + return new ArrayList<>(result); + } + + @Override + public List findVisitedCountriesWithStartDate(Integer userNumber) { + LocalDate today = LocalDate.now(); + + List created = queryFactory + .select( + country.countryName, + country.continent, + country + ) + .from(travel) + .join(travel.location, location) + .join(location.country, country) + .where( + travel.userNumber.eq(userNumber), + travel.endDate.before(today) + ) + .distinct() + .fetch(); + + List participated = queryFactory + .select( + country.countryName, + country.continent, + country + ) + .from(companion) + .join(companion.travel, travel) + .join(travel.location, location) + .join(location.country, country) + .where( + companion.userNumber.eq(userNumber), + travel.endDate.before(today) + ) + .distinct() + .fetch(); + + Set result = new LinkedHashSet<>(); + result.addAll(created); + result.addAll(participated); + + return new ArrayList<>(result); + } + @Override + public List findInternationalVisits(Integer userNumber) { + LocalDate today = LocalDate.now(); + + return queryFactory + .select(country.countryName, country.continent, travel.startDate) + .from(travel) + .join(travel.location, location) + .join(location.country, country) + .where( + travel.userNumber.eq(userNumber) + .or(travel.in( + queryFactory + .select(companion.travel) + .from(companion) + .where(companion.userNumber.eq(userNumber)) + )), + location.locationType.eq(LocationType.INTERNATIONAL), + travel.endDate.before(today) + ) + .fetch(); + } + + @Override + public List findDomesticVisits(Integer userNumber) { + LocalDate today = LocalDate.now(); + + return queryFactory + .select(location.locationName, travel.startDate) + .from(travel) + .join(travel.location, location) + .where( + travel.userNumber.eq(userNumber) + .or(travel.in( + queryFactory + .select(companion.travel) + .from(companion) + .where(companion.userNumber.eq(userNumber)) + )), + location.locationType.eq(LocationType.DOMESTIC), + travel.endDate.before(today) + ) + .fetch(); + } + + @Override + public List findAllUserNumbersWithTravelLog() { + LocalDate today = LocalDate.now(); + + List created = queryFactory + .select(travel.userNumber) + .from(travel) + .where(travel.endDate.before(today)) + .distinct() + .fetch(); + + List participated = queryFactory + .select(companion.userNumber) + .from(companion) + .join(companion.travel, travel) + .where(travel.endDate.before(today)) + .distinct() + .fetch(); + + Set result = new LinkedHashSet<>(); + result.addAll(created); + result.addAll(participated); + + return new ArrayList<>(result); + } + +} diff --git a/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogRepository.java b/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogRepository.java new file mode 100644 index 00000000..2fec8260 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/repository/VisitedCountryLogRepository.java @@ -0,0 +1,9 @@ +package swyp.swyp6_team7.profile.repository; + +import com.querydsl.core.Tuple; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import swyp.swyp6_team7.travel.domain.Travel; + +public interface VisitedCountryLogRepository extends JpaRepository, VisitedCountryLogCustomRepository { +} diff --git a/src/main/java/swyp/swyp6_team7/profile/service/ProfileService.java b/src/main/java/swyp/swyp6_team7/profile/service/ProfileService.java index e6d5eb88..5e7b4ac2 100644 --- a/src/main/java/swyp/swyp6_team7/profile/service/ProfileService.java +++ b/src/main/java/swyp/swyp6_team7/profile/service/ProfileService.java @@ -5,9 +5,11 @@ import org.springframework.transaction.annotation.Transactional; import swyp.swyp6_team7.member.entity.AgeGroup; import swyp.swyp6_team7.member.entity.Users; +import swyp.swyp6_team7.profile.dto.response.ProfileViewResponse; import swyp.swyp6_team7.profile.repository.UserProfileRepository; import swyp.swyp6_team7.member.repository.UserRepository; import swyp.swyp6_team7.profile.dto.ProfileUpdateRequest; +import swyp.swyp6_team7.profile.repository.VisitedCountryLogRepository; import swyp.swyp6_team7.tag.domain.Tag; import swyp.swyp6_team7.tag.domain.UserTagPreference; import swyp.swyp6_team7.tag.repository.TagRepository; @@ -17,6 +19,8 @@ import java.util.Optional; import java.util.Set; import lombok.RequiredArgsConstructor; +import swyp.swyp6_team7.travel.service.TravelAppliedService; +import swyp.swyp6_team7.travel.service.TravelListService; @Service @RequiredArgsConstructor @@ -26,6 +30,9 @@ public class ProfileService { private final UserRepository userRepository; private final TagRepository tagRepository; private final UserTagPreferenceRepository userTagPreferenceRepository; + private final TravelAppliedService travelAppliedService; + private final TravelListService travelListService; + private final VisitedCountryLogService visitedCountryLogService; @Transactional @@ -83,4 +90,21 @@ public Optional getUserByUserNumber(Integer userNumber) { throw e; } } + + // 본인 프로필 조회 메서드 + public ProfileViewResponse getProfileView(Integer userNumber) { + try { + log.info("내 프로필 조회 시작 - userNumber: {}", userNumber); + Integer visitedCountryCount=visitedCountryLogService.calculateVisitedCountryCount(userNumber); + //TODO + Integer travelBadgeCount=0; + + Users user = userRepository.findUserWithTags(userNumber) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + return new ProfileViewResponse(user, visitedCountryCount, travelBadgeCount); + } catch (Exception e) { + log.error("내 프로필 조회 중 에러 발생 - userNumber: {}", userNumber, e); + throw e; + } + } } diff --git a/src/main/java/swyp/swyp6_team7/profile/service/TargetUserProfileService.java b/src/main/java/swyp/swyp6_team7/profile/service/TargetUserProfileService.java new file mode 100644 index 00000000..eb59aeb9 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/service/TargetUserProfileService.java @@ -0,0 +1,67 @@ +package swyp.swyp6_team7.profile.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import swyp.swyp6_team7.image.repository.ImageRepository; +import swyp.swyp6_team7.member.entity.Users; +import swyp.swyp6_team7.member.repository.UserRepository; +import swyp.swyp6_team7.member.service.MemberBlockService; +import swyp.swyp6_team7.profile.dto.UserReportStats; +import swyp.swyp6_team7.profile.dto.response.TargetUserProfileResponse; +import swyp.swyp6_team7.travel.service.TravelAppliedService; +import swyp.swyp6_team7.travel.service.TravelListService; + +@Service +@RequiredArgsConstructor +@Slf4j +public class TargetUserProfileService { + private final UserRepository userRepository; + private final TravelAppliedService travelAppliedService; + private final TravelListService travelListService; + private final VisitedCountryLogService visitedCountryLogService; + private final ImageRepository imageRepository; + private final MemberBlockService memberBlockService; + + // TODO: 링크 수정 + private final static String DEFAULT_PROFILE_IMAGE_URL = "https://moing-hosted-contents.s3.ap-northeast-2.amazonaws.com/images/profile/default/defaultProfile.png"; + + // 상대방 프로필 조회 메서드 (TargetUserProfileResponse 사용) + public TargetUserProfileResponse getOtherUserProfile(Integer userNumber) { + try { + log.info("상대방 프로필 조회 시작 - userNumber: {}", userNumber); + Users user = userRepository.findUserWithTags(userNumber) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + String profileImageUrl = imageRepository.findUrlByRelatedUserNumber(userNumber) + .orElse(DEFAULT_PROFILE_IMAGE_URL);; + + Integer createdTravelCount = travelListService.countCreatedTravelsByUser(userNumber); // 사용자가 만든 여행 개수 + Integer participatedTravelCount = travelAppliedService.countAppliedTrpsByUser(userNumber); // 사용자가 참가한 여행 개수 + + Integer visitedCountryCount = visitedCountryLogService.calculateVisitedCountryCount(userNumber); // 방문한 국가 수 + + // TODO + Integer travelBadgeCount = 0; // 획득한 여행 뱃지 수 + + UserReportStats reportStats = memberBlockService.getUserReportStats(userNumber); + Boolean recentlyReported = reportStats.isRecentlyReported(); //최근 신고 여부 + Integer totalReportCount = reportStats.getTotalReportCount(); // 누적 신고 수 + Integer recentReportCount = reportStats.getRecentReportCount(); // 최근 신고 수 + + + return new TargetUserProfileResponse( + user, + profileImageUrl, + createdTravelCount, + participatedTravelCount, + visitedCountryCount, + travelBadgeCount, + recentlyReported, + totalReportCount, + recentReportCount); + } catch (Exception e) { + log.error("상대방 프로필 조회 중 에러 발생 - userNumber: {}", userNumber, e); + throw e; + } + } +} diff --git a/src/main/java/swyp/swyp6_team7/profile/service/VisitedCountryLogService.java b/src/main/java/swyp/swyp6_team7/profile/service/VisitedCountryLogService.java new file mode 100644 index 00000000..4fedfef8 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/service/VisitedCountryLogService.java @@ -0,0 +1,115 @@ +package swyp.swyp6_team7.profile.service; +import com.querydsl.core.Tuple; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; +import swyp.swyp6_team7.member.service.MemberService; +import swyp.swyp6_team7.profile.dto.response.VisitedCountryLogResponse; +import swyp.swyp6_team7.profile.repository.VisitedCountryLogRepository; +import swyp.swyp6_team7.profile.util.DistanceCalculator; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class VisitedCountryLogService { + private final VisitedCountryLogRepository visitedCountryLogRepository; + private final MemberService memberService; + + public VisitedCountryLogResponse getVisitedCountriesByUser(Integer userNumber) { + List international = visitedCountryLogRepository.findInternationalVisits(userNumber); + List domestic = visitedCountryLogRepository.findDomesticVisits(userNumber); + + // 국제 로그 그룹핑 + Map>> groupedInternational = international.stream() + .collect(Collectors.groupingBy( + tuple -> tuple.get(1, Continent.class), + Collectors.groupingBy( + tuple -> tuple.get(0, String.class), + Collectors.mapping(t -> t.get(2, LocalDate.class), Collectors.toList()) + ) + )); + + Map> internationalLogs = groupedInternational.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().entrySet().stream() + .map(e -> VisitedCountryLogResponse.CountryVisits.builder() + .countryName(e.getKey()) + .visitDates(e.getValue()) + .build()) + .collect(Collectors.toList()) + )); + + // 국내 로그 그룹핑 + Map> groupedDomestic = domestic.stream() + .collect(Collectors.groupingBy( + tuple -> tuple.get(0, String.class), + Collectors.mapping(t -> t.get(1, LocalDate.class), Collectors.toList()) + )); + + List domesticLogs = groupedDomestic.entrySet().stream() + .map(e -> VisitedCountryLogResponse.DomesticVisit.builder() + .locationName(e.getKey()) + .visitDates(e.getValue()) + .build()) + .collect(Collectors.toList()); + + int visitedCountryCount = internationalLogs.values().stream().mapToInt(List::size).sum(); + + return VisitedCountryLogResponse.builder() + .userNumber(userNumber) + .visitedCountriesCount(visitedCountryCount) + .internationalLogs(internationalLogs) + .domesticLogs(domesticLogs) + .build(); + + } + + // 방문한 국가 수 계산 + public int calculateVisitedCountryCount(Integer userNumber) { + List visits = visitedCountryLogRepository.findVisitedCountriesWithStartDate(userNumber); + + Set uniqueCountries = visits.stream() + .map(tuple -> tuple.get(0, String.class)) // country_name + .collect(Collectors.toSet()); + + return uniqueCountries.size(); + } + + // 여행 거리 누적 + public void updateTravelDistance(Integer userNumber) { + List visits = visitedCountryLogRepository.findVisitedCountriesWithStartDate(userNumber); + + double baseLat = 37.5665, baseLon = 126.9780; + + double travelDistance = visits.stream() + .filter(tuple -> { + LocalDate endDate = tuple.get(3, LocalDate.class); + return endDate != null && endDate.isBefore(LocalDate.now()); + }) + .map(tuple -> tuple.get(2, Country.class)) + .filter(c -> c.getLatitude() != null && c.getLongitude() != null) + .mapToDouble(c -> DistanceCalculator.calculateDistance(baseLat, baseLon, c.getLatitude(), c.getLongitude())) + .sum(); + + memberService.updateUserTravelDistance(userNumber, travelDistance); + } + + // 전체 유저 대상 여행 거리 누적 + @Scheduled(cron = "0 0 0 * * *") + public void updateAllUsersTravelDistance() { + List userNumbers = visitedCountryLogRepository.findAllUserNumbersWithTravelLog(); + for (Integer userNumber : userNumbers) { + updateTravelDistance(userNumber); + } + } + + +} diff --git a/src/main/java/swyp/swyp6_team7/profile/util/DistanceCalculator.java b/src/main/java/swyp/swyp6_team7/profile/util/DistanceCalculator.java new file mode 100644 index 00000000..70d036a2 --- /dev/null +++ b/src/main/java/swyp/swyp6_team7/profile/util/DistanceCalculator.java @@ -0,0 +1,19 @@ +package swyp.swyp6_team7.profile.util; + +public class DistanceCalculator { + private static final double EARTH_RADIUS_KM = 6371.0; // 지구 반경 (km) + + // Haversine 공식 적용 + public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) { + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return EARTH_RADIUS_KM * c; // 최종 거리 반환 (km) + } +} diff --git a/src/main/java/swyp/swyp6_team7/travel/controller/TravelAppliedController.java b/src/main/java/swyp/swyp6_team7/travel/controller/TravelAppliedController.java deleted file mode 100644 index f8d1854f..00000000 --- a/src/main/java/swyp/swyp6_team7/travel/controller/TravelAppliedController.java +++ /dev/null @@ -1,46 +0,0 @@ -package swyp.swyp6_team7.travel.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; -import swyp.swyp6_team7.global.utils.api.ApiResponse; -import swyp.swyp6_team7.global.utils.auth.RequireUserNumber; -import swyp.swyp6_team7.travel.dto.response.TravelListResponseDto; -import swyp.swyp6_team7.travel.service.TravelAppliedService; - -@RestController -@RequestMapping("/api/my-applied-travels") -@RequiredArgsConstructor -public class TravelAppliedController { - - private final TravelAppliedService travelAppliedService; - - // 사용자가 신청한 여행 목록 조회 - @GetMapping("") - public ApiResponse> getAppliedTrips( - @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "size", defaultValue = "5") int size, - @RequireUserNumber Integer userNumber - - ) { - Pageable pageable = PageRequest.of(page, size); - // 여행 목록 조회 - Page appliedTrips = travelAppliedService.getAppliedTripsByUser(userNumber, pageable); - - return ApiResponse.success(appliedTrips); - } - - // 사용자가 특정 여행에 대한 참가 취소 - @DeleteMapping("/{travelNumber}/cancel") - public ApiResponse cancelTripApplication( - @PathVariable("travelNumber") int travelNumber, - @RequireUserNumber Integer userNumber - ) { - - // 참가 취소 처리 - travelAppliedService.cancelApplication(userNumber, travelNumber); - return ApiResponse.success(null); - } -} diff --git a/src/main/java/swyp/swyp6_team7/travel/controller/TravelListController.java b/src/main/java/swyp/swyp6_team7/travel/controller/TravelListController.java index 59dd5ca6..a931ac3f 100644 --- a/src/main/java/swyp/swyp6_team7/travel/controller/TravelListController.java +++ b/src/main/java/swyp/swyp6_team7/travel/controller/TravelListController.java @@ -4,33 +4,84 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.GetMapping; -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.bind.annotation.*; import swyp.swyp6_team7.global.utils.api.ApiResponse; import swyp.swyp6_team7.global.utils.auth.RequireUserNumber; import swyp.swyp6_team7.travel.dto.response.TravelListResponseDto; +import swyp.swyp6_team7.travel.service.TravelAppliedService; import swyp.swyp6_team7.travel.service.TravelListService; +import swyp.swyp6_team7.travel.service.TravelRequestedService; @RestController @RequiredArgsConstructor -@RequestMapping("/api/my-travels") +@RequestMapping("/api") public class TravelListController { private final TravelListService travelListService; + private final TravelAppliedService travelAppliedService; + private final TravelRequestedService travelRequestedService; - - @GetMapping("") + // 만든 여행 목록 조회 + @GetMapping("/my-travels") public ApiResponse> getMyCreatedTravels( @RequestParam(value = "page", defaultValue = "0") int page, @RequestParam(value = "size", defaultValue = "5") int size, @RequireUserNumber Integer userNumber ) { Pageable pageable = PageRequest.of(page, size); + Page createdTravelList = travelListService.getTravelListByUser(userNumber, pageable); + + return ApiResponse.success(createdTravelList); + } + + // 동행이 맺어진 여행 리스트 + @GetMapping("/my-applied-travels") + public ApiResponse> getAppliedTrips( + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "5") int size, + @RequireUserNumber Integer userNumber + ) { + Pageable pageable = PageRequest.of(page, size); // 여행 목록 조회 - Page travelList = travelListService.getTravelListByUser(userNumber, pageable); + Page appliedTravelList = travelAppliedService.getAppliedTripsByUser(userNumber, pageable); + + return ApiResponse.success(appliedTravelList); + } + + // 사용자가 특정 여행에 대한 참가 취소 + @DeleteMapping("/my-applied-travels/{travelNumber}/cancel") + public ApiResponse cancelTripApplication( + @PathVariable("travelNumber") int travelNumber, + @RequireUserNumber Integer userNumber + ) { + + // 참가 취소 처리 + travelAppliedService.cancelApplication(userNumber, travelNumber); + return ApiResponse.success(null); + } + + //참가 신청한 여행 목록 조회(주최자 수락 대기중) + @GetMapping("/my-requested-travels") + public ApiResponse> getRequestedTrips( + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "5") int size, + @RequireUserNumber Integer userNumber + ) { + Pageable pageable = PageRequest.of(page, size); + // 서비스 호출하여 신청한 여행 목록 조회 + Page travelList = travelRequestedService.getRequestedTripsByUser(userNumber, pageable); return ApiResponse.success(travelList); } + + // 참가 취소 + @DeleteMapping("/my-requested-travels/{travelNumber}/cancel") + public ApiResponse cancelTripApplication( + @RequireUserNumber Integer userNumber, + @PathVariable("travelNumber") int travelNumber + ) { + // 참가 취소 처리 + travelRequestedService.cancelApplication(userNumber, travelNumber); + return ApiResponse.success(null); + } } diff --git a/src/main/java/swyp/swyp6_team7/travel/controller/TravelRequestedController.java b/src/main/java/swyp/swyp6_team7/travel/controller/TravelRequestedController.java deleted file mode 100644 index 9d22a8db..00000000 --- a/src/main/java/swyp/swyp6_team7/travel/controller/TravelRequestedController.java +++ /dev/null @@ -1,43 +0,0 @@ -package swyp.swyp6_team7.travel.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.*; -import swyp.swyp6_team7.global.utils.api.ApiResponse; -import swyp.swyp6_team7.global.utils.auth.RequireUserNumber; -import swyp.swyp6_team7.travel.dto.response.TravelListResponseDto; -import swyp.swyp6_team7.travel.service.TravelRequestedService; - -@RestController -@RequestMapping("/api/my-requested-travels") -@RequiredArgsConstructor -public class TravelRequestedController { - private final TravelRequestedService travelRequestedService; - - //신청한 여행 목록 조회 - @GetMapping("") - public ApiResponse> getRequestedTrips( - @RequestParam(value = "page", defaultValue = "0") int page, - @RequestParam(value = "size", defaultValue = "5") int size, - @RequireUserNumber Integer userNumber - ) { - Pageable pageable = PageRequest.of(page, size); - // 서비스 호출하여 신청한 여행 목록 조회 - Page travelList = travelRequestedService.getRequestedTripsByUser(userNumber, pageable); - - return ApiResponse.success(travelList); - } - - // 참가 취소 - @DeleteMapping("/{travelNumber}/cancel") - public ApiResponse cancelTripApplication( - @RequireUserNumber Integer userNumber, - @PathVariable("travelNumber") int travelNumber - ) { - // 참가 취소 처리 - travelRequestedService.cancelApplication(userNumber, travelNumber); - return ApiResponse.success(null); - } -} diff --git a/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelCreateRequest.java b/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelCreateRequest.java index a3a05632..1cbc01dc 100644 --- a/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelCreateRequest.java +++ b/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelCreateRequest.java @@ -19,6 +19,7 @@ public class TravelCreateRequest { private String locationName; // 여행지 이름 + private String countryName; // 여행지 국가 이름 @NotNull private LocalDate startDate; // 여행 시작 일자 @NotNull diff --git a/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelUpdateRequest.java b/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelUpdateRequest.java index d4c24b23..3c822d47 100644 --- a/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelUpdateRequest.java +++ b/src/main/java/swyp/swyp6_team7/travel/dto/request/TravelUpdateRequest.java @@ -18,6 +18,7 @@ public class TravelUpdateRequest { private String locationName; + private String countryName; @NotNull private LocalDate startDate; @NotNull diff --git a/src/main/java/swyp/swyp6_team7/travel/service/TravelAppliedService.java b/src/main/java/swyp/swyp6_team7/travel/service/TravelAppliedService.java index 66da6649..20d0734b 100644 --- a/src/main/java/swyp/swyp6_team7/travel/service/TravelAppliedService.java +++ b/src/main/java/swyp/swyp6_team7/travel/service/TravelAppliedService.java @@ -74,4 +74,16 @@ public void cancelApplication(Integer userNumber, int travelNumber) { companionRepository.deleteByTravelAndUserNumber(travel, userNumber); } + + @Transactional(readOnly = true) + public int countAppliedTrpsByUser(Integer userNumber) { + // 사용자가 동반자로 등록된 여행 목록 조회 + List companions = companionRepository.findByUserNumber(userNumber); + // 삭제된 여행은 제외한 실제 참가한 여행 수 계산 + int count = (int) companions.stream() + .map(Companion::getTravel) + .filter(travel -> travel.getStatus() != TravelStatus.DELETED) + .count(); + return count; + } } diff --git a/src/main/java/swyp/swyp6_team7/travel/service/TravelListService.java b/src/main/java/swyp/swyp6_team7/travel/service/TravelListService.java index c1ed1704..01e220d4 100644 --- a/src/main/java/swyp/swyp6_team7/travel/service/TravelListService.java +++ b/src/main/java/swyp/swyp6_team7/travel/service/TravelListService.java @@ -86,4 +86,14 @@ private Page toPage(List dtos, Pag } return new PageImpl<>(dtos.subList(start, end), pageable, dtos.size()); } + + // 사용자가 만든 여행(삭제되지 않은)의 총 개수를 반환하는 메서드 + @Transactional(readOnly = true) + public int countCreatedTravelsByUser(Integer userNumber) { + // 여행 리스트 조회 시, 삭제되지 않은 여행만 카운트 + List travels = travelRepository.findByUserNumber(userNumber).stream() + .filter(travel -> travel.getStatus() != TravelStatus.DELETED) + .collect(Collectors.toList()); + return travels.size(); + } } diff --git a/src/main/java/swyp/swyp6_team7/travel/service/TravelService.java b/src/main/java/swyp/swyp6_team7/travel/service/TravelService.java index 365c61df..6b5589ca 100644 --- a/src/main/java/swyp/swyp6_team7/travel/service/TravelService.java +++ b/src/main/java/swyp/swyp6_team7/travel/service/TravelService.java @@ -11,9 +11,13 @@ import swyp.swyp6_team7.global.exception.MoingApplicationException; import swyp.swyp6_team7.image.repository.ImageRepository; import swyp.swyp6_team7.image.service.ImageProfileService; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; +import swyp.swyp6_team7.location.service.CountryService; import swyp.swyp6_team7.plan.service.PlanService; import swyp.swyp6_team7.tag.domain.Tag; import swyp.swyp6_team7.tag.domain.TravelTag; @@ -52,6 +56,7 @@ public class TravelService { private final ImageRepository imageRepository; private final LocationRepository locationRepository; private final CommentRepository commentRepository; + private final CountryService countryService; private static String defaultProfileImageUrl; @@ -71,8 +76,10 @@ public Travel create(TravelCreateRequest request, int loginUserNumber) { } try { - Location location = getLocation(request.getLocationName()); + + Location location = getLocation(request.getLocationName(), request.getCountryName()); log.info("Location: {}", location.getLocationName()); + List tags = getTags(request.getTags()); Travel createdTravel = travelRepository.save( @@ -99,16 +106,29 @@ private List getTags(List tagNames) { .toList(); } + // TODO: Location 디렉토리로 이동? - private Location getLocation(String locationName) { + private Location getLocation(String locationName, String countryName) { + Country country = countryService.InsertCountry(countryName, Continent.ASIA); + + LocationType locationType; + if (countryName == null) { + locationType = LocationType.UNKNOWN; + } else if ("대한민국".equals(countryName)) { + locationType = LocationType.DOMESTIC; + } else { + locationType = LocationType.INTERNATIONAL; + } + Location location = locationRepository.findByLocationName(locationName) .orElseGet(() -> { - // Location 정보가 없으면 새로운 Location 추가 (locationType은 UNKNOWN으로 설정) Location newLocation = Location.builder() .locationName(locationName) - .locationType(LocationType.UNKNOWN) // UNKNOWN으로 설정 + .locationType(locationType) + .country(country) .build(); - log.info("Location 생성: locationName={}", newLocation.getLocationName()); + log.info("Location 생성: locationName={}, countryName={}, locationType={}", + newLocation.getLocationName(), countryName, locationType.name()); return locationRepository.save(newLocation); }); return location; @@ -203,7 +223,7 @@ public Travel update(int travelNumber, TravelUpdateRequest request, int requestU throw new MoingApplicationException("잘못된 여행 일정 개수입니다."); } - Location location = getLocation(request.getLocationName()); + Location location = getLocation(request.getLocationName(), request.getCountryName()); List requestTagsName = request.getTags().stream() .distinct().limit(TRAVEL_TAG_MAX_COUNT) .toList(); diff --git a/src/main/resources/cities/countries.csv b/src/main/resources/cities/countries.csv new file mode 100644 index 00000000..37c167c0 --- /dev/null +++ b/src/main/resources/cities/countries.csv @@ -0,0 +1,184 @@ +대한민국,37.5665,126.9780,아시아 +일본,35.6762,139.6503,아시아 +중국,39.9042,116.4074,아시아 +미국,38.8951,-77.0364,북아메리카 +캐나다,45.4215,-75.6972,북아메리카 +멕시코,19.4326,-99.1332,북아메리카 +영국,51.5074,-0.1278,유럽 +프랑스,48.8566,2.3522,유럽 +독일,52.5200,13.4050,유럽 +이탈리아,41.9028,12.4964,유럽 +스페인,40.4168,-3.7038,유럽 +호주,-35.2809,149.1300,오세아니아 +뉴질랜드,-41.2865,174.7762,오세아니아 +브라질,-15.7801,-47.9292,남아메리카 +아르헨티나,-34.6037,-58.3816,남아메리카 +칠레,-33.4489,-70.6693,남아메리카 +이집트,30.0444,31.2357,아프리카 +남아프리카공화국,-25.7461,28.1881,아프리카 +케냐,-1.2921,36.8219,아프리카 +나이지리아,9.0765,7.3986,아프리카 +인도,28.6139,77.2090,아시아 +태국,13.7563,100.5018,아시아 +베트남,21.0285,105.8542,아시아 +싱가포르,1.3521,103.8198,아시아 +말레이시아,3.1390,101.6869,아시아 +인도네시아,-6.2088,106.8456,아시아 +러시아,55.7558,37.6173,아시아 +터키,39.9334,32.8597,유럽 +아랍에미리트,24.4539,54.3773,아시아 +사우디아라비아,24.7136,46.6753,아시아 +모로코,34.0209,-6.8416,아프리카 +스위스,46.9480,7.4474,유럽 +네덜란드,52.3676,4.9041,유럽 +스웨덴,59.3293,18.0686,유럽 +노르웨이,59.9139,10.7522,유럽 +덴마크,55.6761,12.5683,유럽 +핀란드,60.1699,24.9384,유럽 +벨기에,50.8503,4.3517,유럽 +포르투갈,38.7223,-9.1393,유럽 +그리스,37.9838,23.7275,유럽 +오스트리아,48.2082,16.3738,유럽 +폴란드,52.2297,21.0122,유럽 +체코,50.0755,14.4378,유럽 +헝가리,47.4979,19.0402,유럽 +크로아티아,45.8150,15.9819,유럽 +페루,-12.0464,-77.0428,남아메리카 +콜롬비아,4.7110,-74.0721,남아메리카 +필리핀,14.5995,120.9842,아시아 +우크라이나,50.4501,30.5234,유럽 +이란,35.6892,51.3890,아시아 +이라크,33.3152,44.3661,아시아 +아프가니스탄,34.5253,69.1783,아시아 +파키스탄,33.7294,73.0931,아시아 +방글라데시,23.8103,90.4125,아시아 +스리랑카,6.9271,79.8612,아시아 +미얀마,19.7633,96.0785,아시아 +몽골,47.8864,106.9057,아시아 +카자흐스탄,51.1694,71.4491,아시아 +우즈베키스탄,41.3775,69.2949,아시아 +아일랜드,53.3498,-6.2603,유럽 +바티칸,41.9029,12.4534,유럽 +룩셈부르크,49.6116,6.1319,유럽 +에스토니아,59.4370,24.7536,유럽 +라트비아,56.9496,24.1052,유럽 +리투아니아,54.6872,25.2797,유럽 +알제리,36.7372,3.0863,아프리카 +튀니지,36.8065,10.1815,아프리카 +리비아,32.8872,13.1913,아프리카 +세네갈,14.7645,-17.3660,아프리카 +가나,5.6037,-0.1870,아프리카 +에티오피아,9.0320,38.7469,아프리카 +소말리아,2.0469,45.3182,아프리카 +콩고민주공화국,-4.4419,15.2663,아프리카 +우간다,0.3476,32.5825,아프리카 +탄자니아,-6.7924,39.2083,아프리카 +볼리비아,-16.4897,-68.1193,남아메리카 +베네수엘라,10.4806,-66.9036,남아메리카 +에콰도르,-0.1807,-78.4678,남아메리카 +파라과이,-25.2637,-57.5759,남아메리카 +우루과이,-34.9011,-56.1915,남아메리카 +쿠바,23.1136,-82.3666,북아메리카 +자메이카,18.0179,-76.8099,북아메리카 +코스타리카,9.9281,-84.0907,북아메리카 +파나마,8.9936,-79.5197,북아메리카 +뉴칼레도니아,-22.2711,166.4572,오세아니아 +피지,-18.1248,178.4501,오세아니아 +솔로몬제도,-9.6457,160.1562,오세아니아 +파푸아뉴기니,-9.4438,147.1803,오세아니아 +바누아투,-17.7334,168.3272,오세아니아 +몰디브,4.1755,73.5093,아시아 +캄보디아,11.5564,104.9282,아시아 +라오스,17.9757,102.6331,아시아 +네팔,27.7172,85.3240,아시아 +부탄,27.4728,89.6390,아시아 +마카오,22.1987,113.5439,아시아 +홍콩,22.3193,114.1694,아시아 +타이완,25.0330,121.5654,아시아 +브루나이,4.5353,114.7277,아시아 +동티모르,-8.5568,125.5603,아시아 +바레인,26.0667,50.5577,아시아 +카타르,25.2854,51.5310,아시아 +쿠웨이트,29.3759,47.9774,아시아 +오만,23.5880,58.3829,아시아 +요르단,31.9454,35.9284,아시아 +레바논,33.8938,35.5018,아시아 +이스라엘,31.7683,35.2137,아시아 +키프로스,35.1856,33.3823,유럽 +조지아,41.7151,44.8271,유럽 +아르메니아,40.1792,44.4991,아시아 +아제르바이잔,40.4093,49.8671,아시아 +튀르키예,39.9334,32.8597,유럽 +몰타,35.9375,14.3754,유럽 +산마리노,43.9424,12.4578,유럽 +안도라,42.5063,1.5218,유럽 +리히텐슈타인,47.1410,9.5209,유럽 +모나코,43.7384,7.4246,유럽 +세르비아,44.7866,20.4489,유럽 +몬테네그로,42.4304,19.2594,유럽 +보스니아 헤르체고비나,43.8563,18.4131,유럽 +알바니아,41.3275,19.8187,유럽 +북마케도니아,41.9981,21.4254,유럽 +불가리아,42.6977,23.3219,유럽 +루마니아,44.4268,26.1025,유럽 +몰도바,47.0105,28.8638,유럽 +슬로바키아,48.1486,17.1077,유럽 +슬로베니아,46.0569,14.5058,유럽 +벨라루스,53.9045,27.5615,유럽 +아이슬란드,64.1466,-21.9426,유럽 +모리셔스,-20.3484,57.5522,아프리카 +세이셸,-4.6796,55.4920,아프리카 +마다가스카르,-18.8792,47.5079,아프리카 +잠비아,-15.3875,28.3228,아프리카 +짐바브웨,-17.8252,31.0335,아프리카 +보츠와나,-24.6282,25.9231,아프리카 +나미비아,-22.5609,17.0658,아프리카 +모잠비크,-25.9692,32.5732,아프리카 +르완다,-1.9403,29.8739,아프리카 +부룬디,-3.3614,29.3599,아프리카 +가봉,0.4162,9.4673,아프리카 +카메룬,3.8480,11.5021,아프리카 +토고,6.1375,1.2123,아프리카 +베냉,6.4969,2.6283,아프리카 +코트디부아르,6.8276,-5.2893,아프리카 +말리,12.6392,-8.0029,아프리카 +부르키나파소,12.3714,-1.5197,아프리카 +니제르,13.5137,2.1098,아프리카 +차드,12.1348,15.0557,아프리카 +수단,15.5007,32.5599,아프리카 +남수단,4.8594,31.5713,아프리카 +에리트레아,15.3229,38.9251,아프리카 +소말릴랜드,9.5582,44.0405,아프리카 +기니,9.9456,-9.6966,아프리카 +시에라리온,8.4606,-13.2317,아프리카 +라이베리아,6.3004,-10.7969,아프리카 +기니비사우,11.8636,-15.5978,아프리카 +감비아,13.4549,-16.5790,아프리카 +과테말라,14.6349,-90.5069,북아메리카 +벨리즈,17.2514,-88.7690,북아메리카 +엘살바도르,13.6929,-89.2182,북아메리카 +온두라스,14.0723,-87.1921,북아메리카 +니카라과,12.1328,-86.2504,북아메리카 +바하마,25.0343,-77.3963,북아메리카 +도미니카공화국,18.4861,-69.9312,북아메리카 +아이티,18.5944,-72.3074,북아메리카 +트리니다드 토바고,10.6918,-61.2225,북아메리카 +바베이도스,13.1939,-59.5432,북아메리카 +그레나다,12.1165,-61.6790,북아메리카 +세인트루시아,13.9094,-60.9789,북아메리카 +도미니카,15.4150,-61.3710,북아메리카 +앤티가 바부다,17.1274,-61.8468,북아메리카 +세인트키츠 네비스,17.3578,-62.7830,북아메리카 +수리남,5.8520,-55.2038,남아메리카 +가이아나,6.8013,-58.1553,남아메리카 +프랑스령 기아나,4.9224,-52.3269,남아메리카 +키리바시,-0.4863,174.9548,오세아니아 +통가,-21.1790,-175.1982,오세아니아 +사모아,-13.7590,-172.1046,오세아니아 +팔라우,7.5150,134.5825,오세아니아 +미크로네시아,6.9248,158.1618,오세아니아 +마셜 제도,7.1315,171.1845,오세아니아 +나우루,-0.5228,166.9315,오세아니아 +투발루,-8.5211,179.1985,오세아니아 +프랑스령 폴리네시아,-17.6797,-149.4068,오세아니아 +쿡 제도,-21.2367,-159.7777,오세아니아 \ No newline at end of file diff --git a/src/main/resources/cities/foreign_cities.csv b/src/main/resources/cities/foreign_cities.csv index c1dfcde0..c2f117f5 100644 --- a/src/main/resources/cities/foreign_cities.csv +++ b/src/main/resources/cities/foreign_cities.csv @@ -49,7 +49,7 @@ 85,북아메리카,미국,애슐랜드,Ashland 86,북아메리카,미국,애틀란타,Atlanta 87,북아메리카,미국,어바인,Irvine -88,북아메리카,미국,"워싱턴, D.C.","Washington, D.C." +88,북아메리카,미국,워싱턴, D.C.","Washington, D.C." 89,북아메리카,미국,콜럼버스,Columbus 90,북아메리카,미국,포틀랜드,Portland 91,북아메리카,미국,피츠버그,Pittsburgh @@ -60,7 +60,7 @@ 95,남아메리카,베네수엘라,카라카스,Caracas 96,동남아시아,베트남,하노이,Hanoi 97,유럽,벨기에,브뤼헤,Brugge -98,유럽,벨기에,앤트워프,"Antwerp(Antwerpen, Anvers)" +98,유럽,벨기에,앤트워프,Antwerp(Antwerpen, Anvers)" 43,남아메리카,볼리비아,라파스,La Paz 41,남아메리카,브라질,브라질리아,brasilia 99,남아메리카,브라질,꾸리찌바,Curitiba @@ -156,7 +156,6 @@ 164,동아프리카,케냐,나이로비,Nairobi 10,남아메리카,콜롬비아,카르타헤나,Cartagena 35,북아프리카,쿠바,아바나(하바나),La habana -165,북아프리카,쿠바,아바나(하바나),La habana 1,유럽,크로아티아,자그레브,Zagreb 19,동아프리카,탄자니아,다르에스살람,Dar es Salaam 4,동남아시아,태국,방콕,Bangkok diff --git a/src/test/java/swyp/swyp6_team7/companion/repository/CompanionCustomRepositoryImplTest.java b/src/test/java/swyp/swyp6_team7/companion/repository/CompanionCustomRepositoryImplTest.java index 4ba3649c..2b45d62f 100644 --- a/src/test/java/swyp/swyp6_team7/companion/repository/CompanionCustomRepositoryImplTest.java +++ b/src/test/java/swyp/swyp6_team7/companion/repository/CompanionCustomRepositoryImplTest.java @@ -10,8 +10,11 @@ import swyp.swyp6_team7.config.DataConfig; import swyp.swyp6_team7.image.domain.Image; import swyp.swyp6_team7.image.repository.ImageRepository; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.member.entity.AgeGroup; import swyp.swyp6_team7.member.entity.Gender; @@ -46,6 +49,9 @@ class CompanionCustomRepositoryImplTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @Autowired private TravelRepository travelRepository; @@ -61,7 +67,7 @@ void findCompanionInfoByTravelNumber() { Image image2 = createImage(user2.getUserNumber(), "user2-profile-image"); imageRepository.saveAll(List.of(image1, image2)); - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save(createTravel(location)); Companion companion1 = createCompanion(travel, user1.getUserNumber()); @@ -88,10 +94,18 @@ private Companion createCompanion(Travel travel, int userNumber) { .build(); } - private Location createLocation() { + private Country createCountry(){ + return countryRepository.save(Country.builder() + .countryName("대한민국") + .continent(Continent.ASIA) + .build()); + } + + private Location createLocation(Country country) { return Location.builder() .locationName("Seoul") .locationType(LocationType.DOMESTIC) + .country(country) .build(); } diff --git a/src/test/java/swyp/swyp6_team7/companion/repository/CompanionRepositoryTest.java b/src/test/java/swyp/swyp6_team7/companion/repository/CompanionRepositoryTest.java index d3b7f025..7cd119cd 100644 --- a/src/test/java/swyp/swyp6_team7/companion/repository/CompanionRepositoryTest.java +++ b/src/test/java/swyp/swyp6_team7/companion/repository/CompanionRepositoryTest.java @@ -7,8 +7,11 @@ import org.springframework.context.annotation.Import; import swyp.swyp6_team7.companion.domain.Companion; import swyp.swyp6_team7.config.DataConfig; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.travel.domain.GenderType; import swyp.swyp6_team7.travel.domain.PeriodType; @@ -31,6 +34,9 @@ class CompanionRepositoryTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @Autowired private TravelRepository travelRepository; @@ -39,7 +45,7 @@ class CompanionRepositoryTest { @Test void findUserNumberByTravelNumber() { // given - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save(createTravel(location)); Companion companion = companionRepository.save(createCompanion(travel, 1)); Companion companion2 = companionRepository.save(createCompanion(travel, 5)); @@ -51,11 +57,18 @@ void findUserNumberByTravelNumber() { assertThat(companionUserNumbers).hasSize(2) .contains(1, 5); } + private Country createCountry(){ + return countryRepository.save(Country.builder() + .countryName("대한민국") + .continent(Continent.ASIA) + .build()); + } - private Location createLocation() { + private Location createLocation(Country country) { return Location.builder() .locationName("Seoul") .locationType(LocationType.DOMESTIC) + .country(country) .build(); } diff --git a/src/test/java/swyp/swyp6_team7/companion/service/CompanionServiceTest.java b/src/test/java/swyp/swyp6_team7/companion/service/CompanionServiceTest.java index 1ad6e505..7b4ed965 100644 --- a/src/test/java/swyp/swyp6_team7/companion/service/CompanionServiceTest.java +++ b/src/test/java/swyp/swyp6_team7/companion/service/CompanionServiceTest.java @@ -8,8 +8,11 @@ import swyp.swyp6_team7.companion.domain.Companion; import swyp.swyp6_team7.companion.dto.CompanionInfoDto; import swyp.swyp6_team7.companion.repository.CompanionRepository; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.member.entity.AgeGroup; import swyp.swyp6_team7.member.entity.Gender; @@ -44,6 +47,9 @@ class CompanionServiceTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @Autowired private TravelRepository travelRepository; @Autowired @@ -66,7 +72,7 @@ public void findCompanionInfoByTravelNumber() { Users user1 = userRepository.save(createUser("user1", AgeGroup.TEEN)); Users user2 = userRepository.save(createUser("user2", AgeGroup.TWENTY)); - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save(createTravel(location)); Companion companion1 = createCompanion(travel, user1.getUserNumber()); @@ -92,10 +98,18 @@ private Companion createCompanion(Travel travel, int userNumber) { .build(); } - private Location createLocation() { + private Country createCountry(){ + return countryRepository.save(Country.builder() + .countryName("대한민국") + .continent(Continent.ASIA) + .build()); + } + + private Location createLocation(Country country) { return Location.builder() .locationName("Seoul") .locationType(LocationType.DOMESTIC) + .country(country) .build(); } diff --git a/src/test/java/swyp/swyp6_team7/enrollment/repository/EnrollmentCustomRepositoryImplTest.java b/src/test/java/swyp/swyp6_team7/enrollment/repository/EnrollmentCustomRepositoryImplTest.java index aca93c1b..b5169ec8 100644 --- a/src/test/java/swyp/swyp6_team7/enrollment/repository/EnrollmentCustomRepositoryImplTest.java +++ b/src/test/java/swyp/swyp6_team7/enrollment/repository/EnrollmentCustomRepositoryImplTest.java @@ -12,8 +12,11 @@ import swyp.swyp6_team7.enrollment.dto.EnrollmentResponse; import swyp.swyp6_team7.image.domain.Image; import swyp.swyp6_team7.image.repository.ImageRepository; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.member.entity.AgeGroup; import swyp.swyp6_team7.member.entity.Gender; @@ -49,6 +52,9 @@ class EnrollmentCustomRepositoryImplTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @Autowired private ImageRepository imageRepository; @@ -65,7 +71,7 @@ void findEnrollmentsByTravelNumber() { createImage(user1.getUserNumber(), "user1-profile-image") ); - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save( createTravel(3, 2, location, TravelStatus.IN_PROGRESS) ); @@ -94,7 +100,7 @@ void findEnrollmentsByTravelNumberOnlyPendingStatus() { Users user1 = userRepository.save(createUser("user1")); Users user2 = userRepository.save(createUser("user2")); - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save( createTravel(3, 2, location, TravelStatus.IN_PROGRESS) ); @@ -122,7 +128,7 @@ void findUserNumbersByTravelNumberAndStatus() { Users user2 = userRepository.save(createUser("user2")); Users user3 = userRepository.save(createUser("user3")); - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save( createTravel(3, 2, location, TravelStatus.IN_PROGRESS) ); @@ -145,7 +151,7 @@ void findUserNumbersByTravelNumberAndStatus() { void findPendingEnrollmentNumberByTravelNumberAndUserNumber() { // given Users user = userRepository.save(createUser("user1")); - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel = travelRepository.save( createTravel(3, 2, location, TravelStatus.IN_PROGRESS) ); @@ -178,11 +184,18 @@ private Image createImage(int userNumber, String url) { .url(url) .build(); } + private Country createCountry(){ + return countryRepository.save(Country.builder() + .countryName("대한민국") + .continent(Continent.ASIA) + .build()); + } - private Location createLocation() { + private Location createLocation(Country country) { return Location.builder() .locationName("Seoul") .locationType(LocationType.DOMESTIC) + .country(country) .build(); } diff --git a/src/test/java/swyp/swyp6_team7/global/IntegrationTest.java b/src/test/java/swyp/swyp6_team7/global/IntegrationTest.java index 193b5ca9..3ee26259 100644 --- a/src/test/java/swyp/swyp6_team7/global/IntegrationTest.java +++ b/src/test/java/swyp/swyp6_team7/global/IntegrationTest.java @@ -16,8 +16,11 @@ import swyp.swyp6_team7.auth.dto.LoginTokenResponse; import swyp.swyp6_team7.auth.service.LoginFacade; import swyp.swyp6_team7.config.RedisContainerConfig; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.member.dto.UserRequestDto; import swyp.swyp6_team7.member.entity.AgeGroup; @@ -75,6 +78,9 @@ public class IntegrationTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + protected Users createUser( String userName, String password @@ -121,6 +127,15 @@ protected LoginTokenResponse login( LoginTokenResponse tokenResponse = loginFacade.login(loginRequestDto); return tokenResponse; } + protected Country getOrCreateCountry(String countryName) { + return countryRepository.findByCountryName(countryName) + .orElseGet(() -> countryRepository.save( + Country.builder() + .countryName(countryName) + .continent(Continent.ASIA) + .build() + )); + } protected Location createLocation(String locationName) { Optional locationPrev = locationRepository.findByLocationName(locationName); @@ -128,9 +143,12 @@ protected Location createLocation(String locationName) { return locationPrev.get(); } + Country country = getOrCreateCountry("countryName"); + Location newLocation = Location.builder() .locationName(locationName) .locationType(LocationType.UNKNOWN) // UNKNOWN으로 설정 + .country(country) .build(); return locationRepository.save(newLocation); diff --git a/src/test/java/swyp/swyp6_team7/tag/service/TravelTagServiceTest.java b/src/test/java/swyp/swyp6_team7/tag/service/TravelTagServiceTest.java index 3747f1eb..148176e1 100644 --- a/src/test/java/swyp/swyp6_team7/tag/service/TravelTagServiceTest.java +++ b/src/test/java/swyp/swyp6_team7/tag/service/TravelTagServiceTest.java @@ -5,8 +5,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.tag.domain.Tag; import swyp.swyp6_team7.tag.domain.TravelTag; @@ -45,12 +48,16 @@ class TravelTagServiceTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @AfterEach void tearDown() { travelTagRepository.deleteAllInBatch(); travelRepository.deleteAllInBatch(); tagRepository.deleteAllInBatch(); locationRepository.deleteAllInBatch(); + countryRepository.deleteAllInBatch(); } @DisplayName("update: 여행과 태그 이름 리스트가 주어지면, 변경된 TravelTag 리스트를 얻을 수 있다.") @@ -95,11 +102,22 @@ void updateWithNoLongerNeedTravelTag() { .containsExactlyInAnyOrder("자연", "쇼핑"); } + private Country getOrCreateCountry(String countryName) { + return countryRepository.findByCountryName(countryName) + .orElseGet(() -> countryRepository.save( + Country.builder() + .countryName(countryName) + .continent(Continent.ASIA) + .build() + )); + } private Travel createTravel(List tags) { + Country country = getOrCreateCountry("국가명"); Location location = locationRepository.save(Location.builder() .locationName("여행지명") .locationType(LocationType.DOMESTIC) + .country(country) .build()); return Travel.builder() diff --git a/src/test/java/swyp/swyp6_team7/travel/repository/TravelCustomRepositoryImplTest.java b/src/test/java/swyp/swyp6_team7/travel/repository/TravelCustomRepositoryImplTest.java index de954966..b1a270d6 100644 --- a/src/test/java/swyp/swyp6_team7/travel/repository/TravelCustomRepositoryImplTest.java +++ b/src/test/java/swyp/swyp6_team7/travel/repository/TravelCustomRepositoryImplTest.java @@ -10,8 +10,11 @@ import swyp.swyp6_team7.bookmark.entity.Bookmark; import swyp.swyp6_team7.bookmark.repository.BookmarkRepository; import swyp.swyp6_team7.config.DataConfig; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.member.entity.AgeGroup; import swyp.swyp6_team7.member.entity.Gender; @@ -57,6 +60,8 @@ class TravelCustomRepositoryImplTest { @Autowired private LocationRepository locationRepository; @Autowired + private CountryRepository countryRepository; + @Autowired private BookmarkRepository bookmarkRepository; @@ -70,7 +75,8 @@ public void getDetailsByNumber() { Tag tag2 = Tag.of("자연"); List tags = tagRepository.saveAll(Arrays.asList(tag1, tag2)); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( host.getUserNumber(), location, "여행", 0, 2, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, tags); @@ -90,7 +96,8 @@ public void getDetailsByNumber() { public void findAllSortedByCreatedAt() { // given Users host = userRepository.save(createHostUser()); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( host.getUserNumber(), location, "여행", 0, 2, GenderType.MIXED, @@ -125,7 +132,8 @@ public void findAllByPreferredTags() { Tag tag4 = Tag.of("즉흥"); tagRepository.saveAll(List.of(tag1, tag2, tag3, tag4)); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, Arrays.asList(tag1)); @@ -168,7 +176,8 @@ public void findAllByPreferredTagsWithDate() { Tag tag4 = Tag.of("즉흥"); tagRepository.saveAll(List.of(tag1, tag2, tag3, tag4)); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); LocalDate startDate1 = LocalDate.of(2024, 11, 22); LocalDate endDate = LocalDate.of(2024, 11, 28); @@ -209,7 +218,8 @@ public void findAllByPreferredTagsWithDate() { void findAllByBookmarkNumberAndTitle() { // given Users host = userRepository.save(createHostUser()); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( host.getUserNumber(), location, "여행1", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -246,7 +256,8 @@ void findAllByBookmarkNumberAndTitle() { void findAllByBookmarkNumberAndTitleWhenSameBookmarkNumber() { // given Users host = userRepository.save(createHostUser()); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( host.getUserNumber(), location, "여행갸", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -282,7 +293,8 @@ void findAllByBookmarkNumberAndTitleWhenSameBookmarkNumber() { void findAllByBookmarkNumberAndTitleWithDate() { // given Users host = userRepository.save(createHostUser()); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); LocalDate startDate1 = LocalDate.of(2024, 11, 22); LocalDate endDate = LocalDate.of(2024, 11, 28); @@ -321,7 +333,8 @@ void findAllByBookmarkNumberAndTitleWithDate() { @Test public void searchWithKeyword() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "Seoul 여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -348,8 +361,10 @@ public void searchWithKeyword() { @Test public void searchWithKeywordThroughTitleAndLocation() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); - Location location2 = locationRepository.save(createLocation("Busan", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); + Location location2 = locationRepository.save(createLocation("Busan", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행1", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -377,7 +392,8 @@ public void searchWithKeywordThroughTitleAndLocation() { @Test public void searchWithoutKeyword() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "Seoul 여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -401,7 +417,8 @@ public void searchWithoutKeyword() { @Test public void searchOnlyActivated() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, DELETED, new ArrayList<>()); @@ -434,7 +451,8 @@ public void searchWithTags() { Tag tag2 = Tag.of("자연"); tagRepository.saveAll(Arrays.asList(tag1, tag2)); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, Arrays.asList(tag1, tag2)); @@ -462,7 +480,8 @@ public void searchWithTags() { @Test public void searchWithGenderFilter() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -492,7 +511,8 @@ public void searchWithGenderFilter() { @Test public void searchWithPeriodFilter() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.MORE_THAN_MONTH, IN_PROGRESS, new ArrayList<>()); @@ -523,7 +543,8 @@ public void searchWithPeriodFilter() { @Test public void searchWithPersonRangeFilter() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 2, GenderType.MIXED, PeriodType.MORE_THAN_MONTH, IN_PROGRESS, new ArrayList<>()); @@ -556,9 +577,11 @@ public void searchWithPersonRangeFilter() { @DisplayName("search: 국내 카테고리(DOMESTIC)에 해당하는 여행을 검색할 수 있다.") @Test public void searchDomesticTravelWithLocationFilter() { - // given - Location domesticLocation = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); - Location internationalLocation = locationRepository.save(createLocation("London", LocationType.INTERNATIONAL)); + Country korea = createCountry("대한민국",Continent.ASIA); + Country england = createCountry("영국",Continent.EUROPE); + + Location domesticLocation = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,korea)); + Location internationalLocation = locationRepository.save(createLocation("London", LocationType.INTERNATIONAL,england)); LocalDate dueDate = LocalDate.of(2024, 11, 7); Travel travel1 = createTravel( @@ -590,8 +613,11 @@ public void searchDomesticTravelWithLocationFilter() { @Test public void searchInternationalTravelWithLocationFilter() { // given - Location domesticLocation = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); - Location internationalLocation = locationRepository.save(createLocation("London", LocationType.INTERNATIONAL)); + Country korea = createCountry("대한민국",Continent.ASIA); + Country england = createCountry("영국",Continent.EUROPE); + + Location domesticLocation = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, korea)); + Location internationalLocation = locationRepository.save(createLocation("London", LocationType.INTERNATIONAL, england)); Travel travel1 = createTravel( 1, domesticLocation, "여행", 0, 0, GenderType.MIXED, @@ -622,7 +648,8 @@ public void searchInternationalTravelWithLocationFilter() { @Test public void searchWithoutSortingType() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -651,7 +678,9 @@ public void searchWithoutSortingType() { @Test public void searchWithRecommendSorting() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -687,7 +716,8 @@ public void searchWithRecommendSorting() { @Test void searchWithRecommendSorting2() { // given - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Country country = createCountry("대한민국",Continent.ASIA); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC,country)); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, new ArrayList<>()); @@ -732,10 +762,18 @@ private Users createHostUser() { .build(); } - private Location createLocation(String locationName, LocationType locationType) { + private Country createCountry(String name, Continent continent){ + return countryRepository.save(Country.builder() + .countryName(name) + .continent(continent) + .build()); + } + + private Location createLocation(String locationName, LocationType locationType, Country country) { return Location.builder() .locationName(locationName) .locationType(locationType) + .country(country) .build(); } diff --git a/src/test/java/swyp/swyp6_team7/travel/repository/TravelRepositoryTest.java b/src/test/java/swyp/swyp6_team7/travel/repository/TravelRepositoryTest.java index e1e8016a..1ee2c02d 100644 --- a/src/test/java/swyp/swyp6_team7/travel/repository/TravelRepositoryTest.java +++ b/src/test/java/swyp/swyp6_team7/travel/repository/TravelRepositoryTest.java @@ -6,8 +6,11 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; import swyp.swyp6_team7.config.DataConfig; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.travel.domain.GenderType; import swyp.swyp6_team7.travel.domain.PeriodType; @@ -29,6 +32,9 @@ class TravelRepositoryTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @DisplayName("TravelNumber가 주어질 때, 해당 Travel의 enrollmentLastViewedAt를 가져올 수 있다.") @Test @@ -63,11 +69,22 @@ void updateEnrollmentsLastViewedAtByNumber() { .contains(LocalDateTime.of(2024, 11, 8, 15, 0)); } + private Country getOrCreateCountry(String countryName) { + return countryRepository.findByCountryName(countryName) + .orElseGet(() -> countryRepository.save( + Country.builder() + .countryName(countryName) + .continent(Continent.ASIA) + .build() + )); + } private Travel createTravel(int nowViewCount, LocalDateTime enrollmentLastViewdAt) { + Country country = getOrCreateCountry("국가명"); Location location = locationRepository.save(Location.builder() .locationName("여행지명") .locationType(LocationType.DOMESTIC) + .country(country) .build()); return Travel.builder() diff --git a/src/test/java/swyp/swyp6_team7/travel/service/TravelHomeServiceTest.java b/src/test/java/swyp/swyp6_team7/travel/service/TravelHomeServiceTest.java index d9db0df1..c2ac178d 100644 --- a/src/test/java/swyp/swyp6_team7/travel/service/TravelHomeServiceTest.java +++ b/src/test/java/swyp/swyp6_team7/travel/service/TravelHomeServiceTest.java @@ -10,8 +10,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import swyp.swyp6_team7.bookmark.service.BookmarkService; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.tag.domain.Tag; import swyp.swyp6_team7.tag.repository.TagRepository; @@ -59,6 +62,9 @@ class TravelHomeServiceTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @MockBean private UserTagPreferenceRepository userTagPreferenceRepository; @@ -74,6 +80,7 @@ void tearDown() { travelRepository.deleteAllInBatch(); tagRepository.deleteAllInBatch(); locationRepository.deleteAllInBatch(); + countryRepository.deleteAllInBatch(); } @DisplayName("최근 생성된 순서로 정렬된 여행 목록을 가져오고 로그인 상태라면 사용자의 북마크 여부를 추가로 설정한다.") @@ -81,7 +88,7 @@ void tearDown() { void getTravelsSortedByCreatedAt() { // given Integer loginUserNumber = 1; - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, createCountry())); Travel travel1 = travelRepository.save(createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, Arrays.asList())); @@ -113,7 +120,7 @@ void getTravelsSortedByCreatedAt() { void getTravelsSortedByCreatedAtWhenNotLogin() { // given Integer loginUserNumber = null; - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, createCountry())); Travel travel1 = travelRepository.save(createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, Arrays.asList())); @@ -143,7 +150,7 @@ void getRecommendTravelsByMember() { Tag tag4 = Tag.of("즉흥"); tagRepository.saveAll(List.of(tag1, tag2, tag3, tag4)); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, createCountry())); Travel travel1 = createTravel( 1, location, "여행", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, Arrays.asList(tag1)); @@ -179,7 +186,7 @@ void getRecommendTravelsByMemberWhenSamePreferredNumberSameRegisterDue() { Tag tag2 = Tag.of("자연"); tagRepository.saveAll(List.of(tag1, tag2)); - Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC)); + Location location = locationRepository.save(createLocation("Seoul", LocationType.DOMESTIC, createCountry())); Travel travel1 = createTravel( 1, location, "여행-나", 0, 0, GenderType.MIXED, PeriodType.ONE_WEEK, IN_PROGRESS, Arrays.asList(tag1, tag2)); @@ -203,11 +210,18 @@ void getRecommendTravelsByMemberWhenSamePreferredNumberSameRegisterDue() { tuple(travel1.getNumber(), Arrays.asList("쇼핑", "자연"), 2, "여행-나") ); } + private Country createCountry(){ + return countryRepository.save(Country.builder() + .countryName("대한민국") + .continent(Continent.ASIA) + .build()); + } - private Location createLocation(String locationName, LocationType locationType) { + private Location createLocation(String locationName, LocationType locationType, Country country) { return Location.builder() .locationName(locationName) .locationType(locationType) + .country(country) .build(); } diff --git a/src/test/java/swyp/swyp6_team7/travel/service/TravelViewCountServiceTest.java b/src/test/java/swyp/swyp6_team7/travel/service/TravelViewCountServiceTest.java index c34711e1..bb952bb0 100644 --- a/src/test/java/swyp/swyp6_team7/travel/service/TravelViewCountServiceTest.java +++ b/src/test/java/swyp/swyp6_team7/travel/service/TravelViewCountServiceTest.java @@ -13,8 +13,11 @@ import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.SimpleTriggerContext; import swyp.swyp6_team7.config.RedisContainerConfig; +import swyp.swyp6_team7.location.domain.Continent; +import swyp.swyp6_team7.location.domain.Country; import swyp.swyp6_team7.location.domain.Location; import swyp.swyp6_team7.location.domain.LocationType; +import swyp.swyp6_team7.location.repository.CountryRepository; import swyp.swyp6_team7.location.repository.LocationRepository; import swyp.swyp6_team7.travel.domain.GenderType; import swyp.swyp6_team7.travel.domain.PeriodType; @@ -54,6 +57,9 @@ class TravelViewCountServiceTest { @Autowired private LocationRepository locationRepository; + @Autowired + private CountryRepository countryRepository; + @Autowired private RedisTemplate redisTemplate; @@ -61,6 +67,7 @@ class TravelViewCountServiceTest { void tearDown() { travelRepository.deleteAllInBatch(); locationRepository.deleteAllInBatch(); + countryRepository.deleteAllInBatch(); redisTemplate.delete(redisTemplate.keys(VIEW_COUNT_KEY_PREFIX + "*")); redisTemplate.delete(redisTemplate.keys(VIEWED_INFO_KEY_PREFIX + "*")); } @@ -189,7 +196,7 @@ void deleteViewInfoScheduler() { @Test void combineViewCountToDatabase() { // given - Location location = locationRepository.save(createLocation()); + Location location = locationRepository.save(createLocation(createCountry())); Travel travel1 = travelRepository.save(createTravel(5, location)); Travel travel2 = travelRepository.save(createTravel(7, location)); @@ -253,10 +260,18 @@ private CronTrigger createTrigger(Scheduled scheduled) { } } - private Location createLocation() { + private Country createCountry(){ + return countryRepository.save(Country.builder() + .countryName("대한민국") + .continent(Continent.ASIA) + .build()); + } + + private Location createLocation(Country country) { return Location.builder() .locationName("Seoul") .locationType(LocationType.DOMESTIC) + .country(country) .build(); }