Skip to content

Commit

Permalink
Merge pull request #72 from saessagMarket/feat/#70-fix-connect-produc…
Browse files Browse the repository at this point in the history
…t-profile

상품 CRUD와 사용자 정보를 세션으로 연결, 글쓴이 프로필 정보 연동
  • Loading branch information
ahyeonkong authored Feb 14, 2025
2 parents 56069cd + 4abf7d1 commit 82fd875
Show file tree
Hide file tree
Showing 19 changed files with 261 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@
import com.market.saessag.domain.product.entity.Product;
import com.market.saessag.domain.product.service.ProductService;
import com.market.saessag.domain.user.dto.SignInResponse;
import com.market.saessag.global.exception.CustomException;
import com.market.saessag.global.exception.ErrorCode;
import com.market.saessag.global.response.ApiResponse;
import com.market.saessag.global.response.SuccessCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;

/**
* 세션 동작 방식
* 1. 사용자가 요청을 보내면 Spring Security 필터체인이 동작
* 2. SecurityContextPersistenceFilter가 세션에서 인증 정보를 찾아 SecurityContextHolder에 설정
* 3. 컨트롤러에서 세션 정보 확인
*/

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;

//통합 조회 (제목, 닉네임, 정렬기준)
@GetMapping()
@GetMapping("/list")
public ApiResponse<Page<ProductResponse>> searchProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
Expand Down Expand Up @@ -50,23 +60,28 @@ public ApiResponse<ProductResponse> getProductDetail(@PathVariable Long id,

//상품 생성
@PostMapping
public ApiResponse<ProductResponse> createProduct(@RequestBody ProductRequest productRequest) {
ProductResponse createdProduct = productService.createProduct(productRequest);
public ApiResponse<ProductResponse> createProduct(@RequestBody ProductRequest productRequest,
HttpServletRequest request) {
ProductResponse createdProduct = productService.createProduct(productRequest, request);
return ApiResponse.success(SuccessCode.PRODUCT_CREATED, createdProduct);
}

//상품 수정
@PutMapping("/{id}")
public ApiResponse<ProductResponse> updateProduct(@PathVariable Long productId,
@RequestBody ProductRequest productRequest) {
ProductResponse updatedProduct = productService.updateProduct(productId, productRequest);
public ApiResponse<ProductResponse> updateProduct(
@PathVariable Long id,
@RequestBody ProductRequest productRequest,
HttpServletRequest request) {
ProductResponse updatedProduct = productService.updateProduct(id, productRequest, request);
return ApiResponse.success(SuccessCode.OK, updatedProduct);
}

//상품 삭제
@DeleteMapping("/{id}")
public ApiResponse<Void> deleteProduct(@PathVariable Long productId) {
boolean isDeleted = productService.deleteProduct(productId);
public ApiResponse<Void> deleteProduct(
@PathVariable Long id,
HttpServletRequest request) {
boolean isDeleted = productService.deleteProduct(id, request);
if (!isDeleted) {
return ApiResponse.error(ErrorCode.PRODUCT_NOT_FOUND);
}
Expand All @@ -75,23 +90,47 @@ public ApiResponse<Void> deleteProduct(@PathVariable Long productId) {

// 상품 좋아요
@PostMapping("/{id}/like")
public ApiResponse<Void> likeProduct(@PathVariable Long productId, @SessionAttribute(name = "user") SignInResponse user) {
productService.likeProduct(productId, user.getId());
return ApiResponse.success(SuccessCode.OK, null);
}
public ApiResponse<Void> likeProduct(@PathVariable Long id, HttpServletRequest request) {

HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

@PostMapping("/bump")
boolean isLiked = productService.likeProduct(id, userSession.getId());

public ApiResponse<?> bumpProduct(@RequestParam Long productId, @SessionAttribute(name = "user") SignInResponse user) {
Product product = productService.bumpProduct(productId, user.getId());
return ApiResponse.success(SuccessCode.OK, product.getId());
return ApiResponse.success(
isLiked ? SuccessCode.LIKE_ADDED : SuccessCode.LIKE_REMOVED,
null
);
}

// 상품 끌어올리기
// @PreAuthorize("isAuthenticated()") 인증된 사용자만 접근 가능한 시큐리티의 메서드 방식 --> 나중에 리팩토링
@PostMapping("/bump/{id}")
public ApiResponse<?> bumpProduct(@PathVariable Long id, HttpServletRequest request) {
// 1. 세션 확인
HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

// note. 본인 소유의 상품의 상태 값만 변경 할 수 있도록 조치 필요
if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

// 2. 서비스 호출 (소유자 검증은 서비스에서 처리)
Product bumpedProduct = productService.bumpProduct(id, userSession.getId());
return ApiResponse.success(SuccessCode.OK, bumpedProduct.getId());
}

// 상품 상태 값 변경(본인 소유의 상품의 상태 값만 변경 가능)
@PostMapping("/changeStatus")
public ApiResponse<ProductChangeStatusResponse> changeStatus(@RequestBody ProductChangeStatusRequest req) {
ProductChangeStatusResponse response = productService.changeStatus(req);
public ApiResponse<ProductChangeStatusResponse> changeStatus(
@RequestBody ProductChangeStatusRequest req,
HttpServletRequest request) {
ProductChangeStatusResponse response = productService.changeStatus(req, request);
return ApiResponse.success(SuccessCode.OK, response);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@Getter
public class ProductChangeStatusRequest {

private Long productId;
private Long id;
private ProductStatus status;

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.market.saessag.domain.product.dto;

import com.market.saessag.domain.product.entity.Product.ProductStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductChangeStatusResponse {

private Long productId;
private Long id;
private ProductStatus status;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.market.saessag.domain.product.dto;

import com.market.saessag.domain.user.entity.User;
import lombok.Getter;

import java.util.List;

@Getter
public class ProductRequest {
private User user;
private String title;
private Long price;
private String description;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.market.saessag.domain.product.dto;

import com.market.saessag.domain.user.dto.UserResponse;
import com.market.saessag.domain.user.dto.UserProfileResponse;
import lombok.Builder;
import lombok.Getter;

Expand All @@ -20,5 +20,5 @@ public class ProductResponse {
private String status;
private Long like;
private Long view;
private final UserResponse user;
private final UserProfileResponse user;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import com.market.saessag.domain.product.repository.ProductLikeRepository;
import com.market.saessag.domain.product.repository.ProductRepository;
import com.market.saessag.domain.product.repository.ProductViewRepository;
import com.market.saessag.domain.user.dto.SignInResponse;
import com.market.saessag.domain.user.entity.User;
import com.market.saessag.domain.user.repository.UserRepository;
import com.market.saessag.domain.user.dto.UserResponse;
import com.market.saessag.domain.user.dto.UserProfileResponse;
import com.market.saessag.global.exception.CustomException;
import com.market.saessag.global.exception.ErrorCode;
import com.market.saessag.util.TimeUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
Expand All @@ -35,9 +38,22 @@ public class ProductService {
private final UserRepository userRepository;
private final ProductLikeRepository productLikeRepository;

// 상품 가져오기
public Product getProduct(Long productId) {
return productRepository.findById(productId)
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));
}

//상품 생성
public ProductResponse createProduct(ProductRequest productRequest) {
User user = userRepository.findById(productRequest.getUser().getId())
public ProductResponse createProduct(ProductRequest productRequest, HttpServletRequest request) {
HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

User user = userRepository.findById(userSession.getId())
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));

Product product = Product.builder()
Expand All @@ -55,11 +71,24 @@ public ProductResponse createProduct(ProductRequest productRequest) {
return convertToDTO(productRepository.save(product));
}


//상품 수정
public ProductResponse updateProduct(Long productId, ProductRequest productRequest) {
public ProductResponse updateProduct(Long productId, ProductRequest productRequest, HttpServletRequest request) {
HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("없는 상품 번호 입니다."));

// 글쓴이와 현재 로그인한 사용자가 같은지 확인
if (!product.getUser().getId().equals(userSession.getId())) {
throw new CustomException(ErrorCode.FORBIDDEN);
}

product.updateProduct(
productRequest.getTitle(),
productRequest.getPrice(),
Expand All @@ -74,13 +103,29 @@ public ProductResponse updateProduct(Long productId, ProductRequest productReque
return convertToDTO(productRepository.save(product));
}

public boolean deleteProduct(Long productId) {
Optional<Product> product = productRepository.findById(productId);
if (product.isPresent()) {
// 상품 삭제
public boolean deleteProduct(Long productId, HttpServletRequest request) {
HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("없는 상품 번호 입니다."));

// 글쓴이와 현재 로그인한 사용자가 같은지 확인
if (!product.getUser().getId().equals(userSession.getId())) {
throw new CustomException(ErrorCode.FORBIDDEN);
}

try {
productRepository.deleteById(productId);
return true;
} catch (Exception e) {
return false;
}
return false;
}

public Page<ProductResponse> searchProducts(int page, int size, String title, String nickname, String sort) {
Expand Down Expand Up @@ -128,7 +173,7 @@ private ProductResponse convertToDTO(Product product) {
.status(product.getStatus().toString())
.like(product.getLikes())
.view(product.getViews())
.user(UserResponse.builder()
.user(UserProfileResponse.builder()
.id(user.getId())
.nickname(user.getNickname())
.profileUrl(user.getProfileUrl())
Expand All @@ -142,13 +187,18 @@ public ProductResponse getProductDetail(Long productId) {
return convertToDTO(id);
}

// 상품 끌어올리기
public Product bumpProduct(Long productId, Long userId) {
Product product = productRepository.findByIdAndUserId(productId, userId)
.orElseThrow(IllegalAccessError::new);
Product product = productRepository.findById(productId)
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));

// 상품 소유자 검증
if (!product.getUser().getId().equals(userId)) {
throw new CustomException(ErrorCode.FORBIDDEN);
}

product.updateBumpAt(LocalDateTime.now());
productRepository.save(product);
return product;
return productRepository.save(product);
}

// 조회수 증가
Expand All @@ -175,37 +225,61 @@ public void incrementView(Long productId, Long userId) {

// 좋아요 클릭
@Transactional
public void likeProduct(Long productId, Long userId) {
public boolean likeProduct(Long productId, Long userId) {
// 상품 조회
Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("상품이 없습니다."));
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));

// 사용자 조회
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("해당 유저가 없습니다."));
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

// 좋아요 상태 확인
ProductLike productLike = productLikeRepository.findByProductAndUser(product, user);
if (productLike == null) { // 좋아요 추가

if (productLike == null) {
productLikeRepository.save(ProductLike.builder()
.product(product)
.user(user)
.build());
product.incrementLikes();
} else { // 좋아요 삭제
productRepository.save(product);
return true; // 좋아요 추가됨
} else {
productLikeRepository.delete(productLike);
product.decrementLikes();
productRepository.save(product);
return false; // 좋아요 취소됨
}
productRepository.save(product);
}

public ProductChangeStatusResponse changeStatus(ProductChangeStatusRequest req) {
Product product = productRepository.findById(req.getProductId())
.orElseThrow(()-> new IllegalArgumentException("상품이 없습니다."));
// 상품 상태 값 변경
public ProductChangeStatusResponse changeStatus(ProductChangeStatusRequest req, HttpServletRequest request) {
HttpSession session = request.getSession();
SignInResponse userSession = (SignInResponse) session.getAttribute("userProfile");

if (userSession == null) {
throw new CustomException(ErrorCode.UNAUTHORIZED);
}

Product product = productRepository.findById(req.getId())
.orElseThrow(() -> new CustomException(ErrorCode.PRODUCT_NOT_FOUND));

product.updateStatus(req.getStatus());
productRepository.save(product);
if (!product.getUser().getId().equals(userSession.getId())) {
throw new CustomException(ErrorCode.FORBIDDEN);
}

try {
product.updateStatus(req.getStatus());
Product savedProduct = productRepository.save(product);

return ProductChangeStatusResponse.builder()
.productId(product.getId())
.status(product.getStatus())
.build();
return ProductChangeStatusResponse.builder()
.id(savedProduct.getId())
.status(savedProduct.getStatus())
.build();
} catch (Exception e) {
throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
}
}

}
Loading

0 comments on commit 82fd875

Please sign in to comment.