diff --git a/src/main/java/com/example/reddiserver/common/ApiExceptionHandler.java b/src/main/java/com/example/reddiserver/common/ApiExceptionHandler.java index 8be0b39..748ba61 100644 --- a/src/main/java/com/example/reddiserver/common/ApiExceptionHandler.java +++ b/src/main/java/com/example/reddiserver/common/ApiExceptionHandler.java @@ -12,7 +12,7 @@ public class ApiExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity> handleExceptions(RuntimeException exception) { - log.error(exception.getMessage()); + log.error("Exception occurred:", exception); // 스택 트레이스 정보를 포함한 로깅 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.errorResponse(exception.getMessage())); } diff --git a/src/main/java/com/example/reddiserver/controller/BrandController.java b/src/main/java/com/example/reddiserver/controller/BrandController.java index ec4f6d1..4dee924 100644 --- a/src/main/java/com/example/reddiserver/controller/BrandController.java +++ b/src/main/java/com/example/reddiserver/controller/BrandController.java @@ -18,7 +18,7 @@ @RequestMapping("/api/brand") public class BrandController { - private final BrandService brandService;; + private final BrandService brandService; @Operation(summary = "브랜드 리스트 조회") @GetMapping("/") diff --git a/src/main/java/com/example/reddiserver/controller/NotionController.java b/src/main/java/com/example/reddiserver/controller/NotionController.java index 152fa92..4a896c6 100644 --- a/src/main/java/com/example/reddiserver/controller/NotionController.java +++ b/src/main/java/com/example/reddiserver/controller/NotionController.java @@ -5,6 +5,9 @@ import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -14,6 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/notion") +@Slf4j public class NotionController { private final NotionService notionService; @@ -21,7 +25,10 @@ public class NotionController { @Hidden @Operation(summary = "[테스트용] 노션 api 호출해서 자체 DB 갱신") @GetMapping("/update") - public ApiResponse> getNotionData() { + public ApiResponse getNotionData() { + + // DB init + notionService.deleteAll(); List brandPageIds = notionService.getBrandPageIds(); notionService.getBrandPageContents(brandPageIds); @@ -29,8 +36,11 @@ public ApiResponse> getNotionData() { // 임시로 하나만 조회 // notionService.getBrandPageContents(Collections.singletonList(brandPageIds.get(0))); + List marketingPageIds = notionService.getMarketingPageIds(); + notionService.getMarketingPageContents(marketingPageIds); + - return ApiResponse.successResponse(notionService.getBrandPageIds()); + return ApiResponse.successWithNoContent(); } } \ No newline at end of file diff --git a/src/main/java/com/example/reddiserver/controller/PostController.java b/src/main/java/com/example/reddiserver/controller/PostController.java new file mode 100644 index 0000000..644e907 --- /dev/null +++ b/src/main/java/com/example/reddiserver/controller/PostController.java @@ -0,0 +1,34 @@ +package com.example.reddiserver.controller; + +import com.example.reddiserver.common.ApiResponse; +import com.example.reddiserver.dto.post.response.PostContentsResponseDto; +import com.example.reddiserver.dto.post.response.PostResponseDto; +import com.example.reddiserver.service.PostService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/post") +public class PostController { + private final PostService postService; + + @Operation(summary = "포스트(마케팅) 리스트 조회") + @GetMapping("/") + public ApiResponse> getPostList() { + List postList = postService.getPostList(); + return ApiResponse.successResponse(postList); + } + + @Operation(summary = "포스트(마케팅) 단건(상세) 조회") + @GetMapping("/{id}") + public ApiResponse getPostById(Long id) { + PostContentsResponseDto post = postService.getPostById(id); + return ApiResponse.successResponse(post); + } +} diff --git a/src/main/java/com/example/reddiserver/dto/brand/response/BrandContentsResponseDto.java b/src/main/java/com/example/reddiserver/dto/brand/response/BrandContentsResponseDto.java index f47bdc9..d8fbb2c 100644 --- a/src/main/java/com/example/reddiserver/dto/brand/response/BrandContentsResponseDto.java +++ b/src/main/java/com/example/reddiserver/dto/brand/response/BrandContentsResponseDto.java @@ -1,24 +1,35 @@ package com.example.reddiserver.dto.brand.response; import com.example.reddiserver.entity.Brand; +import com.example.reddiserver.entity.Post; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import com.fasterxml.jackson.annotation.JsonRawValue; import java.util.List; +import java.util.stream.Collectors; @Getter @Setter @NoArgsConstructor public class BrandContentsResponseDto { - private Long id; + private BrandResponseDto brand; + + private List postIds; @JsonRawValue private String content; - public BrandContentsResponseDto(Brand brand) { - this.id = brand.getId(); - this.content = brand.getContent(); + public static BrandContentsResponseDto from(Brand brand) { + BrandContentsResponseDto brandContentsResponseDto = new BrandContentsResponseDto(); + brandContentsResponseDto.setBrand(BrandResponseDto.from(brand)); + brandContentsResponseDto.setContent(brand.getContent()); + + // Post의 id 목록 추가 + brandContentsResponseDto.setPostIds(brand.getPosts().stream() + .map(Post::getId) + .collect(Collectors.toList())); + return brandContentsResponseDto; } } diff --git a/src/main/java/com/example/reddiserver/dto/brand/response/BrandResponseDto.java b/src/main/java/com/example/reddiserver/dto/brand/response/BrandResponseDto.java index 188522d..345c12d 100644 --- a/src/main/java/com/example/reddiserver/dto/brand/response/BrandResponseDto.java +++ b/src/main/java/com/example/reddiserver/dto/brand/response/BrandResponseDto.java @@ -1,5 +1,6 @@ package com.example.reddiserver.dto.brand.response; +import com.example.reddiserver.dto.post.response.PostResponseDto; import com.example.reddiserver.entity.Brand; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,20 +15,21 @@ public class BrandResponseDto { private Long id; private String name; private List brandTags; - private String imageUrl; + private String cover_url; private String notion_page_url; private String notion_page_created_time; private String notion_page_last_edited_time; - - public BrandResponseDto(Brand brand) { - this.id = brand.getId(); - this.brandTags = BrandTagDto.convertToDtoList(brand.getBrandTags()); - this.name = brand.getName(); - this.imageUrl = brand.getImage_url(); - this.notion_page_url = brand.getNotion_page_url(); - this.notion_page_created_time = brand.getNotion_page_created_time(); - this.notion_page_last_edited_time = brand.getNotion_page_last_edited_time(); - + // 정적 메서드 + public static BrandResponseDto from(Brand brand) { + BrandResponseDto brandResponseDto = new BrandResponseDto(); + brandResponseDto.setId(brand.getId()); + brandResponseDto.setName(brand.getName()); + brandResponseDto.setBrandTags(BrandTagDto.convertToDtoList(brand.getBrandTags())); + brandResponseDto.setCover_url(brand.getCover_url()); + brandResponseDto.setNotion_page_url(brand.getNotion_page_url()); + brandResponseDto.setNotion_page_created_time(brand.getNotion_page_created_time()); + brandResponseDto.setNotion_page_last_edited_time(brand.getNotion_page_last_edited_time()); + return brandResponseDto; } } diff --git a/src/main/java/com/example/reddiserver/dto/brand/response/BrandTagDto.java b/src/main/java/com/example/reddiserver/dto/brand/response/BrandTagDto.java index 12a575f..19112f5 100644 --- a/src/main/java/com/example/reddiserver/dto/brand/response/BrandTagDto.java +++ b/src/main/java/com/example/reddiserver/dto/brand/response/BrandTagDto.java @@ -21,8 +21,6 @@ public BrandTagDto(BrandTag brandTag) { this.tag = brandTag.getTag(); } - // Getters and setters (if needed) - public static List convertToDtoList(List brandTags) { return brandTags.stream() .map(BrandTagDto::new) diff --git a/src/main/java/com/example/reddiserver/dto/post/response/PostContentsResponseDto.java b/src/main/java/com/example/reddiserver/dto/post/response/PostContentsResponseDto.java new file mode 100644 index 0000000..7a02a6b --- /dev/null +++ b/src/main/java/com/example/reddiserver/dto/post/response/PostContentsResponseDto.java @@ -0,0 +1,24 @@ +package com.example.reddiserver.dto.post.response; + +import com.example.reddiserver.entity.Post; +import com.fasterxml.jackson.annotation.JsonRawValue; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class PostContentsResponseDto { + private PostResponseDto post; + + @JsonRawValue + private String content; + + public static PostContentsResponseDto from(Post post) { + PostContentsResponseDto postContentsResponseDto = new PostContentsResponseDto(); + postContentsResponseDto.setPost(PostResponseDto.from(post)); + postContentsResponseDto.setContent(post.getContent()); + return postContentsResponseDto; + } +} diff --git a/src/main/java/com/example/reddiserver/dto/post/response/PostResponseDto.java b/src/main/java/com/example/reddiserver/dto/post/response/PostResponseDto.java new file mode 100644 index 0000000..e804de8 --- /dev/null +++ b/src/main/java/com/example/reddiserver/dto/post/response/PostResponseDto.java @@ -0,0 +1,46 @@ +package com.example.reddiserver.dto.post.response; + +import com.example.reddiserver.dto.brand.response.BrandResponseDto; +import com.example.reddiserver.entity.Post; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +public class PostResponseDto { + private Long id; + private Long brand_id; + private String title; + private String subtitle; + private String description; + private List postTags; + private String cover_url; + private String notion_page_url; + private String notion_page_created_time; + private String notion_page_last_edited_time; + + // 정적 메서드 + public static PostResponseDto from(Post post) { + PostResponseDto postResponseDto = new PostResponseDto(); + postResponseDto.setId(post.getId()); + postResponseDto.setTitle(post.getTitle()); + postResponseDto.setSubtitle(post.getSubtitle()); + postResponseDto.setDescription(post.getDescription()); + postResponseDto.setPostTags(PostTagDto.convertToDtoList(post.getPostTags())); + postResponseDto.setCover_url(post.getCover_url()); + postResponseDto.setNotion_page_url(post.getNotion_page_url()); + postResponseDto.setNotion_page_created_time(post.getNotion_page_created_time()); + postResponseDto.setNotion_page_last_edited_time(post.getNotion_page_last_edited_time()); + + // Brand 정보 추가 + if (post.getBrand() != null){ + postResponseDto.setBrand_id(post.getBrand().getId()); + } + + return postResponseDto; + } +} diff --git a/src/main/java/com/example/reddiserver/dto/post/response/PostTagDto.java b/src/main/java/com/example/reddiserver/dto/post/response/PostTagDto.java new file mode 100644 index 0000000..7ab0475 --- /dev/null +++ b/src/main/java/com/example/reddiserver/dto/post/response/PostTagDto.java @@ -0,0 +1,29 @@ +package com.example.reddiserver.dto.post.response; + +import com.example.reddiserver.entity.PostTag; +import com.example.reddiserver.entity.enums.PostTagType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +public class PostTagDto { + private PostTagType postTagType; + private String tag; + + public PostTagDto(PostTag postTag) { + this.postTagType = postTag.getPostTagType(); + this.tag = postTag.getTag(); + } + + public static List convertToDtoList(List postTags) { + return postTags.stream() + .map(PostTagDto::new) + .toList(); + } +} diff --git a/src/main/java/com/example/reddiserver/entity/Bookmark.java b/src/main/java/com/example/reddiserver/entity/Bookmark.java index 26382c0..f448755 100644 --- a/src/main/java/com/example/reddiserver/entity/Bookmark.java +++ b/src/main/java/com/example/reddiserver/entity/Bookmark.java @@ -22,7 +22,7 @@ public class Bookmark extends BaseTimeEntity { @OneToMany(mappedBy = "bookmark") private List bookmarkPosts = new ArrayList<>(); - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) // 연관관계 주인 @JoinColumn(name = "member_id") private Member member; diff --git a/src/main/java/com/example/reddiserver/entity/BookmarkPost.java b/src/main/java/com/example/reddiserver/entity/BookmarkPost.java index a5a109c..4e8ba1f 100644 --- a/src/main/java/com/example/reddiserver/entity/BookmarkPost.java +++ b/src/main/java/com/example/reddiserver/entity/BookmarkPost.java @@ -16,11 +16,11 @@ public class BookmarkPost extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) // 연관관계 주인 @JoinColumn(name = "bookmark_id") private Bookmark bookmark; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) // 연관관계 주인 @JoinColumn(name = "post_id") private Post post; diff --git a/src/main/java/com/example/reddiserver/entity/Brand.java b/src/main/java/com/example/reddiserver/entity/Brand.java index ca46df3..215b0e3 100644 --- a/src/main/java/com/example/reddiserver/entity/Brand.java +++ b/src/main/java/com/example/reddiserver/entity/Brand.java @@ -26,8 +26,8 @@ public class Brand extends BaseTimeEntity { @Column private String name; - @Column - private String image_url; + @Column(columnDefinition = "LONGTEXT") + private String cover_url; @Column(columnDefinition = "LONGTEXT") private String content; @@ -44,15 +44,4 @@ public class Brand extends BaseTimeEntity { @Column private String notion_page_last_edited_time; - - @Builder - public Brand(String name, String image_url, String content, String notion_page_id, String notion_page_url, String notion_page_created_time, String notion_page_last_edited_time) { - this.name = name; - this.image_url = image_url; - this.content = content; - this.notion_page_id = notion_page_id; - this.notion_page_url = notion_page_url; - this.notion_page_created_time = notion_page_created_time; - this.notion_page_last_edited_time = notion_page_last_edited_time; - } } diff --git a/src/main/java/com/example/reddiserver/entity/BrandTag.java b/src/main/java/com/example/reddiserver/entity/BrandTag.java index 5d5efbb..27a0893 100644 --- a/src/main/java/com/example/reddiserver/entity/BrandTag.java +++ b/src/main/java/com/example/reddiserver/entity/BrandTag.java @@ -17,7 +17,7 @@ public class BrandTag extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) // 연관관계 주인 @JoinColumn(name = "brand_id") private Brand brand; diff --git a/src/main/java/com/example/reddiserver/entity/Post.java b/src/main/java/com/example/reddiserver/entity/Post.java index 868a4f7..6d16b58 100644 --- a/src/main/java/com/example/reddiserver/entity/Post.java +++ b/src/main/java/com/example/reddiserver/entity/Post.java @@ -2,17 +2,15 @@ import com.example.reddiserver.entity.base.BaseTimeEntity; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.util.ArrayList; import java.util.List; @Entity @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Setter +@NoArgsConstructor @Table(name = "posts") public class Post extends BaseTimeEntity { @@ -26,7 +24,7 @@ public class Post extends BaseTimeEntity { @OneToMany(mappedBy = "post") private List postTags = new ArrayList<>(); - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) // 연관관계 주인 @JoinColumn(name = "brand_id") private Brand brand; @@ -34,16 +32,26 @@ public class Post extends BaseTimeEntity { private String title; @Column - private String image_url; + private String subtitle; - @Column(nullable = false, unique = true) + @Column + private String description; + + @Column(columnDefinition = "LONGTEXT") + private String cover_url; + + @Column(columnDefinition = "LONGTEXT") private String content; - @Builder - public Post(Brand brand, String title, String image_url, String content) { - this.brand = brand; - this.title = title; - this.image_url = image_url; - this.content = content; - } + @Column + private String notion_page_id; + + @Column + private String notion_page_url; + + @Column + private String notion_page_created_time; + + @Column + private String notion_page_last_edited_time; } diff --git a/src/main/java/com/example/reddiserver/entity/PostProperty.java b/src/main/java/com/example/reddiserver/entity/PostProperty.java deleted file mode 100644 index 4ef42a5..0000000 --- a/src/main/java/com/example/reddiserver/entity/PostProperty.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.reddiserver.entity; - -import com.example.reddiserver.entity.base.BaseTimeEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.ArrayList; -import java.util.List; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "post_properties") -public class PostProperty extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - - @OneToMany(mappedBy = "postProperty") - private List PostTags = new ArrayList<>();; -} diff --git a/src/main/java/com/example/reddiserver/entity/PostTag.java b/src/main/java/com/example/reddiserver/entity/PostTag.java index 7a0f789..6c80d58 100644 --- a/src/main/java/com/example/reddiserver/entity/PostTag.java +++ b/src/main/java/com/example/reddiserver/entity/PostTag.java @@ -1,13 +1,13 @@ package com.example.reddiserver.entity; import com.example.reddiserver.entity.base.BaseTimeEntity; +import com.example.reddiserver.entity.enums.PostTagType; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -// 연결테이블 @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -17,17 +17,21 @@ public class PostTag extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) // 연관관계 주인 @JoinColumn(name = "post_id") private Post post; - @ManyToOne - @JoinColumn(name = "post_property_id") - private PostProperty postProperty; + @Enumerated(EnumType.STRING) + private PostTagType postTagType; + // 기업, 마케팅_종류, 산업, 타겟층 + + @Column + private String tag; @Builder - public PostTag(Post post, PostProperty postProperty) { + public PostTag(Post post, PostTagType postTagType, String tag) { this.post = post; - this.postProperty = postProperty; + this.postTagType = postTagType; + this.tag = tag; } } diff --git a/src/main/java/com/example/reddiserver/entity/enums/PostTagType.java b/src/main/java/com/example/reddiserver/entity/enums/PostTagType.java new file mode 100644 index 0000000..d1665a2 --- /dev/null +++ b/src/main/java/com/example/reddiserver/entity/enums/PostTagType.java @@ -0,0 +1,5 @@ +package com.example.reddiserver.entity.enums; + +public enum PostTagType { + 기업, 마케팅_종류, 산업, 타겟층 +} diff --git a/src/main/java/com/example/reddiserver/repository/BrandRepository.java b/src/main/java/com/example/reddiserver/repository/BrandRepository.java index 01bf18f..73e245d 100644 --- a/src/main/java/com/example/reddiserver/repository/BrandRepository.java +++ b/src/main/java/com/example/reddiserver/repository/BrandRepository.java @@ -2,6 +2,13 @@ import com.example.reddiserver.entity.Brand; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface BrandRepository extends JpaRepository { + + @Query("SELECT b FROM Brand b WHERE b.notion_page_id = :notion_page_id") + Brand findBrandByNotion_page_id(@Param("notion_page_id") String notion_page_id); } diff --git a/src/main/java/com/example/reddiserver/repository/PostRepository.java b/src/main/java/com/example/reddiserver/repository/PostRepository.java new file mode 100644 index 0000000..b3938b5 --- /dev/null +++ b/src/main/java/com/example/reddiserver/repository/PostRepository.java @@ -0,0 +1,7 @@ +package com.example.reddiserver.repository; + +import com.example.reddiserver.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/reddiserver/repository/PostTagRepository.java b/src/main/java/com/example/reddiserver/repository/PostTagRepository.java new file mode 100644 index 0000000..30a9952 --- /dev/null +++ b/src/main/java/com/example/reddiserver/repository/PostTagRepository.java @@ -0,0 +1,7 @@ +package com.example.reddiserver.repository; + +import com.example.reddiserver.entity.PostTag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostTagRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/reddiserver/service/BrandService.java b/src/main/java/com/example/reddiserver/service/BrandService.java index b2e3c53..8d50c59 100644 --- a/src/main/java/com/example/reddiserver/service/BrandService.java +++ b/src/main/java/com/example/reddiserver/service/BrandService.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -22,10 +21,9 @@ public List getBrandList() { List brands = brandRepository.findAll(); List brandResponseDtos = new ArrayList<>(); - System.out.println("111"); - for (Brand brand : brands) { - brandResponseDtos.add(new BrandResponseDto(brand)); + brandResponseDtos.add(BrandResponseDto.from(brand)); + } return brandResponseDtos; @@ -33,7 +31,7 @@ public List getBrandList() { public BrandContentsResponseDto getBrandById(Long id) { Brand brand = brandRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 브랜드가 없습니다. id=" + id)); - return new BrandContentsResponseDto(brand); + return BrandContentsResponseDto.from(brand); } diff --git a/src/main/java/com/example/reddiserver/service/NotionService.java b/src/main/java/com/example/reddiserver/service/NotionService.java index 1932203..b9206d9 100644 --- a/src/main/java/com/example/reddiserver/service/NotionService.java +++ b/src/main/java/com/example/reddiserver/service/NotionService.java @@ -2,10 +2,17 @@ import com.example.reddiserver.entity.Brand; import com.example.reddiserver.entity.BrandTag; +import com.example.reddiserver.entity.Post; +import com.example.reddiserver.entity.PostTag; import com.example.reddiserver.entity.enums.BrandTagType; +import com.example.reddiserver.entity.enums.PostTagType; import com.example.reddiserver.repository.BrandRepository; import com.example.reddiserver.repository.BrandTagRepository; +import com.example.reddiserver.repository.PostRepository; +import com.example.reddiserver.repository.PostTagRepository; import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.persistence.EntityManager; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -19,18 +26,24 @@ import java.util.*; @Service +@Slf4j @Transactional(readOnly = true) public class NotionService { @Value("${notion.brandDB.id}") private String BRAND_DB_ID; + @Value("${notion.marketingDB.id}") + private String MARKETING_DB_ID; + private final WebClient webClient; - private final ObjectMapper objectMapper; private final BrandRepository brandRepository; private final BrandTagRepository brandTagRepository; + private final PostRepository postRepository; + private final PostTagRepository postTagRepository; + private final EntityManager em; - public NotionService(WebClient.Builder webClientBuilder, @Value("${notion.api.key}") String NOTION_API_KEY, ObjectMapper objectMapper, BrandRepository brandRepository, BrandTagRepository brandTagRepository) { + public NotionService(WebClient.Builder webClientBuilder, @Value("${notion.api.key}") String NOTION_API_KEY, BrandRepository brandRepository, BrandTagRepository brandTagRepository, PostRepository postRepository, PostTagRepository postTagRepository, EntityManager em) { this.webClient = webClientBuilder. baseUrl("https://api.notion.com/v1") .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + NOTION_API_KEY) @@ -38,9 +51,21 @@ public NotionService(WebClient.Builder webClientBuilder, @Value("${notion.api.ke .defaultHeader("Notion-Version", "2022-06-28") .build(); - this.objectMapper = objectMapper; this.brandRepository = brandRepository; this.brandTagRepository = brandTagRepository; + this.postRepository = postRepository; + this.postTagRepository = postTagRepository; + this.em = em; + } + + // brand, brand_tags 테이블 초기화 + @Transactional + public void deleteAll() { + brandTagRepository.deleteAll(); + brandRepository.deleteAll(); + + em.createNativeQuery("ALTER TABLE brands AUTO_INCREMENT = 1").executeUpdate(); + em.createNativeQuery("ALTER TABLE brand_tags AUTO_INCREMENT = 1").executeUpdate(); } // 브랜드 database 순회하면서 브랜드 페이지 Id 추출 @@ -54,7 +79,11 @@ public List getBrandPageIds() { List ids = new ArrayList<>(); for (JsonNode resultNode : response.get("results")) { - if (resultNode.has("id") && resultNode.get("id").isTextual()) { + + JsonNode isPublishedNode = resultNode.get("properties").get("게시여부").get("select"); + + // 게시여부가 Published인 경우에만 브랜드 페이지 Id 추출 + if (!isPublishedNode.isNull() && isPublishedNode.get("name").asText().equals("Published")){ ids.add(resultNode.get("id").asText()); } } @@ -71,99 +100,118 @@ public void getBrandPageContents(List brandPageIds) { for (String brandPageId : brandPageIds) { - // Fetch metadata for the brand page - JsonNode pageMetadata = fetchPageMetadata(brandPageId); - // Extract information from the pageMetadata JsonNode as needed - String pageId = pageMetadata.get("id").asText(); - String createdTime = pageMetadata.get("created_time").asText(); - String lastEditedTime = pageMetadata.get("last_edited_time").asText(); - String title = pageMetadata.get("properties").get("이름").get("title").get(0).get("plain_text").asText(); - String notionUrl = pageMetadata.get("url").asText(); + try { + // Fetch metadata for the brand page + JsonNode pageMetadata = fetchPageMetadata(brandPageId); + + // Extract information from the pageMetadata JsonNode as needed + String pageId = pageMetadata.get("id").asText(); + String createdTime = pageMetadata.get("created_time").asText(); + String lastEditedTime = pageMetadata.get("last_edited_time").asText(); + String title = pageMetadata.get("properties").get("이름").get("title").get(0).get("plain_text").asText(); + + JsonNode coverNode = pageMetadata.get("cover"); + String coverUrl = null; + if (!coverNode.isNull()) { + if (coverNode.get("type").asText().equals("external")) { + coverUrl = coverNode.get("external").get("url").asText(); + } + else if (coverNode.get("type").asText().equals("file")) { + coverUrl = coverNode.get("file").get("url").asText(); + } + } - Brand brand = new Brand(); + String notionUrl = pageMetadata.get("url").asText(); - brand.setNotion_page_id(pageId); - brand.setNotion_page_created_time(createdTime); - brand.setNotion_page_last_edited_time(lastEditedTime); - brand.setName(title); - brand.setNotion_page_url(notionUrl); + Brand brand = new Brand(); + brand.setNotion_page_id(pageId); + brand.setNotion_page_created_time(createdTime); + brand.setNotion_page_last_edited_time(lastEditedTime); + brand.setName(title); + brand.setCover_url(coverUrl); + brand.setNotion_page_url(notionUrl); - // Extract properties information - JsonNode propertiesNode = pageMetadata.get("properties"); + // Extract properties information + JsonNode propertiesNode = pageMetadata.get("properties"); - // Extract key - // Check if propertiesNode is not null before proceeding - if (propertiesNode != null && propertiesNode.isObject()) { - // Iterate over the field names and print them - propertiesNode.fieldNames().forEachRemaining(fieldName -> { - if (fieldName.equals("브랜드_분위기") || fieldName.equals("브랜드_색감") || fieldName.equals("MKT_종류") || fieldName.equals("MKT_타겟층") || fieldName.equals("산업군")) { - JsonNode multiSelect = propertiesNode.get(fieldName).get("multi_select"); + // Extract key + // Check if propertiesNode is not null before proceeding + if (propertiesNode != null && propertiesNode.isObject()) { + // Iterate over the field names and print them + propertiesNode.fieldNames().forEachRemaining(fieldName -> { - if (multiSelect != null && multiSelect.isArray()) { - for (JsonNode select : multiSelect) { - String tag = select.get("name").asText(); + if (fieldName.equals("브랜드_분위기") || fieldName.equals("브랜드_색감") || fieldName.equals("MKT_종류") || fieldName.equals("MKT_타겟층") || fieldName.equals("산업군")) { + JsonNode multiSelect = propertiesNode.get(fieldName).get("multi_select"); - // Brand Tag 에 저장 - BrandTag brandTag = BrandTag.builder() - .brand(brand) - .brandTagType(BrandTagType.valueOf(fieldName)) - .tag(tag) - .build(); + if (multiSelect != null && multiSelect.isArray()) { + for (JsonNode select : multiSelect) { + String tag = select.get("name").asText(); + // Brand Tag 에 저장 + BrandTag brandTag = BrandTag.builder() + .brand(brand) + .brandTagType(BrandTagType.valueOf(fieldName)) + .tag(tag) + .build(); - brandTagRepository.save(brandTag); - // 또는 - // brand.getBrandTags().add(brandTag); + brandTagRepository.save(brandTag); + // 또는 + // brand.getBrandTags().add(brandTag); + + } } } - } - }); - } + }); + } - else { - System.out.println("propertiesNode is null or not an object"); - } + else { + log.error("propertiesNode is null or not an object"); + } - // fetch page contents + // fetch page contents - JsonNode pageContents = fetchPageContents(brandPageId).get("results"); + JsonNode pageContents = fetchPageContents(brandPageId).get("results"); - List contentList = new ArrayList<>(); + List contentList = new ArrayList<>(); - for (JsonNode block : pageContents) { - if (block.isObject()) { - // Convert the JsonNode to ObjectNode for easy removal of keys - ObjectNode blockObject = (ObjectNode) block; + for (JsonNode block : pageContents) { + if (block.isObject()) { + // Convert the JsonNode to ObjectNode for easy removal of keys + ObjectNode blockObject = (ObjectNode) block; - // Remove specific keys - blockObject.remove("object"); - blockObject.remove("id"); - blockObject.remove("parent"); - blockObject.remove("created_time"); - blockObject.remove("last_edited_time"); - blockObject.remove("created_by"); - blockObject.remove("last_edited_by"); - blockObject.remove("has_children"); - blockObject.remove("archived"); - blockObject.remove("is_toggleable"); + // Remove specific keys + blockObject.remove("object"); + blockObject.remove("id"); + blockObject.remove("parent"); + blockObject.remove("created_time"); + blockObject.remove("last_edited_time"); + blockObject.remove("created_by"); + blockObject.remove("last_edited_by"); + blockObject.remove("has_children"); + blockObject.remove("archived"); + blockObject.remove("is_toggleable"); - contentList.add(blockObject); + contentList.add(blockObject); + } } - } - brand.setContent(contentList.toString()); - brandRepository.save(brand); + brand.setContent(contentList.toString()); + brandRepository.save(brand); + + } + catch (Exception e) { + log.error("Error while fetching page metadata for pageId: {}", brandPageId, e); + throw e; + } - // Fetch and process additional metadata for each property - // Example: fetchPropertyMetadata(pageId, "BvYH"); } } @@ -184,4 +232,178 @@ public JsonNode fetchPageContents(String pageId) { .block(); } + // 브랜드 database 순회하면서 브랜드 페이지 Id 추출 + public List getMarketingPageIds() { + JsonNode response = webClient.post().uri("/databases/"+MARKETING_DB_ID+"/query").body(BodyInserters.empty()) + .retrieve() + .bodyToMono(JsonNode.class) + .block(); + + if (response != null && response.has("results") && response.get("results").isArray()) { + List ids = new ArrayList<>(); + + for (JsonNode resultNode : response.get("results")) { + +// log.info("resultNode : {}", resultNode.toPrettyString()); + + JsonNode isPublishedNode = resultNode.get("properties").get("게시여부").get("select"); + + // 게시여부가 Published인 경우에만 브랜드 페이지 Id 추출 + if (!isPublishedNode.isNull() && isPublishedNode.get("name").asText().equals("Published")){ + ids.add(resultNode.get("id").asText()); + } + } + + return ids; + } else { + return Collections.emptyList(); + } + } + + // 브랜드 페이지 순회하면서 브랜드 페이지 내용 추출 후 DB 저장 + @Transactional + public void getMarketingPageContents(List marketingPageIds) { + + + for (String marketingPageId : marketingPageIds) { + + try { + // Fetch metadata for the brand page + JsonNode pageMetadata = fetchPageMetadata(marketingPageId); + + // Extract information from the pageMetadata JsonNode as needed + String pageId = pageMetadata.get("id").asText(); + String createdTime = pageMetadata.get("created_time").asText(); + String lastEditedTime = pageMetadata.get("last_edited_time").asText(); + String title = pageMetadata.get("properties").get("이름").get("title").get(0).get("plain_text").asText(); + + JsonNode subtitleNode = pageMetadata.get("properties").get("소제목").get("rich_text"); + String subtitle = null; + if (!subtitleNode.isEmpty()) { + subtitle = subtitleNode.get(0).get("plain_text").asText(); + } + + JsonNode descriptionNode = pageMetadata.get("properties").get("설명").get("rich_text"); + String description = null; + if (!descriptionNode.isEmpty()) { + description = descriptionNode.get(0).get("plain_text").asText(); + } + + JsonNode coverNode = pageMetadata.get("cover"); + String coverUrl = null; + if (!coverNode.isNull()) { + if (coverNode.get("type").asText().equals("external")) { + coverUrl = coverNode.get("external").get("url").asText(); + } + else if (coverNode.get("type").asText().equals("file")) { + coverUrl = coverNode.get("file").get("url").asText(); + } + } + + String notionUrl = pageMetadata.get("url").asText(); + + Post post = new Post(); + + post.setNotion_page_id(pageId); + post.setNotion_page_created_time(createdTime); + post.setNotion_page_last_edited_time(lastEditedTime); + post.setTitle(title); + post.setSubtitle(subtitle); + post.setDescription(description); + post.setCover_url(coverUrl); + post.setNotion_page_url(notionUrl); + + JsonNode BrandNode = pageMetadata.get("properties").get("브랜드").get("relation"); + String brandId = null; + if (!BrandNode.isEmpty()) { + brandId = BrandNode.get(0).get("id").asText(); + + // brandId로 브랜드 엔티티 가져오기 + Brand brand = brandRepository.findBrandByNotion_page_id(brandId); + + if (brand != null) { + post.setBrand(brand); + } + } + + // Extract properties information + JsonNode propertiesNode = pageMetadata.get("properties"); + + + // 태그 추출 후 저장 + if (propertiesNode != null && propertiesNode.isObject()) { + // Iterate over the field names and print them + propertiesNode.fieldNames().forEachRemaining(fieldName -> { + + if (fieldName.equals("기업") || fieldName.equals("마케팅_종류") || fieldName.equals("산업") || fieldName.equals("타겟층")) { + JsonNode multiSelect = propertiesNode.get(fieldName).get("multi_select"); + + if (multiSelect != null && multiSelect.isArray()) { + for (JsonNode select : multiSelect) { + String tag = select.get("name").asText(); + + // Brand Tag 에 저장 + PostTag postTag = PostTag.builder() + .post(post) + .postTagType(PostTagType.valueOf(fieldName)) + .tag(tag) + .build(); + + + postTagRepository.save(postTag); + // 또는 + // brand.getBrandTags().add(brandTag); + + } + } + } + }); + } else { + log.error("propertiesNode is null or not an object"); + } + + + // fetch page contents + + JsonNode pageContents = fetchPageContents(marketingPageId).get("results"); + + List contentList = new ArrayList<>(); + + for (JsonNode block : pageContents) { + if (block.isObject()) { + // Convert the JsonNode to ObjectNode for easy removal of keys + ObjectNode blockObject = (ObjectNode) block; + + // Remove specific keys + blockObject.remove("object"); + blockObject.remove("id"); + blockObject.remove("parent"); + blockObject.remove("created_time"); + blockObject.remove("last_edited_time"); + blockObject.remove("created_by"); + blockObject.remove("last_edited_by"); + blockObject.remove("has_children"); + blockObject.remove("archived"); + blockObject.remove("is_toggleable"); + + + contentList.add(blockObject); + } + } + + post.setContent(contentList.toString()); + postRepository.save(post); + + } + + + catch (Exception e) { + log.error("Error while fetching page metadata for pageId: {}", marketingPageId, e); // 스택트레이스도 같이 출력 + throw e; + } + + + + } + } } \ No newline at end of file diff --git a/src/main/java/com/example/reddiserver/service/PostService.java b/src/main/java/com/example/reddiserver/service/PostService.java new file mode 100644 index 0000000..1136a64 --- /dev/null +++ b/src/main/java/com/example/reddiserver/service/PostService.java @@ -0,0 +1,36 @@ +package com.example.reddiserver.service; + +import com.example.reddiserver.dto.post.response.PostContentsResponseDto; +import com.example.reddiserver.dto.post.response.PostResponseDto; +import com.example.reddiserver.entity.Post; +import com.example.reddiserver.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class PostService { + + private final PostRepository postRepository; + + public List getPostList() { + List posts = postRepository.findAll(); + List postResponseDtos = new ArrayList<>(); + + for (Post post : posts) { + postResponseDtos.add(PostResponseDto.from(post)); + } + + return postResponseDtos; + } + + public PostContentsResponseDto getPostById(Long id) { + Post post = postRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 포스트가 없습니다. id=" + id)); + return PostContentsResponseDto.from(post); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dcef047..0f3fb4a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -70,5 +70,7 @@ notion: key: ${NOTION_API_KEY} brandDB: id: ${NOTION_BRAND_DB_ID} + marketingDB: + id: ${NOTION_MARKETING_DB_ID}