diff --git a/.gitignore b/.gitignore index 72f03805..5fde39ac 100644 --- a/.gitignore +++ b/.gitignore @@ -179,6 +179,7 @@ gradle-app.setting ### QClass ### **/src/main/generated/ +### application-cloud-local.yml app/src/main/resources/application-cloud-local.yml # End of https://www.toptal.com/developers/gitignore/api/java,intellij+all,macos,gradle \ No newline at end of file diff --git a/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java b/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java index 55e99ea7..2d3bcae5 100644 --- a/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java +++ b/app/api/common-api/src/main/java/org/example/config/SecurityConfig.java @@ -38,9 +38,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(registry -> registry - .requestMatchers(notRequireAuthenticationMatcher()) + .requestMatchers(getMatcherForAnyone()) .permitAll() - .requestMatchers(requireUserAndAdminAuthenticationMatcher()) + .requestMatchers(getMatcherForUserAndAdmin()) .hasAnyRole("USER", "ADMIN") .anyRequest() .hasAnyRole("ADMIN") @@ -60,7 +60,7 @@ private CorsConfigurationSource corsConfigurationSource() { }; } - private RequestMatcher notRequireAuthenticationMatcher() { + private RequestMatcher getMatcherForAnyone() { return RequestMatchers.anyOf( antMatcher("/swagger-ui/**"), antMatcher("/v3/api-docs/**"), @@ -68,6 +68,7 @@ private RequestMatcher notRequireAuthenticationMatcher() { antMatcher("/css/**"), antMatcher("/js/**"), antMatcher(HttpMethod.POST, "/api/v1/users/login"), + antMatcher(HttpMethod.POST, "/admin/login"), antMatcher(HttpMethod.POST, "/admin/signup"), antMatcher(HttpMethod.GET, "/admin/home"), antMatcher(HttpMethod.GET, "/api/v1/artists"), @@ -76,7 +77,7 @@ private RequestMatcher notRequireAuthenticationMatcher() { ); } - private RequestMatcher requireUserAndAdminAuthenticationMatcher() { + private RequestMatcher getMatcherForUserAndAdmin() { return RequestMatchers.anyOf( antMatcher(HttpMethod.GET, "/api/v1/shows/interests"), antMatcher(HttpMethod.POST, "/api/v1/users/logout"), diff --git a/app/api/common-api/src/main/java/org/example/config/SwaggerConfig.java b/app/api/common-api/src/main/java/org/example/config/SwaggerConfig.java index a8754ae0..dd5d3679 100644 --- a/app/api/common-api/src/main/java/org/example/config/SwaggerConfig.java +++ b/app/api/common-api/src/main/java/org/example/config/SwaggerConfig.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,6 +20,7 @@ public class SwaggerConfig { public OpenAPI openAPI() { return new OpenAPI() .info(getInfo()) + .addServersItem(getCurrentServerUrl()) .addSecurityItem(getSecurityRequirement()) .components( new Components().addSecuritySchemes( @@ -35,6 +37,10 @@ private Info getInfo() { .license(getLicense()); } + private Server getCurrentServerUrl() { + return new Server().url("/"); + } + private SecurityRequirement getSecurityRequirement() { return new SecurityRequirement().addList(securitySchemeName); } diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/ArtistAdminController.java b/app/api/show-api/src/main/java/com/example/artist/controller/ArtistAdminController.java index ec6f3ea0..bcad3b4d 100644 --- a/app/api/show-api/src/main/java/com/example/artist/controller/ArtistAdminController.java +++ b/app/api/show-api/src/main/java/com/example/artist/controller/ArtistAdminController.java @@ -5,8 +5,8 @@ import com.example.artist.controller.dto.response.ArtistDetailApiFormResponse; import com.example.artist.service.ArtistAdminService; import com.example.artist.service.dto.response.ArtistDetailServiceResponse; +import com.example.genre.controller.dto.response.GenreNameApiFormResponse; import com.example.genre.service.GenreAdminService; -import com.example.genre.service.dto.response.GenreNameServiceResponse; import jakarta.validation.Valid; import java.util.List; import java.util.UUID; @@ -30,7 +30,9 @@ public class ArtistAdminController { @GetMapping public String createArtist(Model model) { - List genres = genreAdminService.findAllGenres(); + List genres = genreAdminService.findAllGenres().stream() + .map(GenreNameApiFormResponse::new) + .toList(); model.addAttribute("genres", genres); return "artist_create_form"; } @@ -43,7 +45,7 @@ public String createArtist(@Valid ArtistCreateApiForm artistCreateApiForm) { @GetMapping("/list") public String findAllArtist(Model model) { - List artistDetailApiFormResponses = artistAdminService.findAllArtist() + List artistDetailApiFormResponses = artistAdminService.findAllWithGenreNames() .stream() .map(ArtistDetailApiFormResponse::new) .toList(); @@ -54,7 +56,9 @@ public String findAllArtist(Model model) { @GetMapping("/{id}") public String findArtist(@PathVariable("id") UUID id, Model model) { - List genres = genreAdminService.findAllGenres(); + List genres = genreAdminService.findAllGenres().stream() + .map(GenreNameApiFormResponse::new) + .toList(); model.addAttribute("genres", genres); ArtistDetailServiceResponse artistDetailServiceResponse = artistAdminService.findArtistById( @@ -67,8 +71,10 @@ public String findArtist(@PathVariable("id") UUID id, Model model) { } @PutMapping("/{id}") - public String updateArtist(@PathVariable("id") UUID id, - @Valid ArtistUpdateApiForm artistUpdateApiForm) { + public String updateArtist( + @PathVariable("id") UUID id, + @Valid ArtistUpdateApiForm artistUpdateApiForm + ) { artistAdminService.updateArtist(id, artistUpdateApiForm.toArtistUpdateServiceRequest()); return "redirect:/admin/artists/list"; } diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistCreateApiForm.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistCreateApiForm.java index d0983db4..0f777afc 100644 --- a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistCreateApiForm.java +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistCreateApiForm.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.UUID; +import org.springframework.web.multipart.MultipartFile; public record ArtistCreateApiForm( @NotBlank(message = "아티스트의 한국 이름은 필수 요청값 입니다.") @@ -15,6 +16,9 @@ public record ArtistCreateApiForm( @NotBlank(message = "아티스트의 영어 이름은 필수 요청값 입니다.") String englishName, + @NotNull(message = "아티스트의 이미지는 필수 요청값 입니다.") + MultipartFile image, + @NotBlank(message = "아티스트의 국적은 필수 요청값 입니다.") String country, @@ -32,6 +36,7 @@ public ArtistCreateServiceRequest toArtistCreateServiceRequest() { return ArtistCreateServiceRequest.builder() .koreanName(koreanName) .englishName(englishName) + .image(image) .country(country) .artistGenderApiType(artistGenderApiType) .artistApiType(artistApiType) diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUpdateApiForm.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUpdateApiForm.java index 9bfc837c..5bb08920 100644 --- a/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUpdateApiForm.java +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/request/ArtistUpdateApiForm.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.UUID; +import org.springframework.web.multipart.MultipartFile; public record ArtistUpdateApiForm( @NotBlank(message = "아티스트의 한국 이름은 필수 요청값 입니다.") @@ -15,6 +16,9 @@ public record ArtistUpdateApiForm( @NotBlank(message = "아티스트의 영어 이름은 필수 요청값 입니다.") String englishName, + @NotNull(message = "아티스트의 이미지는 필수 요청값 입니다.") + MultipartFile image, + @NotBlank(message = "아티스트의 국적은 필수 요청값 입니다.") String country, @@ -32,6 +36,7 @@ public ArtistUpdateServiceRequest toArtistUpdateServiceRequest() { return ArtistUpdateServiceRequest.builder() .koreanName(koreanName) .englishName(englishName) + .image(image) .country(country) .artistGenderApiType(artistGenderApiType) .artistApiType(artistApiType) diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistDetailApiFormResponse.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistDetailApiFormResponse.java index 0b79f1a2..7687602c 100644 --- a/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistDetailApiFormResponse.java +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistDetailApiFormResponse.java @@ -12,6 +12,7 @@ public record ArtistDetailApiFormResponse( UUID id, String koreanName, String englishName, + String image, String country, ArtistGenderApiType artistGenderApiType, ArtistApiType artistApiType, @@ -24,6 +25,7 @@ public ArtistDetailApiFormResponse( artistDetailServiceResponse.id(), artistDetailServiceResponse.koreanName(), artistDetailServiceResponse.englishName(), + artistDetailServiceResponse.image(), artistDetailServiceResponse.country(), artistDetailServiceResponse.artistGenderApiType(), artistDetailServiceResponse.artistApiType(), diff --git a/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistKoreanNameApiResponse.java b/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistKoreanNameApiResponse.java new file mode 100644 index 00000000..9571a34f --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/controller/dto/response/ArtistKoreanNameApiResponse.java @@ -0,0 +1,20 @@ +package com.example.artist.controller.dto.response; + +import com.example.artist.service.dto.response.ArtistKoreanNameServiceResponse; +import java.util.UUID; + +public record ArtistKoreanNameApiResponse( + UUID id, + String koreanName +) { + + public ArtistKoreanNameApiResponse( + ArtistKoreanNameServiceResponse artistKoreanNameServiceResponse) { + this( + artistKoreanNameServiceResponse.id(), + artistKoreanNameServiceResponse.koreanName() + ); + } + + +} diff --git a/app/api/show-api/src/main/java/com/example/artist/service/ArtistAdminService.java b/app/api/show-api/src/main/java/com/example/artist/service/ArtistAdminService.java index 5dfad41d..861970b7 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/ArtistAdminService.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/ArtistAdminService.java @@ -3,10 +3,13 @@ import com.example.artist.service.dto.request.ArtistCreateServiceRequest; import com.example.artist.service.dto.request.ArtistUpdateServiceRequest; import com.example.artist.service.dto.response.ArtistDetailServiceResponse; +import com.example.artist.service.dto.response.ArtistKoreanNameServiceResponse; +import com.example.component.FileUploadComponent; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.example.dto.artist.response.ArtistDetailResponse; +import org.example.dto.artist.response.ArtistKoreanNameResponse; import org.example.entity.artist.Artist; import org.example.usecase.artist.ArtistUseCase; import org.springframework.stereotype.Service; @@ -16,25 +19,37 @@ public class ArtistAdminService { private final ArtistUseCase artistUseCase; + private final FileUploadComponent fileUploadComponent; public void save(ArtistCreateServiceRequest artistCreateServiceRequest) { - Artist artist = artistCreateServiceRequest.toArtist(); + String imageUrl = fileUploadComponent.uploadFile("artist", artistCreateServiceRequest.image()); + + Artist artist = artistCreateServiceRequest.toArtistWithImageUrl(imageUrl); artistUseCase.save(artist, artistCreateServiceRequest.genreIds()); } - public List findAllArtist() { + public List findAllWithGenreNames() { List artistDetailResponses = artistUseCase.findAllWithGenreNames(); return artistDetailResponses.stream() .map(ArtistDetailServiceResponse::new) .toList(); } + public List findAllArtistKoreanName() { + List artistKoreanNameResponses = artistUseCase.findAllArtistKoreanName(); + return artistKoreanNameResponses.stream() + .map(ArtistKoreanNameServiceResponse::new) + .toList(); + } + public ArtistDetailServiceResponse findArtistById(UUID id) { return new ArtistDetailServiceResponse(artistUseCase.findArtistDetailById(id)); } public void updateArtist(UUID id, ArtistUpdateServiceRequest artistUpdateServiceRequest) { - Artist artist = artistUpdateServiceRequest.toArtist(); + String imageUrl = fileUploadComponent.uploadFile("artist", artistUpdateServiceRequest.image()); + + Artist artist = artistUpdateServiceRequest.toArtist(imageUrl); artistUseCase.updateArtist(id, artist, artistUpdateServiceRequest.genreIds()); } diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java index f57851bf..9d85da69 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistCreateServiceRequest.java @@ -8,21 +8,24 @@ import org.example.entity.artist.Artist; import org.example.entity.artist.ArtistGender; import org.example.entity.artist.ArtistType; +import org.springframework.web.multipart.MultipartFile; @Builder public record ArtistCreateServiceRequest( String koreanName, String englishName, + MultipartFile image, String country, ArtistGenderApiType artistGenderApiType, ArtistApiType artistApiType, List genreIds ) { - public Artist toArtist() { + public Artist toArtistWithImageUrl(String imageUrl) { return Artist.builder() .koreanName(koreanName) .englishName(englishName) + .image(imageUrl) .country(country) .artistGender(ArtistGender.valueOf(artistGenderApiType.name())) .artistType(ArtistType.valueOf(artistApiType.name())) diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java index 53ad6864..e702ce8d 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/request/ArtistUpdateServiceRequest.java @@ -8,21 +8,24 @@ import org.example.entity.artist.Artist; import org.example.entity.artist.ArtistGender; import org.example.entity.artist.ArtistType; +import org.springframework.web.multipart.MultipartFile; @Builder public record ArtistUpdateServiceRequest( String koreanName, String englishName, + MultipartFile image, String country, ArtistGenderApiType artistGenderApiType, ArtistApiType artistApiType, List genreIds ) { - public Artist toArtist() { + public Artist toArtist(String imageUrl) { return Artist.builder() .koreanName(koreanName) .englishName(englishName) + .image(imageUrl) .country(country) .artistGender(ArtistGender.valueOf(artistGenderApiType.name())) .artistType(ArtistType.valueOf(artistApiType.name())) diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistDetailServiceResponse.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistDetailServiceResponse.java index 1712d3c2..5ef976e2 100644 --- a/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistDetailServiceResponse.java +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistDetailServiceResponse.java @@ -10,6 +10,7 @@ public record ArtistDetailServiceResponse( UUID id, String koreanName, String englishName, + String image, String country, ArtistGenderApiType artistGenderApiType, ArtistApiType artistApiType, @@ -21,6 +22,7 @@ public ArtistDetailServiceResponse(ArtistDetailResponse artistDetailResponse) { artistDetailResponse.id(), artistDetailResponse.koreanName(), artistDetailResponse.englishName(), + artistDetailResponse.image(), artistDetailResponse.country(), ArtistGenderApiType.valueOf(artistDetailResponse.artistGender().name()), ArtistApiType.valueOf(artistDetailResponse.artistType().name()), diff --git a/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistKoreanNameServiceResponse.java b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistKoreanNameServiceResponse.java new file mode 100644 index 00000000..3a6771a8 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/artist/service/dto/response/ArtistKoreanNameServiceResponse.java @@ -0,0 +1,18 @@ +package com.example.artist.service.dto.response; + +import java.util.UUID; +import org.example.dto.artist.response.ArtistKoreanNameResponse; + +public record ArtistKoreanNameServiceResponse( + UUID id, + String koreanName +) { + public ArtistKoreanNameServiceResponse(ArtistKoreanNameResponse artistKoreanNameResponse) { + this( + artistKoreanNameResponse.id(), + artistKoreanNameResponse.koreanName() + ); + } + + +} diff --git a/app/api/show-api/src/main/java/com/example/component/FileUploadComponent.java b/app/api/show-api/src/main/java/com/example/component/FileUploadComponent.java new file mode 100644 index 00000000..35de8f88 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/component/FileUploadComponent.java @@ -0,0 +1,11 @@ +package com.example.component; + +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@Component +public interface FileUploadComponent { + + String uploadFile(String directory, MultipartFile multipartFile); + +} diff --git a/app/api/show-api/src/main/java/com/example/genre/controller/dto/response/GenreNameApiFormResponse.java b/app/api/show-api/src/main/java/com/example/genre/controller/dto/response/GenreNameApiFormResponse.java index 2dc0ea83..dd16cb2a 100644 --- a/app/api/show-api/src/main/java/com/example/genre/controller/dto/response/GenreNameApiFormResponse.java +++ b/app/api/show-api/src/main/java/com/example/genre/controller/dto/response/GenreNameApiFormResponse.java @@ -1,5 +1,6 @@ package com.example.genre.controller.dto.response; +import com.example.genre.service.dto.response.GenreNameServiceResponse; import java.util.UUID; public record GenreNameApiFormResponse( @@ -7,4 +8,11 @@ public record GenreNameApiFormResponse( String name ) { + public GenreNameApiFormResponse(GenreNameServiceResponse genreNameServiceResponse) { + this ( + genreNameServiceResponse.id(), + genreNameServiceResponse.name() + ); + } + } diff --git a/app/api/show-api/src/main/java/com/example/genre/service/dto/response/GenreNameServiceResponse.java b/app/api/show-api/src/main/java/com/example/genre/service/dto/response/GenreNameServiceResponse.java index e7546d94..f2e07d14 100644 --- a/app/api/show-api/src/main/java/com/example/genre/service/dto/response/GenreNameServiceResponse.java +++ b/app/api/show-api/src/main/java/com/example/genre/service/dto/response/GenreNameServiceResponse.java @@ -1,10 +1,18 @@ package com.example.genre.service.dto.response; import java.util.UUID; +import org.example.dto.artist.response.GenreNameResponse; public record GenreNameServiceResponse( UUID id, String name ) { + public GenreNameServiceResponse(GenreNameResponse genreNameResponse) { + this( + genreNameResponse.id(), + genreNameResponse.name() + ); + } + } diff --git a/app/api/show-api/src/main/java/com/example/show/controller/ShowAdminController.java b/app/api/show-api/src/main/java/com/example/show/controller/ShowAdminController.java index afbb2664..a27620a9 100644 --- a/app/api/show-api/src/main/java/com/example/show/controller/ShowAdminController.java +++ b/app/api/show-api/src/main/java/com/example/show/controller/ShowAdminController.java @@ -1,5 +1,104 @@ package com.example.show.controller; +import com.example.artist.controller.dto.response.ArtistKoreanNameApiResponse; +import com.example.artist.service.ArtistAdminService; +import com.example.genre.controller.dto.response.GenreNameApiFormResponse; +import com.example.genre.service.GenreAdminService; +import com.example.show.controller.dto.request.ShowCreateApiForm; +import com.example.show.controller.dto.request.ShowUpdateApiForm; +import com.example.show.controller.dto.response.ShowInfoApiResponse; +import com.example.show.service.ShowAdminService; +import com.example.show.service.dto.response.ShowInfoServiceResponse; +import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequiredArgsConstructor +@RequestMapping("/admin/shows") public class ShowAdminController { + private final ShowAdminService showAdminService; + private final ArtistAdminService artistAdminService; + private final GenreAdminService genreAdminService; + + @GetMapping + public String createShow(Model model) { + List artists = artistAdminService.findAllArtistKoreanName() + .stream() + .map(ArtistKoreanNameApiResponse::new) + .toList(); + + List genres = genreAdminService.findAllGenres() + .stream() + .map(GenreNameApiFormResponse::new) + .toList(); + + model.addAttribute("artists", artists); + model.addAttribute("genres", genres); + return "show_create_form"; + } + + @PostMapping + public String createShow(@Valid ShowCreateApiForm showCreateApiForm) { + showAdminService.save(showCreateApiForm.toServiceRequest()); + return "redirect:/admin/shows/list"; + } + + @GetMapping("/list") + public String findAllShow(Model model) { + List shows = showAdminService.findAllShowInfos() + .stream() + .map(ShowInfoApiResponse::new) + .toList(); + + model.addAttribute("shows", shows); + return "show_list_form"; + } + + @GetMapping("/{id}") + public String findShow(@PathVariable("id") UUID id, Model model) { + List artists = artistAdminService.findAllArtistKoreanName() + .stream() + .map(ArtistKoreanNameApiResponse::new) + .toList(); + + List genres = genreAdminService.findAllGenres() + .stream() + .map(GenreNameApiFormResponse::new) + .toList(); + + ShowInfoServiceResponse showInfoServiceResponse = showAdminService.findShowInfo(id); + ShowInfoApiResponse shows = new ShowInfoApiResponse(showInfoServiceResponse); + + model.addAttribute("artists", artists); + model.addAttribute("genres", genres); + model.addAttribute("shows", shows); + + return "show_form"; + } + + @PutMapping("/{id}") + public String updateArtist( + @PathVariable("id") UUID id, + @Valid ShowUpdateApiForm showUpdateApiForm + ) { + showAdminService.updateShow(id, showUpdateApiForm.toServiceRequest()); + return "redirect:/admin/shows/list"; + } + + @DeleteMapping("/{id}") + public String deleteShow(@PathVariable("id") UUID id) { + showAdminService.deleteShow(id); + return "redirect:/admin/shows/list"; + } } diff --git a/app/api/show-api/src/main/java/com/example/show/controller/dto/request/ShowCreateApiForm.java b/app/api/show-api/src/main/java/com/example/show/controller/dto/request/ShowCreateApiForm.java new file mode 100644 index 00000000..0163573e --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/controller/dto/request/ShowCreateApiForm.java @@ -0,0 +1,84 @@ +package com.example.show.controller.dto.request; + +import com.example.show.controller.dto.response.SeatInfoApiResponse; +import com.example.show.controller.dto.response.TicketingInfoApiResponse; +import com.example.show.service.dto.request.ShowCreateServiceRequest; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.springframework.web.multipart.MultipartFile; + +public record ShowCreateApiForm( + + @NotBlank(message = "공연 제목은 필수 요청값 입니다.") + String title, + + @NotBlank(message = "공연 내용은 필수 요청값 입니다.") + String content, + + @NotNull(message = "공연 날짜는 필수 요청값 입니다.") + LocalDate date, + + @NotNull(message = "공연 장소는 필수 요청값 입니다.") + String location, + + @NotNull(message = "공연 포스터는 필수 요청값 입니다.") + MultipartFile post, + + @NotNull(message = "공연 좌석 타입은 필수 요청값 입니다.") + List seatTypes, + + @NotNull(message = "공연 좌석별 가격은 필수 요청값 입니다.") + List pricesPerSeatType, + + @NotNull(message = "공연 티켓 오픈 시간은 필수 요청값 입니다.") + LocalDateTime ticketOpenTime, + + @NotNull(message = "티켓팅 예약 사이트명은 필수 요청값 입니다.") + List ticketBookingSites, + + @NotNull(message = "티켓팅 예약 사이트 URL는 필수 요청값 입니다.") + List ticketingSiteUrls, + + @NotNull(message = "아티스트 ID는 필수 요청값 입니다.") + List artistIds, + + @NotNull(message = "장르 ID는 필수 요청값 입니다.") + List genreIds +) { + + public ShowCreateServiceRequest toServiceRequest() { + return ShowCreateServiceRequest.builder() + .title(title) + .content(content) + .date(date) + .location(location) + .post(post) + .seatInfoApiResponse(getSeatInfoApiResponse()) + .ticketingInfoApiResponse(getTicketingInfoApiResponse()) + .artistIds(artistIds) + .genreIds(genreIds) + .build(); + } + + private SeatInfoApiResponse getSeatInfoApiResponse() { + Map priceInformation = IntStream.range(0, seatTypes.size()) + .boxed() + .collect(Collectors.toMap(seatTypes::get, pricesPerSeatType::get)); + return new SeatInfoApiResponse(priceInformation); + } + + private TicketingInfoApiResponse getTicketingInfoApiResponse() { + Map ticketingInformation = IntStream.range(0, ticketBookingSites.size()) + .boxed() + .collect(Collectors.toMap(ticketBookingSites::get, ticketingSiteUrls::get)); + return new TicketingInfoApiResponse(ticketOpenTime, ticketingInformation); + } + +} diff --git a/app/api/show-api/src/main/java/com/example/show/controller/dto/request/ShowUpdateApiForm.java b/app/api/show-api/src/main/java/com/example/show/controller/dto/request/ShowUpdateApiForm.java new file mode 100644 index 00000000..00f646c0 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/controller/dto/request/ShowUpdateApiForm.java @@ -0,0 +1,84 @@ +package com.example.show.controller.dto.request; + +import com.example.show.controller.dto.response.SeatInfoApiResponse; +import com.example.show.controller.dto.response.TicketingInfoApiResponse; +import com.example.show.service.dto.request.ShowUpdateServiceRequest; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.springframework.web.multipart.MultipartFile; + +public record ShowUpdateApiForm( + + @NotBlank(message = "공연 제목은 필수 요청값 입니다.") + String title, + + @NotBlank(message = "공연 내용은 필수 요청값 입니다.") + String content, + + @NotNull(message = "공연 날짜는 필수 요청값 입니다.") + LocalDate date, + + @NotNull(message = "공연 장소는 필수 요청값 입니다.") + String location, + + @NotNull(message = "공연 포스터는 필수 요청값 입니다.") + MultipartFile post, + + @NotNull(message = "공연 좌석 타입은 필수 요청값 입니다.") + List seatTypes, + + @NotNull(message = "공연 좌석별 가격은 필수 요청값 입니다.") + List pricesPerSeatType, + + @NotNull(message = "공연 티켓 오픈 시간은 필수 요청값 입니다.") + LocalDateTime ticketOpenTime, + + @NotNull(message = "티켓팅 예약 사이트명은 필수 요청값 입니다.") + List ticketBookingSites, + + @NotNull(message = "티켓팅 예약 사이트 URL는 필수 요청값 입니다.") + List ticketingSiteUrls, + + @NotNull(message = "아티스트 ID는 필수 요청값 입니다.") + List artistIds, + + @NotNull(message = "장르 ID는 필수 요청값 입니다.") + List genreIds +) { + + public ShowUpdateServiceRequest toServiceRequest() { + return ShowUpdateServiceRequest.builder() + .title(title) + .content(content) + .date(date) + .location(location) + .post(post) + .seatInfoApiResponse(getSeatInfoApiResponse()) + .ticketingInfoApiResponse(getTicketingInfoApiResponse()) + .artistIds(artistIds) + .genreIds(genreIds) + .build(); + } + + private SeatInfoApiResponse getSeatInfoApiResponse() { + Map priceInformation = IntStream.range(0, seatTypes.size()) + .boxed() + .collect(Collectors.toMap(seatTypes::get, pricesPerSeatType::get)); + return new SeatInfoApiResponse(priceInformation); + } + + private TicketingInfoApiResponse getTicketingInfoApiResponse() { + Map ticketingInformation = IntStream.range(0, ticketBookingSites.size()) + .boxed() + .collect(Collectors.toMap(ticketBookingSites::get, ticketingSiteUrls::get)); + return new TicketingInfoApiResponse(ticketOpenTime, ticketingInformation); + } + +} diff --git a/app/api/show-api/src/main/java/com/example/show/controller/dto/response/SeatInfoApiResponse.java b/app/api/show-api/src/main/java/com/example/show/controller/dto/response/SeatInfoApiResponse.java new file mode 100644 index 00000000..2d8e253c --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/controller/dto/response/SeatInfoApiResponse.java @@ -0,0 +1,15 @@ +package com.example.show.controller.dto.response; + +import java.util.Map; +import org.example.entity.show.info.SeatPrice; + +public record SeatInfoApiResponse( + Map priceInformation +) { + + public static SeatInfoApiResponse from(SeatPrice seatPrice) { + return new SeatInfoApiResponse(seatPrice.getPriceInformation()); + } + + +} diff --git a/app/api/show-api/src/main/java/com/example/show/controller/dto/response/ShowInfoApiResponse.java b/app/api/show-api/src/main/java/com/example/show/controller/dto/response/ShowInfoApiResponse.java new file mode 100644 index 00000000..42182504 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/controller/dto/response/ShowInfoApiResponse.java @@ -0,0 +1,39 @@ +package com.example.show.controller.dto.response; + +import com.example.artist.service.dto.response.ArtistKoreanNameServiceResponse; +import com.example.genre.service.dto.response.GenreNameServiceResponse; +import com.example.show.service.dto.response.ShowInfoServiceResponse; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +public record ShowInfoApiResponse( + + UUID id, + String title, + String content, + LocalDate date, + String location, + String image, + SeatInfoApiResponse seatInfoApiResponse, + TicketingInfoApiResponse ticketingInfoApiResponse, + List artistKoreanNameResponses, + List genreNameResponses + +) { + public ShowInfoApiResponse(ShowInfoServiceResponse showInfoServiceResponse) { + this( + showInfoServiceResponse.id(), + showInfoServiceResponse.title(), + showInfoServiceResponse.content(), + showInfoServiceResponse.date(), + showInfoServiceResponse.location(), + showInfoServiceResponse.image(), + showInfoServiceResponse.seatInfoApiResponse(), + showInfoServiceResponse.ticketingInfoApiResponse(), + showInfoServiceResponse.artistKoreanNameResponses(), + showInfoServiceResponse.genreNameResponses() + ); + } + +} diff --git a/app/api/show-api/src/main/java/com/example/show/controller/dto/response/TicketingInfoApiResponse.java b/app/api/show-api/src/main/java/com/example/show/controller/dto/response/TicketingInfoApiResponse.java new file mode 100644 index 00000000..9e15a474 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/controller/dto/response/TicketingInfoApiResponse.java @@ -0,0 +1,19 @@ +package com.example.show.controller.dto.response; + +import java.time.LocalDateTime; +import java.util.Map; +import org.example.entity.show.info.Ticketing; + +public record TicketingInfoApiResponse( + LocalDateTime ticketOpenTime, + Map ticketingInformation +) { + + public static TicketingInfoApiResponse from(Ticketing ticketing) { + return new TicketingInfoApiResponse( + ticketing.getTicketOpenTime(), + ticketing.getTicketingInformation() + ); + } + +} diff --git a/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java b/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java new file mode 100644 index 00000000..4c6bfe24 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/service/ShowAdminService.java @@ -0,0 +1,57 @@ +package com.example.show.service; + + +import com.example.component.FileUploadComponent; +import com.example.show.service.dto.request.ShowCreateServiceRequest; +import com.example.show.service.dto.request.ShowUpdateServiceRequest; +import com.example.show.service.dto.response.ShowInfoServiceResponse; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.example.dto.artist.response.ShowInfoResponse; +import org.example.entity.show.Show; +import org.example.usecase.show.ShowUseCase; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ShowAdminService { + + private final ShowUseCase showUseCase; + private final FileUploadComponent fileUploadComponent; + + public void save(ShowCreateServiceRequest showCreateServiceRequest) { + String imageUrl = fileUploadComponent.uploadFile("show", showCreateServiceRequest.post()); + Show show = showCreateServiceRequest.toShowWithImageUrl(imageUrl); + + showUseCase.save( + show, + showCreateServiceRequest.artistIds(), + showCreateServiceRequest.genreIds() + ); + } + + public List findAllShowInfos() { + List showInfoResponses = showUseCase.findAllShowInfos(); + return showInfoResponses.stream() + .map(ShowInfoServiceResponse::new) + .toList(); + } + + public ShowInfoServiceResponse findShowInfo(UUID id) { + ShowInfoResponse showInfoResponse = showUseCase.findShowInfo(id); + return new ShowInfoServiceResponse(showInfoResponse); + } + + public void updateShow(UUID id, ShowUpdateServiceRequest showUpdateServiceRequest) { + String imageUrl = fileUploadComponent.uploadFile("show", showUpdateServiceRequest.post()); + Show show = showUpdateServiceRequest.toShowWithImageUrl(imageUrl); + + showUseCase.updateShow(id, show, showUpdateServiceRequest.artistIds(), + showUpdateServiceRequest.genreIds()); + } + + public void deleteShow(UUID id) { + showUseCase.deleteShow(id); + } +} diff --git a/app/api/show-api/src/main/java/com/example/show/service/dto/request/ShowCreateServiceRequest.java b/app/api/show-api/src/main/java/com/example/show/service/dto/request/ShowCreateServiceRequest.java new file mode 100644 index 00000000..304bc586 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/service/dto/request/ShowCreateServiceRequest.java @@ -0,0 +1,55 @@ +package com.example.show.service.dto.request; + +import com.example.show.controller.dto.response.SeatInfoApiResponse; +import com.example.show.controller.dto.response.TicketingInfoApiResponse; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import lombok.Builder; +import org.example.entity.show.Show; +import org.example.entity.show.info.SeatPrice; +import org.example.entity.show.info.Ticketing; +import org.springframework.web.multipart.MultipartFile; + +@Builder +public record ShowCreateServiceRequest( + + String title, + String content, + LocalDate date, + String location, + MultipartFile post, + SeatInfoApiResponse seatInfoApiResponse, + TicketingInfoApiResponse ticketingInfoApiResponse, + List artistIds, + List genreIds +) { + + public Show toShowWithImageUrl(String imageUrl) { + return Show.builder() + .title(title) + .content(content) + .date(date) + .location(location) + .image(imageUrl) + .seatPrice(getSeatPrice()) + .ticketing(getTicketing()) + .build(); + } + + private SeatPrice getSeatPrice() { + SeatPrice seatPrice = new SeatPrice(); + seatInfoApiResponse.priceInformation().forEach(seatPrice::savePriceInformation); + + return seatPrice; + } + + private Ticketing getTicketing() { + Ticketing ticketing = new Ticketing(); + ticketing.saveTicketOpenTime(ticketingInfoApiResponse.ticketOpenTime()); + ticketingInfoApiResponse.ticketingInformation().forEach(ticketing::saveTicketingInformation); + + return ticketing; + } + +} diff --git a/app/api/show-api/src/main/java/com/example/show/service/dto/request/ShowUpdateServiceRequest.java b/app/api/show-api/src/main/java/com/example/show/service/dto/request/ShowUpdateServiceRequest.java new file mode 100644 index 00000000..d83e4f35 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/service/dto/request/ShowUpdateServiceRequest.java @@ -0,0 +1,55 @@ +package com.example.show.service.dto.request; + +import com.example.show.controller.dto.response.SeatInfoApiResponse; +import com.example.show.controller.dto.response.TicketingInfoApiResponse; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import lombok.Builder; +import org.example.entity.show.Show; +import org.example.entity.show.info.SeatPrice; +import org.example.entity.show.info.Ticketing; +import org.springframework.web.multipart.MultipartFile; + +@Builder +public record ShowUpdateServiceRequest( + + String title, + String content, + LocalDate date, + String location, + MultipartFile post, + SeatInfoApiResponse seatInfoApiResponse, + TicketingInfoApiResponse ticketingInfoApiResponse, + List artistIds, + List genreIds +) { + + public Show toShowWithImageUrl(String imageUrl) { + return Show.builder() + .title(title) + .content(content) + .date(date) + .location(location) + .image(imageUrl) + .seatPrice(getSeatPrice()) + .ticketing(getTicketing()) + .build(); + } + + private SeatPrice getSeatPrice() { + SeatPrice seatPrice = new SeatPrice(); + seatInfoApiResponse.priceInformation().forEach(seatPrice::savePriceInformation); + + return seatPrice; + } + + private Ticketing getTicketing() { + Ticketing ticketing = new Ticketing(); + ticketing.saveTicketOpenTime(ticketingInfoApiResponse.ticketOpenTime()); + ticketingInfoApiResponse.ticketingInformation().forEach(ticketing::saveTicketingInformation); + + return ticketing; + } + +} diff --git a/app/api/show-api/src/main/java/com/example/show/service/dto/response/ShowInfoServiceResponse.java b/app/api/show-api/src/main/java/com/example/show/service/dto/response/ShowInfoServiceResponse.java new file mode 100644 index 00000000..3a4b5d05 --- /dev/null +++ b/app/api/show-api/src/main/java/com/example/show/service/dto/response/ShowInfoServiceResponse.java @@ -0,0 +1,58 @@ +package com.example.show.service.dto.response; + +import com.example.artist.service.dto.response.ArtistKoreanNameServiceResponse; +import com.example.genre.service.dto.response.GenreNameServiceResponse; +import com.example.show.controller.dto.response.SeatInfoApiResponse; +import com.example.show.controller.dto.response.TicketingInfoApiResponse; +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.example.dto.artist.response.ArtistKoreanNameResponse; +import org.example.dto.artist.response.GenreNameResponse; +import org.example.dto.artist.response.ShowInfoResponse; + +public record ShowInfoServiceResponse( + UUID id, + String title, + String content, + LocalDate date, + String location, + String image, + SeatInfoApiResponse seatInfoApiResponse, + TicketingInfoApiResponse ticketingInfoApiResponse, + List artistKoreanNameResponses, + List genreNameResponses +) { + + public ShowInfoServiceResponse(ShowInfoResponse showInfoResponse) { + this( + showInfoResponse.id(), + showInfoResponse.title(), + showInfoResponse.content(), + showInfoResponse.date(), + showInfoResponse.location(), + showInfoResponse.image(), + SeatInfoApiResponse.from(showInfoResponse.seatPrice()), + TicketingInfoApiResponse.from(showInfoResponse.ticketing()), + toArtistKoreanNameServiceResponses(showInfoResponse.artistKoreanNameResponses()), + toGenreNameServiceResponses(showInfoResponse.genreNameResponses()) + ); + } + + private static List toArtistKoreanNameServiceResponses( + Set artistKoreanNameResponses) { + return artistKoreanNameResponses + .stream() + .map(ArtistKoreanNameServiceResponse::new) + .toList(); + } + + private static List toGenreNameServiceResponses( + Set genreNameResponses) { + return genreNameResponses + .stream() + .map(GenreNameServiceResponse::new) + .toList(); + } +} diff --git a/app/api/show-api/src/test/java/artist/fixture/dto/ArtistDtoFixture.java b/app/api/show-api/src/test/java/artist/fixture/dto/ArtistDtoFixture.java new file mode 100644 index 00000000..ff82a59a --- /dev/null +++ b/app/api/show-api/src/test/java/artist/fixture/dto/ArtistDtoFixture.java @@ -0,0 +1,44 @@ +package artist.fixture.dto; + +import com.example.artist.service.dto.request.ArtistCreateServiceRequest; +import com.example.artist.service.dto.request.ArtistUpdateServiceRequest; +import com.example.artist.vo.ArtistApiType; +import com.example.artist.vo.ArtistGenderApiType; +import java.util.List; +import java.util.UUID; +import org.springframework.mock.web.MockMultipartFile; + +public class ArtistDtoFixture { + + private static final MockMultipartFile image = new MockMultipartFile( + "image", + "test_image.jpg", + "image/jpeg", + "test image content".getBytes() + ); + + + public static ArtistCreateServiceRequest artistCreateServiceRequest() { + return ArtistCreateServiceRequest.builder() + .koreanName("test_koreanName") + .englishName("test_englishName") + .image(image) + .country("test_country") + .artistGenderApiType(ArtistGenderApiType.MAN) + .artistApiType(ArtistApiType.SOLO) + .genreIds(List.of(UUID.randomUUID())) + .build(); + } + + public static ArtistUpdateServiceRequest artistUpdateServiceRequest() { + return ArtistUpdateServiceRequest.builder() + .koreanName("test_koreanName") + .englishName("test_englishName") + .image(image) + .country("test_country") + .artistGenderApiType(ArtistGenderApiType.MAN) + .artistApiType(ArtistApiType.SOLO) + .genreIds(List.of(UUID.randomUUID())) + .build(); + } +} diff --git a/app/api/show-api/src/test/java/artist/service/ArtistAdminServiceTest.java b/app/api/show-api/src/test/java/artist/service/ArtistAdminServiceTest.java new file mode 100644 index 00000000..9d8461c9 --- /dev/null +++ b/app/api/show-api/src/test/java/artist/service/ArtistAdminServiceTest.java @@ -0,0 +1,69 @@ +package artist.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import artist.fixture.dto.ArtistDtoFixture; +import com.example.artist.service.ArtistAdminService; +import com.example.artist.service.dto.request.ArtistCreateServiceRequest; +import com.example.artist.service.dto.request.ArtistUpdateServiceRequest; +import com.example.component.FileUploadComponent; +import java.util.UUID; +import org.example.entity.artist.Artist; +import org.example.usecase.artist.ArtistUseCase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ArtistAdminServiceTest { + + private final ArtistUseCase artistUseCase = mock(ArtistUseCase.class); + private final FileUploadComponent fileUploadComponent = mock(FileUploadComponent.class); + + private final ArtistAdminService artistAdminService = new ArtistAdminService( + artistUseCase, fileUploadComponent); + + @Test + @DisplayName("아티스트는 업로드된 이미지 URL과 함께 생성된다.") + void artistCreateWithUploadedImageUrl() { + //given + ArtistCreateServiceRequest artistCreateServiceRequest = ArtistDtoFixture.artistCreateServiceRequest(); + given( + fileUploadComponent.uploadFile( + "artist", + artistCreateServiceRequest.image() + ) + ).willReturn("test_imageUrl"); + + //when + artistAdminService.save(artistCreateServiceRequest); + + //then + verify(artistUseCase, times(1)).save(any(Artist.class), anyList()); + } + + @Test + @DisplayName("아티스트는 업로드된 이미지 URL과 함께 업데이트 된다.") + void artistUpdateWithUploadedImageUrl() { + //given + ArtistUpdateServiceRequest artistUpdateServiceRequest = ArtistDtoFixture.artistUpdateServiceRequest(); + UUID artistId = UUID.randomUUID(); + given( + fileUploadComponent.uploadFile( + "artist", + artistUpdateServiceRequest.image() + ) + ).willReturn("test_imageUrl"); + + //when + artistAdminService.updateArtist(artistId, artistUpdateServiceRequest); + + //then + verify(artistUseCase, times(1)).updateArtist(eq(artistId), any(Artist.class), anyList()); + } + +} diff --git a/app/api/show-api/src/test/java/show/fixture/dto/ShowDtoFixture.java b/app/api/show-api/src/test/java/show/fixture/dto/ShowDtoFixture.java new file mode 100644 index 00000000..6ccd3a3a --- /dev/null +++ b/app/api/show-api/src/test/java/show/fixture/dto/ShowDtoFixture.java @@ -0,0 +1,71 @@ +package show.fixture.dto; + +import com.example.show.controller.dto.response.SeatInfoApiResponse; +import com.example.show.controller.dto.response.TicketingInfoApiResponse; +import com.example.show.service.dto.request.ShowCreateServiceRequest; +import com.example.show.service.dto.request.ShowUpdateServiceRequest; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.springframework.mock.web.MockMultipartFile; + +public class ShowDtoFixture { + + private static final MockMultipartFile post = new MockMultipartFile( + "image", + "test_image.jpg", + "image/jpeg", + "test image content".getBytes() + ); + + public static ShowCreateServiceRequest showCreateServiceRequest() { + return ShowCreateServiceRequest.builder() + .title("test_title") + .content("test_content") + .date(LocalDate.EPOCH) + .location("test_location") + .post(post) + .seatInfoApiResponse(getSeatInfoApiResponse()) + .ticketingInfoApiResponse(getTicketingInfoApiResponse()) + .artistIds(List.of(UUID.randomUUID())) + .genreIds(List.of(UUID.randomUUID())) + .build(); + } + + public static ShowUpdateServiceRequest showUpdateServiceRequest() { + return ShowUpdateServiceRequest.builder() + .title("test_title") + .content("test_content") + .date(LocalDate.EPOCH) + .location("test_location") + .post(post) + .seatInfoApiResponse(getSeatInfoApiResponse()) + .ticketingInfoApiResponse(getTicketingInfoApiResponse()) + .artistIds(List.of(UUID.randomUUID())) + .genreIds(List.of(UUID.randomUUID())) + .build(); + } + + private static SeatInfoApiResponse getSeatInfoApiResponse() { + return new SeatInfoApiResponse( + Map.of( + "VIP", 150000, + "R", 120000, + "S", 100000, + "A", 80000 + ) + ); + } + + private static TicketingInfoApiResponse getTicketingInfoApiResponse() { + return new TicketingInfoApiResponse( + LocalDateTime.MAX, + Map.of( + "YES24", "https://YES24URL", + "인터파크", "https://인터파크URL" + ) + ); + } +} diff --git a/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java b/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java new file mode 100644 index 00000000..dcbbaafb --- /dev/null +++ b/app/api/show-api/src/test/java/show/service/ShowAdminServiceTest.java @@ -0,0 +1,69 @@ +package show.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.example.component.FileUploadComponent; +import com.example.show.service.ShowAdminService; +import com.example.show.service.dto.request.ShowCreateServiceRequest; +import com.example.show.service.dto.request.ShowUpdateServiceRequest; +import java.util.UUID; +import org.example.entity.show.Show; +import org.example.usecase.show.ShowUseCase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import show.fixture.dto.ShowDtoFixture; + +class ShowAdminServiceTest { + + private final ShowUseCase showUseCase = mock(ShowUseCase.class); + private final FileUploadComponent fileUploadComponent = mock(FileUploadComponent.class); + + private final ShowAdminService showAdminService = new ShowAdminService( + showUseCase, fileUploadComponent); + + @Test + @DisplayName("공연은 업로드된 이미지 URL과 함께 생성된다.") + void showCreateWithUploadedImageUrl() { + //given + ShowCreateServiceRequest showCreateServiceRequest = ShowDtoFixture.showCreateServiceRequest(); + given( + fileUploadComponent.uploadFile( + "artist", + showCreateServiceRequest.post() + ) + ).willReturn("test_imageUrl"); + + //when + showAdminService.save(showCreateServiceRequest); + + //then + verify(showUseCase, times(1)).save(any(Show.class), anyList(), anyList()); + } + + @Test + @DisplayName("공연은 업로드된 이미지 URL과 함께 업데이트 된다.") + void showUpdateWithUploadedImageUrl() { + //given + ShowUpdateServiceRequest showUpdateServiceRequest = ShowDtoFixture.showUpdateServiceRequest(); + UUID showId = UUID.randomUUID(); + given( + fileUploadComponent.uploadFile( + "artist", + showUpdateServiceRequest.post() + ) + ).willReturn("test_imageUrl"); + + //when + showAdminService.updateShow(showId, showUpdateServiceRequest); + + //then + verify(showUseCase, times(1)).updateShow(eq(showId), any(Show.class), anyList(), anyList()); + } + +} diff --git a/app/api/user-api/src/main/resources/templates/artist_create_form.html b/app/api/user-api/src/main/resources/templates/artist_create_form.html index 888734e1..b32bbdbc 100644 --- a/app/api/user-api/src/main/resources/templates/artist_create_form.html +++ b/app/api/user-api/src/main/resources/templates/artist_create_form.html @@ -3,19 +3,23 @@
-
+

