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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class BirdIdSuggestionController {
@PermitAll
@Operation(
summary = "동정 의견을 기다리는 컬렉션 목록 조회",
description = "bird_id 미확정 PUBLIC 컬렉션 목록을 조회",
description = "bird_id가 미확정이고 동정 의견을 받는 PUBLIC 컬렉션 목록을 조회",
responses = @ApiResponse(
responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = GetPendingCollectionsResponse.class))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ public GetCollectionDetailResponse getCollectionDetail(
- commentCount
- createdAt
- discoveredDate
- canSuggestBirdId
""",
responses = {
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public LikeStatusResponse getLikeStatus(
security = @SecurityRequirement(name = "bearerAuth"),
description = "내가 좋아요한 컬렉션 목록을 조회합니다.",
responses = {
@ApiResponse(responseCode = "200", description = "좋아요한 컬렉션 ID 목록 조회 성공",
@ApiResponse(responseCode = "200", description = "좋아요한 컬렉션 목록 조회 성공",
content = @Content(schema = @Schema(implementation = GetLikedCollectionsResponse.class))),
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
@ApiResponse(responseCode = "404", description = "사용자가 존재하지 않음", content = @Content)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ public class GetCollectionEditDataResponse {

@Schema(description = "이미지 URL", example = "https://cdn.example.com/images/300.jpg")
private String imageUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public record GetLikedCollectionsResponse(
@Schema(name = "GetLikedCollectionsResponse.Item")
public record Item(
@Schema(description = "컬렉션 ID", example = "1")
Long collectionId
Long collectionId,

@Schema(description = "동정 의견을 받을 수 있는 상태인지 여부", example = "true")
Boolean canSuggestBirdId
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public static class Item {
@Schema(description = "내가 좋아요 눌렀는지 여부", example = "true")
private Boolean isLiked;

@Schema(description = "동정 의견을 받을 수 있는 상태인지 여부", example = "true")
private Boolean canSuggestBirdId;

@Schema(description = "컬렉션 소유자 정보")
private UserInfo user;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public record Item(
String thumbnailProfileImageUrl,

@Schema(description = "동정 의견 요청 시각", example = "2025-07-20T00:23:58.164815", requiredMode = Schema.RequiredMode.REQUIRED)
LocalDateTime birdIdSuggestionRequestedAt
LocalDateTime birdIdSuggestionRequestedAt,

@Schema(description = "동정 의견을 받을 수 있는 상태인지 여부", example = "true", requiredMode = Schema.RequiredMode.REQUIRED)
Boolean canSuggestBirdId
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public record Item(
LocalDateTime createdAt,

@Schema(description = "새 발견 일시", example = "2025-01-15")
LocalDate discoveredDate
LocalDate discoveredDate,

@Schema(description = "동정 의견을 받을 수 있는 상태인지 여부", example = "true")
Boolean canSuggestBirdId
) { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public GetPendingCollectionsResponse getPendingCollections() {
thumbnailProfileImageUrls.get(c.getUser().getId()),
startedAt != null
? OffsetDateTimeLocalizer.toSeoulLocalDateTime(startedAt)
: null
: null,
c.canReceiveBirdIdSuggestions()
);
})
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public LikeStatusResponse getLikeStatusResponse(Long userId, Long collectionId)
}

/**
* 사용자가 좋아요한 컬렉션 ID 목록 조회
* 사용자가 좋아요한 컬렉션 목록 조회
*/
public GetLikedCollectionsResponse getLikedCollectionIdsResponse(Long userId) {
userRepository.findById(userId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ public MyCollectionsResponse getMyCollections(Long userId) {
likeCount,
commentCount,
OffsetDateTimeLocalizer.toSeoulLocalDateTime(c.getCreatedAt()),
c.getDiscoveredDate()
c.getDiscoveredDate(),
c.canReceiveBirdIdSuggestions()
);
})
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)
public interface CollectionLikeWebMapper {

// 좋아요한 컬렉션 ID 목록 조회
// 좋아요한 컬렉션 목록 조회
default GetLikedCollectionsResponse toGetLikedCollectionsResponse(List<UserBirdCollection> collections) {
if (collections == null || collections.isEmpty()) {
return new GetLikedCollectionsResponse(List.of());
Expand All @@ -29,6 +29,7 @@ default GetLikedCollectionsResponse toGetLikedCollectionsResponse(List<UserBirdC
List<GetLikedCollectionsResponse.Item> toLikedCollectionItems(List<UserBirdCollection> collections);

@Mapping(target = "collectionId", source = "id")
@Mapping(target = "canSuggestBirdId", expression = "java(collection.canReceiveBirdIdSuggestions())")
GetLikedCollectionsResponse.Item toLikedCollectionItem(UserBirdCollection collection);

// 컬렉션을 좋아요한 사용자 목록 조회
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public interface CollectionWebMapper {
@Mapping(target = "likeCount", source = "likeCount")
@Mapping(target = "commentCount", source = "commentCount")
@Mapping(target = "isLiked", source = "isLiked")
@Mapping(target = "canSuggestBirdId", expression = "java(collection.canReceiveBirdIdSuggestions())")
@Mapping(target = "user.userId", source = "collection.user.id")
@Mapping(target = "user.nickname", source = "collection.user.nickname")
@Mapping(target = "user.profileImageUrl", source = "userProfileImageUrl")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public record CollectionItem(
LocalDate discoveredDate,

@Schema(description = "컬렉션을 업로드한 날짜", example = "2025-05-17", requiredMode = Schema.RequiredMode.REQUIRED)
LocalDate uploadedDate
LocalDate uploadedDate,

@Schema(description = "동정 의견을 받을 수 있는 상태인지 여부", example = "true", requiredMode = Schema.RequiredMode.REQUIRED)
Boolean canSuggestBirdId
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ default List<UserProfileResponse.CollectionItem> toCollectionItems(
thumbnailUrlMap.get(c.getId()),
c.getNote(),
c.getDiscoveredDate(),
OffsetDateTimeLocalizer.toSeoulLocalDate(c.getCreatedAt())
OffsetDateTimeLocalizer.toSeoulLocalDate(c.getCreatedAt()),
c.canReceiveBirdIdSuggestions()
))
.collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetBirdIdSuggestionsResponse;
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetPendingCollectionsResponse;
import org.devkor.apu.saerok_server.domain.collection.application.helper.CollectionImageUrlService;
import org.devkor.apu.saerok_server.domain.collection.core.entity.AccessLevelType;
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
import org.devkor.apu.saerok_server.domain.collection.core.repository.BirdIdSuggestionRepository;
import org.devkor.apu.saerok_server.domain.collection.core.repository.CollectionRepository;
Expand Down Expand Up @@ -69,6 +70,7 @@ private static UserBirdCollection coll(long id, User owner, String note) {
setField(c, "id", id);
setField(c, "user", owner);
setField(c, "note", note);
c.setAccessLevel(AccessLevelType.PUBLIC);
// birdIdSuggestionRequestedAt 필드는 제거되었으므로 더 이상 세팅하지 않음
return c;
}
Expand Down Expand Up @@ -123,12 +125,14 @@ void success() {
assertThat(first.imageUrl()).isEqualTo("http://cdn/img/thumb/key1.jpg");
assertThat(first.profileImageUrl()).isEqualTo("http://cdn/profile/1/profile.jpg");
assertThat(first.thumbnailProfileImageUrl()).isEqualTo("http://cdn/profile/1/thumbnail.webp");
assertThat(first.canSuggestBirdId()).isTrue();

GetPendingCollectionsResponse.Item second = res.items().get(1);
assertThat(second.collectionId()).isEqualTo(2L);
assertThat(second.imageUrl()).isNull();
assertThat(second.profileImageUrl()).isEqualTo("http://cdn/profile/default/default-1.png");
assertThat(second.thumbnailProfileImageUrl()).isEqualTo("http://cdn/profile/default/thumbnail-1.webp");
assertThat(second.canSuggestBirdId()).isTrue();

// 히스토리 조회가 호출됐는지까지 확인
verify(birdIdRequestHistoryRepository).findOpenStartedAtMapByCollectionIds(List.of(1L, 2L));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.devkor.apu.saerok_server.domain.collection.application;

import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetCollectionDetailResponse;
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.MyCollectionsResponse;
import org.devkor.apu.saerok_server.domain.collection.application.helper.CollectionImageUrlService;
import org.devkor.apu.saerok_server.domain.collection.core.entity.AccessLevelType;
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
import org.devkor.apu.saerok_server.domain.collection.core.repository.*;
import org.devkor.apu.saerok_server.domain.collection.core.util.PointFactory;
import org.devkor.apu.saerok_server.domain.collection.mapper.CollectionWebMapper;
import org.devkor.apu.saerok_server.domain.user.core.entity.User;
import org.devkor.apu.saerok_server.domain.user.core.repository.UserRepository;
Expand All @@ -22,9 +24,12 @@
import org.mockito.junit.jupiter.MockitoExtension;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verifyNoInteractions;

Expand Down Expand Up @@ -212,4 +217,44 @@ void getCollectionDetail_userNotFound() {
assertThrows(NotFoundException.class,
() -> collectionQueryService.getCollectionDetailResponse(badUserId, collectionId));
}

@Test
@DisplayName("내 컬렉션 목록에 동정 의견 가능 여부를 포함한다")
void getMyCollections_includesCanSuggestBirdId() throws IllegalAccessException {
Long userId = 1L;
User owner = new User();
userIdField.set(owner, userId);

UserBirdCollection enabled = UserBirdCollection.builder()
.user(owner)
.discoveredDate(LocalDate.of(2026, 7, 22))
.location(PointFactory.create(37.5, 127.0))
.accessLevel(AccessLevelType.PUBLIC)
.birdIdSuggestionEnabled(true)
.build();
collectionIdField.set(enabled, 1L);
org.springframework.test.util.ReflectionTestUtils.setField(enabled, "createdAt", OffsetDateTime.now());

UserBirdCollection disabled = UserBirdCollection.builder()
.user(owner)
.discoveredDate(LocalDate.of(2026, 7, 21))
.location(PointFactory.create(37.5, 127.0))
.accessLevel(AccessLevelType.PUBLIC)
.birdIdSuggestionEnabled(false)
.build();
collectionIdField.set(disabled, 2L);
org.springframework.test.util.ReflectionTestUtils.setField(disabled, "createdAt", OffsetDateTime.now());

List<UserBirdCollection> collections = List.of(enabled, disabled);
given(userRepository.findById(userId)).willReturn(Optional.of(owner));
given(collectionRepository.findByUserId(userId)).willReturn(collections);
given(collectionImageUrlService.getPrimaryImageUrlsFor(collections)).willReturn(Map.of());
given(collectionImageUrlService.getPrimaryImageThumbnailUrlsFor(collections)).willReturn(Map.of());

MyCollectionsResponse response = collectionQueryService.getMyCollections(userId);

assertThat(response.items())
.extracting(MyCollectionsResponse.Item::canSuggestBirdId)
.containsExactly(true, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package org.devkor.apu.saerok_server.domain.collection.mapper;

import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetCollectionDetailResponse;
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetLikedCollectionsResponse;
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetNearbyCollectionsResponse;
import org.devkor.apu.saerok_server.domain.collection.application.helper.CollectionImageUrlService;
import org.devkor.apu.saerok_server.domain.collection.core.entity.AccessLevelType;
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
import org.devkor.apu.saerok_server.domain.collection.core.util.PointFactory;
import org.devkor.apu.saerok_server.domain.profile.api.dto.response.UserProfileResponse;
import org.devkor.apu.saerok_server.domain.profile.mapper.UserProfileMapper;
import org.devkor.apu.saerok_server.domain.user.core.entity.User;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

class CollectionSuggestionFlagMapperTest {

private final CollectionWebMapper collectionWebMapper = Mappers.getMapper(CollectionWebMapper.class);
private final CollectionLikeWebMapper collectionLikeWebMapper = Mappers.getMapper(CollectionLikeWebMapper.class);
private final UserProfileMapper userProfileMapper = Mappers.getMapper(UserProfileMapper.class);

@Test
void collectionResponsesMapCanSuggestBirdIdFromCollectionState() {
UserBirdCollection collection = suggestionEnabledCollection();

GetCollectionDetailResponse detail = collectionWebMapper.toGetCollectionDetailResponse(
collection, null, null, null, 0, 0, false, true
);
GetNearbyCollectionsResponse.Item nearby = collectionWebMapper.toGetNearbyCollectionsResponseItem(
collection, null, null, null, null, 0, 0, false, true
);
GetLikedCollectionsResponse.Item liked = collectionLikeWebMapper.toLikedCollectionItem(collection);

assertThat(detail.getCanSuggestBirdId()).isTrue();
assertThat(nearby.getCanSuggestBirdId()).isTrue();
assertThat(liked.canSuggestBirdId()).isTrue();
}

@Test
void profileCollectionItemsMapCanSuggestBirdIdFromCollectionState() {
UserBirdCollection collection = suggestionEnabledCollection();
CollectionImageUrlService imageUrlService = mock(CollectionImageUrlService.class);
given(imageUrlService.getPrimaryImageUrlsFor(List.of(collection))).willReturn(Map.of());
given(imageUrlService.getPrimaryImageThumbnailUrlsFor(List.of(collection))).willReturn(Map.of());

List<UserProfileResponse.CollectionItem> items =
userProfileMapper.toCollectionItems(List.of(collection), imageUrlService);

assertThat(items).singleElement()
.extracting(UserProfileResponse.CollectionItem::canSuggestBirdId)
.isEqualTo(true);
}

private UserBirdCollection suggestionEnabledCollection() {
User owner = new User();
ReflectionTestUtils.setField(owner, "id", 10L);

UserBirdCollection collection = UserBirdCollection.builder()
.user(owner)
.bird(null)
.discoveredDate(LocalDate.of(2026, 7, 22))
.location(PointFactory.create(37.5, 127.0))
.accessLevel(AccessLevelType.PUBLIC)
.birdIdSuggestionEnabled(true)
.build();
ReflectionTestUtils.setField(collection, "id", 1L);
ReflectionTestUtils.setField(collection, "createdAt", OffsetDateTime.now());
return collection;
}
}
Loading