Skip to content
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
14 changes: 14 additions & 0 deletions src/docs/asciidoc/post-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,17 @@ include::{snippetsDir}/loadPostProductHashTags/1/http-response.adoc[]
include::{snippetsDir}/loadPostProductHashTags/1/response-fields.adoc[]

---


=== **9. 그루밍 라운지 - "요즘 인기있는 글" 조회**

==== Request
include::{snippetsDir}/loadUserPickPopular/1/http-request.adoc[]

==== 성공 Response
include::{snippetsDir}/loadUserPickPopular/1/http-response.adoc[]

==== Response Body Fields
include::{snippetsDir}/loadUserPickPopular/1/response-fields.adoc[]

---
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ftm.server.adapter.in.web.post.controller;

import com.ftm.server.adapter.in.web.post.dto.response.GetUserPickPopularPostsResponse;
import com.ftm.server.application.port.in.user.GetUserPickPopularPostsUseCase;
import com.ftm.server.common.response.ApiResponse;
import com.ftm.server.common.response.enums.SuccessResponseCode;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequiredArgsConstructor
public class GetUserPickPopularPostsController {

private final GetUserPickPopularPostsUseCase userPickPopularPostsUseCase;

@GetMapping("/api/posts/userpick/popular")
public ResponseEntity<ApiResponse> getUserPickPopularPosts() {
List<GetUserPickPopularPostsResponse> userPickPopularPostsResponses =
userPickPopularPostsUseCase.execute().stream()
.map(GetUserPickPopularPostsResponse::from)
.toList();

return ResponseEntity.ok(
ApiResponse.success(SuccessResponseCode.OK, userPickPopularPostsResponses));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.ftm.server.adapter.in.web.post.dto.response;

import com.ftm.server.application.vo.post.UserPickPopularPostsVo;
import java.util.List;
import lombok.Data;

@Data
public class GetUserPickPopularPostsResponse {

private final Integer ranking;
private final Long postId;
private final String title;
private final Long authorId;
private final String authorName;
private final Integer viewCount;
private final Integer likeCount;
private final Long scrapCount;
private final String imageUrl;
private final List<String> hashtags;

public static GetUserPickPopularPostsResponse from(UserPickPopularPostsVo vo) {
return new GetUserPickPopularPostsResponse(
vo.getRanking(),
vo.getPostId(),
vo.getTitle(),
vo.getAuthorId(),
vo.getAuthorName(),
vo.getViewCount(),
vo.getLikeCount(),
vo.getScrapCount(),
vo.getImageUrl(),
vo.getHashtags());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.ftm.server.adapter.out.cache;

import static com.ftm.server.common.consts.StaticConsts.USER_PICK_POPULAR_POSTS_CACHE_KEY_ALL;
import static com.ftm.server.common.consts.StaticConsts.USER_PICK_POPULAR_POSTS_CACHE_NAME;

import com.ftm.server.application.port.out.cache.LoadUserPickPopularWithCachePort;
import com.ftm.server.application.port.out.persistence.post.LoadPostPort;
import com.ftm.server.application.query.FindUserPickPopularPostsQuery;
import com.ftm.server.common.annotation.Adapter;
import com.ftm.server.domain.entity.Post;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;

@RequiredArgsConstructor
@Adapter
@Slf4j
@CacheConfig(cacheManager = "userPickPopularPostsCacheManager")
public class LoadUserPickPopularPostsWithCacheAdapter implements LoadUserPickPopularWithCachePort {

private final LoadPostPort loadPostPort;

@Override
@CachePut(
cacheNames = USER_PICK_POPULAR_POSTS_CACHE_NAME,
key = USER_PICK_POPULAR_POSTS_CACHE_KEY_ALL)
public List<Post> getUserPickPopularPostCachePut() {
return execute();
}

@Override
@Cacheable(
cacheNames = USER_PICK_POPULAR_POSTS_CACHE_NAME,
key = USER_PICK_POPULAR_POSTS_CACHE_KEY_ALL)
public List<Post> getUserPickPopularPost() {
return execute();
}

public List<Post> execute() {
// 최근 1개월 상위 4개 post id를 조회
LocalDateTime since = LocalDate.now().minusMonths(1).atStartOfDay();
List<Post> postList =
loadPostPort.loadUserPickPopularPosts(FindUserPickPopularPostsQuery.of(since, 4));

if (postList.isEmpty()) return List.of();

return postList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import com.ftm.server.adapter.out.persistence.repository.*;
import com.ftm.server.application.port.out.persistence.post.*;
import com.ftm.server.application.query.*;
import com.ftm.server.application.vo.post.PostAndBookmarkCountVo;
import com.ftm.server.application.vo.post.PostWithBookmarkCountVo;
import com.ftm.server.application.vo.post.UserIdAndNameVo;
import com.ftm.server.application.vo.post.UserWithPostCountVo;
import com.ftm.server.common.annotation.Adapter;
import com.ftm.server.common.exception.CustomException;
Expand Down Expand Up @@ -36,7 +38,8 @@ public class PostDomainPersistenceAdapter
DeletePostImagePort,
DeletePostProductPort,
DeletePostProductImagePort,
LoadPostWithBookmarkCountPort {
LoadPostWithBookmarkCountPort,
LoadUserForPostDomainPort {

private final PostRepository postRepository;
private final PostImageRepository postImageRepository;
Expand Down Expand Up @@ -148,6 +151,15 @@ public List<Post> loadPostsByDeleteOption(FindPostByDeleteOptionQuery query) {
.toList();
}

@Override
public List<Post> loadUserPickPopularPosts(FindUserPickPopularPostsQuery query) {
return postRepository
.findTopNPostsByViewCountAndLikeCount(query.getSince(), query.getLimit())
.stream()
.map(postMapper::toDomainEntity)
.toList();
}

@Override
public List<PostImage> loadPostImagesByPostId(FindByPostIdQuery query) {
PostJpaEntity postJpaEntity =
Expand Down Expand Up @@ -301,6 +313,12 @@ public List<UserWithPostCountVo> loadAllPostsWithUserAndBookmarkCount(
return postRepository.findAllPostsWithUserAndBookmarkCount(query);
}

@Override
public List<PostAndBookmarkCountVo> getPostAndBookmarkCount(
FindBookmarkCountByPostIdsQuery query) {
return postRepository.findBookmarkCountsByPostIds(query.getPostIds());
}

@Override
public List<PostImage> loadRepresentativeImagesByPostIds(FindByIdsQuery query) {
return postImageRepository.findRepresentativeImagesByPostIdIn(query).stream()
Expand All @@ -314,4 +332,9 @@ public List<Post> loadPostListByUsers(FindByUserIdsQuery query) {
.map(postMapper::toDomainEntity)
.toList();
}

@Override
public List<UserIdAndNameVo> loadPostAndAuthorName(FindByIdsQuery query) {
return userRepository.findUserNameByUserIds(query.getIds());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ftm.server.adapter.out.persistence.repository;

import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
import com.ftm.server.application.vo.post.PostAndBookmarkCountVo;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
Expand All @@ -22,4 +24,30 @@ public interface PostRepository

@Query("SELECT p FROM PostJpaEntity p WHERE p.user.id IN (:userIds)")
List<PostJpaEntity> findAllByUserIdIn(@Param("userIds") List<Long> userIds);

@Query(
value =
"""
SELECT *
FROM post p
WHERE p.created_at >= :since
ORDER BY
(p.like_count / NULLIF(MAX(p.like_count) OVER (), 0.0)
+ p.view_count / NULLIF(MAX(p.view_count) OVER (), 0.0)) DESC ,
p.id DESC
LIMIT :limit
""",
nativeQuery = true)
List<PostJpaEntity> findTopNPostsByViewCountAndLikeCount(
@Param("since") LocalDateTime since, @Param("limit") int limit);

@Query(
"""
SELECT new com.ftm.server.application.vo.post.PostAndBookmarkCountVo(p.id, COUNT(b))
FROM PostJpaEntity p
LEFT JOIN BookmarkJpaEntity b ON b.post = p
WHERE p.id IN :postIds
GROUP BY p.id
""")
List<PostAndBookmarkCountVo> findBookmarkCountsByPostIds(@Param("postIds") List<Long> postIds);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ftm.server.adapter.out.persistence.repository;

import com.ftm.server.adapter.out.persistence.model.UserJpaEntity;
import com.ftm.server.application.vo.post.UserIdAndNameVo;
import com.ftm.server.domain.enums.SocialProvider;
import com.ftm.server.domain.enums.UserRole;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -37,4 +38,8 @@ List<UserJpaEntity> findAllByDeletedBefore(
@Param("isDeleted") Boolean isDeleted, @Param("end") LocalDateTime end);

Boolean existsByEmailAndIsDeleted(String email, boolean b);

@Query(
"SELECT new com.ftm.server.application.vo.post.UserIdAndNameVo(u.id, u.nickname) from UserJpaEntity u where u.id in :userIds")
List<UserIdAndNameVo> findUserNameByUserIds(@Param("userIds") List<Long> userIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.ftm.server.adapter.out.scheduler;

import com.ftm.server.application.port.out.cache.LoadUserPickPopularWithCachePort;
import com.ftm.server.common.annotation.Adapter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;

@Adapter
@Slf4j
@RequiredArgsConstructor
public class GetUserPickPopularPostsScheduler {

private final LoadUserPickPopularWithCachePort loadUserPickPopularWithCachePort;

// 마지막 실행으로부터 55분 뒤에 재실행됨.
// cache TTL 값인 1시간이 끝나기 이전에 캐시를 업데이트 해 놓는다.
@Scheduled(fixedRateString = "PT55M", initialDelayString = "PT1M")
public void run() {
log.info(
"Loading UserPick Popular Posts at {}",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
loadUserPickPopularWithCachePort.getUserPickPopularPostCachePut();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ftm.server.application.port.in.user;

import com.ftm.server.application.vo.post.UserPickPopularPostsVo;
import com.ftm.server.common.annotation.UseCase;
import java.util.List;

@UseCase
public interface GetUserPickPopularPostsUseCase {
List<UserPickPopularPostsVo> execute();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ftm.server.application.port.out.cache;

import com.ftm.server.common.annotation.Port;
import com.ftm.server.domain.entity.Post;
import java.util.List;

@Port
public interface LoadUserPickPopularWithCachePort {

List<Post> getUserPickPopularPost();

List<Post> getUserPickPopularPostCachePut();
}
Loading
Loading