From 8c7c6ec42de5946ffdcf47b5c565b78cedf8e60b Mon Sep 17 00:00:00 2001 From: kikuke Date: Thu, 31 Aug 2023 21:28:50 +0900 Subject: [PATCH 1/2] [FEAT] launch / tag --- .../easyvel/server/global/dto/PostDto.java | 8 +- .../server/launch/LaunchController.java | 19 ++ .../easyvel/server/sign/SignController.java | 11 +- .../com/easyvel/server/tag/TagController.java | 80 ++++++++ .../com/easyvel/server/tag/TagRepository.java | 10 + .../com/easyvel/server/tag/TagService.java | 190 ++++++++++++++++++ .../com/easyvel/server/tag/dto/TagList.java | 24 +++ 7 files changed, 331 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/easyvel/server/launch/LaunchController.java create mode 100644 src/main/java/com/easyvel/server/tag/TagController.java create mode 100644 src/main/java/com/easyvel/server/tag/TagRepository.java create mode 100644 src/main/java/com/easyvel/server/tag/TagService.java create mode 100644 src/main/java/com/easyvel/server/tag/dto/TagList.java diff --git a/src/main/java/com/easyvel/server/global/dto/PostDto.java b/src/main/java/com/easyvel/server/global/dto/PostDto.java index 35394e7..b2cab3a 100644 --- a/src/main/java/com/easyvel/server/global/dto/PostDto.java +++ b/src/main/java/com/easyvel/server/global/dto/PostDto.java @@ -1,16 +1,14 @@ package com.easyvel.server.global.dto; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; +import lombok.*; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; -@Data +@Getter @Setter +@Builder @NoArgsConstructor @AllArgsConstructor @ToString diff --git a/src/main/java/com/easyvel/server/launch/LaunchController.java b/src/main/java/com/easyvel/server/launch/LaunchController.java new file mode 100644 index 0000000..95adb83 --- /dev/null +++ b/src/main/java/com/easyvel/server/launch/LaunchController.java @@ -0,0 +1,19 @@ +package com.easyvel.server.launch; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/launch") +@RestController +public class LaunchController { + + @Value("${easyvel.info.version}") + private String version; + + @GetMapping("/version") + public String getVersion() { + return version; + } +} diff --git a/src/main/java/com/easyvel/server/sign/SignController.java b/src/main/java/com/easyvel/server/sign/SignController.java index dcbc33f..94adb5a 100644 --- a/src/main/java/com/easyvel/server/sign/SignController.java +++ b/src/main/java/com/easyvel/server/sign/SignController.java @@ -11,7 +11,6 @@ import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -26,7 +25,7 @@ public class SignController { private final AppleAuthService appleAuthService; @EasyvelTokenApiImplicitParams - @GetMapping(value = "/token/refresh") + @GetMapping("/token/refresh") public String refreshToken(@RequestHeader(SecurityConfiguration.TOKEN_HEADER) String token) throws SignException { String uid = jwtTokenProvider.getUid(token); @@ -38,7 +37,7 @@ public String refreshToken(@RequestHeader(SecurityConfiguration.TOKEN_HEADER) St * @return token * @throws Exception */ - @PostMapping(value = "/apple-login") + @PostMapping("/apple-login") public String appleLogin(@Validated @RequestBody GetTokensDto getTokensDto) throws Exception { LOGGER.info("[apple-login] 애플 로그인을 수행합니다."); appleAuthService.checkIdentityToken(getTokensDto.getIdentity_token()); @@ -62,7 +61,7 @@ public String appleLogin(@Validated @RequestBody GetTokensDto getTokensDto) thro return signIn(signInDto); } - @PostMapping(value = "/sign-up") + @PostMapping("/sign-up") public void signUp( @Validated @RequestBody SignUpDto signUpDto) throws SignException{ LOGGER.info("[signUp] 회원가입을 수행합니다. id : {}, pw : ****, name : {}, role : {}", signUpDto.getId(), signUpDto.getName()); @@ -76,7 +75,7 @@ public void signUp( * @return token * @throws SignException */ - @PostMapping(value = "/sign-in") + @PostMapping("/sign-in") public String signIn( @Validated @RequestBody SignInDto signInDto) throws SignException { LOGGER.info("[signIn] 로그인을 시도하고 있습니다. id : {}, pw : ****", signInDto.getId()); @@ -90,7 +89,7 @@ public String signIn( } @EasyvelTokenApiImplicitParams - @PostMapping(value = "/sign-out") + @PostMapping("/sign-out") public void signOut(@RequestHeader("X-AUTH-TOKEN") String token) throws SignException { String uid = jwtTokenProvider.getUid(token); LOGGER.info("[signIn] 회원탈퇴를 시도하고 있습니다. id : {}, pw : ****", uid); diff --git a/src/main/java/com/easyvel/server/tag/TagController.java b/src/main/java/com/easyvel/server/tag/TagController.java new file mode 100644 index 0000000..59fba59 --- /dev/null +++ b/src/main/java/com/easyvel/server/tag/TagController.java @@ -0,0 +1,80 @@ +package com.easyvel.server.tag; + +import com.easyvel.server.annotation.EasyvelTokenApiImplicitParams; +import com.easyvel.server.config.security.SecurityConfiguration; +import com.easyvel.server.global.dto.PostDto; +import com.easyvel.server.jwt.JwtTokenProvider; +import com.easyvel.server.tag.dto.TagList; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +@Slf4j +@RequestMapping("/tags") +@RequiredArgsConstructor +@RestController +public class TagController { + + private final JwtTokenProvider jwtTokenProvider; + private final TagService tagService; + + @ApiOperation("유저 태그 목록") + @EasyvelTokenApiImplicitParams + @GetMapping + public TagList getUserTagList(@RequestHeader(SecurityConfiguration.TOKEN_HEADER) String token) { + String uid = jwtTokenProvider.getUid(token); + + return tagService.getUserTagList(uid); + } + + @ApiOperation("유저 태그 추가") + @EasyvelTokenApiImplicitParams + @PostMapping + public void addUserTag(@RequestBody String tag, + @RequestHeader(SecurityConfiguration.TOKEN_HEADER) String token) { + String uid = jwtTokenProvider.getUid(token); + + tagService.addTag(uid, tag); + } + + @ApiOperation("유저 태그 제거") + @EasyvelTokenApiImplicitParams + @DeleteMapping + public void deleteUserTag(@RequestBody String tag, + @RequestHeader(SecurityConfiguration.TOKEN_HEADER) String token) { + String uid = jwtTokenProvider.getUid(token); + + tagService.deleteTag(uid, tag); + } + + //Todo: 페이징 필요 + @ApiOperation("유저 태그 목록 연관 포스트") + @EasyvelTokenApiImplicitParams + @GetMapping("/posts") + public List getUserTagPostList(@RequestHeader(SecurityConfiguration.TOKEN_HEADER) String token) throws IOException { + String uid = jwtTokenProvider.getUid(token); + + return tagService.getUserTagPostList(uid); + } + + //Todo: 페이징 필요 + @ApiOperation(value = "태그 연관 포스트", notes = "search 파라미터에 검색할 태그를 입력") + @EasyvelTokenApiImplicitParams + @GetMapping("/posts") + public List getUserTagPostList(@RequestParam String search, + @RequestHeader(SecurityConfiguration.TOKEN_HEADER) String token) throws IOException { + String uid = jwtTokenProvider.getUid(token); + + return tagService.getPostDtoListByTag(uid, search); + } + + @ApiOperation(value = "현재 인기있는 태그 목록", notes = "vol 파라미터에 받을 태그 수량을 입력") + @GetMapping("/hot") + public TagList getHotTagList(@RequestParam(defaultValue = "10") int vol) throws IOException { + return tagService.getHotTagList(vol); + } +} diff --git a/src/main/java/com/easyvel/server/tag/TagRepository.java b/src/main/java/com/easyvel/server/tag/TagRepository.java new file mode 100644 index 0000000..e0dec85 --- /dev/null +++ b/src/main/java/com/easyvel/server/tag/TagRepository.java @@ -0,0 +1,10 @@ +package com.easyvel.server.tag; + +import com.easyvel.server.sign.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface TagRepository extends JpaRepository { + Optional findByUser(User user); +} diff --git a/src/main/java/com/easyvel/server/tag/TagService.java b/src/main/java/com/easyvel/server/tag/TagService.java new file mode 100644 index 0000000..a9dd835 --- /dev/null +++ b/src/main/java/com/easyvel/server/tag/TagService.java @@ -0,0 +1,190 @@ +package com.easyvel.server.tag; + +import com.easyvel.server.global.dto.PostDto; +import com.easyvel.server.global.dto.VelogUserInfoDto; +import com.easyvel.server.sign.User; +import com.easyvel.server.sign.UserRepository; +import com.easyvel.server.subscribe.service.SubscribeService; +import com.easyvel.server.tag.dto.TagList; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class TagService { + + private final TagRepository tagRepository; + private final UserRepository userRepository; + private final SubscribeService subscribeService; + + public TagList getUserTagList(String uid) { + User user = getUserByUid(uid); + Tag userTag = getUserTag(user); + + return TagList.make(userTag); + } + + public void addTag(String uid, String tag) { + User user = getUserByUid(uid); + Tag userTag = getUserTag(user); + + if (containsTag(userTag, tag)) + throw new IllegalArgumentException("이미 추가한 태그입니다."); + + userTag.getTags().add(tag); + tagRepository.save(userTag); + } + + public void deleteTag(String uid, String tag) { + User user = getUserByUid(uid); + Tag userTag = getUserTag(user); + + if (!containsTag(userTag, tag)) + throw new IllegalArgumentException("목록에 " + tag + "가 없습니다."); + + userTag.getTags().remove(tag); + tagRepository.save(userTag); + } + + /** + * 해당 태그와 관련된 포스트를 크롤링해 PostDto를 만듭니다. + * @param uid + * @param tag + * @return + * @throws IOException + */ + public List getPostDtoListByTag(String uid, String tag) throws IOException { + List userSubscribeList = getSubscribeNameList(uid); + Elements postsElements = getTagPostsElements(tag); + + return createPostDtoList(userSubscribeList, postsElements); + } + + public List getUserTagPostList(String uid) throws IOException{ + User user = getUserByUid(uid); + Tag userTag = getUserTag(user); + + List postDtoList = new ArrayList<>(); + for (String tag : userTag.getTags()) { + List postDtoListByTag = getPostDtoListByTag(uid, tag); + postDtoList.addAll(postDtoListByTag); + } + postDtoList.sort(PostDto.compareByDate); + + return postDtoList; + } + + /** + * 현재 가장 인기있는 태그 목록을 가져옵니다. + * @param vol 가져올 개수 + * @return + */ + public TagList getHotTagList(int vol) throws IOException { + String tagUrl = "https://velog.io/tags"; + Document doc = Jsoup.connect(tagUrl).get(); + + return getTagList(vol, doc); + } + + private static TagList getTagList(int vol, Document doc) throws IOException { + List tags = new ArrayList<>(); + + Elements elementTags = doc.selectXpath("//*[@id=\"root\"]/div[2]/main/section").select("a"); + int cnt = 0; + for (Element elementTag : elementTags) { + if (cnt >= vol) + break; + + try { + tags.add(elementTag.text()); + } catch (RuntimeException e){ + throw new IOException("문서 구조가 변경되었습니다."); + } + cnt++; + } + + return new TagList(tags); + } + + private List createPostDtoList(List userSubscribeList, Elements postsElements) throws IOException { + List postDtoList = new ArrayList<>(); + for (Element postElement : postsElements) { + try { + PostDto postDto = createPostDto(postElement, userSubscribeList); + postDtoList.add(postDto); + } catch (Exception e){ + throw new IOException("문서 구조가 변경되었습니다."); + } + } + postDtoList.sort(PostDto.compareByDate); + return postDtoList; + } + + //Todo: 이거 subscribe 옮기는게 맞을지..? + private List getSubscribeNameList(String uid) throws IOException { + List userSubscribeList = new ArrayList<>(); + + List subscribers = subscribeService.getSubscribers(uid); + for (VelogUserInfoDto subscriber : subscribers) { + userSubscribeList.add(subscriber.getName()); + } + + return userSubscribeList; + } + + private Elements getTagPostsElements(String tag) throws IOException { + String tagUrl = "https://velog.io/tags/" + tag; + Document doc = Jsoup.connect(tagUrl).get(); + + return doc.select("#root > div > main > div > div").get(1).select("> div"); + } + + //Todo: 전체 적용이 가능한지 알아보기 + private PostDto createPostDto(Element post, List subscribeList) { + PostDto postDto = PostDto.builder() + .name(post.select(".user-info .username a").text()) + .title(post.select("a h2").text()) + .summary(post.select("p").text()) + .date(post.select(".subinfo span").get(0).text()) + .comment(Integer.parseInt(post.select(".subinfo span").get(1).text().replace("개의 댓글", ""))) + .like(Integer.parseInt(post.select(".subinfo span").get(2).text())) + .img(post.select("a div img").attr("src")) + .url("https://velog.io" + post.select("> a").attr("href")) + .build(); + + Elements tags = post.select(".tags-wrapper a"); + for (Element _tag : tags) { + postDto.getTag().add(_tag.text()); + } + + postDto.setSubscribed(subscribeList.contains(postDto.getName())); + + return postDto; + } + + private boolean containsTag(Tag source, String tag) { + return source.getTags().contains(tag); + } + + private Tag getUserTag(User user) { + return tagRepository.findByUser(user) + .orElseThrow(() -> new NullPointerException("user의 Tag 데이터가 NULL 입니다.")); + } + + private User getUserByUid(String uid) { + return userRepository.getByUid(uid) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 uid 입니다.")); + } +} diff --git a/src/main/java/com/easyvel/server/tag/dto/TagList.java b/src/main/java/com/easyvel/server/tag/dto/TagList.java new file mode 100644 index 0000000..f300510 --- /dev/null +++ b/src/main/java/com/easyvel/server/tag/dto/TagList.java @@ -0,0 +1,24 @@ +package com.easyvel.server.tag.dto; + +import com.easyvel.server.tag.Tag; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +public class TagList { + List tags; + + public static TagList make(Tag source) { + List tagList = source.getTags(); + List copyList = new ArrayList<>(tagList); + + return new TagList(copyList); + } +} From fd759295442ee76577718049397e4f88d63b8627 Mon Sep 17 00:00:00 2001 From: Jinsoo Kim <59440722+kikuke@users.noreply.github.com> Date: Tue, 5 Sep 2023 23:23:26 +0900 Subject: [PATCH 2/2] Update TagService.java --- src/main/java/com/easyvel/server/tag/TagService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/easyvel/server/tag/TagService.java b/src/main/java/com/easyvel/server/tag/TagService.java index a9dd835..35f459c 100644 --- a/src/main/java/com/easyvel/server/tag/TagService.java +++ b/src/main/java/com/easyvel/server/tag/TagService.java @@ -98,7 +98,7 @@ public TagList getHotTagList(int vol) throws IOException { return getTagList(vol, doc); } - private static TagList getTagList(int vol, Document doc) throws IOException { + private TagList getTagList(int vol, Document doc) throws IOException { List tags = new ArrayList<>(); Elements elementTags = doc.selectXpath("//*[@id=\"root\"]/div[2]/main/section").select("a");