Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.solidconnection.news.controller;

import com.example.solidconnection.common.resolver.AuthorizedUser;
import com.example.solidconnection.news.dto.LikedNewsResponse;
import com.example.solidconnection.news.dto.NewsCommandResponse;
import com.example.solidconnection.news.dto.NewsCreateRequest;
import com.example.solidconnection.news.dto.NewsListResponse;
Expand Down Expand Up @@ -37,9 +36,10 @@ public class NewsController {
// todo: 추후 Slice 적용
@GetMapping
public ResponseEntity<NewsListResponse> findNewsBySiteUserId(
@RequestParam(value = "site-user-id") Long siteUserId
@AuthorizedUser(required = false) Long siteUserId,
@RequestParam(value = "author-id") Long authorId
) {
NewsListResponse newsListResponse = newsQueryService.findNewsBySiteUserId(siteUserId);
NewsListResponse newsListResponse = newsQueryService.findNewsByAuthorId(siteUserId, authorId);
return ResponseEntity.ok(newsListResponse);
}

Expand Down Expand Up @@ -80,15 +80,6 @@ public ResponseEntity<NewsCommandResponse> deleteNewsById(
return ResponseEntity.ok(newsCommandResponse);
}

@GetMapping("/{news-id}/like")
public ResponseEntity<LikedNewsResponse> isNewsLiked(
@AuthorizedUser long siteUserId,
@PathVariable("news-id") Long newsId
) {
LikedNewsResponse likedNewsResponse = newsLikeService.isNewsLiked(siteUserId, newsId);
return ResponseEntity.ok(likedNewsResponse);
}

@PostMapping("/{news-id}/like")
public ResponseEntity<Void> addNewsLike(
@AuthorizedUser long siteUserId,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.example.solidconnection.news.dto;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;

import com.example.solidconnection.news.domain.News;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.ZonedDateTime;

public record NewsResponse(
Expand All @@ -9,16 +12,21 @@ public record NewsResponse(
String description,
String thumbnailUrl,
String url,

@JsonInclude(NON_NULL)
Boolean isLiked,

ZonedDateTime updatedAt
) {

public static NewsResponse from(News news) {
public static NewsResponse of(News news, Boolean isLiked) {
return new NewsResponse(
news.getId(),
news.getTitle(),
news.getDescription(),
news.getThumbnailUrl(),
news.getUrl(),
isLiked,
news.getUpdatedAt()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.example.solidconnection.news.repository;

import com.example.solidconnection.news.domain.News;
import com.example.solidconnection.news.repository.custom.NewsCustomRepository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NewsRepository extends JpaRepository<News, Long> {
public interface NewsRepository extends JpaRepository<News, Long>, NewsCustomRepository {

List<News> findAllBySiteUserIdOrderByUpdatedAtDesc(long siteUserId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.solidconnection.news.repository.custom;

import com.example.solidconnection.news.dto.NewsResponse;
import java.util.List;

public interface NewsCustomRepository {

List<NewsResponse> findNewsByAuthorIdWithLikeStatus(long authorId, Long siteUserId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example.solidconnection.news.repository.custom;

import com.example.solidconnection.news.dto.NewsResponse;
import jakarta.persistence.EntityManager;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class NewsCustomRepositoryImpl implements NewsCustomRepository {

private final EntityManager entityManager;

@Override
public List<NewsResponse> findNewsByAuthorIdWithLikeStatus(long authorId, Long siteUserId) {
String jpql = """
SELECT new com.example.solidconnection.news.dto.NewsResponse(
n.id,
n.title,
n.description,
n.thumbnailUrl,
n.url,
CASE WHEN ln.id IS NOT NULL THEN true ELSE false END,
n.updatedAt
)
FROM News n
LEFT JOIN LikedNews ln ON n.id = ln.newsId AND ln.siteUserId = :siteUserId
WHERE n.siteUserId = :authorId
ORDER BY n.updatedAt DESC
""";

return entityManager.createQuery(jpql, NewsResponse.class)
.setParameter("authorId", authorId)
.setParameter("siteUserId", siteUserId)
.getResultList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.news.domain.LikedNews;
import com.example.solidconnection.news.dto.LikedNewsResponse;
import com.example.solidconnection.news.repository.LikedNewsRepository;
import com.example.solidconnection.news.repository.NewsRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,15 +19,6 @@ public class NewsLikeService {
private final NewsRepository newsRepository;
private final LikedNewsRepository likedNewsRepository;

@Transactional(readOnly = true)
public LikedNewsResponse isNewsLiked(long siteUserId, long newsId) {
if (!newsRepository.existsById(newsId)) {
throw new CustomException(NEWS_NOT_FOUND);
}
boolean isLike = likedNewsRepository.existsByNewsIdAndSiteUserId(newsId, siteUserId);
return LikedNewsResponse.of(isLike);
}

@Transactional
public void addNewsLike(long siteUserId, long newsId) {
if (!newsRepository.existsById(newsId)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.example.solidconnection.news.service;

import com.example.solidconnection.news.domain.News;
import com.example.solidconnection.news.dto.NewsListResponse;
import com.example.solidconnection.news.dto.NewsResponse;
import com.example.solidconnection.news.repository.NewsRepository;
Expand All @@ -16,11 +15,19 @@ public class NewsQueryService {
private final NewsRepository newsRepository;

@Transactional(readOnly = true)
public NewsListResponse findNewsBySiteUserId(long siteUserId) {
List<News> newsList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(siteUserId);
List<NewsResponse> newsResponseList = newsList.stream()
.map(NewsResponse::from)
.toList();
public NewsListResponse findNewsByAuthorId(Long siteUserId, long authorId) {
// 로그인하지 않은 경우
if (siteUserId == null) {
List<NewsResponse> newsResponseList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(authorId)
.stream()
.map(news -> NewsResponse.of(news, null))
.toList();
return NewsListResponse.from(newsResponseList);
}

// 로그인한 경우
List<NewsResponse> newsResponseList = newsRepository.findNewsByAuthorIdWithLikeStatus(authorId, siteUserId);

return NewsListResponse.from(newsResponseList);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.solidconnection.news.fixture;

import com.example.solidconnection.news.domain.LikedNews;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.test.context.TestComponent;

@TestComponent
@RequiredArgsConstructor
public class LikedNewsFixture {

private final LikedNewsFixtureBuilder likedNewsFixtureBuilder;

public LikedNews 소식지_좋아요(long newsId, long siteUserId) {
return likedNewsFixtureBuilder.likedNews()
.newsId(newsId)
.siteUserId(siteUserId)
.create();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.solidconnection.news.fixture;

import com.example.solidconnection.news.domain.LikedNews;
import com.example.solidconnection.news.repository.LikedNewsRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.test.context.TestComponent;

@TestComponent
@RequiredArgsConstructor
public class LikedNewsFixtureBuilder {

private final LikedNewsRepository likedNewsRepository;

private long newsId;

private long siteUserId;

public LikedNewsFixtureBuilder likedNews() {
return new LikedNewsFixtureBuilder(likedNewsRepository);
}

public LikedNewsFixtureBuilder newsId(long newsId) {
this.newsId = newsId;
return this;
}

public LikedNewsFixtureBuilder siteUserId(long siteUserId) {
this.siteUserId = siteUserId;
return this;
}

public LikedNews create() {
LikedNews likedNews = new LikedNews(newsId, siteUserId);
return likedNewsRepository.save(likedNews);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.news.domain.News;
import com.example.solidconnection.news.dto.LikedNewsResponse;
import com.example.solidconnection.news.fixture.NewsFixture;
import com.example.solidconnection.news.repository.LikedNewsRepository;
import com.example.solidconnection.siteuser.domain.SiteUser;
Expand Down Expand Up @@ -44,31 +43,6 @@ void setUp() {
news = newsFixture.소식지(siteUserFixture.멘토(1, "mentor").getId());
}

@Nested
class 소식지_좋아요_상태를_조회한다 {

@Test
void 좋아요한_소식지의_좋아요_상태를_조회한다() {
// given
newsLikeService.addNewsLike(user.getId(), news.getId());

// when
LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId());

// then
assertThat(response.isLike()).isTrue();
}

@Test
void 좋아요하지_않은_소식지의_좋아요_상태를_조회한다() {
// when
LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId());

// then
assertThat(response.isLike()).isFalse();
}
}

@Nested
class 소식지_좋아요를_등록한다 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import com.example.solidconnection.news.domain.News;
import com.example.solidconnection.news.dto.NewsListResponse;
import com.example.solidconnection.news.dto.NewsResponse;
import com.example.solidconnection.news.fixture.LikedNewsFixture;
import com.example.solidconnection.news.fixture.NewsFixture;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.fixture.SiteUserFixture;
import com.example.solidconnection.support.TestContainerSpringBootTest;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -29,25 +32,70 @@ class NewsQueryServiceTest {
@Autowired
private NewsFixture newsFixture;

@Autowired
private LikedNewsFixture likedNewsFixture;

@Test
void 특정_사용자의_소식지_목록을_성공적으로_조회한다() {
void 로그인하지_않은_사용자가_특정_사용자의_소식지_목록을_성공적으로_조회한다() {
// given
SiteUser user1 = siteUserFixture.멘토(1, "mentor1");
SiteUser user2 = siteUserFixture.멘토(2, "mentor2");
News news1 = newsFixture.소식지(user1.getId());
News news2 = newsFixture.소식지(user1.getId());
newsFixture.소식지(user2.getId());
SiteUser author = siteUserFixture.멘토(1, "author");
SiteUser otherUser = siteUserFixture.멘토(2, "other");

News news1 = newsFixture.소식지(author.getId());
News news2 = newsFixture.소식지(author.getId());
newsFixture.소식지(otherUser.getId());
List<News> newsList = List.of(news1, news2);

// when
NewsListResponse response = newsQueryService.findNewsBySiteUserId(user1.getId());
NewsListResponse response = newsQueryService.findNewsByAuthorId(null, author.getId());

// then
assertAll(
() -> assertThat(response.newsResponseList()).hasSize(newsList.size()),
() -> assertThat(response.newsResponseList())
.extracting(NewsResponse::id)
.containsExactlyInAnyOrder(news1.getId(), news2.getId()),
() -> assertThat(response.newsResponseList())
.extracting(NewsResponse::updatedAt)
.isSortedAccordingTo(Comparator.reverseOrder()),
() -> assertThat(response.newsResponseList())
.extracting(NewsResponse::isLiked)
.containsOnly((Boolean) null)
);
}

@Test
void 로그인한_사용자가_특정_사용자의_소식지_목록을_성공적으로_조회한다() {
// given
SiteUser author = siteUserFixture.멘토(1, "author");
SiteUser loginUser = siteUserFixture.멘토(2, "loginUser");

News news1 = newsFixture.소식지(author.getId());
News news2 = newsFixture.소식지(author.getId());
News news3 = newsFixture.소식지(author.getId());

likedNewsFixture.소식지_좋아요(news1.getId(), loginUser.getId());
likedNewsFixture.소식지_좋아요(news3.getId(), loginUser.getId());

List<News> newsList = List.of(news1, news2, news3);

// when
NewsListResponse response = newsQueryService.findNewsByAuthorId(loginUser.getId(), author.getId());

// then
assertAll(
() -> assertThat(response.newsResponseList())
.extracting(NewsResponse::id)
.containsExactlyInAnyOrder(news1.getId(), news2.getId(), news3.getId()),
() -> assertThat(response.newsResponseList())
.extracting(NewsResponse::updatedAt)
.isSortedAccordingTo(Comparator.reverseOrder())
.isSortedAccordingTo(Comparator.reverseOrder()),
() -> {
Map<Long, Boolean> likeStatusMap = response.newsResponseList().stream()
.collect(Collectors.toMap(NewsResponse::id, NewsResponse::isLiked));
assertThat(likeStatusMap.get(news1.getId())).isTrue();
assertThat(likeStatusMap.get(news2.getId())).isFalse();
assertThat(likeStatusMap.get(news3.getId())).isTrue();
}
);
}
}
Loading