Skip to content

Commit 1a7741e

Browse files
authored
[BACKEND] 로그아웃, 상품 검색, 커스텀 관심사 기능 추가 (#35)
1 parent 64c52aa commit 1a7741e

File tree

22 files changed

+482
-23
lines changed

22 files changed

+482
-23
lines changed

backend/src/main/java/com/cmg/comtogether/common/exception/ErrorCode.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ public enum ErrorCode {
2323

2424
// 카카오 API
2525
OAUTH_INVALID_CODE(400, "OAUTH-000", "유효하지 않은 인가 코드입니다."),
26-
OAUTH_PROVIDER_ERROR(502, "OAUTH-999", "카카오 서버와 통신 중 오류가 발생했습니다.");
26+
OAUTH_PROVIDER_ERROR(502, "OAUTH-999", "카카오 서버와 통신 중 오류가 발생했습니다."),
27+
28+
// 네이버 상품 API
29+
NAVER_API_ERROR(502, "NAVER-999", "네이버 서버와 통신 중 오류가 발생했습니다.");
2730

2831
private final int status;
2932
private final String code;

backend/src/main/java/com/cmg/comtogether/common/exception/GlobalExceptionHandler.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
package com.cmg.comtogether.common.exception;
22

33
import com.cmg.comtogether.common.response.ErrorResponse;
4-
import io.jsonwebtoken.ExpiredJwtException;
5-
import io.jsonwebtoken.MalformedJwtException;
64
import lombok.extern.slf4j.Slf4j;
75
import org.springframework.http.ResponseEntity;
86
import org.springframework.http.converter.HttpMessageNotReadableException;
97
import org.springframework.validation.FieldError;
108
import org.springframework.web.bind.MethodArgumentNotValidException;
9+
import org.springframework.web.bind.MissingServletRequestParameterException;
1110
import org.springframework.web.bind.annotation.ExceptionHandler;
1211
import org.springframework.web.bind.annotation.RestControllerAdvice;
13-
import org.springframework.web.client.HttpClientErrorException;
14-
15-
import java.security.SignatureException;
12+
import org.springframework.web.method.annotation.HandlerMethodValidationException;
1613

1714
@Slf4j
1815
@RestControllerAdvice
1916
public class GlobalExceptionHandler {
2017

18+
@ExceptionHandler(HandlerMethodValidationException.class)
19+
public ResponseEntity<ErrorResponse> handleHandlerMethodValidation(HandlerMethodValidationException e) {
20+
String message = e.getAllErrors().stream()
21+
.map(error -> error.getDefaultMessage())
22+
.findFirst()
23+
.orElse(ErrorCode.INVALID_INPUT.getMessage());
24+
25+
return ResponseEntity
26+
.status(ErrorCode.INVALID_INPUT.getStatus())
27+
.body(ErrorResponse.of(ErrorCode.INVALID_INPUT, message));
28+
}
29+
30+
@ExceptionHandler(MissingServletRequestParameterException.class)
31+
public ResponseEntity<ErrorResponse> handleMissingParams(MissingServletRequestParameterException ex) {
32+
String paramName = ex.getParameterName();
33+
String errorMessage = String.format("필수 파라미터 '%s' 가 누락되었습니다.", paramName);
34+
35+
return ResponseEntity
36+
.status(ErrorCode.INVALID_INPUT.getStatus())
37+
.body(ErrorResponse.of(ErrorCode.INVALID_INPUT, errorMessage));
38+
}
39+
2140
@ExceptionHandler(MethodArgumentNotValidException.class)
2241
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
2342
FieldError fieldError = e.getBindingResult().getFieldError();

backend/src/main/java/com/cmg/comtogether/common/security/config/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CorsConfigurat
3737
"/oauth/**",
3838
"/refresh/**",
3939
"/swagger-ui/**",
40-
"/v3/api-docs/**"
40+
"/v3/api-docs/**",
41+
"/products/**"
4142
).permitAll()
4243
.anyRequest().authenticated()
4344
)

backend/src/main/java/com/cmg/comtogether/interest/controller/InterestController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public class InterestController {
1919
private final InterestService interestService;
2020

2121
@GetMapping
22-
private ResponseEntity<ApiResponse<List<InterestDto>>> getInterests() {
23-
List<InterestDto> allInterest = interestService.getAllInterest();
22+
private ResponseEntity<ApiResponse<List<InterestDto>>> getCommonInterests() {
23+
List<InterestDto> allInterest = interestService.getCommonInterests();
2424
return ResponseEntity.ok(ApiResponse.success(allInterest));
2525
}
2626
}

backend/src/main/java/com/cmg/comtogether/interest/entity/Interest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public class Interest {
1414
@GeneratedValue(strategy = GenerationType.IDENTITY)
1515
private Long interestId;
1616

17-
@Column(nullable = false, unique = true)
17+
@Column(nullable = false)
1818
private String name;
19+
20+
@Builder.Default
21+
private Boolean isCustom = false;
1922
}

backend/src/main/java/com/cmg/comtogether/interest/repository/InterestRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
import com.cmg.comtogether.interest.entity.Interest;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import java.util.List;
7+
68
public interface InterestRepository extends JpaRepository<Interest, Long> {
9+
List<Interest> findByIsCustomFalse();
710
}

backend/src/main/java/com/cmg/comtogether/interest/service/InterestService.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,30 @@ public class InterestService {
1414

1515
private final InterestRepository interestRepository;
1616

17-
public List<InterestDto> getAllInterest(){
18-
return interestRepository.findAll()
17+
public List<InterestDto> getCommonInterests(){
18+
return interestRepository.findByIsCustomFalse()
1919
.stream()
20-
.map(interest -> new InterestDto(interest.getInterestId(), interest.getName()))
20+
.map(this::toDto)
2121
.toList();
2222
}
23+
24+
public List<Interest> findAllById(List<Long> ids){
25+
return interestRepository.findAllById(ids)
26+
.stream()
27+
.toList();
28+
}
29+
30+
public List<Interest> saveCustomInterests(List<String> interestNames){
31+
return interestNames.stream()
32+
.map(interestName -> Interest.builder()
33+
.name(interestName)
34+
.isCustom(true)
35+
.build())
36+
.map(interestRepository::save)
37+
.toList();
38+
}
39+
40+
private InterestDto toDto(Interest interest) {
41+
return new InterestDto(interest.getInterestId(), interest.getName());
42+
}
2343
}

backend/src/main/java/com/cmg/comtogether/jwt/entity/RefreshToken.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import jakarta.persistence.*;
44
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
56
import lombok.Getter;
67
import lombok.NoArgsConstructor;
78

89
@Entity
910
@Getter
11+
@Builder
1012
@NoArgsConstructor
1113
@AllArgsConstructor
1214
public class RefreshToken {

backend/src/main/java/com/cmg/comtogether/jwt/service/JwtService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,9 @@ public TokenDto refreshAccessToken(String refreshToken) {
5050
.refreshToken(newRefreshToken)
5151
.build();
5252
}
53+
54+
public void deleteRefreshToken(Long userId) {
55+
refreshTokenRepository.findByUserId(userId).ifPresent(refreshTokenRepository::delete);
56+
}
5357
}
5458

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.cmg.comtogether.product.controller;
2+
3+
import com.cmg.comtogether.common.response.ApiResponse;
4+
import com.cmg.comtogether.common.security.CustomUserDetails;
5+
import com.cmg.comtogether.product.dto.NaverProductResponseDto;
6+
import com.cmg.comtogether.product.service.ProductService;
7+
import com.cmg.comtogether.user.entity.User;
8+
import jakarta.validation.constraints.Min;
9+
import jakarta.validation.constraints.NotBlank;
10+
import jakarta.validation.constraints.Pattern;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
14+
import org.springframework.validation.annotation.Validated;
15+
import org.springframework.web.bind.annotation.GetMapping;
16+
import org.springframework.web.bind.annotation.RequestMapping;
17+
import org.springframework.web.bind.annotation.RequestParam;
18+
import org.springframework.web.bind.annotation.RestController;
19+
20+
@RestController
21+
@RequestMapping("/products")
22+
@RequiredArgsConstructor
23+
public class ProductController {
24+
25+
private final ProductService productService;
26+
27+
@GetMapping
28+
@Validated
29+
public ResponseEntity<ApiResponse<NaverProductResponseDto>> searchProducts(
30+
@RequestParam @NotBlank(message = "카테고리는 필수입니다.") String category,
31+
@RequestParam(defaultValue = "") String query,
32+
@RequestParam(defaultValue = "10") @Min(1) int display,
33+
@RequestParam(defaultValue = "1") @Min(1) int start,
34+
@RequestParam(defaultValue = "sim") @Pattern(regexp = "sim|date|asc|dsc", message = "sort 값이 유효하지 않습니다.") String sort,
35+
@RequestParam(required = false) String exclude
36+
) {
37+
NaverProductResponseDto responseDto = productService.searchProducts(category, query, display, start, sort, exclude);
38+
return ResponseEntity.ok(ApiResponse.success(responseDto));
39+
}
40+
41+
@GetMapping("/recommend")
42+
@Validated
43+
public ResponseEntity<ApiResponse<NaverProductResponseDto>> recommendProducts(
44+
@RequestParam @NotBlank(message = "카테고리는 필수입니다.") String category,
45+
@RequestParam(defaultValue = "") String query,
46+
@RequestParam(defaultValue = "10") @Min(1) int display,
47+
@RequestParam(defaultValue = "1") @Min(1) int start,
48+
@RequestParam(defaultValue = "sim") @Pattern(regexp = "sim|date|asc|dsc", message = "sort 값이 유효하지 않습니다.") String sort,
49+
@RequestParam(required = false) String exclude,
50+
@AuthenticationPrincipal CustomUserDetails userDetails
51+
) {
52+
User user = userDetails.getUser();
53+
NaverProductResponseDto responseDto = productService.recommendProducts(user.getUserId(), category, query, display, start, sort, exclude);
54+
return ResponseEntity.ok(ApiResponse.success(responseDto));
55+
}
56+
}

0 commit comments

Comments
 (0)