Skip to content

Commit 779f141

Browse files
authored
Refactor/336 팔로우하는 작가 조회, 메인 현황 총 팔로우 집계 (#352)
* refactor/336 입금 환전 내역 수정 * refactor/336 입금 환전 내역 수정 * refactor/336 작가 수익 테스트 수정 * refactor/336 대시보드 메인현황 팔로우수 추가 * refactor/336 대시보드 메인현황 팔로우수 테스트 케이스 작성 * refactor/336 대시보드 팔로우 작가 조회 * refactor/336 대시보드 팔로우 작가 조회
1 parent c7b7716 commit 779f141

File tree

11 files changed

+1204
-328
lines changed

11 files changed

+1204
-328
lines changed

src/main/java/com/back/domain/dashboard/artist/service/ArtistDashboardServiceImpl.java

Lines changed: 388 additions & 110 deletions
Large diffs are not rendered by default.

src/main/java/com/back/domain/dashboard/customer/dto/request/FollowingSearchRequest.java

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import jakarta.validation.constraints.Max;
44
import jakarta.validation.constraints.Min;
5-
import jakarta.validation.constraints.Pattern;
65

76
/**
87
* 팔로우한 작가 목록 검색 요청 DTO
98
* 2025.09.25 생성
9+
* 2025.10.14 수정 - 팔로우 기능 실제 db로 연동
1010
*/
1111
public record FollowingSearchRequest(
1212
/** 페이지 번호 (0부터 시작) */
@@ -16,34 +16,13 @@ public record FollowingSearchRequest(
1616
/** 페이지 크기 (1-100) */
1717
@Min(value = 1, message = "페이지 크기는 1 이상이어야 합니다")
1818
@Max(value = 100, message = "페이지 크기는 100 이하여야 합니다")
19-
Integer size,
20-
21-
/** 검색 키워드 (작가ID/작가명) */
22-
String keyword,
23-
24-
/** 관계 상태 (FOLLOWING 고정) */
25-
@Pattern(regexp = "^FOLLOWING$",
26-
message = "status는 FOLLOWING이어야 합니다")
27-
String status,
28-
29-
/** 정렬 기준 */
30-
@Pattern(regexp = "^(followedAt|artistName|followerCount|lastPublishedAt)$",
31-
message = "sort는 followedAt, artistName, followerCount, lastPublishedAt 중 하나여야 합니다")
32-
String sort,
33-
34-
/** 정렬 방향 */
35-
@Pattern(regexp = "^(ASC|DESC)$",
36-
message = "order는 ASC 또는 DESC여야 합니다")
37-
String order
19+
Integer size
3820
) {
3921
/**
4022
* 기본값이 적용된 생성자
4123
*/
4224
public FollowingSearchRequest {
4325
if (page == null) page = 0;
44-
if (size == null) size = 10;
45-
if (status == null) status = "FOLLOWING";
46-
if (sort == null) sort = "followedAt";
47-
if (order == null) order = "DESC";
26+
if (size == null) size = 8; // Figma 디자인: 한 페이지당 8개 (4x2 그리드)
4827
}
4928
}

src/main/java/com/back/domain/dashboard/customer/dto/response/FollowingResponse.java

Lines changed: 13 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,31 @@
22

33
import com.back.global.util.PageResponse;
44

5-
import java.time.LocalDateTime;
6-
75
/**
86
* 팔로우 관련 응답 DTO
9-
*
7+
* <p>
108
* 사용자가 팔로우한 작가들의 정보를 포함
119
* 2025.09.22 수정 - API 명세 변경에 따른 구조 개편
10+
* 2025.10.14 수정 - Figma 디자인에 맞춰 불필요한 필드 제거
1211
*/
1312
public class FollowingResponse {
14-
13+
1514
/**
1615
* 팔로우한 작가 목록 응답
1716
*/
1817
public static class List extends PageResponse<FollowingResponse.Artist> {
19-
/** 조회 대상 사용자 프로필 */
20-
private final Profile profile;
21-
/** 팔로우 현황 요약 정보 */
22-
private final SummaryDto summary;
23-
18+
2419
public List() {
2520
super();
26-
this.profile = null;
27-
this.summary = null;
2821
}
29-
30-
public List(Profile profile, SummaryDto summary, java.util.List<Artist> content,
31-
int page, int size, long totalElements, int totalPages,
32-
boolean hasNext, boolean hasPrevious) {
22+
23+
public List(java.util.List<Artist> content,
24+
int page, int size, long totalElements, int totalPages,
25+
boolean hasNext, boolean hasPrevious) {
3326
super(content, page, size, totalElements, totalPages, hasNext, hasPrevious);
34-
this.profile = profile;
35-
this.summary = summary;
36-
}
37-
38-
public Profile getProfile() {
39-
return profile;
40-
}
41-
42-
public SummaryDto getSummary() {
43-
return summary;
4427
}
4528
}
46-
47-
/**
48-
* 조회 대상 사용자 프로필 정보
49-
*/
50-
public record Profile(
51-
/** 사용자 ID */
52-
String userId,
53-
/** 닉네임 */
54-
String nickname,
55-
/** 프로필 이미지 URL */
56-
String profileImageUrl
57-
) {}
58-
59-
/**
60-
* 팔로우 현황 요약 정보
61-
*/
62-
public record SummaryDto(
63-
/** 전체 팔로우 작가 수 */
64-
int totalFollowing
65-
) {}
66-
29+
6730
/**
6831
* 작가 정보
6932
*/
@@ -72,33 +35,10 @@ public record Artist(
7235
String artistId,
7336
/** 작가명 */
7437
String artistName,
75-
/** 프로필 이미지 URL */
38+
/** 프로필 이미지 URL (null인 경우 프론트에서 기본 이미지 표시) */
7639
String profileImageUrl,
77-
/** 팔로워 수 */
78-
int followerCount,
7940
/** 작가 페이지 URL */
80-
String artistPageUrl,
81-
/** 팔로우 관계 정보 */
82-
FollowRelation followRelation,
83-
/** 배지 정보 */
84-
Badge badges
85-
) {}
86-
87-
/**
88-
* 팔로우 관계 정보
89-
*/
90-
public record FollowRelation(
91-
/** 관계 상태 (FOLLOWING) */
92-
String status,
93-
/** 팔로우한 날짜 */
94-
LocalDateTime followedAt
95-
) {}
96-
97-
/**
98-
* 작가 배지 정보
99-
*/
100-
public record Badge(
101-
/** 인증 작가 여부 */
102-
Boolean verified
103-
) {}
41+
String artistPageUrl
42+
) {
43+
}
10444
}

src/main/java/com/back/domain/dashboard/customer/service/DashboardServiceImpl.java

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class DashboardServiceImpl implements DashboardService {
4343
private final com.back.domain.payment.cash.repository.CashTransactionRepository cashTransactionRepository;
4444
private final com.back.domain.payment.moriCash.repository.MoriCashPaymentRepository moriCashPaymentRepository;
4545
private final com.back.domain.payment.moriCash.repository.MoriCashBalanceRepository moriCashBalanceRepository;
46+
private final com.back.domain.follow.repository.FollowRepository followRepository;
4647

4748
private static final DateTimeFormatter ORDER_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy. MM. dd");
4849
private static final DateTimeFormatter FUNDING_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy. MM. dd");
@@ -448,42 +449,51 @@ private String mapOrderStatusText(com.back.domain.order.order.entity.OrderStatus
448449

449450
@Override
450451
public FollowingResponse.List getFollowingArtists(Long userId, FollowingSearchRequest request) {
451-
// TODO: 실제 데이터베이스 조회 로직 구현
452452
log.debug("팔로우한 작가 목록 조회 - userId: {}, request: {}", userId, request);
453453

454-
// 사용자 조회
454+
// 1. 사용자 조회
455455
User user = userRepository.findById(userId)
456456
.orElseThrow(() -> new ServiceException("USER_NOT_FOUND", "사용자를 찾을 수 없습니다."));
457457

458-
FollowingResponse.Profile profile = new FollowingResponse.Profile(
459-
user.getId().toString(),
460-
user.getName(),
461-
user.getProfileImageUrl());
462-
463-
FollowingResponse.SummaryDto summary =
464-
new FollowingResponse.SummaryDto(5);
465-
466-
List<FollowingResponse.Artist> content = Arrays.asList(
467-
new FollowingResponse.Artist(
468-
"artist_001", "감성작가",
469-
"https://cdn.example.com/artists/artist_001/profile.jpg",
470-
500, "/artists/artist_001",
471-
new FollowingResponse.FollowRelation("FOLLOWING", LocalDateTime.now()),
472-
new FollowingResponse.Badge(true)
473-
),
474-
new FollowingResponse.Artist(
475-
"artist_002", "캐릭터작가",
476-
"https://cdn.example.com/artists/artist_002/profile.jpg",
477-
123, "/artists/artist_002",
478-
new FollowingResponse.FollowRelation("FOLLOWING", LocalDateTime.now().minusDays(1)),
479-
new FollowingResponse.Badge(false)
480-
)
481-
);
458+
// 2. 팔로우 목록 조회
459+
List<com.back.domain.follow.entity.Follow> follows =
460+
followRepository.findFollowingsByFollowerId(userId);
461+
462+
// 3. 페이징 처리
463+
long total = follows.size();
464+
int start = request.page() * request.size();
465+
int end = Math.min(start + request.size(), follows.size());
466+
List<com.back.domain.follow.entity.Follow> pagedFollows =
467+
follows.subList(start, Math.min(end, follows.size()));
468+
469+
// 4. DTO 변환
470+
List<FollowingResponse.Artist> content = pagedFollows.stream()
471+
.map(this::convertToFollowingArtist)
472+
.collect(Collectors.toList());
473+
474+
// 5. 페이징 정보 계산
475+
int totalPages = (int) Math.ceil((double) total / request.size());
476+
boolean hasNext = request.page() < totalPages - 1;
477+
boolean hasPrevious = request.page() > 0;
482478

483479
return new FollowingResponse.List(
484-
profile, summary, content,
480+
content,
485481
request.page(), request.size(),
486-
5, 1, false, false);
482+
total, totalPages, hasNext, hasPrevious);
483+
}
484+
485+
/**
486+
* Follow 엔티티를 Artist DTO로 변환
487+
*/
488+
private FollowingResponse.Artist convertToFollowingArtist(com.back.domain.follow.entity.Follow follow) {
489+
com.back.domain.artist.entity.ArtistProfile artist = follow.getFollowingArtist();
490+
491+
return new FollowingResponse.Artist(
492+
artist.getId().toString(),
493+
artist.getArtistName(),
494+
artist.getProfileImageUrl(), // null인 경우 프론트에서 기본 이미지 처리
495+
"/artists/" + artist.getId()
496+
);
487497
}
488498

489499
@Override
@@ -719,8 +729,8 @@ public CashResponse.HistoryList getCashHistory(Long userId, CashHistorySearchReq
719729
tx.getBalanceAfter() != null ? tx.getBalanceAfter() : 0,
720730
"모리캐시",
721731
"COMPLETED",
722-
tx.getOrder() != null ?
723-
new CashResponse.Link("/orders/" + tx.getOrder().getOrderNumber()) : null
732+
tx.getOrder() != null ?
733+
new CashResponse.Link("/orders/" + tx.getOrder().getOrderNumber()) : null
724734
)));
725735

726736
// 4. 날짜순 정렬 (최신순)
@@ -729,7 +739,7 @@ public CashResponse.HistoryList getCashHistory(Long userId, CashHistorySearchReq
729739
// 5. 페이징 처리
730740
int start = request.page() * request.size();
731741
int end = Math.min(start + request.size(), allTransactions.size());
732-
var pagedContent = start < allTransactions.size() ?
742+
var pagedContent = start < allTransactions.size() ?
733743
allTransactions.subList(start, end) : List.<CashResponse.Transaction>of();
734744

735745
// 6. 통계 계산

src/main/java/com/back/domain/order/order/repository/OrderRepository.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,22 @@ java.math.BigDecimal findTotalSettlementAmount(
281281
* 사용자별 주문 개수 조회
282282
*/
283283
long countByUser(User user);
284+
285+
/**
286+
* 작가별 배송 완료된 주문 조회 (정산 통계용)
287+
* - 특정 기간 내 DELIVERED 상태 주문만
288+
* - 작가가 판매한 상품의 주문만
289+
*/
290+
@Query("SELECT DISTINCT o FROM Order o " +
291+
"JOIN o.orderItems oi " +
292+
"JOIN oi.product p " +
293+
"WHERE p.user = :artist " +
294+
"AND o.status = com.back.domain.order.order.entity.OrderStatus.DELIVERED " +
295+
"AND o.orderDate >= :startDate " +
296+
"AND o.orderDate <= :endDate")
297+
List<Order> findDeliveredOrdersByArtistInPeriod(
298+
@Param("artist") User artist,
299+
@Param("startDate") java.time.LocalDateTime startDate,
300+
@Param("endDate") java.time.LocalDateTime endDate
301+
);
284302
}

src/main/java/com/back/domain/payment/cash/repository/CashTransactionRepository.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,56 @@ List<CashTransaction> findByTransactionTypeAndStatus(@Param("transactionType") C
7070
"AND ct.status = 'COMPLETED' " +
7171
"ORDER BY ct.completedAt DESC, ct.createDate DESC")
7272
List<CashTransaction> findCompletedChargingByUser(@Param("user") User user);
73+
74+
/**
75+
* 작가별 캐시 거래 내역 조회 (페이징, 다중 조건 필터링, 동적 정렬)
76+
* @param user 사용자
77+
* @param transactionType 거래 유형 (CHARGING, EXCHANGE)
78+
* @param status 거래 상태 (PENDING, COMPLETED, FAILED, CANCELLED)
79+
* @param startDate 시작 날짜
80+
* @param endDate 종료 날짜
81+
* @param pageable 페이징 및 정렬 정보
82+
* @return 조회된 거래 내역 페이지
83+
*/
84+
@Query("SELECT ct FROM CashTransaction ct " +
85+
"WHERE ct.user = :user " +
86+
"AND (:transactionType IS NULL OR ct.transactionType = :transactionType) " +
87+
"AND (:status IS NULL OR ct.status = :status) " +
88+
"AND (:startDate IS NULL OR ct.createDate >= :startDate) " +
89+
"AND (:endDate IS NULL OR ct.createDate <= :endDate)")
90+
Page<CashTransaction> findCashTransactionsByUserWithFilters(
91+
@Param("user") User user,
92+
@Param("transactionType") CashTransactionType transactionType,
93+
@Param("status") CashTransactionStatus status,
94+
@Param("startDate") LocalDateTime startDate,
95+
@Param("endDate") LocalDateTime endDate,
96+
Pageable pageable);
97+
98+
/**
99+
* 기간 내 사용자별 입금(충전) 합계
100+
*/
101+
@Query("SELECT COALESCE(SUM(ct.amount), 0) FROM CashTransaction ct " +
102+
"WHERE ct.user = :user " +
103+
"AND ct.transactionType = 'CHARGING' " +
104+
"AND ct.status = 'COMPLETED' " +
105+
"AND (:startDate IS NULL OR ct.completedAt >= :startDate) " +
106+
"AND (:endDate IS NULL OR ct.completedAt <= :endDate)")
107+
Integer getPeriodDepositTotal(
108+
@Param("user") User user,
109+
@Param("startDate") LocalDateTime startDate,
110+
@Param("endDate") LocalDateTime endDate);
111+
112+
/**
113+
* 기간 내 사용자별 환전 합계
114+
*/
115+
@Query("SELECT COALESCE(SUM(ct.amount), 0) FROM CashTransaction ct " +
116+
"WHERE ct.user = :user " +
117+
"AND ct.transactionType = 'EXCHANGE' " +
118+
"AND ct.status = 'COMPLETED' " +
119+
"AND (:startDate IS NULL OR ct.completedAt >= :startDate) " +
120+
"AND (:endDate IS NULL OR ct.completedAt <= :endDate)")
121+
Integer getPeriodWithdrawalTotal(
122+
@Param("user") User user,
123+
@Param("startDate") LocalDateTime startDate,
124+
@Param("endDate") LocalDateTime endDate);
73125
}

0 commit comments

Comments
 (0)