This commit is contained in:
lsh9672
2025-06-11 16:31:06 +09:00
commit f0fbb47c51
164 changed files with 8667 additions and 0 deletions
@@ -0,0 +1,26 @@
package com.ktds.hi.store.biz.usecase.out;
import java.time.Duration;
import java.util.Optional;
/**
* 캐시 포트 인터페이스
* 캐시 기능을 정의
*/
public interface CachePort {
/**
* 캐시에서 매장 데이터 조회
*/
Optional<Object> getStoreCache(String key);
/**
* 캐시에 매장 데이터 저장
*/
void putStoreCache(String key, Object value, Duration ttl);
/**
* 캐시 무효화
*/
void invalidateStoreCache(Long storeId);
}
@@ -0,0 +1,25 @@
package com.ktds.hi.store.biz.usecase.out;
import com.ktds.hi.store.biz.domain.Store;
/**
* 이벤트 포트 인터페이스
* 이벤트 발행 기능을 정의
*/
public interface EventPort {
/**
* 매장 생성 이벤트 발행
*/
void publishStoreCreatedEvent(Store store);
/**
* 매장 수정 이벤트 발행
*/
void publishStoreUpdatedEvent(Store store);
/**
* 매장 삭제 이벤트 발행
*/
void publishStoreDeletedEvent(Long storeId);
}
@@ -0,0 +1,50 @@
package com.ktds.hi.store.infra.controller;
import com.ktds.hi.store.biz.usecase.in.ExternalIntegrationUseCase;
import com.ktds.hi.store.infra.dto.ExternalSyncRequest;
import com.ktds.hi.store.infra.dto.ExternalSyncResponse;
import com.ktds.hi.store.infra.dto.ExternalConnectRequest;
import com.ktds.hi.store.infra.dto.ExternalConnectResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* 외부 연동 컨트롤러 클래스
* 외부 플랫폼 연동 관련 API를 제공
*/
@RestController
@RequestMapping("/api/external")
@RequiredArgsConstructor
@Tag(name = "외부연동 API", description = "외부 플랫폼 연동 및 동기화 관련 API")
public class ExternalIntegrationController {
private final ExternalIntegrationUseCase externalIntegrationUseCase;
/**
* 외부 플랫폼 리뷰 동기화 API
*/
@PostMapping("/stores/{storeId}/sync-reviews")
@Operation(summary = "외부 플랫폼 리뷰 동기화", description = "외부 플랫폼의 리뷰를 동기화합니다.")
public ResponseEntity<ExternalSyncResponse> syncReviews(
@PathVariable Long storeId,
@Valid @RequestBody ExternalSyncRequest request) {
ExternalSyncResponse response = externalIntegrationUseCase.syncReviews(storeId, request);
return ResponseEntity.ok(response);
}
/**
* 외부 플랫폼 계정 연동 API
*/
@PostMapping("/stores/{storeId}/connect")
@Operation(summary = "외부 플랫폼 계정 연동", description = "외부 플랫폼 계정을 연동합니다.")
public ResponseEntity<ExternalConnectResponse> connectPlatform(
@PathVariable Long storeId,
@Valid @RequestBody ExternalConnectRequest request) {
ExternalConnectResponse response = externalIntegrationUseCase.connectPlatform(storeId, request);
return ResponseEntity.ok(response);
}
}
@@ -0,0 +1,20 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 외부 플랫폼 계정 연동 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExternalConnectResponse {
private Boolean success;
private String message;
}
@@ -0,0 +1,20 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 외부 플랫폼 동기화 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExternalSyncResponse {
private Boolean success;
private String message;
private Integer syncedCount;
}
@@ -0,0 +1,19 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 메뉴 삭제 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuDeleteResponse {
private Boolean success;
private String message;
}
@@ -0,0 +1,20 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 메뉴 저장 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuSaveResponse {
private Boolean success;
private String message;
private Long menuId;
}
@@ -0,0 +1,35 @@
package com.ktds.hi.store.infra.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 메뉴 수정 요청 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuUpdateRequest {
@NotBlank(message = "메뉴명은 필수입니다.")
@Size(max = 100, message = "메뉴명은 100자를 초과할 수 없습니다.")
private String menuName;
@Size(max = 500, message = "설명은 500자를 초과할 수 없습니다.")
private String description;
@NotNull(message = "가격은 필수입니다.")
@Min(value = 1, message = "가격은 0보다 큰 값이어야 합니다.")
private Integer price;
private String category;
private String imageUrl;
private Boolean isAvailable;
}
@@ -0,0 +1,19 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 메뉴 수정 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuUpdateResponse {
private Boolean success;
private String message;
}
@@ -0,0 +1,19 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 매장 삭제 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StoreDeleteResponse {
private Boolean success;
private String message;
}
@@ -0,0 +1,19 @@
package com.ktds.hi.store.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 매장 수정 응답 DTO
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StoreUpdateResponse {
private Boolean success;
private String message;
}
@@ -0,0 +1,58 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.usecase.out.CachePort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Optional;
/**
* 캐시 어댑터 클래스
* Cache Port를 구현하여 Redis 캐시 기능을 제공
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class CacheAdapter implements CachePort {
private final RedisTemplate<String, Object> redisTemplate;
@Override
public Optional<Object> getStoreCache(String key) {
try {
Object value = redisTemplate.opsForValue().get(key);
return Optional.ofNullable(value);
} catch (Exception e) {
log.error("매장 캐시 조회 실패: key={}, error={}", key, e.getMessage());
return Optional.empty();
}
}
@Override
public void putStoreCache(String key, Object value, Duration ttl) {
try {
redisTemplate.opsForValue().set(key, value, ttl);
log.debug("매장 캐시 저장 완료: key={}, ttl={}분", key, ttl.toMinutes());
} catch (Exception e) {
log.error("매장 캐시 저장 실패: key={}, error={}", key, e.getMessage());
}
}
@Override
public void invalidateStoreCache(Long storeId) {
try {
// 매장 관련 모든 캐시 키 패턴 삭제
String storeDetailKey = "store_detail:" + storeId;
String myStoresKey = "my_stores:*";
redisTemplate.delete(storeDetailKey);
log.debug("매장 캐시 무효화 완료: storeId={}", storeId);
} catch (Exception e) {
log.error("매장 캐시 무효화 실패: storeId={}, error={}", storeId, e.getMessage());
}
}
}
@@ -0,0 +1,104 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.domain.Store;
import com.ktds.hi.store.biz.usecase.out.EventPort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
/**
* 이벤트 어댑터 클래스
* Event Port를 구현하여 이벤트 발행 기능을 제공
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class EventAdapter implements EventPort {
private final ApplicationEventPublisher eventPublisher;
@Override
public void publishStoreCreatedEvent(Store store) {
log.info("매장 생성 이벤트 발행: storeId={}", store.getId());
try {
StoreCreatedEvent event = new StoreCreatedEvent(store);
eventPublisher.publishEvent(event);
log.info("매장 생성 이벤트 발행 완료: storeId={}", store.getId());
} catch (Exception e) {
log.error("매장 생성 이벤트 발행 실패: storeId={}, error={}", store.getId(), e.getMessage(), e);
}
}
@Override
public void publishStoreUpdatedEvent(Store store) {
log.info("매장 수정 이벤트 발행: storeId={}", store.getId());
try {
StoreUpdatedEvent event = new StoreUpdatedEvent(store);
eventPublisher.publishEvent(event);
log.info("매장 수정 이벤트 발행 완료: storeId={}", store.getId());
} catch (Exception e) {
log.error("매장 수정 이벤트 발행 실패: storeId={}, error={}", store.getId(), e.getMessage(), e);
}
}
@Override
public void publishStoreDeletedEvent(Long storeId) {
log.info("매장 삭제 이벤트 발행: storeId={}", storeId);
try {
StoreDeletedEvent event = new StoreDeletedEvent(storeId);
eventPublisher.publishEvent(event);
log.info("매장 삭제 이벤트 발행 완료: storeId={}", storeId);
} catch (Exception e) {
log.error("매장 삭제 이벤트 발행 실패: storeId={}, error={}", storeId, e.getMessage(), e);
}
}
/**
* 매장 이벤트 클래스들
*/
public static class StoreCreatedEvent {
private final Store store;
public StoreCreatedEvent(Store store) {
this.store = store;
}
public Store getStore() {
return store;
}
}
public static class StoreUpdatedEvent {
private final Store store;
public StoreUpdatedEvent(Store store) {
this.store = store;
}
public Store getStore() {
return store;
}
}
public static class StoreDeletedEvent {
private final Long storeId;
public StoreDeletedEvent(Long storeId) {
this.storeId = storeId;
}
public Long getStoreId() {
return storeId;
}
}
}
@@ -0,0 +1,226 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.usecase.out.ExternalPlatformPort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* 외부 플랫폼 어댑터 클래스
* External Platform Port를 구현하여 외부 API 연동 기능을 제공
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class ExternalPlatformAdapter implements ExternalPlatformPort {
private final RestTemplate restTemplate;
@Value("${external-api.naver.client-id:}")
private String naverClientId;
@Value("${external-api.naver.client-secret:}")
private String naverClientSecret;
@Value("${external-api.kakao.api-key:}")
private String kakaoApiKey;
@Value("${external-api.google.api-key:}")
private String googleApiKey;
@Value("${external-api.hiorder.api-key:}")
private String hiorderApiKey;
@Override
public int syncNaverReviews(Long storeId, String externalStoreId) {
log.info("네이버 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
try {
// 네이버 API 호출 (Mock)
HttpHeaders headers = new HttpHeaders();
headers.set("X-Naver-Client-Id", naverClientId);
headers.set("X-Naver-Client-Secret", naverClientSecret);
// 실제 API 호출 로직
// ResponseEntity<Map> response = restTemplate.exchange(...);
// Mock 응답
int syncedCount = 15; // Mock 데이터
log.info("네이버 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
return syncedCount;
} catch (Exception e) {
log.error("네이버 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return 0;
}
}
@Override
public int syncKakaoReviews(Long storeId, String externalStoreId) {
log.info("카카오 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
try {
// 카카오 API 호출 (Mock)
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoApiKey);
// Mock 응답
int syncedCount = 12;
log.info("카카오 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
return syncedCount;
} catch (Exception e) {
log.error("카카오 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return 0;
}
}
@Override
public int syncGoogleReviews(Long storeId, String externalStoreId) {
log.info("구글 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
try {
// 구글 Places API 호출 (Mock)
String url = "https://maps.googleapis.com/maps/api/place/details/json?place_id=" +
externalStoreId + "&fields=reviews&key=" + googleApiKey;
// Mock 응답
int syncedCount = 20;
log.info("구글 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
return syncedCount;
} catch (Exception e) {
log.error("구글 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return 0;
}
}
@Override
public int syncHiorderReviews(Long storeId, String externalStoreId) {
log.info("하이오더 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
try {
// 하이오더 API 호출 (Mock)
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + hiorderApiKey);
// Mock 응답
int syncedCount = 8;
log.info("하이오더 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
return syncedCount;
} catch (Exception e) {
log.error("하이오더 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return 0;
}
}
@Override
public boolean connectNaverAccount(Long storeId, String username, String password) {
log.info("네이버 계정 연동 시작: storeId={}, username={}", storeId, username);
try {
// 네이버 계정 인증 로직 (Mock)
// 실제로는 OAuth2 플로우나 ID/PW 인증
// Mock 성공
boolean connected = true;
if (connected) {
// 연동 정보 저장
saveExternalConnection(storeId, "NAVER", username);
}
log.info("네이버 계정 연동 완료: storeId={}, connected={}", storeId, connected);
return connected;
} catch (Exception e) {
log.error("네이버 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return false;
}
}
@Override
public boolean connectKakaoAccount(Long storeId, String username, String password) {
log.info("카카오 계정 연동 시작: storeId={}, username={}", storeId, username);
try {
// 카카오 계정 인증 로직 (Mock)
boolean connected = true;
if (connected) {
saveExternalConnection(storeId, "KAKAO", username);
}
log.info("카카오 계정 연동 완료: storeId={}, connected={}", storeId, connected);
return connected;
} catch (Exception e) {
log.error("카카오 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return false;
}
}
@Override
public boolean connectGoogleAccount(Long storeId, String username, String password) {
log.info("구글 계정 연동 시작: storeId={}, username={}", storeId, username);
try {
// 구글 계정 인증 로직 (Mock)
boolean connected = true;
if (connected) {
saveExternalConnection(storeId, "GOOGLE", username);
}
log.info("구글 계정 연동 완료: storeId={}, connected={}", storeId, connected);
return connected;
} catch (Exception e) {
log.error("구글 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return false;
}
}
@Override
public boolean connectHiorderAccount(Long storeId, String username, String password) {
log.info("하이오더 계정 연동 시작: storeId={}, username={}", storeId, username);
try {
// 하이오더 계정 인증 로직 (Mock)
boolean connected = true;
if (connected) {
saveExternalConnection(storeId, "HIORDER", username);
}
log.info("하이오더 계정 연동 완료: storeId={}, connected={}", storeId, connected);
return connected;
} catch (Exception e) {
log.error("하이오더 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
return false;
}
}
/**
* 외부 연동 정보 저장
*/
private void saveExternalConnection(Long storeId, String platform, String username) {
// 실제로는 ExternalPlatformEntity에 연동 정보 저장
log.info("외부 연동 정보 저장: storeId={}, platform={}, username={}", storeId, platform, username);
}
}
@@ -0,0 +1,85 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.domain.Menu;
import com.ktds.hi.store.biz.usecase.out.MenuRepositoryPort;
import com.ktds.hi.store.infra.gateway.entity.MenuEntity;
import com.ktds.hi.store.infra.gateway.repository.MenuJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 메뉴 리포지토리 어댑터 클래스
* Menu Repository Port를 구현하여 데이터 영속성 기능을 제공
*/
@Component
@RequiredArgsConstructor
public class MenuRepositoryAdapter implements MenuRepositoryPort {
private final MenuJpaRepository menuJpaRepository;
@Override
public List<Menu> findMenusByStoreId(Long storeId) {
return menuJpaRepository.findByStoreId(storeId)
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public Optional<Menu> findMenuById(Long menuId) {
return menuJpaRepository.findById(menuId)
.map(this::toDomain);
}
@Override
public Menu saveMenu(Menu menu) {
MenuEntity entity = toEntity(menu);
MenuEntity saved = menuJpaRepository.save(entity);
return toDomain(saved);
}
@Override
public void deleteMenu(Long menuId) {
menuJpaRepository.deleteById(menuId);
}
/**
* Entity를 Domain으로 변환
*/
private Menu toDomain(MenuEntity entity) {
return Menu.builder()
.id(entity.getId())
.storeId(entity.getStoreId())
.menuName(entity.getMenuName())
.description(entity.getDescription())
.price(entity.getPrice())
.category(entity.getCategory())
.imageUrl(entity.getImageUrl())
.isAvailable(entity.getIsAvailable())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
}
/**
* Domain을 Entity로 변환
*/
private MenuEntity toEntity(Menu domain) {
return MenuEntity.builder()
.id(domain.getId())
.storeId(domain.getStoreId())
.menuName(domain.getMenuName())
.description(domain.getDescription())
.price(domain.getPrice())
.category(domain.getCategory())
.imageUrl(domain.getImageUrl())
.isAvailable(domain.getIsAvailable())
.createdAt(domain.getCreatedAt())
.updatedAt(domain.getUpdatedAt())
.build();
}
}
@@ -0,0 +1,115 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.domain.Store;
import com.ktds.hi.store.biz.usecase.out.StoreRepositoryPort;
import com.ktds.hi.store.infra.gateway.entity.StoreEntity;
import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 매장 리포지토리 어댑터 클래스
* Store Repository Port를 구현하여 데이터 영속성 기능을 제공
*/
@Component
@RequiredArgsConstructor
public class StoreRepositoryAdapter implements StoreRepositoryPort {
private final StoreJpaRepository storeJpaRepository;
@Override
public List<Store> findStoresByOwnerId(Long ownerId) {
return storeJpaRepository.findByOwnerId(ownerId)
.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public Optional<Store> findStoreById(Long storeId) {
return storeJpaRepository.findById(storeId)
.map(this::toDomain);
}
@Override
public Optional<Store> findStoreByIdAndOwnerId(Long storeId, Long ownerId) {
return storeJpaRepository.findByIdAndOwnerId(storeId, ownerId)
.map(this::toDomain);
}
@Override
public Store saveStore(Store store) {
StoreEntity entity = toEntity(store);
StoreEntity saved = storeJpaRepository.save(entity);
return toDomain(saved);
}
@Override
public void deleteStore(Long storeId) {
storeJpaRepository.deleteById(storeId);
}
/**
* Entity를 Domain으로 변환
*/
private Store toDomain(StoreEntity entity) {
return Store.builder()
.id(entity.getId())
.ownerId(entity.getOwnerId())
.storeName(entity.getStoreName())
.address(entity.getAddress())
.latitude(entity.getLatitude())
.longitude(entity.getLongitude())
.category(entity.getCategory())
.description(entity.getDescription())
.phone(entity.getPhone())
.operatingHours(entity.getOperatingHours())
.tags(entity.getTagsJson() != null ? parseTagsJson(entity.getTagsJson()) : List.of())
.status(entity.getStatus())
.rating(entity.getRating())
.reviewCount(entity.getReviewCount())
.imageUrl(entity.getImageUrl())
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
}
/**
* Domain을 Entity로 변환
*/
private StoreEntity toEntity(Store domain) {
return StoreEntity.builder()
.id(domain.getId())
.ownerId(domain.getOwnerId())
.storeName(domain.getStoreName())
.address(domain.getAddress())
.latitude(domain.getLatitude())
.longitude(domain.getLongitude())
.category(domain.getCategory())
.description(domain.getDescription())
.phone(domain.getPhone())
.operatingHours(domain.getOperatingHours())
.tagsJson(domain.getTags() != null ? String.join(",", domain.getTags()) : "")
.status(domain.getStatus())
.rating(domain.getRating())
.reviewCount(domain.getReviewCount())
.imageUrl(domain.getImageUrl())
.createdAt(domain.getCreatedAt())
.updatedAt(domain.getUpdatedAt())
.build();
}
/**
* JSON 태그를 List로 파싱
*/
private List<String> parseTagsJson(String tagsJson) {
if (tagsJson == null || tagsJson.trim().isEmpty()) {
return List.of();
}
return Arrays.asList(tagsJson.split(","));
}
}
@@ -0,0 +1,13 @@
package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.domain.Store;
import com.ktds.hi.store.biz.usecase.out.StoreRepositoryPort;
import com.ktds.hi.store.infra.gateway.entity.StoreEntity;
import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Arrays; // 추가
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -0,0 +1,60 @@
package com.ktds.hi.store.infra.gateway.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
/**
* 메뉴 엔티티 클래스
* 데이터베이스 menus 테이블과 매핑되는 JPA 엔티티
*/
@Entity
@Table(name = "menus")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class MenuEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "store_id", nullable = false)
private Long storeId;
@Column(name = "menu_name", nullable = false, length = 100)
private String menuName;
@Column(length = 500)
private String description;
@Column(nullable = false)
private Integer price;
@Column(length = 50)
private String category;
@Column(name = "image_url", length = 500)
private String imageUrl;
@Column(name = "is_available")
@Builder.Default
private Boolean isAvailable = true;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
@@ -0,0 +1,35 @@
package com.ktds.hi.store.infra.gateway.repository;
import com.ktds.hi.store.infra.gateway.entity.MenuEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 메뉴 JPA 리포지토리 인터페이스
* 메뉴 데이터의 CRUD 작업을 담당
*/
@Repository
public interface MenuJpaRepository extends JpaRepository<MenuEntity, Long> {
/**
* 매장 ID로 이용 가능한 메뉴 목록 조회
*/
List<MenuEntity> findByStoreIdAndIsAvailableTrue(Long storeId);
/**
* 매장 ID로 모든 메뉴 목록 조회
*/
List<MenuEntity> findByStoreId(Long storeId);
/**
* 매장 ID로 메뉴 삭제
*/
void deleteByStoreId(Long storeId);
/**
* 카테고리별 메뉴 조회
*/
List<MenuEntity> findByStoreIdAndCategoryAndIsAvailableTrue(Long storeId, String category);
}
+47
View File
@@ -0,0 +1,47 @@
server:
port: ${STORE_SERVICE_PORT:8082}
spring:
application:
name: store-service
datasource:
url: ${STORE_DB_URL:jdbc:postgresql://localhost:5432/hiorder_store}
username: ${STORE_DB_USERNAME:hiorder_user}
password: ${STORE_DB_PASSWORD:hiorder_pass}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: ${JPA_DDL_AUTO:validate}
show-sql: ${JPA_SHOW_SQL:false}
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
external-api:
naver:
client-id: ${NAVER_CLIENT_ID:}
client-secret: ${NAVER_CLIENT_SECRET:}
base-url: https://openapi.naver.com
kakao:
api-key: ${KAKAO_API_KEY:}
base-url: https://dapi.kakao.com
google:
api-key: ${GOOGLE_API_KEY:}
base-url: https://maps.googleapis.com
hiorder:
api-key: ${HIORDER_API_KEY:}
base-url: ${HIORDER_BASE_URL:https://api.hiorder.com}
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html