Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3c127b3
#54 feat: 사용자 앨범 증가 로직 추가
zyovn Nov 12, 2025
e67a51d
#54 feat: 사진 정보 조회 API
zyovn Nov 12, 2025
6c25476
#54 feat: 띱한 사용자 목록 조회 API
zyovn Nov 12, 2025
d12b693
#54 fix: 베스트 앨범컷 좋아요 수 같을 시 최신순으로 수정
zyovn Nov 12, 2025
1cad54e
#54 feat: 앨범 베스트 컷 조회 API
zyovn Nov 12, 2025
5fdd1fc
#54 fix: 썸네일 생성 시, 캐시 무효화로 수정
zyovn Nov 12, 2025
0989d80
#54 fix: cdn url 파싱 수정
zyovn Nov 13, 2025
0134ae4
#54 refactor: 조회 관련 API response 수정
zyovn Nov 13, 2025
e7f3324
#54 feat: 앨범 정보 API
zyovn Nov 13, 2025
f39f815
#54 chore: 불필요한 import문 제거
zyovn Nov 13, 2025
c5f17d5
#54 docs: swagger 작성
zyovn Nov 13, 2025
6aef2e3
#54 fix: 앨범 최대 사진 개수 수정
zyovn Nov 13, 2025
32ed3d4
#54 chore: 불필요한 import문 제거
zyovn Nov 13, 2025
87e5f23
#54 fix: 사진 상세 정보 response 수정 및 스키마 수정
zyovn Nov 13, 2025
ff55261
#54 fix: response 수정
zyovn Nov 13, 2025
70468ba
#54 fix: 메서드명, validate관련 로직, imageUrl cdn url로 수정
zyovn Nov 13, 2025
fb233c0
#54 docs: schema 수정
zyovn Nov 13, 2025
449a664
#54 refactor: 성공 코드 수정
zyovn Nov 13, 2025
ffa4741
#54 fix: 앨범 베스트컷 조회 API thumbnailUrl 수정 및 패키지 이동
zyovn Nov 13, 2025
6971ce1
#54 fix: 피드백 반영
zyovn Nov 13, 2025
6e4d3eb
#54 fix: 피드백 반영
zyovn Nov 13, 2025
a5bbb7b
#54 fix: 충돌 해결
zyovn Nov 14, 2025
44255e6
#54 chore: 불필요한 import문 제거 및 성공 코드 수정
zyovn Nov 14, 2025
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
52 changes: 47 additions & 5 deletions src/main/java/com/cheeeese/album/application/AlbumService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@
import com.cheeeese.album.infrastructure.persistence.AlbumExpirationRedisRepository;
import com.cheeeese.album.infrastructure.persistence.AlbumRepository;
import com.cheeeese.global.security.CustomUserDetails;
import com.cheeeese.global.util.resolver.CdnUrlResolver;
import com.cheeeese.photo.application.PhotoService;
import com.cheeeese.album.domain.UserAlbum;
import com.cheeeese.album.infrastructure.persistence.UserAlbumRepository;
import com.cheeeese.photo.domain.Photo;
import com.cheeeese.photo.domain.PhotoStatus;
import com.cheeeese.album.dto.response.AlbumBest4CutResponse;
import com.cheeeese.photo.infrastructure.mapper.PhotoMapper;
import com.cheeeese.photo.infrastructure.persistence.PhotoLikesRepository;
import com.cheeeese.photo.infrastructure.persistence.PhotoRepository;
import com.cheeeese.user.domain.User;
import com.cheeeese.user.exception.UserException;
import com.cheeeese.user.exception.code.UserErrorCode;
import com.cheeeese.user.infrastructure.persistence.UserRepository;
import com.github.f4b6a3.uuid.UuidCreator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
Expand All @@ -36,6 +43,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
Expand All @@ -48,8 +56,11 @@ public class AlbumService {
private final AlbumRepository albumRepository;
private final UserAlbumRepository userAlbumRepository;
private final UserRepository userRepository;
private final PhotoRepository photoRepository;
private final PhotoLikesRepository photoLikesRepository;
private final PhotoService photoService;
private final AlbumExpirationRedisRepository albumExpirationRedisRepository;
private final CdnUrlResolver cdnUrlResolver;

@Transactional
public AlbumCreationResponse createAlbum(User user, AlbumCreationRequest request) {
Expand Down Expand Up @@ -78,6 +89,7 @@ public AlbumCreationResponse createAlbum(User user, AlbumCreationRequest request
album,
Role.MAKER
));
userRepository.incrementAlbumCnt(user.getId());

albumExpirationRedisRepository.registerAlbum(album.getId(), expiredAt);

Expand Down Expand Up @@ -127,6 +139,7 @@ public AlbumEnterResponse enterAlbum(String code, User currentUser) {
if (updated == 0) {
throw new AlbumException(AlbumErrorCode.ALBUM_MAX_PARTICIPANT_REACHED);
}
userRepository.incrementAlbumCnt(currentUser.getId());

List<NewEnterResponse.RecentPhotoResponse> recentPhotos = getRecentPhotosWithUploaderInfo(album.getId());

Expand Down Expand Up @@ -180,6 +193,40 @@ public AlbumParticipantResponse getAlbumParticipantList(Authentication authentic
);
}

public AlbumInfoResponse getAlbumInfo(User user, String code) {
Album album = albumValidator.validateAlbumCode(code);

albumValidator.validateAlbumParticipant(album, user);

return PhotoMapper.toAlbumInfoResponse(album);
}

public List<AlbumBest4CutResponse> getAlbumBest4Cut(User user, String code) {
Album album = albumValidator.validateAlbumCode(code);

albumValidator.validateAlbumParticipant(album, user);

List<Photo> topPhotos = photoRepository.findTop4CompletedPhotosByLikes(
album.getId(),
PhotoStatus.COMPLETED,
PageRequest.of(0, 4)
);

List<Long> photoIds = topPhotos.stream()
.map(Photo::getId)
.toList();

Set<Long> likedPhotoIds = photoLikesRepository.findAllLikedPhotoIds(user.getId(), photoIds);

return topPhotos.stream()
.map(photo -> {
String thumbnailUrl = cdnUrlResolver.resolveThumbnail(photo.getThumbnailUrl());
boolean isLiked = likedPhotoIds.contains(photo.getId());
return AlbumMapper.toBest4CutResponse(photo, thumbnailUrl, isLiked);
})
.toList();
}

private User extractUser(Authentication authentication) {
if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
return null;
Expand Down Expand Up @@ -247,9 +294,4 @@ private List<AlbumParticipantListResponse.ParticipantInfo> buildSortedParticipan
)
.toList();
}





}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void validateDownloadPermission(Album album, User user, List<Photo> photo
}
}

