diff --git a/docker-compose.stage.yml b/docker-compose.stage.yml index 707e580..6f78114 100644 --- a/docker-compose.stage.yml +++ b/docker-compose.stage.yml @@ -17,7 +17,8 @@ services: condition: service_healthy extra_hosts: - "host.docker.internal:host-gateway" - volumes: [] + volumes: + - ${FIREBASE_PATH} env_file: - .env diff --git a/src/main/java/org/atdev/artrip/domain/admin/common/config/AdminSecurityConfig.java b/src/main/java/org/atdev/artrip/domain/admin/common/config/AdminSecurityConfig.java deleted file mode 100644 index 86d1235..0000000 --- a/src/main/java/org/atdev/artrip/domain/admin/common/config/AdminSecurityConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.atdev.artrip.domain.admin.common.config; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.web.SecurityFilterChain; - -@Configuration -@RequiredArgsConstructor -@EnableWebSecurity -public class AdminSecurityConfig { - - @Bean - @Order(1) - public SecurityFilterChain adminFilterChain(HttpSecurity http) throws Exception { - http - .securityMatcher("/admin/**") - .authorizeHttpRequests(auth -> auth -// .requestMatchers("/admin/**").hasRole("ADMIN") - .requestMatchers("/admin/**").permitAll() - .anyRequest().authenticated()) - .csrf(csrf -> csrf.disable()); - - return http.build(); - } -} diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/converter/AdminExhibitConverter.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/converter/AdminExhibitConverter.java new file mode 100644 index 0000000..fd701d8 --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/converter/AdminExhibitConverter.java @@ -0,0 +1,72 @@ +package org.atdev.artrip.domain.admin.exhibit.converter; + +import org.atdev.artrip.domain.admin.exhibit.dto.response.AdminExhibitResponse; +import org.atdev.artrip.domain.admin.exhibit.dto.response.AdminExhibitListResponse; +import org.atdev.artrip.domain.exhibit.data.Exhibit; +import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; +import org.atdev.artrip.domain.keyword.data.Keyword; +import org.springframework.stereotype.Component; + +import java.util.stream.Collectors; + +@Component +public class AdminExhibitConverter { + + public AdminExhibitListResponse toListResponse(Exhibit exhibit) { + return AdminExhibitListResponse.builder() + .exhibitId(exhibit.getExhibitId()) + .title(exhibit.getTitle()) + .posterUrl(exhibit.getPosterUrl()) + .status(exhibit.getStatus()) + .startDate(exhibit.getStartDate()) + .endDate(exhibit.getEndDate()) + .exhibitHallName(exhibit.getExhibitHall() != null ? exhibit.getExhibitHall().getName() : null) + .country(exhibit.getExhibitHall() != null ? exhibit.getExhibitHall().getCountry() : null) + .keywordCount(exhibit.getKeywords().size()) + .createdAt(exhibit.getCreatedAt()) + .updatedAt(exhibit.getUpdatedAt()) + .build(); + } + + public AdminExhibitResponse toAdminResponse(Exhibit exhibit) { + return AdminExhibitResponse.builder() + .exhibitId(exhibit.getExhibitId()) + .title(exhibit.getTitle()) + .description(exhibit.getDescription()) + .startDate(exhibit.getStartDate()) + .endDate(exhibit.getEndDate()) + .exhibitHall(toExhibitHallInfo(exhibit.getExhibitHall())) + .openingHours(exhibit.getExhibitHall() != null ? exhibit.getExhibitHall().getOpeningHours() : null) + .status(exhibit.getStatus()) + .posterUrl(exhibit.getPosterUrl()) + .ticketUrl(exhibit.getTicketUrl()) + .keywords(exhibit.getKeywords().stream() + .map(this::toKeywordInfo) + .collect(Collectors.toList())) + .createdAt(exhibit.getCreatedAt()) + .updatedAt(exhibit.getUpdatedAt()) + .build(); + } + + private AdminExhibitResponse.ExhibitHallInfo toExhibitHallInfo(ExhibitHall hall) { + if (hall == null) return null; + + return AdminExhibitResponse.ExhibitHallInfo.builder() + .exhibitHallId(hall.getExhibitHallId()) + .name(hall.getName()) + .address(hall.getAddress()) + .country(hall.getCountry()) + .region(hall.getRegion()) + .phone(hall.getPhone()) + .homepageUrl(hall.getHomepageUrl()) + .build(); + } + + private AdminExhibitResponse.KeywordInfo toKeywordInfo(Keyword keyword) { + return AdminExhibitResponse.KeywordInfo.builder() + .keywordId(keyword.getKeywordId()) + .name(keyword.getName()) + .type(keyword.getType().name()) + .build(); + } +} diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/CreateExhibitRequest.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/request/CreateExhibitRequest.java similarity index 65% rename from src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/CreateExhibitRequest.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/request/CreateExhibitRequest.java index 9ae5a41..132c9f2 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/CreateExhibitRequest.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/request/CreateExhibitRequest.java @@ -1,10 +1,9 @@ -package org.atdev.artrip.domain.admin.exhibit.dto; +package org.atdev.artrip.domain.admin.exhibit.dto.request; import lombok.Data; import org.atdev.artrip.domain.Enum.Status; -import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; @Data @@ -16,8 +15,8 @@ public class CreateExhibitRequest { private String ticketUrl; private String posterUrl; - private LocalDateTime startDate; - private LocalDateTime endDate; + private LocalDate startDate; + private LocalDate endDate; private Status status; diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/UpdateExhibitRequest.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/request/UpdateExhibitRequest.java similarity index 74% rename from src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/UpdateExhibitRequest.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/request/UpdateExhibitRequest.java index 3d50dc2..4e02eea 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/UpdateExhibitRequest.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/request/UpdateExhibitRequest.java @@ -1,10 +1,9 @@ -package org.atdev.artrip.domain.admin.exhibit.dto; +package org.atdev.artrip.domain.admin.exhibit.dto.request; import lombok.Data; import org.atdev.artrip.domain.Enum.Status; -import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; @Data @@ -20,8 +19,8 @@ public class UpdateExhibitRequest { private String region; private String phone; - private LocalDateTime startDate; - private LocalDateTime endDate; + private LocalDate startDate; + private LocalDate endDate; private String openingHours; private Status status; diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/ExhibitListResponse.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/response/AdminExhibitListResponse.java similarity index 74% rename from src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/ExhibitListResponse.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/response/AdminExhibitListResponse.java index f0d1637..157805b 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/ExhibitListResponse.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/response/AdminExhibitListResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.admin.exhibit.dto; +package org.atdev.artrip.domain.admin.exhibit.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; @@ -6,21 +6,22 @@ import lombok.NoArgsConstructor; import org.atdev.artrip.domain.Enum.Status; +import java.time.LocalDate; import java.time.LocalDateTime; @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class ExhibitListResponse { +public class AdminExhibitListResponse { private Long exhibitId; private String title; private String posterUrl; // 이미지 URL private Status status; - private LocalDateTime startDate; - private LocalDateTime endDate; + private LocalDate startDate; + private LocalDate endDate; private String exhibitHallName; private String country; diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/ExhibitAdminResponse.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/response/AdminExhibitResponse.java similarity index 75% rename from src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/ExhibitAdminResponse.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/response/AdminExhibitResponse.java index 5286180..728bef5 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/ExhibitAdminResponse.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/dto/response/AdminExhibitResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.admin.exhibit.dto; +package org.atdev.artrip.domain.admin.exhibit.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; @@ -6,14 +6,14 @@ import lombok.NoArgsConstructor; import org.atdev.artrip.domain.Enum.Status; -import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @Data @Builder @AllArgsConstructor -public class ExhibitAdminResponse { +public class AdminExhibitResponse { private Long exhibitId; private String title; @@ -21,8 +21,8 @@ public class ExhibitAdminResponse { private ExhibitHallInfo exhibitHall; - private LocalDateTime startDate; - private LocalDateTime endDate; + private LocalDate startDate; + private LocalDate endDate; private String openingHours; private Status status; @@ -30,12 +30,11 @@ public class ExhibitAdminResponse { private String posterUrl; private String ticketUrl; - private List keywords; + private List keywords; private LocalDateTime createdAt; private LocalDateTime updatedAt; - // 내부 클래스: 전시장 정보 @Data @Builder @NoArgsConstructor @@ -50,12 +49,11 @@ public static class ExhibitHallInfo { private String homepageUrl; } - // 내부 클래스: 키워드 정보 @Data @Builder @NoArgsConstructor @AllArgsConstructor - public static class keywordInfo { + public static class KeywordInfo { private Long keywordId; private String name; private String type; // GENRE, STYLE diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/service/AdminExhibitService.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/service/AdminExhibitService.java index 23f7bd8..a80632c 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibit/service/AdminExhibitService.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/service/AdminExhibitService.java @@ -4,10 +4,11 @@ import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.admin.common.dto.Criteria; import org.atdev.artrip.domain.admin.common.dto.PagingResponseDTO; -import org.atdev.artrip.domain.admin.exhibit.dto.CreateExhibitRequest; -import org.atdev.artrip.domain.admin.exhibit.dto.ExhibitAdminResponse; -import org.atdev.artrip.domain.admin.exhibit.dto.ExhibitListResponse; -import org.atdev.artrip.domain.admin.exhibit.dto.UpdateExhibitRequest; +import org.atdev.artrip.domain.admin.exhibit.converter.AdminExhibitConverter; +import org.atdev.artrip.domain.admin.exhibit.dto.request.CreateExhibitRequest; +import org.atdev.artrip.domain.admin.exhibit.dto.response.AdminExhibitResponse; +import org.atdev.artrip.domain.admin.exhibit.dto.response.AdminExhibitListResponse; +import org.atdev.artrip.domain.admin.exhibit.dto.request.UpdateExhibitRequest; import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; @@ -25,7 +26,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -36,9 +36,10 @@ public class AdminExhibitService { private final ExhibitHallRepository exhibitHallRepository; private final KeywordRepository keywordRepository; private final ExhibitIndexService exhibitIndexService; + private final AdminExhibitConverter adminExhibitConverter; - @Transactional - public PagingResponseDTO getExhibitList(Criteria cri) { + @Transactional(readOnly = true) + public PagingResponseDTO getExhibitList(Criteria cri) { log.info("Admin Getting Exhibit List : {}", cri); Pageable pageable = cri.toPageable(); @@ -51,22 +52,21 @@ public PagingResponseDTO getExhibitList(Criteria cri) { exhibitPage = exhibitRepository.findAll(pageable); } - Page responsePage = exhibitPage.map(this::convertToListResponse); + Page responsePage = exhibitPage.map(adminExhibitConverter::toListResponse); return PagingResponseDTO.from(responsePage); } - @Transactional - public ExhibitAdminResponse getExhibit (Long exhibitId) { + @Transactional(readOnly = true) + public AdminExhibitResponse getExhibit (Long exhibitId) { log.info("Admin Getting Exhibit : {}", exhibitId); Exhibit exhibit = exhibitRepository.findByIdWithKeywords(exhibitId) .orElseThrow(() -> new GeneralException(ExhibitError._EXHIBIT_NOT_FOUND)); - return convertToAdminResponse(exhibit); + return adminExhibitConverter.toAdminResponse(exhibit); } - @Transactional public Long createExhibit(CreateExhibitRequest request) { log.info("Admin Creating exhibit : title={}", request); @@ -104,7 +104,7 @@ public Long createExhibit(CreateExhibitRequest request) { try { exhibitIndexService.indexExhibit(savedExhibit); - log.info("Exhibit indexing failed : id={}, error={}",savedExhibit.getExhibitId()); + log.info("Exhibit indexing successfully : id={} ",savedExhibit.getExhibitId()); } catch (Exception e) { log.error("Exhibit indexing failed", e); } @@ -189,7 +189,7 @@ private ExhibitHall getOrCreateExhibitHall(Long exhibitHallId, if (exhibitHallId != null) { return exhibitHallRepository.findById(exhibitHallId) - .orElseThrow(() -> new GeneralException(ExhibitError._EXHIBIT_NOT_FOUND)); + .orElseThrow(() -> new GeneralException(ExhibitError._EXHIBIT_HALL_NOT_FOUND)); } else if (exhibitHallName != null) { ExhibitHall hall = ExhibitHall.builder() .name(exhibitHallName) @@ -198,6 +198,8 @@ private ExhibitHall getOrCreateExhibitHall(Long exhibitHallId, .region(region) .phone(phone) .openingHours(openingHours) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) .build(); return exhibitHallRepository.save(hall); } else { @@ -205,61 +207,5 @@ private ExhibitHall getOrCreateExhibitHall(Long exhibitHallId, } } - private ExhibitListResponse convertToListResponse(Exhibit exhibit) { - return ExhibitListResponse.builder() - .exhibitId(exhibit.getExhibitId()) - .title(exhibit.getTitle()) - .posterUrl(exhibit.getPosterUrl()) - .status(exhibit.getStatus()) - .startDate(exhibit.getStartDate()) - .endDate(exhibit.getEndDate()) - .exhibitHallName(exhibit.getExhibitHall() != null ? exhibit.getExhibitHall().getName() : null) - .country(exhibit.getExhibitHall() != null ? exhibit.getExhibitHall().getCountry() : null) - .keywordCount(exhibit.getKeywords().size()) - .createdAt(exhibit.getCreatedAt()) - .updatedAt(exhibit.getUpdatedAt()) - .build(); - } - public ExhibitAdminResponse convertToAdminResponse(Exhibit exhibit) { - return ExhibitAdminResponse.builder() - .exhibitId(exhibit.getExhibitId()) - .title(exhibit.getTitle()) - .description(exhibit.getDescription()) - .startDate(exhibit.getStartDate()) - .endDate(exhibit.getEndDate()) - .exhibitHall(convertToExhibitHallInfo(exhibit.getExhibitHall())) - .openingHours(exhibit.getExhibitHall() != null ? exhibit.getExhibitHall().getOpeningHours() : null) - .status(exhibit.getStatus()) - .posterUrl(exhibit.getPosterUrl()) - .ticketUrl(exhibit.getTicketUrl()) - .keywords(exhibit.getKeywords().stream() - .map(this::convertToKeywordInfo) - .collect(Collectors.toList())) - .createdAt(exhibit.getCreatedAt()) - .updatedAt(exhibit.getUpdatedAt()) - .build(); - } - - private ExhibitAdminResponse.ExhibitHallInfo convertToExhibitHallInfo(ExhibitHall hall) { - if (hall == null) return null; - - return ExhibitAdminResponse.ExhibitHallInfo.builder() - .exhibitHallId(hall.getExhibitHallId()) - .name(hall.getName()) - .address(hall.getAddress()) - .country(hall.getCountry()) - .region(hall.getRegion()) - .phone(hall.getPhone()) - .homepageUrl(hall.getHomepageUrl()) - .build(); - } - - private ExhibitAdminResponse.keywordInfo convertToKeywordInfo(Keyword keyword) { - return ExhibitAdminResponse.keywordInfo.builder() - .keywordId(keyword.getKeywordId()) - .name(keyword.getName()) - .type(keyword.getType().name()) - .build(); - } } diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibit/web/AdminExhibitController.java b/src/main/java/org/atdev/artrip/domain/admin/exhibit/web/AdminExhibitController.java index c3c217f..d908a68 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibit/web/AdminExhibitController.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibit/web/AdminExhibitController.java @@ -6,10 +6,10 @@ import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.admin.common.dto.Criteria; import org.atdev.artrip.domain.admin.common.dto.PagingResponseDTO; -import org.atdev.artrip.domain.admin.exhibit.dto.CreateExhibitRequest; -import org.atdev.artrip.domain.admin.exhibit.dto.ExhibitAdminResponse; -import org.atdev.artrip.domain.admin.exhibit.dto.ExhibitListResponse; -import org.atdev.artrip.domain.admin.exhibit.dto.UpdateExhibitRequest; +import org.atdev.artrip.domain.admin.exhibit.dto.request.CreateExhibitRequest; +import org.atdev.artrip.domain.admin.exhibit.dto.response.AdminExhibitResponse; +import org.atdev.artrip.domain.admin.exhibit.dto.response.AdminExhibitListResponse; +import org.atdev.artrip.domain.admin.exhibit.dto.request.UpdateExhibitRequest; import org.atdev.artrip.domain.admin.exhibit.service.AdminExhibitService; import org.atdev.artrip.global.apipayload.CommonResponse; import org.springframework.web.bind.annotation.*; @@ -36,20 +36,20 @@ public class AdminExhibitController { """ ) @GetMapping - public CommonResponse> getExhibitList(Criteria cri) { + public CommonResponse> getExhibitList(Criteria cri) { log.info("Admin getting exhibit list: {}", cri); - PagingResponseDTO result = adminExhibitService.getExhibitList(cri); + PagingResponseDTO result = adminExhibitService.getExhibitList(cri); return CommonResponse.onSuccess(result); } @Operation(summary = "전시 상세 조회", description = "특정 전시의 상세 정보를 조회합니다.") @GetMapping("/{exhibitId}") - public CommonResponse getExhibit(@PathVariable Long exhibitId) { + public CommonResponse getExhibit(@PathVariable Long exhibitId) { log.info("Admin getting exhibit : {}", exhibitId); - ExhibitAdminResponse result = adminExhibitService.getExhibit(exhibitId); + AdminExhibitResponse result = adminExhibitService.getExhibit(exhibitId); return CommonResponse.onSuccess(result); } diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/converter/AdminExhibitHallConverter.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/converter/AdminExhibitHallConverter.java new file mode 100644 index 0000000..ae59b6a --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/converter/AdminExhibitHallConverter.java @@ -0,0 +1,42 @@ +package org.atdev.artrip.domain.admin.exhibitHall.converter; + +import org.atdev.artrip.domain.admin.exhibitHall.dto.response.ExhibitHallListResponse; +import org.atdev.artrip.domain.admin.exhibitHall.dto.response.ExhibitHallResponse; +import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; +import org.springframework.stereotype.Component; + +@Component +public class AdminExhibitHallConverter { + + public ExhibitHallListResponse toListResponse(ExhibitHall hall, long exhibitCount) { + + return ExhibitHallListResponse.builder() + .exhibitHallId(hall.getExhibitHallId()) + .name(hall.getName()) + .country(hall.getCountry()) + .region(hall.getRegion()) + .phone(hall.getPhone()) + .isDomestic(hall.getIsDomestic()) + .exhibitCount(exhibitCount) + .build(); + } + + public ExhibitHallResponse toResponse(ExhibitHall hall, long exhibitCount) { + + return ExhibitHallResponse.builder() + .exhibitHallId(hall.getExhibitHallId()) + .name(hall.getName()) + .address(hall.getAddress()) + .country(hall.getCountry()) + .region(hall.getRegion()) + .phone(hall.getPhone()) + .homepageUrl(hall.getHomepageUrl()) + .openingHours(hall.getOpeningHours()) + .isDomestic(hall.getIsDomestic()) + .exhibitCount(exhibitCount) + .closedDays(hall.getClosedDays()) + .latitude(hall.getLatitude()) + .longitude(hall.getLongitude()) + .build(); + } +} diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/CreateExhibitHallRequest.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/request/CreateExhibitHallRequest.java similarity index 87% rename from src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/CreateExhibitHallRequest.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/request/CreateExhibitHallRequest.java index 7e0c3a5..9ce2693 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/CreateExhibitHallRequest.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/request/CreateExhibitHallRequest.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.admin.exhibitHall.dto; +package org.atdev.artrip.domain.admin.exhibitHall.dto.request; import lombok.Data; diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/UpdateExhibitHallRequest.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/request/UpdateExhibitHallRequest.java similarity index 79% rename from src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/UpdateExhibitHallRequest.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/request/UpdateExhibitHallRequest.java index 313dc23..c045d6f 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/UpdateExhibitHallRequest.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/request/UpdateExhibitHallRequest.java @@ -1,9 +1,7 @@ -package org.atdev.artrip.domain.admin.exhibitHall.dto; +package org.atdev.artrip.domain.admin.exhibitHall.dto.request; import lombok.Data; -import java.time.LocalDateTime; - @Data public class UpdateExhibitHallRequest { diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/ExhibitHallListResponse.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/response/ExhibitHallListResponse.java similarity index 87% rename from src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/ExhibitHallListResponse.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/response/ExhibitHallListResponse.java index b5d5d1c..fa02d0d 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/ExhibitHallListResponse.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/response/ExhibitHallListResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.admin.exhibitHall.dto; +package org.atdev.artrip.domain.admin.exhibitHall.dto.response; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/ExhibitHallResponse.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/response/ExhibitHallResponse.java similarity index 89% rename from src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/ExhibitHallResponse.java rename to src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/response/ExhibitHallResponse.java index b2cd2e4..1235d3b 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/ExhibitHallResponse.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/dto/response/ExhibitHallResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.admin.exhibitHall.dto; +package org.atdev.artrip.domain.admin.exhibitHall.dto.response; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/service/AdminExhibitHallService.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/service/AdminExhibitHallService.java index c6374ab..7c4fd63 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/service/AdminExhibitHallService.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/service/AdminExhibitHallService.java @@ -4,14 +4,14 @@ import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.admin.common.dto.Criteria; import org.atdev.artrip.domain.admin.common.dto.PagingResponseDTO; -import org.atdev.artrip.domain.admin.exhibitHall.dto.CreateExhibitHallRequest; -import org.atdev.artrip.domain.admin.exhibitHall.dto.ExhibitHallListResponse; -import org.atdev.artrip.domain.admin.exhibitHall.dto.ExhibitHallResponse; -import org.atdev.artrip.domain.admin.exhibitHall.dto.UpdateExhibitHallRequest; +import org.atdev.artrip.domain.admin.exhibitHall.converter.AdminExhibitHallConverter; +import org.atdev.artrip.domain.admin.exhibitHall.dto.request.CreateExhibitHallRequest; +import org.atdev.artrip.domain.admin.exhibitHall.dto.response.ExhibitHallListResponse; +import org.atdev.artrip.domain.admin.exhibitHall.dto.response.ExhibitHallResponse; +import org.atdev.artrip.domain.admin.exhibitHall.dto.request.UpdateExhibitHallRequest; import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; import org.atdev.artrip.domain.exhibitHall.repository.ExhibitHallRepository; -import org.atdev.artrip.global.apipayload.code.status.CommonError; import org.atdev.artrip.global.apipayload.code.status.ExhibitError; import org.atdev.artrip.global.apipayload.exception.GeneralException; import org.springframework.data.domain.Page; @@ -28,8 +28,9 @@ public class AdminExhibitHallService { private final ExhibitHallRepository exhibitHallRepository; private final ExhibitRepository exhibitRepository; + private final AdminExhibitHallConverter exhibitHallConverter; - @Transactional + @Transactional(readOnly = true) public PagingResponseDTO getExhibitHallList(Criteria cri) { log.info("Admin getting exhibit hall list: {}", cri); @@ -42,19 +43,24 @@ public PagingResponseDTO getExhibitHallList(Criteria cr hallPage = exhibitHallRepository.findAll(pageable); } - Page responsePage = hallPage.map(this::convertToListResponse); + Page responsePage = hallPage.map(hall ->{ + long exhibitCount = exhibitRepository.countByExhibitHall_ExhibitHallId(hall.getExhibitHallId()); + return exhibitHallConverter.toListResponse(hall, exhibitCount); + }); return PagingResponseDTO.from(responsePage); } - @Transactional + @Transactional(readOnly = true) public ExhibitHallResponse getExhibitHall(Long exhibitHallId) { log.info("Admin getting exhibit hall : {}", exhibitHallId); ExhibitHall hall = exhibitHallRepository.findById(exhibitHallId).orElseThrow(() -> new GeneralException(ExhibitError._EXHIBIT_HALL_NOT_FOUND)); - return convertToResponse(hall); + long exhibitCount = exhibitRepository.countByExhibitHall_ExhibitHallId(exhibitHallId); + + return exhibitHallConverter.toResponse(hall, exhibitCount); } @Transactional @@ -126,37 +132,5 @@ public void deleteExhibitHall(Long exhibitHallId) { log.info("Exhibit hall deleted: {}", exhibitHallId); } - private ExhibitHallListResponse convertToListResponse(ExhibitHall hall) { - long exhibitCount = exhibitRepository.countByExhibitHall_ExhibitHallId(hall.getExhibitHallId()); - - return ExhibitHallListResponse.builder() - .exhibitHallId(hall.getExhibitHallId()) - .name(hall.getName()) - .country(hall.getCountry()) - .region(hall.getRegion()) - .phone(hall.getPhone()) - .isDomestic(hall.getIsDomestic()) - .exhibitCount(exhibitCount) - .build(); - } - private ExhibitHallResponse convertToResponse(ExhibitHall hall) { - long exhibitCount = exhibitRepository.countByExhibitHall_ExhibitHallId(hall.getExhibitHallId()); - - return ExhibitHallResponse.builder() - .exhibitHallId(hall.getExhibitHallId()) - .name(hall.getName()) - .address(hall.getAddress()) - .country(hall.getCountry()) - .region(hall.getRegion()) - .phone(hall.getPhone()) - .homepageUrl(hall.getHomepageUrl()) - .openingHours(hall.getOpeningHours()) - .isDomestic(hall.getIsDomestic()) - .exhibitCount(exhibitCount) - .closedDays(hall.getClosedDays()) - .latitude(hall.getLatitude()) - .longitude(hall.getLongitude()) - .build(); - } } diff --git a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/web/AdminExhibitHallController.java b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/web/AdminExhibitHallController.java index 3a27e01..dc5d7a1 100644 --- a/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/web/AdminExhibitHallController.java +++ b/src/main/java/org/atdev/artrip/domain/admin/exhibitHall/web/AdminExhibitHallController.java @@ -6,10 +6,10 @@ import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.admin.common.dto.Criteria; import org.atdev.artrip.domain.admin.common.dto.PagingResponseDTO; -import org.atdev.artrip.domain.admin.exhibitHall.dto.CreateExhibitHallRequest; -import org.atdev.artrip.domain.admin.exhibitHall.dto.ExhibitHallListResponse; -import org.atdev.artrip.domain.admin.exhibitHall.dto.ExhibitHallResponse; -import org.atdev.artrip.domain.admin.exhibitHall.dto.UpdateExhibitHallRequest; +import org.atdev.artrip.domain.admin.exhibitHall.dto.request.CreateExhibitHallRequest; +import org.atdev.artrip.domain.admin.exhibitHall.dto.response.ExhibitHallListResponse; +import org.atdev.artrip.domain.admin.exhibitHall.dto.response.ExhibitHallResponse; +import org.atdev.artrip.domain.admin.exhibitHall.dto.request.UpdateExhibitHallRequest; import org.atdev.artrip.domain.admin.exhibitHall.service.AdminExhibitHallService; import org.atdev.artrip.global.apipayload.CommonResponse; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/org/atdev/artrip/domain/auth/data/User.java b/src/main/java/org/atdev/artrip/domain/auth/data/User.java index aee24f1..03e909a 100644 --- a/src/main/java/org/atdev/artrip/domain/auth/data/User.java +++ b/src/main/java/org/atdev/artrip/domain/auth/data/User.java @@ -42,8 +42,8 @@ public class User { @Column(name = "stamp_num") private Byte stampNum; - @Column(name = "push_token") - private String pushToken; + @Column(name = "nick_name") + private String nickName; @Builder.Default @Column(nullable = false) diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/data/Exhibit.java b/src/main/java/org/atdev/artrip/domain/exhibit/data/Exhibit.java index b4bbd2d..24db2e5 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/data/Exhibit.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/data/Exhibit.java @@ -8,6 +8,7 @@ import org.atdev.artrip.domain.keyword.data.Keyword; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; @@ -38,10 +39,10 @@ public class Exhibit { private String description; @Column(name = "start_date") - private LocalDateTime startDate; + private LocalDate startDate; @Column(name = "end_date") - private LocalDateTime endDate; + private LocalDate endDate; @Enumerated(EnumType.STRING) // DB가 CHAR/VARCHAR이면 STRING @Column(name = "status", nullable = false) diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java index bcb71a5..5765a39 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java @@ -138,5 +138,5 @@ ORDER BY RAND() Page findAllByRegion(@Param("region") String region, Pageable pageable); - Optional findByTitleAndStartDate(String title, LocalDateTime startDate); + Optional findByTitleAndStartDate(String title, LocalDate startDate); } diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryCustom.java b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryCustom.java index a795351..bd23c50 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryCustom.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryCustom.java @@ -2,7 +2,6 @@ import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.web.dto.request.ExhibitFilterRequestDto; -import org.atdev.artrip.domain.home.web.dto.RandomExhibitFilterRequestDto; import org.atdev.artrip.domain.home.response.HomeListResponse; import org.atdev.artrip.domain.home.web.dto.RandomExhibitRequest; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java index b9beda5..f7483f8 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java @@ -102,7 +102,8 @@ public List findRandomExhibits(RandomExhibitRequest c) { "concat({0}, ' ~ ', {1})", e.startDate.stringValue(), e.endDate.stringValue() - ) + ), + h.name )) .from(e) .join(e.exhibitHall, h) @@ -180,11 +181,11 @@ private BooleanExpression dateFilter(LocalDate startDate, LocalDate endDate, QEx if (startDate == null && endDate == null) return null; if (endDate != null) { - condition = e.startDate.loe(endDate.atTime(23, 59, 59)); + condition = e.startDate.loe(endDate); } if (startDate != null) { - BooleanExpression startCond = e.endDate.goe(startDate.atStartOfDay()); + BooleanExpression startCond = e.endDate.goe(startDate); condition = (condition == null) ? startCond : condition.and(startCond); } @@ -218,12 +219,8 @@ private BooleanExpression styleIn(Set styles) { private BooleanExpression findDate(LocalDate date){ if (date == null) return null; - LocalDateTime dayStart = date.atStartOfDay(); - LocalDateTime dayEnd = date.atTime(23, 59, 59); - - return QExhibit.exhibit.startDate.loe(dayEnd)//<= - .and(QExhibit.exhibit.endDate.goe(dayStart));//>= + return QExhibit.exhibit.startDate.loe(date)//<= + .and(QExhibit.exhibit.endDate.goe(date));//>= } - } diff --git a/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java b/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java index c2690a1..91da2d3 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java @@ -7,8 +7,8 @@ import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; import org.atdev.artrip.domain.favortie.data.FavoriteExhibit; -import org.atdev.artrip.domain.favortie.dto.CalenderResponse; -import org.atdev.artrip.domain.favortie.dto.FavoriteResponse; +import org.atdev.artrip.domain.favortie.web.dto.response.CalenderResponse; +import org.atdev.artrip.domain.favortie.web.dto.response.FavoriteResponse; import org.atdev.artrip.domain.favortie.repository.FavoriteExhibitRepository; import org.atdev.artrip.global.apipayload.code.status.CommonError; import org.atdev.artrip.global.apipayload.code.status.ExhibitError; @@ -154,7 +154,7 @@ private FavoriteResponse toFavoriteResponse(FavoriteExhibit favorite) { Exhibit exhibit = favorite.getExhibit(); var hall = exhibit.getExhibitHall(); - String period = exhibit.getStartDate().format(fmt) + " ~ " + exhibit.getEndDate().format(fmt); + String period = exhibit.getStartDate().format(fmt) + " - " + exhibit.getEndDate().format(fmt); return FavoriteResponse.builder() .favoriteId(favorite.getFavoriteId()) diff --git a/src/main/java/org/atdev/artrip/domain/favortie/web/FavoriteController.java b/src/main/java/org/atdev/artrip/domain/favortie/web/controller/FavoriteController.java similarity index 97% rename from src/main/java/org/atdev/artrip/domain/favortie/web/FavoriteController.java rename to src/main/java/org/atdev/artrip/domain/favortie/web/controller/FavoriteController.java index d4d5ab9..d3ce691 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/web/FavoriteController.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/web/controller/FavoriteController.java @@ -1,11 +1,11 @@ -package org.atdev.artrip.domain.favortie.web; +package org.atdev.artrip.domain.favortie.web.controller; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.domain.favortie.dto.CalenderResponse; -import org.atdev.artrip.domain.favortie.dto.FavoriteResponse; +import org.atdev.artrip.domain.favortie.web.dto.response.CalenderResponse; +import org.atdev.artrip.domain.favortie.web.dto.response.FavoriteResponse; import org.atdev.artrip.domain.favortie.service.FavoriteExhibitService; import org.atdev.artrip.global.apipayload.CommonResponse; import org.atdev.artrip.global.apipayload.code.status.CommonError; diff --git a/src/main/java/org/atdev/artrip/domain/favortie/dto/CalenderResponse.java b/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/CalenderResponse.java similarity index 85% rename from src/main/java/org/atdev/artrip/domain/favortie/dto/CalenderResponse.java rename to src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/CalenderResponse.java index 6875bc1..46e5bb5 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/dto/CalenderResponse.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/CalenderResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.favortie.dto; +package org.atdev.artrip.domain.favortie.web.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/org/atdev/artrip/domain/favortie/dto/FavoriteResponse.java b/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java similarity index 89% rename from src/main/java/org/atdev/artrip/domain/favortie/dto/FavoriteResponse.java rename to src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java index d0ed264..0316291 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/dto/FavoriteResponse.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.favortie.dto; +package org.atdev.artrip.domain.favortie.web.dto.response; import lombok.*; import org.atdev.artrip.domain.Enum.Status; diff --git a/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java b/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java index 13851b2..67315b7 100644 --- a/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java +++ b/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java @@ -1,22 +1,24 @@ package org.atdev.artrip.domain.home.converter; +import org.atdev.artrip.domain.Enum.KeywordType; import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.reponse.ExhibitDetailResponse; -import org.atdev.artrip.domain.home.web.dto.RandomExhibitFilterRequestDto; +import org.atdev.artrip.domain.home.web.dto.*; import org.atdev.artrip.domain.home.response.FilterResponse; import org.atdev.artrip.domain.home.response.HomeListResponse; -import org.atdev.artrip.domain.home.web.dto.RandomExhibitRequest; +import org.atdev.artrip.domain.keyword.data.Keyword; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Component; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; @Component public class HomeConverter { - private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd"); public FilterResponse toFilterResponse(Slice slice) { @@ -35,7 +37,7 @@ public FilterResponse toFilterResponse(Slice slice) { public HomeListResponse toHomeExhibitListResponse(Exhibit exhibit){ - String period = exhibit.getStartDate().format(formatter) + " ~ " + exhibit.getEndDate().format(formatter); + String period = exhibit.getStartDate().format(formatter) + " - " + exhibit.getEndDate().format(formatter); return HomeListResponse.builder() .exhibit_id(exhibit.getExhibitId()) @@ -49,7 +51,7 @@ public HomeListResponse toHomeExhibitListResponse(Exhibit exhibit){ public ExhibitDetailResponse toHomeExhibitResponse(Exhibit exhibit) { var hall = exhibit.getExhibitHall(); - String period = exhibit.getStartDate().format(formatter) + " ~ " + exhibit.getEndDate().format(formatter); + String period = exhibit.getStartDate().format(formatter) + " - " + exhibit.getEndDate().format(formatter); Double lat = hall.getLatitude() != null ? hall.getLatitude().doubleValue() : null; Double lng = hall.getLongitude() != null ? hall.getLongitude().doubleValue() : null; @@ -73,40 +75,65 @@ public ExhibitDetailResponse toHomeExhibitResponse(Exhibit exhibit) { } - public RandomExhibitRequest from(RandomExhibitFilterRequestDto request, Set genres, Set styles) { + public RandomExhibitRequest from(PersonalizedRequestDto request, List keywords) { + + Set genres = keywords.stream() + .filter(k -> k.getType() == KeywordType.GENRE) + .map(Keyword::getName) + .collect(Collectors.toSet()); + + Set styles = keywords.stream() + .filter(k -> k.getType() == KeywordType.STYLE) + .map(Keyword::getName) + .collect(Collectors.toSet()); return RandomExhibitRequest.builder() .isDomestic(request.getIsDomestic()) .country(normalize(request.getCountry())) .region(normalize(request.getRegion())) - .date(request.getDate()) - .genres(isEmpty(genres)) - .styles(isEmpty(styles)) - .limit(request.getLimit() != null ? request.getLimit() : 3) + .genres(toNullable(genres)) + .styles(toNullable(styles)) + .limit(3) .build(); } - private String normalize(String value) { - if (value == null) return null; - if ("전체".equals(value)) return null; - return value; - } - public RandomExhibitRequest from(RandomExhibitFilterRequestDto request) { - return from(request, request.getGenres(), request.getStyles()); - } + public RandomExhibitRequest from(ScheduleRandomRequestDto request) { - private Set isEmpty(Set value) { - return (value == null || value.isEmpty()) ? null : value; + return RandomExhibitRequest.builder() + .isDomestic(request.getIsDomestic()) + .country(normalize(request.getCountry())) + .region(normalize(request.getRegion())) + .date(request.getDate()) + .limit(2) + .build(); } - public RandomExhibitRequest fromGenre(RandomExhibitFilterRequestDto request) { - + public RandomExhibitRequest fromToday(TodayRandomRequestDto request) { return RandomExhibitRequest.builder() .isDomestic(request.getIsDomestic()) - .country(request.getCountry()) - .region(request.getRegion()) - .singleGenre(request.getSingleGenre() != null ? request.getSingleGenre() : null) - .limit(request.getLimit() != null ? request.getLimit() : 3) + .country(normalize(request.getCountry())) + .region(normalize(request.getRegion())) + .limit(3) .build(); } + public RandomExhibitRequest fromGenre(GenreRandomRequestDto request) { + return RandomExhibitRequest.builder() + .isDomestic(request.getIsDomestic()) + .country(normalize(request.getCountry())) + .region(normalize(request.getRegion())) + .singleGenre(request.getSingleGenre()) + .limit(3) + .build(); + } + + private Set toNullable(Set value) { + return (value == null || value.isEmpty()) ? null : value; + } + + private String normalize(String value) { + if (value == null) return null; + if ("전체".equals(value)) return null; + return value; + } + } diff --git a/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java b/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java index 05d1279..7f588fb 100644 --- a/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java +++ b/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java @@ -17,7 +17,8 @@ public class HomeListResponse { private String title; private String posterUrl; private Status status; - private String exhibitPeriod; + + private String hallName; } diff --git a/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java b/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java index 38bed0b..4c0e06f 100644 --- a/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java +++ b/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java @@ -6,14 +6,13 @@ import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.reponse.ExhibitDetailResponse; import org.atdev.artrip.domain.exhibit.web.dto.request.ExhibitFilterRequestDto; -import org.atdev.artrip.domain.home.web.dto.RandomExhibitFilterRequestDto; +import org.atdev.artrip.domain.home.web.dto.*; import org.atdev.artrip.domain.exhibitHall.repository.ExhibitHallRepository; import org.atdev.artrip.domain.home.converter.HomeConverter; import org.atdev.artrip.domain.home.response.FilterResponse; import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; import org.atdev.artrip.domain.home.response.HomeListResponse; -import org.atdev.artrip.domain.home.web.dto.RandomExhibitRequest; import org.atdev.artrip.domain.keyword.data.Keyword; import org.atdev.artrip.domain.keyword.data.UserKeyword; import org.atdev.artrip.domain.keyword.repository.UserKeywordRepository; @@ -72,7 +71,6 @@ public ExhibitDetailResponse getExhibitDetail(Long exhibitId) { return homeConverter.toHomeExhibitResponse(exhibit); } - public List getAllPersonalized(Long userId,Boolean isDomestic){ if (!userRepository.existsById(userId)) { @@ -139,9 +137,8 @@ public FilterResponse getFilterExhibit(ExhibitFilterRequestDto dto, Pageable pag return homeConverter.toFilterResponse(slice); } - @Transactional - public List getRandomPersonalized(Long userId, RandomExhibitFilterRequestDto requestDto){ + public List getRandomPersonalized(Long userId, PersonalizedRequestDto requestDto){ if (!userRepository.existsById(userId)) { throw new GeneralException(UserError._USER_NOT_FOUND); @@ -152,51 +149,32 @@ public List getRandomPersonalized(Long userId, RandomExhibitFi .map(UserKeyword::getKeyword) .toList(); - Set genres = userKeywords.stream() - .filter(k -> k.getType() == KeywordType.GENRE) - .map(Keyword::getName) - .collect(Collectors.toSet()); - - Set styles = userKeywords.stream() - .filter(k -> k.getType() == KeywordType.STYLE) - .map(Keyword::getName) - .collect(Collectors.toSet()); - RandomExhibitRequest filter = homeConverter.from( - requestDto, - genres.isEmpty() ? null : genres, - styles.isEmpty() ? null : styles - ); - + requestDto, + userKeywords + ); return exhibitRepository.findRandomExhibits(filter); } - public List getRandomSchedule(RandomExhibitFilterRequestDto request){ + public List getRandomSchedule(ScheduleRandomRequestDto request){ RandomExhibitRequest filter = homeConverter.from(request); return exhibitRepository.findRandomExhibits(filter); } - public List getRandomGenre(RandomExhibitFilterRequestDto request){ + public List getRandomGenre(GenreRandomRequestDto request){ RandomExhibitRequest filter = homeConverter.fromGenre(request); return exhibitRepository.findRandomExhibits(filter); } - public List getToday(RandomExhibitFilterRequestDto request){ + public List getToday(TodayRandomRequestDto request){ - RandomExhibitRequest filter = homeConverter.from(request); -// RandomExhibitFilterRequestDto filter = RandomExhibitFilterRequestDto.builder() -// .isDomestic(isDomestic) -// .country(country) -// .region(region) -// .limit(limit) -// .build(); + RandomExhibitRequest filter = homeConverter.fromToday(request); return exhibitRepository.findRandomExhibits(filter); } - } \ No newline at end of file diff --git a/src/main/java/org/atdev/artrip/domain/home/web/controller/HomeController.java b/src/main/java/org/atdev/artrip/domain/home/web/controller/HomeController.java index 51f23d6..6caa906 100644 --- a/src/main/java/org/atdev/artrip/domain/home/web/controller/HomeController.java +++ b/src/main/java/org/atdev/artrip/domain/home/web/controller/HomeController.java @@ -1,14 +1,11 @@ package org.atdev.artrip.domain.home.web.controller; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.atdev.artrip.domain.home.web.dto.RandomExhibitFilterRequestDto; -import org.atdev.artrip.domain.home.web.validationgroup.GenreRandomGroup; -import org.atdev.artrip.domain.home.web.validationgroup.ScheduleRandomGroup; +import org.atdev.artrip.domain.home.web.dto.*; import org.atdev.artrip.domain.home.response.HomeListResponse; import org.atdev.artrip.domain.home.service.HomeService; -import org.atdev.artrip.domain.home.web.validationgroup.TodayRandomGroup; -import org.atdev.artrip.domain.home.web.validationgroup.UserCustomGroup; import org.atdev.artrip.global.apipayload.CommonResponse; import org.atdev.artrip.global.apipayload.code.status.CommonError; import org.atdev.artrip.global.apipayload.code.status.HomeError; @@ -16,7 +13,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -38,7 +34,7 @@ public class HomeController { - isDomestic = false (국외) - country: 필수 - region: 사용하지 않음 - + 예시 요청: { "isDomestic": true, @@ -51,8 +47,7 @@ public class HomeController { @PostMapping("/personalized/random") public ResponseEntity>> getRandomPersonalized( @AuthenticationPrincipal UserDetails userDetails, - @Validated(UserCustomGroup.class) - @RequestBody RandomExhibitFilterRequestDto requestDto){ + @Valid @RequestBody PersonalizedRequestDto requestDto){ long userId = Long.parseLong(userDetails.getUsername()); @@ -83,8 +78,7 @@ public ResponseEntity>> getRandomPersonali ) @PostMapping("/schedule") public ResponseEntity>> getRandomSchedule( - @Validated(ScheduleRandomGroup.class) - @RequestBody RandomExhibitFilterRequestDto request){ + @Valid @RequestBody ScheduleRandomRequestDto request){ List exhibits= homeService.getRandomSchedule(request); @@ -115,8 +109,7 @@ public ResponseEntity>> getRandomSchedule( ) @PostMapping("/genre/random") public ResponseEntity>> getRandomExhibits( - @Validated(GenreRandomGroup.class) - @RequestBody RandomExhibitFilterRequestDto request){ + @Valid @RequestBody GenreRandomRequestDto request){ List exhibits = homeService.getRandomGenre(request); return ResponseEntity.ok(CommonResponse.onSuccess(exhibits)); @@ -144,8 +137,7 @@ public ResponseEntity>> getRandomExhibits( ) @PostMapping("recommend/today") public ResponseEntity>> getTodayRecommendations( - @Validated(TodayRandomGroup.class) - @RequestBody RandomExhibitFilterRequestDto request){ + @Valid @RequestBody TodayRandomRequestDto request){ List exhibits = homeService.getToday(request); @@ -157,4 +149,5 @@ public ResponseEntity>> getTodayRecommenda // List exhibits = exhibitService.getCuratedExhibits(); // return ResponseEntity.ok(ApiResponse.onSuccess(exhibits)); // } + } diff --git a/src/main/java/org/atdev/artrip/domain/home/web/dto/BaseRandomRequestDto.java b/src/main/java/org/atdev/artrip/domain/home/web/dto/BaseRandomRequestDto.java new file mode 100644 index 0000000..706f753 --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/home/web/dto/BaseRandomRequestDto.java @@ -0,0 +1,37 @@ +package org.atdev.artrip.domain.home.web.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class BaseRandomRequestDto { + + @NotNull + protected Boolean isDomestic; + + protected String region; + protected String country; + + @Schema(hidden = true) + @AssertTrue(message = "국내 전시는 region 필수(전체 가능), 국외 전시는 country 필수(전체 가능)이며 둘을 동시에 보낼 수 없습니다.") + public boolean isDomesticRegionCountryValid() { + if (isDomestic == null) return true; + + boolean hasRegion = region != null && !region.isBlank(); + boolean hasCountry = country != null && !country.isBlank(); + + if (isDomestic) { + return hasRegion && !hasCountry; + } else { + return hasCountry && !hasRegion; + } + } +} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/dto/GenreRandomRequestDto.java b/src/main/java/org/atdev/artrip/domain/home/web/dto/GenreRandomRequestDto.java new file mode 100644 index 0000000..e0841c6 --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/home/web/dto/GenreRandomRequestDto.java @@ -0,0 +1,17 @@ +package org.atdev.artrip.domain.home.web.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class GenreRandomRequestDto extends BaseRandomRequestDto{ + + @NotNull + private String singleGenre; +} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/dto/PersonalizedRequestDto.java b/src/main/java/org/atdev/artrip/domain/home/web/dto/PersonalizedRequestDto.java new file mode 100644 index 0000000..dcc4a99 --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/home/web/dto/PersonalizedRequestDto.java @@ -0,0 +1,11 @@ +package org.atdev.artrip.domain.home.web.dto; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@Builder +public class PersonalizedRequestDto extends BaseRandomRequestDto{ + +} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/dto/RandomExhibitFilterRequestDto.java b/src/main/java/org/atdev/artrip/domain/home/web/dto/RandomExhibitFilterRequestDto.java deleted file mode 100644 index 9b02dbe..0000000 --- a/src/main/java/org/atdev/artrip/domain/home/web/dto/RandomExhibitFilterRequestDto.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.atdev.artrip.domain.home.web.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.AssertTrue; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import org.atdev.artrip.domain.home.web.validationgroup.GenreRandomGroup; -import org.atdev.artrip.domain.home.web.validationgroup.ScheduleRandomGroup; -import org.atdev.artrip.domain.home.web.validationgroup.TodayRandomGroup; -import org.atdev.artrip.domain.home.web.validationgroup.UserCustomGroup; - -import java.time.LocalDate; -import java.util.Set; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class RandomExhibitFilterRequestDto { - - @NotNull(groups = { - TodayRandomGroup.class, - ScheduleRandomGroup.class, - GenreRandomGroup.class, - UserCustomGroup.class - }) - private Boolean isDomestic; - - private String country; - private String region; - - @NotEmpty(groups = GenreRandomGroup.class) // 장르별 랜덤 조회 - private String singleGenre; - - @Schema(hidden = true) - private Set genres; - @Schema(hidden = true) - private Set styles; - - @NotNull(groups = ScheduleRandomGroup.class) // 이번주 전시 조회 - private LocalDate date; - - @Schema(hidden = true) - private Integer limit; - - @Schema(hidden = true) - @AssertTrue(message = "국내 전시는 region 필수(전체 가능), 국외 전시는 country 필수(전체 가능)이며 둘을 동시에 보낼 수 없습니다.", - groups = {TodayRandomGroup.class, - ScheduleRandomGroup.class, - GenreRandomGroup.class, - UserCustomGroup.class}) - public boolean isDomesticRegionCountryValid() { - if (isDomestic == null) return true; - - boolean hasRegion = region != null && !region.isBlank(); - boolean hasCountry = country != null && !country.isBlank(); - - if (isDomestic) { - return hasRegion && !hasCountry; - } else { - return hasCountry && !hasRegion; - } - } - -} \ No newline at end of file diff --git a/src/main/java/org/atdev/artrip/domain/home/web/dto/ScheduleRandomRequestDto.java b/src/main/java/org/atdev/artrip/domain/home/web/dto/ScheduleRandomRequestDto.java new file mode 100644 index 0000000..f5a471a --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/home/web/dto/ScheduleRandomRequestDto.java @@ -0,0 +1,20 @@ +package org.atdev.artrip.domain.home.web.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +import java.time.LocalDate; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ScheduleRandomRequestDto extends BaseRandomRequestDto { + + @NotNull + private LocalDate date; + +} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/dto/TodayRandomRequestDto.java b/src/main/java/org/atdev/artrip/domain/home/web/dto/TodayRandomRequestDto.java new file mode 100644 index 0000000..939ebce --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/home/web/dto/TodayRandomRequestDto.java @@ -0,0 +1,15 @@ +package org.atdev.artrip.domain.home.web.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@Builder +public class TodayRandomRequestDto extends BaseRandomRequestDto{ + +} + diff --git a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/GenreRandomGroup.java b/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/GenreRandomGroup.java deleted file mode 100644 index 873172d..0000000 --- a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/GenreRandomGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.atdev.artrip.domain.home.web.validationgroup; - -public interface GenreRandomGroup { -} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/ScheduleRandomGroup.java b/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/ScheduleRandomGroup.java deleted file mode 100644 index f605a98..0000000 --- a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/ScheduleRandomGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.atdev.artrip.domain.home.web.validationgroup; - -public interface ScheduleRandomGroup { -} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/TodayRandomGroup.java b/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/TodayRandomGroup.java deleted file mode 100644 index b9329fd..0000000 --- a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/TodayRandomGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.atdev.artrip.domain.home.web.validationgroup; - -public interface TodayRandomGroup { -} diff --git a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/UserCustomGroup.java b/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/UserCustomGroup.java deleted file mode 100644 index ff0f04d..0000000 --- a/src/main/java/org/atdev/artrip/domain/home/web/validationgroup/UserCustomGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.atdev.artrip.domain.home.web.validationgroup; - -public interface UserCustomGroup { -} diff --git a/src/main/java/org/atdev/artrip/domain/keyword/repository/KeywordRepository.java b/src/main/java/org/atdev/artrip/domain/keyword/repository/KeywordRepository.java index a35e76b..9a74297 100644 --- a/src/main/java/org/atdev/artrip/domain/keyword/repository/KeywordRepository.java +++ b/src/main/java/org/atdev/artrip/domain/keyword/repository/KeywordRepository.java @@ -7,8 +7,10 @@ import java.util.List; import java.util.Optional; +import java.util.Set; @Repository public interface KeywordRepository extends JpaRepository { + List findByNameIn(Set matchedKeyword); } \ No newline at end of file diff --git a/src/main/java/org/atdev/artrip/domain/review/converter/ReviewConverter.java b/src/main/java/org/atdev/artrip/domain/review/converter/ReviewConverter.java index 3d007ea..fe8167e 100644 --- a/src/main/java/org/atdev/artrip/domain/review/converter/ReviewConverter.java +++ b/src/main/java/org/atdev/artrip/domain/review/converter/ReviewConverter.java @@ -108,11 +108,14 @@ public static ReviewListResponse toSummary(Review review){ public static ReviewExhibitResponse toExhibitReviewSummary(Review review){ + User user = review.getUser(); + return ReviewExhibitResponse.builder() .reviewId(review.getReviewId()) .content(createSummary(review,20)) .thumbnailUrl(createThumbnail(review)) .visitDate(review.getVisitDate()) + .Nickname(user.getNickName()) .build(); } diff --git a/src/main/java/org/atdev/artrip/domain/search/service/ExhibitSearchService.java b/src/main/java/org/atdev/artrip/domain/search/service/ExhibitSearchService.java index bbf25d8..f8b96bd 100644 --- a/src/main/java/org/atdev/artrip/domain/search/service/ExhibitSearchService.java +++ b/src/main/java/org/atdev/artrip/domain/search/service/ExhibitSearchService.java @@ -8,7 +8,7 @@ import co.elastic.clients.elasticsearch.core.search.Hit; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.domain.search.response.ExhibitSearchResponse; +import org.atdev.artrip.domain.search.web.dto.response.ExhibitSearchResponse; import org.atdev.artrip.elastic.document.ExhibitDocument; import org.atdev.artrip.global.apipayload.code.status.SearchError; import org.atdev.artrip.global.apipayload.exception.GeneralException; @@ -16,7 +16,6 @@ import java.io.IOException; import java.time.format.DateTimeFormatter; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -115,7 +114,7 @@ public List searchExhibits(String keyword, Long userId) { } private ExhibitSearchResponse convertToExhibitResponse(ExhibitDocument doc) { - DateTimeFormatter ftt = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + DateTimeFormatter ftt = DateTimeFormatter.ofPattern("yyyy.MM.dd"); return ExhibitSearchResponse.builder() .id(doc.getId()) @@ -127,6 +126,8 @@ private ExhibitSearchResponse convertToExhibitResponse(ExhibitDocument doc) { .posterUrl(doc.getPosterUrl()) .ticketUrl(doc.getTicketUrl()) .keywords(doc.getKeywords()) + .latitude(doc.getLatitude()) + .longitude(doc.getLongitude()) .build(); } diff --git a/src/main/java/org/atdev/artrip/domain/search/web/controller/SearchController.java b/src/main/java/org/atdev/artrip/domain/search/web/controller/SearchController.java index e4b14ab..68e9b90 100644 --- a/src/main/java/org/atdev/artrip/domain/search/web/controller/SearchController.java +++ b/src/main/java/org/atdev/artrip/domain/search/web/controller/SearchController.java @@ -6,7 +6,7 @@ import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.search.service.SearchHistoryService; import org.atdev.artrip.global.apipayload.CommonResponse; -import org.atdev.artrip.domain.search.response.ExhibitSearchResponse; +import org.atdev.artrip.domain.search.web.dto.response.ExhibitSearchResponse; import org.atdev.artrip.domain.search.service.ExhibitSearchService; import org.atdev.artrip.global.apipayload.code.status.CommonError; import org.atdev.artrip.global.apipayload.code.status.SearchError; diff --git a/src/main/java/org/atdev/artrip/domain/search/response/ExhibitSearchResponse.java b/src/main/java/org/atdev/artrip/domain/search/web/dto/response/ExhibitSearchResponse.java similarity index 91% rename from src/main/java/org/atdev/artrip/domain/search/response/ExhibitSearchResponse.java rename to src/main/java/org/atdev/artrip/domain/search/web/dto/response/ExhibitSearchResponse.java index f7d1a24..8f9ea82 100644 --- a/src/main/java/org/atdev/artrip/domain/search/response/ExhibitSearchResponse.java +++ b/src/main/java/org/atdev/artrip/domain/search/web/dto/response/ExhibitSearchResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.search.response; +package org.atdev.artrip.domain.search.web.dto.response; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/org/atdev/artrip/domain/search/response/SearchHistoryResponse.java b/src/main/java/org/atdev/artrip/domain/search/web/dto/response/SearchHistoryResponse.java similarity index 88% rename from src/main/java/org/atdev/artrip/domain/search/response/SearchHistoryResponse.java rename to src/main/java/org/atdev/artrip/domain/search/web/dto/response/SearchHistoryResponse.java index c04fa27..4503001 100644 --- a/src/main/java/org/atdev/artrip/domain/search/response/SearchHistoryResponse.java +++ b/src/main/java/org/atdev/artrip/domain/search/web/dto/response/SearchHistoryResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.domain.search.response; +package org.atdev.artrip.domain.search.web.dto.response; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/org/atdev/artrip/elastic/document/ExhibitDocument.java b/src/main/java/org/atdev/artrip/elastic/document/ExhibitDocument.java index a48fee4..25254f6 100644 --- a/src/main/java/org/atdev/artrip/elastic/document/ExhibitDocument.java +++ b/src/main/java/org/atdev/artrip/elastic/document/ExhibitDocument.java @@ -1,5 +1,6 @@ package org.atdev.artrip.elastic.document; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Builder; @@ -12,6 +13,7 @@ import org.springframework.data.elasticsearch.annotations.FieldType; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -29,11 +31,13 @@ public class ExhibitDocument { private String title; private String description; - @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second) - private LocalDateTime startDate; + @Field(type = FieldType.Date, format = DateFormat.date) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd") + private LocalDate startDate; - @Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second) - private LocalDateTime endDate; + @Field(type = FieldType.Date, format = DateFormat.date) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy.MM.dd") + private LocalDate endDate; private Status status; private String posterUrl; diff --git a/src/main/java/org/atdev/artrip/elastic/service/ExhibitIndexService.java b/src/main/java/org/atdev/artrip/elastic/service/ExhibitIndexService.java index cf29da2..1e411d3 100644 --- a/src/main/java/org/atdev/artrip/elastic/service/ExhibitIndexService.java +++ b/src/main/java/org/atdev/artrip/elastic/service/ExhibitIndexService.java @@ -47,6 +47,8 @@ private ExhibitDocument convertToDocument(Exhibit exhibit) { .status(exhibit.getStatus()) .posterUrl(exhibit.getPosterUrl()) .ticketUrl(exhibit.getTicketUrl()) + .latitude(exhibit.getExhibitHall().getLatitude()) + .longitude(exhibit.getExhibitHall().getLongitude()) .keywords(keywordInfos); return builder.build(); @@ -106,8 +108,8 @@ public void createAndApplyIndex() { .analyzer("edge_ngram_analyzer") .searchAnalyzer("standard") .fields("nori", f -> f.text(t2 -> t2.analyzer("ngram_nori_analyzer"))))) - .properties("startDate", p -> p.date(d -> d.format("strict_date_hour_minute_second"))) - .properties("endDate", p -> p.date(d -> d.format("strict_date_hour_minute_second"))) + .properties("startDate", p -> p.date(d -> d.format("yyyy.MM.dd"))) + .properties("endDate", p -> p.date(d -> d.format("yyyy.MM.dd"))) .properties("status", p -> p.keyword(k -> k)) .properties("posterUrl", p -> p.keyword(k -> k)) .properties("ticketUrl", p -> p.keyword(k -> k)) @@ -179,6 +181,7 @@ public int indexAllExhibits() { throw new GeneralException(ElasticError._ES_BULK_INDEX_FAILED); } } + @Transactional public void indexExhibit(Exhibit exhibit) { try { diff --git a/src/main/java/org/atdev/artrip/external/config/WebClientConfig.java b/src/main/java/org/atdev/artrip/external/config/WebClientConfig.java index c9776f9..01ad122 100644 --- a/src/main/java/org/atdev/artrip/external/config/WebClientConfig.java +++ b/src/main/java/org/atdev/artrip/external/config/WebClientConfig.java @@ -6,16 +6,13 @@ import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.external.publicdata.properties.PublicDataProperties; +import org.atdev.artrip.external.culturalapi.properties.PublicDataProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; -import org.springframework.http.codec.xml.Jaxb2XmlDecoder; -import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/client/BasePublicDataClient.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java similarity index 79% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/client/BasePublicDataClient.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java index 36c3501..64af69b 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/client/BasePublicDataClient.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/BasePublicDataClient.java @@ -1,13 +1,12 @@ -package org.atdev.artrip.external.publicdata.exhibit.client; +package org.atdev.artrip.external.culturalapi.cultureinfo.client; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryRegistry; import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.external.publicdata.exhibit.dto.request.BasePublicDataRequest; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.BasePublicDataItem; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.BasePublicDataResponse; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.PublicDataResponse; -import org.atdev.artrip.external.publicdata.properties.PublicDataProperties; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.request.BasePublicDataRequest; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.BasePublicDataItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.PublicDataResponse; +import org.atdev.artrip.external.culturalapi.properties.PublicDataProperties; import org.atdev.artrip.global.apipayload.exception.ExternalApiException; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatusCode; @@ -80,14 +79,19 @@ protected URI buildUri(R request) { UriComponentsBuilder builder = UriComponentsBuilder .fromHttpUrl(properties.getBaseUrl()) .path(getApiPath()) - .queryParam("serviceKey", properties.getServiceKey()) - .queryParam("pageNo", request.getPageNo()) - .queryParam("numOfRows", request.getNumOfRows()) +// .queryParam("serviceKey", properties.getServiceKey()) + .queryParam("PageNo", request.getPageNo()) + .queryParam("numOfrows", request.getNumOfRows()) .queryParam("sortStdr", request.getSortStdr()); addRequestParams(builder, request); - return builder.build(true).toUri(); + String queryString = builder.build().getQuery(); + + String finalUrl = properties.getBaseUrl() + getApiPath() + + "?serviceKey=" +properties.getServiceKey() + "&" + queryString; + + return URI.create(finalUrl); } protected void logResponse(S response) { diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java new file mode 100644 index 0000000..f6e5fe2 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/client/CultureInfoApiClient.java @@ -0,0 +1,160 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.client; + +import io.github.resilience4j.retry.RetryRegistry; +import lombok.extern.slf4j.Slf4j; +import org.atdev.artrip.domain.exhibit.reponse.ExhibitDetailResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.request.CultureInfoRequest; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoListResponse; +import org.atdev.artrip.external.culturalapi.properties.PublicDataProperties; +import org.atdev.artrip.global.apipayload.exception.ExternalApiException; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatusCode; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +import java.net.URI; + +@Slf4j +@Component +public class CultureInfoApiClient extends BasePublicDataClient{ + + private static final String REALM_CODE_EXHIBITION = "D000"; + private static final String PATH_REALM2 = "/realm2"; + private static final String PATH_DETAILS2 = "/detail2"; + + public CultureInfoApiClient( + WebClient publicDataWebClient, + PublicDataProperties properties, + RetryRegistry retryRegistry){ + super(publicDataWebClient, properties, retryRegistry); + } + + @Override + protected String getApiName() { + return "cultureInfoApi"; + } + + @Override + protected String getApiPath() { + return PATH_REALM2; + } + + @Override + protected ParameterizedTypeReference getResponseTypeRef() { + return new ParameterizedTypeReference<>() {}; + } + + @Override + protected Class getResponseClass() { + return CultureInfoListResponse.class; + } + + @Override + protected void addRequestParams(UriComponentsBuilder builder, CultureInfoRequest request) { + builder.queryParam("realmCode", REALM_CODE_EXHIBITION); + + if (request.getFrom() != null) { + builder.queryParam("from", request.getFrom()); + } + + if (request.getTo() != null) { + builder.queryParam("to", request.getTo()); + } + + if (request.getSido() != null) { + builder.queryParam("sido", request.getSido()); + } + + if (request.getKeyword() != null) { + builder.queryParam("keyword", request.getKeyword()); + } + + if (request.getPlace() != null) { + builder.queryParam("place", request.getPlace()); + } + } + + public CultureInfoListResponse fetchFromDate(String from, int pageNo) { + log.debug("지정 날짜 이후 전시 목록 조회 : {}", pageNo); + CultureInfoRequest request = CultureInfoRequest.builder() + .serviceKey(properties.getServiceKey()) + .pageNo(pageNo) + .numOfRows(properties.getPageSize()) + .from(from) + .build(); + return fetch(request); + } + + public CultureInfoListResponse fetchByPeriod(String from, String to, int pageNo) { + log.debug("realm2 기간별 전시 목록 조회 : {} ======", pageNo); + CultureInfoRequest request = CultureInfoRequest.builder() + .serviceKey(properties.getServiceKey()) + .pageNo(pageNo) + .numOfRows(properties.getPageSize()) + .from(from) + .to(to) + .build(); + return fetch(request); + } + + public CultureInfoListResponse fetchExhibits(int pageNo) { + log.debug("realm 전체 목록 조회 : {}", pageNo); + CultureInfoRequest request = CultureInfoRequest.builder() + .serviceKey(properties.getServiceKey()) + .pageNo(pageNo) + .numOfRows(pageNo) + .build(); + return fetch(request); + } + + public CultureInfoDetailResponse fetchDetails(String idx) { + URI uri = buildDetailUri(idx); + log.debug("상세 조회 URI: {}", uri); + + try { + return webClient.get() + .uri(uri) + .retrieve() + .onStatus(HttpStatusCode::isError, response -> + response.bodyToMono(String.class) + .flatMap(body -> Mono.error( + new ExternalApiException(getApiName(), response.statusCode(), body)))) + .bodyToMono(CultureInfoDetailResponse.class) + .doOnSuccess(this::logDetailResponse) + .doOnError(e -> log.error("상세 조회 에러 - idx :{} error : {}", idx, e.getMessage())) + .block(); + } catch (Exception e) { + log.error("상세 API 호출 실패 - idx :{} error : {}", idx, e.getMessage()); + return null; + } + } + + private URI buildDetailUri(String idx) { + String queryString = UriComponentsBuilder.newInstance() + .queryParam("seq", idx) + .build() + .getQuery(); + + String finalUrl = properties.getBaseUrl() + PATH_DETAILS2 + + "?serviceKey=" + properties.getServiceKey() + "&" + queryString; + + return URI.create(finalUrl); + } + + private void logDetailResponse(CultureInfoDetailResponse response) { + if (response == null) { + log.warn("상세 응답 null : {}", getApiName()); + return; + } + if (response.isSuccess() && response.hasData()) { + log.debug("상세 조회 - title : {}", response.getItem().getTitle()); + } else { + log.warn("상세 조회 실패 - error: {}", response.getErrorMessage()); + } + } + +} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/request/BasePublicDataRequest.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/BasePublicDataRequest.java similarity index 75% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/request/BasePublicDataRequest.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/BasePublicDataRequest.java index f8e2412..8dc5709 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/request/BasePublicDataRequest.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/BasePublicDataRequest.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.request; +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.request; import lombok.*; import lombok.experimental.SuperBuilder; @@ -15,7 +15,7 @@ public class BasePublicDataRequest { protected int pageNo = 1; @Builder.Default - protected int numOfRows = 100; + protected int numOfRows = 50; @Builder.Default protected String sortStdr = "1"; diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/request/ExhibitRequest.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/CultureInfoRequest.java similarity index 67% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/request/ExhibitRequest.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/CultureInfoRequest.java index 24903ba..7c9e9b0 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/request/ExhibitRequest.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/request/CultureInfoRequest.java @@ -1,7 +1,6 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.request; +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.request; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @@ -10,12 +9,13 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class ExhibitRequest extends BasePublicDataRequest{ +public class CultureInfoRequest extends BasePublicDataRequest{ private String from; private String to; private String sido; private String keyword; private String realmCode; + private String place; } diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BaseCultureInfoResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BaseCultureInfoResponse.java new file mode 100644 index 0000000..9e17d8c --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BaseCultureInfoResponse.java @@ -0,0 +1,30 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BaseCultureInfoResponse{ + + @JacksonXmlProperty(localName = "header") + protected PublicDataHeader header; + + public boolean isSuccess() { + return header != null && header.isSuccess(); + } + + public String getErrorMessage() { + if (header == null) { + return "응답 헤더가 없습니다."; + } + return String.format("[%s] %s", header.getResultCode(), header.getResultMsg()); + } + + public abstract boolean hasData(); + + public abstract T getResult(); +} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/BasePublicDataItem.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BasePublicDataItem.java similarity index 70% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/BasePublicDataItem.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BasePublicDataItem.java index 19d41f3..67f842e 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/BasePublicDataItem.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/BasePublicDataItem.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; public interface BasePublicDataItem { diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailItem.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailItem.java new file mode 100644 index 0000000..551e1b2 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailItem.java @@ -0,0 +1,44 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class CultureInfoDetailItem { + + @JacksonXmlProperty(localName = "seq") + private String seq; + + @JacksonXmlProperty(localName = "title") + private String title; + + @JacksonXmlProperty(localName = "price") + private String price; + + @JacksonXmlProperty(localName = "contents1") + private String contents1; + + @JacksonXmlProperty(localName = "url") + private String url; + + @JacksonXmlProperty(localName = "phone") + private String phone; + + @JacksonXmlProperty(localName = "imgUrl") + private String imgUrl; + + @JacksonXmlProperty(localName = "placeUrl") + private String placeUrl; + + @JacksonXmlProperty(localName = "placeAddr") + private String placeAddr; + + @JacksonXmlProperty(localName = "placeSeq") + private String placeSeq; +} diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailResponse.java new file mode 100644 index 0000000..8b9e3c7 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoDetailResponse.java @@ -0,0 +1,54 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +@JacksonXmlRootElement(localName = "response") +public class CultureInfoDetailResponse extends BaseCultureInfoResponse { + + @JacksonXmlProperty(localName = "body") + private DetailBody body; + + @Override + public boolean hasData() { + return body != null && body.getItems() != null && body.getItems().getItem() != null; + } + + @Override + public CultureInfoDetailItem getResult() { + return getItem(); + } + + public CultureInfoDetailItem getItem() { + if (body != null && body.getItems() != null) { + return body.getItems().getItem(); + } + return null; + } + + @Getter + @Setter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DetailBody { + @JacksonXmlProperty(localName = "items") + private Items items; + } + + @Getter + @Setter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Items { + @JacksonXmlProperty(localName = "item") + private CultureInfoDetailItem item; + } +} diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoItem.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoItem.java new file mode 100644 index 0000000..1d25ce7 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoItem.java @@ -0,0 +1,73 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class CultureInfoItem implements BasePublicDataItem{ + + + @JacksonXmlProperty(localName = "seq") + private String seq; + + @JacksonXmlProperty(localName = "serviceName") + private String serviceName; + + @JacksonXmlProperty(localName = "title") + private String title; + + @JacksonXmlProperty(localName = "startDate") + private String startDate; + + @JacksonXmlProperty(localName = "endDate") + private String endDate; + + @JacksonXmlProperty(localName = "place") + private String place; + + @JacksonXmlProperty(localName = "realmName") + private String realmName; + + @JacksonXmlProperty(localName = "area") + private String area; + + @JacksonXmlProperty(localName = "sigungu") + private String sigungu; + + @JacksonXmlProperty(localName = "thumbnail") + private String thumbnail; + + @JacksonXmlProperty(localName = "gpsX") + private String gpsX; + + @JacksonXmlProperty(localName = "gpsY") + private String gpsY; + + @Override + public String getUniqueId() { + return seq != null ? seq : (title + "_" + startDate); + } + + @Override + public boolean isValid() { + return title != null && !title.isBlank(); + } + + public boolean isExhibition() { + return "전시".equals(realmName); + } + + public String getFullAddress() { + if (area == null) return null; + if (sigungu != null && !sigungu.isBlank()) { + return area + " " + sigungu; + } + return area; + } +} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/BasePublicDataResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoListResponse.java similarity index 53% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/BasePublicDataResponse.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoListResponse.java index 3985675..1e730b7 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/BasePublicDataResponse.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/CultureInfoListResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; @@ -16,83 +16,74 @@ @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) @JacksonXmlRootElement(localName = "response") -public class BasePublicDataResponse { - - @JacksonXmlProperty(localName = "header") - private Header header; +public class CultureInfoListResponse extends BaseCultureInfoResponse> + implements PublicDataResponse { @JacksonXmlProperty(localName = "body") - private Body body; - - @Getter - @Setter - @NoArgsConstructor - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Header { - @JacksonXmlProperty(localName = "resultCode") - private String resultCode; - - @JacksonXmlProperty(localName = "resultMsg") - private String resultMsg; - } - - @Getter - @Setter - @NoArgsConstructor - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Body { - @JacksonXmlProperty(localName = "totalCount") - private int totalCount; - - @JacksonXmlProperty(localName = "pageNo") - private int pageNo; - - @JacksonXmlProperty(localName = "numOfRows") - private int numOfRows; - - @JacksonXmlElementWrapper(localName = "items") - @JacksonXmlProperty(localName = "item") - private List items; - } + private ListBody body; - public boolean isSuccess() { - return header != null && "0000".equals(header.getResultCode()); + @Override + public boolean hasData() { + return body != null && body.getItems() != null && !body.getItems().isEmpty(); } - public String getErrorMessage() { - if (header == null) { - return "응답 헤더가 없습니다."; - } - return String.format("[%s] %s", header.getResultCode(), header.getResultMsg()); + @Override + public List getResult() { + return getItems(); } + @Override public int getTotalPages() { - if (body == null || body.getNumOfRows() == 0 ) { + if (body == null || body.getNumOfrows() == 0) { return 0; } - return (int) Math.ceil((double) body.getTotalCount() / body.getNumOfRows()); + return (int) Math.ceil((double) body.getTotalCount() / body.getNumOfrows()); } + @Override public int getCurrentPage() { return body != null ? body.getPageNo() : 0; } + @Override public int getTotalCount() { return body != null ? body.getTotalCount() : 0; } - public List getItems() { - if (body == null || body.getItems() == null) { + @Override + public List getItems() { + if(body ==null || body.getItems() ==null) { return Collections.emptyList(); } return body.getItems(); } - public boolean hasData() { - return !getItems().isEmpty(); - } - public boolean isLastPage() { return getCurrentPage() >= getTotalPages(); } + + public List getExhibits() { + return getItems().stream() + .filter(CultureInfoItem::isExhibition) + .toList(); + } + + @Getter + @Setter + @NoArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ListBody { + @JacksonXmlProperty(localName = "totalCount") + private int totalCount; + + @JacksonXmlProperty(localName = "PageNo") + private int pageNo; + + @JacksonXmlProperty(localName = "numOfrows") + private int numOfrows; + + @JacksonXmlElementWrapper(localName = "items") + @JacksonXmlProperty(localName = "item") + private List items; + } } diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataHeader.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataHeader.java new file mode 100644 index 0000000..82f8f57 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataHeader.java @@ -0,0 +1,24 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class PublicDataHeader { + + @JacksonXmlProperty(localName = "resultCode") + private String resultCode; + + @JacksonXmlProperty(localName = "resultMsg") + private String resultMsg; + + public boolean isSuccess() { + return "00".equals(resultCode); + } +} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/PublicDataResponse.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataResponse.java similarity index 84% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/PublicDataResponse.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataResponse.java index 6bbceaf..50aab38 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/PublicDataResponse.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/dto/response/PublicDataResponse.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.response; +package org.atdev.artrip.external.culturalapi.cultureinfo.dto.response; import java.util.List; diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/mapper/ExhibitMapper.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java similarity index 50% rename from src/main/java/org/atdev/artrip/external/publicdata/exhibit/mapper/ExhibitMapper.java rename to src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java index e7acfa2..01eb5b3 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/mapper/ExhibitMapper.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/mapper/CultureInfoMapper.java @@ -1,11 +1,12 @@ -package org.atdev.artrip.external.publicdata.exhibit.mapper; +package org.atdev.artrip.external.culturalapi.cultureinfo.mapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.atdev.artrip.domain.Enum.Status; import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.ExhibitItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoItem; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -17,17 +18,26 @@ @Slf4j @Component -@RequiredArgsConstructor -public class ExhibitMapper { +public class CultureInfoMapper { - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + private static final DateTimeFormatter DATE_FORMATTER_DOT = DateTimeFormatter.ofPattern("yyyy.MM.dd"); private static final DateTimeFormatter DATE_FORMATTER_DASH = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - public Exhibit toExhibit(ExhibitItem item) { + public Exhibit toExhibit(CultureInfoItem item) { + if (item == null) return null; + try { - LocalDateTime startDate = parseEventPeriod(item.getEventPeriod(), true); - LocalDateTime endDate = parseEventPeriod(item.getEventPeriod(), false); + LocalDate startDate = parseDate(item.getStartDate()); + LocalDate endDate = parseDate(item.getEndDate()); + + if (endDate != null && endDate.isBefore(LocalDate.now())) { + return null; + } + Status status = calculateStatus(startDate, endDate); + if (status == Status.FINISHED) { + return null; + } return Exhibit.builder() .title(truncate(item.getTitle(), 255)) @@ -36,7 +46,7 @@ public Exhibit toExhibit(ExhibitItem item) { .endDate(endDate) .status(status) .posterUrl(item.getThumbnail()) - .ticketUrl(item.getUrl()) + .ticketUrl(null) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); @@ -46,7 +56,23 @@ public Exhibit toExhibit(ExhibitItem item) { } } - public ExhibitHall toExhibitHall(ExhibitItem item) { + public void mergeDetailToExhibit(Exhibit exhibit, CultureInfoDetailItem detail) { + if (exhibit == null || detail == null ) return; + + if (StringUtils.hasText(detail.getUrl())) { + exhibit.setTicketUrl(truncate(detail.getUrl(), 500)); + } + + if (StringUtils.hasText(detail.getContents1())) { + String existingDesc = exhibit.getDescription(); + String enhancedDesc = existingDesc +"\n\n" + detail.getContents1(); + exhibit.setDescription(truncate(enhancedDesc, 2000)); + } + + exhibit.setUpdatedAt(LocalDateTime.now()); + } + + public ExhibitHall toExhibitHall(CultureInfoItem item) { if (item == null || !StringUtils.hasText(item.getPlace())) { return null; } @@ -55,8 +81,9 @@ public ExhibitHall toExhibitHall(ExhibitItem item) { .name(truncate(item.getPlace(), 255)) .country("대한민국") .region(parseRegion(item.getArea())) - .address(item.getPlaceAddr()) - .phone(item.getPhone()) + .address(item.getFullAddress()) + .phone(null) + .homepageUrl(null) .isDomestic(true) .latitude(parseCoordinate(item.getGpsX())) .longitude(parseCoordinate(item.getGpsY())) @@ -65,44 +92,56 @@ public ExhibitHall toExhibitHall(ExhibitItem item) { .build(); } - private LocalDateTime parseEventPeriod(String eventPeriod, boolean isStart) { - if (!StringUtils.hasText(eventPeriod)) { - return null; - } + public void mergeDetailToHall(ExhibitHall hall, CultureInfoDetailItem detail) { + if (hall == null || detail == null) return; - try { - // "2020-12-17 ~ 2021-04-11" 형식 - if (eventPeriod.contains("~")) { - String[] dates = eventPeriod.split("~"); - if (dates.length != 2) { - return null; - } - String dateStr = isStart ? dates[0].trim() : dates[1].trim(); - return LocalDate.parse(dateStr, DATE_FORMATTER_DASH).atStartOfDay(); + boolean updated = false; + + if (StringUtils.hasText(detail.getPhone()) && !StringUtils.hasText(hall.getPhone())) { + String cleanPhone = detail.getPhone().replaceAll("[^0-9-]", ""); + + if (StringUtils.hasText(cleanPhone)) { + hall.setPhone(truncate(cleanPhone, 20)); + updated = true; } + } - return parseDate(eventPeriod); - } catch (Exception e) { - log.warn("eventPeriod 파싱 실패: {}", eventPeriod); - return null; + if (StringUtils.hasText(detail.getPlaceUrl()) && !StringUtils.hasText(hall.getHomepageUrl())) { + hall.setHomepageUrl(truncate(detail.getPlaceUrl(), 500)); + updated = true; + } + + if (StringUtils.hasText(detail.getPlaceAddr()) && (!StringUtils.hasText(hall.getAddress()) || hall.getAddress().length() < detail.getPlaceAddr().length())) { + hall.setAddress(truncate(detail.getPlaceAddr(), 500)); + } + + if (updated) { + hall.setUpdatedAt(LocalDateTime.now()); } } - private LocalDateTime parseDate(String date) { + private LocalDate parseDate(String date) { if (!StringUtils.hasText(date)) { return null; } try { - if (date.length() == 8) { - return LocalDate.parse(date, DATE_FORMATTER).atStartOfDay(); - } + if (date.contains("-")) { - return LocalDate.parse(date, DATE_FORMATTER_DASH).atStartOfDay(); + return LocalDate.parse(date, DATE_FORMATTER_DASH); } + + if (date.contains(".")) { + return LocalDate.parse(date, DATE_FORMATTER_DOT); + } + + if (date.length() == 8) { + return LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE); + } + return null; } catch (DateTimeParseException e) { - log.warn("날짜 파싱 실패: {}", date); + log.warn("날짜 파싱 실패 : {}", date); return null; } } @@ -120,13 +159,13 @@ private BigDecimal parseCoordinate(String coordinate) { } } - private Status calculateStatus(LocalDateTime startDate, LocalDateTime endDate) { + private Status calculateStatus(LocalDate startDate, LocalDate endDate) { if (startDate == null || endDate == null) { return Status.ONGOING; } - LocalDateTime now = LocalDateTime.now(); - LocalDateTime threeDaysLater = now.plusDays(3); + LocalDate now = LocalDate.now(); + LocalDate threeDaysLater = now.plusDays(3); if (now.isBefore(startDate)) { return Status.UPCOMING; @@ -139,32 +178,24 @@ private Status calculateStatus(LocalDateTime startDate, LocalDateTime endDate) { } } - private String buildDescription(ExhibitItem item) { + private String buildDescription(CultureInfoItem item) { StringBuilder sb = new StringBuilder(); - // 작가 정보 - if (StringUtils.hasText(item.getPerson())) { - sb.append("작가: ").append(item.getPerson()); + if (StringUtils.hasText(item.getRealmName())) { + sb.append("분류: ").append(item.getRealmName()); } - // 전시 장소 - if (StringUtils.hasText(item.getVenue())) { - if (sb.length() > 0) sb.append("\n"); - sb.append("장소: ").append(item.getVenue()); - } - - // 설명 - if (StringUtils.hasText(item.getSubDescription())) { - if (sb.length() > 0) sb.append("\n\n"); - sb.append(item.getSubDescription()); + if (StringUtils.hasText(item.getArea())) { + if (sb.length() > 0) { + sb.append("지역 :").append(item.getArea()); + } } - // 요금 - if (StringUtils.hasText(item.getCharge())) { - if (sb.length() > 0) sb.append("\n\n"); - sb.append("관람료: ").append(item.getCharge()); + if (StringUtils.hasText(item.getPlace())) { + if (sb.length() > 0) sb.append("\n"); + sb.append("장소: ").append(item.getPlace()); } - + return truncate(sb.toString(), 2000); } diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java new file mode 100644 index 0000000..94ee7d5 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/CultureInfoSyncService.java @@ -0,0 +1,333 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.service; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.atdev.artrip.domain.exhibit.data.Exhibit; +import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; +import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; +import org.atdev.artrip.domain.exhibitHall.repository.ExhibitHallRepository; +import org.atdev.artrip.domain.keyword.data.Keyword; +import org.atdev.artrip.elastic.service.ExhibitIndexService; +import org.atdev.artrip.external.culturalapi.cultureinfo.client.CultureInfoApiClient; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoDetailResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoItem; +import org.atdev.artrip.external.culturalapi.cultureinfo.dto.response.CultureInfoListResponse; +import org.atdev.artrip.external.culturalapi.cultureinfo.mapper.CultureInfoMapper; +import org.atdev.artrip.global.apipayload.exception.ExternalApiException; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.core.parameters.P; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CultureInfoSyncService { + + private final CultureInfoApiClient cultureInfoApiClient; + private final CultureInfoMapper cultureInfoMapper; + private final ExhibitRepository exhibitRepository; + private final ExhibitHallRepository exhibitHallRepository; + private final KeywordMatchingService keywordMatchingService; + private final ExhibitIndexService exhibitIndexService; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final int MAX_PAGES = 200; + private static final long DETAIL_API_MS = 500; + + @Scheduled(cron = "0 0 3 * * *") + public void scheduledSync() { + log.info("----- 전시 정보 스케줄 동기화 start -----"); + + String from = LocalDate.now().format(DATE_FORMATTER); + String to = LocalDate.now().plusMonths(3).format(DATE_FORMATTER); + + SyncResult result = syncByPeriod(from, to); + + log.info("----- 전시 정보 스케줄 동기화 done -----"); + log.info("result - new: {}, updated: {}, failed: {}", + result.getInserted(), result.getUpdated(), result.getFailed()); + } + + public SyncResult syncLatest() { + String from = LocalDate.now().format(DATE_FORMATTER); + log.info("오늘[{}] 이후 동기화 ", from); + return executeSync(pageNo -> cultureInfoApiClient.fetchFromDate(from, pageNo)); + } + + public SyncResult syncAll() { + return executeSync(pageNo -> cultureInfoApiClient.fetchExhibits(pageNo)); + } + + public SyncResult syncByPeriod(String from, String to) { + return executeSync(pageNo -> cultureInfoApiClient.fetchByPeriod(from, to, pageNo)); + } + + private SyncResult executeSync(Function fetcher) { + SyncResult totalResult = new SyncResult(); + int pageNo = 1; + + try { + while (pageNo <= MAX_PAGES) { + CultureInfoListResponse response = fetcher.apply(pageNo); + + if (response.hasData() && !response.getExhibits().isEmpty()) { + String firstTitle = response.getExhibits().get(0).getTitle(); + log.info("Page No :{}, first item : {}", pageNo, firstTitle ); + } + + if (!response.isSuccess() || !response.hasData()) { + log.info("no date found - {}", pageNo); + break; + } + + List exhibitOnly = response.getExhibits(); + if (!exhibitOnly.isEmpty()) { + SyncResult pageResult = processPage(exhibitOnly); + totalResult.merge(pageResult); + + log.debug("페이지 {} 처리 완료 - new: {}, updated: {}, failed: {}", + pageNo, pageResult.getInserted(), pageResult.getUpdated(), pageResult.getFailed()); + + } + + if (response.isLastPage()) { + break; + } + + pageNo++; + Thread.sleep(300); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("동기화 중단됨", e); + } catch (ExternalApiException e) { + log.error("API 요청 failed - page: {}, error: {}", pageNo, e.getMessage()); + } + + log.info("동기화 완료 - new: {}, updated: {}, failed: {}", + totalResult.getInserted(), totalResult.getUpdated(), totalResult.getFailed()); + + return totalResult; + } + + @Transactional + public SyncResult processPage(List items) { + SyncResult result = new SyncResult(); + Map hallCache = new HashMap<>(); + + for (CultureInfoItem item : items) { + try { + processItem(item, hallCache, result); + } catch (Exception e) { + log.debug("처리 실패 - title: {}, error : {}", item.getTitle(), e.getMessage()); + result.incrementFailed(); + } + } + return result; + } + + private void processItem(CultureInfoItem item, Map hallCache, SyncResult result) { + if (!item.isValid()) { + result.incrementSkipped(); + return; + } + + Exhibit exhibit = cultureInfoMapper.toExhibit(item); + if (exhibit == null) { + result.incrementSkipped(); + return; + } + + if (exhibit.getStartDate() == null) { + result.incrementSkipped(); + return; + } + + if (exhibit.getEndDate() != null && exhibit.getEndDate().isBefore(LocalDate.now())) { + result.incrementSkipped(); + return; + } + + ExhibitHall exhibitHall = getOrCreateExhibitHall(item, hallCache); + exhibit.setExhibitHall(exhibitHall); + + CultureInfoDetailItem detailItem = fetchDetailIfNeeded(item); + + if (detailItem != null) { + cultureInfoMapper.mergeDetailToExhibit(exhibit, detailItem); + if (exhibitHall != null) { + cultureInfoMapper.mergeDetailToHall(exhibitHall, detailItem); + } + } + + Optional existing = exhibitRepository + .findByTitleAndStartDate(exhibit.getTitle(), exhibit.getStartDate()); + + if (existing.isPresent()) { + updateExhibit(existing.get(), exhibit); + matchAndSaveKeywords(existing.get(), item, detailItem); + + exhibitIndexService.indexExhibit(existing.get()); + result.incrementUpdated(); + } else { + Exhibit saved = exhibitRepository.save(exhibit); + matchAndSaveKeywords(saved, item, detailItem); + exhibitRepository.save(saved); + + exhibitIndexService.indexExhibit(saved); + result.incrementInserted(); + } + } + + private CultureInfoDetailItem fetchDetailIfNeeded(CultureInfoItem item) { + if (item.getSeq() == null || item.getSeq().isBlank()) return null; + + try { + Thread.sleep(DETAIL_API_MS); + + CultureInfoDetailResponse detailResponse = cultureInfoApiClient.fetchDetails(item.getSeq()); + + if (detailResponse != null && detailResponse.isSuccess() && detailResponse.hasData()) { + log.debug("상세 정보 조회 - seq : {} title : {}", item.getSeq(), item.getTitle()); + return detailResponse.getItem(); + } else { + log.debug("상세 정보 없음 - seq: {}", item.getSeq()); + return null; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("상세 조회 중단 - seq : {}", item.getSeq()); + return null; + } catch (Exception e) { + log.warn("상세 정보 조회 실패 - seq : {}, error: {}", item.getSeq(), e.getMessage()); + return null; + } + } + + private void matchAndSaveKeywords(Exhibit exhibit, CultureInfoItem item, CultureInfoDetailItem detail) { + String searchText = item.getTitle(); + if (detail != null && detail.getContents1() != null) { + searchText += " " + detail.getContents1(); + } + List keywords = keywordMatchingService.matchKeywords(searchText, item.getRealmName()); + exhibit.getKeywords().addAll(keywords); + } + + private ExhibitHall getOrCreateExhibitHall(CultureInfoItem item, Map cache) { + String placeName = item.getPlace(); + if (placeName == null || placeName.isBlank()) { + return null; + } + + if (cache.containsKey(placeName)) { + ExhibitHall cached = cache.get(placeName); + updateHallIfNeeded(cached, item); + return cached; + } + + Optional existing = exhibitHallRepository.findByName(placeName); + if (existing.isPresent()) { + ExhibitHall hall = existing.get(); + updateHallIfNeeded(hall, item); + cache.put(placeName, hall); + return hall; + } + + ExhibitHall newHall = cultureInfoMapper.toExhibitHall(item); + if (newHall != null) { + newHall = exhibitHallRepository.save(newHall); + cache.put(placeName, newHall); + } + + return newHall; + } + + private void updateExhibit(Exhibit existing, Exhibit newData) { + if (newData.getDescription() != null) { + existing.setDescription(newData.getDescription()); + } + if (newData.getEndDate() != null) { + existing.setEndDate(newData.getEndDate()); + } + if (newData.getStatus() != null) { + existing.setStatus(newData.getStatus()); + } + if (newData.getPosterUrl() != null) { + existing.setPosterUrl(newData.getPosterUrl()); + } + if (newData.getTicketUrl() != null) { + existing.setTicketUrl(newData.getTicketUrl()); + } + existing.setUpdatedAt(LocalDateTime.now()); + } + + private void updateHallIfNeeded(ExhibitHall hall, CultureInfoItem item) { + boolean updated = false; + + if (hall.getLatitude() == null && item.getGpsY() != null) { + hall.setLatitude(parseCoordinate(item.getGpsY())); + hall.setLongitude(parseCoordinate(item.getGpsX())); + } + + if (hall.getRegion() == null && item.getArea() !=null) { + hall.setRegion(item.getArea()); + updated = true; + } + + if (hall.getAddress() == null && item.getPlace() != null) { + hall.setAddress(item.getFullAddress()); + updated = true; + } + + if (updated) { + hall.setUpdatedAt(LocalDateTime.now()); + exhibitHallRepository.save(hall); + } + } + + + private BigDecimal parseCoordinate(String coordinate) { + if (coordinate == null || coordinate.isBlank()) { + return null; + } + try { + return new BigDecimal(coordinate.trim()); + } catch (NumberFormatException e) { + log.warn("좌표 파싱 실패: {}", coordinate); + return null; + } + } + + @Getter + public static class SyncResult { + private int inserted = 0; + private int updated = 0; + private int failed = 0; + private int skipped = 0; + + public void incrementInserted() { inserted++; } + public void incrementUpdated() { updated++; } + public void incrementFailed() { failed++; } + public void incrementSkipped() { skipped++; } + + public void merge(SyncResult other) { + this.inserted += other.inserted; + this.updated += other.updated; + this.failed += other.failed; + this.skipped += other.skipped; + } + } +} diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/KeywordMatchingService.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/KeywordMatchingService.java new file mode 100644 index 0000000..b626fea --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/service/KeywordMatchingService.java @@ -0,0 +1,65 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.atdev.artrip.domain.keyword.data.Keyword; +import org.atdev.artrip.domain.keyword.repository.KeywordRepository; +import org.springframework.stereotype.Service; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Slf4j +@Service +@RequiredArgsConstructor +public class KeywordMatchingService { + + private final KeywordRepository keywordRepository; + + private static final Map> KEYWORD_PATTERNS = Map.ofEntries( + // GENRE + Map.entry("사진", List.of("사진", "사진전", "포토", "photo", "photography")), + Map.entry("회화", List.of("회화", "그림", "페인팅", "painting", "드로잉")), + Map.entry("조각", List.of("조각", "sculpture", "입체")), + Map.entry("디지털/미디어", List.of("디지털", "미디어", "digital", "media", "NFT", "AI")), + Map.entry("설치 미술", List.of("설치", "installation")), + Map.entry("현대 미술", List.of("현대", "컨템포러리", "contemporary")), + Map.entry("팝아트", List.of("팝", "팝아트", "pop art")), + + // STYLE + Map.entry("몰입형", List.of("몰입", "immersive")), + Map.entry("인터렉티브", List.of("체험", "인터랙티브", "interactive")), + Map.entry("VR/AR", List.of("VR", "AR", "가상현실", "증강현실")), + Map.entry("미디어 아트", List.of("미디어아트", "미디어 아트", "media art")) + ); + + public List matchKeywords(String searchText, String realmName) { + + log.info("title: {}, description : {}, realmName : {}", searchText); + + String lowerText = searchText != null ? searchText.toLowerCase() : ""; + Set matchedKeywords = new HashSet<>(); + + for (Map.Entry> entry : KEYWORD_PATTERNS.entrySet()) { + for (String pattern : entry.getValue()) { + if (lowerText.contains(pattern.toLowerCase())) { + matchedKeywords.add(entry.getKey()); + break; + } + } + } + + if (matchedKeywords.isEmpty()) { + if ("전시".equals(realmName)) { + matchedKeywords.add("현대 미술"); + } else { + matchedKeywords.add("순수 미술"); + } + } + + return keywordRepository.findByNameIn(matchedKeywords); + } + +} diff --git a/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java new file mode 100644 index 0000000..cf78098 --- /dev/null +++ b/src/main/java/org/atdev/artrip/external/culturalapi/cultureinfo/web/controller/CultureInfoSyncController.java @@ -0,0 +1,86 @@ +package org.atdev.artrip.external.culturalapi.cultureinfo.web.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.atdev.artrip.elastic.service.ExhibitIndexService; +import org.atdev.artrip.external.culturalapi.cultureinfo.service.CultureInfoSyncService; +import org.atdev.artrip.global.apipayload.CommonResponse; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("api/admin/sync") +@RequiredArgsConstructor +public class CultureInfoSyncController { + + private final CultureInfoSyncService CultureInfoSyncService; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + private final ExhibitIndexService exhibitIndexService; + + @GetMapping("exhibits/latest") + public CommonResponse> syncLatest() { + String today = LocalDate.now().format(DATE_FORMATTER); + log.info("최신 전시 동기화 : {}", today); + + CultureInfoSyncService.SyncResult result = CultureInfoSyncService.syncLatest(); + + return CommonResponse.onSuccess(Map.of( + "from", today, + "inserted", result.getInserted(), + "updated", result.getUpdated(), + "skipped", result.getSkipped(), + "failed", result.getFailed() + )); + + } + + @GetMapping("/exhibits") + public CommonResponse> syncByPeriod( + @RequestParam String from, + @RequestParam(required = false) String to) { + log.info("기간별 전시 동기화 시작 - 기간: {} ~ {}", from, to != null ? to : "전체"); + + CultureInfoSyncService.SyncResult result; + if (to != null) { + result = CultureInfoSyncService.syncByPeriod(from, to); + } else { + result = CultureInfoSyncService.syncByPeriod(from, null); + } + + return CommonResponse.onSuccess(Map.of( + "period", Map.of( + "from", from, + "to", to != null ? to : "unlimited" + ), + "inserted", result.getInserted(), + "updated", result.getUpdated(), + "skipped", result.getSkipped(), + "failed", result.getFailed() + )); + } + + @PostMapping("/exhibits/all") + public CommonResponse> syncAll() { + log.info("전체 전시 동기화 시작"); + + CultureInfoSyncService.SyncResult result = CultureInfoSyncService.syncAll(); + + return CommonResponse.onSuccess(Map.of( + "inserted", result.getInserted(), + "updated", result.getUpdated(), + "skipped", result.getSkipped(), + "failed", result.getFailed() + )); + } + + @PostMapping("/reindex") + public CommonResponse reindexES() { + int count = exhibitIndexService.indexAllExhibits(); + return CommonResponse.onSuccess(count); + } +} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/properties/PublicDataProperties.java b/src/main/java/org/atdev/artrip/external/culturalapi/properties/PublicDataProperties.java similarity index 77% rename from src/main/java/org/atdev/artrip/external/publicdata/properties/PublicDataProperties.java rename to src/main/java/org/atdev/artrip/external/culturalapi/properties/PublicDataProperties.java index b81db4e..d4ac4ce 100644 --- a/src/main/java/org/atdev/artrip/external/publicdata/properties/PublicDataProperties.java +++ b/src/main/java/org/atdev/artrip/external/culturalapi/properties/PublicDataProperties.java @@ -1,4 +1,4 @@ -package org.atdev.artrip.external.publicdata.properties; +package org.atdev.artrip.external.culturalapi.properties; import lombok.Getter; import lombok.Setter; @@ -8,7 +8,7 @@ @Getter @Setter @Component -@ConfigurationProperties(prefix = "external.publicdata") +@ConfigurationProperties(prefix = "external.culturalapi") public class PublicDataProperties { private String baseUrl; diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/client/ExhibitApiClient.java b/src/main/java/org/atdev/artrip/external/publicdata/exhibit/client/ExhibitApiClient.java deleted file mode 100644 index 8a59932..0000000 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/client/ExhibitApiClient.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.atdev.artrip.external.publicdata.exhibit.client; - -import io.github.resilience4j.retry.RetryRegistry; -import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.external.publicdata.exhibit.dto.request.ExhibitRequest; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.ExhibitItem; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.ExhibitResponse; -import org.atdev.artrip.external.publicdata.properties.PublicDataProperties; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.util.UriComponentsBuilder; - -@Slf4j -@Component -public class ExhibitApiClient extends BasePublicDataClient{ - - public ExhibitApiClient( - WebClient publicDataWebClient, - PublicDataProperties properties, - RetryRegistry retryRegistry){ - super(publicDataWebClient, properties, retryRegistry); - } - - @Override - protected String getApiName() { - return "exhibitApi"; - } - - @Override - protected String getApiPath() { - return ""; - } - - @Override - protected ParameterizedTypeReference getResponseTypeRef() { - return new ParameterizedTypeReference<>(){}; - } - - @Override - protected Class getResponseClass() { - return ExhibitResponse.class; - } - - @Override - protected void addRequestParams(UriComponentsBuilder builder, ExhibitRequest request) { - if (request.getFrom() != null) { - builder.queryParam("from", request.getFrom()); - } - - if(request.getTo() != null) { - builder.queryParam("to", request.getTo()); - } - - if (request.getSido() != null) { - builder.queryParam("sido", request.getSido()); - } - - if (request.getRealmCode() != null) { - builder.queryParam("realmCode", request.getRealmCode()); - } - } - - public ExhibitResponse fetchByPeriod(String from, String to, int pageNo) { - ExhibitRequest request = ExhibitRequest.builder() - .serviceKey(properties.getServiceKey()) - .pageNo(pageNo) - .numOfRows(properties.getPageSize()) - .from(from) - .to(to) - .build(); - - return fetch(request); - } - - public ExhibitResponse fetchExhibits(int pageNo) { - ExhibitRequest request = ExhibitRequest.builder() - .serviceKey(properties.getServiceKey()) - .pageNo(pageNo) - .numOfRows(properties.getPageSize()) - .build(); - return fetch(request); - } -} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/ExhibitItem.java b/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/ExhibitItem.java deleted file mode 100644 index 2d0a53a..0000000 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/ExhibitItem.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.response; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class ExhibitItem implements BasePublicDataItem{ - - @JacksonXmlProperty(localName = "title") - private String title; - - @JacksonXmlProperty(localName = "collectionDb") - private String collectionDb; // 전시정보 - - @JacksonXmlProperty(localName = "subjectCategory") - private String subjectCategory; // 국내전시/해외전시 - - @JacksonXmlProperty(localName = "rights") - private String rights; // 미술관명 (예: 국립현대미술관) - - @JacksonXmlProperty(localName = "charge") - private String charge; // 요금 - - @JacksonXmlProperty(localName = "venue") - private String venue; // 전시 장소 (예: 1전시실) - - @JacksonXmlProperty(localName = "eventPeriod") - private String eventPeriod; // 기간 (예: 2020-12-17 ~ 2021-04-11) - - @JacksonXmlProperty(localName = "subDescription") - private String subDescription; // 설명 - - @JacksonXmlProperty(localName = "person") - private String person; // 작가 - - @JacksonXmlProperty(localName = "creator") - private String creator; - - @JacksonXmlProperty(localName = "publisher") - private String publisher; - - // place로 사용 (미술관명) - public String getPlace() { - return this.rights; - } - - // 기존 호환성 메서드 - public String getThumbnail() { - return null; // KCISA API에서 이미지 없음 - } - - public String getUrl() { - return null; // KCISA API에서 URL 없음 - } - - public String getPlaceAddr() { - return null; - } - - public String getArea() { - return null; - } - - public String getPhone() { - return null; - } - - public String getGpsX() { - return null; - } - - public String getGpsY() { - return null; - } - - @Override - public String getUniqueId() { - return title + "_" + (eventPeriod != null ? eventPeriod : ""); - } - - @Override - public boolean isValid() { - return title != null && !title.isBlank(); - } -} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/ExhibitResponse.java b/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/ExhibitResponse.java deleted file mode 100644 index 87ebc3e..0000000 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/dto/response/ExhibitResponse.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.atdev.artrip.external.publicdata.exhibit.dto.response; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Collections; -import java.util.List; - -@Getter -@Setter -@NoArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -@JacksonXmlRootElement(localName = "response") -public class ExhibitResponse implements PublicDataResponse { - - @JacksonXmlProperty(localName = "header") - private Header header; - - @JacksonXmlProperty(localName = "body") - private ExhibitBody body; - - @Getter - @Setter - @NoArgsConstructor - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Header { - @JacksonXmlProperty(localName = "resultCode") - private String resultCode; - - @JacksonXmlProperty(localName = "resultMsg") - private String resultMsg; - } - - @Getter - @Setter - @NoArgsConstructor - @JsonIgnoreProperties(ignoreUnknown = true) - public static class ExhibitBody { - @JacksonXmlProperty(localName = "totalCount") - private int totalCount; - - @JacksonXmlProperty(localName = "pageNo") - private int pageNo; - - @JacksonXmlProperty(localName = "numOfRows") - private int numOfRows; - - @JacksonXmlElementWrapper(localName = "items") - @JacksonXmlProperty(localName = "item") - private List items; - } - - @Override - public boolean isSuccess() { - return header != null && "0000".equals(header.getResultCode()); - } - - @Override - public String getErrorMessage() { - if (header == null) { - return "응답 헤더가 없습니다."; - } - return String.format("[%s] %s", header.getResultCode(), header.getResultMsg()); - } - - @Override - public int getTotalPages() { - if (body == null || body.getNumOfRows() == 0) { - return 0; - } - return (int) Math.ceil((double) body.getTotalCount() / body.getNumOfRows()); - } - - @Override - public int getCurrentPage() { - return body != null ? body.getPageNo() : 0; - } - - @Override - public int getTotalCount() { - return body != null ? body.getTotalCount() : 0; - } - - @Override - public List getItems() { - if (body == null || body.getItems() == null) { - return Collections.emptyList(); - } - return body.getItems(); - } - - public List getExhibitsOnly() { - return getItems().stream() - .filter(item -> "전시정보".equals(item.getCollectionDb())) - .toList(); - } -} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/service/ExhibitSyncService.java b/src/main/java/org/atdev/artrip/external/publicdata/exhibit/service/ExhibitSyncService.java deleted file mode 100644 index bcb9a40..0000000 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/service/ExhibitSyncService.java +++ /dev/null @@ -1,219 +0,0 @@ -package org.atdev.artrip.external.publicdata.exhibit.service; - -import jakarta.persistence.EntityManager; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.domain.exhibit.data.Exhibit; -import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; -import org.atdev.artrip.domain.exhibitHall.data.ExhibitHall; -import org.atdev.artrip.domain.exhibitHall.repository.ExhibitHallRepository; -import org.atdev.artrip.external.publicdata.exhibit.client.ExhibitApiClient; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.ExhibitItem; -import org.atdev.artrip.external.publicdata.exhibit.dto.response.ExhibitResponse; -import org.atdev.artrip.external.publicdata.exhibit.mapper.ExhibitMapper; -import org.atdev.artrip.global.apipayload.exception.ExternalApiException; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; - -@Slf4j -@Service -@RequiredArgsConstructor -public class ExhibitSyncService { - - private final ExhibitApiClient exhibitApiClient; - private final ExhibitMapper exhibitMapper; - private final ExhibitRepository exhibitRepository; - private final ExhibitHallRepository exhibitHallRepository; - private final EntityManager entityManager; - - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); - private static final int MAX_PAGES = 100; - - @Scheduled(cron = "0 0 3 * * *") - public void scheduledSync() { - log.info("----- 전시 정보 스케줄 동기화 start -----"); - - String from = LocalDate.now().format(DATE_FORMATTER); - String to = LocalDate.now().plusMonths(3).format(DATE_FORMATTER); - - SyncResult result = syncByPeriod(from, to); - - log.info("----- 전시 정보 스케줄 동기화 done -----"); - log.info("result - new: {}, updated: {}, failed: {}", - result.getInserted(), result.getUpdated(), result.getFailed()); - } - - public SyncResult syncAll() { - return executeSync(pageNo -> exhibitApiClient.fetchExhibits(pageNo)); - } - - public SyncResult syncByPeriod(String from, String to) { - return executeSync(pageNo -> exhibitApiClient.fetchByPeriod(from, to, pageNo)); - } - - private SyncResult executeSync(Function fetcher) { - SyncResult totalResult = new SyncResult(); - int pageNo = 1; - - try { - while (pageNo <= MAX_PAGES) { - ExhibitResponse response = fetcher.apply(pageNo); - - if (!response.isSuccess() || !response.hasData()) { - log.info("no date found - {}", pageNo); - break; - } - - SyncResult pageResult = processItems(response.getItems()); - totalResult.merge(pageResult); - - log.debug("페이지 {} 처리 완료 - new: {}, updated: {}, failed: {}", - pageNo, pageResult.getInserted(), pageResult.getUpdated(), pageResult.getFailed()); - - if (response.isLastPage()) { - break; - } - - pageNo++; - Thread.sleep(500); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("동기화 중단됨", e); - } catch (ExternalApiException e) { - log.error("API 요청 failed - page: {}, error: {}", pageNo, e.getMessage()); - } - - log.info("동기화 완료 - new: {}, updated: {}, failed: {}", - totalResult.getInserted(), totalResult.getUpdated(), totalResult.getFailed()); - - return totalResult; - } - - private SyncResult processItems(List items) { - SyncResult result = new SyncResult(); - Map hallCache = new HashMap<>(); - - for (ExhibitItem item : items) { - try { - processItem(item, hallCache, result); - entityManager.flush(); - } catch (Exception e) { - log.warn("처리 실패 - title: {}, error: {}", item.getTitle(), e.getMessage()); - result.incrementFailed(); - entityManager.clear(); - } - } - return result; - } - - private void processItem(ExhibitItem item, Map hallCache, SyncResult result) { - ExhibitHall exhibitHall = getOrCreateExhibitHall(item, hallCache); - - Exhibit exhibit = exhibitMapper.toExhibit(item); - if (exhibit == null) { - result.incrementFailed(); - return; - } - - exhibit.setExhibitHall(exhibitHall); - - Optional existing = exhibitRepository - .findByTitleAndStartDate(exhibit.getTitle(), exhibit.getStartDate()); - - if (existing.isPresent()) { - updateExhibit(existing.get(), exhibit); - result.incrementUpdated(); - } else { - exhibitRepository.save(exhibit); - result.incrementInserted(); - } - } - - private ExhibitHall getOrCreateExhibitHall(ExhibitItem item, Map cache) { - String placeName = item.getPlace(); - if (placeName == null || placeName.isBlank()) { - return null; - } - - if (cache.containsKey(placeName)) { - ExhibitHall cached = cache.get(placeName); - updateCoordinatesIfNeeded(cached, item); - return cached; - } - - Optional existing = exhibitHallRepository.findByName(placeName); - if (existing.isPresent()) { - ExhibitHall hall = existing.get(); - updateCoordinatesIfNeeded(hall, item); - cache.put(placeName, hall); - return hall; - } - - ExhibitHall newHall = exhibitMapper.toExhibitHall(item); - if (newHall != null) { - newHall = exhibitHallRepository.save(newHall); - cache.put(placeName, newHall); - } - - return newHall; - } - - private void updateExhibit(Exhibit existing, Exhibit newData) { - existing.setDescription(newData.getDescription()); - existing.setEndDate(newData.getEndDate()); - existing.setStatus(newData.getStatus()); - existing.setPosterUrl(newData.getPosterUrl()); - existing.setTicketUrl(newData.getTicketUrl()); - existing.setUpdatedAt(LocalDateTime.now()); - } - - private void updateCoordinatesIfNeeded(ExhibitHall hall, ExhibitItem item) { - if (hall.getLatitude() == null && item.getGpsX() != null) { - hall.setLatitude(parseCoordinate(item.getGpsX())); - hall.setLongitude(parseCoordinate(item.getGpsY())); - exhibitHallRepository.save(hall); - } - } - - private BigDecimal parseCoordinate(String coordinate) { - if (coordinate == null || coordinate.isBlank()) { - return null; - } - try { - return new BigDecimal(coordinate.trim()); - } catch (NumberFormatException e) { - log.warn("좌표 파싱 실패: {}", coordinate); - return null; - } - } - - @Getter - public static class SyncResult { - private int inserted = 0; - private int updated = 0; - private int failed = 0; - - public void incrementInserted() { inserted++; } - public void incrementUpdated() { updated++; } - public void incrementFailed() { failed++; } - - public void merge(SyncResult other) { - this.inserted += other.inserted; - this.updated += other.updated; - this.failed += other.failed; - } - } -} diff --git a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/web/controller/ExhibitSyncController.java b/src/main/java/org/atdev/artrip/external/publicdata/exhibit/web/controller/ExhibitSyncController.java deleted file mode 100644 index 3fc0330..0000000 --- a/src/main/java/org/atdev/artrip/external/publicdata/exhibit/web/controller/ExhibitSyncController.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.atdev.artrip.external.publicdata.exhibit.web.controller; - -import lombok.AllArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.atdev.artrip.external.publicdata.exhibit.service.ExhibitSyncService; -import org.atdev.artrip.global.apipayload.CommonResponse; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("api/admin/sync") -public class ExhibitSyncController { - - private final ExhibitSyncService exhibitSyncService; - - @PostMapping("/exhibits") - public CommonResponse syncAll() { - ExhibitSyncService.SyncResult result = exhibitSyncService.syncAll(); - return CommonResponse.onSuccess(result); - } -} diff --git a/src/main/resources/db/migration/V20251217.1__add_nickname_drop_pushtoken.sql b/src/main/resources/db/migration/V20251217.1__add_nickname_drop_pushtoken.sql new file mode 100644 index 0000000..0897778 --- /dev/null +++ b/src/main/resources/db/migration/V20251217.1__add_nickname_drop_pushtoken.sql @@ -0,0 +1,5 @@ +ALTER TABLE `user` +DROP COLUMN push_token; + +ALTER TABLE `user` + ADD COLUMN nick_name VARCHAR(50) NULL UNIQUE COMMENT '사용자 닉네임'; diff --git a/src/main/resources/db/migration/V20251217__modify_exhibit_status_eumn_type.sql b/src/main/resources/db/migration/V20251217__modify_exhibit_status_eumn_type.sql new file mode 100644 index 0000000..54590ef --- /dev/null +++ b/src/main/resources/db/migration/V20251217__modify_exhibit_status_eumn_type.sql @@ -0,0 +1,2 @@ +alter table exhibit +modify column status enum('FINISHED', 'ONGOING', 'UPCOMING', 'ENDING_SOON')not null ; \ No newline at end of file diff --git a/src/main/resources/db/migration/V20251219__modify_exhibit_date_type.sql b/src/main/resources/db/migration/V20251219__modify_exhibit_date_type.sql new file mode 100644 index 0000000..d20fbfd --- /dev/null +++ b/src/main/resources/db/migration/V20251219__modify_exhibit_date_type.sql @@ -0,0 +1,2 @@ +alter table exhibit modify column start_date date; +alter table exhibit modify column end_date date; \ No newline at end of file