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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;

/**
* 팔로우한 작가 목록 검색 요청 DTO
* 2025.09.25 생성
* 2025.10.14 수정 - 팔로우 기능 실제 db로 연동
*/
public record FollowingSearchRequest(
/** 페이지 번호 (0부터 시작) */
Expand All @@ -16,34 +16,13 @@ public record FollowingSearchRequest(
/** 페이지 크기 (1-100) */
@Min(value = 1, message = "페이지 크기는 1 이상이어야 합니다")
@Max(value = 100, message = "페이지 크기는 100 이하여야 합니다")
Integer size,

/** 검색 키워드 (작가ID/작가명) */
String keyword,

/** 관계 상태 (FOLLOWING 고정) */
@Pattern(regexp = "^FOLLOWING$",
message = "status는 FOLLOWING이어야 합니다")
String status,

/** 정렬 기준 */
@Pattern(regexp = "^(followedAt|artistName|followerCount|lastPublishedAt)$",
message = "sort는 followedAt, artistName, followerCount, lastPublishedAt 중 하나여야 합니다")
String sort,

/** 정렬 방향 */
@Pattern(regexp = "^(ASC|DESC)$",
message = "order는 ASC 또는 DESC여야 합니다")
String order
Integer size
) {
/**
* 기본값이 적용된 생성자
*/
public FollowingSearchRequest {
if (page == null) page = 0;
if (size == null) size = 10;
if (status == null) status = "FOLLOWING";
if (sort == null) sort = "followedAt";
if (order == null) order = "DESC";
if (size == null) size = 8; // Figma 디자인: 한 페이지당 8개 (4x2 그리드)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,31 @@

import com.back.global.util.PageResponse;

import java.time.LocalDateTime;

/**
* 팔로우 관련 응답 DTO
*
* <p>
* 사용자가 팔로우한 작가들의 정보를 포함
* 2025.09.22 수정 - API 명세 변경에 따른 구조 개편
* 2025.10.14 수정 - Figma 디자인에 맞춰 불필요한 필드 제거
*/
public class FollowingResponse {

/**
* 팔로우한 작가 목록 응답
*/
public static class List extends PageResponse<FollowingResponse.Artist> {
/** 조회 대상 사용자 프로필 */
private final Profile profile;
/** 팔로우 현황 요약 정보 */
private final SummaryDto summary;


public List() {
super();
this.profile = null;
this.summary = null;
}
public List(Profile profile, SummaryDto summary, java.util.List<Artist> content,
int page, int size, long totalElements, int totalPages,
boolean hasNext, boolean hasPrevious) {

public List(java.util.List<Artist> content,
int page, int size, long totalElements, int totalPages,
boolean hasNext, boolean hasPrevious) {
super(content, page, size, totalElements, totalPages, hasNext, hasPrevious);
this.profile = profile;
this.summary = summary;
}

public Profile getProfile() {
return profile;
}

public SummaryDto getSummary() {
return summary;
}
}

/**
* 조회 대상 사용자 프로필 정보
*/
public record Profile(
/** 사용자 ID */
String userId,
/** 닉네임 */
String nickname,
/** 프로필 이미지 URL */
String profileImageUrl
) {}

/**
* 팔로우 현황 요약 정보
*/
public record SummaryDto(
/** 전체 팔로우 작가 수 */
int totalFollowing
) {}


/**
* 작가 정보
*/
Expand All @@ -72,33 +35,10 @@ public record Artist(
String artistId,
/** 작가명 */
String artistName,
/** 프로필 이미지 URL */
/** 프로필 이미지 URL (null인 경우 프론트에서 기본 이미지 표시) */
String profileImageUrl,
/** 팔로워 수 */
int followerCount,
/** 작가 페이지 URL */
String artistPageUrl,
/** 팔로우 관계 정보 */
FollowRelation followRelation,
/** 배지 정보 */
Badge badges
) {}

/**
* 팔로우 관계 정보
*/
public record FollowRelation(
/** 관계 상태 (FOLLOWING) */
String status,
/** 팔로우한 날짜 */
LocalDateTime followedAt
) {}

/**
* 작가 배지 정보
*/
public record Badge(
/** 인증 작가 여부 */
Boolean verified
) {}
String artistPageUrl
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class DashboardServiceImpl implements DashboardService {
private final com.back.domain.payment.cash.repository.CashTransactionRepository cashTransactionRepository;
private final com.back.domain.payment.moriCash.repository.MoriCashPaymentRepository moriCashPaymentRepository;
private final com.back.domain.payment.moriCash.repository.MoriCashBalanceRepository moriCashBalanceRepository;
private final com.back.domain.follow.repository.FollowRepository followRepository;

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

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

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

FollowingResponse.Profile profile = new FollowingResponse.Profile(
user.getId().toString(),
user.getName(),
user.getProfileImageUrl());

FollowingResponse.SummaryDto summary =
new FollowingResponse.SummaryDto(5);

List<FollowingResponse.Artist> content = Arrays.asList(
new FollowingResponse.Artist(
"artist_001", "감성작가",
"https://cdn.example.com/artists/artist_001/profile.jpg",
500, "/artists/artist_001",
new FollowingResponse.FollowRelation("FOLLOWING", LocalDateTime.now()),
new FollowingResponse.Badge(true)
),
new FollowingResponse.Artist(
"artist_002", "캐릭터작가",
"https://cdn.example.com/artists/artist_002/profile.jpg",
123, "/artists/artist_002",
new FollowingResponse.FollowRelation("FOLLOWING", LocalDateTime.now().minusDays(1)),
new FollowingResponse.Badge(false)
)
);
// 2. 팔로우 목록 조회
List<com.back.domain.follow.entity.Follow> follows =
followRepository.findFollowingsByFollowerId(userId);

// 3. 페이징 처리
long total = follows.size();
int start = request.page() * request.size();
int end = Math.min(start + request.size(), follows.size());
List<com.back.domain.follow.entity.Follow> pagedFollows =
follows.subList(start, Math.min(end, follows.size()));

// 4. DTO 변환
List<FollowingResponse.Artist> content = pagedFollows.stream()
.map(this::convertToFollowingArtist)
.collect(Collectors.toList());

// 5. 페이징 정보 계산
int totalPages = (int) Math.ceil((double) total / request.size());
boolean hasNext = request.page() < totalPages - 1;
boolean hasPrevious = request.page() > 0;

return new FollowingResponse.List(
profile, summary, content,
content,
request.page(), request.size(),
5, 1, false, false);
total, totalPages, hasNext, hasPrevious);
}

/**
* Follow 엔티티를 Artist DTO로 변환
*/
private FollowingResponse.Artist convertToFollowingArtist(com.back.domain.follow.entity.Follow follow) {
com.back.domain.artist.entity.ArtistProfile artist = follow.getFollowingArtist();

return new FollowingResponse.Artist(
artist.getId().toString(),
artist.getArtistName(),
artist.getProfileImageUrl(), // null인 경우 프론트에서 기본 이미지 처리
"/artists/" + artist.getId()
);
}

@Override
Expand Down Expand Up @@ -719,8 +729,8 @@ public CashResponse.HistoryList getCashHistory(Long userId, CashHistorySearchReq
tx.getBalanceAfter() != null ? tx.getBalanceAfter() : 0,
"모리캐시",
"COMPLETED",
tx.getOrder() != null ?
new CashResponse.Link("/orders/" + tx.getOrder().getOrderNumber()) : null
tx.getOrder() != null ?
new CashResponse.Link("/orders/" + tx.getOrder().getOrderNumber()) : null
)));

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

// 6. 통계 계산
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,22 @@ java.math.BigDecimal findTotalSettlementAmount(
* 사용자별 주문 개수 조회
*/
long countByUser(User user);

/**
* 작가별 배송 완료된 주문 조회 (정산 통계용)
* - 특정 기간 내 DELIVERED 상태 주문만
* - 작가가 판매한 상품의 주문만
*/
@Query("SELECT DISTINCT o FROM Order o " +
"JOIN o.orderItems oi " +
"JOIN oi.product p " +
"WHERE p.user = :artist " +
"AND o.status = com.back.domain.order.order.entity.OrderStatus.DELIVERED " +
"AND o.orderDate >= :startDate " +
"AND o.orderDate <= :endDate")
List<Order> findDeliveredOrdersByArtistInPeriod(
@Param("artist") User artist,
@Param("startDate") java.time.LocalDateTime startDate,
@Param("endDate") java.time.LocalDateTime endDate
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,56 @@ List<CashTransaction> findByTransactionTypeAndStatus(@Param("transactionType") C
"AND ct.status = 'COMPLETED' " +
"ORDER BY ct.completedAt DESC, ct.createDate DESC")
List<CashTransaction> findCompletedChargingByUser(@Param("user") User user);

/**
* 작가별 캐시 거래 내역 조회 (페이징, 다중 조건 필터링, 동적 정렬)
* @param user 사용자
* @param transactionType 거래 유형 (CHARGING, EXCHANGE)
* @param status 거래 상태 (PENDING, COMPLETED, FAILED, CANCELLED)
* @param startDate 시작 날짜
* @param endDate 종료 날짜
* @param pageable 페이징 및 정렬 정보
* @return 조회된 거래 내역 페이지
*/
@Query("SELECT ct FROM CashTransaction ct " +
"WHERE ct.user = :user " +
"AND (:transactionType IS NULL OR ct.transactionType = :transactionType) " +
"AND (:status IS NULL OR ct.status = :status) " +
"AND (:startDate IS NULL OR ct.createDate >= :startDate) " +
"AND (:endDate IS NULL OR ct.createDate <= :endDate)")
Page<CashTransaction> findCashTransactionsByUserWithFilters(
@Param("user") User user,
@Param("transactionType") CashTransactionType transactionType,
@Param("status") CashTransactionStatus status,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate,
Pageable pageable);

/**
* 기간 내 사용자별 입금(충전) 합계
*/
@Query("SELECT COALESCE(SUM(ct.amount), 0) FROM CashTransaction ct " +
"WHERE ct.user = :user " +
"AND ct.transactionType = 'CHARGING' " +
"AND ct.status = 'COMPLETED' " +
"AND (:startDate IS NULL OR ct.completedAt >= :startDate) " +
"AND (:endDate IS NULL OR ct.completedAt <= :endDate)")
Integer getPeriodDepositTotal(
@Param("user") User user,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate);

/**
* 기간 내 사용자별 환전 합계
*/
@Query("SELECT COALESCE(SUM(ct.amount), 0) FROM CashTransaction ct " +
"WHERE ct.user = :user " +
"AND ct.transactionType = 'EXCHANGE' " +
"AND ct.status = 'COMPLETED' " +
"AND (:startDate IS NULL OR ct.completedAt >= :startDate) " +
"AND (:endDate IS NULL OR ct.completedAt <= :endDate)")
Integer getPeriodWithdrawalTotal(
@Param("user") User user,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate);
}
Loading