Skip to content

Commit

Permalink
Merge pull request #12 from SSUDevelog/feat/#11
Browse files Browse the repository at this point in the history
[FEAT] launch / tag 기능 구현
  • Loading branch information
kikuke authored Sep 5, 2023
2 parents 1f709cb + 7a57f56 commit 9773e53
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 10 deletions.
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);
}
}

0 comments on commit 9773e53

Please sign in to comment.