mirror of
https://github.com/cna-bootcamp/lifesub.git
synced 2025-12-06 08:06:24 +00:00
release
This commit is contained in:
parent
2b100e1bcf
commit
11a49c33a8
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -51,7 +51,7 @@ configure(subprojects.findAll { !it.name.endsWith('-biz') && it.name != 'common'
|
||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
|
||||
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
|
||||
// Swagger
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -16,7 +16,9 @@ import java.util.Map;
|
||||
public class LoggingAspect {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Pointcut("execution(* com.unicorn..*.*(..))")
|
||||
//로깅 대상 패키지 지정. swagger관련 패키지는 제외함
|
||||
@Pointcut("execution(* com.unicorn..*.*(..)) && " +
|
||||
"!execution(* org.springdoc..*.*(..))")
|
||||
private void loggingPointcut() {}
|
||||
|
||||
@Before("loggingPointcut()")
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package com.unicorn.lifesub.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaConfig {
|
||||
}
|
||||
@ -7,23 +7,24 @@ import lombok.RequiredArgsConstructor;
|
||||
@RequiredArgsConstructor
|
||||
public enum ErrorCode {
|
||||
// Common
|
||||
INVALID_INPUT_VALUE(400, "Invalid input value"),
|
||||
INTERNAL_SERVER_ERROR(500, "Internal server error"),
|
||||
INVALID_INPUT_VALUE(100, "Invalid input value"),
|
||||
INTERNAL_SERVER_ERROR(110, "Internal server error"),
|
||||
|
||||
// Member
|
||||
MEMBER_NOT_FOUND(404, "Member not found"),
|
||||
INVALID_CREDENTIALS(401, "Invalid credentials"),
|
||||
TOKEN_EXPIRED(401, "Token expired"),
|
||||
SIGNATURE_VERIFICATION_EXCEPTION(20, "서명 검증 실패"),
|
||||
ALGORITHM_MISMATCH_EXCEPTION(30, "알고리즘 불일치"),
|
||||
INVALID_CLAIM_EXCEPTION(40, "유효하지 않은 클레임"),
|
||||
MEMBER_NOT_FOUND(200, "Member not found"),
|
||||
INVALID_CREDENTIALS(210, "Invalid credentials"),
|
||||
TOKEN_EXPIRED(220, "Token expired"),
|
||||
SIGNATURE_VERIFICATION_EXCEPTION(230, "서명 검증 실패"),
|
||||
ALGORITHM_MISMATCH_EXCEPTION(240, "알고리즘 불일치"),
|
||||
INVALID_CLAIM_EXCEPTION(250, "유효하지 않은 클레임"),
|
||||
|
||||
// Subscription
|
||||
SUBSCRIPTION_NOT_FOUND(404, "Subscription not found"),
|
||||
ALREADY_SUBSCRIBED(400, "Already subscribed to this service"),
|
||||
SUBSCRIPTION_NOT_FOUND(300, "Subscription not found"),
|
||||
ALREADY_SUBSCRIBED(310, "Already subscribed to this service"),
|
||||
|
||||
// Recommend
|
||||
NO_SPENDING_DATA(404, "No spending data found"),
|
||||
NO_SPENDING_DATA(400, "No spending data found"),
|
||||
NO_RECOMMENDATION_DATA(410, "추천 구독 카테고리 없음"),
|
||||
|
||||
// UnDefined
|
||||
UNDIFINED_ERROR(0, "정의되지 않은 에러");
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -25,13 +25,11 @@ jwt:
|
||||
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600000}
|
||||
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400000}
|
||||
|
||||
allowedorigins: ${ALLOWED_ORIGINS:*}
|
||||
allowed-origins: ${ALLOWED_ORIGINS:*}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
Binary file not shown.
@ -2,8 +2,14 @@ package com.unicorn.lifesub.member;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = {
|
||||
"com.unicorn.lifesub.member",
|
||||
"com.unicorn.lifesub.common"
|
||||
}
|
||||
)
|
||||
public class MemberApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MemberApplication.class, args);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// File: lifesub/member/src/main/java/com/unicorn/lifesub/member/config/InitialDataLoader.java
|
||||
// File: lifesub/member/src/main/java/com/unicorn/lifesub/member/config/DataLoader.java
|
||||
package com.unicorn.lifesub.member.config;
|
||||
|
||||
import com.unicorn.lifesub.member.repository.entity.MemberEntity;
|
||||
@ -15,7 +15,7 @@ import java.util.stream.IntStream;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InitialDataLoader implements CommandLineRunner {
|
||||
public class DataLoader implements CommandLineRunner {
|
||||
private final MemberRepository memberRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@ -26,7 +26,7 @@ public class InitialDataLoader implements CommandLineRunner {
|
||||
if (memberRepository.count() == 0) {
|
||||
Set<String> userRoles = new HashSet<>();
|
||||
userRoles.add("USER");
|
||||
String encodedPassword = passwordEncoder.encode("P@ssw0rd$");
|
||||
String encodedPassword = passwordEncoder.encode("Passw0rd");
|
||||
|
||||
IntStream.rangeClosed(1, 10).forEach(i -> {
|
||||
String userId = String.format("user%02d", i);
|
||||
@ -31,7 +31,7 @@ public class SecurityConfig {
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final CustomUserDetailsService customUserDetailsService;
|
||||
|
||||
@Value("${allowedorigins}")
|
||||
@Value("${allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
public SecurityConfig(JwtTokenProvider jwtTokenProvider, CustomUserDetailsService customUserDetailsService) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.unicorn.lifesub.member.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
|
||||
@ -25,13 +25,11 @@ jwt:
|
||||
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:3600000}
|
||||
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400000}
|
||||
|
||||
allowedorigins: ${ALLOWED_ORIGINS:*}
|
||||
allowed-origins: ${ALLOWED_ORIGINS:*}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,15 @@
|
||||
package com.unicorn.lifesub.mysub.biz.service;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum FeeLevel {
|
||||
|
||||
LIKFER("liker"),
|
||||
COLLECTOR("collector"),
|
||||
ADDICT("addict");
|
||||
|
||||
private final String feeLevel;
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
package com.unicorn.lifesub.mysub.biz.service;
|
||||
|
||||
import com.unicorn.lifesub.mysub.biz.domain.Category;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.MySubscription;
|
||||
import com.unicorn.lifesub.mysub.biz.dto.MySubResponse;
|
||||
import com.unicorn.lifesub.mysub.biz.dto.TotalFeeResponse;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.MySubscriptionsUseCase;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.TotalFeeUseCase;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.out.MySubscriptionReader;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.Subscription;
|
||||
import com.unicorn.lifesub.mysub.biz.dto.*;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.*;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.out.*;
|
||||
import com.unicorn.lifesub.common.exception.BusinessException;
|
||||
import com.unicorn.lifesub.common.exception.ErrorCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -15,8 +17,17 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MySubscriptionService implements TotalFeeUseCase, MySubscriptionsUseCase {
|
||||
public class MySubscriptionService implements
|
||||
TotalFeeUseCase,
|
||||
MySubscriptionsUseCase,
|
||||
SubscriptionDetailUseCase,
|
||||
SubscribeUseCase,
|
||||
CancelSubscriptionUseCase,
|
||||
CategoryUseCase {
|
||||
|
||||
private final MySubscriptionReader mySubscriptionReader;
|
||||
private final MySubscriptionWriter mySubscriptionWriter;
|
||||
private final SubscriptionReader subscriptionReader;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
@ -43,9 +54,66 @@ public class MySubscriptionService implements TotalFeeUseCase, MySubscriptionsUs
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public SubDetailResponse getSubscriptionDetail(Long subscriptionId) {
|
||||
Subscription subscription = subscriptionReader.findById(subscriptionId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.SUBSCRIPTION_NOT_FOUND));
|
||||
|
||||
return SubDetailResponse.builder()
|
||||
.serviceName(subscription.getName())
|
||||
.logoUrl(subscription.getLogoUrl())
|
||||
.category(subscription.getCategory())
|
||||
.description(subscription.getDescription())
|
||||
.price(subscription.getPrice())
|
||||
.maxSharedUsers(subscription.getMaxSharedUsers())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void subscribe(Long subscriptionId, String userId) {
|
||||
// 구독 서비스 존재 확인
|
||||
subscriptionReader.findById(subscriptionId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.SUBSCRIPTION_NOT_FOUND));
|
||||
|
||||
mySubscriptionWriter.save(userId, subscriptionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void cancel(Long subscriptionId) {
|
||||
mySubscriptionWriter.delete(subscriptionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<CategoryResponse> getAllCategories() {
|
||||
return subscriptionReader.findAllCategories().stream()
|
||||
.map(category -> CategoryResponse.builder()
|
||||
.categoryId(category.getCategoryId())
|
||||
.categoryName(category.getName())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<ServiceListResponse> getServicesByCategory(String categoryId) {
|
||||
return subscriptionReader.findByCategory(categoryId).stream()
|
||||
.map(subscription -> ServiceListResponse.builder()
|
||||
.serviceId(subscription.getId().toString())
|
||||
.serviceName(subscription.getName())
|
||||
.description(subscription.getDescription())
|
||||
.price(subscription.getPrice())
|
||||
.logoUrl(subscription.getLogoUrl())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String calculateFeeLevel(long totalFee) {
|
||||
if (totalFee < 100000) return "구독을 좋아하는 사람";
|
||||
if (totalFee < 200000) return "구독 수집자";
|
||||
return "구독 사치왕";
|
||||
if (totalFee < 100000) return FeeLevel.LIKFER.getFeeLevel();
|
||||
if (totalFee < 200000) return FeeLevel.COLLECTOR.getFeeLevel();
|
||||
return FeeLevel.ADDICT.getFeeLevel();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
mysub-infra/build/resolvedMainClassName
Normal file
1
mysub-infra/build/resolvedMainClassName
Normal file
@ -0,0 +1 @@
|
||||
com.unicorn.lifesub.mysub.infra.MySubApplication
|
||||
@ -5,22 +5,30 @@ spring:
|
||||
application:
|
||||
name: mysub-service
|
||||
datasource:
|
||||
url: ${POSTGRES_URL}
|
||||
username: ${POSTGRES_USER}
|
||||
password: ${POSTGRES_PASSWORD}
|
||||
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:mysub}
|
||||
username: ${POSTGRES_USER:admin}
|
||||
password: ${POSTGRES_PASSWORD:Passw0rd}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
# JPA 설정
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:validate}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||
show-sql: ${JPA_SHOW_SQL:true}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
allowedorigins: ${ALLOWED_ORIGINS:*}
|
||||
jwt:
|
||||
secret-key: ${JWT_SECRET_KEY:8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ}
|
||||
|
||||
allowed-origins: ${ALLOWED_ORIGINS:*}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.unicorn: DEBUG
|
||||
org.hibernate.SQL: TRACE
|
||||
|
||||
Binary file not shown.
@ -2,8 +2,14 @@ package com.unicorn.lifesub.mysub.infra;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = {
|
||||
"com.unicorn.lifesub.mysub",
|
||||
"com.unicorn.lifesub.common"
|
||||
}
|
||||
)
|
||||
public class MySubApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MySubApplication.class, args);
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
// File: lifesub/mysub-infra/src/main/java/com/unicorn/lifesub/mysub/infra/config/DataLoader.java
|
||||
package com.unicorn.lifesub.mysub.infra.config;
|
||||
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.CategoryEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.SubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.repository.CategoryJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.repository.SubscriptionJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataLoader implements CommandLineRunner {
|
||||
|
||||
private final CategoryJpaRepository categoryRepository;
|
||||
private final SubscriptionJpaRepository subscriptionRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void run(String... args) {
|
||||
if (categoryRepository.count() == 0) {
|
||||
loadCategories();
|
||||
}
|
||||
|
||||
if (subscriptionRepository.count() == 0) {
|
||||
loadSubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCategories() {
|
||||
log.info("Loading sample categories...");
|
||||
|
||||
List<CategoryEntity> categories = Arrays.asList(
|
||||
CategoryEntity.builder()
|
||||
.categoryId("OTT")
|
||||
.name("OTT/동영상")
|
||||
.build(),
|
||||
CategoryEntity.builder()
|
||||
.categoryId("MUSIC")
|
||||
.name("음악")
|
||||
.build(),
|
||||
CategoryEntity.builder()
|
||||
.categoryId("FOOD")
|
||||
.name("식품")
|
||||
.build(),
|
||||
CategoryEntity.builder()
|
||||
.categoryId("LIFE")
|
||||
.name("생활")
|
||||
.build(),
|
||||
CategoryEntity.builder()
|
||||
.categoryId("BEAUTY")
|
||||
.name("뷰티")
|
||||
.build(),
|
||||
CategoryEntity.builder()
|
||||
.categoryId("EDU")
|
||||
.name("교육")
|
||||
.build()
|
||||
);
|
||||
|
||||
categoryRepository.saveAll(categories);
|
||||
log.info("Sample categories loaded");
|
||||
}
|
||||
|
||||
private void loadSubscriptions() {
|
||||
log.info("Loading sample subscriptions...");
|
||||
|
||||
// 카테고리별 서비스 매핑
|
||||
Map<String, List<SubscriptionEntity>> subscriptionsByCategory = Map.of(
|
||||
"OTT", Arrays.asList(
|
||||
createSubscription("넷플릭스", "전세계 최대 OTT 서비스", "OTT", 17000, 4, "/images/netflix.png"),
|
||||
createSubscription("티빙", "국내 실시간 방송과 예능/드라마 VOD", "OTT", 13900, 4, "/images/tving.png"),
|
||||
createSubscription("디즈니플러스", "디즈니, 픽사, 마블, 스타워즈 콘텐츠", "OTT", 9900, 4, "/images/disney.png")
|
||||
),
|
||||
"MUSIC", Arrays.asList(
|
||||
createSubscription("멜론", "국내 최대 음원 스트리밍", "MUSIC", 10900, 1, "/images/melon.png"),
|
||||
createSubscription("스포티파이", "전세계 음악 스트리밍", "MUSIC", 10900, 6, "/images/spotify.png"),
|
||||
createSubscription("유튜브 뮤직", "유튜브 음원 스트리밍", "MUSIC", 8900, 5, "/images/youtube-music.png")
|
||||
),
|
||||
"FOOD", Arrays.asList(
|
||||
createSubscription("쿠팡이츠", "식품 정기배송", "FOOD", 4900, 1, "/images/coupang-eats.png"),
|
||||
createSubscription("마켓컬리", "신선식품 새벽배송", "FOOD", 4900, 1, "/images/kurly.png"),
|
||||
createSubscription("배민", "배달음식 구독", "FOOD", 5900, 1, "/images/baemin.png")
|
||||
),
|
||||
"LIFE", Arrays.asList(
|
||||
createSubscription("당근", "중고거래 프리미엄", "LIFE", 3900, 1, "/images/karrot.png"),
|
||||
createSubscription("쿠팡 로켓와우", "무료배송 구독", "LIFE", 4900, 4, "/images/coupang.png"),
|
||||
createSubscription("밀리의 서재", "전자책 구독", "LIFE", 9900, 1, "/images/millie.png")
|
||||
),
|
||||
"BEAUTY", Arrays.asList(
|
||||
createSubscription("올리브영", "뷰티 정기구독", "BEAUTY", 15900, 1, "/images/oliveyoung.png"),
|
||||
createSubscription("시코르", "화장품 구독박스", "BEAUTY", 29900, 1, "/images/chicor.png"),
|
||||
createSubscription("롭스", "뷰티 멤버십", "BEAUTY", 19900, 1, "/images/lohbs.png")
|
||||
),
|
||||
"EDU", Arrays.asList(
|
||||
createSubscription("클래스101", "취미/교양 클래스", "EDU", 19900, 1, "/images/class101.png"),
|
||||
createSubscription("탈잉", "원데이 클래스", "EDU", 29900, 1, "/images/taling.png"),
|
||||
createSubscription("캐치", "IT 실무 교육", "EDU", 99000, 1, "/images/catch.png")
|
||||
)
|
||||
);
|
||||
|
||||
// 모든 서비스 저장
|
||||
subscriptionsByCategory.values().stream()
|
||||
.flatMap(List::stream)
|
||||
.forEach(subscriptionRepository::save);
|
||||
|
||||
log.info("Sample subscriptions loaded");
|
||||
}
|
||||
|
||||
private SubscriptionEntity createSubscription(String name, String description, String category,
|
||||
int price, int maxSharedUsers, String logoUrl) {
|
||||
return SubscriptionEntity.builder()
|
||||
.name(name)
|
||||
.description(description)
|
||||
.category(category)
|
||||
.price(price)
|
||||
.maxSharedUsers(maxSharedUsers)
|
||||
.logoUrl(logoUrl)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ import java.util.List;
|
||||
public class SecurityConfig {
|
||||
protected final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Value("${allowedorigins}")
|
||||
@Value("${allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
|
||||
|
||||
@ -25,7 +25,7 @@ import java.util.stream.Collectors;
|
||||
public class JwtTokenProvider {
|
||||
private final Algorithm algorithm;
|
||||
|
||||
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
|
||||
public JwtTokenProvider(@Value("${jwt.secret-key}") String secretKey) {
|
||||
this.algorithm = Algorithm.HMAC512(secretKey);
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.unicorn.lifesub.mysub.biz.dto.CategoryResponse;
|
||||
import com.unicorn.lifesub.mysub.biz.dto.ServiceListResponse;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.CategoryUseCase;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -17,6 +18,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "구독 카테고리 API", description = "구독 카테고리 관련 API")
|
||||
@RestController
|
||||
@SecurityRequirement(name = "bearerAuth") //이 어노테이션이 없으면 요청 헤더에 Authorization헤더가 안 생김
|
||||
@RequestMapping("/api/mysub")
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryController {
|
||||
|
||||
@ -6,6 +6,7 @@ import com.unicorn.lifesub.mysub.biz.dto.TotalFeeResponse;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.MySubscriptionsUseCase;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.TotalFeeUseCase;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -15,6 +16,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "마이구독 API", description = "마이구독 관련 API")
|
||||
@RestController
|
||||
@SecurityRequirement(name = "bearerAuth") //이 어노테이션이 없으면 요청 헤더에 Authorization헤더가 안 생김
|
||||
@RequestMapping("/api/mysub")
|
||||
@RequiredArgsConstructor
|
||||
public class MySubController {
|
||||
|
||||
@ -6,6 +6,7 @@ import com.unicorn.lifesub.mysub.biz.usecase.in.CancelSubscriptionUseCase;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.SubscribeUseCase;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.in.SubscriptionDetailUseCase;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "구독 서비스 API", description = "구독 서비스 관련 API")
|
||||
@RestController
|
||||
@SecurityRequirement(name = "bearerAuth") //이 어노테이션이 없으면 요청 헤더에 Authorization헤더가 안 생김
|
||||
@RequestMapping("/api/mysub/services")
|
||||
@RequiredArgsConstructor
|
||||
public class ServiceController {
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package com.unicorn.lifesub.mysub.infra.adapter;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway;
|
||||
|
||||
import com.unicorn.lifesub.common.exception.BusinessException;
|
||||
import com.unicorn.lifesub.common.exception.ErrorCode;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.MySubscription;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.out.MySubscriptionReader;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.out.MySubscriptionWriter;
|
||||
import com.unicorn.lifesub.mysub.infra.entity.MySubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.entity.SubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.repository.MySubscriptionJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.repository.SubscriptionJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.MySubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.SubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.repository.MySubscriptionJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.repository.SubscriptionJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -18,7 +18,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MySubscriptionAdapter implements MySubscriptionReader, MySubscriptionWriter {
|
||||
public class MySubscriptionGateway implements MySubscriptionReader, MySubscriptionWriter {
|
||||
private final MySubscriptionJpaRepository mySubscriptionRepository;
|
||||
private final SubscriptionJpaRepository subscriptionRepository;
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
package com.unicorn.lifesub.mysub.infra.adapter;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway;
|
||||
|
||||
import com.unicorn.lifesub.common.exception.BusinessException;
|
||||
import com.unicorn.lifesub.common.exception.ErrorCode;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.Subscription;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.Category;
|
||||
import com.unicorn.lifesub.mysub.biz.usecase.out.SubscriptionReader;
|
||||
import com.unicorn.lifesub.mysub.infra.entity.SubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.entity.CategoryEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.repository.SubscriptionJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.repository.CategoryJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.SubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.CategoryEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.repository.SubscriptionJpaRepository;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.repository.CategoryJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -18,7 +16,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SubscriptionAdapter implements SubscriptionReader {
|
||||
public class SubscriptionGateway implements SubscriptionReader {
|
||||
private final SubscriptionJpaRepository subscriptionRepository;
|
||||
private final CategoryJpaRepository categoryRepository;
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.unicorn.lifesub.mysub.infra.entity;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway.entity;
|
||||
|
||||
import com.unicorn.lifesub.common.entity.BaseTimeEntity;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.Category;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
@Entity
|
||||
@Table(name = "categories")
|
||||
@ -1,4 +1,4 @@
|
||||
package com.unicorn.lifesub.mysub.infra.entity;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway.entity;
|
||||
|
||||
import com.unicorn.lifesub.mysub.biz.domain.MySubscription;
|
||||
import jakarta.persistence.*;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.unicorn.lifesub.mysub.infra.entity;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway.entity;
|
||||
|
||||
import com.unicorn.lifesub.common.entity.BaseTimeEntity;
|
||||
import com.unicorn.lifesub.mysub.biz.domain.Subscription;
|
||||
@ -1,6 +1,6 @@
|
||||
package com.unicorn.lifesub.mysub.infra.repository;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway.repository;
|
||||
|
||||
import com.unicorn.lifesub.mysub.infra.entity.CategoryEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.CategoryEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface CategoryJpaRepository extends JpaRepository<CategoryEntity, String> {
|
||||
@ -1,6 +1,6 @@
|
||||
package com.unicorn.lifesub.mysub.infra.repository;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway.repository;
|
||||
|
||||
import com.unicorn.lifesub.mysub.infra.entity.MySubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.MySubscriptionEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.List;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.unicorn.lifesub.mysub.infra.repository;
|
||||
package com.unicorn.lifesub.mysub.infra.gateway.repository;
|
||||
|
||||
import com.unicorn.lifesub.mysub.infra.entity.SubscriptionEntity;
|
||||
import com.unicorn.lifesub.mysub.infra.gateway.entity.SubscriptionEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
@ -9,21 +9,26 @@ spring:
|
||||
username: ${POSTGRES_USER:admin}
|
||||
password: ${POSTGRES_PASSWORD:Passw0rd}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
# JPA 설정
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
show-sql: ${JPA_SHOW_SQL:true}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
|
||||
allowedorigins: ${ALLOWED_ORIGINS:*}
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ==}
|
||||
secret-key: ${JWT_SECRET_KEY:8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ}
|
||||
|
||||
allowed-origins: ${ALLOWED_ORIGINS:*}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.unicorn: DEBUG
|
||||
org.hibernate.SQL: TRACE
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
recommend/build/resolvedMainClassName
Normal file
1
recommend/build/resolvedMainClassName
Normal file
@ -0,0 +1 @@
|
||||
com.unicorn.lifesub.recommend.RecommendApplication
|
||||
@ -5,22 +5,31 @@ spring:
|
||||
application:
|
||||
name: recommend-service
|
||||
datasource:
|
||||
url: ${POSTGRES_URL}
|
||||
username: ${POSTGRES_USER}
|
||||
password: ${POSTGRES_PASSWORD}
|
||||
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:recommend}
|
||||
username: ${POSTGRES_USER:admin}
|
||||
password: ${POSTGRES_PASSWORD:Passw0rd}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
# JPA 설정
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:validate}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||
show-sql: ${JPA_SHOW_SQL:true}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
allowedorigins: ${ALLOWED_ORIGINS:*}
|
||||
jwt:
|
||||
secret-key: ${JWT_SECRET_KEY:8O2HQ13etL2BWZvYOiWsJ5uWFoLi6NBUG8divYVoCgtHVvlk3dqRksMl16toztDUeBTSIuOOPvHIrYq11G2BwQ}
|
||||
|
||||
allowed-origins: ${ALLOWED_ORIGINS:*}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.unicorn: DEBUG
|
||||
org.hibernate.SQL: TRACE
|
||||
|
||||
|
||||
Binary file not shown.
@ -3,8 +3,12 @@ package com.unicorn.lifesub.recommend;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = {
|
||||
"com.unicorn.lifesub.recommend",
|
||||
"com.unicorn.lifesub.common"
|
||||
}
|
||||
)
|
||||
public class RecommendApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RecommendApplication.class, args);
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
// File: lifesub/recommend/src/main/java/com/unicorn/lifesub/recommend/config/DataLoader.java
|
||||
package com.unicorn.lifesub.recommend.config;
|
||||
|
||||
import com.unicorn.lifesub.recommend.repository.entity.RecommendedCategoryEntity;
|
||||
import com.unicorn.lifesub.recommend.repository.entity.SpendingEntity;
|
||||
import com.unicorn.lifesub.recommend.repository.jpa.RecommendRepository;
|
||||
import com.unicorn.lifesub.recommend.repository.jpa.SpendingRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataLoader implements CommandLineRunner {
|
||||
|
||||
private final RecommendRepository recommendRepository;
|
||||
private final SpendingRepository spendingRepository;
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final List<String> SPENDING_CATEGORIES = Arrays.asList(
|
||||
"COSMETICS", "ENTERTAINMENT", "EDUCATION", "RESTAURANT", "MUSIC", "DAILY"
|
||||
);
|
||||
|
||||
private static final List<String> SUBSCRIPTION_CATEGORIES = Arrays.asList(
|
||||
"BEAUTY", "OTT", "EDU", "FOOD", "MUSIC", "LIFE"
|
||||
);
|
||||
|
||||
@Override // CommandLineRunner의 run 메소드 구현
|
||||
@Transactional
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("Initializing sample data...");
|
||||
initSpendingHistory();
|
||||
initRecommendedCategories();
|
||||
log.info("Sample data initialization completed.");
|
||||
}
|
||||
|
||||
private void initSpendingHistory() {
|
||||
// 기존 데이터 삭제
|
||||
spendingRepository.deleteAll();
|
||||
|
||||
List<SpendingEntity> spendings = new ArrayList<>();
|
||||
LocalDate now = LocalDate.now();
|
||||
|
||||
// user01 ~ user10까지의 지출 데이터 생성
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
String userId = String.format("user%02d", i);
|
||||
|
||||
// 각 사용자별로 지난 한달간 5~10건의 지출 데이터 생성
|
||||
int numTransactions = 5 + random.nextInt(6);
|
||||
|
||||
for (int j = 0; j < numTransactions; j++) {
|
||||
String category = SPENDING_CATEGORIES.get(random.nextInt(SPENDING_CATEGORIES.size()));
|
||||
long amount = (50 + random.nextInt(451)) * 1000L; // 5만원 ~ 50만원 사이
|
||||
int daysAgo = random.nextInt(30); // 최근 30일 이내
|
||||
|
||||
spendings.add(SpendingEntity.builder()
|
||||
.userId(userId)
|
||||
.category(category)
|
||||
.amount(amount)
|
||||
.spendingDate(now.minusDays(daysAgo))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
spendingRepository.saveAll(spendings);
|
||||
log.info("Spending history data initialized with {} records", spendings.size());
|
||||
}
|
||||
|
||||
private void initRecommendedCategories() {
|
||||
// 기존 데이터 삭제
|
||||
recommendRepository.deleteAll();
|
||||
|
||||
// 지출 카테고리와 추천 구독 카테고리 매핑 데이터 생성
|
||||
List<RecommendedCategoryEntity> categories = Arrays.asList(
|
||||
RecommendedCategoryEntity.builder()
|
||||
.spendingCategory("COSMETICS")
|
||||
.recommendCategory("BEAUTY")
|
||||
.build(),
|
||||
RecommendedCategoryEntity.builder()
|
||||
.spendingCategory("ENTERTAINMENT")
|
||||
.recommendCategory("OTT")
|
||||
.build(),
|
||||
RecommendedCategoryEntity.builder()
|
||||
.spendingCategory("EDUCATION")
|
||||
.recommendCategory("EDU")
|
||||
.build(),
|
||||
RecommendedCategoryEntity.builder()
|
||||
.spendingCategory("RESTAURANT")
|
||||
.recommendCategory("FOOD")
|
||||
.build(),
|
||||
RecommendedCategoryEntity.builder()
|
||||
.spendingCategory("MUSIC")
|
||||
.recommendCategory("MUSIC")
|
||||
.build(),
|
||||
RecommendedCategoryEntity.builder()
|
||||
.spendingCategory("DAILY")
|
||||
.recommendCategory("LIFE")
|
||||
.build()
|
||||
);
|
||||
|
||||
recommendRepository.saveAll(categories);
|
||||
log.info("Recommended categories data initialized with {} records", categories.size());
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ import java.util.List;
|
||||
public class SecurityConfig {
|
||||
protected final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Value("${allowedorigins}")
|
||||
@Value("${allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
|
||||
|
||||
@ -25,7 +25,7 @@ import java.util.stream.Collectors;
|
||||
public class JwtTokenProvider {
|
||||
private final Algorithm algorithm;
|
||||
|
||||
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
|
||||
public JwtTokenProvider(@Value("${jwt.secret-key}") String secretKey) {
|
||||
this.algorithm = Algorithm.HMAC512(secretKey);
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import com.unicorn.lifesub.common.dto.ApiResponse;
|
||||
import com.unicorn.lifesub.recommend.dto.RecommendCategoryDTO;
|
||||
import com.unicorn.lifesub.recommend.service.RecommendService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "구독추천 API", description = "구독추천 관련 API")
|
||||
@RestController
|
||||
@SecurityRequirement(name = "bearerAuth") //이 어노테이션이 없으면 요청 헤더에 Authorization헤더가 안 생김
|
||||
@RequestMapping("/api/recommend")
|
||||
@RequiredArgsConstructor
|
||||
public class RecommendController {
|
||||
|
||||
@ -8,6 +8,7 @@ import java.time.LocalDate;
|
||||
@Builder
|
||||
public class RecommendCategoryDTO {
|
||||
private String categoryName;
|
||||
private String imagePath;
|
||||
private LocalDate baseDate;
|
||||
private String spendingCategory;
|
||||
private Long totalSpending;
|
||||
}
|
||||
|
||||
@ -1,19 +1,10 @@
|
||||
package com.unicorn.lifesub.recommend.repository.jpa;
|
||||
|
||||
import com.unicorn.lifesub.recommend.repository.entity.RecommendedCategoryEntity;
|
||||
import com.unicorn.lifesub.recommend.repository.entity.SpendingEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RecommendRepository extends JpaRepository<RecommendedCategoryEntity, Long> {
|
||||
Optional<RecommendedCategoryEntity> findBySpendingCategory(String category);
|
||||
|
||||
@Query("SELECT s FROM SpendingEntity s WHERE s.userId = :userId AND s.spendingDate >= :startDate")
|
||||
List<SpendingEntity> findSpendingsByUserIdAndDateAfter(@Param("userId") String userId,
|
||||
@Param("startDate") LocalDate startDate);
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// File: lifesub/recommend/src/main/java/com/unicorn/lifesub/recommend/repository/jpa/SpendingRepository.java
|
||||
package com.unicorn.lifesub.recommend.repository.jpa;
|
||||
|
||||
import com.unicorn.lifesub.recommend.repository.entity.SpendingEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface SpendingRepository extends JpaRepository<SpendingEntity, Long> {
|
||||
@Query("SELECT s FROM SpendingEntity s WHERE s.userId = :userId AND s.spendingDate >= :startDate")
|
||||
List<SpendingEntity> findSpendingsByUserIdAndDateAfter(@Param("userId") String userId,
|
||||
@Param("startDate") LocalDate startDate);
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
// File: lifesub/recommend/src/main/java/com/unicorn/lifesub/recommend/service/RecommendServiceImpl.java
|
||||
package com.unicorn.lifesub.recommend.service;
|
||||
|
||||
import com.unicorn.lifesub.common.exception.BusinessException;
|
||||
@ -6,16 +7,20 @@ import com.unicorn.lifesub.recommend.domain.RecommendedCategory;
|
||||
import com.unicorn.lifesub.recommend.domain.SpendingCategory;
|
||||
import com.unicorn.lifesub.recommend.dto.RecommendCategoryDTO;
|
||||
import com.unicorn.lifesub.recommend.repository.jpa.RecommendRepository;
|
||||
import com.unicorn.lifesub.recommend.repository.jpa.SpendingRepository;
|
||||
import com.unicorn.lifesub.recommend.repository.entity.SpendingEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RecommendServiceImpl implements RecommendService {
|
||||
private final RecommendRepository recommendRepository;
|
||||
private final SpendingRepository spendingRepository;
|
||||
private final SpendingAnalyzer spendingAnalyzer;
|
||||
|
||||
@Override
|
||||
@ -23,27 +28,29 @@ public class RecommendServiceImpl implements RecommendService {
|
||||
public RecommendCategoryDTO getRecommendedCategory(String userId) {
|
||||
LocalDate startDate = LocalDate.now().minusMonths(1);
|
||||
|
||||
SpendingCategory topSpending = spendingAnalyzer.analyzeSpending(
|
||||
recommendRepository.findSpendingsByUserIdAndDateAfter(userId, startDate)
|
||||
);
|
||||
List<SpendingEntity> spendings = spendingRepository.findSpendingsByUserIdAndDateAfter(userId, startDate);
|
||||
|
||||
if (spendings.isEmpty()) {
|
||||
throw new BusinessException(ErrorCode.NO_SPENDING_DATA);
|
||||
}
|
||||
|
||||
SpendingCategory topSpending = spendingAnalyzer.analyzeSpending(spendings);
|
||||
|
||||
if (topSpending == null) {
|
||||
throw new BusinessException(ErrorCode.NO_SPENDING_DATA);
|
||||
}
|
||||
|
||||
RecommendedCategory recommendedCategory = recommendRepository
|
||||
.findBySpendingCategory(topSpending.getCategory())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.NO_SPENDING_DATA))
|
||||
.toDomain();
|
||||
.findBySpendingCategory(topSpending.getCategory())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.NO_RECOMMENDATION_DATA))
|
||||
.toDomain();
|
||||
|
||||
return RecommendCategoryDTO.builder()
|
||||
.categoryName(recommendedCategory.getRecommendCategory())
|
||||
.imagePath(getCategoryImagePath(recommendedCategory.getRecommendCategory()))
|
||||
.baseDate(recommendedCategory.getBaseDate())
|
||||
.spendingCategory(topSpending.getCategory())
|
||||
.totalSpending(topSpending.getTotalAmount())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getCategoryImagePath(String category) {
|
||||
return "/images/categories/" + category.toLowerCase() + ".png";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user