Skip to content

Commit f3d7104

Browse files
authored
Merge pull request #39 from prgrms-aibe-devcourse/feat/#20
feat/ #20 상품 데이터 정제
2 parents 9aaef35 + 0475247 commit f3d7104

12 files changed

Lines changed: 640 additions & 2 deletions

File tree

src/main/java/com/closetnangam/be/domain/clothes/repository/ClothesRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
import com.closetnangam.be.domain.clothes.entity.Clothes;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import java.util.Optional;
7+
68
public interface ClothesRepository extends JpaRepository<Clothes, Long> {
9+
10+
Optional<Clothes> findByExternalProductId(String externalProductId);
711
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//package com.closetnangam.be.domain.recommendation.service;
2+
//
3+
//import com.closetnangam.be.domain.recommendation.dto.response.RecommendResponse;
4+
//import com.closetnangam.be.global.external.naver.service.NaverApiService;
5+
//import lombok.RequiredArgsConstructor;
6+
//import org.springframework.stereotype.Component;
7+
//
8+
//import java.util.List;
9+
//import java.util.stream.Collectors;
10+
//
11+
//// 1. 네가 만든 클래스 (나중에 실제 로직을 여기에 채워!)
12+
//@Component
13+
//@RequiredArgsConstructor
14+
//public class NaverProductProvider {
15+
//
16+
// // 외부 API 서비스 (이미 네 팀에 존재하겠지?)
17+
// private final NaverApiService naverApiService;
18+
//
19+
// public List<RecommendResponse> fetchProducts(String query) {
20+
// // 여기에 네이버 API 호출 로직을 구현하면 돼!
21+
// return naverApiService.searchItems(query).stream()
22+
// .map(item -> new RecommendResponse(
23+
// item.getTitle(),
24+
// item.getLink(),
25+
// item.getImage(),
26+
// item.getLprice()))
27+
// .collect(Collectors.toList());
28+
// }
29+
//}

src/main/java/com/closetnangam/be/domain/user/entity/User.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ public class User extends BaseEntity {
1414

1515
@Id
1616
@GeneratedValue(strategy = GenerationType.IDENTITY)
17+
@Column(name = "user_id")
1718
private Long id;
1819

1920
@Column(nullable = false, unique = true)
2021
private String nickname;
2122

22-
@Column(name = "profile_image")
23+
@Column(name = "profile_image_url")
2324
private String profileImage;
2425

2526
@Column(nullable = false, unique = true)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.closetnangam.be.global.common.controller;
2+
3+
import com.closetnangam.be.global.auth.jwt.JwtTokenProvider;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import org.springframework.context.annotation.Profile;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RequestParam;
11+
import org.springframework.web.bind.annotation.RestController;
12+
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
16+
@Tag(name = "Mock Auth", description = "개발용 임시 인증 API")
17+
@RestController
18+
@RequestMapping("/api/v1/auth")
19+
@Profile("local")
20+
public class MockAuthController {
21+
22+
private final JwtTokenProvider jwtTokenProvider;
23+
24+
public MockAuthController(JwtTokenProvider jwtTokenProvider) {
25+
this.jwtTokenProvider = jwtTokenProvider;
26+
}
27+
28+
@Operation(summary = "테스트용 JWT 토큰 발급", description = "로그인 없이 고유 ID를 지정해 임시 액세스 토큰을 생성합니다.")
29+
@GetMapping("/mock-token")
30+
public ResponseEntity<Map<String, Object>> getMockToken(
31+
@RequestParam(defaultValue = "1") Long userId
32+
) {
33+
// JwtTokenProvider의 팩토리 메서드를 이용해 토큰 생성
34+
String accessToken = jwtTokenProvider.createAccessToken(userId);
35+
36+
Map<String, Object> response = new HashMap<>();
37+
response.put("success", true);
38+
response.put("message", "임시 토큰이 정상 발급되었습니다. 개발용으로만 사용하세요.");
39+
response.put("data", Map.of(
40+
"userId", userId,
41+
"accessToken", accessToken,
42+
"headerValue", "Bearer " + accessToken
43+
));
44+
45+
return ResponseEntity.ok(response);
46+
}
47+
}

src/main/java/com/closetnangam/be/global/config/SecurityConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ public class SecurityConfig {
2929
"/oauth2/**",
3030
"/api/v1/categories/**",
3131
"/api/categories/**", // TODO: FE /api/v1 마이그레이션 완료 후 제거 필요
32-
"/api/v1/clothes/registration-methods"
32+
"/api/v1/clothes/registration-methods",
33+
"/api/naver/**",
34+
"/api/weather/**",
35+
"/api/v1/auth/mock-token"
36+
3337
};
3438

39+
3540
private final OAuth2UserService oAuth2UserService;
3641
private final OAuth2SuccessHandler oAuth2SuccessHandler;
3742
private final JwtTokenProvider jwtTokenProvider;
@@ -53,4 +58,5 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5358

5459
return http.build();
5560
}
61+
5662
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.closetnangam.be.global.external.clothes.controller;
2+
3+
import com.closetnangam.be.domain.wardrobe.service.WardrobeService;
4+
import com.closetnangam.be.global.auth.util.SecurityUtils;
5+
import com.closetnangam.be.global.common.response.ApiResponse;
6+
import com.closetnangam.be.global.external.clothes.dto.record.NaverProductCreateRequest;
7+
import com.closetnangam.be.global.external.clothes.dto.response.SaveNaverProductResponse;
8+
import com.closetnangam.be.global.external.clothes.service.ExternalClothesService;
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import jakarta.validation.Valid;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.web.bind.annotation.PostMapping;
14+
import org.springframework.web.bind.annotation.RequestBody;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RestController;
17+
18+
@Tag(name = "External Clothes", description = "외부 서비스 상품 연동 API")
19+
@RestController
20+
@RequestMapping("/api/v1/external/clothes")
21+
@RequiredArgsConstructor
22+
public class ExternalClothesController {
23+
24+
private final ExternalClothesService externalClothesService;
25+
private final WardrobeService wardrobeService;
26+
27+
@Operation(summary = "네이버 상품 저장", description = "네이버에서 검색한 상품 정보를 저장합니다.")
28+
@PostMapping("/naver")
29+
public ApiResponse<SaveNaverProductResponse> saveNaverProduct(
30+
@Valid @RequestBody NaverProductCreateRequest request
31+
) {
32+
Long clothesId = externalClothesService.getOrCreateExternalClothes(
33+
request,
34+
request.colors(),
35+
request.styles()
36+
);
37+
38+
return ApiResponse.ok(new SaveNaverProductResponse(clothesId));
39+
}
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.closetnangam.be.global.external.clothes.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.Size;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
public class NaverItemRequest {
13+
14+
@NotBlank(message = "상품명은 필수입니다")
15+
@Size(max = 255, message = "상품명은 255자 이하여야 합니다")
16+
private String title;
17+
18+
@NotBlank(message = "상품 링크는 필수입니다")
19+
@Size(max = 500, message = "링크는 500자 이하여야 합니다")
20+
private String link;
21+
22+
@NotBlank(message = "이미지 URL은 필수입니다")
23+
@Size(max = 500, message = "이미지 URL은 500자 이하여야 합니다")
24+
private String image;
25+
26+
@Size(max = 100)
27+
private String brand;
28+
29+
@NotBlank(message = "상품 ID는 필수입니다")
30+
private String productId;
31+
32+
@Size(max = 100)
33+
private String category1;
34+
35+
@Size(max = 100)
36+
private String category3;
37+
38+
public String getCleanTitle() {
39+
if (this.title == null) {
40+
return "";
41+
}
42+
return this.title.replaceAll("<[^>]*>", "").trim();
43+
}
44+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.closetnangam.be.global.external.clothes.dto.record;
2+
3+
import com.closetnangam.be.global.external.clothes.dto.request.ClothesStyleDto;
4+
import com.closetnangam.be.global.external.clothes.dto.request.ClothingColorDto;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import jakarta.validation.Valid;
7+
import jakarta.validation.constraints.NotBlank;
8+
import jakarta.validation.constraints.Size;
9+
10+
import java.util.List;
11+
12+
public record NaverProductCreateRequest(
13+
@NotBlank(message = "상품 ID는 필수입니다.")
14+
String productId,
15+
String brand,
16+
String category3,
17+
18+
@JsonProperty("title")
19+
@NotBlank(message = "상품명은 필수입니다.")
20+
@Size(max = 255, message = "상품명은 255자 이내여야 합니다.")
21+
String cleanTitle,
22+
23+
@NotBlank(message = "이미지 URL은 필수입니다.")
24+
@Size(max = 500, message = "이미지 URL이 너무 깁니다.")
25+
String image,
26+
27+
@NotBlank(message = "상품 링크는 필수입니다.")
28+
@Size(max = 500, message = "링크 주소가 너무 깁니다.")
29+
String link,
30+
31+
@JsonProperty("colors")
32+
@Valid
33+
List<ClothingColorDto> colors,
34+
35+
@JsonProperty("styles")
36+
@Valid
37+
List<ClothesStyleDto> styles
38+
) {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.closetnangam.be.global.external.clothes.dto.request;
2+
3+
import com.closetnangam.be.domain.clothes.enums.StyleRole;
4+
import jakarta.validation.constraints.NotNull;
5+
import jakarta.validation.constraints.PositiveOrZero;
6+
7+
public record ClothesStyleDto(
8+
@NotNull Long styleId,
9+
@NotNull StyleRole styleRole,
10+
@NotNull @PositiveOrZero Byte sortOrder
11+
) {
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.closetnangam.be.global.external.clothes.dto.request;
2+
3+
import com.closetnangam.be.domain.clothes.enums.ColorRole;
4+
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotNull;
6+
import jakarta.validation.constraints.PositiveOrZero;
7+
import jakarta.validation.constraints.Size;
8+
9+
public record ClothingColorDto(
10+
@NotBlank @Size(max = 50) String colorCode,
11+
@NotNull ColorRole colorRole,
12+
@NotNull @PositiveOrZero Byte sortOrder
13+
) {
14+
}

0 commit comments

Comments
 (0)