diff --git a/.gitignore b/.gitignore index 277899bb..bf469f25 100644 --- a/.gitignore +++ b/.gitignore @@ -203,4 +203,7 @@ gradle-app.setting *.hprof *.yaml +# CommandLineRunner for loading dummy data +src/main/java/team7/inplace/place/config/DataLoader.java + # End of https://www.toptal.com/developers/gitignore/api/macos,java,intellij,gradle \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..0ee5ec68 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/main/resources/InplaceSecurity"] + path = src/main/resources/InplaceSecurity + url = https://github.com/suhyeon7497/InplaceSecurity.git diff --git a/README.md b/README.md index 12ab0554..b52c56a7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ # Team7_BE ## Project Version -- Spring Boots 3.3.3 -- Java 17 LTS ---- -## 리뷰 요청 -### PlaceControllerTest -- Mock을 사용하여 테스트를 진행했는데, 적절히 사용했는지 잘 모르겠습니다. - - PlaceService를 mock, PlaceController를 InjectedMocks로 지정했는데 블로그 찾아보니 사람마다 조금씩 달라 적절한지 궁금합니다. +- Spring Boots 3.3.3 +- Java 17 LTS \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7ee55b06..47964dc6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,41 +1,45 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.3' - id 'io.spring.dependency-management' version '1.1.6' + id 'java' + id 'org.springframework.boot' version '3.3.3' + id 'io.spring.dependency-management' version '1.1.6' } group = 'inplace' version = '0.0.1-SNAPSHOT' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - - - testImplementation 'org.springframework.boot:spring-boot-starter-test' - runtimeOnly 'com.mysql:mysql-connector-j' - runtimeOnly 'com.h2database:h2' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'com.h2database:h2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/team7/inplace/InplaceApplication.java b/src/main/java/team7/inplace/InplaceApplication.java index 0f739c6a..587c3c33 100644 --- a/src/main/java/team7/inplace/InplaceApplication.java +++ b/src/main/java/team7/inplace/InplaceApplication.java @@ -2,12 +2,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class InplaceApplication { - public static void main(String[] args) { - SpringApplication.run(InplaceApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(InplaceApplication.class, args); + } } diff --git a/src/main/java/team7/inplace/influencer/domain/Influencer.java b/src/main/java/team7/inplace/influencer/domain/Influencer.java new file mode 100644 index 00000000..326cd3bf --- /dev/null +++ b/src/main/java/team7/inplace/influencer/domain/Influencer.java @@ -0,0 +1,34 @@ +package team7.inplace.influencer.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@RequiredArgsConstructor // 테스팅을 위한 부분 추가, 협의 하에 다른 방식 채택 가능 +public class Influencer { + /* + * 더미 데이터 입니다 !!! + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column + @NonNull + private String name; + @Column + @NonNull + private String job; + @Column + @NonNull + private String imgUrl; +} diff --git a/src/main/java/team7/inplace/video/repository/InfluencerRepository.java b/src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java similarity index 77% rename from src/main/java/team7/inplace/video/repository/InfluencerRepository.java rename to src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java index 7d7d12f6..f525222f 100644 --- a/src/main/java/team7/inplace/video/repository/InfluencerRepository.java +++ b/src/main/java/team7/inplace/influencer/persistence/InfluencerRepository.java @@ -1,9 +1,8 @@ -package team7.inplace.video.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import team7.inplace.video.entity.Influencer; +package team7.inplace.influencer.persistence; import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import team7.inplace.influencer.domain.Influencer; public interface InfluencerRepository extends JpaRepository { // 더미 데이터 입니다!! diff --git a/src/main/java/team7/inplace/place/application/CategoryService.java b/src/main/java/team7/inplace/place/application/CategoryService.java new file mode 100644 index 00000000..e3103751 --- /dev/null +++ b/src/main/java/team7/inplace/place/application/CategoryService.java @@ -0,0 +1,21 @@ +package team7.inplace.place.application; + +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import team7.inplace.place.application.dto.CategoryInfo; +import team7.inplace.place.domain.Category; +import team7.inplace.place.persistence.PlaceRepository; + +@Service +@RequiredArgsConstructor +public class CategoryService { + + private final PlaceRepository placeRepository; + + public List getCategories() { + return Arrays.stream(Category.values()).map(category -> new CategoryInfo(category.name())) + .toList(); + } +} diff --git a/src/main/java/team7/inplace/place/application/PlaceService.java b/src/main/java/team7/inplace/place/application/PlaceService.java new file mode 100644 index 00000000..911618b7 --- /dev/null +++ b/src/main/java/team7/inplace/place/application/PlaceService.java @@ -0,0 +1,33 @@ +package team7.inplace.place.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import team7.inplace.place.application.command.PlacesCommand.PlacesCoordinateCommand; +import team7.inplace.place.application.dto.PlaceInfo; +import team7.inplace.place.domain.Place; +import team7.inplace.place.persistence.PlaceRepository; + +@Service +@RequiredArgsConstructor +public class PlaceService { + + private final PlaceRepository placeRepository; + + public Page getPlacesWithinRadius( + PlacesCoordinateCommand placesCoordinateCommand) { + + // 주어진 좌표로 장소를 찾고, 해당 페이지의 결과를 가져옵니다. + Page placesPage = getPlacesByDistance(placesCoordinateCommand); + + return placesPage.map(PlaceInfo::of); + } + + private Page getPlacesByDistance(PlacesCoordinateCommand comm) { + return placeRepository.getPlacesByDistance( + comm.latitude(), + comm.longitude(), + comm.pageable()); + } + +} diff --git a/src/main/java/team7/inplace/place/application/command/PlacesCommand.java b/src/main/java/team7/inplace/place/application/command/PlacesCommand.java new file mode 100644 index 00000000..350cd6ec --- /dev/null +++ b/src/main/java/team7/inplace/place/application/command/PlacesCommand.java @@ -0,0 +1,14 @@ +package team7.inplace.place.application.command; + +import org.springframework.data.domain.Pageable; + +public record PlacesCommand() { + + public record PlacesCoordinateCommand( + String longitude, + String latitude, + Pageable pageable + ) { + + } +} diff --git a/src/main/java/team7/inplace/place/application/dto/CategoryInfo.java b/src/main/java/team7/inplace/place/application/dto/CategoryInfo.java new file mode 100644 index 00000000..15a9687d --- /dev/null +++ b/src/main/java/team7/inplace/place/application/dto/CategoryInfo.java @@ -0,0 +1,5 @@ +package team7.inplace.place.application.dto; + +public record CategoryInfo(String name) { + +} diff --git a/src/main/java/team7/inplace/place/application/dto/PlaceForVideo.java b/src/main/java/team7/inplace/place/application/dto/PlaceForVideo.java new file mode 100644 index 00000000..6a87ebb4 --- /dev/null +++ b/src/main/java/team7/inplace/place/application/dto/PlaceForVideo.java @@ -0,0 +1,13 @@ +package team7.inplace.place.application.dto; + +public record PlaceForVideo( + Long placeId, + String placeName +) { + public static PlaceForVideo of(Long placeId, String placeName) { + return new PlaceForVideo( + placeId, + placeName + ); + } +} diff --git a/src/main/java/team7/inplace/place/application/dto/PlaceInfo.java b/src/main/java/team7/inplace/place/application/dto/PlaceInfo.java new file mode 100644 index 00000000..9560102f --- /dev/null +++ b/src/main/java/team7/inplace/place/application/dto/PlaceInfo.java @@ -0,0 +1,47 @@ +package team7.inplace.place.application.dto; + + +import team7.inplace.place.domain.Address; +import team7.inplace.place.domain.Place; + +public record PlaceInfo( + Long placeId, + String placeName, + AddressInfo address, + String category, + String influencerName, + String longitude, + String latitude, + Boolean likes +) { + + + // influencer, likes 추가 예정 + public record AddressInfo( + String address1, + String address2, + String address3 + ) { + + public static AddressInfo of(Address address) { + return new PlaceInfo.AddressInfo( + address.getAddress1(), + address.getAddress2(), + address.getAddress3() + ); + } + } + + public static PlaceInfo of(Place place) { + return new PlaceInfo( + place.getId(), + place.getName(), + AddressInfo.of(place.getAddress()), + place.getCategory().toString(), + null, + place.getCoordinate().getLongitude(), + place.getCoordinate().getLatitude(), + null + ); + } +} diff --git a/src/main/java/team7/inplace/place/config/DataLoader.java b/src/main/java/team7/inplace/place/config/DataLoader.java new file mode 100644 index 00000000..ef81e810 --- /dev/null +++ b/src/main/java/team7/inplace/place/config/DataLoader.java @@ -0,0 +1,97 @@ +package team7.inplace.place.config; + +import java.util.Arrays; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import team7.inplace.place.domain.Address; +import team7.inplace.place.domain.Category; +import team7.inplace.place.domain.Coordinate; +import team7.inplace.place.domain.Menu; +import team7.inplace.place.domain.Place; +import team7.inplace.place.domain.PlaceTime; +import team7.inplace.place.persistence.PlaceRepository; + +@Component +public class DataLoader implements CommandLineRunner { + + private final PlaceRepository placeRepository; + + public DataLoader(PlaceRepository placeRepository) { + this.placeRepository = placeRepository; + } + + @Override + public void run(String... args) throws Exception { + // 더미 데이터 생성 + Place place1 = Place.builder() + .name("Place 1") + .pet(false) + .wifi(true) + .parking(false) + .fordisabled(true) + .nursery(false) + .smokingroom(false) + .address(new Address("Address 1", "Address 2", "Address 3")) + .menuImgUrl("menu.jpg") + .category(Category.CAFE) + .coordinate(new Coordinate("127.0", "37.0")) + .timeList(Arrays.asList( + new PlaceTime("Opening Hours", "9:00 AM", "Monday"), + new PlaceTime("Closing Hours", "10:00 PM", "Monday") + )) + .menuList(Arrays.asList( + new Menu(5000L, true, "Coffee"), + new Menu(7000L, false, "Cake") + )) + .build(); + + Place place2 = Place.builder() + .name("Place 2") + .pet(false) + .wifi(true) + .parking(false) + .fordisabled(true) + .nursery(false) + .smokingroom(false) + .address(new Address("Address 1", "Address 2", "Address 3")) + .menuImgUrl("menu.jpg") + .category(Category.CAFE) + .coordinate(new Coordinate("137.0", "10.0")) + .timeList(Arrays.asList( + new PlaceTime("Opening Hours", "9:00 AM", "Monday"), + new PlaceTime("Closing Hours", "10:00 PM", "Monday") + )) + .menuList(Arrays.asList( + new Menu(5000L, true, "Coffee"), + new Menu(7000L, false, "Cake") + )) + .build(); + + Place place3 = Place.builder() + .name("Place 3") + .pet(false) + .wifi(true) + .parking(false) + .fordisabled(true) + .nursery(false) + .smokingroom(false) + .address(new Address("Address 1", "Address 2", "Address 3")) + .menuImgUrl("menu.jpg") + .category(Category.CAFE) + .coordinate(new Coordinate("126.0", "30.0")) + .timeList(Arrays.asList( + new PlaceTime("Opening Hours", "9:00 AM", "Monday"), + new PlaceTime("Closing Hours", "10:00 PM", "Monday") + )) + .menuList(Arrays.asList( + new Menu(5000L, true, "Coffee"), + new Menu(7000L, false, "Cake") + )) + .build(); + + // 저장 + placeRepository.save(place1); + placeRepository.save(place2); + placeRepository.save(place3); + } +} diff --git a/src/main/java/team7/inplace/place/controller/PlaceController.java b/src/main/java/team7/inplace/place/controller/PlaceController.java deleted file mode 100644 index dfc1d095..00000000 --- a/src/main/java/team7/inplace/place/controller/PlaceController.java +++ /dev/null @@ -1,27 +0,0 @@ -package team7.inplace.place.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import team7.inplace.place.domain.CategoryListDTO; -import team7.inplace.place.service.PlaceService; - -@RestController -@RequestMapping("/places") -public class PlaceController { - private final PlaceService placeService; - - @Autowired - public PlaceController(PlaceService placeService) { - this.placeService = placeService; - } - @GetMapping("/categories") - public ResponseEntity getCategories() { - CategoryListDTO response = placeService.getCategories(); - - return new ResponseEntity<>(response, HttpStatus.OK); - } -} diff --git a/src/main/java/team7/inplace/place/domain/Address.java b/src/main/java/team7/inplace/place/domain/Address.java new file mode 100644 index 00000000..5af0460c --- /dev/null +++ b/src/main/java/team7/inplace/place/domain/Address.java @@ -0,0 +1,26 @@ +package team7.inplace.place.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@Getter +public class Address { + + @Column(nullable = false, length = 50) + private String address1; + + @Column(nullable = false, length = 50) + private String address2; + + @Column(nullable = false, length = 50) + private String address3; + +} diff --git a/src/main/java/team7/inplace/place/domain/CategoryListDTO.java b/src/main/java/team7/inplace/place/domain/CategoryListDTO.java deleted file mode 100644 index 20998a1c..00000000 --- a/src/main/java/team7/inplace/place/domain/CategoryListDTO.java +++ /dev/null @@ -1,7 +0,0 @@ -package team7.inplace.place.domain; - -import java.util.List; - -public record CategoryListDTO(List categories) { - -} diff --git a/src/main/java/team7/inplace/place/domain/Coordinate.java b/src/main/java/team7/inplace/place/domain/Coordinate.java new file mode 100644 index 00000000..92c396b4 --- /dev/null +++ b/src/main/java/team7/inplace/place/domain/Coordinate.java @@ -0,0 +1,22 @@ +package team7.inplace.place.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@Getter +public class Coordinate { + + @Column(nullable = false, columnDefinition = "TEXT") + private String longitude; + + @Column(nullable = false, columnDefinition = "TEXT") + private String latitude; +} diff --git a/src/main/java/team7/inplace/place/domain/Menu.java b/src/main/java/team7/inplace/place/domain/Menu.java new file mode 100644 index 00000000..eb2b6daf --- /dev/null +++ b/src/main/java/team7/inplace/place/domain/Menu.java @@ -0,0 +1,28 @@ +package team7.inplace.place.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@Getter +public class Menu { + + @Column(columnDefinition = "NUMBER") + private Long price; + + @ColumnDefault("false") + @Column + private boolean recommend; + + @Column(length = 50) + private String menuName; + +} diff --git a/src/main/java/team7/inplace/place/domain/Place.java b/src/main/java/team7/inplace/place/domain/Place.java index 5ddfdf8c..b490b32a 100644 --- a/src/main/java/team7/inplace/place/domain/Place.java +++ b/src/main/java/team7/inplace/place/domain/Place.java @@ -1,13 +1,18 @@ package team7.inplace.place.domain; +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -24,8 +29,7 @@ public class Place { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(nullable = false) - private Long placeId; + private Long id; @Column(nullable = false, length = 50) private String name; @@ -54,15 +58,6 @@ public class Place { @Column(nullable = false) private boolean smokingroom; - @Column(nullable = false, length = 50) - private String address1; - - @Column(nullable = false, length = 50) - private String address2; - - @Column(nullable = false, length = 50) - private String address3; - @Column(columnDefinition = "TEXT") private String menuImgUrl; @@ -70,10 +65,20 @@ public class Place { @Column(nullable = false) private Category category; - @Column(nullable = false, columnDefinition = "TEXT") - private String longitude; + @Embedded + private Address address; + + @Embedded + private Coordinate coordinate; + + @ElementCollection + @CollectionTable(name = "place_times", joinColumns = @JoinColumn(name = "place_id")) + private List timeList; + + @ElementCollection + @CollectionTable(name = "menus", joinColumns = @JoinColumn(name = "place_id")) + private List menuList; - @Column(nullable = false, columnDefinition = "TEXT") - private String latitude; + // influencerName, likes 기능은 추후 추가 예정 } diff --git a/src/main/java/team7/inplace/place/domain/PlaceTime.java b/src/main/java/team7/inplace/place/domain/PlaceTime.java new file mode 100644 index 00000000..d3b9ac2c --- /dev/null +++ b/src/main/java/team7/inplace/place/domain/PlaceTime.java @@ -0,0 +1,25 @@ +package team7.inplace.place.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@Getter +public class PlaceTime { + + @Column(length = 50) + private String timeName; + + @Column(length = 50) + private String timeSe; + + @Column(length = 50) + private String dayOfWeek; +} diff --git a/src/main/java/team7/inplace/place/persistence/PlaceRepository.java b/src/main/java/team7/inplace/place/persistence/PlaceRepository.java new file mode 100644 index 00000000..dee3c1c4 --- /dev/null +++ b/src/main/java/team7/inplace/place/persistence/PlaceRepository.java @@ -0,0 +1,30 @@ +package team7.inplace.place.persistence; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import team7.inplace.place.domain.Place; + +@Repository +public interface PlaceRepository extends JpaRepository { + + //거리 계산 + @Query(value = "SELECT *, " + + "(6371 * acos(cos(radians(CAST(:latitude AS DECIMAL(10, 6)))) " + + "* cos(radians(CAST(latitude AS DECIMAL(10, 6)))) " + + "* cos(radians(CAST(longitude AS DECIMAL(10, 6))) - radians(CAST(:longitude AS DECIMAL(10, 6)))) " + + + "+ sin(radians(CAST(:latitude AS DECIMAL(10, 6)))) " + + "* sin(radians(CAST(latitude AS DECIMAL(10, 6)))))) AS distance " + + "FROM places " + + "ORDER BY distance", + countQuery = "SELECT count(*) FROM place", // 총 개수 쿼리 + nativeQuery = true) + Page getPlacesByDistance( + @Param("latitude") String latitude, + @Param("longitude") String longitude, + Pageable pageable); +} diff --git a/src/main/java/team7/inplace/place/presentation/PlaceController.java b/src/main/java/team7/inplace/place/presentation/PlaceController.java new file mode 100644 index 00000000..5374968f --- /dev/null +++ b/src/main/java/team7/inplace/place/presentation/PlaceController.java @@ -0,0 +1,52 @@ +package team7.inplace.place.presentation; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import team7.inplace.place.application.CategoryService; +import team7.inplace.place.application.PlaceService; +import team7.inplace.place.application.command.PlacesCommand.PlacesCoordinateCommand; +import team7.inplace.place.application.dto.CategoryInfo; +import team7.inplace.place.application.dto.PlaceInfo; +import team7.inplace.place.presentation.dto.CategoriesResponse; +import team7.inplace.place.presentation.dto.PlacesResponse; +import team7.inplace.place.presentation.dto.PlacesSearchParams; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/places") +public class PlaceController { + + private final PlaceService placeService; + private final CategoryService categoryService; + + @GetMapping + public ResponseEntity getPlaces( + @ModelAttribute PlacesSearchParams searchParams, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + Pageable pageable = PageRequest.of(page, size); + Page placeInfos = placeService.getPlacesWithinRadius( + new PlacesCoordinateCommand(searchParams.getLongitude(), searchParams.getLatitude(), + pageable)); + return new ResponseEntity<>(new PlacesResponse(placeInfos), HttpStatus.OK); + } + + @GetMapping("/categories") + public ResponseEntity getCategories() { + List categories = categoryService.getCategories(); + CategoriesResponse response = new CategoriesResponse(categories); + + return new ResponseEntity<>(response, HttpStatus.OK); + } +} diff --git a/src/main/java/team7/inplace/place/presentation/dto/CategoriesResponse.java b/src/main/java/team7/inplace/place/presentation/dto/CategoriesResponse.java new file mode 100644 index 00000000..4fbee8db --- /dev/null +++ b/src/main/java/team7/inplace/place/presentation/dto/CategoriesResponse.java @@ -0,0 +1,8 @@ +package team7.inplace.place.presentation.dto; + +import java.util.List; +import team7.inplace.place.application.dto.CategoryInfo; + +public record CategoriesResponse(List categories) { + +} diff --git a/src/main/java/team7/inplace/place/presentation/dto/PlacesResponse.java b/src/main/java/team7/inplace/place/presentation/dto/PlacesResponse.java new file mode 100644 index 00000000..b7b2a805 --- /dev/null +++ b/src/main/java/team7/inplace/place/presentation/dto/PlacesResponse.java @@ -0,0 +1,8 @@ +package team7.inplace.place.presentation.dto; + +import org.springframework.data.domain.Page; +import team7.inplace.place.application.dto.PlaceInfo; + +public record PlacesResponse(Page places) { + +} diff --git a/src/main/java/team7/inplace/place/presentation/dto/PlacesSearchParams.java b/src/main/java/team7/inplace/place/presentation/dto/PlacesSearchParams.java new file mode 100644 index 00000000..f9b0b566 --- /dev/null +++ b/src/main/java/team7/inplace/place/presentation/dto/PlacesSearchParams.java @@ -0,0 +1,20 @@ +package team7.inplace.place.presentation.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class PlacesSearchParams { + + private String longitude; + + private String latitude; + + private String categories; + + private String influencers; + +} diff --git a/src/main/java/team7/inplace/place/repository/PlaceRepository.java b/src/main/java/team7/inplace/place/repository/PlaceRepository.java deleted file mode 100644 index 30488a18..00000000 --- a/src/main/java/team7/inplace/place/repository/PlaceRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package team7.inplace.place.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import team7.inplace.place.domain.Place; - -@Repository -public interface PlaceRepository extends JpaRepository { - -} diff --git a/src/main/java/team7/inplace/place/service/PlaceService.java b/src/main/java/team7/inplace/place/service/PlaceService.java deleted file mode 100644 index 694477c1..00000000 --- a/src/main/java/team7/inplace/place/service/PlaceService.java +++ /dev/null @@ -1,9 +0,0 @@ -package team7.inplace.place.service; - -import java.util.List; -import org.springframework.stereotype.Service; -import team7.inplace.place.domain.CategoryListDTO; - -public interface PlaceService { - CategoryListDTO getCategories(); -} diff --git a/src/main/java/team7/inplace/place/service/PlaceServiceImpl.java b/src/main/java/team7/inplace/place/service/PlaceServiceImpl.java deleted file mode 100644 index 6aa30cf3..00000000 --- a/src/main/java/team7/inplace/place/service/PlaceServiceImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package team7.inplace.place.service; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import org.springframework.stereotype.Service; -import team7.inplace.place.domain.Category; -import team7.inplace.place.domain.CategoryListDTO; - -@Service -public class PlaceServiceImpl implements PlaceService{ - - @Override - public CategoryListDTO getCategories() { - List categories = Arrays.stream(Category.values()).toList(); - return new CategoryListDTO(categories); - } -} diff --git a/src/main/java/team7/inplace/security/application/CustomOAuth2UserService.java b/src/main/java/team7/inplace/security/application/CustomOAuth2UserService.java new file mode 100644 index 00000000..88b26780 --- /dev/null +++ b/src/main/java/team7/inplace/security/application/CustomOAuth2UserService.java @@ -0,0 +1,48 @@ +package team7.inplace.security.application; + +import jakarta.transaction.Transactional; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import team7.inplace.security.application.dto.CustomOAuth2User; +import team7.inplace.security.application.dto.KakaoOAuthResponse; +import team7.inplace.security.domain.User; +import team7.inplace.security.persistence.UserRepository; + +public class CustomOAuth2UserService implements OAuth2UserService { + + private final DefaultOAuth2UserService defaultOAuth2UserService; + private final UserRepository userRepository; + + public CustomOAuth2UserService(DefaultOAuth2UserService defaultOAuth2UserService, + UserRepository userRepository) { + this.defaultOAuth2UserService = defaultOAuth2UserService; + this.userRepository = userRepository; + } + + @Transactional + @Override + public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) + throws OAuth2AuthenticationException { + OAuth2User oAuth2User = defaultOAuth2UserService.loadUser(oAuth2UserRequest); + KakaoOAuthResponse kakaoOAuthResponse = new KakaoOAuthResponse(oAuth2User.getAttributes()); + User user = register(kakaoOAuthResponse); + return CustomOAuth2User.of(user); + } + + private User register(KakaoOAuthResponse kakaoOAuthResponse) { + if (isExistUser(kakaoOAuthResponse)) { + return userRepository.findByUsername(kakaoOAuthResponse.getEmail()) + .orElseThrow(); + } + User user = kakaoOAuthResponse.toUser(); + userRepository.save(user); + return user; + } + + private boolean isExistUser(KakaoOAuthResponse kakaoOAuthResponse) { + return userRepository.existsByUsername(kakaoOAuthResponse.getEmail()); + } +} diff --git a/src/main/java/team7/inplace/security/application/dto/CustomOAuth2User.java b/src/main/java/team7/inplace/security/application/dto/CustomOAuth2User.java new file mode 100644 index 00000000..03892c7a --- /dev/null +++ b/src/main/java/team7/inplace/security/application/dto/CustomOAuth2User.java @@ -0,0 +1,34 @@ +package team7.inplace.security.application.dto; + +import java.util.Collection; +import java.util.Map; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; +import team7.inplace.security.domain.User; +import team7.inplace.security.domain.UserType; + +public record CustomOAuth2User( + String username, + String nickname, + UserType userType +) implements OAuth2User { + + @Override + public Map getAttributes() { + return null; + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getName() { + return username; + } + + public static CustomOAuth2User of(User user) { + return new CustomOAuth2User(user.getUsername(), user.getNickname(), user.getUserType()); + } +} diff --git a/src/main/java/team7/inplace/security/application/dto/KakaoOAuthResponse.java b/src/main/java/team7/inplace/security/application/dto/KakaoOAuthResponse.java new file mode 100644 index 00000000..41909632 --- /dev/null +++ b/src/main/java/team7/inplace/security/application/dto/KakaoOAuthResponse.java @@ -0,0 +1,30 @@ +package team7.inplace.security.application.dto; + +import java.util.Map; +import team7.inplace.security.domain.User; +import team7.inplace.security.domain.UserType; + +public record KakaoOAuthResponse( + Map attribute +) { + + public KakaoOAuthResponse(Map attribute) { + this.attribute = (Map) attribute.get("kakao_account"); + } + + public String getProvider() { + return "kakao"; + } + + public String getEmail() { + return attribute.get("email").toString(); + } + + public String getNickname() { + return ((Map) attribute.get("profile")).get("nickname").toString(); + } + + public User toUser() { + return new User(this.getEmail(), null, this.getNickname(), UserType.KAKAO); + } +} diff --git a/src/main/java/team7/inplace/security/config/JwtProperties.java b/src/main/java/team7/inplace/security/config/JwtProperties.java new file mode 100644 index 00000000..bf7a3969 --- /dev/null +++ b/src/main/java/team7/inplace/security/config/JwtProperties.java @@ -0,0 +1,12 @@ +package team7.inplace.security.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.jwt") +public record JwtProperties( + String secret, + Long accessTokenExpiredTime, + Long refreshTokenExpiredTime +) { + +} diff --git a/src/main/java/team7/inplace/security/config/SecurityConfig.java b/src/main/java/team7/inplace/security/config/SecurityConfig.java new file mode 100644 index 00000000..c6b98853 --- /dev/null +++ b/src/main/java/team7/inplace/security/config/SecurityConfig.java @@ -0,0 +1,51 @@ +package team7.inplace.security.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import team7.inplace.security.application.CustomOAuth2UserService; +import team7.inplace.security.handler.CustomSuccessHandler; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final CustomOAuth2UserService customOauth2UserService; + private final CustomSuccessHandler customSuccessHandler; + + public SecurityConfig(CustomOAuth2UserService customOAuth2UserService, + CustomSuccessHandler customSuccessHandler) { + this.customOauth2UserService = customOAuth2UserService; + this.customSuccessHandler = customSuccessHandler; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + + //h2-console 접속 가능 + http.headers((headers) -> headers.frameOptions(FrameOptionsConfig::sameOrigin)) + .authorizeHttpRequests((auth) -> auth + .requestMatchers("/h2-console/**").permitAll()); + + //http 설정 + http.csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .oauth2Login((oauth2) -> oauth2 + .userInfoEndpoint((userInfoEndPointConfig) -> userInfoEndPointConfig + .userService(customOauth2UserService)).successHandler(customSuccessHandler)) + .authorizeHttpRequests((auth) -> auth + .requestMatchers("/login").permitAll() + .requestMatchers("/hello").authenticated() + .anyRequest().permitAll()) + .sessionManagement((session) -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + return http.build(); + } +} diff --git a/src/main/java/team7/inplace/security/config/SecurityHandlerConfig.java b/src/main/java/team7/inplace/security/config/SecurityHandlerConfig.java new file mode 100644 index 00000000..2ce02e48 --- /dev/null +++ b/src/main/java/team7/inplace/security/config/SecurityHandlerConfig.java @@ -0,0 +1,15 @@ +package team7.inplace.security.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import team7.inplace.security.handler.CustomSuccessHandler; +import team7.inplace.security.util.JwtUtil; + +@Configuration +public class SecurityHandlerConfig { + + @Bean + public CustomSuccessHandler customSuccessHandler(JwtUtil jwtUtil) { + return new CustomSuccessHandler(jwtUtil); + } +} diff --git a/src/main/java/team7/inplace/security/config/SecurityServiceConfig.java b/src/main/java/team7/inplace/security/config/SecurityServiceConfig.java new file mode 100644 index 00000000..c9d3d2b5 --- /dev/null +++ b/src/main/java/team7/inplace/security/config/SecurityServiceConfig.java @@ -0,0 +1,22 @@ +package team7.inplace.security.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import team7.inplace.security.application.CustomOAuth2UserService; +import team7.inplace.security.persistence.UserRepository; + +@Configuration +public class SecurityServiceConfig { + + @Bean + public DefaultOAuth2UserService defaultOAuth2UserService() { + return new DefaultOAuth2UserService(); + } + + @Bean + public CustomOAuth2UserService customOAuth2UserService( + DefaultOAuth2UserService defaultOAuth2UserService, UserRepository userRepository) { + return new CustomOAuth2UserService(defaultOAuth2UserService, userRepository); + } +} diff --git a/src/main/java/team7/inplace/security/config/SecurityUtilConfig.java b/src/main/java/team7/inplace/security/config/SecurityUtilConfig.java new file mode 100644 index 00000000..bc3799f5 --- /dev/null +++ b/src/main/java/team7/inplace/security/config/SecurityUtilConfig.java @@ -0,0 +1,14 @@ +package team7.inplace.security.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import team7.inplace.security.util.JwtUtil; + +@Configuration +public class SecurityUtilConfig { + + @Bean + public JwtUtil jwtUtil(JwtProperties jwtProperties) { + return new JwtUtil(jwtProperties); + } +} diff --git a/src/main/java/team7/inplace/security/domain/User.java b/src/main/java/team7/inplace/security/domain/User.java new file mode 100644 index 00000000..d994b061 --- /dev/null +++ b/src/main/java/team7/inplace/security/domain/User.java @@ -0,0 +1,48 @@ +package team7.inplace.security.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "\"USER\"") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "username", nullable = false) + private String username; + + @Column(name = "password") + private String password; + + @Column(name = "nickname") + private String nickname; + + @Column(name = "user_type") + @Enumerated(EnumType.STRING) + private UserType userType; + + public User(String username, String password, String nickname, UserType userType) { + this.username = username; + this.password = password; + this.nickname = nickname; + this.userType = userType; + } + + public void updateInfo(String nickname) { + this.nickname = nickname; + } +} diff --git a/src/main/java/team7/inplace/security/domain/UserType.java b/src/main/java/team7/inplace/security/domain/UserType.java new file mode 100644 index 00000000..da269dba --- /dev/null +++ b/src/main/java/team7/inplace/security/domain/UserType.java @@ -0,0 +1,14 @@ +package team7.inplace.security.domain; + +import lombok.Getter; + +@Getter +public enum UserType { + KAKAO("kakao"); + + private final String provider; + + UserType(String provider) { + this.provider = provider; + } +} diff --git a/src/main/java/team7/inplace/security/handler/CustomSuccessHandler.java b/src/main/java/team7/inplace/security/handler/CustomSuccessHandler.java new file mode 100644 index 00000000..1e668599 --- /dev/null +++ b/src/main/java/team7/inplace/security/handler/CustomSuccessHandler.java @@ -0,0 +1,47 @@ +package team7.inplace.security.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import team7.inplace.security.application.dto.CustomOAuth2User; +import team7.inplace.security.util.JwtUtil; + +public class CustomSuccessHandler implements AuthenticationSuccessHandler { + + private final JwtUtil jwtUtil; + + public CustomSuccessHandler(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) + throws IOException, ServletException { + + CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal(); + String username = customOAuth2User.getName(); + Cookie accessTokenCookie = createCookie("access_token", + jwtUtil.createAccessToken(username)); + Cookie refreshTokenCookie = createCookie("refresh_token", + jwtUtil.createRefreshToken(username)); + + response.addCookie(accessTokenCookie); + response.addCookie(refreshTokenCookie); + response.sendRedirect("http://localhost:8080/successCookie"); + } + + private Cookie createCookie(String key, String value) { + Cookie cookie = new Cookie(key, value); + cookie.setMaxAge(60 * 60); + cookie.setPath("/"); + cookie.setHttpOnly(true); + + return cookie; + } + +} diff --git a/src/main/java/team7/inplace/security/persistence/UserRepository.java b/src/main/java/team7/inplace/security/persistence/UserRepository.java new file mode 100644 index 00000000..e5bc38a2 --- /dev/null +++ b/src/main/java/team7/inplace/security/persistence/UserRepository.java @@ -0,0 +1,14 @@ +package team7.inplace.security.persistence; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import team7.inplace.security.domain.User; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); + + boolean existsByUsername(String username); +} diff --git a/src/main/java/team7/inplace/security/util/JwtUtil.java b/src/main/java/team7/inplace/security/util/JwtUtil.java new file mode 100644 index 00000000..d5ac3ba8 --- /dev/null +++ b/src/main/java/team7/inplace/security/util/JwtUtil.java @@ -0,0 +1,51 @@ +package team7.inplace.security.util; + +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Jwts.SIG; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import team7.inplace.security.config.JwtProperties; + +public class JwtUtil { + + private final SecretKey secretKey; + private final JwtParser jwtParser; + private final Long accessTokenExpiredTime; + private final Long refreshTokenExpiredTime; + + public JwtUtil(JwtProperties jwtProperties) { + this.secretKey = new SecretKeySpec(jwtProperties.secret().getBytes(StandardCharsets.UTF_8), + SIG.HS256.key().build().getAlgorithm()); + this.jwtParser = Jwts.parser().verifyWith(this.secretKey).build(); + this.accessTokenExpiredTime = jwtProperties.accessTokenExpiredTime(); + this.refreshTokenExpiredTime = jwtProperties.refreshTokenExpiredTime(); + } + + public String createAccessToken(String username) { + return createToken(username, accessTokenExpiredTime); + } + + public String createRefreshToken(String username) { + return createToken(username, refreshTokenExpiredTime); + } + + private String createToken(String username, Long expiredTime) { + return Jwts.builder() + .claim("username", username) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiredTime)) + .signWith(secretKey) + .compact(); + } + + public String getUsername(String token) { + return jwtParser.parseSignedClaims(token).getPayload().get("username", String.class); + } + + public Boolean isExpired(String token) { + return jwtParser.parseSignedClaims(token).getPayload().getExpiration().before(new Date()); + } +} diff --git a/src/main/java/team7/inplace/video/DTO/PlaceForVideo.java b/src/main/java/team7/inplace/video/DTO/PlaceForVideo.java deleted file mode 100644 index 9ab2c42a..00000000 --- a/src/main/java/team7/inplace/video/DTO/PlaceForVideo.java +++ /dev/null @@ -1,4 +0,0 @@ -package team7.inplace.video.DTO; - -public record PlaceForVideo(Long placeId, String placeName) { -} diff --git a/src/main/java/team7/inplace/video/DTO/VideoData.java b/src/main/java/team7/inplace/video/DTO/VideoData.java deleted file mode 100644 index c371c49d..00000000 --- a/src/main/java/team7/inplace/video/DTO/VideoData.java +++ /dev/null @@ -1,4 +0,0 @@ -package team7.inplace.video.DTO; - -public record VideoData(Long videoId, String videoAlias, String videoUrl, PlaceForVideo place) { -} diff --git a/src/main/java/team7/inplace/video/DTO/VideoResponse.java b/src/main/java/team7/inplace/video/DTO/VideoResponse.java deleted file mode 100644 index 0ce3f9b8..00000000 --- a/src/main/java/team7/inplace/video/DTO/VideoResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package team7.inplace.video.DTO; - -public record VideoResponse(Long videoId, String videoAlias, String videoUrl, PlaceForVideo place) { - public VideoResponse(VideoData videoData) { - this(videoData.videoId(), videoData.videoAlias(), videoData.videoUrl(), videoData.place()); - } -} diff --git a/src/main/java/team7/inplace/video/application/AliasUtil.java b/src/main/java/team7/inplace/video/application/AliasUtil.java new file mode 100644 index 00000000..80667687 --- /dev/null +++ b/src/main/java/team7/inplace/video/application/AliasUtil.java @@ -0,0 +1,24 @@ +package team7.inplace.video.application; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import team7.inplace.place.domain.Category; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class AliasUtil { + private final Template template; + public static String makeAlias(String influencerName, Category category){ + String alias = mapTemplateToCategory(category); + return influencerName + " " + alias; + } + + // 카테고리에 따라 템플릿을 매핑하는 메서드 + private static String mapTemplateToCategory(Category category) { + return switch (category) { + case CAFE -> Template.CAFE.getRandomTemplate(); + case WESTERN -> Template.WESTERN.getRandomTemplate(); + case JAPANESE -> Template.JAPANESE.getRandomTemplate(); + case KOREAN -> Template.KOREAN.getRandomTemplate(); + }; + } +} diff --git a/src/main/java/team7/inplace/video/application/Template.java b/src/main/java/team7/inplace/video/application/Template.java new file mode 100644 index 00000000..f4228236 --- /dev/null +++ b/src/main/java/team7/inplace/video/application/Template.java @@ -0,0 +1,52 @@ +package team7.inplace.video.application; + +import lombok.Getter; +import team7.inplace.place.domain.Category; + +import java.util.*; + +@Getter +public enum Template { + // 카페 관련 템플릿 + CAFE(new String[]{ + "이(가) 방문한 카페에서 따뜻한 커피를 즐겨보세요!", + "이(가) 방문했던 카페에서 여유로운 시간을 보내보세요!", + "이(가) 추천하는 사진 명소 예쁜 카페에 방문해보세요!" + }), + + // 양식 관련 템플릿 + WESTERN(new String[]{ + "이(가) 추천! 맛있는 서양 음식을 즐길 수 있는 곳!", + "이(가) 추천하는 고급스러운 서양 요리를 맛볼 수 있습니다.", + "이(가) 극찬! 서양식 디저트를 꼭 한 번 시도해보세요!" + }), + + // 일식 관련 템플릿 + JAPANESE(new String[]{ + "피셜, 현지와 다름없는 정통 일본 요리 레스토랑.", + "이(가) 추천하는 맛있는 일본 초밥과 라멘을 즐겨보세요.", + "이(가) 극찬! 일본 요리의 정수를 느껴보세요." + }), + + // 한식 관련 템플릿 + KOREAN(new String[]{ + "이(가) 전통 한식을 맛볼 수 있는 한식당입니다.", + "이(가) 추천하는 가게에서 정성스럽게 준비된 한식으로 든든한 한 끼를!", + "이(가) 극찬! 한식의 깊은 맛을 느껴보세요." + }); + + // 템플릿 배열을 반환하는 메서드 + // 필드 선언 + private final String[] templates; + + // 생성자 + Template(String[] templates) { + this.templates = templates; + } + + // 특정 템플릿을 랜덤으로 반환하는 메서드 + public String getRandomTemplate() { + int randomIndex = (int) (Math.random() * templates.length); + return templates[randomIndex]; + } +} diff --git a/src/main/java/team7/inplace/video/application/VideoService.java b/src/main/java/team7/inplace/video/application/VideoService.java new file mode 100644 index 00000000..2c39d074 --- /dev/null +++ b/src/main/java/team7/inplace/video/application/VideoService.java @@ -0,0 +1,53 @@ +package team7.inplace.video.application; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import team7.inplace.influencer.domain.Influencer; +import team7.inplace.influencer.persistence.InfluencerRepository; +import team7.inplace.place.application.dto.PlaceForVideo; +import team7.inplace.place.domain.Place; +import team7.inplace.video.application.dto.VideoInfo; +import team7.inplace.video.domain.Video; +import team7.inplace.video.persistence.VideoRepository; + +@Service +@RequiredArgsConstructor +public class VideoService { + private final VideoRepository videoRepository; + private final InfluencerRepository influencerRepository; + + public List findByInfluencer(List influencers) { + // 인플루언서 정보 처리 + List influencerIds = influencerRepository.findByNameIn(influencers).stream() + .map(Influencer::getId) + .toList(); + + // 인플루언서 정보로 필터링한 비디오 정보 불러오기 + List