store register add

This commit is contained in:
youbeen
2025-06-13 16:40:46 +09:00
parent b25c2edcc0
commit 17a68d3cdb
38 changed files with 2906 additions and 2327 deletions
@@ -1,143 +1,143 @@
package com.ktds.hi.review.biz.service;
import com.ktds.hi.review.biz.usecase.in.CreateReviewUseCase;
import com.ktds.hi.review.biz.usecase.in.DeleteReviewUseCase;
import com.ktds.hi.review.biz.usecase.in.GetReviewUseCase;
import com.ktds.hi.review.biz.usecase.out.ReviewRepository;
import com.ktds.hi.review.biz.domain.Review;
import com.ktds.hi.review.biz.domain.ReviewStatus;
import com.ktds.hi.review.infra.dto.request.ReviewCreateRequest;
import com.ktds.hi.review.infra.dto.response.*;
import com.ktds.hi.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 리뷰 인터랙터 클래스
* 리뷰 생성, 조회, 삭제 기능을 구현
*/
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class ReviewInteractor implements CreateReviewUseCase, DeleteReviewUseCase, GetReviewUseCase {
private final ReviewRepository reviewRepository;
@Override
public ReviewCreateResponse createReview(Long memberId, ReviewCreateRequest request) {
// 리뷰 생성
Review review = Review.builder()
.storeId(request.getStoreId())
.memberId(memberId)
.memberNickname("회원" + memberId) // TODO: 회원 서비스에서 닉네임 조회
.rating(request.getRating())
.content(request.getContent())
.imageUrls(request.getImageUrls())
.status(ReviewStatus.ACTIVE)
.likeCount(0)
.dislikeCount(0)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
Review savedReview = reviewRepository.saveReview(review);
log.info("리뷰 생성 완료: reviewId={}, storeId={}, memberId={}",
savedReview.getId(), savedReview.getStoreId(), savedReview.getMemberId());
return ReviewCreateResponse.builder()
.reviewId(savedReview.getId())
.message("리뷰가 성공적으로 등록되었습니다")
.build();
}
@Override
public ReviewDeleteResponse deleteReview(Long reviewId, Long memberId) {
Review review = reviewRepository.findReviewByIdAndMemberId(reviewId, memberId)
.orElseThrow(() -> new BusinessException("리뷰를 찾을 수 없거나 권한이 없습니다"));
reviewRepository.deleteReview(reviewId);
log.info("리뷰 삭제 완료: reviewId={}, memberId={}", reviewId, memberId);
return ReviewDeleteResponse.builder()
.success(true)
.message("리뷰가 삭제되었습니다")
.build();
}
@Override
@Transactional(readOnly = true)
public List<ReviewListResponse> getStoreReviews(Long storeId, Integer page, Integer size) {
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 20);
Page<Review> reviews = reviewRepository.findReviewsByStoreId(storeId, pageable);
return reviews.stream()
.filter(review -> review.getStatus() == ReviewStatus.ACTIVE)
.map(review -> ReviewListResponse.builder()
.reviewId(review.getId())
.memberNickname(review.getMemberNickname())
.rating(review.getRating())
.content(review.getContent())
.imageUrls(review.getImageUrls())
.likeCount(review.getLikeCount())
.dislikeCount(review.getDislikeCount())
.createdAt(review.getCreatedAt())
.build())
.collect(Collectors.toList());
}
@Override
@Transactional(readOnly = true)
public ReviewDetailResponse getReviewDetail(Long reviewId) {
Review review = reviewRepository.findReviewById(reviewId)
.orElseThrow(() -> new BusinessException("존재하지 않는 리뷰입니다"));
if (review.getStatus() != ReviewStatus.ACTIVE) {
throw new BusinessException("삭제되었거나 숨겨진 리뷰입니다");
}
return ReviewDetailResponse.builder()
.reviewId(review.getId())
.storeId(review.getStoreId())
.memberNickname(review.getMemberNickname())
.rating(review.getRating())
.content(review.getContent())
.imageUrls(review.getImageUrls())
.likeCount(review.getLikeCount())
.dislikeCount(review.getDislikeCount())
.createdAt(review.getCreatedAt())
.build();
}
@Override
@Transactional(readOnly = true)
public List<ReviewListResponse> getMyReviews(Long memberId, Integer page, Integer size) {
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 20);
Page<Review> reviews = reviewRepository.findReviewsByMemberId(memberId, pageable);
return reviews.stream()
.filter(review -> review.getStatus() == ReviewStatus.ACTIVE)
.map(review -> ReviewListResponse.builder()
.reviewId(review.getId())
.memberNickname(review.getMemberNickname())
.rating(review.getRating())
.content(review.getContent())
.imageUrls(review.getImageUrls())
.likeCount(review.getLikeCount())
.dislikeCount(review.getDislikeCount())
.createdAt(review.getCreatedAt())
.build())
.collect(Collectors.toList());
}
}
package com.ktds.hi.review.biz.service;
import com.ktds.hi.review.biz.usecase.in.CreateReviewUseCase;
import com.ktds.hi.review.biz.usecase.in.DeleteReviewUseCase;
import com.ktds.hi.review.biz.usecase.in.GetReviewUseCase;
import com.ktds.hi.review.biz.usecase.out.ReviewRepository;
import com.ktds.hi.review.biz.domain.Review;
import com.ktds.hi.review.biz.domain.ReviewStatus;
import com.ktds.hi.review.infra.dto.request.ReviewCreateRequest;
import com.ktds.hi.review.infra.dto.response.*;
import com.ktds.hi.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 리뷰 인터랙터 클래스
* 리뷰 생성, 조회, 삭제 기능을 구현
*/
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class ReviewInteractor implements CreateReviewUseCase, DeleteReviewUseCase, GetReviewUseCase {
private final ReviewRepository reviewRepository;
@Override
public ReviewCreateResponse createReview(Long memberId, ReviewCreateRequest request) {
// 리뷰 생성
Review review = Review.builder()
.storeId(request.getStoreId())
.memberId(memberId)
.memberNickname("회원" + memberId) // TODO: 회원 서비스에서 닉네임 조회
.rating(request.getRating())
.content(request.getContent())
.imageUrls(request.getImageUrls())
.status(ReviewStatus.ACTIVE)
.likeCount(0)
.dislikeCount(0)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
Review savedReview = reviewRepository.saveReview(review);
log.info("리뷰 생성 완료: reviewId={}, storeId={}, memberId={}",
savedReview.getId(), savedReview.getStoreId(), savedReview.getMemberId());
return ReviewCreateResponse.builder()
.reviewId(savedReview.getId())
.message("리뷰가 성공적으로 등록되었습니다")
.build();
}
@Override
public ReviewDeleteResponse deleteReview(Long reviewId, Long memberId) {
Review review = reviewRepository.findReviewByIdAndMemberId(reviewId, memberId)
.orElseThrow(() -> new BusinessException("리뷰를 찾을 수 없거나 권한이 없습니다"));
reviewRepository.deleteReview(reviewId);
log.info("리뷰 삭제 완료: reviewId={}, memberId={}", reviewId, memberId);
return ReviewDeleteResponse.builder()
.success(true)
.message("리뷰가 삭제되었습니다")
.build();
}
@Override
@Transactional(readOnly = true)
public List<ReviewListResponse> getStoreReviews(Long storeId, Integer page, Integer size) {
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 20);
Page<Review> reviews = reviewRepository.findReviewsByStoreId(storeId, pageable);
return reviews.stream()
.filter(review -> review.getStatus() == ReviewStatus.ACTIVE)
.map(review -> ReviewListResponse.builder()
.reviewId(review.getId())
.memberNickname(review.getMemberNickname())
.rating(review.getRating())
.content(review.getContent())
.imageUrls(review.getImageUrls())
.likeCount(review.getLikeCount())
.dislikeCount(review.getDislikeCount())
.createdAt(review.getCreatedAt())
.build())
.collect(Collectors.toList());
}
@Override
@Transactional(readOnly = true)
public ReviewDetailResponse getReviewDetail(Long reviewId) {
Review review = reviewRepository.findReviewById(reviewId)
.orElseThrow(() -> new BusinessException("존재하지 않는 리뷰입니다"));
if (review.getStatus() != ReviewStatus.ACTIVE) {
throw new BusinessException("삭제되었거나 숨겨진 리뷰입니다");
}
return ReviewDetailResponse.builder()
.reviewId(review.getId())
.storeId(review.getStoreId())
.memberNickname(review.getMemberNickname())
.rating(review.getRating())
.content(review.getContent())
.imageUrls(review.getImageUrls())
.likeCount(review.getLikeCount())
.dislikeCount(review.getDislikeCount())
.createdAt(review.getCreatedAt())
.build();
}
@Override
@Transactional(readOnly = true)
public List<ReviewListResponse> getMyReviews(Long memberId, Integer page, Integer size) {
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 20);
Page<Review> reviews = reviewRepository.findReviewsByMemberId(memberId, pageable);
return reviews.stream()
.filter(review -> review.getStatus() == ReviewStatus.ACTIVE)
.map(review -> ReviewListResponse.builder()
.reviewId(review.getId())
.memberNickname(review.getMemberNickname())
.rating(review.getRating())
.content(review.getContent())
.imageUrls(review.getImageUrls())
.likeCount(review.getLikeCount())
.dislikeCount(review.getDislikeCount())
.createdAt(review.getCreatedAt())
.build())
.collect(Collectors.toList());
}
}
@@ -1,50 +1,50 @@
package com.ktds.hi.review.infra.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfigurationSource;
import lombok.RequiredArgsConstructor;
/**
* Analytics 서비스 보안 설정 클래스
* 테스트를 위해 모든 엔드포인트를 인증 없이 접근 가능하도록 설정
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final CorsConfigurationSource corsConfigurationSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
// Swagger 관련 경로 모두 허용
.requestMatchers("/swagger-ui.html","/swagger-ui/**", "/swagger-ui.html").permitAll()
.requestMatchers("/api-docs/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
// Analytics API 모두 허용 (테스트용)
.requestMatchers("/api/analytics/**").permitAll()
.requestMatchers("/api/action-plans/**").permitAll()
// Actuator 엔드포인트 허용
.requestMatchers("/actuator/**").permitAll()
// 기타 모든 요청 허용 (테스트용)
.anyRequest().permitAll()
);
return http.build();
}
}
package com.ktds.hi.review.infra.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfigurationSource;
import lombok.RequiredArgsConstructor;
/**
* Analytics 서비스 보안 설정 클래스
* 테스트를 위해 모든 엔드포인트를 인증 없이 접근 가능하도록 설정
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final CorsConfigurationSource corsConfigurationSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
// Swagger 관련 경로 모두 허용
.requestMatchers("/swagger-ui.html","/swagger-ui/**", "/swagger-ui.html").permitAll()
.requestMatchers("/api-docs/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/swagger-resources/**", "/webjars/**").permitAll()
// Analytics API 모두 허용 (테스트용)
.requestMatchers("/api/analytics/**").permitAll()
.requestMatchers("/api/action-plans/**").permitAll()
// Actuator 엔드포인트 허용
.requestMatchers("/actuator/**").permitAll()
// 기타 모든 요청 허용 (테스트용)
.anyRequest().permitAll()
);
return http.build();
}
}
@@ -1,34 +1,34 @@
package com.ktds.hi.review.infra.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
final String securitySchemeName = "Bearer Authentication";
return new OpenAPI()
.addServersItem(new Server().url("/"))
.info(new Info()
.title("하이오더 리뷰 관리 서비스 API")
.description("리뷰 작성, 조회, 삭제, 반응, 댓글 등 리뷰 관련 기능을 제공하는 API")
.version("1.0.0"))
.addSecurityItem(new SecurityRequirement()
.addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
package com.ktds.hi.review.infra.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
final String securitySchemeName = "Bearer Authentication";
return new OpenAPI()
.addServersItem(new Server().url("/"))
.info(new Info()
.title("하이오더 리뷰 관리 서비스 API")
.description("리뷰 작성, 조회, 삭제, 반응, 댓글 등 리뷰 관련 기능을 제공하는 API")
.version("1.0.0"))
.addSecurityItem(new SecurityRequirement()
.addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
+42 -42
View File
@@ -1,42 +1,42 @@
server:
port: ${REVIEW_SERVICE_PORT:8083}
spring:
application:
name: review-service
datasource:
url: ${REVIEW_DB_URL:jdbc:postgresql://20.214.91.15:5432/hiorder_review}
username: ${REVIEW_DB_USERNAME:hiorder_user}
password: ${REVIEW_DB_PASSWORD:hiorder_pass}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: ${JPA_DDL_AUTO:update}
show-sql: ${JPA_SHOW_SQL:false}
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
servlet:
multipart:
max-file-size: ${MAX_FILE_SIZE:10MB}
max-request-size: ${MAX_REQUEST_SIZE:50MB}
file-storage:
base-path: ${FILE_STORAGE_PATH:/var/hiorder/uploads}
allowed-extensions: jpg,jpeg,png,gif,webp
max-file-size: 10485760 # 10MB
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
server:
port: ${REVIEW_SERVICE_PORT:8083}
spring:
application:
name: review-service
datasource:
url: ${REVIEW_DB_URL:jdbc:postgresql://20.214.91.15:5432/hiorder_review}
username: ${REVIEW_DB_USERNAME:hiorder_user}
password: ${REVIEW_DB_PASSWORD:hiorder_pass}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: ${JPA_DDL_AUTO:update}
show-sql: ${JPA_SHOW_SQL:false}
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
servlet:
multipart:
max-file-size: ${MAX_FILE_SIZE:10MB}
max-request-size: ${MAX_REQUEST_SIZE:50MB}
file-storage:
base-path: ${FILE_STORAGE_PATH:/var/hiorder/uploads}
allowed-extensions: jpg,jpeg,png,gif,webp
max-file-size: 10485760 # 10MB
springdoc:
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html