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

부산대 BE_박민재 6주차 과제(3단계) #383

Open
wants to merge 31 commits into
base: minjae4650
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0b695be
copy: 저번주차 order 코드 복사
minjae4650 Jul 30, 2024
a27853f
feat: 회원 API 명세 수정
minjae4650 Jul 30, 2024
72dcb57
feat: ProductController에 페이지네이션 및 정렬 추가
minjae4650 Jul 30, 2024
a31aff1
feat: ProductService 인터페이스에 페이지네이션 및 카테고리 필터링 추가
minjae4650 Jul 30, 2024
403f59d
feat: ProductServiceImpl에 페이지네이션 및 카테고리 필터링 추가
minjae4650 Jul 30, 2024
62a178a
feat: ProductRepository에 findAllByCategoryId 메서드 추가
minjae4650 Jul 30, 2024
8cce6d6
feat: AdminController에 페이지네이션 추가 및 코드 개선
minjae4650 Jul 30, 2024
db10359
feat: OptionController 구현 및 옵션 CRUD 기능 추가
minjae4650 Aug 1, 2024
5720ba4
feat: OptionService 인터페이스에 옵션 CRUD 메서드 정의
minjae4650 Aug 1, 2024
ebac09c
feat: OptionServiceImpl에 옵션 CRUD 로직 구현
minjae4650 Aug 1, 2024
6cb9f0d
feat: WishController에 페이지네이션 및 정렬 기능 추가
minjae4650 Aug 1, 2024
8077b45
feat: WishService 인터페이스에 페이지네이션 지원 추가
minjae4650 Aug 1, 2024
2828875
feat: WishServiceImpl에서 페이지네이션 및 안전한 삭제 기능 추가
minjae4650 Aug 1, 2024
85979f1
feat: WishRepository에 페이지네이션 및 안전한 조회 메서드 추가
minjae4650 Aug 1, 2024
bd2c5cb
feat: OrderController에 주문 생성 및 조회 기능 추가
minjae4650 Aug 1, 2024
a173539
feat: OrderService 인터페이스에 주문 생성 및 조회 메서드 정의
minjae4650 Aug 1, 2024
036c215
feat: OrderServiceImpl에 주문 생성 및 조회 로직 구현
minjae4650 Aug 1, 2024
2162893
refactor: deleteProduct 제품 미발견 시 예외 처리
minjae4650 Aug 1, 2024
5a5a928
feat: Product 관련 test 구현
minjae4650 Aug 1, 2024
b5baeeb
feat: Wish 관련 test 구현
minjae4650 Aug 1, 2024
fa5201d
feat: Member 관련 test 구현
minjae4650 Aug 1, 2024
4ec3869
feat: 모든 경로와 출처에 대해 CORS 허용
minjae4650 Aug 2, 2024
15741ac
refactor: redirect-url 수정
minjae4650 Aug 4, 2024
2404752
refactor: redirect-url 수정
minjae4650 Aug 4, 2024
73ff806
refactor: redirect-url 수정
minjae4650 Aug 4, 2024
d54bda4
refactor: API 명세로 수정
minjae4650 Aug 4, 2024
58d05a7
feat: Member 엔티티에 포인트 관리 기능 추가
minjae4650 Aug 4, 2024
64c7264
feat: Order 엔티티에 결제 관련 필드 추가
minjae4650 Aug 4, 2024
62523a6
feat: OrderServiceImpl에 결제 로직 및 할인 정책 구현
minjae4650 Aug 4, 2024
bb74b69
refactor: OrderController에서 서비스로 Member 객체 전달
minjae4650 Aug 4, 2024
879e2f7
chore: 데이터베이스 스키마 업데이트
minjae4650 Aug 4, 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
16 changes: 16 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,25 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'org.mindrot:jbcrypt:0.4'
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
compileOnly 'org.projectlombok:lombok:1.18.34'
annotationProcessor 'org.projectlombok:lombok:1.18.34'
testCompileOnly 'org.projectlombok:lombok:1.18.34'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.34'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.mockito:mockito-core:3.12.4'
testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4'
}

tasks.named('test') {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/gift/component/LoginMember.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gift.component;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginMember {
}
52 changes: 52 additions & 0 deletions src/main/java/gift/component/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gift.component;

import gift.service.MemberService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import jakarta.servlet.http.HttpServletRequest;

import java.nio.charset.StandardCharsets;

@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

private final MemberService memberService;

@Value("${jwt.secret}")
private String secretKey;

public LoginMemberArgumentResolver(MemberService memberService) {
this.memberService = memberService;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(LoginMember.class) != null;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String token = authorizationHeader.substring(7);
Claims claims = Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)))
.build()
.parseSignedClaims(token)
.getPayload();
Long memberId = Long.parseLong(claims.getSubject());
return memberService.findById(memberId).orElseThrow(() -> new RuntimeException("Member not found"));
}
throw new RuntimeException("Invalid token");
}
}
22 changes: 22 additions & 0 deletions src/main/java/gift/config/JasyptConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gift.config;

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;

