Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5기] 4주차 쇼핑몰 과제 제출 - 우지 #3

Open
wants to merge 78 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
36efafe
chore: gradle 버전 변경(8.4 -> 8.5)
kwj1270 Jul 15, 2024
e75d53b
feat: 테스트 컨테이너 환경 설정
kwj1270 Jul 15, 2024
4fa4b4f
test: 이메일과 비밀번호를 입력하면 회원 가입을 할 수 있다
kwj1270 Jul 15, 2024
ca18698
test: 미가입 고객은 이메일과 비밀번호를 입력하면 회원 가입 할 수 있다
kwj1270 Jul 16, 2024
5a6ce44
test: 비유효한 이메일을 입력하면 회원 가입 할 수 없다
kwj1270 Jul 16, 2024
1d53fbf
test: 비유효한 패스워드를 입력하면 회원 가입 할 수 없다
kwj1270 Jul 16, 2024
aaab09d
test: 이미 존재하는 이메일을 입력하면 회원 가입 할 수 없다
kwj1270 Jul 16, 2024
6e99471
feat: 미가입 고객은 이메일과 비밀번호를 입력하면 회원 가입 할 수 있다
kwj1270 Jul 16, 2024
d158c22
feat: 비유효한 이메일을 입력하면 회원 가입 할 수 없다
kwj1270 Jul 16, 2024
cfee7ae
feat: 비유효한 패스워드를 입력하면 회원 가입 할 수 없다
kwj1270 Jul 16, 2024
49dc546
feat: 이미 존재하는 이메일을 입력하면 회원 가입 할 수 없다
kwj1270 Jul 16, 2024
22f5ce2
refactor: 비유효한 사용자 파라미터 정보가 들어왔을 때 에러 코드를 400 으로 변환한다
kwj1270 Jul 16, 2024
3287090
refactor: register 네이밍 대신 sign up 네이밍으로 변경
kwj1270 Jul 17, 2024
5bfb5da
test: 일반 사용자는 회원 가입이 되어있다면 이메일과 비밀번호로 로그인을 할 수 있다
kwj1270 Jul 17, 2024
ae4f214
feat: 일반 사용자는 회원 가입이 되어있다면 이메일과 비밀번호로 로그인을 할 수 있다
kwj1270 Jul 17, 2024
6ae1565
test: 일반 사용자는 회원 가입 되어있지 않은 이메일로 로그인을 할 수 없다
kwj1270 Jul 17, 2024
c6c5432
refactor: 일반 사용자는 회원 가입이 되어있다면 이메일과 비밀번호로 로그인을 할 수 있다
kwj1270 Jul 17, 2024
cd95497
feat: 일반 사용자는 회원 가입 되어있지 않은 이메일로 로그인을 할 수 없다
kwj1270 Jul 17, 2024
afd14dc
test: 비유효한 이메일 혹은 패스워드를 입력하면 로그인 할 수 없다
kwj1270 Jul 17, 2024
90db0d6
test: 회원 가입이 되어있지만 이메일 혹은 비밀번호 잘못 입력하면 로그인을 할 수 없다
kwj1270 Jul 17, 2024
8471c27
feat: 회원 가입이 되어있지만 이메일 혹은 비밀번호 잘못 입력하면 로그인을 할 수 없다
kwj1270 Jul 17, 2024
9665150
refactor: 일반 사용자 네이밍 고객으로 수정
kwj1270 Jul 17, 2024
1336e4b
test: 판매자는 이메일과 비밀번호를 입력하면 회원 가입을 할 수 있다
kwj1270 Jul 18, 2024
9dfc0cb
feat: 판매자는 이메일과 비밀번호를 입력하면 회원 가입을 할 수 있다
kwj1270 Jul 18, 2024
9c3c6f5
refactor: 판매자는 이메일과 비밀번호를 입력하면 회원 가입을 할 수 있다
kwj1270 Jul 18, 2024
a21587d
test: 판매자는 회원가입이 되어있다면 이메일과 비밀번호로 로그인을 할 수 있다
kwj1270 Jul 18, 2024
83fbc29
feat: 판매자는 회원가입이 되어있다면 이메일과 비밀번호로 로그인을 할 수 있다
kwj1270 Jul 18, 2024
70dc601
test: 판매자는 로그인 되어있다면 샵을 개설할 수 있다
kwj1270 Jul 20, 2024
67111b7
feat: 판매자는 로그인 되어있다면 샵을 개설할 수 있다
kwj1270 Jul 20, 2024
839e9af
refactor: 판매자는 로그인 되어있다면 샵을 개설할 수 있다
kwj1270 Jul 20, 2024
da1fc9c
test: 고객은 로그인 되어있어도 샵을 개설할 수 없다
kwj1270 Jul 20, 2024
bb94efe
feat: 고객은 로그인 되어있어도 샵을 개설할 수 없다
kwj1270 Jul 21, 2024
90e95f6
test: 어드민 이메일과 비밀번호를 입력하면 회원 가입을 할 수 있다
kwj1270 Jul 21, 2024
f1574ce
test: 어드민은 메인 카테고리를 생성할 수 있다
kwj1270 Jul 21, 2024
4f2eee4
test: 어드민이 아닌 사용자는 메인 카테고리를 생성할 수 없다
kwj1270 Jul 21, 2024
f80b447
feat: 어드민은 메인 카테고리를 생성할 수 있다
kwj1270 Jul 21, 2024
17b820a
test: 어드민은 메인 카테고리가 존재한다면 서브 카테고리를 생성할 수 있다
kwj1270 Jul 21, 2024
fb7467c
refactor: 패키지 및 불필요한 클래스 통합
kwj1270 Jul 21, 2024
8c81011
test: 판매자는 상점에 상품을 등록할 수 있다
kwj1270 Jul 21, 2024
c8dd530
feat: 판매자는 상점에 상품을 등록할 수 있다
kwj1270 Jul 21, 2024
743833f
test: 상품 이름이 공백 포함 15글자 넘어간다면 상품을 등록할 수 없다
kwj1270 Jul 21, 2024
4d77715
test: 상품 이름이 영문 욕설이 들었다면 상품을 등록할 수 없다
kwj1270 Jul 22, 2024
a65aefd
test: 상품 이름이 허용하지 않는 특수문자가 들어왔을 경우 상품을 등록할 수 없다
kwj1270 Jul 22, 2024
06f06a2
test: 상품이 상점에 등록되어 있으면 사용자는 위시 리스트를 등록할 수 있다
kwj1270 Jul 22, 2024
458b8c5
feat: 상품이 상점에 등록되어 있으면 사용자는 위시 리스트를 등록할 수 있다
kwj1270 Jul 22, 2024
8746778
test: 위시 리스트에 등록된 상품이 추가 등록되어도 위시리스트의 상품은 하나이다
kwj1270 Jul 22, 2024
b4470a2
test: 모든 사용자는 상점에 등록된 상품을 볼 수 있다
kwj1270 Jul 23, 2024
4216887
refactor: 패키지 구조 개선 및 패키지 싸이클 의존성 제거
kwj1270 Jul 23, 2024
8d895b2
feat: 인수 테스트 네이밍 추가
kwj1270 Jul 23, 2024
9d67410
teat: 모든 사용자는 카테고리별 상품 목록을 볼 수 있다
kwj1270 Jul 25, 2024
a988858
teat: 모든 사용자는 상점의 상품 목록을 볼 수 있다
kwj1270 Jul 25, 2024
091cee4
feat: 모든 사용자는 상점의 상품 목록을 볼 수 있다
kwj1270 Jul 25, 2024
9a0e504
feat: Flyway 설정 및 MySQL 인프라 환경 추가
kwj1270 Jul 26, 2024
7affbdf
feat: 클라이언트 통신을 위한 CORS 적용
kwj1270 Jul 28, 2024
9d34b5f
test: 위시 리스트가 등록되어 있다면 사용자는 위시 리스트를 조회할 수 있다
kwj1270 Jul 28, 2024
59929b1
test: 로그인 되어있다면 내 정보를 확인할 수 있다
kwj1270 Jul 28, 2024
c280870
test: 고객은 로그인 되어있다면 로그아웃을 할 수 있다
kwj1270 Jul 28, 2024
b3e043e
test: 판매자는 로그인 되어있다면 내 정보를 확인할 수 있다
kwj1270 Jul 28, 2024
f26bff8
test: 판매자는 로그인 되어있다면 로그아웃을 할 수 있다
kwj1270 Jul 28, 2024
cfcb217
refactor: 썸네일 이미지 경로에 대한 네이밍 변경
kwj1270 Jul 28, 2024
ee4606a
feat: 상품 등록시 상세 이미지 경로 개념 추가
kwj1270 Jul 28, 2024
4c4575d
refactor: 사용자 관련 불필요한 클래스 정리
kwj1270 Jul 28, 2024
e388daa
refactor: 상품 관련 불필요한 클래스 정리
kwj1270 Jul 28, 2024
311d592
refactor: json 파싱을 위한 기본 생성자 private 처리
kwj1270 Jul 28, 2024
f217035
test: 어드민 회원가입 및 로그인 단위 테스트
kwj1270 Jul 28, 2024
e7be370
test: 상점 등록 단위 테스트
kwj1270 Jul 28, 2024
5ae35b5
test: 위시리스트 단위 테스트 추가
kwj1270 Jul 28, 2024
29d436a
test: 카테고리 등록 단위 테스트
kwj1270 Jul 28, 2024
acd2561
test: 서브 카테고리를 등록할 수 있다
kwj1270 Jul 28, 2024
480f877
test: 상품을 등록할 수 있다.
kwj1270 Jul 28, 2024
50d0d84
test: 상품 이름이 영문 욕설이 들었다면 상품을 등록할 수 없다
kwj1270 Jul 28, 2024
07edb84
refactor: 어드민 생성 변수 위치 수정
kwj1270 Jul 28, 2024
9b03cb5
docs: 기능 요구사항 추가
kwj1270 Jul 29, 2024
0a89fae
refactor: Repository 네이밍 변경
kwj1270 Jul 29, 2024
63b5557
refactor: CustomerExceptionHandler 를 CommonExceptionHandler 로 변경
kwj1270 Jul 29, 2024
36fd014
feat: Transactional 어노테이션 추가
kwj1270 Jul 29, 2024
5b5bab0
feat: 불필요한 시나리오 테스트 제거
kwj1270 Jul 29, 2024
5ba87fe
feat: docker compose 에 redis 환경 설정 추가
kwj1270 Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# spring-shopping-product
# spring-shopping-product