추가할 아티스트

- +
- + +
+
+ +
- +
diff --git a/app/api/user-api/src/main/resources/templates/artist_form.html b/app/api/user-api/src/main/resources/templates/artist_form.html index 2a47a2b1..0f72508a 100644 --- a/app/api/user-api/src/main/resources/templates/artist_form.html +++ b/app/api/user-api/src/main/resources/templates/artist_form.html @@ -2,19 +2,23 @@

아티스트 수정

- +
- +
- + +
+
+ +
- +
diff --git a/app/api/user-api/src/main/resources/templates/artist_list_form.html b/app/api/user-api/src/main/resources/templates/artist_list_form.html index 22deed16..e13dab5f 100644 --- a/app/api/user-api/src/main/resources/templates/artist_list_form.html +++ b/app/api/user-api/src/main/resources/templates/artist_list_form.html @@ -7,6 +7,7 @@

아티스트 목록

아티스트 한국 이름 아티스트 영어 이름 + 아티스트 이미지 국적 성별 타입 @@ -18,6 +19,9 @@

아티스트 목록

+ + 아티스트 이미지 +
diff --git a/app/api/user-api/src/main/resources/templates/genre_create_form.html b/app/api/user-api/src/main/resources/templates/genre_create_form.html index 5e6191f8..4f3ecba3 100644 --- a/app/api/user-api/src/main/resources/templates/genre_create_form.html +++ b/app/api/user-api/src/main/resources/templates/genre_create_form.html @@ -10,7 +10,7 @@

