Skip to content

Commit

Permalink
Merge branch 'article-develop' into feature/#1538-아티클_좋아요_기능_추가
Browse files Browse the repository at this point in the history
  • Loading branch information
hong-sile authored Sep 25, 2023
2 parents 0eab097 + 92cc7eb commit fa492e5
Show file tree
Hide file tree
Showing 24 changed files with 593 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import wooteco.prolog.article.domain.Article;
import wooteco.prolog.article.domain.ArticleFilterType;
import wooteco.prolog.article.domain.repository.ArticleRepository;
import wooteco.prolog.article.ui.ArticleRequest;
import wooteco.prolog.article.ui.ArticleResponse;
Expand All @@ -17,6 +18,12 @@
import wooteco.prolog.member.application.MemberService;
import wooteco.prolog.member.domain.Member;

import java.util.List;

import static java.lang.Boolean.TRUE;
import static java.util.stream.Collectors.toList;
import static wooteco.prolog.common.exception.BadRequestCode.ARTICLE_NOT_FOUND_EXCEPTION;

@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
Expand All @@ -35,13 +42,6 @@ public Long create(final ArticleRequest articleRequest, final LoginMember loginM
return articleRepository.save(article).getId();
}

public List<ArticleResponse> getAll() {
return articleRepository.findAllByOrderByCreatedAtDesc()
.stream()
.map(ArticleResponse::from)
.collect(toList());
}

