Skip to content

Commit b607fce

Browse files
committed
feat: 내가 작성한 유저픽 게시글 목록 조회 api 추가(#115)
1 parent a3de76d commit b607fce

File tree

18 files changed

+556
-2
lines changed

18 files changed

+556
-2
lines changed

src/docs/asciidoc/user-api.adoc

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ include::{snippetsDir}/userExit/1/http-response.adoc[]
177177
==== Response Body Fields
178178
include::{snippetsDir}/userExit/1/response-fields.adoc[]
179179

180+
180181
=== **10. 북마크 생성 api**
181182

182183
게시글 북마크를 생성
@@ -199,4 +200,36 @@ include::{snippetsDir}/createBookmark/1/response-fields.adoc[]
199200

200201
==== 실패 Response
201202
실패1.
202-
include::{snippetsDir}/createBookmark/3/http-response.adoc[]
203+
include::{snippetsDir}/createBookmark/3/http-response.adoc[]
204+
205+
206+
=== **11. 내가 작성한 유저픽 게시글 목록 조회**
207+
208+
마이페이지 > 작성한 게시글 목록 > 더보기 버튼 클릭 시 내가 작성한 유저픽 게시글 목록을 조회하는 api 입니다. +
209+
무한 스크롤, 더보기 형식으로 조회하여 모든 데이터 개수와 번호를 부여하는 Page 방식이 아닌, +
210+
다음 페이지의 데이터가 존재하는지의 여부를 함께 응답하는 Slice 방식을 사용했습니다.
211+
212+
==== Request
213+
include::{snippetsDir}/loadMyPosts/1/http-request.adoc[]
214+
215+
==== Request Query Parameter Fields
216+
include::{snippetsDir}/loadMyPosts/1/query-parameters.adoc[]
217+
218+
==== 성공 Response
219+
include::{snippetsDir}/loadMyPosts/1/http-response.adoc[]
220+
221+
==== Response Body Fields
222+
include::{snippetsDir}/loadMyPosts/1/response-fields.adoc[]
223+
224+
==== 실패 Response
225+
실패 1. 인증되지 않은 유저인 경우
226+
227+
include::{snippetsDir}/loadMyPosts/2/http-response.adoc[]
228+
229+
실패 2. 요청 페이지 번호 유효성 검증에 실패할 경우
230+
231+
include::{snippetsDir}/loadMyPosts/3/http-response.adoc[]
232+
233+
실패 3. 요청 페이지당 개수 유효성 검증에 실패할 경우
234+
235+
include::{snippetsDir}/loadMyPosts/4/http-response.adoc[]
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.ftm.server.adapter.in.web.user.controller;
2+
3+
import com.ftm.server.adapter.in.web.user.dto.response.LoadMyPostsResponse;
4+
import com.ftm.server.application.port.in.user.LoadMyPostsUseCase;
5+
import com.ftm.server.application.query.FindPostsByPagingQuery;
6+
import com.ftm.server.application.vo.post.PostPagingVo;
7+
import com.ftm.server.common.exception.CustomException;
8+
import com.ftm.server.common.response.ApiResponse;
9+
import com.ftm.server.common.response.enums.ErrorResponseCode;
10+
import com.ftm.server.common.response.enums.SuccessResponseCode;
11+
import com.ftm.server.infrastructure.security.UserPrincipal;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.http.HttpStatus;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
16+
import org.springframework.web.bind.annotation.GetMapping;
17+
import org.springframework.web.bind.annotation.RequestParam;
18+
import org.springframework.web.bind.annotation.RestController;
19+
20+
@RestController
21+
@RequiredArgsConstructor
22+
public class LoadMyPostsController {
23+
24+
private final LoadMyPostsUseCase loadMyPostsUseCase;
25+
26+
@GetMapping("/api/users/me/posts")
27+
public ResponseEntity<ApiResponse<LoadMyPostsResponse>> loadMyPosts(
28+
@AuthenticationPrincipal UserPrincipal userPrincipal,
29+
@RequestParam(value = "page", defaultValue = "0") int page,
30+
@RequestParam(value = "size", defaultValue = "5") int size) {
31+
// 요청 페이징 데이터 유효성 검증
32+
if (page < 0) throw new CustomException(ErrorResponseCode.BAD_REQUEST_PAGING_INDEX_RANGE);
33+
if (size < 1 || size > 10)
34+
throw new CustomException(ErrorResponseCode.BAD_REQUEST_PAGING_SIZE_RANGE);
35+
36+
PostPagingVo vo =
37+
loadMyPostsUseCase.execute(
38+
FindPostsByPagingQuery.of(userPrincipal.getId(), page, size));
39+
40+
return ResponseEntity.status(HttpStatus.OK)
41+
.body(ApiResponse.success(SuccessResponseCode.OK, LoadMyPostsResponse.from(vo)));
42+
}
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.ftm.server.adapter.in.web.user.dto.response;
2+
3+
import com.ftm.server.application.vo.post.PostPagingVo;
4+
import com.ftm.server.application.vo.post.PostSummaryVo;
5+
import java.util.List;
6+
import lombok.Getter;
7+
8+
@Getter
9+
public class LoadMyPostsResponse {
10+
11+
private final List<PostSummaryVo> items;
12+
private final Boolean hasNext;
13+
14+
private LoadMyPostsResponse(PostPagingVo postPagingVo) {
15+
this.items = postPagingVo.getItems();
16+
this.hasNext = postPagingVo.getHasNext();
17+
}
18+
19+
public static LoadMyPostsResponse from(PostPagingVo postPagingVo) {
20+
return new LoadMyPostsResponse(postPagingVo);
21+
}
22+
}

src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import lombok.RequiredArgsConstructor;
1717
import lombok.extern.slf4j.Slf4j;
1818
import org.springframework.dao.DataIntegrityViolationException;
19+
import org.springframework.data.domain.Slice;
1920

2021
@Adapter
2122
@RequiredArgsConstructor
@@ -32,6 +33,7 @@ public class UserDomainPersistenceAdapter
3233
UpdateUserPort,
3334
UpdateUserImagePort,
3435
LoadPostUserDomainPort,
36+
LoadPostImageUserDomainPort,
3537
UpdatePostUserDomainPort,
3638
DeleteUserImagePort,
3739
DeleteGroomingTestResultPort,
@@ -47,6 +49,7 @@ public class UserDomainPersistenceAdapter
4749
private final GroomingLevelRepository groomingLevelRepository;
4850
private final UserImageRepository userImageRepository;
4951
private final PostRepository postRepository;
52+
private final PostImageRepository postImageRepository;
5053
private final BookmarkRepository bookmarkRepository;
5154
private final GroomingTestResultRepository groomingTestResultRepository;
5255

@@ -56,6 +59,7 @@ public class UserDomainPersistenceAdapter
5659
private final UserImageMapper userImageMapper;
5760
private final PostMapper postMapper;
5861
private final BookmarkMapper bookmarkMapper;
62+
private final PostImageMapper postImageMapper;
5963

6064
@Override
6165
public Optional<EmailVerificationLogs> loadEmailVerificationLogByEmail(FindByEmailQuery query) {
@@ -193,6 +197,18 @@ public List<Post> loadPostListByUser(FindByUserIdQuery query) {
193197
.toList();
194198
}
195199

200+
@Override
201+
public Slice<Post> loadPostsByUserIdWithPaging(FindPostsByPagingQuery query) {
202+
return postRepository.findAllByUserIdWithPaging(query).map(postMapper::toDomainEntity);
203+
}
204+
205+
@Override
206+
public List<PostImage> loadRepresentativeImagesByPostIds(FindByIdsQuery query) {
207+
return postImageRepository.findRepresentativeImagesByPostIdIn(query).stream()
208+
.map(postImageMapper::toDomainEntity)
209+
.toList();
210+
}
211+
196212
@Override
197213
public void updatePostListBySystemUser(List<Post> postList) {
198214
UserJpaEntity systemUser = userRepository.findById(postList.get(0).getUserId()).get();

src/main/java/com/ftm/server/adapter/out/persistence/repository/PostCustomRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
44
import com.ftm.server.application.query.FindPostByDeleteOptionQuery;
5+
import com.ftm.server.application.query.FindPostsByPagingQuery;
56
import java.util.List;
7+
import org.springframework.data.domain.Slice;
68

79
public interface PostCustomRepository {
810

911
List<PostJpaEntity> findAllByDeletedBefore(FindPostByDeleteOptionQuery query);
12+
13+
Slice<PostJpaEntity> findAllByUserIdWithPaging(FindPostsByPagingQuery query);
1014
}

src/main/java/com/ftm/server/adapter/out/persistence/repository/PostCustomRepositoryImpl.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44

55
import com.ftm.server.adapter.out.persistence.model.PostJpaEntity;
66
import com.ftm.server.application.query.FindPostByDeleteOptionQuery;
7+
import com.ftm.server.application.query.FindPostsByPagingQuery;
78
import com.querydsl.jpa.impl.JPAQueryFactory;
89
import java.time.LocalTime;
910
import java.util.List;
1011
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.domain.Slice;
14+
import org.springframework.data.domain.SliceImpl;
1115
import org.springframework.stereotype.Repository;
1216

1317
@Repository
@@ -25,4 +29,26 @@ public List<PostJpaEntity> findAllByDeletedBefore(FindPostByDeleteOptionQuery qu
2529
postJpaEntity.deletedAt.loe(query.getDeletedAt().atTime(LocalTime.MAX)))
2630
.fetch();
2731
}
32+
33+
@Override
34+
public Slice<PostJpaEntity> findAllByUserIdWithPaging(FindPostsByPagingQuery query) {
35+
Pageable pageable = query.getPageable();
36+
List<PostJpaEntity> content =
37+
queryFactory
38+
.selectFrom(postJpaEntity)
39+
.where(postJpaEntity.user.id.eq(query.getUserId()))
40+
.offset(pageable.getOffset())
41+
.limit(pageable.getPageSize() + 1) // 한 개 더 가져와서 hasNext 판별
42+
.orderBy(postJpaEntity.createdAt.desc())
43+
.fetch();
44+
45+
List<PostJpaEntity> result = content;
46+
47+
boolean hasNext = content.size() > pageable.getPageSize();
48+
if (hasNext) {
49+
result = content.subList(0, pageable.getPageSize()); // 초과분 제거
50+
}
51+
52+
return new SliceImpl<>(result, pageable, hasNext);
53+
}
2854
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.ftm.server.adapter.out.persistence.repository;
2+
3+
import com.ftm.server.adapter.out.persistence.model.PostImageJpaEntity;
4+
import com.ftm.server.application.query.FindByIdsQuery;
5+
import java.util.List;
6+
7+
public interface PostImageCustomRepository {
8+
9+
// 여러개의 게시글 이미지 중 대표 이미지 한 개 조회 (썸네일용 이미지, 업로드가 가장 먼저된 이미지 조회)
10+
List<PostImageJpaEntity> findRepresentativeImagesByPostIdIn(FindByIdsQuery query);
11+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.ftm.server.adapter.out.persistence.repository;
2+
3+
import static com.ftm.server.adapter.out.persistence.model.QPostImageJpaEntity.postImageJpaEntity;
4+
5+
import com.ftm.server.adapter.out.persistence.model.PostImageJpaEntity;
6+
import com.ftm.server.application.query.FindByIdsQuery;
7+
import com.querydsl.jpa.JPAExpressions;
8+
import com.querydsl.jpa.impl.JPAQueryFactory;
9+
import java.util.List;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Repository;
12+
13+
@Repository
14+
@RequiredArgsConstructor
15+
public class PostImageCustomRepositoryImpl implements PostImageCustomRepository {
16+
17+
private final JPAQueryFactory queryFactory;
18+
19+
@Override
20+
public List<PostImageJpaEntity> findRepresentativeImagesByPostIdIn(FindByIdsQuery query) {
21+
return queryFactory
22+
.selectFrom(postImageJpaEntity)
23+
.where(
24+
postImageJpaEntity.id.in(
25+
JPAExpressions.select(postImageJpaEntity.id.min())
26+
.from(postImageJpaEntity)
27+
.where(postImageJpaEntity.post.id.in(query.getIds()))
28+
.groupBy(postImageJpaEntity.post.id)))
29+
.fetch();
30+
}
31+
}

src/main/java/com/ftm/server/adapter/out/persistence/repository/PostImageRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import org.springframework.data.jpa.repository.Modifying;
99
import org.springframework.data.jpa.repository.Query;
1010

11-
public interface PostImageRepository extends JpaRepository<PostImageJpaEntity, Long> {
11+
public interface PostImageRepository
12+
extends JpaRepository<PostImageJpaEntity, Long>, PostImageCustomRepository {
1213

1314
List<PostImageJpaEntity> findAllByPost(PostJpaEntity post);
1415

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.ftm.server.application.port.in.user;
2+
3+
import com.ftm.server.application.query.FindPostsByPagingQuery;
4+
import com.ftm.server.application.vo.post.PostPagingVo;
5+
import com.ftm.server.common.annotation.UseCase;
6+
7+
@UseCase
8+
public interface LoadMyPostsUseCase {
9+
10+
PostPagingVo execute(FindPostsByPagingQuery query);
11+
}

0 commit comments

Comments
 (0)