추가할 장르

- +
diff --git a/app/api/user-api/src/main/resources/templates/genre_form.html b/app/api/user-api/src/main/resources/templates/genre_form.html index 0d6a1541..205115b3 100644 --- a/app/api/user-api/src/main/resources/templates/genre_form.html +++ b/app/api/user-api/src/main/resources/templates/genre_form.html @@ -6,7 +6,7 @@

장르 수정

- +
diff --git a/app/api/user-api/src/main/resources/templates/home.html b/app/api/user-api/src/main/resources/templates/home.html index 2c77256a..0a066cd9 100644 --- a/app/api/user-api/src/main/resources/templates/home.html +++ b/app/api/user-api/src/main/resources/templates/home.html @@ -1,6 +1,26 @@
+
+
+
+
공연 추가
+

새로운 공연을 추가합니다.

+ 추가하기 +
+
+
+
+
+
+
공연 조회
+

등록된 공연을 조회합니다.

+ 조회하기 +
+
+
+
+
diff --git a/app/api/user-api/src/main/resources/templates/show_create_form.html b/app/api/user-api/src/main/resources/templates/show_create_form.html new file mode 100644 index 00000000..b16c3ef1 --- /dev/null +++ b/app/api/user-api/src/main/resources/templates/show_create_form.html @@ -0,0 +1,137 @@ + +
+ + +

