Skip to content

Commit

Permalink
Merge pull request #144 from tukcomCD2024/feat/#136-category-statistics
Browse files Browse the repository at this point in the history
Feat/#136 통계 관련 기능 구현
  • Loading branch information
yeonjy authored Jun 17, 2024
2 parents 826ced8 + cfd4c6b commit f29d013
Show file tree
Hide file tree
Showing 19 changed files with 459 additions and 13 deletions.
10 changes: 10 additions & 0 deletions backend/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ repositories {
mavenCentral()
}

ext {
queryDslVersion = "5.0.0"
}

dependencies {
if (isAppleSilicon()) {
runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.94.Final:osx-aarch_64")
Expand All @@ -41,6 +45,12 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.fasterxml.jackson.core:jackson-core:2.17.0'

implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.rollthedice.backend.domain.news.dto.NewsUrlDto;
import com.rollthedice.backend.domain.news.entity.News;
import com.rollthedice.backend.domain.news.service.NewsCategory;
import com.rollthedice.backend.domain.news.entity.NewsCategory;
import com.rollthedice.backend.domain.news.service.NewsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -11,7 +11,6 @@
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -32,7 +31,7 @@ public class NewsCrawlingService {
private final NewsService newsService;

@Transactional
// @Scheduled(cron = CRON, zone = ZONE)
// @Scheduled(cron = CRON, zone = ZONE)
public void scrap() throws IOException {
for (NewsCategory category : NewsCategory.values()) {
String categoryUrl = MAIN_URL + category.getNum();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.rollthedice.backend.domain.news.service;
package com.rollthedice.backend.domain.news.entity;

import lombok.Getter;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import com.rollthedice.backend.domain.member.entity.Member;
import com.rollthedice.backend.domain.news.entity.ReadNews;
import com.rollthedice.backend.domain.statistics.repository.ReadNewsCustomRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ReadNewsRepository extends JpaRepository<ReadNews, Long> {
public interface ReadNewsRepository extends JpaRepository<ReadNews, Long>, ReadNewsCustomRepository {
List<ReadNews> getTop3ByMemberOrderByCreatedAtDesc(Member member);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.rollthedice.backend.domain.statistics.api;

import com.rollthedice.backend.domain.statistics.dto.response.CategoryStatisticsResponse;
import com.rollthedice.backend.domain.statistics.dto.response.DateViewStatisticsResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;

import java.util.List;

public interface StatisticsApi {
@Operation(
summary = "최근 일주일 날짜별 뉴스 조회수 조회",
description = "최근 일주일간 날짜별로 뉴스 조회수를 조회합니다.",
security = {@SecurityRequirement(name = "access_token")},
tags = {"Statistics"}
)
@ApiResponse(
responseCode = "200",
description = "요청에 성공하였습니다."
)
List<DateViewStatisticsResponse> getViewsOfDates();

@Operation(
summary = "카테고리별 조회수 조회",
description = "카테고리별 조회수를 조회합니다.",
security = {@SecurityRequirement(name = "access_token")},
tags = {"Statistics"}
)
@ApiResponse(
responseCode = "200",
description = "요청에 성공하였습니다."
)
List<CategoryStatisticsResponse> getCategoryStatistics();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.rollthedice.backend.domain.statistics.api;

import com.rollthedice.backend.domain.statistics.dto.response.CategoryStatisticsResponse;
import com.rollthedice.backend.domain.statistics.dto.response.DateViewStatisticsResponse;
import com.rollthedice.backend.domain.statistics.service.StatisticsService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("statistics")
public class StatisticsController implements StatisticsApi {
private final StatisticsService statisticsService;

@ResponseStatus(HttpStatus.OK)
@GetMapping("/per-dates")
@Override
public List<DateViewStatisticsResponse> getViewsOfDates() {
return statisticsService.getViewsOfDates();
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/categories")
@Override
public List<CategoryStatisticsResponse> getCategoryStatistics() {
return statisticsService.getCategoryStatistics();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.rollthedice.backend.domain.statistics.dto.response;

import lombok.*;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CategoryStatisticsResponse {
private Long views;
private String category;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.rollthedice.backend.domain.statistics.dto.response;

import lombok.*;

import java.time.LocalDate;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DateViewStatisticsResponse {
private Long views;
private LocalDate dateTime;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.rollthedice.backend.domain.statistics.repository;

import com.rollthedice.backend.domain.member.entity.Member;

import java.time.LocalDate;

public interface ReadNewsCustomRepository {
Long getCountOfReadNewsByDate(Member member, LocalDate date);

Long getCountOfReadNewsByCategory(Member member, String category);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.rollthedice.backend.domain.statistics.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.rollthedice.backend.domain.member.entity.Member;
import lombok.RequiredArgsConstructor;

import java.time.LocalDate;

import static com.rollthedice.backend.domain.news.entity.QNews.news;
import static com.rollthedice.backend.domain.news.entity.QReadNews.readNews;

@RequiredArgsConstructor
public class ReadNewsCustomRepositoryImpl implements ReadNewsCustomRepository {
private final JPAQueryFactory queryFactory;

@Override
public Long getCountOfReadNewsByDate(Member member, LocalDate date) {
return queryFactory
.select(readNews.count())
.from(readNews)
.where(readNews.member.eq(member)
.and(readNews.createdAt.between(
date.atStartOfDay(),
date.plusDays(1).atStartOfDay().minusNanos(1))))
.fetchOne();
}

@Override
public Long getCountOfReadNewsByCategory(Member member, String category) {
return queryFactory
.select(readNews.count())
.from(readNews)
.join(readNews.news, news)
.where(readNews.member.eq(member)
.and(news.category.eq(category))
).fetchOne();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.rollthedice.backend.domain.statistics.service;

import com.rollthedice.backend.domain.member.entity.Member;
import com.rollthedice.backend.domain.news.entity.NewsCategory;
import com.rollthedice.backend.domain.news.repository.ReadNewsRepository;
import com.rollthedice.backend.domain.statistics.dto.response.CategoryStatisticsResponse;
import com.rollthedice.backend.domain.statistics.dto.response.DateViewStatisticsResponse;
import com.rollthedice.backend.global.oauth2.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;

@Service
@RequiredArgsConstructor
public class StatisticsService {
private final ReadNewsRepository readNewsRepository;
private final AuthService authService;

public List<DateViewStatisticsResponse> getViewsOfDates() {
Member member = authService.getMember();
List<DateViewStatisticsResponse> responses = new ArrayList<>();
IntStream.range(0, 7)
.forEach(day -> responses.add(DateViewStatisticsResponse.builder()
.views(readNewsRepository.getCountOfReadNewsByDate(member, LocalDate.now().minusDays(day)))
.dateTime(LocalDate.now().minusDays(day)).build()));
return responses;
}

public List<CategoryStatisticsResponse> getCategoryStatistics() {
Member member = authService.getMember();
List<CategoryStatisticsResponse> responses = new ArrayList<>();
for (NewsCategory category : NewsCategory.values()) {
long views = readNewsRepository.getCountOfReadNewsByCategory(member, category.getName());
CategoryStatisticsResponse response = CategoryStatisticsResponse.builder()
.category(category.getName())
.views(views).build();
responses.add(response);
}
return responses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.rollthedice.backend.global.config;


import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class QuerydslConfig {

private final EntityManager em;

@Autowired
public QuerydslConfig(EntityManager em) {
this.em = em;
}

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.rollthedice.backend.domain.news.controller;
package com.rollthedice.backend.domain.news.api;

import com.rollthedice.backend.domain.news.api.NewsController;
import com.rollthedice.backend.domain.news.exception.NewsNotFoundException;
import com.rollthedice.backend.domain.news.repository.NewsRepository;
import com.rollthedice.backend.domain.news.service.NewsService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.rollthedice.backend.domain.news.repository;

import com.rollthedice.backend.domain.member.entity.Member;
import com.rollthedice.backend.domain.member.repository.MemberRepository;
import com.rollthedice.backend.domain.news.entity.News;
import com.rollthedice.backend.domain.news.entity.NewsCategory;
import com.rollthedice.backend.domain.news.entity.ReadNews;
import com.rollthedice.backend.support.RepositoryTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDate;

import static com.rollthedice.backend.domain.member.MemberFixture.MEMBER;
import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("ReadNewsRepository의 ")
@RepositoryTest
public class ReadNewsRepositoryTest {
@Autowired
private ReadNewsRepository readNewsRepository;
@Autowired
private NewsRepository newsRepository;
@Autowired
private MemberRepository memberRepository;

private Member member;

@BeforeEach
void setUp() {
member = memberRepository.save(MEMBER());
}

@Test
@DisplayName("하루동안 조회한 뉴스 개수를 반환할 수 있는가")
void getCountOfReadNewsByDate() {
//given
int expect = 5;
for (int i = 0; i < expect; i++) {
readNewsRepository.save(ReadNews.builder().member(member).news(newsRepository.save(News.builder().id((long) i).build())).build());
}

//when
Long result = readNewsRepository.getCountOfReadNewsByDate(member, LocalDate.now());

//then
assertThat(result).isEqualTo(expect);
}

@Test
@DisplayName("회원이 카테고리별로 조회한 뉴스 개수를 반환할 수 있는가")
void getCountOfReadNewsByCategory() {
//given
int expect = 5;
String category = NewsCategory.SCIENCE.getName();
for (int i = 0; i < expect; i++) {
readNewsRepository.save(ReadNews.builder().member(member)
.news(newsRepository.save(
News.builder().id((long) i).category(category).build())).build());
}

//when
Long result = readNewsRepository.getCountOfReadNewsByCategory(member, category);

//then
assertThat(result).isEqualTo(expect);
}
}
Loading

0 comments on commit f29d013

Please sign in to comment.