diff --git a/.gitignore b/.gitignore index 76d31bf..6efd80f 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,8 @@ gradle-app.setting application-*.yml !application.yml !application-test.yml + +smtp.yml +redis.yml +db-main.yml +logging.yml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f8956e9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "jtoon-core/core-api/src/main/resources/config"] + path = jtoon-core/core-api/src/main/resources/config + url = https://github.com/BE-04-JTOON/private-env.git diff --git a/build.gradle b/build.gradle index a3be76e..4189c0c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,56 +1,51 @@ -buildscript { - ext { - springBootVersion = '3.1.2' - } - - repositories { - mavenCentral() - } - - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - classpath "io.spring.gradle:dependency-management-plugin:1.1.2" - } +plugins { + id 'java' + id "java-library" + id 'org.springframework.boot' apply false + id 'io.spring.dependency-management' apply false } -subprojects { - group 'shop.jtoon' - version '0.0.1-SNAPSHOT' +java.sourceCompatibility = javaVersion - apply plugin: 'java-library' - apply plugin: 'idea' - apply plugin: 'org.springframework.boot' - apply plugin: 'io.spring.dependency-management' +applicationVersion=project['applicationVersion'] +projectGroup=project['projectGroup'] +javaVersion=project['javaVersion'] + +allprojects { + group = projectGroup + version = applicationVersion - sourceCompatibility = 17 + apply plugin: "java" + apply plugin: "java-library" repositories { mavenCentral() maven { url 'https://jitpack.io' } } +} + +subprojects { + apply plugin: 'org.springframework.boot' + apply plugin: 'io.spring.dependency-management' dependencies { + // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - } - test { - useJUnitPlatform() + // Test + testImplementation 'org.springframework.boot:spring-boot-starter-test' } -} -project(':module-application') { - bootJar.enabled = false -} - -project(':module-domain') { - bootJar.enabled = false -} + tasks.getByName('bootJar') { + enabled = false + } -project(':module-internal') { - bootJar.enabled = false + tasks.getByName("jar") { + enabled = true + } } -project(':module-core') { - bootJar.enabled = false -} +tasks.named('test') { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..84243cb --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +# Project dependency versions +applicationVersion=0.0.1 +javaVersion=17 + +# Project Configs +projectGroup=shop.jtoon + + +# Spring dependency versions +springBootVersion=3.2.4 +springDependencyManagementVersion=1.1.4 +springCloudDependenciesVersion=2023.0.1 \ No newline at end of file diff --git a/jtoon-core/README.md b/jtoon-core/README.md new file mode 100644 index 0000000..e5dcffa --- /dev/null +++ b/jtoon-core/README.md @@ -0,0 +1,45 @@ +# JToon Core + +Jtoon 서비스의 해결하고자하는 도메인에 대한 모듈 + +## 계층별 지키고자 하는 조건 +(layered 기반) + +- Presentation Layer : 외부 의존성이 높은 영역, Controller, dto 존재 +- Domain Layer(Service) : 해결하고자 하는 문제인 도메인에 집중하고자 하는 layer +- Repository Layer : 상세 구현 로직이 다양한 자원에 접근할 수 있는 기능을 제공하는 레이어 + +## Include + +### 초기 +- `core-api`: 전체 도메인 로직에 대한 모듈만 존재 + +단점: +1. 모든 기능이 해당 모듈에 몰려있어서 많은 의존성을 가지고 있다. +2. 도메인은 문제를 해결하기 위한 영역이기 때문에 외부의 의존성을 가지는 것이 맞을까? +3. Batch서버를 따로 올린다고 했을 때, Runnable한 API가 2개가 생기게 되는데 이때 동일한 도메인 기능을 서로 다르게 관리하게 된다. + +## 2번째 리팩토링 + +### Core API +Presentation 영역, 외부 의존성이 높다. + +- Controller, Dto가 존재 +- 확장되지 않는 서비스에 대한 로직 예를들어 Admin은 여기에 추가하고 추후에 변경 + +### Core domain +오직 Domain 서비스만 관리하는 모듈 + +- Domain서 서비스를 문제만 해결하기 위해 spring framework의 의존성을 없애는 것으로 한다. +- spring framework가 자체적으로 변경되든, framework를 변경할때 domain 로직이 변경되는 것이 옳지 않다고 생각 + - 단 DB가 존재하기 때문에, JPA는 추가 + +#### 문제 +- Bean등록을 어떻게 하면 좋을까? +- Transaction은 어떻게 해야할까 + + +이렇게하면, 여러 개의 Runnable한 서비스가 가능하다 + +단점: +- 도메인이 DB에 의존적이다. 근데 DB를 변경하는 경우가 많을까? 에 대한 생각을 해볼 필요가 있다. \ No newline at end of file diff --git a/jtoon-core/core-api/build.gradle b/jtoon-core/core-api/build.gradle new file mode 100644 index 0000000..cfd400a --- /dev/null +++ b/jtoon-core/core-api/build.gradle @@ -0,0 +1,48 @@ +tasks.getByName('bootJar') { + enabled = true +} + +tasks.getByName("jar") { + enabled = false +} + +dependencies { + + implementation project(":jtoon-core:core-domain") + + implementation project(":jtoon-internal:core-web") + implementation project(":jtoon-internal:iamport-client") + implementation project(":jtoon-internal:s3-client") + implementation project(":jtoon-internal:smtp-client") + + implementation project(":jtoon-support:logging") + implementation project(":jtoon-support:monitoring") + + implementation project(":jtoon-system") + + implementation project(":jtoon-db:db-redis") + + // Web + implementation 'org.springframework.boot:spring-boot-starter-web' + + // Bean Validation + implementation 'org.springframework.boot:spring-boot-starter-validation' + + // AOP + implementation 'org.springframework.boot:spring-boot-starter-aop' + + // Security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + + // OAuth2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // RestDocs + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' +} diff --git a/module-application/app-api/src/docs/asciidoc/index.adoc b/jtoon-core/core-api/src/docs/asciidoc/index.adoc similarity index 100% rename from module-application/app-api/src/docs/asciidoc/index.adoc rename to jtoon-core/core-api/src/docs/asciidoc/index.adoc diff --git a/module-application/app-api/src/docs/asciidoc/payment.adoc b/jtoon-core/core-api/src/docs/asciidoc/payment.adoc similarity index 100% rename from module-application/app-api/src/docs/asciidoc/payment.adoc rename to jtoon-core/core-api/src/docs/asciidoc/payment.adoc diff --git a/module-application/app-api/src/main/java/shop/jtoon/JToonApplication.java b/jtoon-core/core-api/src/main/java/shop/jtoon/JToonApplication.java similarity index 100% rename from module-application/app-api/src/main/java/shop/jtoon/JToonApplication.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/JToonApplication.java diff --git a/module-application/app-api/src/main/java/shop/jtoon/global/aop/ApiInformationAspect.java b/jtoon-core/core-api/src/main/java/shop/jtoon/global/aop/ApiInformationAspect.java similarity index 99% rename from module-application/app-api/src/main/java/shop/jtoon/global/aop/ApiInformationAspect.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/global/aop/ApiInformationAspect.java index 00910cd..1bbeb69 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/global/aop/ApiInformationAspect.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/global/aop/ApiInformationAspect.java @@ -1,6 +1,8 @@ package shop.jtoon.global.aop; -import lombok.extern.slf4j.Slf4j; +import java.lang.reflect.Method; +import java.util.Arrays; + import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterReturning; @@ -10,8 +12,7 @@ import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; -import java.lang.reflect.Method; -import java.util.Arrays; +import lombok.extern.slf4j.Slf4j; @Slf4j @Aspect diff --git a/module-application/app-api/src/main/java/shop/jtoon/global/config/PasswordEncoderConfig.java b/jtoon-core/core-api/src/main/java/shop/jtoon/global/config/PasswordEncoderConfig.java similarity index 100% rename from module-application/app-api/src/main/java/shop/jtoon/global/config/PasswordEncoderConfig.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/global/config/PasswordEncoderConfig.java diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/member/application/LoginService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/member/application/LoginService.java new file mode 100644 index 0000000..070bad6 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/member/application/LoginService.java @@ -0,0 +1,49 @@ +package shop.jtoon.member.application; + +import org.springframework.stereotype.Service; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import shop.jtoon.login.service.LoginDomainService; +import shop.jtoon.member.domain.MyInfo; +import shop.jtoon.member.dto.OAuthSignUpDto; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.request.LocalSignUpReq; +import shop.jtoon.security.request.LoginReq; +import shop.jtoon.security.service.JwtService; +import shop.jtoon.security.service.RefreshTokenService; +import shop.jtoon.security.util.TokenCookie; +import shop.jtoon.util.SecurityConstant; + +@Service +@RequiredArgsConstructor +public class LoginService { + + private final JwtService jwtProvider; + private final RefreshTokenService refreshTokenServiceImpl; + + private final LoginDomainService loginService; + + public void signUp(LocalSignUpReq localSignUpReq) { + loginService.signUp(localSignUpReq.toLoginInfo(), localSignUpReq.toUserInfo()); + } + + public void loginMember(LoginReq loginReq, HttpServletResponse response) { + loginService.login(loginReq.toLoginInfo()); + + String accessToken = jwtProvider.generateAccessToken(loginReq.email()); + String refreshToken = jwtProvider.generateRefreshToken(); + refreshTokenServiceImpl.saveRefreshToken(refreshToken, loginReq.email()); + + response.addCookie(TokenCookie.of(SecurityConstant.ACCESS_TOKEN_HEADER, accessToken)); + response.addCookie(TokenCookie.of(SecurityConstant.REFRESH_TOKEN_HEADER, refreshToken)); + } + + public Member generateOrGetSocialMember(OAuthSignUpDto oAuthSignUpDto) { + return loginService.generateOrGetSocialMember(oAuthSignUpDto.toLoginInfo(), oAuthSignUpDto.toUserInfo()); + } + + public MyInfo readMyInfo(String email) { + return loginService.findMemberDtoByEmail(email); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/MemberDto.java b/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/MemberDto.java new file mode 100644 index 0000000..b465340 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/MemberDto.java @@ -0,0 +1,29 @@ +package shop.jtoon.member.dto; + +import lombok.Builder; +import shop.jtoon.member.domain.MyInfo; +import shop.jtoon.member.entity.Gender; +import shop.jtoon.member.entity.Role; + +@Builder +public record MemberDto( + Long id, + String email, + String name, + String nickname, + Gender gender, + Role role, + String phone +) { + public static MemberDto toDto(MyInfo myInfo) { + return MemberDto.builder() + .id(myInfo.id()) + .email(myInfo.email()) + .name(myInfo.name()) + .nickname(myInfo.nickname()) + .gender(myInfo.gender()) + .role(myInfo.role()) + .phone(myInfo.phone()) + .build(); + } +} diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/dto/OAuthAttributes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/OAuthAttributes.java similarity index 96% rename from module-domain/domain-member/src/main/java/shop/jtoon/dto/OAuthAttributes.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/OAuthAttributes.java index b81d363..5375f01 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/dto/OAuthAttributes.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/OAuthAttributes.java @@ -1,13 +1,14 @@ -package shop.jtoon.dto; +package shop.jtoon.member.dto; import static shop.jtoon.type.ErrorStatus.*; import java.util.Map; import java.util.UUID; + import lombok.AccessLevel; import lombok.Builder; -import shop.jtoon.entity.LoginType; import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.member.entity.LoginType; @Builder(access = AccessLevel.PRIVATE) public record OAuthAttributes( diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/OAuthSignUpDto.java b/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/OAuthSignUpDto.java new file mode 100644 index 0000000..f75ef1e --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/member/dto/OAuthSignUpDto.java @@ -0,0 +1,36 @@ +package shop.jtoon.member.dto; + +import lombok.Builder; + +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.login.domain.UserInfo; +import shop.jtoon.member.entity.Gender; +import shop.jtoon.member.entity.LoginType; + +@Builder +public record OAuthSignUpDto( + String email, + String password, + String name, + String nickname, + String gender, + String phone, + String loginType +) { + public LoginInfo toLoginInfo() { + return LoginInfo.builder() + .email(email) + .password(password) + .loginType(LoginType.from(loginType)) + .build(); + } + + public UserInfo toUserInfo() { + return UserInfo.builder() + .name(name) + .nickname(nickname) + .gender(Gender.from(gender)) + .phone(phone) + .build(); + } +} diff --git a/module-application/app-api/src/main/java/shop/jtoon/member/presentation/MemberController.java b/jtoon-core/core-api/src/main/java/shop/jtoon/member/presentation/LoginController.java similarity index 67% rename from module-application/app-api/src/main/java/shop/jtoon/member/presentation/MemberController.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/member/presentation/LoginController.java index 19f7ebc..9ea1ba8 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/member/presentation/MemberController.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/member/presentation/LoginController.java @@ -12,33 +12,33 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import shop.jtoon.member.application.EmailService; -import shop.jtoon.member.application.MemberService; +import shop.jtoon.application.SmtpService; +import shop.jtoon.member.application.LoginService; import shop.jtoon.member.request.LocalSignUpReq; import shop.jtoon.security.request.LoginReq; @RestController @RequiredArgsConstructor @RequestMapping("/members") -public class MemberController { +public class LoginController { - private final MemberService memberService; - private final EmailService emailService; + private final LoginService memberService; + private final SmtpService smtpService; @PostMapping("/sign-up") @ResponseStatus(HttpStatus.CREATED) - public void signUp(@RequestBody @Valid LocalSignUpReq localSignUpReq) { - memberService.signUp(localSignUpReq); + public void signUp(@RequestBody @Valid LocalSignUpReq request) { + memberService.signUp(request); + } + + @PostMapping("/local-login") + public void login(@RequestBody @Valid LoginReq request, HttpServletResponse servletResponse) { + memberService.loginMember(request, servletResponse); } @GetMapping("/email-authorization") @ResponseStatus(HttpStatus.CREATED) public void authenticateEmail(@RequestParam(value = "email") String email) { - emailService.sendEmailAuthentication(email); - } - - @PostMapping("/local-login") - public void login(@RequestBody @Valid LoginReq loginReq, HttpServletResponse response) { - memberService.loginMember(loginReq, response); + smtpService.sendMail(email); } } diff --git a/module-application/app-api/src/main/java/shop/jtoon/member/request/LocalSignUpReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/member/request/LocalSignUpReq.java similarity index 69% rename from module-application/app-api/src/main/java/shop/jtoon/member/request/LocalSignUpReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/member/request/LocalSignUpReq.java index e7d5ae1..ae6001d 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/member/request/LocalSignUpReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/member/request/LocalSignUpReq.java @@ -6,10 +6,10 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import shop.jtoon.entity.Gender; -import shop.jtoon.entity.LoginType; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Role; +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.login.domain.UserInfo; +import shop.jtoon.member.entity.Gender; +import shop.jtoon.member.entity.LoginType; public record LocalSignUpReq( @Pattern(regexp = EMAIL_PATTERN) String email, @@ -20,16 +20,20 @@ public record LocalSignUpReq( @Pattern(regexp = PHONE_PATTERN) String phone, @NotNull String loginType ) { - public Member toEntity(String encryptedPassword) { - return Member.builder() + public LoginInfo toLoginInfo() { + return LoginInfo.builder() .email(email) - .password(encryptedPassword) + .password(password) + .loginType(LoginType.from(loginType)) + .build(); + } + + public UserInfo toUserInfo() { + return UserInfo.builder() .name(name) .nickname(nickname) .gender(Gender.from(gender)) .phone(phone) - .role(Role.USER) - .loginType(LoginType.from(loginType)) .build(); } } diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/payment/application/PaymentService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/application/PaymentService.java new file mode 100644 index 0000000..d89b6f2 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/application/PaymentService.java @@ -0,0 +1,59 @@ +package shop.jtoon.payment.application; + +import java.math.BigDecimal; +import java.util.List; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.dto.MemberDto; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.service.MemberService; +import shop.jtoon.payment.domain.CancelPayInfo; +import shop.jtoon.payment.domain.Item; +import shop.jtoon.payment.domain.Pay; +import shop.jtoon.payment.domain.Receipt; +import shop.jtoon.payment.request.CancelReq; +import shop.jtoon.payment.request.ConditionReq; +import shop.jtoon.payment.request.PaymentReq; +import shop.jtoon.payment.response.PaymentRes; +import shop.jtoon.payment.service.PayService; +import shop.jtoon.service.IamportService; + +@Service +@RequiredArgsConstructor +public class PaymentService { + + private final IamportService iamportService; + private final MemberService memberService; + private final PayService payService; + + public BigDecimal validateAndCreatePayment(PaymentReq paymentReq, MemberDto memberDto) { + Pay pay = paymentReq.toPay(); + Item item = paymentReq.toItem(); + + Member member = memberService.read(memberDto.email()); + iamportService.validateIamport(pay.impUid(), item.amount()); + payService.createPaymentInfo(pay, item, member); + + return paymentReq.amount(); + } + + public void cancelPayment(CancelReq cancelReq) { + CancelPayInfo cancelPayInfo = cancelReq.cancelPayInfo(); + + iamportService.validateIamport(cancelPayInfo.impUid(), cancelPayInfo.checksum()); + iamportService.cancelIamport( + cancelPayInfo.impUid(), + cancelPayInfo.reason(), + cancelPayInfo.checksum(), + cancelPayInfo.refundHolder() + ); + } + + public List getPayments(ConditionReq conditionReq, MemberDto memberDto) { + List paymentInfos = payService.readPayments(conditionReq.merchantsUid(), memberDto.email()); + + return PaymentRes.of(paymentInfos); + } +} diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/presentation/PaymentController.java b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/presentation/PaymentController.java similarity index 79% rename from module-application/app-api/src/main/java/shop/jtoon/payment/presentation/PaymentController.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/payment/presentation/PaymentController.java index e261440..9a3413d 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/presentation/PaymentController.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/presentation/PaymentController.java @@ -1,20 +1,25 @@ package shop.jtoon.payment.presentation; +import java.math.BigDecimal; +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; import shop.jtoon.annotation.CurrentUser; -import shop.jtoon.dto.MemberDto; +import shop.jtoon.member.dto.MemberDto; import shop.jtoon.payment.application.PaymentService; import shop.jtoon.payment.request.CancelReq; import shop.jtoon.payment.request.ConditionReq; import shop.jtoon.payment.request.PaymentReq; import shop.jtoon.payment.response.PaymentRes; -import java.math.BigDecimal; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/payments") diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/request/CancelReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/CancelReq.java similarity index 67% rename from module-application/app-api/src/main/java/shop/jtoon/payment/request/CancelReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/CancelReq.java index 51325cb..6bf1e32 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/request/CancelReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/CancelReq.java @@ -1,12 +1,13 @@ package shop.jtoon.payment.request; +import java.math.BigDecimal; + import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Builder; - -import java.math.BigDecimal; +import shop.jtoon.payment.domain.CancelPayInfo; @Builder public record CancelReq( @@ -16,4 +17,14 @@ public record CancelReq( @NotNull @DecimalMin("1") BigDecimal checksum, @NotBlank @Size(max = 10) String refundHolder ) { + + public CancelPayInfo cancelPayInfo() { + return CancelPayInfo.builder() + .impUid(impUid) + .merchantUid(merchantUid) + .reason(reason) + .checksum(checksum) + .refundHolder(refundHolder) + .build(); + } } diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/request/ConditionReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/ConditionReq.java similarity index 100% rename from module-application/app-api/src/main/java/shop/jtoon/payment/request/ConditionReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/ConditionReq.java index 012acf4..f5e02cd 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/request/ConditionReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/ConditionReq.java @@ -1,9 +1,9 @@ package shop.jtoon.payment.request; -import lombok.Builder; - import java.util.List; +import lombok.Builder; + @Builder public record ConditionReq( List merchantsUid diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/PaymentReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/PaymentReq.java new file mode 100644 index 0000000..1a4b566 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/request/PaymentReq.java @@ -0,0 +1,65 @@ +package shop.jtoon.payment.request; + +import static shop.jtoon.util.RegExp.*; + +import java.math.BigDecimal; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Builder; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.domain.Buyer; +import shop.jtoon.payment.domain.Item; +import shop.jtoon.payment.domain.Pay; +import shop.jtoon.payment.entity.CookieItem; +import shop.jtoon.payment.entity.PaymentInfo; + +@Builder +public record PaymentReq( + @NotBlank String impUid, + @NotBlank String merchantUid, + @NotBlank String payMethod, + @NotBlank String itemName, + @NotNull @DecimalMin("1") BigDecimal amount, + @Pattern(regexp = EMAIL_PATTERN) String buyerEmail, + @NotBlank @Size(max = 10) String buyerName, + @Pattern(regexp = PHONE_PATTERN) String buyerPhone +) { + + public PaymentInfo toEntity(Member member) { + return PaymentInfo.builder() + .impUid(this.impUid) + .merchantUid(this.merchantUid) + .payMethod(this.payMethod) + .cookieItem(CookieItem.from(this.itemName)) + .amount(this.amount) + .member(member) + .build(); + } + + public Pay toPay() { + return Pay.builder() + .impUid(impUid) + .merchantUid(merchantUid) + .payMethod(payMethod) + .build(); + } + + public Item toItem() { + return Item.builder() + .itemName(itemName) + .amount(amount) + .build(); + } + + public Buyer toBuyer() { + return Buyer.builder() + .buyerEmail(buyerEmail) + .buyerName(buyerName) + .buyerPhone(buyerPhone) + .build(); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/payment/response/PaymentRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/response/PaymentRes.java new file mode 100644 index 0000000..d5c3c4d --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/payment/response/PaymentRes.java @@ -0,0 +1,33 @@ +package shop.jtoon.payment.response; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import lombok.Builder; +import shop.jtoon.payment.domain.Receipt; +import shop.jtoon.payment.entity.PaymentInfo; + +@Builder +public record PaymentRes( + String itemName, + int itemCount, + BigDecimal amount, + LocalDateTime createdAt +) { + + public static PaymentRes toDto(Receipt receipt) { + return PaymentRes.builder() + .itemName(receipt.itemName()) + .itemCount(receipt.itemCount()) + .amount(receipt.amount()) + .createdAt(receipt.createdAt()) + .build(); + } + + public static List of(List paymentInfos) { + return paymentInfos.stream() + .map(PaymentRes::toDto) + .toList(); + } +} diff --git a/module-application/app-api/src/main/java/shop/jtoon/security/application/AuthenticationServiceImpl.java b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/AuthenticationServiceImpl.java similarity index 74% rename from module-application/app-api/src/main/java/shop/jtoon/security/application/AuthenticationServiceImpl.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/security/application/AuthenticationServiceImpl.java index b516af0..18116df 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/security/application/AuthenticationServiceImpl.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/AuthenticationServiceImpl.java @@ -2,26 +2,29 @@ import static shop.jtoon.util.SecurityConstant.*; +import java.util.List; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; -import java.util.List; import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.MemberDto; -import shop.jtoon.member.application.MemberService; +import shop.jtoon.member.application.LoginService; +import shop.jtoon.member.domain.MyInfo; +import shop.jtoon.member.dto.MemberDto; import shop.jtoon.security.service.AuthenticationService; @Service @RequiredArgsConstructor public class AuthenticationServiceImpl implements AuthenticationService { - private final MemberService memberService; + private final LoginService memberService; @Override public Authentication getAuthentication(String claimsEmail) { - MemberDto memberDto = memberService.findMemberDtoByEmail(claimsEmail); + MyInfo myInfo = memberService.readMyInfo(claimsEmail); + MemberDto memberDto = MemberDto.toDto(myInfo); return new UsernamePasswordAuthenticationToken(memberDto, BLANK, List.of(new SimpleGrantedAuthority(memberDto.role().toString()))); diff --git a/module-application/app-api/src/main/java/shop/jtoon/security/application/JwtServiceImpl.java b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/JwtServiceImpl.java similarity index 99% rename from module-application/app-api/src/main/java/shop/jtoon/security/application/JwtServiceImpl.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/security/application/JwtServiceImpl.java index eb2c786..bc347eb 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/security/application/JwtServiceImpl.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/JwtServiceImpl.java @@ -2,6 +2,10 @@ import static shop.jtoon.type.ErrorStatus.*; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; + import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -10,9 +14,6 @@ import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.util.Date; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import shop.jtoon.exception.UnauthorizedException; diff --git a/module-application/app-api/src/main/java/shop/jtoon/security/application/OAuth2Service.java b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/OAuth2Service.java similarity index 88% rename from module-application/app-api/src/main/java/shop/jtoon/security/application/OAuth2Service.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/security/application/OAuth2Service.java index 7da9608..21cb94b 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/security/application/OAuth2Service.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/OAuth2Service.java @@ -1,5 +1,8 @@ package shop.jtoon.security.application; +import java.util.Collections; +import java.util.Map; + import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; @@ -8,25 +11,21 @@ import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.Map; import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.OAuthAttributes; -import shop.jtoon.entity.LoginType; -import shop.jtoon.entity.Member; import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.member.application.MemberService; +import shop.jtoon.member.application.LoginService; +import shop.jtoon.member.dto.OAuthAttributes; +import shop.jtoon.member.entity.LoginType; +import shop.jtoon.member.entity.Member; import shop.jtoon.security.service.CustomOAuth2UserService; import shop.jtoon.type.ErrorStatus; @Service -@Transactional @RequiredArgsConstructor public class OAuth2Service implements CustomOAuth2UserService { - private final MemberService memberService; + private final LoginService memberService; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { diff --git a/module-application/app-api/src/main/java/shop/jtoon/security/application/RefreshTokenServiceImpl.java b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/RefreshTokenServiceImpl.java similarity index 100% rename from module-application/app-api/src/main/java/shop/jtoon/security/application/RefreshTokenServiceImpl.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/security/application/RefreshTokenServiceImpl.java index 217f11d..2f58668 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/security/application/RefreshTokenServiceImpl.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/security/application/RefreshTokenServiceImpl.java @@ -3,8 +3,8 @@ import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; -import shop.jtoon.security.service.RefreshTokenService; import shop.jtoon.service.RedisTokenService; +import shop.jtoon.security.service.RefreshTokenService; @Service @RequiredArgsConstructor diff --git a/module-application/app-api/src/main/java/shop/jtoon/security/presentation/AuthController.java b/jtoon-core/core-api/src/main/java/shop/jtoon/security/presentation/AuthController.java similarity index 100% rename from module-application/app-api/src/main/java/shop/jtoon/security/presentation/AuthController.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/security/presentation/AuthController.java diff --git a/module-application/app-api/src/main/java/shop/jtoon/security/request/LoginReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/security/request/LoginReq.java similarity index 57% rename from module-application/app-api/src/main/java/shop/jtoon/security/request/LoginReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/security/request/LoginReq.java index e972cfb..39f6167 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/security/request/LoginReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/security/request/LoginReq.java @@ -4,9 +4,19 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.member.entity.LoginType; public record LoginReq( @Pattern(regexp = EMAIL_PATTERN) String email, @Pattern(regexp = PASSWORD_PATTERN) @NotBlank String password ) { + + public LoginInfo toLoginInfo() { + return LoginInfo.builder() + .email(email) + .password(password) + .loginType(LoginType.KAKAO) + .build(); + } } diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java new file mode 100644 index 0000000..908c908 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java @@ -0,0 +1,69 @@ +package shop.jtoon.webtoon.application; + +import static shop.jtoon.common.ImageType.*; +import static shop.jtoon.type.ErrorStatus.*; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import lombok.RequiredArgsConstructor; + +import shop.jtoon.exception.InvalidRequestException; + +import shop.jtoon.webtoon.domain.EpisodeMainInfo; +import shop.jtoon.webtoon.domain.EpisodeSchema; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.request.CreateEpisodeReq; +import shop.jtoon.webtoon.request.GetEpisodesReq; +import shop.jtoon.webtoon.response.EpisodeInfoRes; +import shop.jtoon.webtoon.response.EpisodeItemRes; +import shop.jtoon.webtoon.service.EpisodeDomainService; + +@Service +@RequiredArgsConstructor +public class EpisodeService { + + private final WebtoonClientService webtoonClientService; + private final EpisodeDomainService episodeDomainService; + + public void createEpisode( + Long memberId, + Long webtoonId, + MultipartFile mainImage, + MultipartFile thumbnailImage, + CreateEpisodeReq request + ) { + Webtoon webtoon = episodeDomainService.readWebtoon(webtoonId, memberId, request.no()); + String mainUrl = webtoonClientService.upload(request.toUploadImageDto(EPISODE_MAIN, webtoon.getTitle(), mainImage)); + String thumbnailUrl = webtoonClientService.upload(request.toUploadImageDto( + EPISODE_THUMBNAIL, + webtoon.getTitle(), + thumbnailImage + )); + + try { + EpisodeSchema episode = request.toEpisodeSchema(); + episodeDomainService.createEpisode(episode, webtoon, mainUrl, thumbnailUrl); + } catch (RuntimeException e) { + webtoonClientService.deleteImage(mainUrl); + webtoonClientService.deleteImage(thumbnailUrl); + throw new InvalidRequestException(EPISODE_CREATE_FAIL); + } + } + + public List getEpisodes(Long webtoonId, GetEpisodesReq request) { + return EpisodeItemRes.from(episodeDomainService.readEpisodes(webtoonId, request.getSize(), request.getOffset())); + } + + public EpisodeInfoRes getEpisode(Long episodeId) { + EpisodeMainInfo episode = episodeDomainService.readEpisode(episodeId); + + return EpisodeInfoRes.from(episode); + } + + public void purchaseEpisode(Long memberId, Long episodeId) { + episodeDomainService.purchaseEpisode(memberId, episodeId); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java new file mode 100644 index 0000000..77a7420 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonClientService.java @@ -0,0 +1,37 @@ +package shop.jtoon.webtoon.application; + +import static shop.jtoon.common.ImageType.*; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.common.ImageType; +import shop.jtoon.dto.UploadImageDto; +import shop.jtoon.service.S3Service; +import shop.jtoon.webtoon.request.CreateEpisodeReq; +import shop.jtoon.webtoon.request.CreateWebtoonReq; + +@Service +@RequiredArgsConstructor +public class WebtoonClientService { + + private final S3Service s3Service; + + public String upload(ImageType imageType, CreateWebtoonReq request, MultipartFile thumbnailImage) { + UploadImageDto uploadImageDto = request.toUploadImageDto(imageType, thumbnailImage); + + return s3Service.uploadImage(uploadImageDto); + } + + public String upload(UploadImageDto uploadImageDto) { + return s3Service.uploadImage(uploadImageDto); + } + + public void deleteImage(String thumbnailUrl) { + s3Service.deleteImage(thumbnailUrl); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java new file mode 100644 index 0000000..362090a --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java @@ -0,0 +1,57 @@ +package shop.jtoon.webtoon.application; + +import static shop.jtoon.common.ImageType.*; +import static shop.jtoon.type.ErrorStatus.*; + +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.dto.UploadImageDto; + +import shop.jtoon.exception.InvalidRequestException; + +import shop.jtoon.webtoon.domain.WebtoonDetail; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; +import shop.jtoon.webtoon.request.CreateWebtoonReq; +import shop.jtoon.webtoon.request.GetWebtoonsReq; +import shop.jtoon.webtoon.response.WebtoonInfoRes; +import shop.jtoon.webtoon.response.WebtoonItemRes; +import shop.jtoon.webtoon.service.WebtoonDomainService; + +@Service +@RequiredArgsConstructor +public class WebtoonService { + + + private final WebtoonClientService webtoonClientService; + private final WebtoonDomainService webtoonDomainService; + + public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWebtoonReq request) { + webtoonDomainService.validateDuplicateTitle(request.title()); + String thumbnailUrl = webtoonClientService.upload(request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage)); + + try { + webtoonDomainService.createWebtoon(memberId, + request.toWebtoonInfo(thumbnailUrl), + request.toWebtoonGenres(), + request.toWebtoonDayOfWeeks()); + } catch (RuntimeException e) { + webtoonClientService.deleteImage(thumbnailUrl); + throw new InvalidRequestException(WEBTOON_CREATE_FAIL); + } + } + + public Map> getWebtoons(GetWebtoonsReq request) { + return WebtoonItemRes.from(webtoonDomainService.readWebtoons(request.toSearchWebtoon())); + } + + public WebtoonInfoRes getWebtoon(Long webtoonId) { + WebtoonDetail detail = webtoonDomainService.readWebtoonDetail(webtoonId); + + return WebtoonInfoRes.of(detail); + } +} diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java similarity index 90% rename from module-application/app-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java index 69255f3..d5e1028 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/presentation/WebtoonController.java @@ -16,18 +16,18 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import shop.jtoon.annotation.CurrentUser; -import shop.jtoon.dto.MemberDto; -import shop.jtoon.entity.enums.DayOfWeek; -import shop.jtoon.response.EpisodeInfoRes; -import shop.jtoon.response.EpisodeItemRes; -import shop.jtoon.response.WebtoonInfoRes; -import shop.jtoon.response.WebtoonItemRes; +import shop.jtoon.member.dto.MemberDto; import shop.jtoon.webtoon.application.EpisodeService; import shop.jtoon.webtoon.application.WebtoonService; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; import shop.jtoon.webtoon.request.CreateEpisodeReq; import shop.jtoon.webtoon.request.CreateWebtoonReq; import shop.jtoon.webtoon.request.GetEpisodesReq; import shop.jtoon.webtoon.request.GetWebtoonsReq; +import shop.jtoon.webtoon.response.EpisodeInfoRes; +import shop.jtoon.webtoon.response.EpisodeItemRes; +import shop.jtoon.webtoon.response.WebtoonInfoRes; +import shop.jtoon.webtoon.response.WebtoonItemRes; @RestController @RequiredArgsConstructor diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java similarity index 79% rename from module-application/app-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java index 7ff77de..4cd487f 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateEpisodeReq.java @@ -12,8 +12,9 @@ import shop.jtoon.common.FileName; import shop.jtoon.common.ImageType; import shop.jtoon.dto.UploadImageDto; -import shop.jtoon.entity.Episode; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.domain.EpisodeSchema; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Webtoon; @Builder public record CreateEpisodeReq( @@ -23,15 +24,12 @@ public record CreateEpisodeReq( @NotNull LocalDateTime openedAt ) { - public Episode toEntity(Webtoon webtoon, String mainUrl, String thumbnailUrl) { - return Episode.builder() + public EpisodeSchema toEpisodeSchema() { + return EpisodeSchema.builder() .no(no) .title(title) .hasComment(hasComment) .openedAt(openedAt) - .mainUrl(mainUrl) - .thumbnailUrl(thumbnailUrl) - .webtoon(webtoon) .build(); } diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java similarity index 58% rename from module-application/app-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java index 7e720d3..e6f52b8 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/CreateWebtoonReq.java @@ -12,13 +12,15 @@ import shop.jtoon.common.FileName; import shop.jtoon.common.ImageType; import shop.jtoon.dto.UploadImageDto; -import shop.jtoon.entity.DayOfWeekWebtoon; -import shop.jtoon.entity.GenreWebtoon; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.entity.enums.AgeLimit; -import shop.jtoon.entity.enums.DayOfWeek; -import shop.jtoon.entity.enums.Genre; +import shop.jtoon.webtoon.domain.WebtoonDayOfWeeks; +import shop.jtoon.webtoon.domain.WebtoonGenres; +import shop.jtoon.webtoon.domain.WebtoonInfo; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.GenreWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.AgeLimit; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; +import shop.jtoon.webtoon.entity.enums.Genre; @Builder public record CreateWebtoonReq( @@ -30,29 +32,25 @@ public record CreateWebtoonReq( @Min(0) int cookieCount ) { - public Webtoon toWebtoonEntity(Member member, String thumbnailUrl) { - return Webtoon.builder() + public WebtoonInfo toWebtoonInfo(String thumbnailUrl) { + return WebtoonInfo.builder() .title(title) .description(description) .ageLimit(ageLimit) .thumbnailUrl(thumbnailUrl) .cookieCount(cookieCount) - .author(member) .build(); } - public List toDayOfWeekWebtoonEntity(Webtoon webtoon) { - return dayOfWeeks.stream() - .map(dayOfWeek -> DayOfWeekWebtoon.create(dayOfWeek, webtoon)) - .toList(); + public WebtoonGenres toWebtoonGenres() { + return WebtoonGenres.builder().genres(genres).build(); } - public List toGenreWebtoonEntity(Webtoon webtoon) { - return genres.stream() - .map(genre -> GenreWebtoon.create(genre, webtoon)) - .toList(); + public WebtoonDayOfWeeks toWebtoonDayOfWeeks() { + return WebtoonDayOfWeeks.builder().dayOfWeeks(dayOfWeeks).build(); } + public UploadImageDto toUploadImageDto(ImageType imageType, MultipartFile image) { return UploadImageDto.builder() .imageType(imageType) diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/GetEpisodesReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/GetEpisodesReq.java similarity index 99% rename from module-application/app-api/src/main/java/shop/jtoon/webtoon/request/GetEpisodesReq.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/GetEpisodesReq.java index e32364e..d77297f 100644 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/GetEpisodesReq.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/GetEpisodesReq.java @@ -7,4 +7,5 @@ @Getter @SuperBuilder public class GetEpisodesReq extends CustomPageRequest { + } diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/GetWebtoonsReq.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/GetWebtoonsReq.java new file mode 100644 index 0000000..55d563c --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/request/GetWebtoonsReq.java @@ -0,0 +1,19 @@ +package shop.jtoon.webtoon.request; + +import lombok.Builder; +import shop.jtoon.webtoon.domain.SearchWebtoon; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; + +@Builder +public record GetWebtoonsReq( + DayOfWeek day, + String keyword +) { + + public SearchWebtoon toSearchWebtoon() { + return SearchWebtoon.builder() + .day(day) + .keyword(keyword) + .build(); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/AuthorRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/AuthorRes.java new file mode 100644 index 0000000..02e5c2a --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/AuthorRes.java @@ -0,0 +1,19 @@ +package shop.jtoon.webtoon.response; + +import lombok.Builder; +import shop.jtoon.member.entity.Member; +import shop.jtoon.webtoon.domain.Author; + +@Builder +public record AuthorRes( + Long id, + String nickname +) { + + public static AuthorRes from(Author author) { + return AuthorRes.builder() + .id(author.id()) + .nickname(author.nickname()) + .build(); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java new file mode 100644 index 0000000..fad7d6a --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeInfoRes.java @@ -0,0 +1,17 @@ +package shop.jtoon.webtoon.response; + +import lombok.Builder; +import shop.jtoon.webtoon.domain.EpisodeMainInfo; +import shop.jtoon.webtoon.entity.Episode; + +@Builder +public record EpisodeInfoRes( + String mainUrl +) { + + public static EpisodeInfoRes from(EpisodeMainInfo episode) { + return EpisodeInfoRes.builder() + .mainUrl(episode.mainUrl()) + .build(); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeItemRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeItemRes.java new file mode 100644 index 0000000..ffc7320 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeItemRes.java @@ -0,0 +1,34 @@ +package shop.jtoon.webtoon.response; + +import java.time.format.DateTimeFormatter; +import java.util.List; + +import lombok.Builder; +import shop.jtoon.webtoon.domain.SearchEpisode; +import shop.jtoon.webtoon.entity.Episode; + +@Builder +public record EpisodeItemRes( + Long episodeId, + int no, + String title, + String thumbnailUrl, + String openedAt +) { + + public static EpisodeItemRes from(SearchEpisode episode) { + return EpisodeItemRes.builder() + .episodeId(episode.episodeId()) + .no(episode.no()) + .title(episode.title()) + .thumbnailUrl(episode.thumbnailUrl()) + .openedAt(episode.openedAt()) + .build(); + } + + public static List from(List episodes) { + return episodes.stream() + .map(EpisodeItemRes::from) + .toList(); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeRes.java similarity index 86% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeRes.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeRes.java index 9144d43..a551c87 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeRes.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/EpisodeRes.java @@ -1,10 +1,10 @@ -package shop.jtoon.response; +package shop.jtoon.webtoon.response; import java.time.LocalDateTime; import lombok.Builder; -import shop.jtoon.entity.Episode; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Webtoon; @Builder public record EpisodeRes( diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/GenreRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/GenreRes.java new file mode 100644 index 0000000..af5faa6 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/GenreRes.java @@ -0,0 +1,27 @@ +package shop.jtoon.webtoon.response; + +import java.util.List; + +import lombok.Builder; +import shop.jtoon.webtoon.entity.GenreWebtoon; +import shop.jtoon.webtoon.entity.enums.Genre; + +@Builder +public record GenreRes( + Genre type, + String name +) { + + public static GenreRes from(Genre genre) { + return GenreRes.builder() + .type(genre) + .name(genre.getText()) + .build(); + } + + public static List from(List genres) { + return genres.stream() + .map(GenreRes::from) + .toList(); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonInfoRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonInfoRes.java new file mode 100644 index 0000000..2eca58b --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonInfoRes.java @@ -0,0 +1,34 @@ +package shop.jtoon.webtoon.response; + +import java.util.List; + +import lombok.Builder; +import shop.jtoon.webtoon.domain.WebtoonDetail; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.AgeLimit; + +@Builder +public record WebtoonInfoRes( + String title, + String description, + List dayOfWeeks, + List genres, + AgeLimit ageLimit, + String thumbnailUrl, + int favoriteCount, + AuthorRes author +) { + + public static WebtoonInfoRes of(WebtoonDetail webtoonDetail) { + return WebtoonInfoRes.builder() + .title(webtoonDetail.title()) + .description(webtoonDetail.description()) + .dayOfWeeks(webtoonDetail.dayOfWeeks()) + .genres(GenreRes.from(webtoonDetail.genres())) + .ageLimit(webtoonDetail.ageLimit()) + .thumbnailUrl(webtoonDetail.thumbnailUrl()) + .favoriteCount(webtoonDetail.favoriteCount()) + .author(AuthorRes.from(webtoonDetail.author())) + .build(); + } +} diff --git a/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonItemRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonItemRes.java new file mode 100644 index 0000000..3fcee98 --- /dev/null +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonItemRes.java @@ -0,0 +1,38 @@ +package shop.jtoon.webtoon.response; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.Builder; +import shop.jtoon.webtoon.domain.WebtoonSchema; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; + +@Builder +public record WebtoonItemRes( + Long webtoonId, + String title, + String thumbnailUrl, + int ageLimit, + AuthorRes author +) { + + public static WebtoonItemRes from(WebtoonSchema webtoon) { + return WebtoonItemRes.builder() + .webtoonId(webtoon.webtoonId()) + .title(webtoon.title()) + .thumbnailUrl(webtoon.thumbnailUrl()) + .ageLimit(webtoon.ageLimit()) + .author(AuthorRes.from(webtoon.author())) + .build(); + } + + public static Map> from(Map> webtoons) { + Map> webtoonRes = new HashMap<>(); + for (DayOfWeek day : webtoons.keySet()) { + webtoonRes.put(day, webtoons.get(day).stream().map(WebtoonItemRes::from).toList()); + } + + return webtoonRes; + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonRes.java b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonRes.java similarity index 81% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonRes.java rename to jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonRes.java index c41a3b2..39831db 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonRes.java +++ b/jtoon-core/core-api/src/main/java/shop/jtoon/webtoon/response/WebtoonRes.java @@ -1,11 +1,11 @@ -package shop.jtoon.response; +package shop.jtoon.webtoon.response; import java.time.LocalDateTime; import lombok.Builder; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.entity.enums.AgeLimit; +import shop.jtoon.member.entity.Member; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.AgeLimit; @Builder public record WebtoonRes( diff --git a/jtoon-core/core-api/src/main/resources/application.yml b/jtoon-core/core-api/src/main/resources/application.yml new file mode 100644 index 0000000..745d475 --- /dev/null +++ b/jtoon-core/core-api/src/main/resources/application.yml @@ -0,0 +1 @@ +spring.profiles.active: local \ No newline at end of file diff --git a/jtoon-core/core-api/src/main/resources/config b/jtoon-core/core-api/src/main/resources/config new file mode 160000 index 0000000..4992fd4 --- /dev/null +++ b/jtoon-core/core-api/src/main/resources/config @@ -0,0 +1 @@ +Subproject commit 4992fd48cf255fa80e7ee2834563f6695cc8365a diff --git a/module-application/app-api/src/main/resources/static/docs/index.html b/jtoon-core/core-api/src/main/resources/docs/index.html similarity index 100% rename from module-application/app-api/src/main/resources/static/docs/index.html rename to jtoon-core/core-api/src/main/resources/docs/index.html diff --git a/module-application/app-api/src/main/resources/static/docs/payment.html b/jtoon-core/core-api/src/main/resources/docs/payment.html similarity index 100% rename from module-application/app-api/src/main/resources/static/docs/payment.html rename to jtoon-core/core-api/src/main/resources/docs/payment.html diff --git a/module-application/app-api/src/test/resources/application-test.yml b/jtoon-core/core-api/src/test/resources/application-test.yml similarity index 100% rename from module-application/app-api/src/test/resources/application-test.yml rename to jtoon-core/core-api/src/test/resources/application-test.yml diff --git a/module-application/app-api/src/test/resources/test.png b/jtoon-core/core-api/src/test/resources/test.png similarity index 100% rename from module-application/app-api/src/test/resources/test.png rename to jtoon-core/core-api/src/test/resources/test.png diff --git a/jtoon-core/core-domain/build.gradle b/jtoon-core/core-domain/build.gradle new file mode 100644 index 0000000..1a777e9 --- /dev/null +++ b/jtoon-core/core-domain/build.gradle @@ -0,0 +1,40 @@ +def generatedDir = "src/main/generated" + +sourceSets { + main { + java { + srcDirs = ['src/main/java'] + } + + resources { + srcDir "${project.projectDir}/src/main/java" + } + } +} + +clean { + delete file(generatedDir) +} + +dependencies { + + implementation project(":jtoon-system") + + // Password Encoder + implementation 'org.springframework.security:spring-security-crypto' + + // JPA + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + // H2 + implementation 'com.h2database:h2' + + // MySQL + implementation 'com.mysql:mysql-connector-j:8.0.33' + + // Querydsl + api 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' + annotationProcessor 'jakarta.annotation:jakarta.annotation-api' + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' +} \ No newline at end of file diff --git a/module-domain/domain-jpa/src/main/java/shop/jtoon/entity/BaseTimeEntity.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/common/BaseTimeEntity.java similarity index 96% rename from module-domain/domain-jpa/src/main/java/shop/jtoon/entity/BaseTimeEntity.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/common/BaseTimeEntity.java index 8b4895b..d817c5b 100644 --- a/module-domain/domain-jpa/src/main/java/shop/jtoon/entity/BaseTimeEntity.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/common/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.common; import java.time.LocalDateTime; diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/config/DbConfig.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/config/DbConfig.java new file mode 100644 index 0000000..39d5685 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/config/DbConfig.java @@ -0,0 +1,24 @@ +package shop.jtoon.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +@Configuration +public class DbConfig { + + @Bean + @ConfigurationProperties(prefix = "jtoon-db.datasource.main") + public HikariConfig mainHikariConfig() { + return new HikariConfig(); + } + + @Bean + public HikariDataSource hikariDataSource(@Qualifier("mainHikariConfig") HikariConfig mainHikariConfig) { + return new HikariDataSource(mainHikariConfig); + } +} diff --git a/module-domain/domain-jpa/src/main/java/shop/jtoon/config/JpaConfig.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/config/JpaConfig.java similarity index 100% rename from module-domain/domain-jpa/src/main/java/shop/jtoon/config/JpaConfig.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/config/JpaConfig.java diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/login/domain/LoginInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/domain/LoginInfo.java new file mode 100644 index 0000000..fb04acb --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/domain/LoginInfo.java @@ -0,0 +1,20 @@ +package shop.jtoon.login.domain; + +import lombok.Builder; +import shop.jtoon.member.entity.LoginType; + +@Builder +public record LoginInfo( + String email, + String password, + LoginType loginType +) { + + public LoginInfo encode(String encodedPassword) { + return LoginInfo.builder() + .email(email) + .password(encodedPassword) + .loginType(loginType) + .build(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/login/domain/UserInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/domain/UserInfo.java new file mode 100644 index 0000000..f7ab0f8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/domain/UserInfo.java @@ -0,0 +1,14 @@ +package shop.jtoon.login.domain; + +import lombok.Builder; +import shop.jtoon.member.entity.Gender; + +@Builder +public record UserInfo( + String name, + String nickname, + Gender gender, + String phone +) { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/CrytoService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/CrytoService.java new file mode 100644 index 0000000..1a7305f --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/CrytoService.java @@ -0,0 +1,21 @@ +package shop.jtoon.login.service; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class CrytoService { + + private final PasswordEncoder passwordEncoder; + + public String encoded(String originPassword) { + return passwordEncoder.encode(originPassword); + } + + public boolean passwordMatch(String requestPassword, String encodedPassword) { + return passwordEncoder.matches(requestPassword, encodedPassword); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginDomainService.java new file mode 100644 index 0000000..03025d8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginDomainService.java @@ -0,0 +1,50 @@ +package shop.jtoon.login.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; + +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.member.domain.MyInfo; +import shop.jtoon.login.domain.UserInfo; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.repository.MemberReader; +import shop.jtoon.member.repository.MemberWriter; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class LoginDomainService { + + private final LoginManager loginManager; + private final MemberReader memberReader; + private final MemberWriter memberWriter; + + @Transactional + public void signUp(LoginInfo loginInfo, UserInfo userInfo) { + Member member = loginManager.signUp(loginInfo, userInfo); + memberWriter.write(member); + } + + @Transactional + public void login(LoginInfo loginInfo) { + Member member = memberReader.readLoginRequestMember(loginInfo); + member.updateLastLogin(); + } + + @Transactional + public Member generateOrGetSocialMember(LoginInfo loginInfo, UserInfo userInfo) { + Optional member = memberReader.readOptionalByEmail(loginInfo.email()); + + return member.orElseGet(() -> memberWriter.signUp(loginInfo, userInfo)); + } + + public MyInfo findMemberDtoByEmail(String email) { + Member member = memberReader.readByEmail(email); + + return MyInfo.of(member); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginManager.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginManager.java new file mode 100644 index 0000000..ba5e249 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginManager.java @@ -0,0 +1,32 @@ +package shop.jtoon.login.service; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.DuplicatedException; +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.login.domain.UserInfo; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.repository.MemberReader; +import shop.jtoon.type.ErrorStatus; + +@Service +@RequiredArgsConstructor +public class LoginManager { + + private final CrytoService crytoService; + private final MemberReader memberReader; + + public Member signUp(LoginInfo loginInfo, UserInfo userInfo) { + LoginInfo encodedLoginInfo = loginInfo.encode(crytoService.encoded(loginInfo.password())); + validateDuplicateEmail(encodedLoginInfo.email()); + + return LoginMapper.toMember(loginInfo, userInfo); + } + + private void validateDuplicateEmail(String email) { + if (memberReader.readMemberExist(email)) { + throw new DuplicatedException(ErrorStatus.MEMBER_EMAIL_CONFLICT); + } + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginMapper.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginMapper.java new file mode 100644 index 0000000..1a0cbaf --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/login/service/LoginMapper.java @@ -0,0 +1,25 @@ +package shop.jtoon.login.service; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.login.domain.UserInfo; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.entity.Role; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LoginMapper { + + public static Member toMember(LoginInfo loginInfo, UserInfo userInfo) { + return Member.builder() + .email(loginInfo.email()) + .password(loginInfo.password()) + .loginType(loginInfo.loginType()) + .name(userInfo.name()) + .nickname(userInfo.nickname()) + .gender(userInfo.gender()) + .phone(userInfo.phone()) + .role(Role.USER) + .build(); + } +} diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/dto/MemberDto.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/domain/MyInfo.java similarity index 59% rename from module-domain/domain-member/src/main/java/shop/jtoon/dto/MemberDto.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/domain/MyInfo.java index 7ddf941..644e879 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/dto/MemberDto.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/domain/MyInfo.java @@ -1,12 +1,12 @@ -package shop.jtoon.dto; +package shop.jtoon.member.domain; import lombok.Builder; -import shop.jtoon.entity.Gender; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Role; +import shop.jtoon.member.entity.Gender; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.entity.Role; @Builder -public record MemberDto( +public record MyInfo( Long id, String email, String name, @@ -15,8 +15,9 @@ public record MemberDto( Role role, String phone ) { - public static MemberDto toDto(Member member) { - return MemberDto.builder() + + public static MyInfo of(Member member) { + return MyInfo.builder() .id(member.getId()) .email(member.getEmail()) .name(member.getName()) diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/entity/Gender.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Gender.java similarity index 96% rename from module-domain/domain-member/src/main/java/shop/jtoon/entity/Gender.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Gender.java index 3daf22c..5448881 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/entity/Gender.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Gender.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.member.entity; import java.util.Arrays; import java.util.Collections; diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/entity/LoginType.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/LoginType.java similarity index 96% rename from module-domain/domain-member/src/main/java/shop/jtoon/entity/LoginType.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/LoginType.java index 66b8456..ba01968 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/entity/LoginType.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/LoginType.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.member.entity; import java.util.Arrays; import java.util.Collections; diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/entity/Member.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Member.java similarity index 73% rename from module-domain/domain-member/src/main/java/shop/jtoon/entity/Member.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Member.java index 2889468..8b4059d 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/entity/Member.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Member.java @@ -1,7 +1,7 @@ -package shop.jtoon.entity; +package shop.jtoon.member.entity; import static java.util.Objects.*; -import static shop.jtoon.util.RegExp.*; +import static shop.jtoon.type.ErrorStatus.*; import java.time.LocalDateTime; import java.util.Objects; @@ -14,12 +14,11 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import jakarta.validation.constraints.Pattern; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import shop.jtoon.type.ErrorStatus; +import shop.jtoon.common.BaseTimeEntity; @Entity @Getter @@ -32,7 +31,6 @@ public class Member extends BaseTimeEntity { @Column(name = "member_id") private Long id; - @Pattern(regexp = EMAIL_PATTERN) @Column(name = "email", nullable = false, unique = true, length = 40, updatable = false) private String email; @@ -49,7 +47,6 @@ public class Member extends BaseTimeEntity { @Column(name = "gender", nullable = false, updatable = false) private Gender gender; - @Pattern(regexp = PHONE_PATTERN) @Column(name = "phone", nullable = false, unique = true, length = 11) private String phone; @@ -79,12 +76,12 @@ private Member( Role role, LoginType loginType ) { - this.email = requireNonNull(email, ErrorStatus.MEMBER_EMAIL_INVALID_FORMAT.getMessage()); - this.password = requireNonNull(password, ErrorStatus.MEMBER_PASSWORD_INVALID_FORMAT.getMessage()); - this.name = requireNonNull(name, ErrorStatus.MEMBER_NAME_INVALID_FORMAT.getMessage()); - this.nickname = requireNonNull(nickname, ErrorStatus.MEMBER_NICKNAME_INVALID_FORMAT.getMessage()); - this.gender = requireNonNull(gender, ErrorStatus.MEMBER_GENDER_INVALID_FORMAT.getMessage()); - this.phone = requireNonNull(phone, ErrorStatus.MEMBER_PHONE_INVALID_FORMAT.getMessage()); + this.email = requireNonNull(email, MEMBER_EMAIL_INVALID_FORMAT.getMessage()); + this.password = requireNonNull(password, MEMBER_PASSWORD_INVALID_FORMAT.getMessage()); + this.name = requireNonNull(name, MEMBER_NAME_INVALID_FORMAT.getMessage()); + this.nickname = requireNonNull(nickname, MEMBER_NICKNAME_INVALID_FORMAT.getMessage()); + this.gender = requireNonNull(gender, MEMBER_GENDER_INVALID_FORMAT.getMessage()); + this.phone = requireNonNull(phone, MEMBER_PHONE_INVALID_FORMAT.getMessage()); this.role = role; this.loginType = loginType; } diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/entity/MemberStatus.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/MemberStatus.java similarity index 83% rename from module-domain/domain-member/src/main/java/shop/jtoon/entity/MemberStatus.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/MemberStatus.java index 386b8b2..79e5673 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/entity/MemberStatus.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/MemberStatus.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.member.entity; import lombok.AccessLevel; import lombok.AllArgsConstructor; diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/entity/Role.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Role.java similarity index 81% rename from module-domain/domain-member/src/main/java/shop/jtoon/entity/Role.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Role.java index c23d1be..eec8ff5 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/entity/Role.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/entity/Role.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.member.entity; import lombok.AccessLevel; import lombok.AllArgsConstructor; diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberReader.java new file mode 100644 index 0000000..527bc7b --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberReader.java @@ -0,0 +1,57 @@ +package shop.jtoon.member.repository; + +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.login.service.CrytoService; +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.member.entity.LoginType; +import shop.jtoon.member.entity.Member; +import shop.jtoon.type.ErrorStatus; + +@Repository +@RequiredArgsConstructor +public class MemberReader { + + private final CrytoService crytoService; + private final MemberRepository memberRepository; + + public Member read(Long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MEMBER_NOT_FOUND)); + } + + public Optional readOptionalByEmail(String email) { + return memberRepository.findByEmail(email); + } + + public Member readByEmail(String email) { + return memberRepository.findByEmail(email) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MEMBER_EMAIL_NOT_FOUND)); + } + + public boolean readMemberExist(String email) { + return memberRepository.findByEmail(email).isPresent(); + } + + public Member readLoginRequestMember(LoginInfo loginInfo) { + Member member = readByEmail(loginInfo.email()); + validateLoginRequest(loginInfo, member); + + return member; + } + + private void validateLoginRequest(LoginInfo loginInfo, Member member) { + if (!crytoService.passwordMatch(loginInfo.password(), member.getPassword())) { + throw new InvalidRequestException(ErrorStatus.MEMBER_WRONG_LOGIN_INFO); + } + + if (!member.getLoginType().equals(LoginType.LOCAL)) { + throw new InvalidRequestException(ErrorStatus.MEMBER_DUPLICATE_SOCIAL_LOGIN); + } + } +} diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/repository/MemberRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberRepository.java similarity index 72% rename from module-domain/domain-member/src/main/java/shop/jtoon/repository/MemberRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberRepository.java index 3eec032..5ff8b1e 100644 --- a/module-domain/domain-member/src/main/java/shop/jtoon/repository/MemberRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberRepository.java @@ -1,9 +1,10 @@ -package shop.jtoon.repository; +package shop.jtoon.member.repository; + +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; -import shop.jtoon.entity.Member; +import shop.jtoon.member.entity.Member; public interface MemberRepository extends JpaRepository { Optional findByEmail(String email); diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberWriter.java new file mode 100644 index 0000000..81a6e7e --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/repository/MemberWriter.java @@ -0,0 +1,26 @@ +package shop.jtoon.member.repository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.login.service.LoginMapper; +import shop.jtoon.login.domain.LoginInfo; +import shop.jtoon.login.domain.UserInfo; +import shop.jtoon.member.entity.Member; + +@Repository +@RequiredArgsConstructor +public class MemberWriter { + + private final MemberRepository memberRepository; + + public void write(Member member) { + memberRepository.save(member); + } + + public Member signUp(LoginInfo loginInfo, UserInfo userInfo) { + Member member = LoginMapper.toMember(loginInfo, userInfo); + + return memberRepository.save(member); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/member/service/MemberService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/service/MemberService.java new file mode 100644 index 0000000..e2fb89a --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/member/service/MemberService.java @@ -0,0 +1,19 @@ +package shop.jtoon.member.service; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.domain.MyInfo; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.repository.MemberReader; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberReader memberReader; + + public Member read(String email) { + return memberReader.readByEmail(email); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Buyer.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Buyer.java new file mode 100644 index 0000000..56af5f8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Buyer.java @@ -0,0 +1,12 @@ +package shop.jtoon.payment.domain; + +import lombok.Builder; + +@Builder +public record Buyer( + String buyerEmail, + String buyerName, + String buyerPhone +) { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/CancelPayInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/CancelPayInfo.java new file mode 100644 index 0000000..6216b7e --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/CancelPayInfo.java @@ -0,0 +1,16 @@ +package shop.jtoon.payment.domain; + +import java.math.BigDecimal; + +import lombok.Builder; + +@Builder +public record CancelPayInfo( + String impUid, + String merchantUid, + String reason, + BigDecimal checksum, + String refundHolder +) { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Item.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Item.java new file mode 100644 index 0000000..cc780bd --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Item.java @@ -0,0 +1,13 @@ +package shop.jtoon.payment.domain; + +import java.math.BigDecimal; + +import lombok.Builder; + +@Builder +public record Item( + String itemName, + BigDecimal amount +) { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Pay.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Pay.java new file mode 100644 index 0000000..4229e92 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Pay.java @@ -0,0 +1,12 @@ +package shop.jtoon.payment.domain; + +import lombok.Builder; + +@Builder +public record Pay( + String impUid, + String merchantUid, + String payMethod +) { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Receipt.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Receipt.java new file mode 100644 index 0000000..5482da5 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/domain/Receipt.java @@ -0,0 +1,24 @@ +package shop.jtoon.payment.domain; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import lombok.Builder; +import shop.jtoon.payment.entity.PaymentInfo; + +@Builder +public record Receipt( + String itemName, + int itemCount, + BigDecimal amount, + LocalDateTime createdAt +) { + public static Receipt toReceipt(PaymentInfo paymentInfo) { + return Receipt.builder() + .itemName(paymentInfo.getCookieItem().getItemName()) + .itemCount(paymentInfo.getCookieItem().getCount()) + .amount(paymentInfo.getAmount()) + .createdAt(paymentInfo.getCreatedAt()) + .build(); + } +} diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/entity/CookieItem.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/CookieItem.java similarity index 97% rename from module-domain/domain-payment/src/main/java/shop/jtoon/entity/CookieItem.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/CookieItem.java index 2260be8..29e5897 100644 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/entity/CookieItem.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/CookieItem.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.payment.entity; import java.math.BigDecimal; import java.util.Arrays; diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/entity/MemberCookie.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/MemberCookie.java similarity index 93% rename from module-domain/domain-payment/src/main/java/shop/jtoon/entity/MemberCookie.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/MemberCookie.java index 1a46698..92b3b29 100644 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/entity/MemberCookie.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/MemberCookie.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.payment.entity; import static java.util.Objects.*; import static shop.jtoon.type.ErrorStatus.*; @@ -17,6 +17,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.member.entity.Member; @Entity @Getter diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/entity/PaymentInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/PaymentInfo.java similarity index 78% rename from module-domain/domain-payment/src/main/java/shop/jtoon/entity/PaymentInfo.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/PaymentInfo.java index 1cde93e..2df8419 100644 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/entity/PaymentInfo.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/entity/PaymentInfo.java @@ -1,16 +1,28 @@ -package shop.jtoon.entity; +package shop.jtoon.payment.entity; -import jakarta.persistence.*; +import static java.util.Objects.*; + +import java.math.BigDecimal; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.member.entity.Member; import shop.jtoon.type.ErrorStatus; -import java.math.BigDecimal; - -import static java.util.Objects.requireNonNull; - @Getter @Entity @Table(name = "payments_info") diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/CookieReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/CookieReader.java new file mode 100644 index 0000000..e30f87f --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/CookieReader.java @@ -0,0 +1,21 @@ +package shop.jtoon.payment.repository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.entity.MemberCookie; +import shop.jtoon.type.ErrorStatus; + +@Repository +@RequiredArgsConstructor +public class CookieReader { + + private final MemberCookieRepository memberCookieRepository; + + public MemberCookie read(Member member) { + return memberCookieRepository.findByMember(member) + .orElseThrow(() -> new NotFoundException(ErrorStatus.MEMBER_COOKIE_NOT_FOUND)); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/CookieWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/CookieWriter.java new file mode 100644 index 0000000..f2fe0e1 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/CookieWriter.java @@ -0,0 +1,18 @@ +package shop.jtoon.payment.repository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.entity.MemberCookie; + +@Repository +@RequiredArgsConstructor +public class CookieWriter { + + private final MemberCookieRepository memberCookieRepository; + + public void write(MemberCookie memberCookie) { + memberCookieRepository.save(memberCookie); + } +} diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/repository/MemberCookieRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/MemberCookieRepository.java similarity index 70% rename from module-domain/domain-payment/src/main/java/shop/jtoon/repository/MemberCookieRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/MemberCookieRepository.java index 57b836a..48bc13c 100644 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/repository/MemberCookieRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/MemberCookieRepository.java @@ -1,11 +1,12 @@ -package shop.jtoon.repository; +package shop.jtoon.payment.repository; + +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.MemberCookie; -import java.util.Optional; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.entity.MemberCookie; @Repository public interface MemberCookieRepository extends JpaRepository { diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/repository/PaymentInfoRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentInfoRepository.java similarity index 78% rename from module-domain/domain-payment/src/main/java/shop/jtoon/repository/PaymentInfoRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentInfoRepository.java index 75227f5..848171a 100644 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/repository/PaymentInfoRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentInfoRepository.java @@ -1,9 +1,9 @@ -package shop.jtoon.repository; +package shop.jtoon.payment.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import shop.jtoon.entity.PaymentInfo; +import shop.jtoon.payment.entity.PaymentInfo; @Repository public interface PaymentInfoRepository extends JpaRepository { diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/repository/PaymentInfoSearchRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentInfoSearchRepository.java similarity index 55% rename from module-domain/domain-payment/src/main/java/shop/jtoon/repository/PaymentInfoSearchRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentInfoSearchRepository.java index 223a20d..172bf7a 100644 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/repository/PaymentInfoSearchRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentInfoSearchRepository.java @@ -1,15 +1,16 @@ -package shop.jtoon.repository; +package shop.jtoon.payment.repository; + +import java.util.List; -import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import shop.jtoon.entity.PaymentInfo; -import shop.jtoon.util.DynamicQuery; -import java.util.List; +import com.querydsl.jpa.impl.JPAQueryFactory; -import static shop.jtoon.entity.QMember.*; -import static shop.jtoon.entity.QPaymentInfo.*; +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.entity.QMember; +import shop.jtoon.payment.entity.PaymentInfo; +import shop.jtoon.payment.entity.QPaymentInfo; +import shop.jtoon.util.DynamicQuery; @Repository @RequiredArgsConstructor @@ -18,11 +19,11 @@ public class PaymentInfoSearchRepository { private final JPAQueryFactory queryFactory; public List searchByMerchantsUidAndEmail(List merchantsUid, String email) { - return queryFactory.selectFrom(paymentInfo) - .join(paymentInfo.member, member).fetchJoin() + return queryFactory.selectFrom(QPaymentInfo.paymentInfo) + .join(QPaymentInfo.paymentInfo.member, QMember.member).fetchJoin() .where( - DynamicQuery.filterCondition(merchantsUid, paymentInfo.merchantUid::in), - DynamicQuery.generateEq(email, paymentInfo.member.email::eq) + DynamicQuery.filterCondition(merchantsUid, QPaymentInfo.paymentInfo.merchantUid::in), + DynamicQuery.generateEq(email, QPaymentInfo.paymentInfo.member.email::eq) ) .fetch(); } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentReader.java new file mode 100644 index 0000000..64f77b0 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentReader.java @@ -0,0 +1,28 @@ +package shop.jtoon.payment.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.payment.entity.PaymentInfo; + +@Repository +@RequiredArgsConstructor +public class PaymentReader { + + private final PaymentInfoRepository paymentInfoRepository; + private final PaymentInfoSearchRepository paymentInfoSearchRepository; + + public boolean existsImpUid(String impUid) { + return paymentInfoRepository.existsByImpUid(impUid); + } + + public boolean existsMerchantUid(String merchantUid) { + return paymentInfoRepository.existsByMerchantUid(merchantUid); + } + + public List searchByMerchantsUidAndEmail(List merchantsUid, String email) { + return paymentInfoSearchRepository.searchByMerchantsUidAndEmail(merchantsUid, email); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentWriter.java new file mode 100644 index 0000000..6b6f2ce --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/repository/PaymentWriter.java @@ -0,0 +1,17 @@ +package shop.jtoon.payment.repository; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.payment.entity.PaymentInfo; + +@Repository +@RequiredArgsConstructor +public class PaymentWriter { + + private final PaymentInfoRepository paymentInfoRepository; + + public void write(final PaymentInfo paymentInfo) { + paymentInfoRepository.save(paymentInfo); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/CookieManager.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/CookieManager.java new file mode 100644 index 0000000..32d9626 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/CookieManager.java @@ -0,0 +1,34 @@ +package shop.jtoon.payment.service; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.domain.Item; +import shop.jtoon.payment.entity.CookieItem; +import shop.jtoon.payment.entity.MemberCookie; +import shop.jtoon.payment.repository.CookieReader; +import shop.jtoon.payment.repository.CookieWriter; +import shop.jtoon.webtoon.entity.PurchasedEpisode; + +@Service +@RequiredArgsConstructor +public class CookieManager { + + private final CookieWriter cookieWriter; + private final CookieReader cookieReader; + + public void bakeCookie(Item item, Member member) { + CookieItem cookie = CookieItem.from(item.itemName()); + MemberCookie memberCookie = MemberCookie.create(cookie.getCount(), member); + cookieWriter.write(memberCookie); + } + + public int useCookie(int cookieCount, Member member) { + MemberCookie memberCookie = cookieReader.read(member); + memberCookie.decreaseCookieCount(cookieCount); + + return memberCookie.getCookieCount(); + } + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PayMapper.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PayMapper.java new file mode 100644 index 0000000..8e1c027 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PayMapper.java @@ -0,0 +1,25 @@ +package shop.jtoon.payment.service; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.domain.Buyer; +import shop.jtoon.payment.domain.Item; +import shop.jtoon.payment.domain.Pay; +import shop.jtoon.payment.entity.CookieItem; +import shop.jtoon.payment.entity.PaymentInfo; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PayMapper { + + public static PaymentInfo toPaymentInfo(Pay pay, Item item, Member member) { + return PaymentInfo.builder() + .impUid(pay.impUid()) + .merchantUid(pay.merchantUid()) + .payMethod(pay.payMethod()) + .cookieItem(CookieItem.from(item.itemName())) + .amount(item.amount()) + .member(member) + .build(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PayService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PayService.java new file mode 100644 index 0000000..d76277a --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PayService.java @@ -0,0 +1,55 @@ +package shop.jtoon.payment.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.repository.MemberReader; +import shop.jtoon.payment.domain.Item; +import shop.jtoon.payment.domain.Pay; +import shop.jtoon.payment.domain.Receipt; +import shop.jtoon.payment.entity.PaymentInfo; +import shop.jtoon.payment.repository.PaymentReader; +import shop.jtoon.payment.repository.PaymentWriter; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class PayService { + + private final MemberReader memberReader; + private final PaymentManager paymentManager; + private final CookieManager cookieManager; + private final PaymentReader paymentReader; + private final PaymentWriter paymentWriter; + + @Transactional + public void createPaymentInfo(Pay pay, Item item, Member member) { + createPayInfo(pay, item, member); + createCookie(item, member); + } + + public List readPayments(List merchantsUid, String email) { + Member member = memberReader.readByEmail(email); + List paymentInfos = paymentReader.searchByMerchantsUidAndEmail( + merchantsUid, + member.getEmail() + ); + + return paymentInfos.stream() + .map(Receipt::toReceipt) + .toList(); + } + + private void createPayInfo(Pay pay, Item item, Member member) { + paymentManager.validatePayItem(pay, item); + paymentWriter.write(PayMapper.toPaymentInfo(pay, item, member)); + } + + private void createCookie(Item item, Member member) { + cookieManager.bakeCookie(item, member); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PaymentManager.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PaymentManager.java new file mode 100644 index 0000000..a003cce --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/payment/service/PaymentManager.java @@ -0,0 +1,50 @@ +package shop.jtoon.payment.service; + +import java.math.BigDecimal; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.DuplicatedException; +import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.payment.domain.Item; +import shop.jtoon.payment.domain.Pay; +import shop.jtoon.payment.entity.CookieItem; +import shop.jtoon.payment.repository.PaymentReader; +import shop.jtoon.type.ErrorStatus; + +@Service +@RequiredArgsConstructor +public class PaymentManager { + + private final PaymentReader paymentReader; + + public void validatePayItem(Pay pay, Item item) { + validatePaymentInfo(item.itemName(), item.amount()); + validateImpUid(pay.impUid()); + validateMerchantUid(pay.merchantUid()); + } + + public void validatePaymentInfo(String itemName, BigDecimal amount) { + CookieItem cookieItem = CookieItem.from(itemName); + validateAmount(amount, cookieItem.getAmount()); + } + + private void validateAmount(BigDecimal amount, BigDecimal cookieAmount) { + if (!amount.equals(cookieAmount)) { + throw new InvalidRequestException(ErrorStatus.PAYMENT_AMOUNT_INVALID); + } + } + + private void validateImpUid(String impUid) { + if (paymentReader.existsImpUid(impUid)) { + throw new DuplicatedException(ErrorStatus.PAYMENT_IMP_UID_DUPLICATED); + } + } + + private void validateMerchantUid(String merchantUid) { + if (paymentReader.existsMerchantUid(merchantUid)) { + throw new DuplicatedException(ErrorStatus.PAYMENT_MERCHANT_UID_DUPLICATED); + } + } +} diff --git a/module-domain/domain-jpa/src/main/java/shop/jtoon/util/DynamicQuery.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/util/DynamicQuery.java similarity index 100% rename from module-domain/domain-jpa/src/main/java/shop/jtoon/util/DynamicQuery.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/util/DynamicQuery.java diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/Author.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/Author.java new file mode 100644 index 0000000..5fdfa6d --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/Author.java @@ -0,0 +1,18 @@ +package shop.jtoon.webtoon.domain; + +import lombok.Builder; +import shop.jtoon.member.entity.Member; + +@Builder +public record Author( + Long id, + String nickname +) { + + public static Author from(Member author) { + return Author.builder() + .id(author.getId()) + .nickname(author.getNickname()) + .build(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java new file mode 100644 index 0000000..c21db2b --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeMainInfo.java @@ -0,0 +1,16 @@ +package shop.jtoon.webtoon.domain; + +import lombok.Builder; +import shop.jtoon.webtoon.entity.Episode; + +@Builder +public record EpisodeMainInfo( + String mainUrl +) { + + public static EpisodeMainInfo of(Episode episode) { + return EpisodeMainInfo.builder() + .mainUrl(episode.getMainUrl()) + .build(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java new file mode 100644 index 0000000..b2d2faf --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/EpisodeSchema.java @@ -0,0 +1,29 @@ +package shop.jtoon.webtoon.domain; + +import java.time.LocalDateTime; + +import org.antlr.v4.runtime.misc.NotNull; + +import lombok.Builder; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Webtoon; + +@Builder +public record EpisodeSchema( + int no, + String title, + boolean hasComment, + LocalDateTime openedAt +) { + public Episode toEpisode(Webtoon webtoon, String mainUrl, String thumbnailUrl) { + return Episode.builder() + .no(no) + .title(title) + .hasComment(hasComment) + .openedAt(openedAt) + .mainUrl(mainUrl) + .thumbnailUrl(thumbnailUrl) + .webtoon(webtoon) + .build(); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeItemRes.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/SearchEpisode.java similarity index 67% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeItemRes.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/SearchEpisode.java index 0453fa2..f84038f 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeItemRes.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/SearchEpisode.java @@ -1,21 +1,20 @@ -package shop.jtoon.response; +package shop.jtoon.webtoon.domain; import java.time.format.DateTimeFormatter; import lombok.Builder; -import shop.jtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Episode; @Builder -public record EpisodeItemRes( +public record SearchEpisode( Long episodeId, int no, String title, String thumbnailUrl, String openedAt ) { - - public static EpisodeItemRes from(Episode episode) { - return EpisodeItemRes.builder() + public static SearchEpisode from(Episode episode) { + return SearchEpisode.builder() .episodeId(episode.getId()) .no(episode.getNo()) .title(episode.getTitle()) diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/SearchWebtoon.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/SearchWebtoon.java new file mode 100644 index 0000000..8a07244 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/SearchWebtoon.java @@ -0,0 +1,12 @@ +package shop.jtoon.webtoon.domain; + +import lombok.Builder; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; + +@Builder +public record SearchWebtoon( + DayOfWeek day, + String keyword +) { + +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonDayOfWeeks.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonDayOfWeeks.java new file mode 100644 index 0000000..8d1dde2 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonDayOfWeeks.java @@ -0,0 +1,20 @@ +package shop.jtoon.webtoon.domain; + +import java.util.List; +import java.util.Set; + +import lombok.Builder; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; + +@Builder +public record WebtoonDayOfWeeks( + Set dayOfWeeks +) { + public List toDayOfWeekWebtoonEntity(Webtoon webtoon) { + return dayOfWeeks.stream() + .map(dayOfWeek -> DayOfWeekWebtoon.create(dayOfWeek, webtoon)) + .toList(); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonInfoRes.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonDetail.java similarity index 52% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonInfoRes.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonDetail.java index b72b46d..777b641 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonInfoRes.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonDetail.java @@ -1,25 +1,26 @@ -package shop.jtoon.response; +package shop.jtoon.webtoon.domain; import java.util.List; import lombok.Builder; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.entity.enums.AgeLimit; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.AgeLimit; +import shop.jtoon.webtoon.entity.enums.Genre; @Builder -public record WebtoonInfoRes( +public record WebtoonDetail( String title, String description, List dayOfWeeks, - List genres, + List genres, AgeLimit ageLimit, String thumbnailUrl, int favoriteCount, - AuthorRes author + Author author ) { - public static WebtoonInfoRes of(Webtoon webtoon, List dayOfWeeks, List genres) { - return WebtoonInfoRes.builder() + public static WebtoonDetail of(Webtoon webtoon, List dayOfWeeks, List genres) { + return WebtoonDetail.builder() .title(webtoon.getTitle()) .description(webtoon.getDescription()) .dayOfWeeks(dayOfWeeks) @@ -27,7 +28,8 @@ public static WebtoonInfoRes of(Webtoon webtoon, List dayOfWeeks, List genres +) { + public List toGenreWebtoonEntity(Webtoon webtoon) { + return genres.stream() + .map(genre -> GenreWebtoon.create(genre, webtoon)) + .toList(); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonInfo.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonInfo.java new file mode 100644 index 0000000..53b6ac1 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonInfo.java @@ -0,0 +1,26 @@ +package shop.jtoon.webtoon.domain; + +import lombok.Builder; +import shop.jtoon.member.entity.Member; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.AgeLimit; + +@Builder +public record WebtoonInfo( + String title, + String description, + String thumbnailUrl, + AgeLimit ageLimit, + int cookieCount +) { + public Webtoon toWebtoonEntity(Member member) { + return Webtoon.builder() + .title(title) + .description(description) + .ageLimit(ageLimit) + .thumbnailUrl(thumbnailUrl) + .cookieCount(cookieCount) + .author(member) + .build(); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonItemRes.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonSchema.java similarity index 52% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonItemRes.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonSchema.java index fc078d9..cec10f1 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/WebtoonItemRes.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/domain/WebtoonSchema.java @@ -1,24 +1,23 @@ -package shop.jtoon.response; +package shop.jtoon.webtoon.domain; import lombok.Builder; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.Webtoon; @Builder -public record WebtoonItemRes( +public record WebtoonSchema( Long webtoonId, String title, String thumbnailUrl, int ageLimit, - AuthorRes author + Author author ) { - - public static WebtoonItemRes from(Webtoon webtoon) { - return WebtoonItemRes.builder() + public static WebtoonSchema from(Webtoon webtoon) { + return WebtoonSchema.builder() .webtoonId(webtoon.getId()) .title(webtoon.getTitle()) .thumbnailUrl(webtoon.getThumbnailUrl()) .ageLimit(webtoon.getAgeLimit().getValue()) - .author(AuthorRes.from(webtoon.getAuthor())) + .author(Author.from(webtoon.getAuthor())) .build(); } } diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/DayOfWeekWebtoon.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/DayOfWeekWebtoon.java similarity index 92% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/DayOfWeekWebtoon.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/DayOfWeekWebtoon.java index b7ff7c3..68bf7ba 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/DayOfWeekWebtoon.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/DayOfWeekWebtoon.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.webtoon.entity; import static java.util.Objects.*; import static shop.jtoon.type.ErrorStatus.*; @@ -18,7 +18,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import shop.jtoon.entity.enums.DayOfWeek; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; @Entity @Getter diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/Episode.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Episode.java similarity index 97% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/Episode.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Episode.java index dcedc21..a45bdc2 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/Episode.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Episode.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.webtoon.entity; import static java.util.Objects.*; import static shop.jtoon.type.ErrorStatus.*; @@ -22,6 +22,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.common.BaseTimeEntity; @Entity @Getter diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/GenreWebtoon.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/GenreWebtoon.java similarity index 87% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/GenreWebtoon.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/GenreWebtoon.java index 6a58be2..9bdde2d 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/GenreWebtoon.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/GenreWebtoon.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.webtoon.entity; import static java.util.Objects.*; import static shop.jtoon.type.ErrorStatus.*; @@ -18,7 +18,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import shop.jtoon.entity.enums.Genre; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.webtoon.entity.enums.Genre; @Entity @Getter @@ -46,9 +47,6 @@ private GenreWebtoon(Genre genre, Webtoon webtoon) { } public static GenreWebtoon create(Genre genre, Webtoon webtoon) { - return GenreWebtoon.builder() - .genre(genre) - .webtoon(webtoon) - .build(); + return GenreWebtoon.builder().genre(genre).webtoon(webtoon).build(); } } diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/PurchasedEpisode.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/PurchasedEpisode.java similarity index 92% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/PurchasedEpisode.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/PurchasedEpisode.java index c33739d..ea7010e 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/PurchasedEpisode.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/PurchasedEpisode.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.webtoon.entity; import static java.util.Objects.*; import static shop.jtoon.type.ErrorStatus.*; @@ -16,6 +16,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.member.entity.Member; @Entity @Getter diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/Webtoon.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java similarity index 93% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/Webtoon.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java index 9b5f79f..61daa8d 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/Webtoon.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/Webtoon.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.webtoon.entity; import static java.util.Objects.*; import static shop.jtoon.type.ErrorStatus.*; @@ -16,8 +16,10 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import shop.jtoon.entity.enums.AgeLimit; import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.common.BaseTimeEntity; +import shop.jtoon.member.entity.Member; +import shop.jtoon.webtoon.entity.enums.AgeLimit; @Entity @Getter diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/AgeLimit.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/AgeLimit.java similarity index 86% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/AgeLimit.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/AgeLimit.java index 6549ffd..3b82cd3 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/AgeLimit.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/AgeLimit.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity.enums; +package shop.jtoon.webtoon.entity.enums; import lombok.AccessLevel; import lombok.Getter; diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/DayOfWeek.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/DayOfWeek.java similarity index 84% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/DayOfWeek.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/DayOfWeek.java index 15e3c23..57c54e3 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/DayOfWeek.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/DayOfWeek.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity.enums; +package shop.jtoon.webtoon.entity.enums; import lombok.AccessLevel; import lombok.Getter; diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/Genre.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/Genre.java similarity index 91% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/Genre.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/Genre.java index 2084a95..ae1be69 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/entity/enums/Genre.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/entity/enums/Genre.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity.enums; +package shop.jtoon.webtoon.entity.enums; import lombok.AccessLevel; import lombok.Getter; diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/DayOfWeekWebtoonRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/DayOfWeekWebtoonRepository.java similarity index 64% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/DayOfWeekWebtoonRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/DayOfWeekWebtoonRepository.java index bb3ce2c..32fcbef 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/DayOfWeekWebtoonRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/DayOfWeekWebtoonRepository.java @@ -1,11 +1,11 @@ -package shop.jtoon.repository; +package shop.jtoon.webtoon.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import shop.jtoon.entity.DayOfWeekWebtoon; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; public interface DayOfWeekWebtoonRepository extends JpaRepository { diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeReader.java new file mode 100644 index 0000000..63fb2e8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeReader.java @@ -0,0 +1,33 @@ +package shop.jtoon.webtoon.repository; + +import static shop.jtoon.type.ErrorStatus.*; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.payment.repository.CookieReader; +import shop.jtoon.payment.repository.PaymentReader; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.PurchasedEpisode; +import shop.jtoon.webtoon.entity.Webtoon; + +@Repository +@RequiredArgsConstructor +public class EpisodeReader { + + private final EpisodeRepository episodeRepository; + private final PurchasedEpisodeRepository purchasedEpisodeRepository; + + public boolean validateDuplicateNo(Webtoon webtoon, int no) { + return episodeRepository.existsByWebtoonAndNo(webtoon, no); + } + + public Episode read(Long episodeId) { + return episodeRepository.findById(episodeId).orElseThrow(() -> new NotFoundException(EPISODE_NOT_FOUND)); + } + + public void save(PurchasedEpisode purchasedEpisode) { + purchasedEpisodeRepository.save(purchasedEpisode); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/EpisodeRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeRepository.java similarity index 61% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/EpisodeRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeRepository.java index c9e57c6..610e244 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/EpisodeRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeRepository.java @@ -1,9 +1,9 @@ -package shop.jtoon.repository; +package shop.jtoon.webtoon.repository; import org.springframework.data.jpa.repository.JpaRepository; -import shop.jtoon.entity.Episode; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Webtoon; public interface EpisodeRepository extends JpaRepository { diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/EpisodeSearchRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeSearchRepository.java similarity index 60% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/EpisodeSearchRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeSearchRepository.java index 4ac6372..cae6bca 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/EpisodeSearchRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeSearchRepository.java @@ -1,6 +1,4 @@ -package shop.jtoon.repository; - -import static shop.jtoon.entity.QEpisode.*; +package shop.jtoon.webtoon.repository; import java.util.List; @@ -9,8 +7,9 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; -import shop.jtoon.entity.Episode; import shop.jtoon.util.DynamicQuery; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.QEpisode; @Repository @RequiredArgsConstructor @@ -19,11 +18,11 @@ public class EpisodeSearchRepository { private final JPAQueryFactory jpaQueryFactory; public List getEpisodes(Long webtoonId, int size, Long offset) { - return jpaQueryFactory.selectFrom(episode) - .where(DynamicQuery.generateEq(webtoonId, episode.webtoon.id::eq)) + return jpaQueryFactory.selectFrom(QEpisode.episode) + .where(DynamicQuery.generateEq(webtoonId, QEpisode.episode.webtoon.id::eq)) .limit(size) .offset(offset) - .orderBy(episode.no.desc()) + .orderBy(QEpisode.episode.no.desc()) .fetch(); } } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeWriter.java new file mode 100644 index 0000000..0183989 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/EpisodeWriter.java @@ -0,0 +1,29 @@ +package shop.jtoon.webtoon.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.webtoon.domain.SearchEpisode; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Webtoon; + +@Repository +@RequiredArgsConstructor +public class EpisodeWriter { + + private final EpisodeRepository episodeRepository; + private final EpisodeSearchRepository episodeSearchRepository; + + public void write(Episode episode) { + episodeRepository.save(episode); + } + + public List readEpisodes(Long webtoonId, int size, Long offset) { + return episodeSearchRepository.getEpisodes(webtoonId, size, offset) + .stream() + .map(SearchEpisode::from) + .toList(); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/GenreWebtoonRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/GenreWebtoonRepository.java similarity index 64% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/GenreWebtoonRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/GenreWebtoonRepository.java index d4bfd13..1a6b736 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/GenreWebtoonRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/GenreWebtoonRepository.java @@ -1,11 +1,11 @@ -package shop.jtoon.repository; +package shop.jtoon.webtoon.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import shop.jtoon.entity.GenreWebtoon; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.GenreWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; public interface GenreWebtoonRepository extends JpaRepository { diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/PurchasedEpisodeRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/PurchasedEpisodeRepository.java similarity index 63% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/PurchasedEpisodeRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/PurchasedEpisodeRepository.java index 3424161..275d007 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/PurchasedEpisodeRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/PurchasedEpisodeRepository.java @@ -1,8 +1,8 @@ -package shop.jtoon.repository; +package shop.jtoon.webtoon.repository; import org.springframework.data.jpa.repository.JpaRepository; -import shop.jtoon.entity.PurchasedEpisode; +import shop.jtoon.webtoon.entity.PurchasedEpisode; public interface PurchasedEpisodeRepository extends JpaRepository { } diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java new file mode 100644 index 0000000..c89d6b8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonReader.java @@ -0,0 +1,49 @@ +package shop.jtoon.webtoon.repository; + +import static shop.jtoon.type.ErrorStatus.*; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.webtoon.domain.SearchWebtoon; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.GenreWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.Genre; + +@Repository +@RequiredArgsConstructor +public class WebtoonReader { + + private final WebtoonRepository webtoonRepository; + private final WebtoonSearchRepository webtoonSearchRepository; + private final DayOfWeekWebtoonRepository dayOfWeekWebtoonRepository; + private final GenreWebtoonRepository genreWebtoonRepository; + + public List search(SearchWebtoon search) { + return webtoonSearchRepository.findWebtoons(search.day(), search.keyword()); + } + + public Webtoon read(Long webtoonId) { + return webtoonRepository.findById(webtoonId).orElseThrow(() -> new NotFoundException(WEBTOON_NOT_FOUND)); + } + + public List readDayOfWebtoon(Webtoon webtoon) { + return dayOfWeekWebtoonRepository.findByWebtoon(webtoon) + .stream() + .map(DayOfWeekWebtoon::getDayOfWeekName) + .toList(); + } + + public List readGenreOfWebtoon(Webtoon webtoon) { + return genreWebtoonRepository.findByWebtoon(webtoon).stream() + .map(GenreWebtoon::getGenre).toList(); + } + + public boolean exsistsTitie(String title) { + return webtoonRepository.existsByTitle(title); + } +} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/WebtoonRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java similarity index 68% rename from module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/WebtoonRepository.java rename to jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java index a71e340..4a480d7 100644 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/WebtoonRepository.java +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonRepository.java @@ -1,8 +1,8 @@ -package shop.jtoon.repository; +package shop.jtoon.webtoon.repository; import org.springframework.data.jpa.repository.JpaRepository; -import shop.jtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.Webtoon; public interface WebtoonRepository extends JpaRepository { diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java new file mode 100644 index 0000000..12f3208 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonSearchRepository.java @@ -0,0 +1,39 @@ +package shop.jtoon.webtoon.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.entity.QMember; +import shop.jtoon.util.DynamicQuery; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.QDayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.QWebtoon; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; + +@Repository +@RequiredArgsConstructor +public class WebtoonSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findWebtoons(DayOfWeek dayOfWeek, String keyword) { + return jpaQueryFactory.selectFrom(QDayOfWeekWebtoon.dayOfWeekWebtoon) + .join(QDayOfWeekWebtoon.dayOfWeekWebtoon.webtoon, QWebtoon.webtoon) + .join(QWebtoon.webtoon.author, QMember.member) + .where( + DynamicQuery.generateEq(dayOfWeek, QDayOfWeekWebtoon.dayOfWeekWebtoon.dayOfWeek::eq), + DynamicQuery.generateEq(keyword, this::containsKeyword) + ) + .fetch(); + } + + private BooleanExpression containsKeyword(String keyword) { + return QWebtoon.webtoon.title.contains(keyword) + .or(QWebtoon.webtoon.author.nickname.contains(keyword)); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java new file mode 100644 index 0000000..f5e31a7 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/repository/WebtoonWriter.java @@ -0,0 +1,26 @@ +package shop.jtoon.webtoon.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.GenreWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; + +@Repository +@RequiredArgsConstructor +public class WebtoonWriter { + + private final WebtoonRepository webtoonRepository; + private final DayOfWeekWebtoonRepository dayOfWeekWebtoonRepository; + private final GenreWebtoonRepository genreWebtoonRepository; + + public void createWebtoon(Webtoon webtoon, List dayOfWeekWebtoons, + List genreWebtoons) { + webtoonRepository.save(webtoon); + dayOfWeekWebtoonRepository.saveAll(dayOfWeekWebtoons); + genreWebtoonRepository.saveAll(genreWebtoons); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java new file mode 100644 index 0000000..2f99597 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/EpisodeDomainService.java @@ -0,0 +1,64 @@ +package shop.jtoon.webtoon.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.repository.MemberReader; +import shop.jtoon.payment.service.CookieManager; +import shop.jtoon.webtoon.domain.EpisodeMainInfo; +import shop.jtoon.webtoon.domain.EpisodeSchema; +import shop.jtoon.webtoon.domain.SearchEpisode; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.repository.EpisodeReader; +import shop.jtoon.webtoon.repository.EpisodeWriter; +import shop.jtoon.webtoon.repository.WebtoonReader; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class EpisodeDomainService { + + private final WebtoonManger webtoonManger; + private final CookieManager cookieManager; + + private final WebtoonReader webtoonReader; + private final EpisodeReader episodeReader; + private final MemberReader memberReader; + private final EpisodeWriter episodeWriter; + + + public Webtoon readWebtoon(Long webtoonId, Long memberId, int no) { + Webtoon webtoon = webtoonReader.read(webtoonId); + webtoonManger.validationWebtoon(webtoon, memberId, no); + + return webtoon; + } + + public void createEpisode(EpisodeSchema episodeSchema,Webtoon webtoon, String mainUrl, String thumbnailUrl) { + Episode episode = episodeSchema.toEpisode(webtoon,mainUrl,thumbnailUrl); + episodeWriter.write(episode); + } + + public EpisodeMainInfo readEpisode(Long episodeId) { + Episode episode = episodeReader.read(episodeId); + + return EpisodeMainInfo.of(episode); + } + + @Transactional + public void purchaseEpisode(Long memberId, Long episodeId) { + Member member = memberReader.read(memberId); + Episode episode = episodeReader.read(episodeId); + cookieManager.useCookie(episode.getCookieCount(), member); + webtoonManger.purchase(episode, member); + } + + public List readEpisodes(Long webtoonId, int size, Long offset) { + return episodeWriter.readEpisodes(webtoonId, size, offset); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java new file mode 100644 index 0000000..6adf7a8 --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonDomainService.java @@ -0,0 +1,68 @@ +package shop.jtoon.webtoon.service; + +import static java.util.stream.Collectors.*; +import static shop.jtoon.type.ErrorStatus.*; + +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.member.entity.Member; +import shop.jtoon.member.repository.MemberReader; +import shop.jtoon.webtoon.domain.SearchWebtoon; +import shop.jtoon.webtoon.domain.WebtoonDayOfWeeks; +import shop.jtoon.webtoon.domain.WebtoonDetail; +import shop.jtoon.webtoon.domain.WebtoonSchema; +import shop.jtoon.webtoon.domain.WebtoonGenres; +import shop.jtoon.webtoon.domain.WebtoonInfo; +import shop.jtoon.webtoon.entity.DayOfWeekWebtoon; +import shop.jtoon.webtoon.entity.GenreWebtoon; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.entity.enums.DayOfWeek; +import shop.jtoon.webtoon.entity.enums.Genre; +import shop.jtoon.webtoon.repository.WebtoonReader; +import shop.jtoon.webtoon.repository.WebtoonWriter; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class WebtoonDomainService { + + private final WebtoonManger webtoonManger; + private final MemberReader memberReader; + private final WebtoonWriter webtoonWriter; + private final WebtoonReader webtoonReader; + + public void validateDuplicateTitle(String title) { + webtoonManger.validationTitle(title); + } + + @Transactional + public void createWebtoon(Long memberId, WebtoonInfo info, WebtoonGenres genres, WebtoonDayOfWeeks dayOfWeeks) { + Member member = memberReader.read(memberId); + + Webtoon webtoon = info.toWebtoonEntity(member); + List dayOfWeekWebtoons = dayOfWeeks.toDayOfWeekWebtoonEntity(webtoon); + List genreWebtoons = genres.toGenreWebtoonEntity(webtoon); + + webtoonWriter.createWebtoon(webtoon, dayOfWeekWebtoons, genreWebtoons); + } + + public Map> readWebtoons(SearchWebtoon search) { + return webtoonReader.search(search).stream() + .collect(groupingBy(DayOfWeekWebtoon::getDayOfWeek, + mapping(dayOfWeekWebtoon -> WebtoonSchema.from(dayOfWeekWebtoon.getWebtoon()), toList()))); + } + + public WebtoonDetail readWebtoonDetail(Long webtoonId) { + Webtoon webtoon = webtoonReader.read(webtoonId); + List dayOfWeeks = webtoonReader.readDayOfWebtoon(webtoon); + List genres = webtoonReader.readGenreOfWebtoon(webtoon); + + return WebtoonDetail.of(webtoon, dayOfWeeks, genres); + } +} diff --git a/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java new file mode 100644 index 0000000..acecf6a --- /dev/null +++ b/jtoon-core/core-domain/src/main/java/shop/jtoon/webtoon/service/WebtoonManger.java @@ -0,0 +1,47 @@ +package shop.jtoon.webtoon.service; + +import static shop.jtoon.type.ErrorStatus.*; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import shop.jtoon.exception.DuplicatedException; +import shop.jtoon.member.entity.Member; +import shop.jtoon.payment.repository.CookieReader; +import shop.jtoon.payment.service.CookieManager; +import shop.jtoon.webtoon.entity.Episode; +import shop.jtoon.webtoon.entity.PurchasedEpisode; +import shop.jtoon.webtoon.entity.Webtoon; +import shop.jtoon.webtoon.repository.EpisodeReader; +import shop.jtoon.webtoon.repository.WebtoonReader; + +@Service +@RequiredArgsConstructor +public class WebtoonManger { + + private final WebtoonReader webtoonReader; + private final EpisodeReader episodeReader; + + + public void validationTitle(String title) { + if (webtoonReader.exsistsTitie(title)) { + throw new DuplicatedException(WEBTOON_TITLE_DUPLICATED); + } + } + + public void validationWebtoon(Webtoon webtoon, Long memberId, int no) { + webtoon.validateAuthor(memberId); + validateDuplicateNo(webtoon, no); + } + + private void validateDuplicateNo(Webtoon webtoon, int no) { + if (episodeReader.validateDuplicateNo(webtoon, no)) { + throw new DuplicatedException(EPISODE_NUMBER_DUPLICATED); + } + } + + public void purchase(Episode episode, Member member) { + PurchasedEpisode purchasedEpisode = PurchasedEpisode.create(member, episode); + episodeReader.save(purchasedEpisode); + } +} diff --git a/jtoon-db/db-redis/build.gradle b/jtoon-db/db-redis/build.gradle new file mode 100644 index 0000000..a4e9c74 --- /dev/null +++ b/jtoon-db/db-redis/build.gradle @@ -0,0 +1,7 @@ +dependencies { + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + compileOnly project(":jtoon-system") + compileOnly project(":jtoon-core:core-domain") +} \ No newline at end of file diff --git a/module-domain/domain-redis/src/main/java/shop/jtoon/config/RedisConfig.java b/jtoon-db/db-redis/src/main/java/shop/jtoon/config/RedisConfig.java similarity index 100% rename from module-domain/domain-redis/src/main/java/shop/jtoon/config/RedisConfig.java rename to jtoon-db/db-redis/src/main/java/shop/jtoon/config/RedisConfig.java diff --git a/module-domain/domain-redis/src/main/java/shop/jtoon/repository/StringRedisRepository.java b/jtoon-db/db-redis/src/main/java/shop/jtoon/repository/StringRedisRepository.java similarity index 82% rename from module-domain/domain-redis/src/main/java/shop/jtoon/repository/StringRedisRepository.java rename to jtoon-db/db-redis/src/main/java/shop/jtoon/repository/StringRedisRepository.java index ac602b3..8845247 100644 --- a/module-domain/domain-redis/src/main/java/shop/jtoon/repository/StringRedisRepository.java +++ b/jtoon-db/db-redis/src/main/java/shop/jtoon/repository/StringRedisRepository.java @@ -2,11 +2,13 @@ import static shop.jtoon.util.SecurityConstant.*; +import java.util.concurrent.TimeUnit; + import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; -import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; +import shop.jtoon.util.SecurityConstant; @Repository @RequiredArgsConstructor @@ -15,7 +17,7 @@ public class StringRedisRepository { private final StringRedisTemplate redisTemplate; public void save(String key, String value, Long expireMin) { - redisTemplate.opsForValue().set(key, value, expireMin * MINUTE, TimeUnit.MILLISECONDS); + redisTemplate.opsForValue().set(key, value, expireMin * SecurityConstant.MINUTE, TimeUnit.MILLISECONDS); } public String getData(String key) { diff --git a/module-domain/domain-redis/src/main/java/shop/jtoon/service/RedisTokenService.java b/jtoon-db/db-redis/src/main/java/shop/jtoon/service/RedisTokenService.java similarity index 100% rename from module-domain/domain-redis/src/main/java/shop/jtoon/service/RedisTokenService.java rename to jtoon-db/db-redis/src/main/java/shop/jtoon/service/RedisTokenService.java diff --git a/jtoon-internal/README.md b/jtoon-internal/README.md new file mode 100644 index 0000000..81d4322 --- /dev/null +++ b/jtoon-internal/README.md @@ -0,0 +1,9 @@ +# Jtoon internal + +외부 시스템과 통신을 위한 모듈의 집함 + +## 종류 +- `core-web`: 모안 모듈 추후 Gateway로 분리할 수 있음 +- `iamport-client`: 결제관련 모듈 +- `s3-client`: AWS의 s3 통신을 위한 모듈 +- `smtp-client`: smtp 요청을 위한 모듈 \ No newline at end of file diff --git a/module-internal/core-web/build.gradle b/jtoon-internal/core-web/build.gradle similarity index 92% rename from module-internal/core-web/build.gradle rename to jtoon-internal/core-web/build.gradle index 25e23ff..8237d0d 100644 --- a/module-internal/core-web/build.gradle +++ b/jtoon-internal/core-web/build.gradle @@ -1,4 +1,7 @@ dependencies { + + implementation project(':jtoon-system') + // Web implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/module-internal/core-web/src/main/java/shop/jtoon/annotation/CurrentUser.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/annotation/CurrentUser.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/annotation/CurrentUser.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/annotation/CurrentUser.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/config/SecurityConfig.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/config/SecurityConfig.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/config/SecurityConfig.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/config/SecurityConfig.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/config/WebConfig.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/config/WebConfig.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/config/WebConfig.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/config/WebConfig.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/error/handler/GlobalExceptionHandler.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/error/handler/GlobalExceptionHandler.java similarity index 91% rename from module-internal/core-web/src/main/java/shop/jtoon/error/handler/GlobalExceptionHandler.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/error/handler/GlobalExceptionHandler.java index 393b41a..af65683 100644 --- a/module-internal/core-web/src/main/java/shop/jtoon/error/handler/GlobalExceptionHandler.java +++ b/jtoon-internal/core-web/src/main/java/shop/jtoon/error/handler/GlobalExceptionHandler.java @@ -1,5 +1,8 @@ package shop.jtoon.error.handler; + +import java.util.List; + import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; @@ -7,12 +10,16 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; + import shop.jtoon.error.model.ErrorResponse; -import shop.jtoon.exception.*; +import shop.jtoon.exception.DuplicatedException; +import shop.jtoon.exception.ForbiddenException; +import shop.jtoon.exception.IamportException; +import shop.jtoon.exception.InvalidRequestException; +import shop.jtoon.exception.NotFoundException; +import shop.jtoon.exception.UnauthorizedException; import shop.jtoon.type.ErrorStatus; -import java.util.List; - @RestControllerAdvice public class GlobalExceptionHandler { diff --git a/module-internal/core-web/src/main/java/shop/jtoon/error/model/ErrorResponse.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/error/model/ErrorResponse.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/error/model/ErrorResponse.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/error/model/ErrorResponse.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/filter/AuthenticationFilter.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/filter/AuthenticationFilter.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/filter/AuthenticationFilter.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/filter/AuthenticationFilter.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2FailureHandler.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2FailureHandler.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2FailureHandler.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2FailureHandler.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2SuccessHandler.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2SuccessHandler.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2SuccessHandler.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/handler/OAuth2SuccessHandler.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/service/AuthenticationService.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/AuthenticationService.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/service/AuthenticationService.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/AuthenticationService.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/service/AuthorizationService.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/AuthorizationService.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/service/AuthorizationService.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/AuthorizationService.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/service/CustomOAuth2UserService.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/CustomOAuth2UserService.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/service/CustomOAuth2UserService.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/CustomOAuth2UserService.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/service/JwtService.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/JwtService.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/service/JwtService.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/JwtService.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/service/RefreshTokenService.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/RefreshTokenService.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/service/RefreshTokenService.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/service/RefreshTokenService.java diff --git a/module-internal/core-web/src/main/java/shop/jtoon/security/util/TokenCookie.java b/jtoon-internal/core-web/src/main/java/shop/jtoon/security/util/TokenCookie.java similarity index 100% rename from module-internal/core-web/src/main/java/shop/jtoon/security/util/TokenCookie.java rename to jtoon-internal/core-web/src/main/java/shop/jtoon/security/util/TokenCookie.java diff --git a/module-internal/core-web/src/test/java/shop/jtoon/security/SecurityApplicationTest.java b/jtoon-internal/core-web/src/test/java/shop/jtoon/security/SecurityApplicationTest.java similarity index 100% rename from module-internal/core-web/src/test/java/shop/jtoon/security/SecurityApplicationTest.java rename to jtoon-internal/core-web/src/test/java/shop/jtoon/security/SecurityApplicationTest.java diff --git a/module-internal/core-web/src/test/java/shop/jtoon/security/filter/AuthenticationFilterTest.java b/jtoon-internal/core-web/src/test/java/shop/jtoon/security/filter/AuthenticationFilterTest.java similarity index 100% rename from module-internal/core-web/src/test/java/shop/jtoon/security/filter/AuthenticationFilterTest.java rename to jtoon-internal/core-web/src/test/java/shop/jtoon/security/filter/AuthenticationFilterTest.java diff --git a/module-internal/iamport-client/build.gradle b/jtoon-internal/iamport-client/build.gradle similarity index 89% rename from module-internal/iamport-client/build.gradle rename to jtoon-internal/iamport-client/build.gradle index c5fba35..a23f6bf 100644 --- a/module-internal/iamport-client/build.gradle +++ b/jtoon-internal/iamport-client/build.gradle @@ -1,4 +1,6 @@ dependencies { + + implementation project(':jtoon-system') // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' diff --git a/module-internal/iamport-client/src/main/java/shop/jtoon/config/IamportConfig.java b/jtoon-internal/iamport-client/src/main/java/shop/jtoon/config/IamportConfig.java similarity index 100% rename from module-internal/iamport-client/src/main/java/shop/jtoon/config/IamportConfig.java rename to jtoon-internal/iamport-client/src/main/java/shop/jtoon/config/IamportConfig.java diff --git a/module-internal/iamport-client/src/main/java/shop/jtoon/service/IamportService.java b/jtoon-internal/iamport-client/src/main/java/shop/jtoon/service/IamportService.java similarity index 100% rename from module-internal/iamport-client/src/main/java/shop/jtoon/service/IamportService.java rename to jtoon-internal/iamport-client/src/main/java/shop/jtoon/service/IamportService.java diff --git a/module-internal/iamport-client/src/test/java/shop/jtoon/IamportClientApplicationTest.java b/jtoon-internal/iamport-client/src/test/java/shop/jtoon/IamportClientApplicationTest.java similarity index 100% rename from module-internal/iamport-client/src/test/java/shop/jtoon/IamportClientApplicationTest.java rename to jtoon-internal/iamport-client/src/test/java/shop/jtoon/IamportClientApplicationTest.java diff --git a/module-internal/iamport-client/src/test/java/shop/jtoon/service/IamportServiceTest.java b/jtoon-internal/iamport-client/src/test/java/shop/jtoon/service/IamportServiceTest.java similarity index 100% rename from module-internal/iamport-client/src/test/java/shop/jtoon/service/IamportServiceTest.java rename to jtoon-internal/iamport-client/src/test/java/shop/jtoon/service/IamportServiceTest.java diff --git a/module-internal/s3-client/build.gradle b/jtoon-internal/s3-client/build.gradle similarity index 84% rename from module-internal/s3-client/build.gradle rename to jtoon-internal/s3-client/build.gradle index dac3873..4d7d080 100644 --- a/module-internal/s3-client/build.gradle +++ b/jtoon-internal/s3-client/build.gradle @@ -1,4 +1,7 @@ dependencies { + + implementation project(":jtoon-system") + // S3 implementation 'org.springframework.boot:spring-boot-starter-web' implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2") diff --git a/module-internal/s3-client/src/main/java/shop/jtoon/common/FileName.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/common/FileName.java similarity index 100% rename from module-internal/s3-client/src/main/java/shop/jtoon/common/FileName.java rename to jtoon-internal/s3-client/src/main/java/shop/jtoon/common/FileName.java diff --git a/module-internal/s3-client/src/main/java/shop/jtoon/common/ImageType.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/common/ImageType.java similarity index 100% rename from module-internal/s3-client/src/main/java/shop/jtoon/common/ImageType.java rename to jtoon-internal/s3-client/src/main/java/shop/jtoon/common/ImageType.java diff --git a/module-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java similarity index 100% rename from module-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java rename to jtoon-internal/s3-client/src/main/java/shop/jtoon/dto/UploadImageDto.java diff --git a/module-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java similarity index 100% rename from module-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java rename to jtoon-internal/s3-client/src/main/java/shop/jtoon/service/S3Service.java diff --git a/module-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java b/jtoon-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java similarity index 100% rename from module-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java rename to jtoon-internal/s3-client/src/main/java/shop/jtoon/util/S3Manager.java diff --git a/jtoon-internal/smtp-client/build.gradle b/jtoon-internal/smtp-client/build.gradle new file mode 100644 index 0000000..72a6c28 --- /dev/null +++ b/jtoon-internal/smtp-client/build.gradle @@ -0,0 +1,7 @@ +dependencies { + + implementation project(':jtoon-system') + + // SMTP + implementation 'org.springframework.boot:spring-boot-starter-mail' +} diff --git a/module-internal/smtp-client/src/main/java/shop/jtoon/application/SmtpService.java b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/application/SmtpService.java similarity index 77% rename from module-internal/smtp-client/src/main/java/shop/jtoon/application/SmtpService.java rename to jtoon-internal/smtp-client/src/main/java/shop/jtoon/application/SmtpService.java index 732e6eb..b20cb17 100644 --- a/module-internal/smtp-client/src/main/java/shop/jtoon/application/SmtpService.java +++ b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/application/SmtpService.java @@ -1,5 +1,7 @@ package shop.jtoon.application; +import java.util.UUID; + import org.springframework.beans.factory.annotation.Value; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; @@ -8,7 +10,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import shop.jtoon.entity.Mail; +import shop.jtoon.domain.Mail; @Service @Slf4j @@ -17,9 +19,16 @@ public class SmtpService { private final JavaMailSender javaMailSender; - @Value("${spring.mail.username}") + @Value("${mail.username}") private String senderUsername; + public void sendMail(String email) { + UUID uuid = UUID.randomUUID(); + String randomUuid = uuid.toString().substring(0, 6); + + sendMail(Mail.forAuthentication(email, randomUuid)); + } + public void sendMail(Mail mail) { MimeMessagePreparator mimeMessagePreparator = mimeMessage -> { MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8"); diff --git a/jtoon-internal/smtp-client/src/main/java/shop/jtoon/config/MailProperties.java b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/config/MailProperties.java new file mode 100644 index 0000000..a863ec2 --- /dev/null +++ b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/config/MailProperties.java @@ -0,0 +1,32 @@ +package shop.jtoon.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import lombok.Getter; + +@Getter +@Configuration +public class MailProperties { + + @Value("${mail.host}") + private String host; + + @Value("${mail.port}") + private Integer port; + + @Value("${mail.username}") + private String username; + + @Value("${mail.password}") + private String password; + + @Value("${mail.properties.mail.smtp.auth}") + private String auth; + + @Value("${mail.properties.mail.smtp.starttls.enable}") + private String starttls; + + @Value("${mail.properties.mail.smtp.ssl.trust}") + private String sslTrust; +} \ No newline at end of file diff --git a/jtoon-internal/smtp-client/src/main/java/shop/jtoon/config/SmtpConfig.java b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/config/SmtpConfig.java new file mode 100644 index 0000000..09e6a23 --- /dev/null +++ b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/config/SmtpConfig.java @@ -0,0 +1,35 @@ +package shop.jtoon.config; + +import java.util.Properties; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class SmtpConfig { + + private final MailProperties mailProperties; + + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(mailProperties.getHost()); + mailSender.setPort(mailProperties.getPort()); + + mailSender.setUsername(mailProperties.getUsername()); + mailSender.setPassword(mailProperties.getPassword()); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.smtp.auth", mailProperties.getAuth()); + props.put("mail.smtp.starttls.enable", mailProperties.getStarttls()); + props.put("mail.debug", "true"); + + return mailSender; + } +} diff --git a/module-internal/smtp-client/src/main/java/shop/jtoon/entity/Mail.java b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/domain/Mail.java similarity index 96% rename from module-internal/smtp-client/src/main/java/shop/jtoon/entity/Mail.java rename to jtoon-internal/smtp-client/src/main/java/shop/jtoon/domain/Mail.java index ff8ecdd..fed900e 100644 --- a/module-internal/smtp-client/src/main/java/shop/jtoon/entity/Mail.java +++ b/jtoon-internal/smtp-client/src/main/java/shop/jtoon/domain/Mail.java @@ -1,4 +1,4 @@ -package shop.jtoon.entity; +package shop.jtoon.domain; import static java.util.Objects.*; diff --git a/module-internal/smtp-client/src/test/java/shop/jtoon/SmtpApplicationTest.java b/jtoon-internal/smtp-client/src/test/java/shop/jtoon/SmtpApplicationTest.java similarity index 100% rename from module-internal/smtp-client/src/test/java/shop/jtoon/SmtpApplicationTest.java rename to jtoon-internal/smtp-client/src/test/java/shop/jtoon/SmtpApplicationTest.java diff --git a/module-internal/smtp-client/src/test/java/shop/jtoon/application/SmtpServiceTest.java b/jtoon-internal/smtp-client/src/test/java/shop/jtoon/application/SmtpServiceTest.java similarity index 97% rename from module-internal/smtp-client/src/test/java/shop/jtoon/application/SmtpServiceTest.java rename to jtoon-internal/smtp-client/src/test/java/shop/jtoon/application/SmtpServiceTest.java index 3ab5abb..cb22a7b 100644 --- a/module-internal/smtp-client/src/test/java/shop/jtoon/application/SmtpServiceTest.java +++ b/jtoon-internal/smtp-client/src/test/java/shop/jtoon/application/SmtpServiceTest.java @@ -12,7 +12,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mail.javamail.JavaMailSenderImpl; -import shop.jtoon.entity.Mail; +import shop.jtoon.domain.Mail; @ExtendWith(MockitoExtension.class) class SmtpServiceTest { diff --git a/jtoon-support/README.md b/jtoon-support/README.md new file mode 100644 index 0000000..03b521f --- /dev/null +++ b/jtoon-support/README.md @@ -0,0 +1,19 @@ +# Jtoon Support + +Jtoon 서포트 모듈 + +## 분류 이유 + +`MSA전환시 MS로 분리가 되게 되는데 모니터링, 로깅같은 기능은 공통으로 필요하기 때문에 분리` + +## 종류 + +### Logging +로깅 모듈 + + + +### Monitoring +모니터링 모듈 + +`Spring Actuator`, `Prometeus`, `Grafana`로 구성 diff --git a/module-core/build.gradle b/jtoon-support/logging/build.gradle similarity index 100% rename from module-core/build.gradle rename to jtoon-support/logging/build.gradle diff --git a/jtoon-support/logging/src/main/resources/logback/logback-dev.xml b/jtoon-support/logging/src/main/resources/logback/logback-dev.xml new file mode 100644 index 0000000..1a9803c --- /dev/null +++ b/jtoon-support/logging/src/main/resources/logback/logback-dev.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + ${LOGS_ABSOLUTE_PATH}/tyleLog.log + + ${FILE_LOG_PATTERN} + + + ${LOGS_ABSOLUTE_PATH}/tyleLog.%d{yyyy-MM-dd}.log + 10 + 1GB + + + + + + + + + + + + + + diff --git a/jtoon-support/logging/src/main/resources/logback/logback-local.xml b/jtoon-support/logging/src/main/resources/logback/logback-local.xml new file mode 100644 index 0000000..b3d6268 --- /dev/null +++ b/jtoon-support/logging/src/main/resources/logback/logback-local.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + utf8 + + + + + + + + + + diff --git a/jtoon-support/logging/src/main/resources/logging.yml b/jtoon-support/logging/src/main/resources/logging.yml new file mode 100644 index 0000000..2039fe5 --- /dev/null +++ b/jtoon-support/logging/src/main/resources/logging.yml @@ -0,0 +1,7 @@ +spring: + sleuth: + trace-id128: true + sampler: + probability: 1.0 + +logging.config: classpath:logback/logback-${spring.profiles.active}.xml \ No newline at end of file diff --git a/module-application/app-api/src/main/resources/application.yml b/jtoon-support/monitoring/build.gradle similarity index 100% rename from module-application/app-api/src/main/resources/application.yml rename to jtoon-support/monitoring/build.gradle diff --git a/jtoon-system/README.md b/jtoon-system/README.md new file mode 100644 index 0000000..82a94cc --- /dev/null +++ b/jtoon-system/README.md @@ -0,0 +1,7 @@ +# Jtoon System +프로젝트 전반에 걸쳐 사용하는 공통 시스템 + +## 종류 +- Exception +- Util +- \ No newline at end of file diff --git a/jtoon-system/build.gradle b/jtoon-system/build.gradle new file mode 100644 index 0000000..0ce6a16 --- /dev/null +++ b/jtoon-system/build.gradle @@ -0,0 +1,3 @@ +dependencies { + +} \ No newline at end of file diff --git a/module-core/src/main/java/shop/jtoon/exception/DuplicatedException.java b/jtoon-system/src/main/java/shop/jtoon/exception/DuplicatedException.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/exception/DuplicatedException.java rename to jtoon-system/src/main/java/shop/jtoon/exception/DuplicatedException.java diff --git a/module-core/src/main/java/shop/jtoon/exception/ForbiddenException.java b/jtoon-system/src/main/java/shop/jtoon/exception/ForbiddenException.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/exception/ForbiddenException.java rename to jtoon-system/src/main/java/shop/jtoon/exception/ForbiddenException.java diff --git a/module-core/src/main/java/shop/jtoon/exception/IamportException.java b/jtoon-system/src/main/java/shop/jtoon/exception/IamportException.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/exception/IamportException.java rename to jtoon-system/src/main/java/shop/jtoon/exception/IamportException.java diff --git a/module-core/src/main/java/shop/jtoon/exception/InvalidRequestException.java b/jtoon-system/src/main/java/shop/jtoon/exception/InvalidRequestException.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/exception/InvalidRequestException.java rename to jtoon-system/src/main/java/shop/jtoon/exception/InvalidRequestException.java diff --git a/module-core/src/main/java/shop/jtoon/exception/NotFoundException.java b/jtoon-system/src/main/java/shop/jtoon/exception/NotFoundException.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/exception/NotFoundException.java rename to jtoon-system/src/main/java/shop/jtoon/exception/NotFoundException.java diff --git a/module-core/src/main/java/shop/jtoon/exception/UnauthorizedException.java b/jtoon-system/src/main/java/shop/jtoon/exception/UnauthorizedException.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/exception/UnauthorizedException.java rename to jtoon-system/src/main/java/shop/jtoon/exception/UnauthorizedException.java diff --git a/module-core/src/main/java/shop/jtoon/type/CustomPageRequest.java b/jtoon-system/src/main/java/shop/jtoon/type/CustomPageRequest.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/type/CustomPageRequest.java rename to jtoon-system/src/main/java/shop/jtoon/type/CustomPageRequest.java diff --git a/module-core/src/main/java/shop/jtoon/type/ErrorStatus.java b/jtoon-system/src/main/java/shop/jtoon/type/ErrorStatus.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/type/ErrorStatus.java rename to jtoon-system/src/main/java/shop/jtoon/type/ErrorStatus.java diff --git a/module-core/src/main/java/shop/jtoon/util/RegExp.java b/jtoon-system/src/main/java/shop/jtoon/util/RegExp.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/util/RegExp.java rename to jtoon-system/src/main/java/shop/jtoon/util/RegExp.java diff --git a/module-core/src/main/java/shop/jtoon/util/SecurityConstant.java b/jtoon-system/src/main/java/shop/jtoon/util/SecurityConstant.java similarity index 100% rename from module-core/src/main/java/shop/jtoon/util/SecurityConstant.java rename to jtoon-system/src/main/java/shop/jtoon/util/SecurityConstant.java diff --git a/module-application/README.md b/module-application/README.md deleted file mode 100644 index 7259711..0000000 --- a/module-application/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Application Layer - -| | **어플리케이션 모듈** | **내부 모듈** | **도메인 모듈** | **공통 모듈** | -|:---------:|:-------------:|:---------:|:----------:|:---------:| -| **사용 가능** | - | O | O | O | - -- 독립적으로 실행 가능한 어플리케이션 모듈 계층입니다. -- 어플리케이션 모듈은 하위 설계 했던 모듈들을 조립하여 서비스 비지니스를 완성시킵니다. - -
- -#### 아래와 같은 모듈을 주로 배치할 수 있습니다. - -> - app-api -> - app-admin diff --git a/module-application/app-api/Dockerfile b/module-application/app-api/Dockerfile deleted file mode 100644 index 2de4f22..0000000 --- a/module-application/app-api/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -### Docker 이미지를 생성할 때 기반이 되는 베이스 이미지를 설정한다. -FROM amazoncorretto:17 -### 조셉팀 ^-^ -MAINTAINER 박세연, 신재윤, 홍혁준, 김영명, 김희빈 -### 경로에 해당하는 파일을 Docker 이미지 내부로 복사한다. -COPY ${PWD}/build/libs/app-api-0.0.1-SNAPSHOT.jar jtoon.jar -### Docker 컨테이너가 시작될 때 실행할 명령을 지정한다. - -ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "/jtoon.jar"] diff --git a/module-application/app-api/build.gradle b/module-application/app-api/build.gradle deleted file mode 100644 index f76ac67..0000000 --- a/module-application/app-api/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -plugins { - id 'org.asciidoctor.jvm.convert' version '3.3.2' -} - -configurations { - asciidoctorExtensions -} - -ext { - snippetsDir = file('build/generated-snippets') // 스니펫이 생성되는 폴더 설정 -} - -test { - outputs.dir snippetsDir // 스니펫을 'snippetsDir'로 생성하도록 test task 설정 -} - -asciidoctor { - configurations 'asciidoctorExtensions' // 'Asciidoctor' 확장에 대한 설정 - inputs.dir snippetsDir // 불러올 스니펫 위치를 'snippetsDir'로 설정 - dependsOn test // test task 이후에 'asciidoctor'를 실행하도록 설정 -} - -asciidoctor.doFirst { - delete file('src/main/resources/static/docs') -} - -tasks.register('copyDocument', Copy) { - dependsOn asciidoctor - from file("build/docs/asciidoc") - into file("src/main/resources/static/docs") -} - -bootJar { - dependsOn copyDocument -} - -build { - dependsOn copyDocument -} - -dependencies { - // Web - implementation 'org.springframework.boot:spring-boot-starter-web' - - // Bean Validation - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // JPA - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - - // AOP - implementation 'org.springframework.boot:spring-boot-starter-aop' - - // H2 - implementation 'com.h2database:h2' - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' - - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'org.springframework.security:spring-security-test' - - // OAuth2 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - - // JWT - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' - - // Asciidoctor - asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' - - // RestDocs - testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/member/application/EmailService.java b/module-application/app-api/src/main/java/shop/jtoon/member/application/EmailService.java deleted file mode 100644 index 65709b8..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/member/application/EmailService.java +++ /dev/null @@ -1,24 +0,0 @@ -package shop.jtoon.member.application; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import shop.jtoon.application.SmtpService; -import shop.jtoon.entity.Mail; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class EmailService { - - private final SmtpService smtpService; - - public void sendEmailAuthentication(String email) { - UUID uuid = UUID.randomUUID(); - String randomUuid = uuid.toString().substring(0, 6); - - smtpService.sendMail(Mail.forAuthentication(email, randomUuid)); - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/member/application/MemberService.java b/module-application/app-api/src/main/java/shop/jtoon/member/application/MemberService.java deleted file mode 100644 index c41177d..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/member/application/MemberService.java +++ /dev/null @@ -1,96 +0,0 @@ -package shop.jtoon.member.application; - -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import shop.jtoon.dto.MemberDto; -import shop.jtoon.dto.OAuthSignUpDto; -import shop.jtoon.entity.LoginType; -import shop.jtoon.entity.Member; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.member.request.LocalSignUpReq; -import shop.jtoon.repository.MemberRepository; -import shop.jtoon.security.request.LoginReq; -import shop.jtoon.security.service.JwtService; -import shop.jtoon.security.service.RefreshTokenService; -import shop.jtoon.security.util.TokenCookie; - -import java.util.Optional; - -import static shop.jtoon.type.ErrorStatus.*; -import static shop.jtoon.util.SecurityConstant.*; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class MemberService { - - private final JwtService jwtServiceImpl; - private final RefreshTokenService refreshTokenServiceImpl; - private final MemberRepository memberRepository; - private final PasswordEncoder passwordEncoder; - - @Transactional - public void signUp(LocalSignUpReq localSignUpReq) { - validateDuplicateEmail(localSignUpReq.email()); - String encryptedPassword = passwordEncoder.encode(localSignUpReq.password()); - Member member = localSignUpReq.toEntity(encryptedPassword); - - memberRepository.save(member); - } - - public void validateDuplicateEmail(String email) { - if (memberRepository.findByEmail(email).isPresent()) { - throw new DuplicatedException(MEMBER_EMAIL_CONFLICT); - } - } - - @Transactional - public void loginMember(LoginReq loginReq, HttpServletResponse response) { - Member member = findByEmail(loginReq.email()); - - if (!passwordEncoder.matches(loginReq.password(), member.getPassword())) { - throw new InvalidRequestException(MEMBER_WRONG_LOGIN_INFO); - } - - if (!member.getLoginType().equals(LoginType.LOCAL)) { - throw new InvalidRequestException(MEMBER_DUPLICATE_SOCIAL_LOGIN); - } - - member.updateLastLogin(); - - String accessToken = jwtServiceImpl.generateAccessToken(loginReq.email()); - String refreshToken = jwtServiceImpl.generateRefreshToken(); - refreshTokenServiceImpl.saveRefreshToken(refreshToken, loginReq.email()); - - response.addCookie(TokenCookie.of(ACCESS_TOKEN_HEADER, accessToken)); - response.addCookie(TokenCookie.of(REFRESH_TOKEN_HEADER, refreshToken)); - } - - @Transactional - public Member generateOrGetSocialMember(OAuthSignUpDto OAuthSignUpDto) { - Optional member = memberRepository.findByEmail(OAuthSignUpDto.email()); - - return member.orElseGet(() -> memberRepository.save(OAuthSignUpDto.toEntity(BLANK))); - } - - public MemberDto findMemberDtoByEmail(String email) { - Member member = findByEmail(email); - - return MemberDto.toDto(member); - } - - public Member findById(Long id) { - return memberRepository.findById(id) - .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); - } - - public Member findByEmail(String email) { - return memberRepository.findByEmail(email) - .orElseThrow(() -> new NotFoundException(MEMBER_EMAIL_NOT_FOUND)); - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/application/MemberCookieService.java b/module-application/app-api/src/main/java/shop/jtoon/payment/application/MemberCookieService.java deleted file mode 100644 index c4333ed..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/application/MemberCookieService.java +++ /dev/null @@ -1,45 +0,0 @@ -package shop.jtoon.payment.application; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import shop.jtoon.entity.CookieItem; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.MemberCookie; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.repository.MemberCookieRepository; -import shop.jtoon.type.ErrorStatus; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class MemberCookieService { - - private final MemberCookieRepository memberCookieRepository; - - @Transactional - public void createMemberCookie(String cookieItem, Member member) { - CookieItem item = CookieItem.from(cookieItem); - MemberCookie memberCookie = MemberCookie.create(item.getCount(), member); - memberCookieRepository.save(memberCookie); - } - - @Transactional - public int useCookie(int cookieCount, Member member) { - MemberCookie memberCookie = getByMember(member); - memberCookie.decreaseCookieCount(cookieCount); - - return memberCookie.getCookieCount(); - } - - public int getMemberCookieCount(Member member) { - MemberCookie memberCookie = getByMember(member); - - return memberCookie.getCookieCount(); - } - - private MemberCookie getByMember(Member member) { - return memberCookieRepository.findByMember(member) - .orElseThrow(() -> new NotFoundException(ErrorStatus.MEMBER_COOKIE_NOT_FOUND)); - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/application/PaymentInfoService.java b/module-application/app-api/src/main/java/shop/jtoon/payment/application/PaymentInfoService.java deleted file mode 100644 index 8bac33a..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/application/PaymentInfoService.java +++ /dev/null @@ -1,62 +0,0 @@ -package shop.jtoon.payment.application; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PaymentInfo; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.payment.request.PaymentReq; -import shop.jtoon.payment.response.PaymentRes; -import shop.jtoon.repository.PaymentInfoRepository; -import shop.jtoon.repository.PaymentInfoSearchRepository; -import shop.jtoon.service.PaymentInfoDomainService; -import shop.jtoon.type.ErrorStatus; - -import java.util.List; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class PaymentInfoService { - - private final PaymentInfoDomainService paymentInfoDomainService; - private final PaymentInfoRepository paymentInfoRepository; - private final PaymentInfoSearchRepository paymentInfoSearchRepository; - - @Transactional - public void createPaymentInfo(PaymentReq paymentReq, Member member) { - PaymentInfo paymentInfo = paymentReq.toEntity(member); - paymentInfoRepository.save(paymentInfo); - } - - public List getPaymentsInfo(List merchantsUid, Member member) { - List paymentsInfo = paymentInfoSearchRepository.searchByMerchantsUidAndEmail( - merchantsUid, - member.getEmail() - ); - - return paymentsInfo.stream() - .map(PaymentRes::toDto) - .toList(); - } - - public void validatePaymentInfo(PaymentReq paymentReq) { - paymentInfoDomainService.validatePaymentInfo(paymentReq.itemName(), paymentReq.amount()); - validateImpUid(paymentReq.impUid()); - validateMerchantUid(paymentReq.merchantUid()); - } - - - private void validateImpUid(String impUid) { - if (paymentInfoRepository.existsByImpUid(impUid)) { - throw new DuplicatedException(ErrorStatus.PAYMENT_IMP_UID_DUPLICATED); - } - } - - private void validateMerchantUid(String merchantUid) { - if (paymentInfoRepository.existsByMerchantUid(merchantUid)) { - throw new DuplicatedException(ErrorStatus.PAYMENT_MERCHANT_UID_DUPLICATED); - } - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/application/PaymentService.java b/module-application/app-api/src/main/java/shop/jtoon/payment/application/PaymentService.java deleted file mode 100644 index 2e68bc6..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/application/PaymentService.java +++ /dev/null @@ -1,55 +0,0 @@ -package shop.jtoon.payment.application; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import shop.jtoon.dto.MemberDto; -import shop.jtoon.entity.Member; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.payment.request.CancelReq; -import shop.jtoon.payment.request.ConditionReq; -import shop.jtoon.payment.request.PaymentReq; -import shop.jtoon.payment.response.PaymentRes; -import shop.jtoon.service.IamportService; - -import java.math.BigDecimal; -import java.util.List; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class PaymentService { - - private final IamportService iamportService; - private final PaymentInfoService paymentInfoService; - private final MemberCookieService memberCookieService; - private final MemberService memberService; - - @Transactional - public BigDecimal validateAndCreatePayment(PaymentReq paymentReq, MemberDto memberDto) { - Member member = memberService.findByEmail(memberDto.email()); - iamportService.validateIamport(paymentReq.impUid(), paymentReq.amount()); - paymentInfoService.validatePaymentInfo(paymentReq); - paymentInfoService.createPaymentInfo(paymentReq, member); - memberCookieService.createMemberCookie(paymentReq.itemName(), member); - - return paymentReq.amount(); - } - - @Transactional - public void cancelPayment(CancelReq cancelReq) { - iamportService.validateIamport(cancelReq.impUid(), cancelReq.checksum()); - iamportService.cancelIamport( - cancelReq.impUid(), - cancelReq.reason(), - cancelReq.checksum(), - cancelReq.refundHolder() - ); - } - - public List getPayments(ConditionReq conditionReq, MemberDto memberDto) { - Member member = memberService.findByEmail(memberDto.email()); - - return paymentInfoService.getPaymentsInfo(conditionReq.merchantsUid(), member); - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/request/PaymentReq.java b/module-application/app-api/src/main/java/shop/jtoon/payment/request/PaymentReq.java deleted file mode 100644 index b84dea1..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/request/PaymentReq.java +++ /dev/null @@ -1,36 +0,0 @@ -package shop.jtoon.payment.request; - -import jakarta.validation.constraints.*; -import lombok.Builder; -import shop.jtoon.entity.CookieItem; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PaymentInfo; - -import java.math.BigDecimal; - -import static shop.jtoon.util.RegExp.EMAIL_PATTERN; -import static shop.jtoon.util.RegExp.PHONE_PATTERN; - -@Builder -public record PaymentReq( - @NotBlank String impUid, - @NotBlank String merchantUid, - @NotBlank String payMethod, - @NotBlank String itemName, - @NotNull @DecimalMin("1") BigDecimal amount, - @Pattern(regexp = EMAIL_PATTERN) String buyerEmail, - @NotBlank @Size(max = 10) String buyerName, - @Pattern(regexp = PHONE_PATTERN) String buyerPhone -) { - - public PaymentInfo toEntity(Member member) { - return PaymentInfo.builder() - .impUid(this.impUid) - .merchantUid(this.merchantUid) - .payMethod(this.payMethod) - .cookieItem(CookieItem.from(this.itemName)) - .amount(this.amount) - .member(member) - .build(); - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/payment/response/PaymentRes.java b/module-application/app-api/src/main/java/shop/jtoon/payment/response/PaymentRes.java deleted file mode 100644 index bf4d696..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/payment/response/PaymentRes.java +++ /dev/null @@ -1,25 +0,0 @@ -package shop.jtoon.payment.response; - -import lombok.Builder; -import shop.jtoon.entity.PaymentInfo; - -import java.math.BigDecimal; -import java.time.LocalDateTime; - -@Builder -public record PaymentRes( - String itemName, - int itemCount, - BigDecimal amount, - LocalDateTime createdAt -) { - - public static PaymentRes toDto(PaymentInfo paymentInfo) { - return PaymentRes.builder() - .itemName(paymentInfo.getCookieItem().getItemName()) - .itemCount(paymentInfo.getCookieItem().getCount()) - .amount(paymentInfo.getAmount()) - .createdAt(paymentInfo.getCreatedAt()) - .build(); - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java b/module-application/app-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java deleted file mode 100644 index 11d5f62..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/application/EpisodeService.java +++ /dev/null @@ -1,106 +0,0 @@ -package shop.jtoon.webtoon.application; - -import static shop.jtoon.common.ImageType.*; -import static shop.jtoon.type.ErrorStatus.*; - -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.UploadImageDto; -import shop.jtoon.entity.Episode; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PurchasedEpisode; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.payment.application.MemberCookieService; -import shop.jtoon.repository.EpisodeRepository; -import shop.jtoon.repository.EpisodeSearchRepository; -import shop.jtoon.repository.PurchasedEpisodeRepository; -import shop.jtoon.response.EpisodeInfoRes; -import shop.jtoon.response.EpisodeItemRes; -import shop.jtoon.service.S3Service; -import shop.jtoon.webtoon.request.CreateEpisodeReq; -import shop.jtoon.webtoon.request.GetEpisodesReq; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class EpisodeService { - - private final MemberService memberService; - private final MemberCookieService memberCookieService; - private final WebtoonService webtoonService; - private final S3Service s3Service; - private final EpisodeRepository episodeRepository; - private final EpisodeSearchRepository episodeSearchRepository; - private final PurchasedEpisodeRepository purchasedEpisodeRepository; - - @Transactional - public void createEpisode( - Long memberId, - Long webtoonId, - MultipartFile mainImage, - MultipartFile thumbnailImage, - CreateEpisodeReq request - ) { - Webtoon webtoon = webtoonService.getWebtoonById(webtoonId); - webtoon.validateAuthor(memberId); - validateDuplicateNo(webtoon, request.no()); - UploadImageDto uploadMainImageDto = request.toUploadImageDto(EPISODE_MAIN, webtoon.getTitle(), mainImage); - UploadImageDto uploadThumbnailImageDto = request.toUploadImageDto( - EPISODE_THUMBNAIL, - webtoon.getTitle(), - thumbnailImage - ); - String mainUrl = s3Service.uploadImage(uploadMainImageDto); - String thumbnailUrl = s3Service.uploadImage(uploadThumbnailImageDto); - - try { - Episode episode = request.toEntity(webtoon, mainUrl, thumbnailUrl); - episodeRepository.save(episode); - } catch (RuntimeException e) { - s3Service.deleteImage(mainUrl); - s3Service.deleteImage(thumbnailUrl); - throw new InvalidRequestException(EPISODE_CREATE_FAIL); - } - } - - public List getEpisodes(Long webtoonId, GetEpisodesReq request) { - return episodeSearchRepository.getEpisodes(webtoonId, request.getSize(), request.getOffset()) - .stream() - .map(EpisodeItemRes::from) - .toList(); - } - - public EpisodeInfoRes getEpisode(Long episodeId) { - Episode episode = getEpisodeById(episodeId); - return EpisodeInfoRes.from(episode); - } - - @Transactional - public void purchaseEpisode(Long memberId, Long episodeId) { - Member member = memberService.findById(memberId); - Episode episode = getEpisodeById(episodeId); - memberCookieService.useCookie(episode.getCookieCount(), member); - PurchasedEpisode purchasedEpisode = PurchasedEpisode.create(member, episode); - purchasedEpisodeRepository.save(purchasedEpisode); - } - - private Episode getEpisodeById(Long episodeId) { - return episodeRepository.findById(episodeId) - .orElseThrow(() -> new NotFoundException(EPISODE_NOT_FOUND)); - } - - private void validateDuplicateNo(Webtoon webtoon, int no) { - if (episodeRepository.existsByWebtoonAndNo(webtoon, no)) { - throw new DuplicatedException(EPISODE_NUMBER_DUPLICATED); - } - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java b/module-application/app-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java deleted file mode 100644 index 229b81b..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/application/WebtoonService.java +++ /dev/null @@ -1,108 +0,0 @@ -package shop.jtoon.webtoon.application; - -import static java.util.stream.Collectors.*; -import static shop.jtoon.common.ImageType.*; -import static shop.jtoon.type.ErrorStatus.*; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import shop.jtoon.dto.UploadImageDto; -import shop.jtoon.entity.DayOfWeekWebtoon; -import shop.jtoon.entity.GenreWebtoon; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.entity.enums.DayOfWeek; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.repository.DayOfWeekWebtoonRepository; -import shop.jtoon.repository.GenreWebtoonRepository; -import shop.jtoon.repository.WebtoonRepository; -import shop.jtoon.repository.WebtoonSearchRepository; -import shop.jtoon.response.GenreRes; -import shop.jtoon.response.WebtoonInfoRes; -import shop.jtoon.response.WebtoonItemRes; -import shop.jtoon.service.S3Service; -import shop.jtoon.webtoon.request.CreateWebtoonReq; -import shop.jtoon.webtoon.request.GetWebtoonsReq; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class WebtoonService { - - private final MemberService memberService; - private final S3Service s3Service; - private final WebtoonRepository webtoonRepository; - private final WebtoonSearchRepository webtoonSearchRepository; - private final DayOfWeekWebtoonRepository dayOfWeekWebtoonRepository; - private final GenreWebtoonRepository genreWebtoonRepository; - - @Transactional - public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWebtoonReq request) { - Member member = memberService.findById(memberId); - validateDuplicateTitle(request.title()); - UploadImageDto uploadImageDto = request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage); - String thumbnailUrl = s3Service.uploadImage(uploadImageDto); - - try { - Webtoon webtoon = request.toWebtoonEntity(member, thumbnailUrl); - List dayOfWeekWebtoons = request.toDayOfWeekWebtoonEntity(webtoon); - List genreWebtoons = request.toGenreWebtoonEntity(webtoon); - webtoonRepository.save(webtoon); - dayOfWeekWebtoonRepository.saveAll(dayOfWeekWebtoons); - genreWebtoonRepository.saveAll(genreWebtoons); - } catch (RuntimeException e) { - s3Service.deleteImage(thumbnailUrl); - throw new InvalidRequestException(WEBTOON_CREATE_FAIL); - } - } - - public Map> getWebtoons(GetWebtoonsReq request) { - return webtoonSearchRepository.findWebtoons(request.day(), request.keyword()) - .stream() - .collect(groupingBy( - DayOfWeekWebtoon::getDayOfWeek, - mapping(dayOfWeekWebtoon -> WebtoonItemRes.from(dayOfWeekWebtoon.getWebtoon()), toList()) - )); - } - - public WebtoonInfoRes getWebtoon(Long webtoonId) { - Webtoon webtoon = getWebtoonById(webtoonId); - List dayOfWeeks = getDayOfWeeks(webtoon); - List genres = getGenres(webtoon); - - return WebtoonInfoRes.of(webtoon, dayOfWeeks, genres); - } - - public Webtoon getWebtoonById(Long webtoonId) { - return webtoonRepository.findById(webtoonId) - .orElseThrow(() -> new NotFoundException(WEBTOON_NOT_FOUND)); - } - - private List getDayOfWeeks(Webtoon webtoon) { - return dayOfWeekWebtoonRepository.findByWebtoon(webtoon) - .stream() - .map(DayOfWeekWebtoon::getDayOfWeekName) - .toList(); - } - - private List getGenres(Webtoon webtoon) { - return genreWebtoonRepository.findByWebtoon(webtoon) - .stream() - .map(GenreRes::from) - .toList(); - } - - private void validateDuplicateTitle(String title) { - if (webtoonRepository.existsByTitle(title)) { - throw new DuplicatedException(WEBTOON_TITLE_DUPLICATED); - } - } -} diff --git a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/GetWebtoonsReq.java b/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/GetWebtoonsReq.java deleted file mode 100644 index 68c4b53..0000000 --- a/module-application/app-api/src/main/java/shop/jtoon/webtoon/request/GetWebtoonsReq.java +++ /dev/null @@ -1,11 +0,0 @@ -package shop.jtoon.webtoon.request; - -import lombok.Builder; -import shop.jtoon.entity.enums.DayOfWeek; - -@Builder -public record GetWebtoonsReq( - DayOfWeek day, - String keyword -) { -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/config/WithCurrentUser.java b/module-application/app-api/src/test/java/shop/jtoon/config/WithCurrentUser.java deleted file mode 100644 index ac7804d..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/config/WithCurrentUser.java +++ /dev/null @@ -1,34 +0,0 @@ -package shop.jtoon.config; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.test.context.support.WithSecurityContext; -import org.springframework.security.test.context.support.WithSecurityContextFactory; -import shop.jtoon.dto.MemberDto; -import shop.jtoon.factory.MemberFactory; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -import static shop.jtoon.util.SecurityConstant.BLANK; - -@Retention(RetentionPolicy.RUNTIME) -@WithSecurityContext(factory = WithCurrentUser.WithCustomSecurityContextFactory.class) -public @interface WithCurrentUser { - - final class WithCustomSecurityContextFactory implements WithSecurityContextFactory { - @Override - public SecurityContext createSecurityContext(WithCurrentUser customUser) { - MemberDto memberDto = MemberFactory.createMemberDto(); - List roles = List.of(new SimpleGrantedAuthority(memberDto.role().toString())); - Authentication auth = new UsernamePasswordAuthenticationToken(memberDto, BLANK, roles); - SecurityContextHolder.getContext().setAuthentication(auth); - - return SecurityContextHolder.getContext(); - } - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/factory/MemberFactory.java b/module-application/app-api/src/test/java/shop/jtoon/factory/MemberFactory.java deleted file mode 100644 index c268db4..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/factory/MemberFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package shop.jtoon.factory; - -import shop.jtoon.dto.MemberDto; -import shop.jtoon.entity.Gender; -import shop.jtoon.entity.LoginType; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Role; - -public class MemberFactory { - - private static String defaultEmail = "test@naver.com"; - - public static Member createMember() { - return Member.builder() - .email(defaultEmail) - .password("Qwe123!!") - .name("홍도산") - .nickname("개발을담다") - .gender(Gender.MALE) - .phone("01012331233") - .role(Role.USER) - .loginType(LoginType.LOCAL) - .build(); - } - - public static MemberDto createMemberDto() { - return MemberDto.builder() - .id(1L) - .email(defaultEmail) - .name("testName") - .nickname("testNickname") - .gender(Gender.MALE) - .phone("01012331233") - .role(Role.USER) - .build(); - } - - public static MemberDto createMemberDto(Long id, Member member) { - return MemberDto.builder() - .id(id) - .email(member.getEmail()) - .name(member.getName()) - .nickname(member.getNickname()) - .gender(member.getGender()) - .phone(member.getPhone()) - .role(member.getRole()) - .build(); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/factory/PaymentFactory.java b/module-application/app-api/src/test/java/shop/jtoon/factory/PaymentFactory.java deleted file mode 100644 index 32d20b6..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/factory/PaymentFactory.java +++ /dev/null @@ -1,80 +0,0 @@ -package shop.jtoon.factory; - -import shop.jtoon.entity.CookieItem; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PaymentInfo; -import shop.jtoon.payment.request.CancelReq; -import shop.jtoon.payment.request.ConditionReq; -import shop.jtoon.payment.request.PaymentReq; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -public class PaymentFactory { - - public static PaymentInfo createPaymentInfo(String impUid, String merchantUid, Member member) { - return PaymentInfo.builder() - .impUid(impUid) - .merchantUid(merchantUid) - .payMethod("card") - .cookieItem(CookieItem.COOKIE_ONE) - .amount(BigDecimal.valueOf(1000)) - .member(member) - .build(); - } - - public static PaymentReq createPaymentReq(String impUid, String merchantUid, String email) { - return PaymentReq.builder() - .impUid(impUid) - .merchantUid(merchantUid) - .payMethod("card") - .itemName(CookieItem.COOKIE_ONE.getItemName()) - .amount(CookieItem.COOKIE_ONE.getAmount()) - .buyerEmail(email) - .buyerName("홍도산") - .buyerPhone("01012311231") - .build(); - } - - public static PaymentReq createPaymentReq(String impUid, String merchantUid, BigDecimal amount, String email) { - return PaymentReq.builder() - .impUid(impUid) - .merchantUid(merchantUid) - .payMethod("card") - .itemName(CookieItem.COOKIE_ONE.getItemName()) - .amount(amount) - .buyerEmail(email) - .buyerName("홍도산") - .buyerPhone("01012311231") - .build(); - } - - public static CancelReq createCancelReq(PaymentReq paymentReq) { - return CancelReq.builder() - .impUid(paymentReq.impUid()) - .merchantUid(paymentReq.merchantUid()) - .reason("reason") - .checksum(CookieItem.COOKIE_ONE.getAmount()) - .refundHolder(paymentReq.buyerEmail()) - .build(); - } - - public static CancelReq createCancelReq(String impUid, String merchantUid, String name) { - return CancelReq.builder() - .impUid(impUid) - .merchantUid(merchantUid) - .reason("reason") - .checksum(CookieItem.COOKIE_ONE.getAmount()) - .refundHolder(name) - .build(); - } - - public static ConditionReq createConditionReq(String... merchantUid) { - List merchantsUid = new ArrayList<>(List.of(merchantUid)); - - return ConditionReq.builder() - .merchantsUid(merchantsUid) - .build(); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/factory/PaymentSnippetFactory.java b/module-application/app-api/src/test/java/shop/jtoon/factory/PaymentSnippetFactory.java deleted file mode 100644 index a0e4640..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/factory/PaymentSnippetFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -package shop.jtoon.factory; - -import org.springframework.restdocs.payload.RequestFieldsSnippet; -import org.springframework.restdocs.payload.ResponseFieldsSnippet; - -import static org.springframework.restdocs.payload.JsonFieldType.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; - -public class PaymentSnippetFactory { - public static final RequestFieldsSnippet PAYMENT_REQUEST = requestFields( - fieldWithPath("impUid").type(STRING).description("포트원 결제 고유번호"), - fieldWithPath("merchantUid").type(STRING).description("가맹점 주문번호"), - fieldWithPath("payMethod").type(STRING).description("결제 방법"), - fieldWithPath("itemName").type(STRING).description("결제된 상품명"), - fieldWithPath("amount").type(NUMBER).description("결제된 금액"), - fieldWithPath("buyerEmail").type(STRING).description("구매자 이메일"), - fieldWithPath("buyerName").type(STRING).description("구매자 이름"), - fieldWithPath("buyerPhone").type(STRING).description("구매자 전화번호") - ); - - public static final RequestFieldsSnippet CANCEL_REQUEST = requestFields( - fieldWithPath("impUid").type(STRING).description("포트원 결제 고유번호"), - fieldWithPath("merchantUid").type(STRING).description("가맹점 주문번호"), - fieldWithPath("reason").type(STRING).description("환불 사유"), - fieldWithPath("checksum").type(NUMBER).description("환불 가능 금액"), - fieldWithPath("refundHolder").type(STRING).description("환불 수령자") - ); - - public static final RequestFieldsSnippet CONDITION_REQUEST = requestFields( - fieldWithPath("merchantsUid").type(ARRAY).description("가맹점 주문번호 리스트") - ); - - public static final ResponseFieldsSnippet CONDITION_RESPONSE = responseFields( - fieldWithPath("[].itemName").type(STRING).description("상품명"), - fieldWithPath("[].itemCount").type(NUMBER).description("상품 수량"), - fieldWithPath("[].amount").type(NUMBER).description("결제 금액"), - fieldWithPath("[].createdAt").type(STRING).description("결제 일시") - ); -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/factory/WebtoonFactory.java b/module-application/app-api/src/test/java/shop/jtoon/factory/WebtoonFactory.java deleted file mode 100644 index 34ccd18..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/factory/WebtoonFactory.java +++ /dev/null @@ -1,98 +0,0 @@ -package shop.jtoon.factory; - -import org.springframework.mock.web.MockMultipartFile; -import shop.jtoon.entity.Episode; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.entity.enums.AgeLimit; -import shop.jtoon.entity.enums.DayOfWeek; -import shop.jtoon.entity.enums.Genre; -import shop.jtoon.webtoon.request.CreateEpisodeReq; -import shop.jtoon.webtoon.request.CreateWebtoonReq; -import shop.jtoon.webtoon.request.GetEpisodesReq; -import shop.jtoon.webtoon.request.GetWebtoonsReq; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Set; - -public class WebtoonFactory { - - public static Webtoon createWebtoon(Member member) { - return Webtoon.builder() - .title("웹툰 제목") - .description("웹툰 설명") - .ageLimit(AgeLimit.ALL) - .thumbnailUrl("https://webtoons/thumbnail") - .cookieCount(3) - .author(member) - .build(); - } - - public static CreateWebtoonReq createWebtoonReq() { - Set dayOfWeeks = new HashSet<>(); - dayOfWeeks.add(DayOfWeek.MON); - Set genres = new HashSet<>(); - genres.add(Genre.ROMANCE); - - return CreateWebtoonReq.builder() - .title("재윤이의 모험일기") - .description("재윤이의 개쩌는 모험이야기, 설레지") - .dayOfWeeks(dayOfWeeks) - .genres(genres) - .ageLimit(AgeLimit.ADULT) - .cookieCount(2) - .build(); - } - - public static GetWebtoonsReq getWebtoonsReq() { - return GetWebtoonsReq.builder() - .day(DayOfWeek.MON) - .keyword("") - .build(); - } - - public static Episode createEpisode(Webtoon webtoon, int no) { - return Episode.builder() - .no(no) - .title("회차 제목") - .mainUrl("https://webtoons/episodes/main") - .thumbnailUrl("https://webtoons/episodes/thumbnail") - .hasComment(true) - .openedAt(LocalDateTime.of(2023, 9, 20, 0, 0, 0)) - .webtoon(webtoon) - .build(); - } - - public static CreateEpisodeReq createEpisodeReq() { - return CreateEpisodeReq.builder() - .no(1) - .title("회차 제목") - .hasComment(true) - .openedAt(LocalDateTime.of(2023, 9, 20, 0, 0, 0)) - .build(); - } - - public static GetEpisodesReq createGetEpisodesReq() { - return GetEpisodesReq.builder().build(); - } - - public static MockMultipartFile createMultipartFile() { - Path path = Paths.get("src/test/resources/test.png"); - - try { - return new MockMultipartFile( - "image", - "test.png", - "image/png", - Files.readAllBytes(path) - ); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/payment/application/MemberCookieServiceTest.java b/module-application/app-api/src/test/java/shop/jtoon/payment/application/MemberCookieServiceTest.java deleted file mode 100644 index 7dadddf..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/payment/application/MemberCookieServiceTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package shop.jtoon.payment.application; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import shop.jtoon.entity.CookieItem; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.MemberCookie; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.factory.MemberFactory; -import shop.jtoon.repository.MemberCookieRepository; -import shop.jtoon.type.ErrorStatus; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; - - -@ExtendWith(MockitoExtension.class) -class MemberCookieServiceTest { - - @InjectMocks - private MemberCookieService memberCookieService; - - @Mock - private MemberCookieRepository memberCookieRepository; - - private Member member; - - @BeforeEach - void beforeEach() { - member = MemberFactory.createMember(); - } - - @DisplayName("createMemberCookie - 한 회원의 쿠키 정보가 성공적으로 저장될 때, - Void") - @Test - void createMemberCookie_Void() { - // When - memberCookieService.createMemberCookie(CookieItem.COOKIE_ONE.getItemName(), member); - - // Then - verify(memberCookieRepository).save(any(MemberCookie.class)); - } - - @DisplayName("useCookie - 해당 회원에 대한 쿠키 정보가 존재하지 않을 때, - NotFoundException (MemberCookie)") - @Test - void useCookie_NotFoundException_MemberCookie() { - // Given - given(memberCookieRepository.findByMember(any(Member.class))).willReturn(Optional.empty()); - - // When, Then - assertThatThrownBy(() -> memberCookieService.useCookie(2, member)) - .isInstanceOf(NotFoundException.class) - .hasMessage(ErrorStatus.MEMBER_COOKIE_NOT_FOUND.getMessage()); - } - - @DisplayName("useCookie - 사용할 쿠키 갯수가 해당 회원이 가진 쿠키 갯수보다 적을 때, - InvalidRequestException") - @Test - void useCookie_InvalidRequestException() { - // Given - MemberCookie memberCookie = MemberCookie.create(0, member); - given(memberCookieRepository.findByMember(any(Member.class))).willReturn(Optional.of(memberCookie)); - - // When, Then - assertThatThrownBy(() -> memberCookieService.useCookie(2, member)) - .isInstanceOf(InvalidRequestException.class) - .hasMessage(ErrorStatus.EPISODE_NOT_ENOUGH_COOKIES.getMessage()); - } - - @DisplayName("useCookie - 쿠키 갯수가 충분할 때, - 남은 쿠키 갯수") - @Test - void useCookie_CookieCount() { - // Given - MemberCookie memberCookie = MemberCookie.create(7, member); - given(memberCookieRepository.findByMember(any(Member.class))).willReturn(Optional.of(memberCookie)); - - // When - int actual = memberCookieService.useCookie(2, member); - - // Then - assertThat(actual).isEqualTo(5); - } - - @DisplayName("getMemberCookieCount - 해당 회원에 대한 쿠키 정보가 존재하지 않을 때, - NotFoundException (MemberCookie)") - @Test - void getMemberCookieCount_NotFoundException_MemberCookie() { - // Given - given(memberCookieRepository.findByMember(any(Member.class))).willReturn(Optional.empty()); - - // When, Then - assertThatThrownBy(() -> memberCookieService.getMemberCookieCount(member)) - .isInstanceOf(NotFoundException.class) - .hasMessage(ErrorStatus.MEMBER_COOKIE_NOT_FOUND.getMessage()); - } - - @DisplayName("getMemberCookieCount - 해당 회원이 가진 쿠키 갯수를 성공적으로 조회, - 쿠키 갯수") - @Test - void getMemberCookieCount_CookieCount() { - // Given - MemberCookie memberCookie = MemberCookie.create(7, member); - given(memberCookieRepository.findByMember(any(Member.class))).willReturn(Optional.of(memberCookie)); - - // When - int actual = memberCookieService.getMemberCookieCount(member); - - // Then - assertThat(actual).isEqualTo(7); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/payment/application/PaymentInfoServiceTest.java b/module-application/app-api/src/test/java/shop/jtoon/payment/application/PaymentInfoServiceTest.java deleted file mode 100644 index 6e179ff..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/payment/application/PaymentInfoServiceTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package shop.jtoon.payment.application; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PaymentInfo; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.factory.MemberFactory; -import shop.jtoon.factory.PaymentFactory; -import shop.jtoon.payment.request.PaymentReq; -import shop.jtoon.payment.response.PaymentRes; -import shop.jtoon.repository.PaymentInfoRepository; -import shop.jtoon.repository.PaymentInfoSearchRepository; -import shop.jtoon.service.PaymentInfoDomainService; -import shop.jtoon.type.ErrorStatus; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; - -@ExtendWith(MockitoExtension.class) -class PaymentInfoServiceTest { - - @InjectMocks - private PaymentInfoService paymentInfoService; - - @Mock - private PaymentInfoDomainService paymentInfoDomainService; - - @Mock - private PaymentInfoRepository paymentInfoRepository; - - @Mock - private PaymentInfoSearchRepository paymentInfoSearchRepository; - - private Member member; - private PaymentReq paymentReq; - - @BeforeEach - void beforeEach() { - member = MemberFactory.createMember(); - paymentReq = PaymentFactory.createPaymentReq("imp123", "mer123", member.getEmail()); - } - - @DisplayName("createPaymentInfo - 한 회원의 결제 정보가 성공적으로 저장될 때, - Void") - @Test - void createPaymentInfo_Void() { - // When - paymentInfoService.createPaymentInfo(paymentReq, member); - - // Then - verify(paymentInfoRepository).save(any(PaymentInfo.class)); - } - - @DisplayName("getPaymentsInfo - 조회 결과가 없을 때, - Empty List") - @Test - void getPaymentsInfo_EmptyList() { - // Given - List paymentInfos = new ArrayList<>(); - given(paymentInfoSearchRepository.searchByMerchantsUidAndEmail(anyList(), any(String.class))) - .willReturn(paymentInfos); - - // When - List actual = paymentInfoService.getPaymentsInfo(Collections.emptyList(), member); - - // Then - assertThat(actual).isEmpty(); - } - - @DisplayName("getPaymentsInfo - 조회 결과가 2개 일때, - PaymentInfoRes List") - @Test - void getPaymentsInfo_PaymentInfoResList() { - // Given - List paymentInfos = new ArrayList<>(); - paymentInfos.add(PaymentFactory.createPaymentInfo("imp123", "mer123", member)); - paymentInfos.add(PaymentFactory.createPaymentInfo("imp456", "mer789", member)); - given(paymentInfoSearchRepository.searchByMerchantsUidAndEmail(anyList(), any(String.class))) - .willReturn(paymentInfos); - - // When - List actual = paymentInfoService.getPaymentsInfo(Collections.emptyList(), member); - - // Then - assertThat(actual).hasSize(2); - } - - @DisplayName("validatePaymentInfo - 결제 검증에 대한 서비스를 잘 호출하는 지 검증 - Void") - @Test - void validatePaymentInfo_Void() { - // When - paymentInfoService.validatePaymentInfo(paymentReq); - - // Then - assertThatNoException() - .isThrownBy(() -> paymentInfoDomainService.validatePaymentInfo(any(String.class), any(BigDecimal.class))); - } - - @DisplayName("validatePaymentInfo - 결제 고유번호가 중복 됐을 때, - DuplicatedException (ImpUid)") - @Test - void validatePaymentInfo_DuplicatedException_ImpUid() { - // Given - given(paymentInfoRepository.existsByImpUid(any(String.class))).willReturn(true); - - // When, Then - assertThatThrownBy(() -> paymentInfoService.validatePaymentInfo(paymentReq)) - .isInstanceOf(DuplicatedException.class) - .hasMessage(ErrorStatus.PAYMENT_IMP_UID_DUPLICATED.getMessage()); - } - - @DisplayName("validatePaymentInfo - 주문번호가 중복 됐을 때, - DuplicatedException (MerchantUid)") - @Test - void validatePaymentInfo_DuplicatedException_MerchantUid() { - // Given - given(paymentInfoRepository.existsByImpUid(any(String.class))).willReturn(false); - given(paymentInfoRepository.existsByMerchantUid(any(String.class))).willReturn(true); - - // When, Then - assertThatThrownBy(() -> paymentInfoService.validatePaymentInfo(paymentReq)) - .isInstanceOf(DuplicatedException.class) - .hasMessage(ErrorStatus.PAYMENT_MERCHANT_UID_DUPLICATED.getMessage()); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/payment/application/PaymentServiceTest.java b/module-application/app-api/src/test/java/shop/jtoon/payment/application/PaymentServiceTest.java deleted file mode 100644 index 864ca9e..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/payment/application/PaymentServiceTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package shop.jtoon.payment.application; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import shop.jtoon.dto.MemberDto; -import shop.jtoon.entity.Member; -import shop.jtoon.factory.MemberFactory; -import shop.jtoon.factory.PaymentFactory; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.payment.request.CancelReq; -import shop.jtoon.payment.request.ConditionReq; -import shop.jtoon.payment.request.PaymentReq; -import shop.jtoon.payment.response.PaymentRes; -import shop.jtoon.service.IamportService; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.verify; - -@ExtendWith(MockitoExtension.class) -class PaymentServiceTest { - - @InjectMocks - private PaymentService paymentService; - - @Mock - private IamportService iamportService; - - @Mock - private PaymentInfoService paymentInfoService; - - @Mock - private MemberCookieService memberCookieService; - - @Mock - private MemberService memberService; - - private PaymentReq paymentReq; - private CancelReq cancelReq; - private Member member; - private MemberDto memberDto; - - @BeforeEach - void beforeEach() { - member = MemberFactory.createMember(); - memberDto = MemberFactory.createMemberDto(1L, member); - paymentReq = PaymentFactory.createPaymentReq("imp123", "mer123", member.getEmail()); - cancelReq = PaymentFactory.createCancelReq(paymentReq); - } - - @DisplayName("validateAndCreatePayment - 결제에 대한 검증 및 생성 서비스를 정상적으로 호출하는 지 검증, - BigDecimal(Amount)") - @Test - void validateAndCreatePayment_Amount() { - // Given - given(memberService.findByEmail(any(String.class))).willReturn(member); - - // When - BigDecimal actual = paymentService.validateAndCreatePayment(paymentReq, memberDto); - - // Then - verify(iamportService).validateIamport(any(String.class), any(BigDecimal.class)); - verify(paymentInfoService).validatePaymentInfo(any(PaymentReq.class)); - verify(paymentInfoService).createPaymentInfo(any(PaymentReq.class), any(Member.class)); - verify(memberCookieService).createMemberCookie(any(String.class), any(Member.class)); - assertThat(actual).isEqualTo(paymentReq.amount()); - } - - @DisplayName("cancelPayment - 결제 취소에 대한 검증 및 취소 서비스를 정상적으로 호출하는 지 검증, - Void ") - @Test - void cancelPayment_Void() { - // When - paymentService.cancelPayment(cancelReq); - - // Then - verify(iamportService).validateIamport(any(String.class), any(BigDecimal.class)); - verify(iamportService).cancelIamport(any(String.class), any(String.class), any(BigDecimal.class), - any(String.class)); - } - - @DisplayName("getPayments - 결제 내역 조회에 대한 서비스를 정상적으로 호출하는 지 검증 - PaymentRes List") - @Test - void getPayments_PaymentRes_List() { - // Given - ConditionReq conditionReq = ConditionReq.builder() - .merchantsUid(Collections.emptyList()) - .build(); - given(memberService.findByEmail(any(String.class))).willReturn(member); - given(paymentInfoService.getPaymentsInfo(anyList(), any(Member.class))).willReturn(Collections.emptyList()); - - // When - List actual = paymentService.getPayments(conditionReq, memberDto); - - // Then - assertThat(actual).isEmpty(); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/payment/presentation/PaymentControllerTest.java b/module-application/app-api/src/test/java/shop/jtoon/payment/presentation/PaymentControllerTest.java deleted file mode 100644 index 5a7c2f0..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/payment/presentation/PaymentControllerTest.java +++ /dev/null @@ -1,348 +0,0 @@ -package shop.jtoon.payment.presentation; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.transaction.annotation.Transactional; -import shop.jtoon.config.WithCurrentUser; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PaymentInfo; -import shop.jtoon.exception.IamportException; -import shop.jtoon.factory.MemberFactory; -import shop.jtoon.factory.PaymentFactory; -import shop.jtoon.factory.PaymentSnippetFactory; -import shop.jtoon.payment.request.CancelReq; -import shop.jtoon.payment.request.ConditionReq; -import shop.jtoon.payment.request.PaymentReq; -import shop.jtoon.repository.MemberRepository; -import shop.jtoon.repository.PaymentInfoRepository; -import shop.jtoon.service.IamportService; - -import java.math.BigDecimal; -import java.util.List; - -import static org.hamcrest.Matchers.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willDoNothing; -import static org.mockito.BDDMockito.willThrow; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static shop.jtoon.type.ErrorStatus.*; - -@Transactional -@SpringBootTest -@AutoConfigureMockMvc -@AutoConfigureRestDocs -@ActiveProfiles("test") -class PaymentControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private PaymentInfoRepository paymentInfoRepository; - - @Autowired - private MemberRepository memberRepository; - - @MockBean - private IamportService iamportService; - - private String impUid; - private String merchantUid; - private Member member; - - @BeforeEach - void beforeEach() { - impUid = "impUid123"; - merchantUid = "merchant123"; - member = MemberFactory.createMember(); - memberRepository.save(member); - } - - @DisplayName("POST: /payments/validation - 결제 승인 후 결제 정보에 대해 검증 및 생성이 성공적으로 됐을 때, - Amount") - @WithCurrentUser - @Test - void validatePayment_Amount() throws Exception { - // Given - PaymentReq paymentReq = PaymentFactory.createPaymentReq(impUid, merchantUid, member.getEmail()); - willDoNothing() - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - - // When, Then - mockMvc.perform(post("/payments/validation") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(paymentReq))) - .andDo(print()) - .andDo(document("payments/validation", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.PAYMENT_REQUEST)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().string(paymentReq.amount().toString())); - } - - @DisplayName("POST: /payments/validation - 아임포트 서버에서 결제 정보가 조회되지 않거나, 조회된 결제 금액과 환불될 금액이 다를 때, - IamportException") - @WithCurrentUser - @Test - void validatePayment_IamportException() throws Exception { - // Given - PaymentReq paymentReq = PaymentFactory.createPaymentReq(impUid, merchantUid, member.getEmail()); - willThrow(IamportException.class) - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - - // When, Then - mockMvc.perform(post("/payments/validation") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(paymentReq))) - .andDo(print()) - .andDo(document("payments/validation", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.PAYMENT_REQUEST)) - .andExpect(status().isInternalServerError()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); - } - - @DisplayName("POST: /payments/validation - 결제된 금액과 서버에서 알고 있는 금액이 다를 때, - InvalidRequestException") - @WithCurrentUser - @Test - void validatePayment_InvalidRequestException() throws Exception { - // Given - PaymentReq paymentReq = PaymentFactory.createPaymentReq(impUid, merchantUid, BigDecimal.ONE, member.getEmail()); - willDoNothing() - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - - // When, Then - mockMvc.perform(post("/payments/validation") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(paymentReq))) - .andDo(print()) - .andDo(document("payments/validation", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.PAYMENT_REQUEST)) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.message").value(PAYMENT_AMOUNT_INVALID.getMessage())); - } - - @DisplayName("POST: /payments/validation - 포트원 결제 고유번호가 중복 됐을 때, - ImpUid DuplicatedException") - @WithCurrentUser - @Test - void validatePayment_ImpUid_DuplicatedException() throws Exception { - // Given - PaymentInfo paymentInfo = PaymentFactory.createPaymentInfo(impUid, merchantUid + "7", member); - PaymentReq paymentReq = PaymentFactory.createPaymentReq(impUid, merchantUid, member.getEmail()); - paymentInfoRepository.save(paymentInfo); - willDoNothing() - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - - // When, Then - mockMvc.perform(post("/payments/validation") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(paymentReq))) - .andDo(print()) - .andDo(document("payments/validation", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.PAYMENT_REQUEST)) - .andExpect(status().isConflict()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.message").value(PAYMENT_IMP_UID_DUPLICATED.getMessage())); - } - - @DisplayName("POST: /payments/validation - 가맹점 주문번호가 중복 됐을 때, - MerchantUid DuplicatedException") - @WithCurrentUser - @Test - void validatePayment_MerchantUid_DuplicatedException() throws Exception { - // Given - PaymentInfo paymentInfo = PaymentFactory.createPaymentInfo(impUid + "7", merchantUid, member); - PaymentReq paymentReq = PaymentFactory.createPaymentReq(impUid, merchantUid, member.getEmail()); - paymentInfoRepository.save(paymentInfo); - willDoNothing() - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - - // When, Then - mockMvc.perform(post("/payments/validation") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(paymentReq))) - .andDo(print()) - .andDo(document("payments/validation", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.PAYMENT_REQUEST)) - .andExpect(status().isConflict()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.message", is(PAYMENT_MERCHANT_UID_DUPLICATED.getMessage()))); - } - - @DisplayName("POST: /payments/cancel - 결제 취소 요청 후 결제 취소 정보에 대해 검증 및 취소 요청이 성공적으로 됐을 때, - Void") - @Test - void cancelPayment_Void() throws Exception { - // Given - CancelReq cancelReq = PaymentFactory.createCancelReq(impUid, merchantUid, member.getName()); - willDoNothing() - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - willDoNothing() - .given(iamportService) - .cancelIamport(any(String.class), any(String.class), any(BigDecimal.class), any(String.class)); - - // When, Then - mockMvc.perform(post("/payments/cancel") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(cancelReq))) - .andDo(print()) - .andDo(document("payments/cancel", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.CANCEL_REQUEST)) - .andExpect(status().isOk()); - } - - @DisplayName("POST: /payments/cancel - 아임포트 서버에서 결제 취소 요청을 실패할 때, - IamportException") - @Test - void cancelPayment_IamportException() throws Exception { - // Given - CancelReq cancelReq = PaymentFactory.createCancelReq(impUid, merchantUid, member.getName()); - willThrow(IamportException.class) - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - - // When, Then - mockMvc.perform(post("/payments/cancel") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(cancelReq))) - .andDo(print()) - .andDo(document("payments/cancel", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.CANCEL_REQUEST)) - .andExpect(status().isInternalServerError()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); - } - - @DisplayName("POST: /payments/cancel - 아임포트 서버에서 조회된 결제 금액과 환불될 금액이 다를 때, - IamportException") - @Test - void cancelPayment_validate_IamportException() throws Exception { - // Given - CancelReq cancelReq = PaymentFactory.createCancelReq(impUid, merchantUid, member.getName()); - willDoNothing() - .given(iamportService) - .validateIamport(any(String.class), any(BigDecimal.class)); - willThrow(IamportException.class) - .given(iamportService) - .cancelIamport(any(String.class), any(String.class), any(BigDecimal.class), any(String.class)); - - // When, Then - mockMvc.perform(post("/payments/cancel") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(cancelReq))) - .andDo(print()) - .andDo(document("payments/cancel", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.CANCEL_REQUEST)) - .andExpect(status().isInternalServerError()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); - } - - @DisplayName("POST: /payments/search - 한 사용자에 대해 모든 결제 내역을 조회 했을 때, - List") - @WithCurrentUser - @Test - void getPayments_PaymentInfo_List() throws Exception { - // Given - ConditionReq conditionReq = PaymentFactory.createConditionReq(); - PaymentInfo paymentInfo1 = PaymentFactory.createPaymentInfo(impUid + "1", merchantUid + "1", member); - PaymentInfo paymentInfo2 = PaymentFactory.createPaymentInfo(impUid + "2", merchantUid + "2", member); - PaymentInfo paymentInfo3 = PaymentFactory.createPaymentInfo(impUid + "3", merchantUid + "3", member); - paymentInfoRepository.saveAll(List.of(paymentInfo1, paymentInfo2, paymentInfo3)); - - // When, Then - mockMvc.perform(post("/payments/search") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(conditionReq))) - .andDo(print()) - .andDo(document("payments/search", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.CONDITION_REQUEST, - PaymentSnippetFactory.CONDITION_RESPONSE)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$", hasSize(3))); - } - - @DisplayName("POST: /payments/search - 한 사용자에 대해 특정 결제 내역을 조회 했을 때, - List") - @WithCurrentUser - @Test - void getPayments_PaymentInfo() throws Exception { - // Given - ConditionReq conditionReq = PaymentFactory.createConditionReq(merchantUid + "1"); - PaymentInfo paymentInfo1 = PaymentFactory.createPaymentInfo(impUid + "1", merchantUid + "1", member); - PaymentInfo paymentInfo2 = PaymentFactory.createPaymentInfo(impUid + "2", merchantUid + "2", member); - paymentInfoRepository.saveAll(List.of(paymentInfo1, paymentInfo2)); - - // When, Then - mockMvc.perform(post("/payments/search") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(conditionReq))) - .andDo(print()) - .andDo(document("payments/search", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.CONDITION_REQUEST, - PaymentSnippetFactory.CONDITION_RESPONSE)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].itemName", is(paymentInfo1.getCookieItem().getItemName()))) - .andExpect(jsonPath("$[0].itemCount", is((paymentInfo1.getCookieItem().getCount())))) - .andExpect(jsonPath("$[0].amount", is(paymentInfo1.getAmount().intValue()))); - } - - @DisplayName("POST: /payments/search - 한 사용자에 대해 존재하지 않는 결제 내역을 조회 했을 때, - Empty List") - @WithCurrentUser - @Test - void getPayments_Empty_List() throws Exception { - // Given - ConditionReq conditionReq = PaymentFactory.createConditionReq("empty merchant"); - PaymentInfo paymentInfo = PaymentFactory.createPaymentInfo(impUid, merchantUid, member); - paymentInfoRepository.save(paymentInfo); - - // When, Then - mockMvc.perform(post("/payments/search") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(conditionReq))) - .andDo(print()) - .andDo(document("payments/search", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - PaymentSnippetFactory.CONDITION_REQUEST)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$", empty())); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/security/application/AuthenticationServiceTest.java b/module-application/app-api/src/test/java/shop/jtoon/security/application/AuthenticationServiceTest.java deleted file mode 100644 index 093c131..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/security/application/AuthenticationServiceTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package shop.jtoon.security.application; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.servlet.HandlerExceptionResolver; -import shop.jtoon.member.application.EmailService; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.member.presentation.MemberController; -import shop.jtoon.payment.application.PaymentService; -import shop.jtoon.payment.presentation.PaymentController; -import shop.jtoon.security.filter.AuthenticationFilter; -import shop.jtoon.security.service.AuthorizationService; -import shop.jtoon.security.service.RefreshTokenService; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest(controllers = {MemberController.class, PaymentController.class}) -class AuthenticationServiceTest { - - @Autowired - MemberController memberController; - - @MockBean - MemberService memberService; - - @MockBean - EmailService emailService; - - @MockBean - RefreshTokenService refreshTokenService; - - MockMvc mockMvc; - - @MockBean - PaymentService paymentService; - - AuthenticationFilter authenticationFilter; - - @Autowired - HandlerExceptionResolver handlerExceptionResolver; - - @BeforeEach - void init() { - authenticationFilter = new AuthenticationFilter( - handlerExceptionResolver, - new AuthorizationService(), - new AuthenticationServiceImpl(memberService), - new JwtServiceImpl(refreshTokenService) - ); - - mockMvc = MockMvcBuilders.standaloneSetup(memberController) - .addFilter(authenticationFilter) - .build(); - } - - @DisplayName("인증없이 접근 성공") - @Test - void noAuthentication_access_success() throws Exception { - // given - mockMvc.perform(get("/members/email-authorization?email=example@naver.com")) - .andExpect(status().isCreated()); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/security/application/JwtServiceImplTest.java b/module-application/app-api/src/test/java/shop/jtoon/security/application/JwtServiceImplTest.java deleted file mode 100644 index 82df283..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/security/application/JwtServiceImplTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package shop.jtoon.security.application; - -import io.jsonwebtoken.security.Keys; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; -import shop.jtoon.exception.UnauthorizedException; -import shop.jtoon.security.service.RefreshTokenService; - -import java.nio.charset.StandardCharsets; -import java.security.Key; - -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.BDDMockito.given; - -@ExtendWith(MockitoExtension.class) -class JwtServiceImplTest { - - @InjectMocks - JwtServiceImpl jwtServiceImpl; - - @Mock - RefreshTokenService refreshTokenService; - - Key secretKey; - - @BeforeEach - void initProperties() { - String test = "testtesttesttesttesttesttesttesttesttesttesttesttesttest"; - ReflectionTestUtils.setField(jwtServiceImpl, "ISS", "test"); - ReflectionTestUtils.setField(jwtServiceImpl, "SALT", test); - ReflectionTestUtils.setField(jwtServiceImpl, "ACCESS_EXPIRE", 600); - ReflectionTestUtils.setField(jwtServiceImpl, "REFRESH_EXPIRE", 10080); - secretKey = Keys.hmacShaKeyFor(test.getBytes(StandardCharsets.UTF_8)); - ReflectionTestUtils.setField(jwtServiceImpl, "secretKey", secretKey); - } - - @DisplayName("Access Token 생성 성공 테스트") - @Test - void accessToken_create_success() { - // given - String email = "abc@gmail.com"; - - // when, then - assertThatNoException().isThrownBy(() -> jwtServiceImpl.generateAccessToken(email)); - } - - @DisplayName("Refresh Token 생성 성공 테스트") - @Test - void refreshToken_create_success() { - // when, then - assertThatNoException().isThrownBy(() -> jwtServiceImpl.generateRefreshToken()); - } - - @DisplayName("Refresh Token이 존재하지 않으면 예외 발생 테스트") - @Test - void refreshToken_notExists_thenThrowException() { - // given - String refreshToken = jwtServiceImpl.generateRefreshToken(); - given(refreshTokenService.hasRefreshToken(refreshToken)).willReturn(Boolean.FALSE); - - // when, then - assertThatThrownBy(() -> jwtServiceImpl.validateRefreshTokenRedis(refreshToken)) - .isInstanceOf(UnauthorizedException.class); - } - - @DisplayName("Refresh Token 업데이트 성공 테스트") - @Disabled - @Test - void updateRefreshToken_success() { - // given - String accessToken = jwtServiceImpl.generateAccessToken("abc@gmail.com"); - String oldRefreshToken = jwtServiceImpl.generateRefreshToken(); - String newRefreshToken = jwtServiceImpl.generateRefreshToken(); - - // when, then - assertThatNoException().isThrownBy(() -> jwtServiceImpl.updateRefreshTokenDb(accessToken, newRefreshToken, oldRefreshToken)); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/webtoon/application/EpisodeServiceTest.java b/module-application/app-api/src/test/java/shop/jtoon/webtoon/application/EpisodeServiceTest.java deleted file mode 100644 index fa5b64a..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/webtoon/application/EpisodeServiceTest.java +++ /dev/null @@ -1,203 +0,0 @@ -package shop.jtoon.webtoon.application; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockMultipartFile; -import shop.jtoon.dto.UploadImageDto; -import shop.jtoon.entity.Episode; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PurchasedEpisode; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.factory.MemberFactory; -import shop.jtoon.factory.WebtoonFactory; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.payment.application.MemberCookieService; -import shop.jtoon.repository.EpisodeRepository; -import shop.jtoon.repository.EpisodeSearchRepository; -import shop.jtoon.repository.PurchasedEpisodeRepository; -import shop.jtoon.response.EpisodeInfoRes; -import shop.jtoon.response.EpisodeItemRes; -import shop.jtoon.service.S3Service; -import shop.jtoon.webtoon.request.CreateEpisodeReq; -import shop.jtoon.webtoon.request.GetEpisodesReq; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class EpisodeServiceTest { - - @InjectMocks - private EpisodeService episodeService; - - @Mock - private MemberService memberService; - - @Mock - private MemberCookieService memberCookieService; - - @Mock - private WebtoonService webtoonService; - - @Mock - private S3Service s3Service; - - @Mock - private EpisodeRepository episodeRepository; - - @Mock - private EpisodeSearchRepository episodeSearchRepository; - - @Mock - private PurchasedEpisodeRepository purchasedEpisodeRepository; - - private Member member; - private Webtoon webtoon; - - @BeforeEach - void beforeEach() { - member = spy(MemberFactory.createMember()); - lenient().when(member.getId()).thenReturn(1L); - webtoon = spy(WebtoonFactory.createWebtoon(member)); - lenient().when(webtoon.getId()).thenReturn(1L); - } - - @DisplayName("createEpisode - 회차 생성 성공, - Void") - @Test - void createEpisode_Void() { - // Given - CreateEpisodeReq request = WebtoonFactory.createEpisodeReq(); - MockMultipartFile image = WebtoonFactory.createMultipartFile(); - given(webtoonService.getWebtoonById(webtoon.getId())).willReturn(webtoon); - given(s3Service.uploadImage(any(UploadImageDto.class))).willReturn("https://webtoons/episodes/image"); - - // When - episodeService.createEpisode(member.getId(), webtoon.getId(), image, image, request); - - // Then - verify(episodeRepository).save(any(Episode.class)); - } - - @DisplayName("createEpisode - 회차 번호 중복, - DuplicatedException") - @Test - void createEpisode_DuplicatedException() { - // Given - CreateEpisodeReq request = WebtoonFactory.createEpisodeReq(); - MockMultipartFile image = WebtoonFactory.createMultipartFile(); - given(webtoonService.getWebtoonById(webtoon.getId())).willReturn(webtoon); - given(episodeRepository.existsByWebtoonAndNo(any(Webtoon.class), anyInt())).willReturn(true); - - // When, Then - assertThatThrownBy(() -> episodeService.createEpisode(member.getId(), webtoon.getId(), image, image, request)) - .isInstanceOf(DuplicatedException.class) - .hasMessage("이미 존재하는 회차 번호입니다."); - } - - @DisplayName("createEpisode - 회차 생성 실패 시 이미지 삭제 서비스 호출, - InvalidRequestException") - @Test - void createEpisode_InvalidRequestException() { - // Given - CreateEpisodeReq request = WebtoonFactory.createEpisodeReq(); - MockMultipartFile image = WebtoonFactory.createMultipartFile(); - given(webtoonService.getWebtoonById(webtoon.getId())).willReturn(webtoon); - given(s3Service.uploadImage(any(UploadImageDto.class))).willReturn("https://webtoons/episodes/image"); - given(episodeRepository.save(any(Episode.class))).willThrow(new RuntimeException()); - - // When, Then - assertThatThrownBy(() -> episodeService.createEpisode(member.getId(), webtoon.getId(), image, image, request)) - .isInstanceOf(InvalidRequestException.class) - .hasMessage("회차 생성에 실패했습니다."); - verify(s3Service, times(2)).deleteImage(anyString()); - } - - @DisplayName("getEpisodes - 조회할 회차 리스트가 없을 때, - Empty List") - @Test - void getEpisodes_EmptyList() { - // Given - GetEpisodesReq request = WebtoonFactory.createGetEpisodesReq(); - List episodes = new ArrayList<>(); - given(episodeSearchRepository.getEpisodes(webtoon.getId(), request.getSize(), request.getOffset())) - .willReturn(episodes); - - // When - List actual = episodeService.getEpisodes(webtoon.getId(), request); - - // Then - assertThat(actual).isEmpty(); - } - - @DisplayName("getEpisodes - 회차 리스트 조회 성공, - List") - @Test - void getEpisodes_EpisodeItemResList() { - // Given - GetEpisodesReq request = WebtoonFactory.createGetEpisodesReq(); - List episodes = new ArrayList<>(); - episodes.add(WebtoonFactory.createEpisode(webtoon, 1)); - episodes.add(WebtoonFactory.createEpisode(webtoon, 2)); - given(episodeSearchRepository.getEpisodes(webtoon.getId(), request.getSize(), request.getOffset())) - .willReturn(episodes); - - // When - List actual = episodeService.getEpisodes(webtoon.getId(), request); - - // Then - assertThat(actual).hasSize(episodes.size()); - } - - @DisplayName("getEpisode - 회차 정보가 존재하지 않을 때, - NotFoundException") - @Test - void getEpisode_NotFoundException() { - // Given - given(episodeRepository.findById(anyLong())).willReturn(Optional.empty()); - - // When, Then - assertThatThrownBy(() -> episodeService.getEpisode(1L)) - .isInstanceOf(NotFoundException.class) - .hasMessage("존재하지 않는 회차입니다."); - } - - @DisplayName("getEpisode - 회차 정보 조회 성공, - EpisodeInfoRes") - @Test - void getEpisode_EpisodeInfoRes() { - // Given - Episode episode = WebtoonFactory.createEpisode(webtoon, 1); - given(episodeRepository.findById(anyLong())).willReturn(Optional.of(episode)); - - // When - EpisodeInfoRes actual = episodeService.getEpisode(1L); - - // Then - assertThat(actual.mainUrl()).isEqualTo("https://webtoons/episodes/main"); - } - - @DisplayName("purchaseEpisode - 회차 구매 성공, - Void") - @Test - void purchaseEpisode_Void() { - // Given - Episode episode = spy(WebtoonFactory.createEpisode(webtoon, 1)); - given(episode.getId()).willReturn(1L); - given(memberService.findById(member.getId())).willReturn(member); - given(episodeRepository.findById(anyLong())).willReturn(Optional.of(episode)); - - // When - episodeService.purchaseEpisode(member.getId(), episode.getId()); - - // Then - verify(memberCookieService).useCookie(episode.getCookieCount(), member); - verify(purchasedEpisodeRepository).save(any(PurchasedEpisode.class)); - } -} diff --git a/module-application/app-api/src/test/java/shop/jtoon/webtoon/application/WebtoonServiceTest.java b/module-application/app-api/src/test/java/shop/jtoon/webtoon/application/WebtoonServiceTest.java deleted file mode 100644 index 79614b2..0000000 --- a/module-application/app-api/src/test/java/shop/jtoon/webtoon/application/WebtoonServiceTest.java +++ /dev/null @@ -1,203 +0,0 @@ -package shop.jtoon.webtoon.application; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentMatchers; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockMultipartFile; -import shop.jtoon.dto.UploadImageDto; -import shop.jtoon.entity.DayOfWeekWebtoon; -import shop.jtoon.entity.GenreWebtoon; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Webtoon; -import shop.jtoon.entity.enums.DayOfWeek; -import shop.jtoon.entity.enums.Genre; -import shop.jtoon.exception.DuplicatedException; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.exception.NotFoundException; -import shop.jtoon.factory.MemberFactory; -import shop.jtoon.factory.WebtoonFactory; -import shop.jtoon.member.application.MemberService; -import shop.jtoon.repository.DayOfWeekWebtoonRepository; -import shop.jtoon.repository.GenreWebtoonRepository; -import shop.jtoon.repository.WebtoonRepository; -import shop.jtoon.repository.WebtoonSearchRepository; -import shop.jtoon.response.WebtoonInfoRes; -import shop.jtoon.response.WebtoonItemRes; -import shop.jtoon.service.S3Service; -import shop.jtoon.type.ErrorStatus; -import shop.jtoon.webtoon.request.CreateWebtoonReq; -import shop.jtoon.webtoon.request.GetWebtoonsReq; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.BDDMockito.*; - -@ExtendWith(MockitoExtension.class) -class WebtoonServiceTest { - - @InjectMocks - private WebtoonService webtoonService; - - @Mock - private MemberService memberService; - - @Mock - private S3Service s3Service; - - @Mock - private WebtoonRepository webtoonRepository; - - @Mock - private WebtoonSearchRepository webtoonSearchRepository; - - @Mock - private DayOfWeekWebtoonRepository dayOfWeekWebtoonRepository; - - @Mock - private GenreWebtoonRepository genreWebtoonRepository; - - private Member member; - - @BeforeEach - void init() { - member = spy(MemberFactory.createMember()); - lenient().when(member.getId()).thenReturn(1L); - } - - @DisplayName("createWebtoon - 웹툰 생성 성공 - Void") - @Test - void createWebtoon_success() { - // Given - CreateWebtoonReq request = WebtoonFactory.createWebtoonReq(); - MockMultipartFile image = WebtoonFactory.createMultipartFile(); - given(memberService.findById(member.getId())).willReturn(member); - given(s3Service.uploadImage(any(UploadImageDto.class))).willReturn("thumbnailUrl"); - - // When - webtoonService.createWebtoon(member.getId(), image, request); - - // Then - verify(webtoonRepository).save(any(Webtoon.class)); - verify(dayOfWeekWebtoonRepository).saveAll(ArgumentMatchers.anyList()); - verify(genreWebtoonRepository).saveAll(ArgumentMatchers.anyList()); - } - - @DisplayName("createWebtoon - 웹툰 생성 실패 시 이미지 삭제 서비스 호출 - InvalidRequestException") - @Test - void createWebtoon_fail_invalid_request() { - // Given - CreateWebtoonReq request = WebtoonFactory.createWebtoonReq(); - MockMultipartFile image = WebtoonFactory.createMultipartFile(); - Long memberId = member.getId(); - given(memberService.findById(member.getId())).willReturn(member); - given(s3Service.uploadImage(any(UploadImageDto.class))).willReturn("thumbnailUrl"); - given(webtoonRepository.save(any(Webtoon.class))).willThrow(new RuntimeException()); - - // When, Then - assertThatThrownBy(() -> webtoonService.createWebtoon(memberId, image, request)) - .isInstanceOf(InvalidRequestException.class) - .hasMessage(ErrorStatus.WEBTOON_CREATE_FAIL.getMessage()); - verify(s3Service).deleteImage("thumbnailUrl"); - } - - @DisplayName("createWebtoon - 웹툰 생성 실패, 제목 중복 - DuplicatedException") - @Test - void createWebtoon_fail_duplicate_title() { - // Given - CreateWebtoonReq request = WebtoonFactory.createWebtoonReq(); - MockMultipartFile image = WebtoonFactory.createMultipartFile(); - Long memberId = member.getId(); - given(webtoonRepository.existsByTitle(any(String.class))).willReturn(true); - - // When, Then - assertThatThrownBy(() -> webtoonService.createWebtoon(memberId, image, request)) - .isInstanceOf(DuplicatedException.class) - .hasMessage(ErrorStatus.WEBTOON_TITLE_DUPLICATED.getMessage()); - } - - @DisplayName("getWebtoons - 웹툰 목록 조회 리스트 없을 때 - Empty Map") - @Test - void getWebtoons_empty_map() { - // Given - GetWebtoonsReq request = WebtoonFactory.getWebtoonsReq(); - Map> actual = new HashMap<>(); - List expect = new ArrayList<>(); - - given(webtoonSearchRepository.findWebtoons(any(DayOfWeek.class), any(String.class))).willReturn(expect); - - // When - actual = webtoonService.getWebtoons(request); - - // Then - assertThat(actual).isEmpty(); - } - - @DisplayName("getWebtoons - 웹툰 목록 조회 성공 - Map") - @Test - void getWebtoons_success() { - // Given - GetWebtoonsReq request = WebtoonFactory.getWebtoonsReq(); - Webtoon webtoon = WebtoonFactory.createWebtoon(member); - Map> actual = new HashMap<>(); - - List expect = new ArrayList<>(); - expect.add(DayOfWeekWebtoon.create(DayOfWeek.MON, webtoon)); - expect.add(DayOfWeekWebtoon.create(DayOfWeek.MON, webtoon)); - expect.add(DayOfWeekWebtoon.create(DayOfWeek.FRI, webtoon)); - - given(webtoonSearchRepository.findWebtoons(any(DayOfWeek.class), any(String.class))).willReturn(expect); - - // When - actual = webtoonService.getWebtoons(request); - - // Then - assertThat(actual).hasSize(2); - assertThat(actual.get(DayOfWeek.MON)).hasSize(2); - assertThat(actual.get(DayOfWeek.FRI)).hasSize(1); - } - - @DisplayName("getWebtoon - 웹툰 단건 조회 성공 - WebtoonInfoRes") - @Test - void getWebtoon_success() { - // Given - Webtoon webtoon = WebtoonFactory.createWebtoon(member); - List dayOfWeekWebtoon = new ArrayList<>(); - List genres = new ArrayList<>(); - dayOfWeekWebtoon.add(DayOfWeekWebtoon.create(DayOfWeek.MON, webtoon)); - dayOfWeekWebtoon.add(DayOfWeekWebtoon.create(DayOfWeek.FRI, webtoon)); - genres.add(GenreWebtoon.create(Genre.ACTION, webtoon)); - - given(webtoonRepository.findById(anyLong())).willReturn(Optional.of(webtoon)); - given(dayOfWeekWebtoonRepository.findByWebtoon(webtoon)).willReturn(dayOfWeekWebtoon); - given(genreWebtoonRepository.findByWebtoon(webtoon)).willReturn(genres); - - // When - WebtoonInfoRes actual = webtoonService.getWebtoon(anyLong()); - - // Then - assertThat(actual.thumbnailUrl()).isEqualTo("https://webtoons/thumbnail"); - assertThat(actual.dayOfWeeks()).hasSize(2); - assertThat(actual.dayOfWeeks().get(0)).isEqualTo(DayOfWeek.MON.toString()); - assertThat(actual.genres()).hasSize(1); - assertThat(actual.genres().get(0).type()).isEqualTo(Genre.ACTION); - } - - @DisplayName("getWebtoon - 웹턴 단건 조회 실패, 웹툰 없음 - NotFoundException") - @Test - void getWebtoon_fail_notfound_webtoon() { - // Given - given(webtoonRepository.findById(anyLong())).willReturn(Optional.empty()); - - // When, Then - assertThatThrownBy(() -> webtoonService.getWebtoon(1L)) - .isInstanceOf(NotFoundException.class) - .hasMessage(ErrorStatus.WEBTOON_NOT_FOUND.getMessage()); - } -} diff --git a/module-application/build.gradle b/module-application/build.gradle deleted file mode 100644 index d7dec73..0000000 --- a/module-application/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -project(':module-application:app-api') { - dependencies { - // Domain Layer - implementation project(':module-domain:domain-member') - implementation project(':module-domain:domain-payment') - implementation project(':module-domain:domain-webtoon') - implementation project(':module-domain:domain-redis') - - // Internal Layer - implementation project(':module-internal:core-web') - implementation project(':module-internal:iamport-client') - implementation project(':module-internal:s3-client') - implementation project(':module-internal:smtp-client') - - // Core Layer - implementation project(':module-core') - } -} diff --git a/module-core/README.md b/module-core/README.md deleted file mode 100644 index dce7497..0000000 --- a/module-core/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Core Layer - -| | **어플리케이션 모듈** | **내부 모듈** | **도메인 모듈** | **공통 모듈** | -|:---------:|:-------------:|:---------:|:----------:|:---------:| -| **사용 가능** | X | X | X | - | - -- 시스템 내 모든 모듈들이 의존할 수 있을만큼 얇은 의존성을 제공해야 하기 때문에, 프로젝트 내 어떠한 모듈도 의존하지 않아야 합니다. -- 즉 순수 Java Class 만 정의할 수 있습니다. - -
- -#### 아래와 같은 클래스를 배치할 수 있습니다. - -> - Type 성격의 DTO -> - 기본적인 Util Class diff --git a/module-domain/README.md b/module-domain/README.md deleted file mode 100644 index 0d3f904..0000000 --- a/module-domain/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Domain Layer - -| | **어플리케이션 모듈** | **내부 모듈** | **도메인 모듈** | **공통 모듈** | -|:---------:|:-------------:|:---------:|:----------:|:---------:| -| **사용 가능** | X | X | - | O | - -- 시스템의 중심 도메인을 다루는 모듈을 이 계층에 배치하였습니다. -- 저장소와 밀접한 중심 도메인을 다루는 계층은 더 견고하고 특별하게 격리되고 관리되어야 하기 때문에 반드시 분리되어야 합니다. -- 이 계층의 모듈은 오로지 도메인에 집중합니다. -- 어떠한 도메인이든 그 도메인이 가져야할 서비스와 무관한 도메인의 비지니스가 있습니다. -- 도메인 중심 설계에 대한 내용으로 견고한 도메인에서부터 프로젝트를 만들어 올라가면 자연스럽게 이러한 구조가 나올 수 있기도 합니다. -- 하나의 모듈은 최대 하나의 인프라스터럭처를 갖는 것은 의존성의 전파를 방지하기 위해서입니다. - -
- -#### 이 계층은 아래와 같은 원칙을 갖습니다. - -> 1) 어플리케이션 서비스 비지니스를 모른다. -> 2) 하나의 모듈은 최대 하나의 인프라스트럭처에 대한 책임만 갖는다. -> 3) 도메인 모듈을 조합한 더 큰 단위의 도메인 모듈이 있을 수 있다. - -#### 아래와 같은 클래스를 배치할 수 있습니다. - -> 1) Domain Entity: Java Class 로 표현된 도메인 Class들이 이곳에 위치한다. -> 2) Domain Repository: 도메인의 조회, 저장, 수정, 삭제 역할을 한다. -> 3) Domain Service: 도메인의 비지니스의 책임을 가진다. diff --git a/module-domain/build.gradle b/module-domain/build.gradle deleted file mode 100644 index 25b1ce4..0000000 --- a/module-domain/build.gradle +++ /dev/null @@ -1,47 +0,0 @@ -project(':module-domain:domain-jpa') { - bootJar.enabled = false - - dependencies { - implementation project(':module-core') - } -} - -project(':module-domain:domain-member') { - bootJar.enabled = false - - dependencies { - api project(':module-domain:domain-jpa') - implementation project(':module-core') - } -} - -project(':module-domain:domain-payment') { - bootJar.enabled = false - - dependencies { - api project(':module-domain:domain-jpa') - implementation project(':module-domain:domain-member') - implementation project(':module-core') - } -} - -project(':module-domain:domain-redis') { - bootJar.enabled = false - - dependencies { - api project(':module-domain:domain-jpa') - implementation project(':module-domain:domain-member') - implementation project(':module-core') - } -} - -project(':module-domain:domain-webtoon') { - bootJar.enabled = false - - dependencies { - api project(':module-domain:domain-jpa') - implementation project(':module-domain:domain-member') - implementation project(':module-domain:domain-payment') - implementation project(':module-core') - } -} diff --git a/module-domain/domain-jpa/build.gradle b/module-domain/domain-jpa/build.gradle deleted file mode 100644 index 857161d..0000000 --- a/module-domain/domain-jpa/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -dependencies { - // MySQL - implementation 'com.mysql:mysql-connector-j:8.0.33' - - // JPA - api 'org.springframework.boot:spring-boot-starter-data-jpa' - - // Querydsl - api 'com.querydsl:querydsl-jpa:5.0.0:jakarta' - annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' - annotationProcessor 'jakarta.annotation:jakarta.annotation-api' - annotationProcessor 'jakarta.persistence:jakarta.persistence-api' -} diff --git a/module-domain/domain-member/build.gradle b/module-domain/domain-member/build.gradle deleted file mode 100644 index 268b7cd..0000000 --- a/module-domain/domain-member/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -dependencies { - // Bean Validation - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' - - // Querydsl - annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' - annotationProcessor 'jakarta.annotation:jakarta.annotation-api' - annotationProcessor 'jakarta.persistence:jakarta.persistence-api' - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' - - // H2 - testImplementation 'com.h2database:h2' -} diff --git a/module-domain/domain-member/src/main/java/shop/jtoon/dto/OAuthSignUpDto.java b/module-domain/domain-member/src/main/java/shop/jtoon/dto/OAuthSignUpDto.java deleted file mode 100644 index 94a77a2..0000000 --- a/module-domain/domain-member/src/main/java/shop/jtoon/dto/OAuthSignUpDto.java +++ /dev/null @@ -1,31 +0,0 @@ -package shop.jtoon.dto; - -import lombok.Builder; -import shop.jtoon.entity.Gender; -import shop.jtoon.entity.LoginType; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Role; - -@Builder -public record OAuthSignUpDto( - String email, - String password, - String name, - String nickname, - String gender, - String phone, - String loginType -) { - public Member toEntity(String encryptedPassword) { - return Member.builder() - .email(email) - .password(encryptedPassword) - .name(name) - .nickname(nickname) - .gender(Gender.from(gender)) - .phone(phone) - .role(Role.USER) - .loginType(LoginType.from(loginType)) - .build(); - } -} diff --git a/module-domain/domain-member/src/test/java/shop/jtoon/MemberDomainApplicationTest.java b/module-domain/domain-member/src/test/java/shop/jtoon/MemberDomainApplicationTest.java deleted file mode 100644 index a8cb8bd..0000000 --- a/module-domain/domain-member/src/test/java/shop/jtoon/MemberDomainApplicationTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package shop.jtoon; - -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class MemberDomainApplicationTest { -} diff --git a/module-domain/domain-member/src/test/java/shop/jtoon/repository/MemberRepositoryTest.java b/module-domain/domain-member/src/test/java/shop/jtoon/repository/MemberRepositoryTest.java deleted file mode 100644 index 274dae9..0000000 --- a/module-domain/domain-member/src/test/java/shop/jtoon/repository/MemberRepositoryTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package shop.jtoon.repository; - -import static org.assertj.core.api.Assertions.*; -import static shop.jtoon.entity.Gender.*; -import static shop.jtoon.entity.LoginType.*; -import static shop.jtoon.entity.Role.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; - -import shop.jtoon.config.JpaConfig; -import shop.jtoon.entity.Gender; -import shop.jtoon.entity.LoginType; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.Role; - -@DataJpaTest -@Import(JpaConfig.class) -class MemberRepositoryTest { - - @Autowired - MemberRepository memberRepository; - - Member member; - - @BeforeEach - void initMember() { - member = Member.builder() - .email("abc@naver.com") - .password("Testing!123") - .name("HI") - .nickname("Unique") - .gender(MALE) - .phone("01012345678") - .role(USER) - .loginType(LOCAL) - .build(); - } - - @Test - @DisplayName("MemberRepository 빈 등록 성공") - void MemberRepository_bean_NotNull() { - assertThat(memberRepository).isNotNull(); - } - - @Test - @DisplayName("멤버 저장 성공") - void MemberRepository_save_success() { - // given, when - Member saved = memberRepository.save(member); - - // then - assertThat(saved.getId()).isNotNull(); - } - - @Nested - @DisplayName("멤버 엔티티 생성 실패 테스트") - class MemberEntityTest { - - String email = "abc@gmail.com"; - String password = "testing!123"; - String name = "HI"; - String nickname = "Unique"; - Gender gender = MALE; - String phone = "01012345678"; - Role role = USER; - LoginType loginType = LOCAL; - - @Test - @DisplayName("멤버 엔티티 이메일 null 테스트") - void Member_email_null_fail() { - // given, when, then - assertThatThrownBy(() -> Member.builder() - .email(null) - .password(password) - .name(name) - .nickname(nickname) - .gender(gender) - .phone(phone) - .role(role) - .loginType(loginType) - .build()).isInstanceOf(NullPointerException.class); - } - - @Test - @DisplayName("멤버 엔티티 비밀번호 null 테스트") - void Member_password_null_fail() { - // given, when, then - assertThatThrownBy(() -> Member.builder() - .email(email) - .password(null) - .name(name) - .nickname(nickname) - .gender(gender) - .phone(phone) - .role(role) - .loginType(loginType) - .build()).isInstanceOf(NullPointerException.class); - } - - @Test - @DisplayName("멤버 엔티티 이름 null 테스트") - void Member_name_null_fail() { - // given, when, then - assertThatThrownBy(() -> Member.builder() - .email(email) - .password(password) - .name(null) - .nickname(nickname) - .gender(gender) - .phone(phone) - .role(role) - .loginType(loginType) - .build()).isInstanceOf(NullPointerException.class); - } - - @Test - @DisplayName("멤버 엔티티 닉네임 null 테스트") - void Member_nickname_null_fail() { - // given, when, then - assertThatThrownBy(() -> Member.builder() - .email(email) - .password(password) - .name(name) - .nickname(null) - .gender(gender) - .phone(phone) - .role(role) - .loginType(loginType) - .build()).isInstanceOf(NullPointerException.class); - } - - @Test - @DisplayName("멤버 엔티티 성별 null 테스트") - void Member_gender_null_fail() { - // given, when, then - assertThatThrownBy(() -> Member.builder() - .email(email) - .password(password) - .name(name) - .nickname(nickname) - .gender(null) - .phone(phone) - .role(role) - .loginType(loginType) - .build()).isInstanceOf(NullPointerException.class); - } - - @Test - @DisplayName("멤버 엔티티 전화번호 null 테스트") - void Member_phone_null_fail() { - // given, when, then - assertThatThrownBy(() -> Member.builder() - .email(email) - .password(password) - .name(name) - .nickname(nickname) - .gender(gender) - .phone(null) - .role(role) - .loginType(loginType) - .build()).isInstanceOf(NullPointerException.class); - } - } -} diff --git a/module-domain/domain-payment/build.gradle b/module-domain/domain-payment/build.gradle deleted file mode 100644 index 4951fe5..0000000 --- a/module-domain/domain-payment/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -dependencies { - // Bean Validation - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // Querydsl - annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' - annotationProcessor 'jakarta.annotation:jakarta.annotation-api' - annotationProcessor 'jakarta.persistence:jakarta.persistence-api' - - // H2 - runtimeOnly 'com.h2database:h2' - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/module-domain/domain-payment/src/main/java/shop/jtoon/service/PaymentInfoDomainService.java b/module-domain/domain-payment/src/main/java/shop/jtoon/service/PaymentInfoDomainService.java deleted file mode 100644 index 37679a6..0000000 --- a/module-domain/domain-payment/src/main/java/shop/jtoon/service/PaymentInfoDomainService.java +++ /dev/null @@ -1,27 +0,0 @@ -package shop.jtoon.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import shop.jtoon.entity.CookieItem; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.type.ErrorStatus; - -import java.math.BigDecimal; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class PaymentInfoDomainService { - - public void validatePaymentInfo(String itemName, BigDecimal amount) { - CookieItem cookieItem = CookieItem.from(itemName); - validateAmount(amount, cookieItem.getAmount()); - } - - private void validateAmount(BigDecimal amount, BigDecimal cookieAmount) { - if (!amount.equals(cookieAmount)) { - throw new InvalidRequestException(ErrorStatus.PAYMENT_AMOUNT_INVALID); - } - } -} diff --git a/module-domain/domain-payment/src/test/java/shop/jtoon/DomainPaymentApplicationTest.java b/module-domain/domain-payment/src/test/java/shop/jtoon/DomainPaymentApplicationTest.java deleted file mode 100644 index 210c4fa..0000000 --- a/module-domain/domain-payment/src/test/java/shop/jtoon/DomainPaymentApplicationTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package shop.jtoon; - -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -class DomainPaymentApplicationTest { -} diff --git a/module-domain/domain-payment/src/test/java/shop/jtoon/factory/CreatorFactory.java b/module-domain/domain-payment/src/test/java/shop/jtoon/factory/CreatorFactory.java deleted file mode 100644 index a054533..0000000 --- a/module-domain/domain-payment/src/test/java/shop/jtoon/factory/CreatorFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -package shop.jtoon.factory; - -import shop.jtoon.entity.*; - -import java.math.BigDecimal; - -public class CreatorFactory { - - public static PaymentInfo createPaymentInfo(String impUid, String merchantUid, Member member) { - return PaymentInfo.builder() - .impUid(impUid) - .merchantUid(merchantUid) - .payMethod("card") - .cookieItem(CookieItem.COOKIE_ONE) - .amount(BigDecimal.valueOf(1000)) - .member(member) - .build(); - } - - public static Member createMember(String email) { - return Member.builder() - .email(email) - .password("Qwe123!!") - .name("홍도산") - .nickname("개발을담다") - .gender(Gender.MALE) - .phone("01012331233") - .role(Role.USER) - .loginType(LoginType.LOCAL) - .build(); - } -} diff --git a/module-domain/domain-payment/src/test/java/shop/jtoon/repository/PaymentInfoSearchRepositoryTest.java b/module-domain/domain-payment/src/test/java/shop/jtoon/repository/PaymentInfoSearchRepositoryTest.java deleted file mode 100644 index 6d36d3f..0000000 --- a/module-domain/domain-payment/src/test/java/shop/jtoon/repository/PaymentInfoSearchRepositoryTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package shop.jtoon.repository; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; -import shop.jtoon.config.JpaConfig; -import shop.jtoon.entity.Member; -import shop.jtoon.entity.PaymentInfo; -import shop.jtoon.factory.CreatorFactory; - -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -@DataJpaTest -@Import({JpaConfig.class, PaymentInfoSearchRepository.class}) -class PaymentInfoSearchRepositoryTest { - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private PaymentInfoRepository paymentInfoRepository; - - @Autowired - private PaymentInfoSearchRepository paymentInfoSearchRepository; - - private Member member; - private PaymentInfo paymentInfo1; - private PaymentInfo paymentInfo2; - - @BeforeEach - void beforeEach() { - member = CreatorFactory.createMember("example123@naver.com"); - memberRepository.save(member); - paymentInfo1 = CreatorFactory.createPaymentInfo("imp123", "mer123", member); - paymentInfoRepository.save(paymentInfo1); - paymentInfo2 = CreatorFactory.createPaymentInfo("imp789", "mer789", member); - paymentInfoRepository.save(paymentInfo2); - } - - @DisplayName("paymentInfoSearchRepository - Bean 등록 여부 테스트 - NotNull") - @Test - void paymentInfoSearchRepository_NotNull() { - // Then - assertThat(paymentInfoSearchRepository).isNotNull(); - } - - @DisplayName("searchByMerchantsUidAndEmail - 해당 이메일과 일치하는 회원의 결제 정보 조회 - 모든 결제 정보") - @Test - void searchByMerchantsUidAndEmail_PaymentInfo_List1() { - // When - List actual = paymentInfoSearchRepository - .searchByMerchantsUidAndEmail(null, member.getEmail()); - - // Then - assertThat(actual).hasSize(2); - } - - @DisplayName("searchByMerchantsUidAndEmail - 해당 이메일과 일치하고 주문번호들과 일치하는 결제 정보 조회 - 결제 정보 2건") - @Test - void searchByMerchantsUidAndEmail_PaymentInfo_List2() { - //Given - List merchantsUid = new ArrayList<>(); - merchantsUid.add(paymentInfo1.getMerchantUid()); - merchantsUid.add(paymentInfo2.getMerchantUid()); - - // When - List actual = paymentInfoSearchRepository - .searchByMerchantsUidAndEmail(merchantsUid, member.getEmail()); - - // Then - assertThat(actual).hasSize(2); - } - - @DisplayName("searchByMerchantsUidAndEmail - 해당 이메일과 일치하고 주문번호와 일치하는 결제 정보 조회 - 결제 정보 1건") - @Test - void searchByMerchantsUidAndEmail_PaymentInfo() { - //Given - List merchantsUid = new ArrayList<>(); - merchantsUid.add(paymentInfo1.getMerchantUid()); - - // When - List actual = paymentInfoSearchRepository - .searchByMerchantsUidAndEmail(merchantsUid, member.getEmail()); - - // Then - assertThat(actual).hasSize(1); - } - - @DisplayName("searchByMerchantsUidAndEmail - 해당 이메일과 일치하지만 주문번호가 일치하지 않는 경우 - 0건") - @Test - void searchByMerchantsUidAndEmail_PaymentInfo_Null1() { - //Given - List merchantsUid = new ArrayList<>(); - merchantsUid.add(paymentInfo1.getMerchantUid()); - - // When - List actual = paymentInfoSearchRepository - .searchByMerchantsUidAndEmail(merchantsUid, "notfoundemail@naver.com"); - - // Then - assertThat(actual).isEmpty(); - } - - @DisplayName("searchByMerchantsUidAndEmail - 해당 이메일과 일치하지 않지만 주문번호가 일치하는 경우 - 0건") - @Test - void searchByMerchantsUidAndEmail_PaymentInfo_Null2() { - //Given - List merchantsUid = new ArrayList<>(); - merchantsUid.add("notfoundmerchantuid"); - - // When - List actual = paymentInfoSearchRepository - .searchByMerchantsUidAndEmail(merchantsUid, member.getEmail()); - - // Then - assertThat(actual).isEmpty(); - } -} diff --git a/module-domain/domain-payment/src/test/java/shop/jtoon/service/PaymentInfoDomainServiceTest.java b/module-domain/domain-payment/src/test/java/shop/jtoon/service/PaymentInfoDomainServiceTest.java deleted file mode 100644 index e72bce0..0000000 --- a/module-domain/domain-payment/src/test/java/shop/jtoon/service/PaymentInfoDomainServiceTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package shop.jtoon.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.junit.jupiter.MockitoExtension; -import shop.jtoon.entity.CookieItem; -import shop.jtoon.exception.InvalidRequestException; -import shop.jtoon.type.ErrorStatus; - -import java.math.BigDecimal; - -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@ExtendWith(MockitoExtension.class) -class PaymentInfoDomainServiceTest { - - @InjectMocks - private PaymentInfoDomainService paymentInfoDomainService; - - private String itemName; - private BigDecimal amount; - - @BeforeEach - void beforeEach() { - itemName = CookieItem.COOKIE_ONE.getItemName(); - amount = CookieItem.COOKIE_ONE.getAmount(); - } - - @DisplayName("validatePaymentInfo - 결제 정보의 쿠키 가격과 실제 서버에 존재하는 쿠키 가격이 같을 때, - Void") - @Test - void validatePaymentInfo_Void() { - // When, Then - assertThatNoException() - .isThrownBy(() -> paymentInfoDomainService.validatePaymentInfo(itemName, amount)); - } - - @DisplayName("validatePaymentInfo - 결제 정보의 쿠키 가격과 실제 서버에서 알고 있는 쿠키 가격이 다를 때, - InvalidRequestException") - @Test - void validatePaymentInfo_InvalidRequestException() { - // When, Then - assertThatThrownBy(() -> paymentInfoDomainService.validatePaymentInfo(itemName, BigDecimal.ONE)) - .isInstanceOf(InvalidRequestException.class) - .hasMessage(ErrorStatus.PAYMENT_AMOUNT_INVALID.getMessage()); - } -} diff --git a/module-domain/domain-redis/build.gradle b/module-domain/domain-redis/build.gradle deleted file mode 100644 index fea6135..0000000 --- a/module-domain/domain-redis/build.gradle +++ /dev/null @@ -1,15 +0,0 @@ -dependencies { - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' - - // JWT - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' - - // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' -} diff --git a/module-domain/domain-webtoon/build.gradle b/module-domain/domain-webtoon/build.gradle deleted file mode 100644 index 5e9b79b..0000000 --- a/module-domain/domain-webtoon/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -dependencies { - // Bean Validation - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // Querydsl - annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' - annotationProcessor 'jakarta.annotation:jakarta.annotation-api' - annotationProcessor 'jakarta.persistence:jakarta.persistence-api' - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/WebtoonSearchRepository.java b/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/WebtoonSearchRepository.java deleted file mode 100644 index 077b17e..0000000 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/repository/WebtoonSearchRepository.java +++ /dev/null @@ -1,40 +0,0 @@ -package shop.jtoon.repository; - -import static shop.jtoon.entity.QDayOfWeekWebtoon.*; -import static shop.jtoon.entity.QMember.*; -import static shop.jtoon.entity.QWebtoon.*; - -import java.util.List; - -import org.springframework.stereotype.Repository; - -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; - -import lombok.RequiredArgsConstructor; -import shop.jtoon.entity.DayOfWeekWebtoon; -import shop.jtoon.entity.enums.DayOfWeek; -import shop.jtoon.util.DynamicQuery; - -@Repository -@RequiredArgsConstructor -public class WebtoonSearchRepository { - - private final JPAQueryFactory jpaQueryFactory; - - public List findWebtoons(DayOfWeek dayOfWeek, String keyword) { - return jpaQueryFactory.selectFrom(dayOfWeekWebtoon) - .join(dayOfWeekWebtoon.webtoon, webtoon) - .join(webtoon.author, member) - .where( - DynamicQuery.generateEq(dayOfWeek, dayOfWeekWebtoon.dayOfWeek::eq), - DynamicQuery.generateEq(keyword, this::containsKeyword) - ) - .fetch(); - } - - private BooleanExpression containsKeyword(String keyword) { - return webtoon.title.contains(keyword) - .or(webtoon.author.nickname.contains(keyword)); - } -} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/AuthorRes.java b/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/AuthorRes.java deleted file mode 100644 index e76b429..0000000 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/AuthorRes.java +++ /dev/null @@ -1,18 +0,0 @@ -package shop.jtoon.response; - -import lombok.Builder; -import shop.jtoon.entity.Member; - -@Builder -public record AuthorRes( - Long id, - String nickname -) { - - public static AuthorRes from(Member author) { - return AuthorRes.builder() - .id(author.getId()) - .nickname(author.getNickname()) - .build(); - } -} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeInfoRes.java b/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeInfoRes.java deleted file mode 100644 index 675f966..0000000 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/EpisodeInfoRes.java +++ /dev/null @@ -1,16 +0,0 @@ -package shop.jtoon.response; - -import lombok.Builder; -import shop.jtoon.entity.Episode; - -@Builder -public record EpisodeInfoRes( - String mainUrl -) { - - public static EpisodeInfoRes from(Episode episode) { - return EpisodeInfoRes.builder() - .mainUrl(episode.getMainUrl()) - .build(); - } -} diff --git a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/GenreRes.java b/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/GenreRes.java deleted file mode 100644 index cbb8c1a..0000000 --- a/module-domain/domain-webtoon/src/main/java/shop/jtoon/response/GenreRes.java +++ /dev/null @@ -1,19 +0,0 @@ -package shop.jtoon.response; - -import lombok.Builder; -import shop.jtoon.entity.GenreWebtoon; -import shop.jtoon.entity.enums.Genre; - -@Builder -public record GenreRes( - Genre type, - String name -) { - - public static GenreRes from(GenreWebtoon genreWebtoon) { - return GenreRes.builder() - .type(genreWebtoon.getGenre()) - .name(genreWebtoon.getGenre().getText()) - .build(); - } -} diff --git a/module-internal/README.md b/module-internal/README.md deleted file mode 100644 index 9f8c43f..0000000 --- a/module-internal/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Internal Layer - -| | **어플리케이션 모듈** | **내부 모듈** | **도메인 모듈** | **공통 모듈** | -|:---------:|:-------------:|:---------:|:----------:|:---------:| -| **사용 가능** | X | - | X | O | - -- 저장소, 도메인 외 시스템에서 필요한 모듈들은 이 계층에 속하게 됩니다. -- 이 계층은 시스템 전체적인 기능을 서포트하기 위한 기능 모듈이 만들어질 수 있습니다. -- 이 계층 역시 프로젝트 안의 어떠한 실행 가능한 어플리케이션에서도 독립 사용 가능한 모듈이 위치되어야 하므로, 도메인 계층을 의존하지 않습니다. - -
- -#### 이 계층은 아래와 같은 원칙을 갖습니다. - -> 1) 어플리케이션 비지니스를 모른다. -> 2) 도메인 비지니스를 모른다. - -#### 아래와 같은 모듈들을 배치할 수 있습니다. - -> - core-web: web 설정을 사용하는 프로젝트에서 사용할 수 있는 모듈로, 주로 Web Filter 를 이용한 보안, 로깅 등으로 활용되며, 웹에 대한 필수적인 공통 설정을 하기도 한다. -> - xxx-client: 외부의 xxx 시스템과 통신을 책임지는 모듈로, 각 외부 시스템별로 따로 모듈을 만든다. 이 모듈은 비지니스와 관계없이 요청과 응답을 할 수 있는 사용성을 제공한다. diff --git a/module-internal/build.gradle b/module-internal/build.gradle deleted file mode 100644 index 4b9bfbd..0000000 --- a/module-internal/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -project(':module-internal:core-web') { - bootJar.enabled = false - - dependencies { - implementation project(':module-core') - } -} - -project(':module-internal:s3-client') { - bootJar.enabled = false - - dependencies { - implementation project(':module-core') - } -} - -project(':module-internal:iamport-client') { - bootJar.enabled = false - - dependencies { - implementation project(':module-core') - } -} - -project(':module-internal:smtp-client') { - bootJar.enabled = false - - dependencies { - implementation project(':module-core') - } -} diff --git a/module-internal/smtp-client/build.gradle b/module-internal/smtp-client/build.gradle deleted file mode 100644 index 4ca5877..0000000 --- a/module-internal/smtp-client/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -dependencies { - // Bean Validation - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // SMTP - implementation 'org.springframework.boot:spring-boot-starter-mail' - - // Test - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} diff --git a/settings.gradle b/settings.gradle index fa694a6..f910ea2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,19 +1,24 @@ +pluginManagement { + resolutionStrategy { + eachPlugin { + if (requested.id.id == "org.springframework.boot") useVersion(springBootVersion) + if (requested.id.id == "io.spring.dependency-management") useVersion(springDependencyManagementVersion) + } + } +} rootProject.name = 'jtoon' -include 'module-application' -include 'module-application:app-api' +include 'jtoon-core:core-api' +include 'jtoon-core:core-domain' -include 'module-domain' -include 'module-domain:domain-member' -include 'module-domain:domain-payment' -include 'module-domain:domain-redis' -include 'module-domain:domain-webtoon' -include 'module-domain:domain-jpa' +include 'jtoon-support:logging' +include 'jtoon-support:monitoring' -include 'module-internal' -include 'module-internal:core-web' -include 'module-internal:s3-client' -include 'module-internal:iamport-client' -include 'module-internal:smtp-client' +include 'jtoon-system' +include 'jtoon-internal:core-web' +include 'jtoon-internal:s3-client' +include 'jtoon-internal:iamport-client' +include 'jtoon-internal:smtp-client' + +include 'jtoon-db:db-redis' -include 'module-core'