추가할 공연 정보

+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ +
+ +
+ + +
+
+
+
+ + +
+ +
+ +
+ + +
+
+
+
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+ + +
+ + diff --git a/app/api/user-api/src/main/resources/templates/show_form.html b/app/api/user-api/src/main/resources/templates/show_form.html new file mode 100644 index 00000000..f432017e --- /dev/null +++ b/app/api/user-api/src/main/resources/templates/show_form.html @@ -0,0 +1,138 @@ + +
+
+

공연 수정

+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ +
+ +
+ + +
+
+
+
+ + +
+ +
+ +
+ + +
+
+
+
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+ +
+
+ + + diff --git a/app/api/user-api/src/main/resources/templates/show_list_form.html b/app/api/user-api/src/main/resources/templates/show_list_form.html new file mode 100644 index 00000000..ca55e6d4 --- /dev/null +++ b/app/api/user-api/src/main/resources/templates/show_list_form.html @@ -0,0 +1,94 @@ + + +
+
+

공연 목록

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
제목내용날짜장소이미지좌석 가격 타입티켓팅 타입아티스트장르작업
제목내용날짜장소 + 공연 이미지 + 좌석 가격 타입 + 티켓팅 오픈 시간: 티켓팅 오픈 시간
+ 티켓팅 사이트: 티켓팅 사이트 +
+
    +
  • 아티스트
  • +
