Skip to content

Commit e29a7a3

Browse files
authored
[feat] 내활동: 내가 쓴 글 조회 기능 구현 #58 (#68)
* feat: 단일 히스토리 게시글 정보 DTO 추가 - HistoryPostItemDto: 게시글 목록 페이지에서 사용될 게시글의 핵심 정보(id, 제목, 이미지 URL, 좋아요/댓글 수)를 담는 DTO - from() 정적 팩토리 메서드: Post 엔티티를 HistoryPostItemDto로 변환하는 로직 구현 * feat: 히스토리 목록 페이지네이션을 위한 DTO 추가 - HistoryPostListDto: 히스토리 목록, 다음 페이지 유무, 다음 페이지 커서(nextCreatedAt, nextId) 정보를 담는 DTO - HistoryPostItemDto: 단일 히스토리 항목 정보를 담는 DTO * feat: 마이페이지 게시글 목록 조회 기능 추가 (커서 기반 페이지네이션) - `HistoryRepository`에 사용자 게시글 목록 조회를 위한 쿼리 메서드 2개 추가 - `findMyPostsFirstPage`: 초기 페이지 게시글을 조회 - `findMyPostsAfter`: 다음 페이지 게시글을 커서를 기반으로 조회하여 성능 개선 * feat: 마이페이지 게시글 목록 조회 서비스 로직 구현 - `HistoryService.getMyPosts()`: 사용자 ID, 커서(마지막 생성 시간, ID), 조회 개수(limit)를 인자로 받아 게시글 목록을 반환하는 메서드 추가 - `HistoryRepository`를 사용하여 커서 기반 페이지네이션을 처리 * feat: 마이페이지 게시글 목록 조회 API 구현
1 parent 2e5c6db commit e29a7a3

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.back.domain.history.controller;
2+
3+
import com.back.domain.history.dto.HistoryPostListDto;
4+
import com.back.domain.history.service.HistoryService;
5+
import com.back.global.rsData.RsData;
6+
import jakarta.validation.constraints.Max;
7+
import jakarta.validation.constraints.Min;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.format.annotation.DateTimeFormat;
10+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
11+
import org.springframework.validation.annotation.Validated;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RequestParam;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
import java.time.LocalDateTime;
18+
19+
@RestController
20+
@RequestMapping("/api/me")
21+
@RequiredArgsConstructor
22+
@Validated
23+
public class MyHistoryController {
24+
25+
private final HistoryService historyService;
26+
27+
@GetMapping("/posts")
28+
public RsData<HistoryPostListDto> getMyPosts(
29+
@AuthenticationPrincipal(expression = "id") Long userId,
30+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime lastCreatedAt,
31+
@RequestParam(required = false) Long lastId,
32+
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int limit
33+
) {
34+
HistoryPostListDto body = historyService.getMyPosts(userId, lastCreatedAt, lastId, limit);
35+
return RsData.successOf(body);
36+
}
37+
}
38+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.back.domain.history.dto;
2+
3+
import com.back.domain.post.post.entity.Post;
4+
import java.time.LocalDateTime;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
8+
@Getter
9+
@Builder
10+
public class HistoryPostItemDto {
11+
private Long id;
12+
private String title;
13+
private String imageUrl;
14+
private LocalDateTime createdAt;
15+
private Integer likeCount;
16+
private Integer commentCount;
17+
18+
public static HistoryPostItemDto from(Post p) {
19+
return HistoryPostItemDto.builder()
20+
.id(p.getId())
21+
.title(p.getTitle())
22+
.imageUrl(p.getImageUrl())
23+
.createdAt(p.getCreatedAt())
24+
.likeCount(p.getLikeCount())
25+
.commentCount(p.getCommentCount())
26+
.build();
27+
}
28+
}
29+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.back.domain.history.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.List;
8+
9+
@Getter
10+
@AllArgsConstructor
11+
public class HistoryPostListDto {
12+
private List<HistoryPostItemDto> items;
13+
private boolean hasNext;
14+
private LocalDateTime nextCreatedAt;
15+
private Long nextId;
16+
}
17+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.back.domain.history.repository;
2+
3+
import com.back.domain.post.post.entity.Post;
4+
import com.back.domain.post.post.enums.PostStatus;
5+
import jakarta.persistence.QueryHint;
6+
import org.springframework.data.domain.Pageable;
7+
import org.springframework.data.jpa.repository.JpaRepository;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.jpa.repository.QueryHints;
10+
import org.springframework.data.repository.query.Param;
11+
import org.springframework.stereotype.Repository;
12+
13+
import java.time.LocalDateTime;
14+
import java.util.List;
15+
16+
@Repository
17+
public interface HistoryRepository extends JpaRepository<Post, Long> {
18+
19+
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
20+
@Query("""
21+
select p from Post p
22+
where p.user.id = :userId
23+
and p.status <> :deleted
24+
order by p.createdAt desc, p.id desc
25+
""")
26+
List<Post> findMyPostsFirstPage(@Param("userId") Long userId,
27+
@Param("deleted") PostStatus deleted,
28+
Pageable pageable);
29+
30+
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))
31+
@Query("""
32+
select p from Post p
33+
where p.user.id = :userId
34+
and p.status <> :deleted
35+
and (p.createdAt < :lastCreatedAt or (p.createdAt = :lastCreatedAt and p.id < :lastId))
36+
order by p.createdAt desc, p.id desc
37+
""")
38+
List<Post> findMyPostsAfter(@Param("userId") Long userId,
39+
@Param("deleted") PostStatus deleted,
40+
@Param("lastCreatedAt") LocalDateTime lastCreatedAt,
41+
@Param("lastId") Long lastId,
42+
Pageable pageable);
43+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.back.domain.history.service;
2+
3+
import com.back.domain.history.dto.HistoryPostItemDto;
4+
import com.back.domain.history.dto.HistoryPostListDto;
5+
import com.back.domain.history.repository.HistoryRepository;
6+
import com.back.domain.post.post.entity.Post;
7+
import com.back.domain.post.post.enums.PostStatus;
8+
import org.springframework.data.domain.PageRequest;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.stereotype.Service;
11+
import org.springframework.transaction.annotation.Transactional;
12+
13+
import java.time.LocalDateTime;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
@Service
18+
@RequiredArgsConstructor
19+
public class HistoryService {
20+
21+
private final HistoryRepository historyRepository;
22+
23+
@Transactional(readOnly = true)
24+
public HistoryPostListDto getMyPosts(Long userId, LocalDateTime lastCreatedAt, Long lastId, int limit) {
25+
int safeLimit = Math.max(1, Math.min(limit, 100));
26+
int fetchSize = safeLimit + 1;
27+
28+
List<Post> rows;
29+
if (lastCreatedAt == null || lastId == null) {
30+
rows = historyRepository.findMyPostsFirstPage(userId, PostStatus.DELETED, PageRequest.of(0, fetchSize));
31+
} else {
32+
rows = historyRepository.findMyPostsAfter(userId, PostStatus.DELETED, lastCreatedAt, lastId, PageRequest.of(0, fetchSize));
33+
}
34+
35+
boolean hasNext = rows.size() > safeLimit;
36+
if (hasNext) rows = rows.subList(0, safeLimit);
37+
38+
List<HistoryPostItemDto> items = new ArrayList<>();
39+
for (Post p : rows) items.add(HistoryPostItemDto.from(p));
40+
41+
LocalDateTime nextCreatedAt = null;
42+
Long nextId = null;
43+
if (hasNext && !rows.isEmpty()) {
44+
Post last = rows.get(rows.size() - 1);
45+
nextCreatedAt = last.getCreatedAt();
46+
nextId = last.getId();
47+
}
48+
49+
return new HistoryPostListDto(items, hasNext, nextCreatedAt, nextId);
50+
}
51+
}

0 commit comments

Comments
 (0)