diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java index 809d9dc64..1364a6fc8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/entity/GroupCapsuleOpen.java @@ -9,11 +9,10 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import java.util.Objects; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; import site.timecapsulearchive.core.global.entity.BaseEntity; @@ -39,16 +38,9 @@ public class GroupCapsuleOpen extends BaseEntity { @JoinColumn(name = "member_id", nullable = false) private Member member; - @Builder - private GroupCapsuleOpen(Boolean isOpened, Capsule capsule, Member member) { - this.isOpened = Objects.requireNonNull(isOpened); - this.capsule = Objects.requireNonNull(capsule); - this.member = Objects.requireNonNull(member); - } - - public static GroupCapsuleOpen createOf(Member member, Capsule capsule, Boolean isOpened) { - return new GroupCapsuleOpen(isOpened, capsule, member); - } + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "group_id", nullable = false) + private Group group; public void open() { this.isOpened = Boolean.TRUE; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java index 06fb9b364..376efced6 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApi.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupMemberCapsuleOpenStatusListResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleCreateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleUpdateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleDetailResponse; @@ -154,6 +155,35 @@ ResponseEntity> getMyGroupCapsules( ZonedDateTime createAt ); + @Operation( + summary = "그룹원의 그룹 캡슐 개봉 상태 확인", + description = """ + 그룹원의 그룹 캡슐 개봉 상태를 확인한다. + """, + security = {@SecurityRequirement(name = "user_token")}, + tags = {"group capsule"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "처리 완료" + ), + @ApiResponse( + responseCode = "404", + description = "그룹 캡슐의 개봉 상태를 찾을 수 없는 경우 예외가 발생한다.", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ) + }) + ResponseEntity> getGroupCapsuleOpenStatus( + Long memberId, + + @Parameter(in = ParameterIn.PATH, description = "개봉 상태를 확인할 캡슐 아이디", required = true) + Long capsuleId, + + @Parameter(in = ParameterIn.QUERY, description = "생성할 그룹 아이디", required = true) + Long groupId + ); + @Operation( summary = "그룹 캡슐 개봉", description = """ @@ -177,7 +207,7 @@ ResponseEntity> openCapsule( Long memberId, @Parameter(in = ParameterIn.PATH, description = "개봉할 그룹 캡슐 아이디", required = true) - @PathVariable("capsule_id") Long capsuleId + Long capsuleId ); @Operation( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java index 824421e0b..f63841be1 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/api/GroupCapsuleApiController.java @@ -2,6 +2,7 @@ import jakarta.validation.Valid; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; import org.springframework.http.ResponseEntity; @@ -17,12 +18,15 @@ import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleOpenStateDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupMemberCapsuleOpenStatusDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.MyGroupCapsuleDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleCreateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.reqeust.GroupCapsuleUpdateRequest; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleDetailResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleOpenStateResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsulePageResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupCapsuleSummaryResponse; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupMemberCapsuleOpenStatusListResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.MyGroupCapsuleSliceResponse; import site.timecapsulearchive.core.domain.capsule.group_capsule.facade.GroupCapsuleFacade; import site.timecapsulearchive.core.domain.capsule.group_capsule.service.GroupCapsuleService; @@ -139,6 +143,24 @@ public ResponseEntity> getMyGroupCapsules( ); } + @GetMapping(value = "/{capsule_id}/open-status", produces = {"application/json"}) + @Override + public ResponseEntity> getGroupCapsuleOpenStatus( + @AuthenticationPrincipal final Long memberId, + @PathVariable("capsule_id") final Long capsuleId, + @RequestParam("group_id") final Long groupId + ) { + List groupMemberCapsuleOpenStatus = groupCapsuleService.findGroupMemberCapsuleOpenStatus( + memberId, capsuleId, groupId); + + return ResponseEntity.ok( + ApiSpec.success( + SuccessCode.SUCCESS, + GroupMemberCapsuleOpenStatusListResponse.create(groupMemberCapsuleOpenStatus) + ) + ); + } + @PostMapping("/{capsule_id}/open") @Override public ResponseEntity> openCapsule( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupMemberCapsuleOpenStatusDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupMemberCapsuleOpenStatusDto.java new file mode 100644 index 000000000..ee9481f1b --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/dto/GroupMemberCapsuleOpenStatusDto.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto; + +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.response.GroupMemberCapsuleOpenStatusResponse; + +public record GroupMemberCapsuleOpenStatusDto( + Long memberId, + String nickname, + String profileUrl, + boolean isOpened +) { + + public GroupMemberCapsuleOpenStatusResponse toResponse() { + return GroupMemberCapsuleOpenStatusResponse.builder() + .memberId(memberId) + .nickname(nickname) + .profileUrl(profileUrl) + .isOpened(isOpened) + .build(); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupMemberCapsuleOpenStatusListResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupMemberCapsuleOpenStatusListResponse.java new file mode 100644 index 000000000..6aabc3fd7 --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupMemberCapsuleOpenStatusListResponse.java @@ -0,0 +1,20 @@ +package site.timecapsulearchive.core.domain.capsule.group_capsule.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupMemberCapsuleOpenStatusDto; + +@Schema(description = "그룹원들 캡슐 개봉 상태") +public record GroupMemberCapsuleOpenStatusListResponse( + List groupMemberCapsuleOpenStatus +) { + + public static GroupMemberCapsuleOpenStatusListResponse create( + List groupMemberCapsuleOpenStatus) { + List groupMemberCapsuleOpenStatusResponses = groupMemberCapsuleOpenStatus.stream() + .map(GroupMemberCapsuleOpenStatusDto::toResponse) + .toList(); + + return new GroupMemberCapsuleOpenStatusListResponse(groupMemberCapsuleOpenStatusResponses); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupMemberCapsuleOpenStatusResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupMemberCapsuleOpenStatusResponse.java new file mode 100644 index 000000000..fc4a98fae --- /dev/null +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/data/response/GroupMemberCapsuleOpenStatusResponse.java @@ -0,0 +1,23 @@ +package site.timecapsulearchive.core.domain.capsule.group_capsule.data.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Schema(description = "그룹원별 캡슐 개봉 상태") +@Builder +public record GroupMemberCapsuleOpenStatusResponse( + + @Schema(description = "회원 아이디") + Long memberId, + + @Schema(description = "회원 닉네임") + String nickname, + + @Schema(description = "회원 프로필") + String profileUrl, + + @Schema(description = "개봉 상태") + boolean isOpened +) { + +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java index c4e160daa..16c37f7c7 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/facade/GroupCapsuleFacade.java @@ -56,6 +56,6 @@ public void saveGroupCapsule( final List groupMemberIds = memberGroupQueryService.findGroupMemberIds(groupId); - groupCapsuleOpenService.bulkSave(groupMemberIds, capsule); + groupCapsuleOpenService.bulkSave(groupId, groupMemberIds, capsule); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java index 218d84b9b..31f6e152f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepository.java @@ -1,5 +1,10 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.repository; +import static site.timecapsulearchive.core.domain.capsule.entity.QGroupCapsuleOpen.groupCapsuleOpen; +import static site.timecapsulearchive.core.domain.member.entity.QMember.member; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -11,19 +16,26 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupMemberCapsuleOpenStatusDto; +import site.timecapsulearchive.core.domain.member.entity.QMember; @Repository @RequiredArgsConstructor public class GroupCapsuleOpenQueryRepository { private final JdbcTemplate jdbcTemplate; + private final JPAQueryFactory jpaQueryFactory; - public void bulkSave(final List groupMemberIds, final Capsule capsule) { + public void bulkSave( + final Long groupId, + final List groupMemberIds, + final Capsule capsule + ) { jdbcTemplate.batchUpdate( """ INSERT INTO group_capsule_open ( - group_capsule_open_id, is_opened, member_id, capsule_id, created_at, updated_at - ) values (?, ? ,? ,? ,?, ?) + group_capsule_open_id, is_opened, member_id, capsule_id, group_id, created_at, updated_at + ) values (?, ? ,? ,? ,?, ?, ?) """, new BatchPreparedStatementSetter() { @Override @@ -34,8 +46,9 @@ public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setBoolean(2, isOpened); ps.setLong(3, groupMemberIds.get(i)); ps.setLong(4, capsule.getId()); - ps.setTimestamp(5, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setLong(5, groupId); ps.setTimestamp(6, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); + ps.setTimestamp(7, Timestamp.valueOf(ZonedDateTime.now().toLocalDateTime())); } @Override @@ -45,4 +58,26 @@ public int getBatchSize() { } ); } + + public List findGroupMemberCapsuleOpenStatus( + final Long capsuleId, + final Long groupId + ) { + return jpaQueryFactory + .select( + Projections.constructor( + GroupMemberCapsuleOpenStatusDto.class, + groupCapsuleOpen.member.id, + groupCapsuleOpen.member.nickname, + groupCapsuleOpen.member.profileUrl, + groupCapsuleOpen.isOpened + ) + ) + .from(groupCapsuleOpen) + .join(groupCapsuleOpen.member, member) + .where(groupCapsuleOpen.group.id.eq(groupId) + .and(groupCapsuleOpen.capsule.id.eq(capsuleId)) + ) + .fetch(); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleOpenService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleOpenService.java index 32496954d..c695e2d7e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleOpenService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleOpenService.java @@ -12,7 +12,11 @@ public class GroupCapsuleOpenService { private final GroupCapsuleOpenQueryRepository repository; - public void bulkSave(final List groupMemberIds, final Capsule capsule) { - repository.bulkSave(groupMemberIds, capsule); + public void bulkSave( + final Long groupId, + final List groupMemberIds, + final Capsule capsule + ) { + repository.bulkSave(groupId, groupMemberIds, capsule); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java index 396e808a3..4988f6b01 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleService.java @@ -2,6 +2,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; import org.locationtech.jts.geom.Point; import org.springframework.data.domain.Slice; @@ -14,12 +15,17 @@ import site.timecapsulearchive.core.domain.capsule.generic_capsule.repository.CapsuleRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleCreateRequestDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; -import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleOpenStateDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleSummaryDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupMemberCapsuleOpenStatusDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.MyGroupCapsuleDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; @Service @Transactional(readOnly = true) @@ -28,6 +34,8 @@ public class GroupCapsuleService { private final CapsuleRepository capsuleRepository; private final GroupCapsuleQueryRepository groupCapsuleQueryRepository; + private final GroupCapsuleOpenQueryRepository groupCapsuleOpenQueryRepository; + private final MemberGroupRepository memberGroupRepository; @Transactional public Capsule saveGroupCapsule( @@ -98,7 +106,8 @@ public Slice findMyGroupCapsuleSlice( */ @Transactional public GroupCapsuleOpenStateDto openGroupCapsule(final Long memberId, final Long capsuleId) { - Capsule groupCapsule = capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(memberId, + Capsule groupCapsule = capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId( + memberId, capsuleId) .orElseThrow(CapsuleNotFondException::new); @@ -119,5 +128,18 @@ public GroupCapsuleOpenStateDto openGroupCapsule(final Long memberId, final Long groupCapsule.open(); return GroupCapsuleOpenStateDto.opened(); } + + public List findGroupMemberCapsuleOpenStatus( + final Long memberId, + final Long capsuleId, + final Long groupId + ) { + boolean isGroupMember = memberGroupRepository.existMemberGroupByMemberIdAndGroupId(memberId, groupId); + if (!isGroupMember) { + throw new NoGroupAuthorityException(); + } + + return groupCapsuleOpenQueryRepository.findGroupMemberCapsuleOpenStatus(capsuleId, groupId); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java index b11a3eade..c8aebdf57 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepository.java @@ -20,4 +20,6 @@ public interface MemberGroupQueryRepository { Optional findGroupMembersCount(Long groupId); List findGroupMemberIdsByGroupId(final Long groupId); + + boolean existMemberGroupByMemberIdAndGroupId(Long memberId, Long groupId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java index 84eb64bfe..51fec1240 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member_group/repository/member_group_repository/MemberGroupQueryRepositoryImpl.java @@ -103,6 +103,7 @@ public Optional findGroupMembersCount(final Long groupId) { ); } + @Override public List findGroupMemberIdsByGroupId(final Long groupId) { return jpaQueryFactory .select(memberGroup.member.id) @@ -110,4 +111,14 @@ public List findGroupMemberIdsByGroupId(final Long groupId) { .where(memberGroup.group.id.eq(groupId)) .fetch(); } + + @Override + public boolean existMemberGroupByMemberIdAndGroupId(Long memberId, Long groupId) { + final Integer count = jpaQueryFactory.selectOne() + .from(memberGroup) + .where(memberGroup.member.id.eq(memberId).and(memberGroup.group.id.eq(groupId))) + .fetchFirst(); + + return count != null; + } } diff --git a/backend/core/src/main/resources/db/migration/V29__group_capsule_open_add_group.sql b/backend/core/src/main/resources/db/migration/V29__group_capsule_open_add_group.sql new file mode 100644 index 000000000..c276318fe --- /dev/null +++ b/backend/core/src/main/resources/db/migration/V29__group_capsule_open_add_group.sql @@ -0,0 +1,3 @@ +alter table group_capsule_open add column group_id BIGINT; +ALTER TABLE group_capsule_open + ADD CONSTRAINT fk_group_capsule_open_group_id FOREIGN KEY (group_id) REFERENCES `group` (group_id); \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java index c9fdb0112..79ee69326 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/CapsuleFixture.java @@ -1,7 +1,6 @@ package site.timecapsulearchive.core.common.fixture.domain; import java.lang.reflect.Field; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; @@ -136,8 +135,10 @@ public static Optional groupCapsuleNotAllMemberOpen( Capsule capsule = capsuleBuilder.dueDate(ZonedDateTimeSupplier.utc().get()) .build(); - List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(false, - capsule, groupMembers); + Group group = GroupFixture.group(); + + List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(group, + false, capsule, groupMembers); setFieldValue(capsule, "id", capsuleId); setFieldValue(capsule, "groupCapsuleOpens", groupCapsuleOpens); @@ -153,7 +154,10 @@ public static Optional groupCapsuleHalfMemberOpen( Capsule capsule = capsuleBuilder.dueDate(ZonedDateTimeSupplier.utc().get()) .build(); + Group group = GroupFixture.group(); + List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpensNotAllOpened( + group, capsule, groupMembers); setFieldValue(capsule, "id", capsuleId); setFieldValue(capsule, "groupCapsuleOpens", groupCapsuleOpens); @@ -182,8 +186,10 @@ public static Optional groupCapsuleAllMemberOpen( Capsule capsule = capsuleBuilder.dueDate(ZonedDateTimeSupplier.utc().get()) .build(); - List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(true, - capsule, groupMembers); + Group group = GroupFixture.group(); + + List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(group, + true, capsule, groupMembers); setFieldValue(capsule, "id", capsuleId); setFieldValue(capsule, "groupCapsuleOpens", groupCapsuleOpens); @@ -199,22 +205,13 @@ public static Optional groupCapsuleExcludeSpecificMember( Capsule capsule = capsuleBuilder.dueDate(ZonedDateTimeSupplier.utc().get()) .build(); + Group group = GroupFixture.group(); + List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpensNotOpenSpecificMemberId( - capsule, groupMembers, memberId); + group, capsule, groupMembers, memberId); setFieldValue(capsule, "id", capsuleId); setFieldValue(capsule, "groupCapsuleOpens", groupCapsuleOpens); return Optional.ofNullable(capsule); } - - public static Optional groupCapsuleAlreadyOpen(Long memberId, Long capsuleId) { - CapsuleBuilder capsuleBuilder = getCapsuleBuilder(memberId); - Capsule capsule = capsuleBuilder.dueDate(ZonedDateTimeSupplier.utc().get()) - .build(); - - setFieldValue(capsule, "id", capsuleId); - setFieldValue(capsule, "isOpened", true); - - return Optional.ofNullable(capsule); - } } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java index e718b048e..7a4c17ff8 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/GroupCapsuleOpenFixture.java @@ -1,50 +1,69 @@ package site.timecapsulearchive.core.common.fixture.domain; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.stream.Stream; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; -import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; -import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; +import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; public class GroupCapsuleOpenFixture { - public static List groupCapsuleOpens(Boolean isOpened, Capsule capsule, - List groupMembers) { + public static List groupCapsuleOpens( + Group group, + Boolean isOpened, + Capsule capsule, + List groupMembers + ) { return groupMembers.stream() - .map(member -> GroupCapsuleOpen.builder() - .isOpened(isOpened) - .capsule(capsule) - .member(member) - .build() - ).toList(); + .map(member -> getGroupCapsuleOpen(group, isOpened, capsule, member)) + .toList(); } - public static Optional groupCapsuleOpen(int dataPrefix) { - Member member = MemberFixture.member(dataPrefix); - CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(member); + private static GroupCapsuleOpen getGroupCapsuleOpen( + Group group, + Boolean isOpened, + Capsule capsule, + Member member + ) { + try { + Constructor declaredConstructor = GroupCapsuleOpen.class.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + + GroupCapsuleOpen groupCapsuleOpen = declaredConstructor.newInstance(); + setFieldValue(groupCapsuleOpen, "group", group); + setFieldValue(groupCapsuleOpen, "capsule", capsule); + setFieldValue(groupCapsuleOpen, "isOpened", isOpened); + setFieldValue(groupCapsuleOpen, "member", member); + return groupCapsuleOpen; + } catch (Exception e) { + throw new RuntimeException(e); + } + } - return Optional.of( - GroupCapsuleOpen.createOf( - MemberFixture.member(dataPrefix), - CapsuleFixture.capsule(member, capsuleSkin, CapsuleType.GROUP), - Boolean.FALSE - ) - ); + private static void setFieldValue(Object instance, String fieldName, Object value) { + try { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, value); + } catch (Exception e) { + throw new RuntimeException(e); + } } public static List groupCapsuleOpensNotAllOpened( + Group group, Capsule capsule, List groupMembers ) { int mid = groupMembers.size() / 2; - List opened = groupCapsuleOpens(true, capsule, + List opened = groupCapsuleOpens(group, true, capsule, groupMembers.subList(0, mid)); - List notOpened = groupCapsuleOpens(false, capsule, + List notOpened = groupCapsuleOpens(group, false, capsule, groupMembers.subList(mid, groupMembers.size() - 1)); return Stream.of(opened, notOpened) @@ -53,6 +72,7 @@ public static List groupCapsuleOpensNotAllOpened( } public static List groupCapsuleOpensNotOpenSpecificMemberId( + Group group, Capsule capsule, List groupMembers, Long memberId @@ -72,8 +92,8 @@ public static List groupCapsuleOpensNotOpenSpecificMemberId( } List result = new ArrayList<>(); - result.add(GroupCapsuleOpen.createOf(specificMember, capsule, false)); - result.addAll(groupCapsuleOpens(true, capsule, filteredMember)); + result.add(getGroupCapsuleOpen(group, false, capsule, specificMember)); + result.addAll(groupCapsuleOpens(group, true, capsule, filteredMember)); return result; } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupMemberCapsuleOpenStatusDtoFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupMemberCapsuleOpenStatusDtoFixture.java new file mode 100644 index 000000000..695ef1c7b --- /dev/null +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/dto/GroupMemberCapsuleOpenStatusDtoFixture.java @@ -0,0 +1,21 @@ +package site.timecapsulearchive.core.common.fixture.dto; + +import java.util.ArrayList; +import java.util.List; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupMemberCapsuleOpenStatusDto; + +public class GroupMemberCapsuleOpenStatusDtoFixture { + + public static List groupMemberCapsuleOpenStatusDto( + Long memberId, + int size + ) { + List result = new ArrayList<>(); + for (long count = memberId; count < size; count++) { + result.add(new GroupMemberCapsuleOpenStatusDto(count, count + "test-nickname", + count + "test-profile", true)); + } + + return result; + } +} diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java index 4a9c6c10d..5637192d6 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleOpenQueryRepositoryTest.java @@ -19,12 +19,12 @@ import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; import site.timecapsulearchive.core.common.fixture.domain.CapsuleSkinFixture; import site.timecapsulearchive.core.common.fixture.domain.GroupCapsuleOpenFixture; +import site.timecapsulearchive.core.common.fixture.domain.GroupFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; -import site.timecapsulearchive.core.domain.capsule.entity.CapsuleType; import site.timecapsulearchive.core.domain.capsule.entity.GroupCapsuleOpen; -import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; import site.timecapsulearchive.core.domain.capsuleskin.entity.CapsuleSkin; +import site.timecapsulearchive.core.domain.group.entity.Group; import site.timecapsulearchive.core.domain.member.entity.Member; @TestConstructor(autowireMode = AutowireMode.ALL) @@ -32,21 +32,29 @@ class GroupCapsuleOpenQueryRepositoryTest extends RepositoryTest { private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final GroupCapsuleOpenQueryRepository groupCapsuleOpenRepository; + + private Long groupId; private Capsule capsule; private List groupMembers; private List groupCapsuleOpens; public GroupCapsuleOpenQueryRepositoryTest( JdbcTemplate jdbcTemplate, - DataSource dataSource + DataSource dataSource, + JPAQueryFactory jpaQueryFactory ) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate); + this.groupCapsuleOpenRepository = new GroupCapsuleOpenQueryRepository(jdbcTemplate, + jpaQueryFactory); } @Transactional @BeforeEach void setUp(@Autowired EntityManager entityManager) { + Group group = GroupFixture.group(); + entityManager.persist(group); + groupId = group.getId(); + groupMembers = MemberFixture.members(1, 5); groupMembers.forEach(entityManager::persist); @@ -55,10 +63,11 @@ void setUp(@Autowired EntityManager entityManager) { CapsuleSkin capsuleSkin = CapsuleSkinFixture.capsuleSkin(groupLeader); entityManager.persist(capsuleSkin); - capsule = CapsuleFixture.capsule(groupLeader, capsuleSkin, CapsuleType.GROUP); + capsule = CapsuleFixture.groupCapsule(groupLeader, capsuleSkin, group); entityManager.persist(capsule); - groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(false, capsule, groupMembers); + groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(group, false, capsule, + groupMembers); } @Test @@ -68,7 +77,7 @@ void setUp(@Autowired EntityManager entityManager) { Long capsuleId = capsule.getId(); // when - groupCapsuleOpenRepository.bulkSave(groupMemberIds, capsule); + groupCapsuleOpenRepository.bulkSave(groupId, groupMemberIds, capsule); //then String sql = "SELECT count(*) from group_capsule_open WHERE capsule_id = (:capsuleId) and member_id in (:groupMemberIds)"; diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java index e74d162de..0e1aab206 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/repository/GroupCapsuleQueryRepositoryTest.java @@ -76,7 +76,7 @@ void setup(@Autowired EntityManager entityManager) { capsuleId = capsule.getId(); //그룹 캡슐 오픈 여부 - List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(false, + List groupCapsuleOpens = GroupCapsuleOpenFixture.groupCapsuleOpens(group, false, capsule, groupMember); groupCapsuleOpens.forEach(entityManager::persist); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java index 31e5d2ca5..4d9721f51 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/capsule/group_capsule/service/GroupCapsuleServiceTest.java @@ -1,5 +1,6 @@ package site.timecapsulearchive.core.domain.capsule.group_capsule.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.anyLong; @@ -14,6 +15,7 @@ import site.timecapsulearchive.core.common.fixture.domain.CapsuleFixture; import site.timecapsulearchive.core.common.fixture.domain.MemberFixture; import site.timecapsulearchive.core.common.fixture.dto.CapsuleDtoFixture; +import site.timecapsulearchive.core.common.fixture.dto.GroupMemberCapsuleOpenStatusDtoFixture; import site.timecapsulearchive.core.domain.capsule.entity.Capsule; import site.timecapsulearchive.core.domain.capsule.exception.CapsuleNotFondException; import site.timecapsulearchive.core.domain.capsule.exception.GroupCapsuleOpenNotFoundException; @@ -22,9 +24,13 @@ import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.CapsuleOpenStatus; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleDetailDto; import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupCapsuleOpenStateDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.data.dto.GroupMemberCapsuleOpenStatusDto; +import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleOpenQueryRepository; import site.timecapsulearchive.core.domain.capsule.group_capsule.repository.GroupCapsuleQueryRepository; import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberSummaryDto; import site.timecapsulearchive.core.domain.member.entity.Member; +import site.timecapsulearchive.core.domain.member_group.exception.NoGroupAuthorityException; +import site.timecapsulearchive.core.domain.member_group.repository.member_group_repository.MemberGroupRepository; import site.timecapsulearchive.core.global.error.ErrorCode; class GroupCapsuleServiceTest { @@ -36,9 +42,13 @@ class GroupCapsuleServiceTest { private final CapsuleRepository capsuleRepository = mock(CapsuleRepository.class); private final GroupCapsuleQueryRepository groupCapsuleQueryRepository = mock( GroupCapsuleQueryRepository.class); + private final GroupCapsuleOpenQueryRepository groupCapsuleOpenQueryRepository = mock( + GroupCapsuleOpenQueryRepository.class); + private final MemberGroupRepository memberGroupRepository = mock(MemberGroupRepository.class); private final GroupCapsuleService groupCapsuleService = new GroupCapsuleService( - capsuleRepository, groupCapsuleQueryRepository); + capsuleRepository, groupCapsuleQueryRepository, groupCapsuleOpenQueryRepository, + memberGroupRepository); @Test void 개봉된_그룹_캡슐의_상세_내용을_볼_수_있다() { @@ -196,7 +206,8 @@ class GroupCapsuleServiceTest { @Test void 그룹_캡슐이_없는_경우_그룹_캡슐_개봉_시_예외가_발생한다() { //given - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(Optional.empty()); //when @@ -212,7 +223,8 @@ class GroupCapsuleServiceTest { ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC")); Optional groupCapsule = CapsuleFixture.groupCapsuleSpecificTime(memberId, capsuleId, now.plusYears(5)); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -232,7 +244,8 @@ class GroupCapsuleServiceTest { //given Optional groupCapsule = CapsuleFixture.groupCapsuleSpecificTime(memberId, capsuleId, null); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -254,7 +267,8 @@ class GroupCapsuleServiceTest { Optional groupCapsule = CapsuleFixture.groupCapsuleNotAllMemberOpen(memberId, capsuleId, groupMembers); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -276,7 +290,8 @@ class GroupCapsuleServiceTest { Optional groupCapsule = CapsuleFixture.groupCapsuleHalfMemberOpen(memberId, capsuleId, groupMembers); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -295,7 +310,8 @@ class GroupCapsuleServiceTest { void 그룹_캡슐_개봉이_없는_경우_예외가_발생한다() { //given Optional groupCapsule = CapsuleFixture.groupCapsuleEmptyOpen(memberId, capsuleId); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -312,7 +328,8 @@ class GroupCapsuleServiceTest { Optional groupCapsule = CapsuleFixture.groupCapsuleAllMemberOpen(memberId, capsuleId, groupMembers); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -334,7 +351,8 @@ class GroupCapsuleServiceTest { Optional groupCapsule = CapsuleFixture.groupCapsuleExcludeSpecificMember(memberId, capsuleId, groupMembers); - given(capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) + given( + capsuleRepository.findNotOpenedGroupCapsuleByMemberIdAndCapsuleId(anyLong(), anyLong())) .willReturn(groupCapsule); //when @@ -348,4 +366,38 @@ class GroupCapsuleServiceTest { CapsuleOpenStatus.OPEN); }); } + + @Test + void 그룹원이_아닌_사용자가_그룹_캡슐_개봉_상태를_조회하면_오류가_발생한다() throws Exception { + //given + Long groupId = 1L; + + //when + //then + assertThatThrownBy( + () -> groupCapsuleService.findGroupMemberCapsuleOpenStatus(memberId, capsuleId, + groupId)) + .isInstanceOf(NoGroupAuthorityException.class) + .hasMessageContaining(ErrorCode.NO_GROUP_AUTHORITY_ERROR.getMessage()); + } + + @Test + void 그룹원이_그룹_캡슐_개봉_상태를_조회하면_그룹_캡슐_개봉_상태를_조회할_수_있다() throws Exception { + //given + Long groupId = 1L; + int size = 20; + given(memberGroupRepository.existMemberGroupByMemberIdAndGroupId(memberId, groupId)) + .willReturn(true); + given(groupCapsuleOpenQueryRepository.findGroupMemberCapsuleOpenStatus(capsuleId, groupId)) + .willReturn( + GroupMemberCapsuleOpenStatusDtoFixture.groupMemberCapsuleOpenStatusDto(memberId, + size)); + + //when + List groupMemberCapsuleOpenStatus = groupCapsuleService.findGroupMemberCapsuleOpenStatus( + memberId, capsuleId, groupId); + + //then + assertThat(groupMemberCapsuleOpenStatus).isNotEmpty(); + } }