+
+
    +
  • 장르
  • +
+
+
+ +
+
+ + +
+
+
+
+ + + + diff --git a/app/domain/build.gradle b/app/domain/build.gradle index d0c68379..cfb74087 100644 --- a/app/domain/build.gradle +++ b/app/domain/build.gradle @@ -11,6 +11,8 @@ allprojects { annotationProcessor 'jakarta.annotation:jakarta.annotation-api' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + testImplementation "org.springframework.boot:spring-boot-starter-test" + tasks.named('test') { useJUnitPlatform() } diff --git a/app/domain/show-domain/src/main/java/org/example/config/ShowDomainConfig.java b/app/domain/show-domain/src/main/java/org/example/config/ShowDomainConfig.java index 536abeee..db717598 100644 --- a/app/domain/show-domain/src/main/java/org/example/config/ShowDomainConfig.java +++ b/app/domain/show-domain/src/main/java/org/example/config/ShowDomainConfig.java @@ -8,8 +8,11 @@ @Configuration @ComponentScan(basePackages = "org.example") @EntityScan(basePackages = "org.example.entity") -@EnableJpaRepositories(basePackages = {"org.example.repository.genre", - "org.example.repository.artist"}) +@EnableJpaRepositories(basePackages = { + "org.example.repository.genre", + "org.example.repository.artist", + "org.example.repository.show"} +) public class ShowDomainConfig { } \ No newline at end of file diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java index 3c140107..7cdffeb7 100644 --- a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistDetailResponse.java @@ -9,6 +9,7 @@ public record ArtistDetailResponse( UUID id, String koreanName, String englishName, + String image, String country, ArtistGender artistGender, ArtistType artistType, diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistKoreanNameResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistKoreanNameResponse.java new file mode 100644 index 00000000..4f8aa242 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ArtistKoreanNameResponse.java @@ -0,0 +1,10 @@ +package org.example.dto.artist.response; + +import java.util.UUID; + +public record ArtistKoreanNameResponse( + UUID id, + String koreanName +) { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/GenreNameResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/GenreNameResponse.java new file mode 100644 index 00000000..1353e7b4 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/GenreNameResponse.java @@ -0,0 +1,10 @@ +package org.example.dto.artist.response; + +import java.util.UUID; + +public record GenreNameResponse( + UUID id, + String name +) { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ShowInfoResponse.java b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ShowInfoResponse.java new file mode 100644 index 00000000..6ac8afd9 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/dto/artist/response/ShowInfoResponse.java @@ -0,0 +1,22 @@ +package org.example.dto.artist.response; + +import java.time.LocalDate; +import java.util.Set; +import java.util.UUID; +import org.example.entity.show.info.SeatPrice; +import org.example.entity.show.info.Ticketing; + +public record ShowInfoResponse( + UUID id, + String title, + String content, + LocalDate date, + String location, + String image, + SeatPrice seatPrice, + Ticketing ticketing, + Set artistKoreanNameResponses, + Set genreNameResponses +) { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java b/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java index 44f2d3cd..23d69b89 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/artist/Artist.java @@ -25,6 +25,9 @@ public class Artist extends BaseEntity { @Column(name = "english_name", nullable = false) private String englishName; + @Column(name = "image", nullable = false) + private String image; + @Column(name = "country", nullable = false) private String country; @@ -37,10 +40,11 @@ public class Artist extends BaseEntity { private ArtistType artistType; @Builder - private Artist(String koreanName, String englishName, String country, ArtistGender artistGender, + private Artist(String koreanName, String englishName, String image, String country, ArtistGender artistGender, ArtistType artistType) { this.koreanName = koreanName; this.englishName = englishName; + this.image = image; this.country = country; this.artistGender = artistGender; this.artistType = artistType; @@ -55,9 +59,10 @@ public List toArtistGenre(List genreIds) { .toList(); } - public void changeArtist(Artist newArtist) { + public void changeArtistInfo(Artist newArtist) { this.koreanName = newArtist.koreanName; this.englishName = newArtist.englishName; + this.image = newArtist.image; this.country = newArtist.country; this.artistGender = newArtist.artistGender; this.artistType = newArtist.artistType; diff --git a/app/domain/show-domain/src/main/java/org/example/entity/show/Show.java b/app/domain/show-domain/src/main/java/org/example/entity/show/Show.java index 41d56813..5be55baf 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/show/Show.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/show/Show.java @@ -5,7 +5,10 @@ import jakarta.persistence.Enumerated; import jakarta.persistence.Table; import java.time.LocalDate; +import java.util.List; +import java.util.UUID; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.example.entity.BaseEntity; @@ -38,4 +41,44 @@ public class Show extends BaseEntity { @Enumerated private Ticketing ticketing; + + @Builder + private Show(String title, String content, LocalDate date, String location, String image, + SeatPrice seatPrice, Ticketing ticketing) { + this.title = title; + this.content = content; + this.date = date; + this.location = location; + this.image = image; + this.seatPrice = seatPrice; + this.ticketing = ticketing; + } + + public List toShowArtist(List artistIds) { + return artistIds.stream() + .map(artistId -> ShowArtist.builder() + .showId(getId()) + .artistId(artistId) + .build()) + .toList(); + } + + public List toShowGenre(List genreIds) { + return genreIds.stream() + .map(genreId -> ShowGenre.builder() + .showId(getId()) + .genreId(genreId) + .build()) + .toList(); + } + + public void changeShowInfo(Show newShow) { + this.title = newShow.title; + this.content = newShow.content; + this.date = newShow.date; + this.location = newShow.location; + this.image = newShow.image; + this.seatPrice = newShow.seatPrice; + this.ticketing = newShow.ticketing; + } } \ No newline at end of file diff --git a/app/domain/show-domain/src/main/java/org/example/entity/show/ShowArtist.java b/app/domain/show-domain/src/main/java/org/example/entity/show/ShowArtist.java index 1faedf8e..322797af 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/show/ShowArtist.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/show/ShowArtist.java @@ -5,6 +5,7 @@ import jakarta.persistence.Table; import java.util.UUID; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.example.entity.BaseEntity; @@ -20,4 +21,10 @@ public class ShowArtist extends BaseEntity { @Column(name = "artist_id", nullable = false) private UUID artistId; + + @Builder + private ShowArtist(UUID showId, UUID artistId) { + this.showId = showId; + this.artistId = artistId; + } } diff --git a/app/domain/show-domain/src/main/java/org/example/entity/show/ShowGenre.java b/app/domain/show-domain/src/main/java/org/example/entity/show/ShowGenre.java index aaf74717..68e82130 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/show/ShowGenre.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/show/ShowGenre.java @@ -5,6 +5,7 @@ import jakarta.persistence.Table; import java.util.UUID; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.example.entity.BaseEntity; @@ -21,4 +22,9 @@ public class ShowGenre extends BaseEntity { @Column(name = "genre_id", nullable = false) private UUID genreId; + @Builder + private ShowGenre(UUID showId, UUID genreId) { + this.showId = showId; + this.genreId = genreId; + } } \ No newline at end of file diff --git a/app/domain/show-domain/src/main/java/org/example/entity/show/info/SeatPrice.java b/app/domain/show-domain/src/main/java/org/example/entity/show/info/SeatPrice.java index e3094ade..36ac8bbc 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/show/info/SeatPrice.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/show/info/SeatPrice.java @@ -17,4 +17,8 @@ public class SeatPrice { public void savePriceInformation(String seatType, Integer price) { priceInformation.put(seatType, price); } + + public Map getPriceInformation() { + return new HashMap<>(priceInformation); + } } diff --git a/app/domain/show-domain/src/main/java/org/example/entity/show/info/Ticketing.java b/app/domain/show-domain/src/main/java/org/example/entity/show/info/Ticketing.java index 6751e69d..f974012c 100644 --- a/app/domain/show-domain/src/main/java/org/example/entity/show/info/Ticketing.java +++ b/app/domain/show-domain/src/main/java/org/example/entity/show/info/Ticketing.java @@ -3,6 +3,7 @@ import io.hypersistence.utils.hibernate.type.json.JsonType; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import org.hibernate.annotations.Type; @@ -10,14 +11,29 @@ @Embeddable public class Ticketing { + @Column(name = "ticket_open_time", nullable = false) + private LocalDateTime ticketOpenTime; + @Type(JsonType.class) @Column(name = "ticketing", columnDefinition = "jsonb", nullable = false) private Map ticketingInformation = new HashMap<>(); + public void saveTicketOpenTime(LocalDateTime ticketOpenTime) { + this.ticketOpenTime = ticketOpenTime; + } + public void saveTicketingInformation( String ticketBookingSite, String ticketingSiteUrl ) { ticketingInformation.put(ticketBookingSite, ticketingSiteUrl); } + + public LocalDateTime getTicketOpenTime() { + return ticketOpenTime; + } + + public Map getTicketingInformation() { + return new HashMap<>(ticketingInformation); + } } diff --git a/app/domain/show-domain/src/main/java/org/example/error/ShowError.java b/app/domain/show-domain/src/main/java/org/example/error/ShowError.java new file mode 100644 index 00000000..1afbf0d0 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/error/ShowError.java @@ -0,0 +1,27 @@ +package org.example.error; + +import org.example.exception.BusinessError; + +public enum ShowError implements BusinessError { + ENTITY_NOT_FOUND_ERROR { + @Override + public int getHttpStatus() { + return 404; + } + + @Override + public String getErrorCode() { + return "SHW-001"; + } + + @Override + public String getClientMessage() { + return "존재하지 않은 공연입니다."; + } + + @Override + public String getLogMessage() { + return "요청 값이 잘못 처리되었습니다."; + } + }, +} diff --git a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistGenreRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistGenreRepository.java index 9217e417..c9bfaf8c 100644 --- a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistGenreRepository.java +++ b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistGenreRepository.java @@ -8,4 +8,6 @@ public interface ArtistGenreRepository extends JpaRepository { List findAllByArtistId(UUID artistId); + + List findAllByGenreId(UUID genreId); } diff --git a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java index 4151a1a8..251df911 100644 --- a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java +++ b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepository.java @@ -4,10 +4,13 @@ import java.util.Optional; import java.util.UUID; import org.example.dto.artist.response.ArtistDetailResponse; +import org.example.dto.artist.response.ArtistKoreanNameResponse; public interface ArtistQuerydslRepository { List findAllWithGenreNames(); Optional findArtistWithGenreNamesById(UUID id); + + List findAllArtistKoreanName(); } diff --git a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java index 25bb1e88..478811ea 100644 --- a/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java +++ b/app/domain/show-domain/src/main/java/org/example/repository/artist/ArtistQuerydslRepositoryImpl.java @@ -15,6 +15,7 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.example.dto.artist.response.ArtistDetailResponse; +import org.example.dto.artist.response.ArtistKoreanNameResponse; import org.springframework.stereotype.Repository; @Repository @@ -33,6 +34,7 @@ public List findAllWithGenreNames() { artist.id, artist.koreanName, artist.englishName, + artist.image, artist.country, artist.artistGender, artist.artistType, @@ -47,7 +49,6 @@ public Optional findArtistWithGenreNamesById(UUID id) { return Optional.ofNullable( createArtistJoinArtistGenreAndGenreQuery() .where(artist.id.eq(id)) - .where(artist.isDeleted.isFalse()) .transform( groupBy(artist.id).as( Projections.constructor( @@ -55,6 +56,7 @@ public Optional findArtistWithGenreNamesById(UUID id) { artist.id, artist.koreanName, artist.englishName, + artist.image, artist.country, artist.artistGender, artist.artistType, @@ -83,4 +85,17 @@ private BooleanExpression isGenreEqualArtistIdAndIsDeletedFalse() { } + @Override + public List findAllArtistKoreanName() { + return jpaQueryFactory + .select( + Projections.constructor( + ArtistKoreanNameResponse.class, + artist.id, + artist.koreanName + ) + ) + .from(artist) + .fetch(); + } } diff --git a/app/domain/show-domain/src/main/java/org/example/repository/show/ShowArtistRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowArtistRepository.java new file mode 100644 index 00000000..c2dc36dd --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowArtistRepository.java @@ -0,0 +1,12 @@ +package org.example.repository.show; + +import java.util.List; +import java.util.UUID; +import org.example.entity.show.ShowArtist; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ShowArtistRepository extends JpaRepository { + List findAllByShowId(UUID showId); + + List findAllByArtistId(UUID artistId); +} diff --git a/app/domain/show-domain/src/main/java/org/example/repository/show/ShowGenreRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowGenreRepository.java new file mode 100644 index 00000000..d76d3871 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowGenreRepository.java @@ -0,0 +1,13 @@ +package org.example.repository.show; + +import java.util.List; +import java.util.UUID; +import org.example.entity.show.ShowGenre; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ShowGenreRepository extends JpaRepository { + + List findAllByShowId(UUID showId); + + List findAllByGenreId(UUID genreId); +} diff --git a/app/domain/show-domain/src/main/java/org/example/repository/show/ShowQuerydslRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowQuerydslRepository.java new file mode 100644 index 00000000..feebf4b5 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowQuerydslRepository.java @@ -0,0 +1,12 @@ +package org.example.repository.show; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.example.dto.artist.response.ShowInfoResponse; + +public interface ShowQuerydslRepository { + List findAllShowInfos(); + + Optional findShowInfoById(UUID id); +} diff --git a/app/domain/show-domain/src/main/java/org/example/repository/show/ShowQuerydslRepositoryImpl.java b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowQuerydslRepositoryImpl.java new file mode 100644 index 00000000..88a3bbf7 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowQuerydslRepositoryImpl.java @@ -0,0 +1,129 @@ +package org.example.repository.show; + +import static com.querydsl.core.group.GroupBy.groupBy; +import static com.querydsl.core.group.GroupBy.set; +import static org.example.entity.artist.QArtist.artist; +import static org.example.entity.genre.QGenre.genre; +import static org.example.entity.show.QShow.show; +import static org.example.entity.show.QShowArtist.showArtist; +import static org.example.entity.show.QShowGenre.showGenre; + +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.example.dto.artist.response.ArtistKoreanNameResponse; +import org.example.dto.artist.response.GenreNameResponse; +import org.example.dto.artist.response.ShowInfoResponse; +import org.example.entity.show.Show; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ShowQuerydslRepositoryImpl implements ShowQuerydslRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findAllShowInfos() { + return createShowJoinArtistAndGenreQuery() + .transform( + groupBy(show.id).list( + Projections.constructor( + ShowInfoResponse.class, + show.id, + show.title, + show.content, + show.date, + show.location, + show.image, + show.seatPrice, + show.ticketing, + set( + Projections.constructor( + ArtistKoreanNameResponse.class, + artist.id, + artist.koreanName + ) + ), + set( + Projections.constructor( + GenreNameResponse.class, + genre.id, + genre.name + ) + ) + ) + ) + ); + } + + @Override + public Optional findShowInfoById(UUID id) { + return Optional.ofNullable( + createShowJoinArtistAndGenreQuery() + .where(show.id.eq(id)) + .transform( + groupBy(show.id).as( + Projections.constructor( + ShowInfoResponse.class, + show.id, + show.title, + show.content, + show.date, + show.location, + show.image, + show.seatPrice, + show.ticketing, + set( + Projections.constructor( + ArtistKoreanNameResponse.class, + artist.id, + artist.koreanName + ) + ), + set( + Projections.constructor( + GenreNameResponse.class, + genre.id, + genre.name + ) + ) + ) + ) + ) + .get(id) + ); + } + + private JPAQuery createShowJoinArtistAndGenreQuery() { + return jpaQueryFactory + .selectFrom(show) + .join(showArtist).on(isShowArtistEqualShowIdAndIsDeletedFalse()) + .join(artist).on(isArtistIdEqualShowArtistAndIsDeletedFalse()) + .join(showGenre).on(isShowGenreEqualShowIdAndIsDeletedFalse()) + .join(genre).on(isGenreIdEqualShowGenreAndIsDeletedFalse()) + .where(show.isDeleted.isFalse()); + } + + private BooleanExpression isShowArtistEqualShowIdAndIsDeletedFalse() { + return showArtist.showId.eq(show.id).and(showArtist.isDeleted.isFalse()); + } + + private BooleanExpression isArtistIdEqualShowArtistAndIsDeletedFalse() { + return artist.id.eq(showArtist.artistId).and(artist.isDeleted.isFalse()); + } + + private BooleanExpression isShowGenreEqualShowIdAndIsDeletedFalse() { + return showGenre.showId.eq(show.id).and(showGenre.isDeleted.isFalse()); + } + + private BooleanExpression isGenreIdEqualShowGenreAndIsDeletedFalse() { + return genre.id.eq(showGenre.genreId).and(genre.isDeleted.isFalse()); + } + +} diff --git a/app/domain/show-domain/src/main/java/org/example/repository/show/ShowRepository.java b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowRepository.java new file mode 100644 index 00000000..d7e81071 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/repository/show/ShowRepository.java @@ -0,0 +1,9 @@ +package org.example.repository.show; + +import java.util.UUID; +import org.example.entity.show.Show; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ShowRepository extends JpaRepository, ShowQuerydslRepository { + +} diff --git a/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java b/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java index 54fb3b5f..eed20d52 100644 --- a/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java +++ b/app/domain/show-domain/src/main/java/org/example/usecase/artist/ArtistUseCase.java @@ -4,13 +4,16 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.example.dto.artist.response.ArtistDetailResponse; +import org.example.dto.artist.response.ArtistKoreanNameResponse; import org.example.entity.BaseEntity; import org.example.entity.artist.Artist; import org.example.entity.artist.ArtistGenre; +import org.example.entity.show.ShowArtist; import org.example.error.ArtistError; import org.example.exception.BusinessException; import org.example.repository.artist.ArtistGenreRepository; import org.example.repository.artist.ArtistRepository; +import org.example.repository.show.ShowArtistRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +23,7 @@ public class ArtistUseCase { private final ArtistRepository artistRepository; private final ArtistGenreRepository artistGenreRepository; + private final ShowArtistRepository showArtistRepository; @Transactional public void save(Artist artist, List genreIds) { @@ -33,6 +37,10 @@ public List findAllWithGenreNames() { return artistRepository.findAllWithGenreNames(); } + public List findAllArtistKoreanName() { + return artistRepository.findAllArtistKoreanName(); + } + public ArtistDetailResponse findArtistDetailById(UUID id) { return artistRepository.findArtistWithGenreNamesById(id) .orElseThrow(() -> new BusinessException(ArtistError.ENTITY_NOT_FOUND_ERROR)); @@ -41,7 +49,7 @@ public ArtistDetailResponse findArtistDetailById(UUID id) { @Transactional public void updateArtist(UUID id, Artist newArtist, List newGenreIds) { Artist artist = findArtistById(id); - artist.changeArtist(newArtist); + artist.changeArtistInfo(newArtist); List currentGenres = artistGenreRepository.findAllByArtistId(artist.getId()); @@ -65,6 +73,12 @@ public void updateArtist(UUID id, Artist newArtist, List newGenreIds) { public void deleteArtist(UUID id) { Artist artist = findArtistById(id); artist.softDelete(); + + List artistGenres = artistGenreRepository.findAllByArtistId(artist.getId()); + artistGenres.forEach(BaseEntity::softDelete); + + List showArtists = showArtistRepository.findAllByArtistId(artist.getId()); + showArtists.forEach(BaseEntity::softDelete); } private Artist findArtistById(UUID id) { diff --git a/app/domain/show-domain/src/main/java/org/example/usecase/genre/GenreUseCase.java b/app/domain/show-domain/src/main/java/org/example/usecase/genre/GenreUseCase.java index d8aa56c8..98ba2147 100644 --- a/app/domain/show-domain/src/main/java/org/example/usecase/genre/GenreUseCase.java +++ b/app/domain/show-domain/src/main/java/org/example/usecase/genre/GenreUseCase.java @@ -3,10 +3,15 @@ import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.example.entity.BaseEntity; +import org.example.entity.artist.ArtistGenre; import org.example.entity.genre.Genre; +import org.example.entity.show.ShowGenre; import org.example.error.GenreError; import org.example.exception.BusinessException; +import org.example.repository.artist.ArtistGenreRepository; import org.example.repository.genre.GenreRepository; +import org.example.repository.show.ShowGenreRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +20,8 @@ public class GenreUseCase { private final GenreRepository genreRepository; + private final ArtistGenreRepository artistGenreRepository; + private final ShowGenreRepository showGenreRepository; @Transactional public void save(Genre genre) { @@ -35,6 +42,12 @@ public void updateGenre(UUID id, String name) { public void deleteGenre(UUID id) { Genre genre = findGenreById(id); genre.softDelete(); + + List artistGenres = artistGenreRepository.findAllByGenreId(genre.getId()); + artistGenres.forEach(BaseEntity::softDelete); + + List showGenres = showGenreRepository.findAllByGenreId(genre.getId()); + showGenres.forEach(BaseEntity::softDelete); } public Genre findGenreById(UUID id) { diff --git a/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java b/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java new file mode 100644 index 00000000..5f27e421 --- /dev/null +++ b/app/domain/show-domain/src/main/java/org/example/usecase/show/ShowUseCase.java @@ -0,0 +1,109 @@ +package org.example.usecase.show; + +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.example.dto.artist.response.ShowInfoResponse; +import org.example.entity.BaseEntity; +import org.example.entity.show.Show; +import org.example.entity.show.ShowArtist; +import org.example.entity.show.ShowGenre; +import org.example.error.ShowError; +import org.example.exception.BusinessException; +import org.example.repository.show.ShowArtistRepository; +import org.example.repository.show.ShowGenreRepository; +import org.example.repository.show.ShowRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class ShowUseCase { + + private final ShowRepository showRepository; + private final ShowArtistRepository showArtistRepository; + private final ShowGenreRepository showGenreRepository; + + @Transactional + public void save(Show show, List artistIds, List genreIds) { + showRepository.save(show); + + List showArtists = show.toShowArtist(artistIds); + showArtistRepository.saveAll(showArtists); + + List showGenres = show.toShowGenre(genreIds); + showGenreRepository.saveAll(showGenres); + } + + public List findAllShowInfos() { + return showRepository.findAllShowInfos(); + } + + public ShowInfoResponse findShowInfo(UUID id) { + return showRepository.findShowInfoById(id) + .orElseThrow(() -> new BusinessException(ShowError.ENTITY_NOT_FOUND_ERROR)); + } + + @Transactional + public void updateShow(UUID id, Show newShow, List newArtistIds, List newGenreIds) { + Show show = findShowById(id); + show.changeShowInfo(newShow); + + updateShowArtist(newArtistIds, show); + updateShowGenre(newGenreIds, show); + } + + private void updateShowArtist(List newArtistIds, Show show) { + List currentShowArtists = showArtistRepository.findAllByShowId(show.getId()); + List currentArtistIds = currentShowArtists.stream() + .map(ShowArtist::getArtistId) + .toList(); + + List artistIdsToAdd = newArtistIds.stream() + .filter(newArtistId -> !currentArtistIds.contains(newArtistId)) + .toList(); + List showArtistsToAdd = show.toShowArtist(artistIdsToAdd); + showArtistRepository.saveAll(showArtistsToAdd); + + List showArtistsToRemove = currentShowArtists.stream() + .filter(showArtist -> !newArtistIds.contains(showArtist.getArtistId())) + .toList(); + showArtistsToRemove.forEach(BaseEntity::softDelete); + } + + private void updateShowGenre(List newGenreIds, Show show) { + List currentShowGenres = showGenreRepository.findAllByShowId(show.getId()); + List currentGenreIds = currentShowGenres.stream() + .map(ShowGenre::getGenreId) + .toList(); + + List genreIdsToAdd = newGenreIds.stream() + .filter(newGenreId -> !currentGenreIds.contains(newGenreId)) + .toList(); + List showGenresToAdd = show.toShowGenre(genreIdsToAdd); + showGenreRepository.saveAll(showGenresToAdd); + + List showGenresToRemove = currentShowGenres.stream() + .filter(showGenre -> !newGenreIds.contains(showGenre.getGenreId())) + .toList(); + showGenresToRemove.forEach(BaseEntity::softDelete); + } + + @Transactional + public void deleteShow(UUID id) { + Show show = findShowById(id); + show.softDelete(); + + List showArtists = showArtistRepository.findAllByShowId(show.getId()); + showArtists.forEach(BaseEntity::softDelete); + + List showGenres = showGenreRepository.findAllByShowId(show.getId()); + showGenres.forEach(BaseEntity::softDelete); + } + + + private Show findShowById(UUID id) { + return showRepository.findById(id) + .orElseThrow(() -> new BusinessException(ShowError.ENTITY_NOT_FOUND_ERROR)); + } +} diff --git a/app/domain/show-domain/src/test/java/show/domain/ShowTest.java b/app/domain/show-domain/src/test/java/show/domain/ShowTest.java new file mode 100644 index 00000000..ff610c88 --- /dev/null +++ b/app/domain/show-domain/src/test/java/show/domain/ShowTest.java @@ -0,0 +1,50 @@ +package show.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.UUID; +import org.example.entity.show.Show; +import org.example.entity.show.ShowArtist; +import org.example.entity.show.ShowGenre; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import show.fixture.ShowFixture; + +class ShowTest { + + private final Show show = ShowFixture.show(); + + @Test + @DisplayName("아티스트 아이디들로 ShowArtist들을 생성할 수 있다.") + void artistIdsToShowArtist() { + // given + List artistIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + + // when + List showArtists = show.toShowArtist(artistIds); + + // then + assertThat(showArtists).allSatisfy(showArtist -> { + assertThat(showArtist.getShowId()).isEqualTo(show.getId()); + assertThat(artistIds).contains(showArtist.getArtistId()); + }); + } + + @Test + @DisplayName("장르 아이디들로 ShowGenre들을 생성할 수 있다.") + void genreIdsToShowGenre() { + // given + List genreIds = List.of(UUID.randomUUID(), UUID.randomUUID()); + + // when + List showGenres = show.toShowGenre(genreIds); + + // then + assertThat(showGenres).allSatisfy(showGenre -> { + assertThat(showGenre.getShowId()).isEqualTo(show.getId()); + assertThat(genreIds).contains(showGenre.getGenreId()); + }); + } + +} diff --git a/app/domain/show-domain/src/test/java/show/fixture/ShowFixture.java b/app/domain/show-domain/src/test/java/show/fixture/ShowFixture.java new file mode 100644 index 00000000..06992606 --- /dev/null +++ b/app/domain/show-domain/src/test/java/show/fixture/ShowFixture.java @@ -0,0 +1,29 @@ +package show.fixture; + +import java.time.LocalDate; +import org.example.entity.show.Show; +import org.example.entity.show.info.SeatPrice; +import org.example.entity.show.info.Ticketing; + +public class ShowFixture { + + public static Show show() { + return Show.builder() + .title("test_title") + .content("test_content") + .date(LocalDate.EPOCH) + .location("test_location") + .image("test_image") + .seatPrice(getSeatPrice()) + .ticketing(getTicketing()) + .build(); + } + + private static SeatPrice getSeatPrice() { + return new SeatPrice(); + } + + private static Ticketing getTicketing() { + return new Ticketing(); + } +} diff --git a/app/infrastructure/build.gradle b/app/infrastructure/build.gradle index b3b62f26..a3ca096f 100644 --- a/app/infrastructure/build.gradle +++ b/app/infrastructure/build.gradle @@ -9,4 +9,5 @@ allprojects { dependencies { implementation project(":app:infrastructure:redis") + implementation project(":app:infrastructure:s3") } \ No newline at end of file diff --git a/app/infrastructure/redis/src/main/java/org/example/config/RedisBeanConfig.java b/app/infrastructure/redis/src/main/java/org/example/config/RedisBeanConfig.java deleted file mode 100644 index d6bd380a..00000000 --- a/app/infrastructure/redis/src/main/java/org/example/config/RedisBeanConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.example.config; - -import org.example.property.RedisProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(RedisProperty.class) -@ComponentScan(basePackages = "org.example") -public class RedisBeanConfig { - -} diff --git a/app/infrastructure/redis/src/main/java/org/example/config/RedisConfig.java b/app/infrastructure/redis/src/main/java/org/example/config/RedisConfig.java index 207a8903..168de53c 100644 --- a/app/infrastructure/redis/src/main/java/org/example/config/RedisConfig.java +++ b/app/infrastructure/redis/src/main/java/org/example/config/RedisConfig.java @@ -2,7 +2,9 @@ import lombok.RequiredArgsConstructor; import org.example.property.RedisProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; @@ -10,6 +12,8 @@ import org.springframework.data.redis.serializer.GenericToStringSerializer; @Configuration +@EnableConfigurationProperties(RedisProperty.class) +@ComponentScan(basePackages = "org.example") @RequiredArgsConstructor public class RedisConfig { diff --git a/app/infrastructure/s3/build.gradle b/app/infrastructure/s3/build.gradle index 00d892e4..512c0fa0 100644 --- a/app/infrastructure/s3/build.gradle +++ b/app/infrastructure/s3/build.gradle @@ -2,8 +2,8 @@ bootJar.enabled = false jar.enabled = true dependencies { - implementation project(":app:api:common-api") + implementation project(":app:api:show-api") //aws s3 - implementation 'io.awspring.cloud:spring-cloud-aws-s3:3.1.1' + implementation ("com.amazonaws:aws-java-sdk-s3:1.12.761") } diff --git a/app/infrastructure/s3/src/main/java/org/example/component/S3Component.java b/app/infrastructure/s3/src/main/java/org/example/component/S3Component.java new file mode 100644 index 00000000..bd87a88f --- /dev/null +++ b/app/infrastructure/s3/src/main/java/org/example/component/S3Component.java @@ -0,0 +1,58 @@ +package org.example.component; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.example.component.FileUploadComponent; +import java.io.IOException; +import java.util.Objects; +import org.example.config.S3Config; +import org.example.error.S3Error; +import org.example.exception.BusinessException; +import org.example.util.FileName; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@Component +public class S3Component implements FileUploadComponent { + + private final AmazonS3 amazonS3; + private final String bucket; + + public S3Component(S3Config s3Config, AmazonS3 amazonS3) { + this.bucket = s3Config.getBucketName(); + this.amazonS3 = amazonS3; + } + + + @Override + public String uploadFile(String directory, MultipartFile multipartFile) { + String fileName = FileName.build(directory, + Objects.requireNonNull(multipartFile.getOriginalFilename())); + + try { + amazonS3.putObject(getPutObjectRequest(multipartFile, fileName)); + } catch (IOException e) { + throw new BusinessException(S3Error.FILE_UPLOAD_ERROR); + } + + return amazonS3.getUrl(bucket, fileName).toString(); + } + + private PutObjectRequest getPutObjectRequest(MultipartFile multipartFile, String fileName) + throws IOException { + return new PutObjectRequest( + bucket, + fileName, + multipartFile.getInputStream(), + getObjectMetadata(multipartFile) + ); + } + + private ObjectMetadata getObjectMetadata(MultipartFile multipartFile) { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(multipartFile.getContentType()); + objectMetadata.setContentLength(multipartFile.getSize()); + return objectMetadata; + } +} diff --git a/app/infrastructure/s3/src/main/java/org/example/config/S3Config.java b/app/infrastructure/s3/src/main/java/org/example/config/S3Config.java new file mode 100644 index 00000000..eb1ed777 --- /dev/null +++ b/app/infrastructure/s3/src/main/java/org/example/config/S3Config.java @@ -0,0 +1,41 @@ +package org.example.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import lombok.RequiredArgsConstructor; +import org.example.property.S3Property; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(S3Property.class) +@ComponentScan(basePackages = {"org.example"}) +@RequiredArgsConstructor +public class S3Config { + + private final S3Property s3Property; + + @Bean + public AmazonS3 s3Client() { + return AmazonS3ClientBuilder.standard() + .withRegion(s3Property.region()) + .withCredentials(new AWSStaticCredentialsProvider(awsBasicCredentials())) + .build(); + } + + private BasicAWSCredentials awsBasicCredentials() { + return new BasicAWSCredentials( + s3Property.credentials().accessKey(), + s3Property.credentials().secretKey() + ); + } + + public String getBucketName() { + return s3Property.s3().bucket(); + } + +} diff --git a/app/infrastructure/s3/src/main/java/org/example/error/S3Error.java b/app/infrastructure/s3/src/main/java/org/example/error/S3Error.java new file mode 100644 index 00000000..fc9392be --- /dev/null +++ b/app/infrastructure/s3/src/main/java/org/example/error/S3Error.java @@ -0,0 +1,27 @@ +package org.example.error; + +import org.example.exception.BusinessError; + +public enum S3Error implements BusinessError { + FILE_UPLOAD_ERROR { + @Override + public int getHttpStatus() { + return 500; + } + + @Override + public String getErrorCode() { + return "S3-001"; + } + + @Override + public String getClientMessage() { + return "파일 업로드 중 오류가 발생했습니다."; + } + + @Override + public String getLogMessage() { + return "S3에 파일 업로드 실패 오류"; + } + } +} diff --git a/app/infrastructure/s3/src/main/java/org/example/property/S3Property.java b/app/infrastructure/s3/src/main/java/org/example/property/S3Property.java new file mode 100644 index 00000000..b0e8172a --- /dev/null +++ b/app/infrastructure/s3/src/main/java/org/example/property/S3Property.java @@ -0,0 +1,22 @@ +package org.example.property; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "cloud.aws") +public record S3Property( + Credentials credentials, + String region, + S3 s3 +) { + + public record Credentials( + String accessKey, + String secretKey + ) { + } + + public record S3( + String bucket + ) { + } +} diff --git a/app/infrastructure/s3/src/main/java/org/example/util/FileName.java b/app/infrastructure/s3/src/main/java/org/example/util/FileName.java new file mode 100644 index 00000000..76a4afb4 --- /dev/null +++ b/app/infrastructure/s3/src/main/java/org/example/util/FileName.java @@ -0,0 +1,17 @@ +package org.example.util; + +public final class FileName { + + private FileName() { + } + + public static String build(String directory, String originalFileName) { + int fileExtensionIndex = originalFileName.lastIndexOf("."); + String fileExtension = originalFileName.substring(fileExtensionIndex); + String fileName = originalFileName.substring(0, fileExtensionIndex); + String now = String.valueOf(System.currentTimeMillis()); + + return directory + "/" + fileName + "_" + now + fileExtension; + } + +} diff --git a/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java b/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java index 3e92ad50..9f88ccf4 100644 --- a/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java +++ b/app/infrastructure/src/main/java/org/example/config/InfrastructureConfig.java @@ -4,7 +4,7 @@ import org.springframework.context.annotation.Import; @Configuration -@Import(RedisBeanConfig.class) +@Import({RedisConfig.class, S3Config.class}) public class InfrastructureConfig { } diff --git a/app/src/main/resources/application-dev.yml b/app/src/main/resources/application-dev.yml index dc782d94..3c808981 100644 --- a/app/src/main/resources/application-dev.yml +++ b/app/src/main/resources/application-dev.yml @@ -24,3 +24,8 @@ cloud: credentials: accessKey: ${ACCESS_KEY} secretKey: ${SECRET_KEY} + region: ${REGION} + s3: + bucket: ${BUCKET} + stack: + auto: false diff --git a/app/src/main/resources/application-prod.yml b/app/src/main/resources/application-prod.yml index 9566e042..e001fad8 100644 --- a/app/src/main/resources/application-prod.yml +++ b/app/src/main/resources/application-prod.yml @@ -11,4 +11,9 @@ cloud: aws: credentials: accessKey: ${ACCESS_KEY} - secretKey: ${SECRET_KEY} \ No newline at end of file + secretKey: ${SECRET_KEY} + region: ${REGION} + s3: + bucket: ${BUCKET} + stack: + auto: false \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 674417bb..b1edc9f1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,4 +15,5 @@ include(":app:api:user-api") include(":app:api:show-api") include (":app:infrastructure") -include (":app:infrastructure:redis") \ No newline at end of file +include (":app:infrastructure:redis") +include (":app:infrastructure:s3") \ No newline at end of file