@Configuration
public class JasyptConfig {

@Value("${jasypt.encryptor.password}")
private String encryptorPassword;

@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(encryptorPassword);
encryptor.setAlgorithm("PBEWithMD5AndDES");
return encryptor;
}
}
18 changes: 18 additions & 0 deletions src/main/java/gift/config/OpenApiConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gift.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class OpenApiConfig implements WebMvcConfigurer {

@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/api/**")
.build();
}
}
36 changes: 36 additions & 0 deletions src/main/java/gift/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gift.config;

import gift.component.LoginMemberArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final LoginMemberArgumentResolver loginMemberArgumentResolver;

public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) {
this.loginMemberArgumentResolver = loginMemberArgumentResolver;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberArgumentResolver);
}

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:8080",
"http://3.39.251.25"
)
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
}
}
37 changes: 37 additions & 0 deletions src/main/java/gift/controller/AdminController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package gift.controller;

import gift.model.Product;
import gift.service.ProductService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/admin/products")
public class AdminController {

private final ProductService productService;

public AdminController(ProductService productService) {
this.productService = productService;
}

@GetMapping("")
public String showProducts(Model model,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<Product> productPage = productService.getProducts(pageable, null);

model.addAttribute("products", productPage.getContent());
model.addAttribute("currentPage", page);
model.addAttribute("pageSize", size);
model.addAttribute("totalPages", productPage.getTotalPages());
return "products_admin";
}
}
61 changes: 61 additions & 0 deletions src/main/java/gift/controller/CategoryController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gift.controller;

import gift.dto.ApiResponse;
import gift.model.Category;
import gift.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/categories")
public class CategoryController {

private final CategoryService categoryService;

@Autowired
public CategoryController(CategoryService categoryService) {
this.categoryService = categoryService;
}

@GetMapping
public ResponseEntity<ApiResponse<List<Category>>> getAllCategories() {
List<Category> categories = categoryService.getAllCategories();
ApiResponse<List<Category>> response = new ApiResponse<>(true, "Categories retrieved successfully", categories, null);
return new ResponseEntity<>(response, HttpStatus.OK);
}

@PostMapping
public ResponseEntity<ApiResponse<Category>> createCategory(@RequestBody Category category) {
Category createdCategory = categoryService.createCategory(category);
ApiResponse<Category> response = new ApiResponse<>(true, "Category created successfully", createdCategory, null);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}

@PutMapping("/{id}")
public ResponseEntity<ApiResponse<Category>> updateCategory(@PathVariable Long id, @RequestBody Category updatedCategory) {
try {
Category category = categoryService.updateCategory(id, updatedCategory);
ApiResponse<Category> response = new ApiResponse<>(true, "Category updated successfully", category, null);
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (RuntimeException e) {
ApiResponse<Category> response = new ApiResponse<>(false, "Category not found", null, "404");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
}

@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteCategory(@PathVariable Long id) {
try {
categoryService.deleteCategory(id);
ApiResponse<Void> response = new ApiResponse<>(true, "Category deleted successfully", null, null);
return new ResponseEntity<>(response, HttpStatus.NO_CONTENT);
} catch (RuntimeException e) {
ApiResponse<Void> response = new ApiResponse<>(false, "Category not found", null, "404");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
}
}
41 changes: 41 additions & 0 deletions src/main/java/gift/controller/KakaoAuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gift.controller;

import gift.dto.ApiResponse;
import gift.dto.UserInfo;
import gift.service.KakaoAuthService;
import gift.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class KakaoAuthController {

private final KakaoAuthService kakaoAuthService;
private final UserService userService;

public KakaoAuthController(KakaoAuthService kakaoAuthService, UserService userService) {
this.kakaoAuthService = kakaoAuthService;
this.userService = userService;
}

@GetMapping("/oauth/kakao")
public ApiResponse<String> kakaoLogin(@RequestParam("code") String authorizationCode, HttpServletRequest request) {
try {
String accessToken = kakaoAuthService.getAccessToken(authorizationCode);
UserInfo userInfo = kakaoAuthService.getUserInfo(accessToken);

HttpSession session = request.getSession();
session.setAttribute("accessToken", accessToken);
session.setAttribute("userInfo", userInfo);

userService.saveUser(accessToken, userInfo);

return new ApiResponse<>(true, "Access token retrieved successfully", accessToken, null);
} catch (Exception e) {
return new ApiResponse<>(true, "Access token retrieved successfully", null, null);
}
}
}
18 changes: 18 additions & 0 deletions src/main/java/gift/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gift.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

@GetMapping("/login")
public String login() {
return "login";
}

@GetMapping("/order")
public String order() {
return "order";
}
}
45 changes: 45 additions & 0 deletions src/main/java/gift/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package gift.controller;

import gift.dto.ApiResponse;
import gift.model.Member;
import gift.service.MemberService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/members")
public class MemberController {

private final MemberService memberService;

public MemberController(MemberService memberService) {
this.memberService = memberService;
}

@PostMapping("/register")
public ResponseEntity<ApiResponse<String>> register(@RequestBody Member member) {
return memberService.registerMember(member)
.map(token -> {
ApiResponse<String> response = new ApiResponse<>(true, "Member registered successfully", token, null);
return new ResponseEntity<>(response, HttpStatus.OK);
})
.orElseGet(() -> {
ApiResponse<String> response = new ApiResponse<>(false, "Registration failed", null, "500");
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
});
}

@PostMapping("/login")
public ResponseEntity<ApiResponse<String>> login(@RequestBody Member member) {
return memberService.login(member.getEmail(), member.getPassword())
.map(token -> {
ApiResponse<String> response = new ApiResponse<>(true, "Login successful", token, null);
return new ResponseEntity<>(response, HttpStatus.OK);
})
.orElseGet(() -> {
ApiResponse<String> response = new ApiResponse<>(false, "Invalid email or password", null, "403");
return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
});
}
}
Loading