private void validateAlbumParticipant(Album album, User user) {
public void validateAlbumParticipant(Album album, User user) {
validateAlbumExpiration(album);

validateUserBlacklisted(album, user);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.cheeeese.album.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

@Builder
@Schema(description = "베스트 앨범컷 조회 API")
public record AlbumBest4CutResponse(
@Schema(description = "썸네일 이미지 url", example = "https://cdn.say-cheese.me/...")
String thumbnailUrl,

@Schema(description = "좋아요 수", example = "1")
int likeCnt,

@Schema(description = "좋아요 여부", example = "true")
boolean isLiked
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.cheeeese.album.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDate;

@Builder
@Schema(description = "앨범 정보 API")
public record AlbumInfoResponse(
@Schema(description = "생성자 ID", example = "1")
Long makerId,

@Schema(description = "앨범 제목", example = "김수한무거북이")
String title,

@Schema(description = "테마 이모지", example = "U+1F9C0")
String themeEmoji,

@Schema(description = "참여 가능 인원 수", example = "64")
int participant,

@Schema(description = "현재 참여자 수", example = "30")
int currentParticipant,

@Schema(description = "이벤트 날짜", example = "2025-02-01")
LocalDate eventDate,

@Schema(description = "현재 사진 수", example = "1212")
int currentPhotoCnt
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.cheeeese.album.domain.type.AlbumJoinStatus;
import com.cheeeese.album.dto.response.*;
import com.cheeeese.photo.domain.Photo;
import com.cheeeese.album.dto.response.AlbumBest4CutResponse;
import com.cheeeese.user.domain.User;

import java.time.LocalDate;
Expand Down Expand Up @@ -33,7 +34,7 @@ public static Album toEntity(
.participant(participant)
.currentParticipant(1)
.eventDate(eventDate)
.maxPhotoCount(10)
.maxPhotoCount(100)
.currentPhotoCount(0)
.isInfoAvailable(isInfoAvailable)
.expiredAt(expiredAt)
Expand Down Expand Up @@ -144,4 +145,12 @@ public static UploadAvailableCountResponse toAvailableCountResponse(
.currentPhotoCount(currentCount)
.build();
}

public static AlbumBest4CutResponse toBest4CutResponse(Photo photo, String thumbnailUrl, boolean isLiked) {
return AlbumBest4CutResponse.builder()
.thumbnailUrl(thumbnailUrl)
.likeCnt(photo.getLikesCnt())
.isLiked(isLiked)
.build();
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/cheeeese/album/presentation/AlbumController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import com.cheeeese.album.presentation.swagger.AlbumSwagger;
import com.cheeeese.global.common.CommonResponse;
import com.cheeeese.global.util.CurrentUser;
import com.cheeeese.album.dto.response.AlbumBest4CutResponse;
import com.cheeeese.user.domain.User;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import static com.cheeeese.global.common.code.SuccessCode.*;

@RestController
Expand Down Expand Up @@ -70,4 +73,27 @@ public CommonResponse<AlbumParticipantResponse> getAlbumParticipants(
);
}

@Override
@GetMapping("/{code}/info")
public CommonResponse<AlbumInfoResponse> getAlbumInfo(
@CurrentUser User user,
@PathVariable String code
) {
return CommonResponse.success(
ALBUM_INFO_GET_SUCCESS,
albumService.getAlbumInfo(user, code)
);
}

@Override
@GetMapping("/{code}/best-4cut")
public CommonResponse<List<AlbumBest4CutResponse>> getAlbumBest4Cut(
@CurrentUser User user,
@PathVariable String code
) {
return CommonResponse.success(
ALBUM_BEST4CUT_GET_SUCCESS,
albumService.getAlbumBest4Cut(user, code)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.cheeeese.album.dto.response.*;
import com.cheeeese.global.common.CommonResponse;
import com.cheeeese.global.util.CurrentUser;
import com.cheeeese.album.dto.response.AlbumBest4CutResponse;
import com.cheeeese.user.domain.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -16,6 +17,8 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

@Tag(name = "[앨범]", description = "앨범 관련 API")
public interface AlbumSwagger {
@Operation(
Expand Down Expand Up @@ -254,4 +257,42 @@ CommonResponse<AlbumParticipantResponse> getAlbumParticipants(
Authentication authentication,
@PathVariable String code
);

@Operation(
summary = "앨범 정보 조회 API",
description = """
### PathVariable
---
`code`: 앨범 코드
"""
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "앨범 정보 조회가 성공적으로 실행되었습니다."
)
})
CommonResponse<AlbumInfoResponse> getAlbumInfo(
@CurrentUser User user,
@PathVariable String code
);

@Operation(
summary = "앨범 내 베스트컷 (좋아요순 사진 4개) 조회 API",
description = """
### PathVariable
---
`code`: 앨범 코드 (String)
"""
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "앨범 내 베스트컷 조회가 성공적으로 실행되었습니다."
)
})
CommonResponse<List<AlbumBest4CutResponse>> getAlbumBest4Cut(
@CurrentUser User user,
@PathVariable String code
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.cheeeese.cheese4cut.dto.response.Cheese4cutResponse;
import com.cheeeese.cheese4cut.presentation.swagger.Cheese4cutSwagger;
import com.cheeeese.global.common.CommonResponse;
import com.cheeeese.global.common.code.SuccessCode;
import com.cheeeese.global.util.CurrentUser;
import com.cheeeese.user.domain.User;
import jakarta.validation.Valid;
Expand All @@ -16,8 +15,7 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import static com.cheeeese.global.common.code.SuccessCode.CHEESE4CUT_FINALIZE_SUCCESS;
import static com.cheeeese.global.common.code.SuccessCode.PRESIGNED_URL_ISSUE_SUCCESS;
import static com.cheeeese.global.common.code.SuccessCode.*;

@Validated
@RestController
Expand All @@ -33,7 +31,7 @@ public CommonResponse<Cheese4cutResponse> getCheese4cut(
Authentication authentication,
@PathVariable @NotBlank String code
) {
return CommonResponse.success(SuccessCode.CHEESE4CUT_GET_SUCCESS,
return CommonResponse.success(CHEESE4CUT_GET_SUCCESS,
cheese4cutService.getCheese4cutByAlbumCode(authentication, code));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public enum SuccessCode implements BaseCode {
ALBUM_ENTER_SUCCESS(HttpStatus.OK, "앨범 입장이 성공적으로 완료되었습니다."),
ALBUM_CREATE_SUCCESS(HttpStatus.OK, "앨범 생성이 성공적으로 완료되었습니다."),
ALBUM_PARTICIPANT_FETCH_SUCCESS(HttpStatus.OK, "앨범 참여자 조회가 성공적으로 완료되었습니다."),
ALBUM_INFO_GET_SUCCESS(HttpStatus.OK, "앨범 정보 조회가 성공적으로 완료되었습니다."),
ALBUM_BEST4CUT_GET_SUCCESS(HttpStatus.OK, "베스트 앨범컷 조회가 성공적으로 완료되었습니다."),

// photo
PHOTO_AVAILABLE_COUNT_FETCH_SUCCESS(HttpStatus.OK, "업로드 가능 사진 수 조회가 성공적으로 완료되었습니다."),
Expand All @@ -36,6 +38,7 @@ public enum SuccessCode implements BaseCode {
PHOTO_DETAIL_GET_SUCCESS(HttpStatus.OK, "앨범 내 사진 상세 조회가 성공적으로 완료되었습니다."),
PHOTO_LIKES_CREATE_SUCCESS(HttpStatus.OK, "사진 좋아요 생성이 완료되었습니다."),
PHOTO_LIKES_DELETE_SUCCESS(HttpStatus.OK, "사진 좋아요 삭제가 완료되었습니다."),
PHOTO_LIKERS_GET_SUCCESS(HttpStatus.OK, "띱한 사람 목록 조회가 성공적으로 완료되었습니다."),

// cheese4cut
CHEESE4CUT_GET_SUCCESS(HttpStatus.OK, "치즈네컷 조회가 성공적으로 완료되었습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public String resolveCut(String path) {
private String resolve(String domain, String path) {
if (path == null || path.isBlank()) return null;
if (path.startsWith("http")) return path;
if (path.startsWith("say-cheeeese/")) {
path = path.substring("say-cheeeese/".length());
}
if (path.startsWith("/")) path = path.substring(1);
return domain + "/" + path;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
public class PhotoCallbackService {

private final PhotoRepository photoRepository;
private final PhotoQueryService photoQueryService;

public void markUploadCompleted(PhotoCompleteRequest request) {
int updated = photoRepository.updateStatusAndUrl(
Expand All @@ -27,5 +28,10 @@ public void markUploadCompleted(PhotoCompleteRequest request) {
if (updated == 0) {
throw new PhotoException(PhotoErrorCode.THUMBNAIL_UPDATE_FAILED);
}

String albumCode = photoRepository.findAlbumCodeByPhotoId(request.photoId());
if (albumCode != null) {
photoQueryService.invalidatePhotoCache(albumCode);
}
}
}
Loading