Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] launch / tag 기능 구현 #12

Merged
merged 3 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/main/java/com/easyvel/server/global/dto/PostDto.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/easyvel/server/launch/LaunchController.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
9 changes: 4 additions & 5 deletions src/main/java/com/easyvel/server/sign/SignController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand All @@ -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);

Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand Down
80 changes: 80 additions & 0 deletions src/main/java/com/easyvel/server/tag/TagController.java
Original file line number Diff line number Diff line change
@@ -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<PostDto> 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<PostDto> 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);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/easyvel/server/tag/TagRepository.java
Original file line number Diff line number Diff line change
@@ -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<Tag, Long> {
Optional<Tag> findByUser(User user);
}
190 changes: 190 additions & 0 deletions src/main/java/com/easyvel/server/tag/TagService.java
Original file line number Diff line number Diff line change
@@ -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<PostDto> getPostDtoListByTag(String uid, String tag) throws IOException {
List<String> userSubscribeList = getSubscribeNameList(uid);
Elements postsElements = getTagPostsElements(tag);

return createPostDtoList(userSubscribeList, postsElements);
}

public List<PostDto> getUserTagPostList(String uid) throws IOException{
User user = getUserByUid(uid);
Tag userTag = getUserTag(user);

List<PostDto> postDtoList = new ArrayList<>();
for (String tag : userTag.getTags()) {
List<PostDto> 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 TagList getTagList(int vol, Document doc) throws IOException {
List<String> 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<PostDto> createPostDtoList(List<String> userSubscribeList, Elements postsElements) throws IOException {
List<PostDto> 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<String> getSubscribeNameList(String uid) throws IOException {
List<String> userSubscribeList = new ArrayList<>();

List<VelogUserInfoDto> 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<String> 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 입니다."));
}
}
24 changes: 24 additions & 0 deletions src/main/java/com/easyvel/server/tag/dto/TagList.java
Original file line number Diff line number Diff line change
@@ -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<String> tags;

public static TagList make(Tag source) {
List<String> tagList = source.getTags();
List<String> copyList = new ArrayList<>(tagList);

return new TagList(copyList);
}
}