# 기능 요구사항
![기능 요구 사항](src/main/resources/asset/feature.png)

# 테이블 설계
![img.png](src/main/resources/asset/table.png)
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ repositories {

dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web")
Expand All @@ -32,6 +33,12 @@ dependencies {
runtimeOnly("com.mysql:mysql-connector-j")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("io.rest-assured:rest-assured:5.4.0")
testImplementation("io.rest-assured:json-path:5.4.0")
testImplementation("org.testcontainers:testcontainers:1.19.8")
testImplementation("org.testcontainers:junit-jupiter:1.19.8")
testImplementation("org.testcontainers:mysql:1.19.8")
testImplementation("com.redis:testcontainers-redis:2.2.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

Expand Down
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3.8"
services:
shopping-mysql:
container_name: shopping-db
image: mysql:8.0.34
restart: always
ports:
- '3306:3306'
environment:
- MYSQL_ROOT_PASSWORD=1234
volumes:
- ./infra/mysql/initdb.d:/docker-entrypoint-initdb.d
shopping-redis:
image: redis:7.2.5
restart: always
ports:
- '6379:6379'
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
1 change: 1 addition & 0 deletions infra/mysql/initdb.d/create_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
create database shopping;
44 changes: 44 additions & 0 deletions src/main/java/shopping/admin/application/AdminService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package shopping.admin.application;

import org.springframework.stereotype.Service;
import shopping.admin.application.command.AdminSignInCommand;
import shopping.admin.application.command.AdminSignUpCommand;
import shopping.admin.domain.Admin;
import shopping.admin.domain.AdminRepository;
import shopping.common.auth.AccessTokenRepository;
import shopping.common.auth.AuthorizationType;
import shopping.common.exception.PasswordMissMatchException;

@Service
public class AdminService implements AdminSignUpUseCase, AdminSignInUseCase {
private final AccessTokenRepository accessTokenRepository;
private final AdminRepository adminRepository;

public AdminService(final AccessTokenRepository accessTokenRepository, final AdminRepository adminRepository) {
this.accessTokenRepository = accessTokenRepository;
this.adminRepository = adminRepository;
}

@Override
public Admin signUp(final AdminSignUpCommand adminSignUpCommand) {
return adminRepository.save(init(adminSignUpCommand));
}

@Override
public String signIn(final AdminSignInCommand adminSignInCommand) {
final Admin admin = adminRepository.findByEmail(adminSignInCommand.email());
if (admin.isSamePassword(adminSignInCommand.password())) {
return accessTokenRepository.create(AuthorizationType.ADMIN, admin.id());
}
throw new PasswordMissMatchException();
}

private Admin init(final AdminSignUpCommand adminSignUpCommand) {
return new Admin(
null,
adminSignUpCommand.name(),
adminSignUpCommand.email(),
adminSignUpCommand.password()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package shopping.admin.application;

import shopping.admin.application.command.AdminSignInCommand;

public interface AdminSignInUseCase {
String signIn(final AdminSignInCommand adminSignInCommand);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package shopping.admin.application;

import shopping.admin.application.command.AdminSignUpCommand;
import shopping.admin.domain.Admin;

public interface AdminSignUpUseCase {
Admin signUp(AdminSignUpCommand adminSignUpCommand);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package shopping.admin.application.command;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Pattern;
import shopping.common.CommandValidating;

public record AdminSignInCommand(
@Email String email,
@Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$%^&*]).{12,}$") String password
) implements CommandValidating<AdminSignInCommand> {

public AdminSignInCommand(final String email, final String password) {
this.email = email;
this.password = password;
this.validateSelf(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package shopping.admin.application.command;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import shopping.common.CommandValidating;

public record AdminSignUpCommand(
@Email String email,
@NotBlank String name,
@Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$%^&*]).{12,}$") String password
) implements CommandValidating<AdminSignUpCommand> {

public AdminSignUpCommand(final String email, final String name, final String password) {
this.email = email;
this.name = name;
this.password = password;
this.validateSelf(this);
}
}
7 changes: 7 additions & 0 deletions src/main/java/shopping/admin/domain/Admin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package shopping.admin.domain;

public record Admin(Long id, String name, String email, String password) {
public boolean isSamePassword(final String password) {
return this.password.equals(password);
}
}
7 changes: 7 additions & 0 deletions src/main/java/shopping/admin/domain/AdminRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package shopping.admin.domain;

public interface AdminRepository {
Admin save(final Admin admin);

Admin findByEmail(String email);
}
17 changes: 17 additions & 0 deletions src/main/java/shopping/admin/infrastructure/AdminEntityMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package shopping.admin.infrastructure;

import shopping.admin.domain.Admin;
import shopping.admin.infrastructure.persistence.AdminEntity;

public class AdminEntityMapper {
private AdminEntityMapper() {
}

public static AdminEntity domainToEntity(final Admin admin) {
return new AdminEntity(admin.id(), admin.name(), admin.email(), admin.password());
}

public static Admin entityToDomain(final AdminEntity adminEntity) {
return new Admin(adminEntity.getId(), adminEntity.getName(), adminEntity.getEmail(), adminEntity.getPassword());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package shopping.admin.infrastructure;

import jakarta.persistence.EntityNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import shopping.admin.domain.Admin;
import shopping.admin.domain.AdminRepository;
import shopping.admin.infrastructure.persistence.AdminEntity;
import shopping.admin.infrastructure.persistence.AdminEntityJpaRepository;

import java.util.List;

import static shopping.admin.infrastructure.AdminEntityMapper.domainToEntity;
import static shopping.admin.infrastructure.AdminEntityMapper.entityToDomain;

@Component
public class AdminRepositoryAdapter implements AdminRepository {
private final AdminEntityJpaRepository adminEntityJpaRepository;

public AdminRepositoryAdapter(final AdminEntityJpaRepository adminEntityJpaRepository) {
this.adminEntityJpaRepository = adminEntityJpaRepository;
}

@Transactional
@Override
public Admin save(final Admin admin) {
final AdminEntity adminEntity = adminEntityJpaRepository.save(domainToEntity(admin));
return entityToDomain(adminEntity);
}

@Transactional(readOnly = true)
@Override
public Admin findByEmail(final String email) {
final List<AdminEntity> all = adminEntityJpaRepository.findAll();
return adminEntityJpaRepository.findByEmail(email)
.map(AdminEntityMapper::entityToDomain)
.orElseThrow(() -> new EntityNotFoundException());
}
}
38 changes: 38 additions & 0 deletions src/main/java/shopping/admin/infrastructure/api/AdminApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package shopping.admin.infrastructure.api;

import org.springframework.http.ResponseEntity;
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.RestController;
import shopping.admin.application.AdminSignInUseCase;
import shopping.admin.application.AdminSignUpUseCase;
import shopping.admin.infrastructure.api.dto.AdminSignInHttpRequest;
import shopping.admin.infrastructure.api.dto.AdminSignInHttpResponse;
import shopping.admin.infrastructure.api.dto.AdminSignUpHttpRequest;

import java.net.URI;

@RequestMapping("/api/admins")
@RestController
public class AdminApi {
private final AdminSignUpUseCase adminSignUpUseCase;
private final AdminSignInUseCase adminSignInUseCase;

public AdminApi(final AdminSignUpUseCase adminSignUpUseCase, final AdminSignInUseCase adminSignInUseCase) {
this.adminSignUpUseCase = adminSignUpUseCase;
this.adminSignInUseCase = adminSignInUseCase;
}

@PostMapping("/sign-up")
public ResponseEntity<String> register(@RequestBody final AdminSignUpHttpRequest adminSignUpHttpRequest) {
adminSignUpUseCase.signUp(adminSignUpHttpRequest.toCommand());
return ResponseEntity.created(URI.create("")).build();
}

@PostMapping("/sign-in")
public ResponseEntity<AdminSignInHttpResponse> signIn(@RequestBody final AdminSignInHttpRequest adminSignInHttpRequest) {
final String accessToken = adminSignInUseCase.signIn(adminSignInHttpRequest.toCommand());
return ResponseEntity.ok(new AdminSignInHttpResponse(accessToken));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package shopping.admin.infrastructure.api.dto;

import shopping.admin.application.command.AdminSignInCommand;

public class AdminSignInHttpRequest {

private String email;
private String password;

private AdminSignInHttpRequest() {
}

public AdminSignInHttpRequest(final String email, final String password) {
this.email = email;
this.password = password;
}

public AdminSignInCommand toCommand() {
return new AdminSignInCommand(email, password);
}

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package shopping.admin.infrastructure.api.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

public class AdminSignInHttpResponse {

@JsonProperty("access_token")
private String accessToken;

public AdminSignInHttpResponse() {
}

public AdminSignInHttpResponse(final String accessToken) {
this.accessToken = accessToken;
}

public String getAccessToken() {
return accessToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package shopping.admin.infrastructure.api.dto;

import shopping.admin.application.command.AdminSignUpCommand;

public class AdminSignUpHttpRequest {

private String email;
private String name;
private String password;

private AdminSignUpHttpRequest() {
}

public AdminSignUpHttpRequest(final String email, String name, final String password) {
this.email = email;
this.name = name;
this.password = password;
}

public AdminSignUpCommand toCommand() {
return new AdminSignUpCommand(email, name, password);
}

public String getEmail() {
return email;
}

public String getName() {
return name;
}

public String getPassword() {
return password;
}
}
Loading