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
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ sonarqube {
'**/*Dto*.java, **/*Request*.java, **/*Response*.java, **/*Exception*.java, **/*ErrorCode*.java, **/*Validator*.java,' +
'**/*FcmService*.java, **/EventImage.java, **/Notification.java, **/Favorites.java ,**/ImageEventListener.java,' +
'**/TempAlbum.java, **/TempAlbumImage.java, **/TempAlbumType.java, **/TempAlbumType.java, **/FileExtension.java,' +
'**/RefundTask.java, **/RefundTaskStatus.java, cherrypic-batch/**, **/StorageUnitConverter.java, **/ImageRepositoryImpl.java'
'**/RefundTask.java, **/RefundTaskStatus.java, cherrypic-batch/**, **/StorageUnitConverter.java, **/ImageRepositoryImpl.java' +
'**/AlbumParticipationHistory.java, **/ParticipationAction.java'
property 'sonar.java.coveragePlugin', 'jacoco'
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.cherrypic.domain.album.repository;

import org.cherrypic.album.entity.AlbumParticipationHistory;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AlbumParticipationHistoryRepository
extends JpaRepository<AlbumParticipationHistory, Long>,
AlbumParticipationHistoryRepositoryCustom {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.cherrypic.domain.album.repository;

import org.cherrypic.domain.member.dto.response.ParticipationHistoryResponse;
import org.cherrypic.global.pagination.SortDirection;
import org.springframework.data.domain.Slice;

public interface AlbumParticipationHistoryRepositoryCustom {
Slice<ParticipationHistoryResponse> findParticipationHistory(
Long memberId, Long lastHistoryId, int size, SortDirection direction);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.cherrypic.domain.album.repository;

import static org.cherrypic.album.entity.QAlbumParticipationHistory.albumParticipationHistory;

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.cherrypic.domain.member.dto.response.ParticipationHistoryResponse;
import org.cherrypic.global.pagination.SortDirection;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class AlbumParticipationHistoryRepositoryImpl
implements AlbumParticipationHistoryRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public Slice<ParticipationHistoryResponse> findParticipationHistory(
Long memberId, Long lastHistoryId, int size, SortDirection direction) {
List<ParticipationHistoryResponse> results =
queryFactory
.select(
Projections.constructor(
ParticipationHistoryResponse.class,
albumParticipationHistory.id,
albumParticipationHistory.albumTitleSnapshot,
albumParticipationHistory.action,
albumParticipationHistory.createdAt))
.from(albumParticipationHistory)
.where(
albumParticipationHistory.memberId.eq(memberId),
lastHistoryIdCondition(lastHistoryId, direction))
.orderBy(
direction == SortDirection.DESC
? albumParticipationHistory.id.desc()
: albumParticipationHistory.id.asc())
.limit(size + 1)
.fetch();

return checkLastPage(size, results);
}

private BooleanExpression lastHistoryIdCondition(Long historyId, SortDirection direction) {
if (historyId == null) {
return null;
}

return direction == SortDirection.DESC
? albumParticipationHistory.id.lt(historyId)
: albumParticipationHistory.id.gt(historyId);
}

private Slice<ParticipationHistoryResponse> checkLastPage(
int pageSize, List<ParticipationHistoryResponse> results) {
boolean hasNext = false;

if (results.size() > pageSize) {
hasNext = true;
results.remove(pageSize);
}

return new SliceImpl<>(results, PageRequest.of(0, pageSize), hasNext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.cherrypic.album.entity.Album;
import org.cherrypic.album.entity.AlbumParticipationHistory;
import org.cherrypic.album.entity.InvitationCode;
import org.cherrypic.album.enums.AlbumType;
import org.cherrypic.album.enums.ParticipationAction;
import org.cherrypic.domain.album.dto.event.AlbumDeleteNotificationSendEvent;
import org.cherrypic.domain.album.dto.event.AlbumImagesDeleteEvent;
import org.cherrypic.domain.album.dto.request.AlbumCreateRequest;
import org.cherrypic.domain.album.dto.request.AlbumUpdateRequest;
import org.cherrypic.domain.album.dto.response.*;
import org.cherrypic.domain.album.exception.AlbumErrorCode;
import org.cherrypic.domain.album.repository.AlbumParticipationHistoryRepository;
import org.cherrypic.domain.album.repository.AlbumRepository;
import org.cherrypic.domain.album.repository.InvitationCodeRepository;
import org.cherrypic.domain.event.repository.EventImageRepository;
Expand Down Expand Up @@ -65,6 +68,7 @@ public class AlbumServiceImpl implements AlbumService {
private final ImageRepository imageRepository;
private final EventImageRepository eventImageRepository;
private final NotificationRepository notificationRepository;
private final AlbumParticipationHistoryRepository albumParticipationHistoryRepository;

private final ApplicationEventPublisher eventPublisher;

Expand Down Expand Up @@ -105,6 +109,10 @@ public AlbumCreateResponse createAlbum(AlbumCreateRequest request) {
Subscription.createSubscription(currentMember, album, payment.getPaidAt()));
}

albumParticipationHistoryRepository.save(
AlbumParticipationHistory.createAlbumParticipationHistory(
currentMember.getId(), album.getTitle(), ParticipationAction.JOIN));

return AlbumCreateResponse.from(album);
}

Expand Down Expand Up @@ -190,6 +198,10 @@ public AlbumJoinResponse joinAlbum(Long albumId, String code) {

favoritesRepository.save(Favorites.createFavorites(participant));

albumParticipationHistoryRepository.save(
AlbumParticipationHistory.createAlbumParticipationHistory(
currentMember.getId(), album.getTitle(), ParticipationAction.JOIN));

return AlbumJoinResponse.from(participant);
}

Expand Down Expand Up @@ -272,6 +284,10 @@ public void deleteAlbum(Long albumId) {
paymentRepository.deleteAllByAlbumId(album.getId());

albumRepository.deleteByAlbumId(album.getId());

albumParticipationHistoryRepository.save(
AlbumParticipationHistory.createAlbumParticipationHistory(
currentMember.getId(), album.getTitle(), ParticipationAction.DELETED));
}

private Album getAlbumById(Long albumId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cherrypic.domain.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -9,17 +10,25 @@
import org.cherrypic.domain.member.dto.response.LocalImageDeletionToggleResponse;
import org.cherrypic.domain.member.dto.response.MemberInfoResponse;
import org.cherrypic.domain.member.dto.response.MemberProfileUpdateResponse;
import org.cherrypic.domain.member.dto.response.ParticipationHistoryResponse;
import org.cherrypic.domain.member.service.MemberService;
import org.cherrypic.domain.member.service.ParticipationHistoryQueryService;
import org.cherrypic.global.annotation.PageSize;
import org.cherrypic.global.pagination.SliceResponse;
import org.cherrypic.global.pagination.SortDirection;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
@Tag(name = "1-2. 회원 API", description = "회원 관련 API입니다.")
@Validated
public class MemberController {

private final MemberService memberService;
private final ParticipationHistoryQueryService participationHistoryQueryService;

@GetMapping("/me")
@Operation(summary = "회원 정보 조회", description = "로그인한 회원 정보를 조회합니다.")
Expand All @@ -40,6 +49,20 @@ public LocalImageDeletionToggleResponse localImageDeletionToggle() {
return memberService.toggleLocalImageDeletion();
}

@GetMapping("/me/participation-history")
@Operation(summary = "앨범 참여 이력 조회", description = "사용자가 생성/입장, 삭제, 퇴장, 강퇴된 앨범 이력을 조회합니다.")
public SliceResponse<ParticipationHistoryResponse> participationHistoryGet(
@Parameter(description = "이전 페이지의 마지막 앨범 참여 이력 ID (첫 요청 시 생략)")
@RequestParam(required = false)
Long lastHistoryId,
@Parameter(description = "페이지당 조회할 참여 이력 수") @RequestParam @PageSize Integer size,
@Parameter(description = "정렬 방향 (ASC: 오래된순, DESC: 최신순)")
@RequestParam(defaultValue = "DESC")
SortDirection direction) {
return participationHistoryQueryService.getParticipationHistory(
lastHistoryId, size, direction);
}

@PostMapping("/fcm-tokens")
@Operation(
summary = "FCM 토큰 저장",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.cherrypic.domain.member.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import org.cherrypic.album.enums.ParticipationAction;

public record ParticipationHistoryResponse(
@Schema(description = "앨범 참여 이력 ID", example = "1") Long historyId,
@Schema(description = "앨범 이름 스냅샷 (액션 발생 직전 이름 기준)", example = "가족여행") String albumTitle,
@Schema(description = "액션 타입", example = "JOIN") ParticipationAction action,
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd",
timezone = "Asia/Seoul")
@Schema(description = "액션 발생일", example = "2025-08-01")
LocalDateTime eventTime) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.cherrypic.domain.member.service;

import lombok.RequiredArgsConstructor;
import org.cherrypic.domain.album.repository.AlbumParticipationHistoryRepository;
import org.cherrypic.domain.member.dto.response.ParticipationHistoryResponse;
import org.cherrypic.global.pagination.SliceResponse;
import org.cherrypic.global.pagination.SortDirection;
import org.cherrypic.global.util.MemberUtil;
import org.cherrypic.member.entity.Member;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ParticipationHistoryQueryService {

private final MemberUtil memberUtil;
private final AlbumParticipationHistoryRepository albumParticipationHistoryRepository;

public SliceResponse<ParticipationHistoryResponse> getParticipationHistory(
Long lastHistoryId, int size, SortDirection direction) {
final Member currentMember = memberUtil.getCurrentMember();

Slice<ParticipationHistoryResponse> results =
albumParticipationHistoryRepository.findParticipationHistory(
currentMember.getId(), lastHistoryId, size, direction);

return SliceResponse.from(results);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.cherrypic.album.entity.Album;
import org.cherrypic.album.entity.AlbumParticipationHistory;
import org.cherrypic.album.enums.AlbumType;
import org.cherrypic.album.enums.ParticipationAction;
import org.cherrypic.domain.album.exception.AlbumErrorCode;
import org.cherrypic.domain.album.repository.AlbumParticipationHistoryRepository;
import org.cherrypic.domain.album.repository.AlbumRepository;
import org.cherrypic.domain.favorites.repository.FavoritesRepository;
import org.cherrypic.domain.notification.repository.NotificationRepository;
Expand Down Expand Up @@ -40,6 +43,7 @@ public class ParticipantServiceImpl implements ParticipantService {
private final SubscriptionRepository subscriptionRepository;
private final FavoritesRepository favoritesRepository;
private final NotificationRepository notificationRepository;
private final AlbumParticipationHistoryRepository albumParticipationHistoryRepository;

@Override
public void leaveAlbum(Long albumId) {
Expand All @@ -57,6 +61,10 @@ public void leaveAlbum(Long albumId) {
notificationRepository.deleteByReceiverIdAndAlbumId(currentMember.getId(), albumId);
} catch (ObjectOptimisticLockingFailureException ignored) {
}

albumParticipationHistoryRepository.save(
AlbumParticipationHistory.createAlbumParticipationHistory(
currentMember.getId(), album.getTitle(), ParticipationAction.LEAVE));
}

@Override
Expand All @@ -78,6 +86,10 @@ public void kickParticipant(Long albumId, Long participantId) {
target.getMember().getId(), album.getId());
} catch (ObjectOptimisticLockingFailureException ignored) {
}

albumParticipationHistoryRepository.save(
AlbumParticipationHistory.createAlbumParticipationHistory(
target.getMember().getId(), album.getTitle(), ParticipationAction.KICK));
}

@Override
Expand Down
Loading