@Transactional
public void update(final Long id, final ArticleRequest articleRequest,
final LoginMember loginMember) {
Expand Down Expand Up @@ -81,4 +81,16 @@ public void likeArticle(final Long id, final LoginMember loginMember, final Bool
final Member member = memberService.findById(loginMember.getId());
article.setLike(member, isLike);
}

public List<ArticleResponse> getFilteredArticles(final LoginMember member, final ArticleFilterType course, final boolean onlyBookmarked) {
if (member.isMember() && onlyBookmarked) {
return articleRepository.findArticlesByCourseAndMember(course.getGroupName(), member.getId()).stream()
.map(article -> ArticleResponse.of(article,member.getId()))
.collect(toList());
}

return articleRepository.findArticlesByCourse(course.getGroupName()).stream()
.map(article -> ArticleResponse.of(article,member.getId()))
.collect(toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package wooteco.prolog.article.domain;

import lombok.Getter;

@Getter
public enum ArticleFilterType {
ALL(""),
ANDROID("안드로이드"),
BACKEND("백엔드"),
FRONTEND("프론트엔드");

private final String groupName;

ArticleFilterType(String groupName) {
this.groupName = groupName;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
package wooteco.prolog.article.domain.repository;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import wooteco.prolog.article.domain.Article;

import java.util.List;
import java.util.Optional;

public interface ArticleRepository extends JpaRepository<Article, Long> {

List<Article> findAllByOrderByCreatedAtDesc();

@Query("select a from Article a join fetch a.articleBookmarks where a.id = :id")


Optional<Article> findFetchBookmarkById(@Param("id") final Long id);

@Query("select a from Article a join fetch a.articleLikes where a.id = :id")
Optional<Article> findFetchLikeById(@Param("id") final Long id);
}

Optional<Article> findFetchById(@Param("id") final Long id);

@Query("SELECT DISTINCT a FROM Article a " +
"JOIN GroupMember gm ON a.member.id = gm.member.id " +
"JOIN gm.group mg " +
"WHERE mg.name LIKE %:course")
List<Article> findArticlesByCourse(@Param("course") String course);

@Query("SELECT DISTINCT a FROM Article a " +
"JOIN GroupMember gm ON a.member.id = gm.member.id " +
"JOIN gm.group mg " +
"JOIN a.articleBookmarks.articleBookmarks ab " +
"WHERE mg.name LIKE %:course AND ab.memberId = :memberId")
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package wooteco.prolog.article.ui;

import java.net.URI;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -11,11 +9,16 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import wooteco.prolog.article.application.ArticleService;
import wooteco.prolog.article.domain.ArticleFilterType;
import wooteco.prolog.login.domain.AuthMemberPrincipal;
import wooteco.prolog.login.ui.LoginMember;

import java.net.URI;
import java.util.List;

@RequiredArgsConstructor
@RestController
@RequestMapping("/articles")
Expand All @@ -30,12 +33,6 @@ public ResponseEntity<Void> createArticles(@RequestBody final ArticleRequest art
return ResponseEntity.created(URI.create("/articles/" + id)).build();
}

@GetMapping
public ResponseEntity<List<ArticleResponse>> getArticles() {
final List<ArticleResponse> allArticles = articleService.getAll();
return ResponseEntity.ok(allArticles);
}

@PutMapping("/{id}")
public ResponseEntity<Void> updateArticle(@RequestBody final ArticleRequest articleRequest,
@AuthMemberPrincipal final LoginMember member,
Expand Down Expand Up @@ -65,5 +62,13 @@ public ResponseEntity<Void> likeArticle(@PathVariable final Long id,
@RequestBody final ArticleLikesRequest request) {
articleService.likeArticle(id, member, request.getLike());
return ResponseEntity.ok().build();

@GetMapping
public ResponseEntity<List<ArticleResponse>> getFilteredArticles(@AuthMemberPrincipal final LoginMember member,
@RequestParam("course") final ArticleFilterType course,
@RequestParam("onlyBookmarked") boolean onlyBookmarked) {
final List<ArticleResponse> articleResponses = articleService.getFilteredArticles(member, course, onlyBookmarked);

return ResponseEntity.ok(articleResponses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@ public class ArticleResponse {
private final String title;
private final String url;
private final String imageUrl;
private final boolean isBookmarked;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private final LocalDateTime createdAt;

private ArticleResponse() {
this(null, null, null, null, null, null);
this(null, null, null, null, null, false, null);
}

public static ArticleResponse from(final Article article) {
public static ArticleResponse of(final Article article, final Long memberId) {
final Long id = article.getId();
final String nickName = article.getMember().getNickname();
final String title = article.getTitle().getTitle();
final String url = article.getUrl().getUrl();
final String imageUrl = article.getImageUrl().getUrl();
final boolean isBookmarked = article.getArticleBookmarks().containBookmark(memberId);
final LocalDateTime createdAt = article.getCreatedAt();
return new ArticleResponse(id, nickName, title, url, imageUrl, createdAt);
return new ArticleResponse(id, nickName, title, url, imageUrl, isBookmarked, createdAt);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package wooteco.prolog.common;

import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import wooteco.prolog.article.domain.ArticleFilterType;

import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;

@Configuration
public class WebConverterConfig implements WebMvcConfigurer {
Expand All @@ -18,5 +20,9 @@ public void addFormatters(FormatterRegistry registry) {
registry.addConverter(String.class, LocalDate.class,
source -> LocalDate.parse(source, DateTimeFormatter.BASIC_ISO_DATE)
);

registry.addConverter(String.class, ArticleFilterType.class,
source -> ArticleFilterType.valueOf(source.toUpperCase())
);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
package wooteco.prolog.article.application;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static wooteco.prolog.login.ui.LoginMember.Authority.MEMBER;
import static wooteco.prolog.member.domain.Role.CREW;

import java.util.Optional;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -20,17 +11,33 @@
import wooteco.prolog.article.domain.Article;
import wooteco.prolog.article.domain.ArticleBookmarks;
import wooteco.prolog.article.domain.ArticleLikes;
import wooteco.prolog.article.domain.ArticleFilterType;
import wooteco.prolog.article.domain.ImageUrl;
import wooteco.prolog.article.domain.Title;
import wooteco.prolog.article.domain.Url;
import wooteco.prolog.article.domain.repository.ArticleRepository;
import wooteco.prolog.article.ui.ArticleRequest;
import wooteco.prolog.article.ui.ArticleResponse;
import wooteco.prolog.common.exception.BadRequestException;
import wooteco.prolog.login.ui.LoginMember;
import wooteco.prolog.member.application.MemberService;
import wooteco.prolog.member.domain.Member;
import wooteco.prolog.member.domain.Role;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static wooteco.prolog.login.ui.LoginMember.Authority.ANONYMOUS;
import static wooteco.prolog.login.ui.LoginMember.Authority.MEMBER;
import static wooteco.prolog.member.domain.Role.CREW;

@ExtendWith(MockitoExtension.class)
class ArticleServiceTest {

Expand Down Expand Up @@ -236,6 +243,7 @@ void remove() {
}
}


@Nested
@DisplayName("아티클의 좋아요 상태를 바꿀 수 있다.")
class likeArticle {
Expand Down Expand Up @@ -285,5 +293,40 @@ void remove() {
assertThat(articleBookmarks.isAlreadyLike(member.getId()))
.isFalse();
}

@DisplayName("비로그인 사용자가 백엔드 아티클을 필터링 한다.")
@Test
void filter() {
//given
final Member member = new Member(1L, "username", "nickname", CREW, 1L, "url");
final Article article = new Article(member, new Title("title"), new Url("url"), new ImageUrl("imageUrl"));
final LoginMember unLoginMember = new LoginMember(1L, ANONYMOUS);

when(articleRepository.findArticlesByCourse(any())).thenReturn(Arrays.asList(article));

//when
final List<ArticleResponse> articleResponses = articleService.getFilteredArticles(unLoginMember, ArticleFilterType.BACKEND, false);

//then
verify(articleRepository).findArticlesByCourse(any());
Assertions.assertThat(articleResponses.get(0).getTitle()).isEqualTo(article.getTitle().getTitle());
}

@DisplayName("로그인 유저가 북마크 백엔드 아티클을 필터링 한다.")
@Test
void filter_isBookmarked() {
//given
final Member member = new Member(1L, "username", "nickname", CREW, 1L, "url");
final Article article = new Article(member, new Title("title"), new Url("url"), new ImageUrl("imageUrl"));
final LoginMember loginMember = new LoginMember(1L, MEMBER);

when(articleRepository.findArticlesByCourseAndMember(any(), any())).thenReturn(Arrays.asList(article));

//when
final List<ArticleResponse> articleResponses = articleService.getFilteredArticles(loginMember, ArticleFilterType.BACKEND, true);

//then
verify(articleRepository).findArticlesByCourseAndMember(any(), any());
Assertions.assertThat(articleResponses.get(0).getTitle()).isEqualTo(article.getTitle().getTitle());
}
}
15 changes: 14 additions & 1 deletion frontend/src/apis/articles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { client } from '.';
import { ArticleRequest, MetaOgRequest, MetaOgResponse } from '../models/Article';
import {
ArticleBookmarkPutRequest,
ArticleRequest,
MetaOgRequest,
MetaOgResponse,
} from '../models/Article';

export const requestGetArticles = () => client.get(`/articles`);

Expand All @@ -8,3 +13,11 @@ export const requestPostArticles = (body: ArticleRequest) => client.post('/artic
export const requestGetMetaOg = ({ url }: MetaOgRequest) => {
return client.get<MetaOgResponse>(`/meta-og?url=${url}`);
};

export const requestPutArticleBookmark = ({ articleId, bookmark }: ArticleBookmarkPutRequest) => {
return client.put(`/articles/${articleId}/bookmark`, { checked: bookmark });
};

export const requestGetFilteredArticle = (course: string, bookmark: boolean) => {
return client.get(`/articles?course=${course}&onlyBookmarked=${bookmark}`);
};
Loading

0 comments on commit fa492e5

Please sign in to comment.