diff --git a/src/main/java/com/somemore/auth/oauth/handler/success/CustomOAuthSuccessHandler.java b/src/main/java/com/somemore/auth/oauth/handler/success/CustomOAuthSuccessHandler.java index b090b8db4..01d478ac6 100644 --- a/src/main/java/com/somemore/auth/oauth/handler/success/CustomOAuthSuccessHandler.java +++ b/src/main/java/com/somemore/auth/oauth/handler/success/CustomOAuthSuccessHandler.java @@ -6,7 +6,7 @@ import com.somemore.auth.oauth.OAuthProvider; import com.somemore.auth.oauth.naver.service.query.ProcessNaverOAuthUserService; import com.somemore.auth.redirect.RedirectUseCase; -import com.somemore.volunteer.usecase.FindVolunteerIdUseCase; +import com.somemore.volunteer.usecase.VolunteerQueryUseCase; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -26,7 +26,7 @@ public class CustomOAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final ProcessNaverOAuthUserService processNaverOAuthService; - private final FindVolunteerIdUseCase findVolunteerIdUseCase; + private final VolunteerQueryUseCase volunteerQueryUseCase; private final GenerateTokensOnLoginUseCase generateTokensOnLoginUseCase; private final CookieUseCase cookieUseCase; private final RedirectUseCase redirectUseCase; @@ -45,7 +45,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo } } - UUID volunteerId = findVolunteerIdUseCase.findVolunteerIdByOAuthId(oAuthId); + UUID volunteerId = volunteerQueryUseCase.getVolunteerIdByOAuthId(oAuthId); EncodedToken accessToken = generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken(volunteerId); cookieUseCase.setAccessToken(response, accessToken.value()); diff --git a/src/main/java/com/somemore/domains/VolunteerManagement.java b/src/main/java/com/somemore/domains/VolunteerManagement.java deleted file mode 100644 index 0b0085bd4..000000000 --- a/src/main/java/com/somemore/domains/VolunteerManagement.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.somemore.domains; - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -import java.util.UUID; - -@Getter -@Setter -@Entity -@Table(name = "Volunteer_management") -public class VolunteerManagement { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - @Column(name = "apply_status", nullable = false, length = 20) - private String applyStatus; - - @ColumnDefault("0") - @Column(name = "attended", nullable = false) - private Boolean attended = false; - -} \ No newline at end of file diff --git a/src/main/java/com/somemore/facade/validator/VolunteerDetailAccessValidator.java b/src/main/java/com/somemore/facade/validator/VolunteerDetailAccessValidator.java new file mode 100644 index 000000000..9d7a5601f --- /dev/null +++ b/src/main/java/com/somemore/facade/validator/VolunteerDetailAccessValidator.java @@ -0,0 +1,8 @@ +package com.somemore.facade.validator; + +import java.util.UUID; + +public interface VolunteerDetailAccessValidator { + + void validateByCenterId(UUID centerId, UUID targetVolunteerId); +} diff --git a/src/main/java/com/somemore/facade/validator/VolunteerDetailAccessValidatorImpl.java b/src/main/java/com/somemore/facade/validator/VolunteerDetailAccessValidatorImpl.java new file mode 100644 index 000000000..218d598ec --- /dev/null +++ b/src/main/java/com/somemore/facade/validator/VolunteerDetailAccessValidatorImpl.java @@ -0,0 +1,35 @@ +package com.somemore.facade.validator; + +import com.somemore.global.exception.BadRequestException; +import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase; +import com.somemore.volunteerApply.usecase.VolunteerApplyQueryUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_VOLUNTEER_DETAIL; + +@Component +@RequiredArgsConstructor +public class VolunteerDetailAccessValidatorImpl implements VolunteerDetailAccessValidator { + + private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; + private final VolunteerApplyQueryUseCase volunteerApplyQueryUseCase; + + /** + * 기관 ID를 기반으로 완료되지 않은 모집글들의 ID를 조회하고, + * 해당 모집글들에 연관된 봉사자들의 ID 목록에 타겟 봉사자 ID가 포함되어 있는지 검증. + */ + public void validateByCenterId(UUID centerId, UUID targetVolunteerId) { + List allNotCompletedIdsByCenterId = recruitBoardQueryUseCase.getNotCompletedIdsByCenterIds(centerId); + + List volunteerIdsByRecruitIds = volunteerApplyQueryUseCase.getVolunteerIdsByRecruitIds(allNotCompletedIdsByCenterId); + + volunteerIdsByRecruitIds.stream() + .filter(volunteerId -> volunteerId.equals(targetVolunteerId)) + .findAny() + .orElseThrow(() -> new BadRequestException(UNAUTHORIZED_VOLUNTEER_DETAIL)); + } +} \ No newline at end of file diff --git a/src/main/java/com/somemore/global/exception/BadRequestException.java b/src/main/java/com/somemore/global/exception/BadRequestException.java index 488546df3..1f0d08221 100644 --- a/src/main/java/com/somemore/global/exception/BadRequestException.java +++ b/src/main/java/com/somemore/global/exception/BadRequestException.java @@ -7,4 +7,8 @@ public class BadRequestException extends RuntimeException{ public BadRequestException(final String message) { super(message); } + + public BadRequestException(final ExceptionMessage message) { + super(message.getMessage()); + } } diff --git a/src/main/java/com/somemore/global/exception/ExceptionMessage.java b/src/main/java/com/somemore/global/exception/ExceptionMessage.java index 1aa5d8fdd..257147885 100644 --- a/src/main/java/com/somemore/global/exception/ExceptionMessage.java +++ b/src/main/java/com/somemore/global/exception/ExceptionMessage.java @@ -8,19 +8,21 @@ @Getter public enum ExceptionMessage { - NOT_EXISTS_CENTER("존재하지 않는 기관 입니다."), - NOT_EXISTS_COMMUNITY_BOARD("존재하지 않는 게시글 입니다."), + NOT_EXISTS_CENTER("존재하지 않는 기관입니다."), + NOT_EXISTS_COMMUNITY_BOARD("존재하지 않는 게시글입니다."), UNAUTHORIZED_COMMUNITY_BOARD("해당 게시글에 권한이 없습니다."), NOT_EXISTS_COMMUNITY_COMMENT("존재하지 않는 댓글 입니다."), UNAUTHORIZED_COMMUNITY_COMMENT("해당 댓글에 권한이 없습니다."), NOT_EXISTS_LOCATION("존재하지 않는 위치 ID 입니다."), - NOT_EXISTS_RECRUIT_BOARD("존재하지 않는 봉사 모집글 ID 입니다."), + NOT_EXISTS_RECRUIT_BOARD("존재하지 않는 봉사 모집글입니다."), UNAUTHORIZED_RECRUIT_BOARD("해당 봉사 모집글에 권한이 없습니다."), UPLOAD_FAILED("파일 업로드에 실패했습니다."), INVALID_FILE_TYPE("지원하지 않는 파일 형식입니다."), FILE_SIZE_EXCEEDED("파일 크기가 허용된 한도를 초과했습니다."), EMPTY_FILE("파일이 존재하지 않습니다."), INSTANTIATION_NOT_ALLOWED("인스턴스화 할 수 없는 클래스 입니다."), + NOT_EXISTS_VOLUNTEER("존재하지 않는 봉사자입니다."), + UNAUTHORIZED_VOLUNTEER_DETAIL("해당 봉사자의 상세 정보 조회 권한이 없습니다."), CANNOT_CANCEL_DELETED_INTEREST_CENTER("이미 삭제된 관심 기관은 취소할 수 없습니다."), DUPLICATE_INTEREST_CENTER("이미 관심 표시한 기관입니다.") ; diff --git a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java index e4082d1f2..8ca512d22 100644 --- a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java +++ b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java @@ -6,9 +6,12 @@ import com.somemore.recruitboard.repository.mapper.RecruitBoardWithLocation; import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; + import java.util.List; + import java.util.Optional; import java.util.UUID; + import org.springframework.data.domain.Page; public interface RecruitBoardRepository { @@ -26,4 +29,6 @@ public interface RecruitBoardRepository { Page findAllNearby(RecruitBoardNearByCondition condition); Page findAllByCenterId(UUID centerId, RecruitBoardSearchCondition condition); + + List findNotCompletedIdsByCenterId(UUID centerId); } diff --git a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java index 966ece64b..67d22e617 100644 --- a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java @@ -14,6 +14,9 @@ import com.somemore.location.utils.GeoUtils; import com.somemore.recruitboard.domain.QRecruitBoard; import com.somemore.recruitboard.domain.RecruitBoard; + +import java.util.List; + import com.somemore.recruitboard.domain.RecruitStatus; import com.somemore.recruitboard.domain.VolunteerType; import com.somemore.recruitboard.repository.mapper.RecruitBoardDetail; @@ -21,9 +24,10 @@ import com.somemore.recruitboard.repository.mapper.RecruitBoardWithLocation; import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -import java.util.List; + import java.util.Optional; import java.util.UUID; + import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; @@ -54,25 +58,40 @@ public Optional findById(Long id) { QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; RecruitBoard result = queryFactory - .selectFrom(recruitBoard) - .where(isNotDeleted().and(idEq(id))) - .fetchOne(); + .selectFrom(recruitBoard) + .where(isNotDeleted().and(idEq(id))) + .fetchOne(); return Optional.ofNullable(result); } + @Override + public List findNotCompletedIdsByCenterId(UUID centerId) { + QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; + + return queryFactory + .select(recruitBoard.id) + .from(recruitBoard) + .where( + recruitBoard.centerId.eq(centerId) + .and(isNotCompleted()) + .and(isNotDeleted()) + ) + .fetch(); + } + @Override public Optional findWithLocationById(Long id) { QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; QLocation location = QLocation.location; return Optional.ofNullable( - queryFactory.select( - getRecruitBoardWithLocationConstructorExpression(recruitBoard, location)) - .from(recruitBoard) - .join(location).on(recruitBoard.locationId.eq(location.id)) - .where(isNotDeleted().and(idEq(id))) - .fetchOne()); + queryFactory.select( + getRecruitBoardWithLocationConstructorExpression(recruitBoard, location)) + .from(recruitBoard) + .join(location).on(recruitBoard.locationId.eq(location.id)) + .where(isNotDeleted().and(idEq(id))) + .fetchOne()); } @Override @@ -82,26 +101,26 @@ public Page findAllWithCenter(RecruitBoardSearchConditio Pageable pageable = condition.pageable(); BooleanExpression predicate = isNotDeleted() - .and(keywordEq(condition.keyword())) - .and(volunteerTypeEq(condition.type())) - .and(regionEq(condition.region())) - .and(admittedEq(condition.admitted())) - .and(statusEq(condition.status())); + .and(keywordEq(condition.keyword())) + .and(volunteerTypeEq(condition.type())) + .and(regionEq(condition.region())) + .and(admittedEq(condition.admitted())) + .and(statusEq(condition.status())); List content = queryFactory - .select(getRecruitBoardWithCenterConstructorExpression(recruitBoard, center)) - .from(recruitBoard) - .where(predicate) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(toOrderSpecifiers(pageable.getSort())) - .fetch(); + .select(getRecruitBoardWithCenterConstructorExpression(recruitBoard, center)) + .from(recruitBoard) + .where(predicate) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .fetch(); JPAQuery countQuery = queryFactory - .select(recruitBoard.count()) - .from(recruitBoard) - .where(predicate); + .select(recruitBoard.count()) + .from(recruitBoard) + .where(predicate); return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } @@ -115,56 +134,56 @@ public Page findAllNearby(RecruitBoardNearByCondition condit Pageable pageable = condition.pageable(); BooleanExpression predicate = isNotDeleted() - .and(locationBetween(condition)) - .and(keywordEq(condition.keyword())); + .and(locationBetween(condition)) + .and(keywordEq(condition.keyword())); List content = queryFactory - .select(getRecruitBoardDetailConstructorExpression(recruitBoard, location, center)) - .from(recruitBoard) - .join(location).on(recruitBoard.locationId.eq(location.id)) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .where(predicate) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(toOrderSpecifiers(pageable.getSort())) - .fetch(); + .select(getRecruitBoardDetailConstructorExpression(recruitBoard, location, center)) + .from(recruitBoard) + .join(location).on(recruitBoard.locationId.eq(location.id)) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .where(predicate) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .fetch(); JPAQuery countQuery = queryFactory - .select(recruitBoard.count()) - .from(recruitBoard) - .join(location).on(recruitBoard.locationId.eq(location.id)) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .where(predicate); + .select(recruitBoard.count()) + .from(recruitBoard) + .join(location).on(recruitBoard.locationId.eq(location.id)) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .where(predicate); return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } @Override public Page findAllByCenterId(UUID centerId, - RecruitBoardSearchCondition condition) { + RecruitBoardSearchCondition condition) { QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; Pageable pageable = condition.pageable(); BooleanExpression predicate = isNotDeleted() - .and(centerIdEq(centerId)) - .and(keywordEq(condition.keyword())) - .and(volunteerTypeEq(condition.type())) - .and(regionEq(condition.region())) - .and(admittedEq(condition.admitted())) - .and(statusEq(condition.status())); + .and(centerIdEq(centerId)) + .and(keywordEq(condition.keyword())) + .and(volunteerTypeEq(condition.type())) + .and(regionEq(condition.region())) + .and(admittedEq(condition.admitted())) + .and(statusEq(condition.status())); List content = queryFactory - .selectFrom(recruitBoard) - .where(predicate) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(toOrderSpecifiers(pageable.getSort())) - .fetch(); + .selectFrom(recruitBoard) + .where(predicate) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .fetch(); JPAQuery countQuery = queryFactory - .select(recruitBoard.count()) - .from(recruitBoard) - .where(predicate); + .select(recruitBoard.count()) + .from(recruitBoard) + .where(predicate); return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } @@ -181,26 +200,31 @@ private BooleanExpression isNotDeleted() { return recruitBoard.deleted.eq(false); } + private BooleanExpression isNotCompleted() { + QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; + return recruitBoard.recruitStatus.in(RecruitStatus.RECRUITING, RecruitStatus.CLOSED); + } + private BooleanExpression keywordEq(String keyword) { return StringUtils.isNotBlank(keyword) - ? recruitBoard.title.containsIgnoreCase( - keyword) : null; + ? recruitBoard.title.containsIgnoreCase( + keyword) : null; } private BooleanExpression volunteerTypeEq(VolunteerType type) { return type != null ? recruitBoard.recruitmentInfo.volunteerType.eq(type) - : null; + : null; } private BooleanExpression regionEq(String region) { return StringUtils.isNotBlank(region) - ? recruitBoard.recruitmentInfo.region.eq( - region) : null; + ? recruitBoard.recruitmentInfo.region.eq( + region) : null; } private BooleanExpression admittedEq(Boolean admitted) { return admitted != null ? recruitBoard.recruitmentInfo.admitted.eq(admitted) - : null; + : null; } private BooleanExpression statusEq(RecruitStatus status) { @@ -209,9 +233,9 @@ private BooleanExpression statusEq(RecruitStatus status) { private BooleanExpression locationBetween(RecruitBoardNearByCondition condition) { double[] coordinates = GeoUtils.calculateMaxMinCoordinates( - condition.latitude(), - condition.longitude(), - condition.radius()); + condition.latitude(), + condition.longitude(), + condition.radius()); double minLatitude = coordinates[0]; double minLongitude = coordinates[1]; @@ -219,47 +243,47 @@ private BooleanExpression locationBetween(RecruitBoardNearByCondition condition) double maxLongitude = coordinates[3]; return location.latitude.between(minLatitude, maxLatitude) - .and(location.longitude.between(minLongitude, maxLongitude)); + .and(location.longitude.between(minLongitude, maxLongitude)); } private OrderSpecifier[] toOrderSpecifiers(Sort sort) { QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; return sort.stream() - .map(order -> { - String property = order.getProperty(); - - if ("created_at".equals(property)) { - return order.isAscending() - ? recruitBoard.createdAt.asc() - : recruitBoard.createdAt.desc(); - } else if ("volunteer_start_date_time".equals(property)) { - return order.isAscending() - ? recruitBoard.recruitmentInfo.volunteerStartDateTime.asc() - : recruitBoard.recruitmentInfo.volunteerStartDateTime.desc(); - } else { - throw new IllegalStateException("Invalid sort property: " + property); - } - }) - .toArray(OrderSpecifier[]::new); + .map(order -> { + String property = order.getProperty(); + + if ("created_at".equals(property)) { + return order.isAscending() + ? recruitBoard.createdAt.asc() + : recruitBoard.createdAt.desc(); + } else if ("volunteer_start_date_time".equals(property)) { + return order.isAscending() + ? recruitBoard.recruitmentInfo.volunteerStartDateTime.asc() + : recruitBoard.recruitmentInfo.volunteerStartDateTime.desc(); + } else { + throw new IllegalStateException("Invalid sort property: " + property); + } + }) + .toArray(OrderSpecifier[]::new); } private static ConstructorExpression getRecruitBoardWithCenterConstructorExpression( - QRecruitBoard recruitBoard, QCenter center) { + QRecruitBoard recruitBoard, QCenter center) { return Projections.constructor(RecruitBoardWithCenter.class, - recruitBoard, center.name); + recruitBoard, center.name); } private static ConstructorExpression getRecruitBoardWithLocationConstructorExpression( - QRecruitBoard recruitBoard, QLocation location) { + QRecruitBoard recruitBoard, QLocation location) { return Projections.constructor(RecruitBoardWithLocation.class, - recruitBoard, location.address, location.latitude, location.longitude); + recruitBoard, location.address, location.latitude, location.longitude); } private static ConstructorExpression getRecruitBoardDetailConstructorExpression( - QRecruitBoard recruitBoard, QLocation location, QCenter center) { + QRecruitBoard recruitBoard, QLocation location, QCenter center) { return Projections.constructor(RecruitBoardDetail.class, - recruitBoard, location.address, location.latitude, location.longitude, center.name); + recruitBoard, location.address, location.latitude, location.longitude, center.name); } } diff --git a/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java b/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java index 112cce4a9..15047157c 100644 --- a/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java +++ b/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java @@ -1,13 +1,8 @@ package com.somemore.recruitboard.service.query; -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD; - import com.somemore.center.usecase.query.CenterQueryUseCase; import com.somemore.global.exception.BadRequestException; import com.somemore.recruitboard.domain.RecruitBoard; -import com.somemore.recruitboard.repository.mapper.RecruitBoardDetail; -import com.somemore.recruitboard.repository.mapper.RecruitBoardWithCenter; -import com.somemore.recruitboard.repository.mapper.RecruitBoardWithLocation; import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; @@ -15,13 +10,20 @@ import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; import com.somemore.recruitboard.dto.response.RecruitBoardWithLocationResponseDto; import com.somemore.recruitboard.repository.RecruitBoardRepository; +import com.somemore.recruitboard.repository.mapper.RecruitBoardDetail; +import com.somemore.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.recruitboard.repository.mapper.RecruitBoardWithLocation; import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase; -import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD; + @RequiredArgsConstructor @Transactional(readOnly = true) @Service @@ -39,38 +41,43 @@ public RecruitBoardResponseDto getById(Long id) { @Override public RecruitBoardWithLocationResponseDto getWithLocationById(Long id) { RecruitBoardWithLocation recruitBoardWithLocation = recruitBoardRepository.findWithLocationById( - id).orElseThrow( - () -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage()) + id).orElseThrow( + () -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage()) ); return RecruitBoardWithLocationResponseDto.from(recruitBoardWithLocation); } @Override public Page getAllWithCenter( - RecruitBoardSearchCondition condition) { + RecruitBoardSearchCondition condition) { Page boards = recruitBoardRepository.findAllWithCenter(condition); return boards.map(RecruitBoardWithCenterResponseDto::from); } @Override public Page getRecruitBoardsNearby( - RecruitBoardNearByCondition condition) { + RecruitBoardNearByCondition condition) { Page boards = recruitBoardRepository.findAllNearby(condition); return boards.map(RecruitBoardDetailResponseDto::from); } @Override public Page getRecruitBoardsByCenterId(UUID centerId, - RecruitBoardSearchCondition condition) { + RecruitBoardSearchCondition condition) { centerQueryUseCase.validateCenterExists(centerId); Page boards = recruitBoardRepository.findAllByCenterId(centerId, condition); return boards.map(RecruitBoardResponseDto::from); } + @Override + public List getNotCompletedIdsByCenterIds(UUID centerId) { + return recruitBoardRepository.findNotCompletedIdsByCenterId(centerId); + } + private RecruitBoard getRecruitBoard(Long id) { return recruitBoardRepository.findById(id).orElseThrow( - () -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage()) + () -> new BadRequestException(NOT_EXISTS_RECRUIT_BOARD.getMessage()) ); } } diff --git a/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java b/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java index 708df7097..4ce52f9b6 100644 --- a/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java +++ b/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java @@ -6,9 +6,11 @@ import com.somemore.recruitboard.dto.response.RecruitBoardResponseDto; import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; import com.somemore.recruitboard.dto.response.RecruitBoardWithLocationResponseDto; -import java.util.UUID; import org.springframework.data.domain.Page; +import java.util.List; +import java.util.UUID; + public interface RecruitBoardQueryUseCase { RecruitBoardResponseDto getById(Long id); @@ -18,8 +20,11 @@ public interface RecruitBoardQueryUseCase { Page getAllWithCenter(RecruitBoardSearchCondition condition); Page getRecruitBoardsNearby( - RecruitBoardNearByCondition condition); + RecruitBoardNearByCondition condition); Page getRecruitBoardsByCenterId(UUID centerId, - RecruitBoardSearchCondition condition); + RecruitBoardSearchCondition condition); + + List getNotCompletedIdsByCenterIds(UUID centerId); + } diff --git a/src/main/java/com/somemore/volunteer/controller/VolunteerQueryController.java b/src/main/java/com/somemore/volunteer/controller/VolunteerQueryController.java new file mode 100644 index 000000000..47787d9c9 --- /dev/null +++ b/src/main/java/com/somemore/volunteer/controller/VolunteerQueryController.java @@ -0,0 +1,60 @@ +package com.somemore.volunteer.controller; + +import com.somemore.global.common.response.ApiResponse; +import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.usecase.VolunteerQueryUseCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +@RestController +@Slf4j +@RequiredArgsConstructor +@Tag(name = "Find Volunteer", description = "Find Volunteer") +public class VolunteerQueryController { + + private final VolunteerQueryUseCase volunteerQueryUseCase; + + @GetMapping("/profile/me") + @Operation(summary = "본인 상세 프로필 조회", description = "현재 로그인된 사용자의 상세 프로필을 조회합니다.") + public ApiResponse getMyProfile( + @AuthenticationPrincipal String volunteerId) { + + return ApiResponse.ok( + 200, + volunteerQueryUseCase.getMyProfile(UUID.fromString(volunteerId)), + "프로필 조회 성공"); + } + + @GetMapping("/profile/{volunteerId}") + @Operation(summary = "타인 프로필 조회", description = "특정 봉사자의 프로필을 조회합니다. 상세 정보는 포함되지 않습니다.") + public ApiResponse getVolunteerProfile( + @PathVariable UUID volunteerId) { + + return ApiResponse.ok( + 200, + volunteerQueryUseCase.getVolunteerProfile(volunteerId), + "프로필 조회 성공" + ); + } + + @GetMapping("/profile/{volunteerId}/detailed") + @Operation(summary = "타인 상세 프로필 조회", description = "기관이 작성한 모집 글에 지원한 봉사자의 상세 프로필을 조회합니다.") + public ApiResponse getVolunteerDetailedProfile( + @PathVariable UUID volunteerId, + @AuthenticationPrincipal String centerId) { + + return ApiResponse.ok( + 200, + volunteerQueryUseCase.getVolunteerDetailedProfile(volunteerId, UUID.fromString(centerId)), + "프로필 조회 성공" + ); + } +} diff --git a/src/main/java/com/somemore/volunteer/domain/VolunteerDetail.java b/src/main/java/com/somemore/volunteer/domain/VolunteerDetail.java index 9f9321a29..f1eb33ce6 100644 --- a/src/main/java/com/somemore/volunteer/domain/VolunteerDetail.java +++ b/src/main/java/com/somemore/volunteer/domain/VolunteerDetail.java @@ -1,5 +1,6 @@ package com.somemore.volunteer.domain; +import com.somemore.global.common.BaseEntity; import com.somemore.volunteer.dto.request.VolunteerRegisterRequestDto; import jakarta.persistence.*; import lombok.AccessLevel; @@ -13,7 +14,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Table(name = "volunteer_detail") -public class VolunteerDetail { +public class VolunteerDetail extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/somemore/volunteer/dto/response/VolunteerResponseDto.java b/src/main/java/com/somemore/volunteer/dto/response/VolunteerResponseDto.java new file mode 100644 index 000000000..37725f14c --- /dev/null +++ b/src/main/java/com/somemore/volunteer/dto/response/VolunteerResponseDto.java @@ -0,0 +1,71 @@ +package com.somemore.volunteer.dto.response; + +import com.somemore.volunteer.domain.Volunteer; +import com.somemore.volunteer.domain.VolunteerDetail; + +public record VolunteerResponseDto( + String volunteerId, + String nickname, + String imgUrl, + String introduce, + String tier, + Integer totalVolunteerHours, + Integer totalVolunteerCount, + VolunteerDetailResponseDto volunteerDetailResponseDto +) { + + public static VolunteerResponseDto from( + Volunteer volunteer, + VolunteerDetail volunteerDetail + ) { + return new VolunteerResponseDto( + volunteer.getId().toString(), + volunteer.getNickname(), + volunteer.getImgUrl(), + volunteer.getIntroduce(), + volunteer.getTier().name(), + volunteer.getTotalVolunteerHours(), + volunteer.getTotalVolunteerCount(), + VolunteerDetailResponseDto.from(volunteerDetail) + ); + } + + public static VolunteerResponseDto from( + Volunteer volunteer + ) { + return new VolunteerResponseDto( + volunteer.getId().toString(), + volunteer.getNickname(), + volunteer.getImgUrl(), + volunteer.getIntroduce(), + volunteer.getTier().name(), + volunteer.getTotalVolunteerHours(), + volunteer.getTotalVolunteerCount(), + null + ); + } + + private record VolunteerDetailResponseDto( + String name, + String email, + String gender, + String birthDate, + String contactNumber + ) { + public static VolunteerDetailResponseDto from( + VolunteerDetail volunteerDetail + ) { + return new VolunteerDetailResponseDto( + volunteerDetail.getName(), + volunteerDetail.getEmail(), + volunteerDetail.getGender().name(), + volunteerDetail.getBirthDate(), + volunteerDetail.getContactNumber() + ); + } + } + + +} + + diff --git a/src/main/java/com/somemore/volunteer/repository/VolunteerDetailJpaRepository.java b/src/main/java/com/somemore/volunteer/repository/VolunteerDetailJpaRepository.java new file mode 100644 index 000000000..9c333f15c --- /dev/null +++ b/src/main/java/com/somemore/volunteer/repository/VolunteerDetailJpaRepository.java @@ -0,0 +1,7 @@ +package com.somemore.volunteer.repository; + +import com.somemore.volunteer.domain.VolunteerDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VolunteerDetailJpaRepository extends JpaRepository { +} diff --git a/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepository.java b/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepository.java index 82e8f96f7..2ce9cb40a 100644 --- a/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepository.java +++ b/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepository.java @@ -1,11 +1,13 @@ package com.somemore.volunteer.repository; import com.somemore.volunteer.domain.VolunteerDetail; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import java.util.Optional; import java.util.UUID; -public interface VolunteerDetailRepository extends JpaRepository { +@Repository +public interface VolunteerDetailRepository { + VolunteerDetail save(VolunteerDetail volunteerDetail); Optional findByVolunteerId(UUID volunteerId); } diff --git a/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepositoryImpl.java b/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepositoryImpl.java new file mode 100644 index 000000000..2bdf71c2e --- /dev/null +++ b/src/main/java/com/somemore/volunteer/repository/VolunteerDetailRepositoryImpl.java @@ -0,0 +1,47 @@ +package com.somemore.volunteer.repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.somemore.volunteer.domain.QVolunteerDetail; +import com.somemore.volunteer.domain.VolunteerDetail; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@RequiredArgsConstructor +@Repository +public class VolunteerDetailRepositoryImpl implements VolunteerDetailRepository { + + private final VolunteerDetailJpaRepository volunteerDetailJpaRepository; + private final JPAQueryFactory queryFactory; + + private final QVolunteerDetail volunteerDetail = QVolunteerDetail.volunteerDetail; + + @Override + public VolunteerDetail save(VolunteerDetail volunteerDetail) { + return volunteerDetailJpaRepository.save(volunteerDetail); + } + + @Override + public Optional findByVolunteerId(UUID volunteerId) { + return findOne(volunteerDetail.volunteerId.eq(volunteerId)); + } + + private Optional findOne(BooleanExpression condition) { + VolunteerDetail result = queryFactory + .selectFrom(volunteerDetail) + .where( + condition, + isNotDeleted() + ) + .fetchOne(); + + return Optional.ofNullable(result); + } + + private BooleanExpression isNotDeleted() { + return volunteerDetail.deleted.isFalse(); + } +} diff --git a/src/main/java/com/somemore/volunteer/repository/VolunteerJpaRepository.java b/src/main/java/com/somemore/volunteer/repository/VolunteerJpaRepository.java index f7261b45d..362a63daa 100644 --- a/src/main/java/com/somemore/volunteer/repository/VolunteerJpaRepository.java +++ b/src/main/java/com/somemore/volunteer/repository/VolunteerJpaRepository.java @@ -4,7 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; +import java.util.UUID; public interface VolunteerJpaRepository extends JpaRepository { - Optional findByOauthId(String oauthId); } diff --git a/src/main/java/com/somemore/volunteer/repository/VolunteerRepository.java b/src/main/java/com/somemore/volunteer/repository/VolunteerRepository.java index 03c29cad3..364db842b 100644 --- a/src/main/java/com/somemore/volunteer/repository/VolunteerRepository.java +++ b/src/main/java/com/somemore/volunteer/repository/VolunteerRepository.java @@ -9,7 +9,8 @@ @Repository public interface VolunteerRepository { Volunteer save(Volunteer volunteer); - String findNicknameById(UUID id); + Optional findById(UUID id); Optional findByOauthId(String oauthId); + String findNicknameById(UUID id); void deleteAllInBatch(); } diff --git a/src/main/java/com/somemore/volunteer/repository/VolunteerRepositoryImpl.java b/src/main/java/com/somemore/volunteer/repository/VolunteerRepositoryImpl.java index b47f5afbc..87a6ef8f6 100644 --- a/src/main/java/com/somemore/volunteer/repository/VolunteerRepositoryImpl.java +++ b/src/main/java/com/somemore/volunteer/repository/VolunteerRepositoryImpl.java @@ -1,5 +1,7 @@ package com.somemore.volunteer.repository; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import com.somemore.volunteer.domain.QVolunteer; import com.somemore.volunteer.domain.Volunteer; @@ -8,37 +10,71 @@ import java.util.Optional; import java.util.UUID; +import java.util.function.Function; @RequiredArgsConstructor @Repository -public class VolunteerRepositoryImpl implements VolunteerRepository{ +public class VolunteerRepositoryImpl implements VolunteerRepository { private final VolunteerJpaRepository volunteerJpaRepository; private final JPAQueryFactory queryFactory; + private final QVolunteer volunteer = QVolunteer.volunteer; + @Override public Volunteer save(Volunteer volunteer) { return volunteerJpaRepository.save(volunteer); } @Override - public String findNicknameById(UUID id) { - QVolunteer volunteer = QVolunteer.volunteer; - - return queryFactory - .select(volunteer.nickname) - .from(volunteer) - .where(volunteer.id.eq(id)) - .fetchOne(); + public Optional findById(UUID id) { + return findOne(volunteer.id.eq(id)); } @Override public Optional findByOauthId(String oauthId) { - return volunteerJpaRepository.findByOauthId(oauthId); + return findOne(volunteer.oauthId.eq(oauthId)); + } + + @Override + public String findNicknameById(UUID id) { + return findDynamicField(id, volunteer.nickname) + .orElse(null); } @Override public void deleteAllInBatch() { volunteerJpaRepository.deleteAllInBatch(); } -} + + private Optional findOne(BooleanExpression condition) { + + return Optional.ofNullable( + queryFactory + .selectFrom(volunteer) + .where( + condition, + isNotDeleted() + ) + .fetchOne() + ); + } + + private Optional findDynamicField(UUID id, Path field) { + + return Optional.ofNullable( + queryFactory + .select(field) + .from(volunteer) + .where( + volunteer.id.eq(id), + isNotDeleted() + ) + .fetchOne() + ); + } + + private BooleanExpression isNotDeleted() { + return volunteer.deleted.isFalse(); + } +} \ No newline at end of file diff --git a/src/main/java/com/somemore/volunteer/service/FindVolunteerIdService.java b/src/main/java/com/somemore/volunteer/service/FindVolunteerIdService.java deleted file mode 100644 index eab4b0d2b..000000000 --- a/src/main/java/com/somemore/volunteer/service/FindVolunteerIdService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.somemore.volunteer.service; - -import com.somemore.volunteer.repository.VolunteerRepository; -import com.somemore.volunteer.usecase.FindVolunteerIdUseCase; -import jakarta.persistence.EntityNotFoundException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.UUID; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class FindVolunteerIdService implements FindVolunteerIdUseCase { - - private final VolunteerRepository volunteerRepository; - - @Override - public UUID findVolunteerIdByOAuthId(String oAuthId) { - return volunteerRepository.findByOauthId(oAuthId) - .orElseThrow(EntityNotFoundException::new) - .getId(); - } - - @Override - public String getNicknameById(UUID id) { - return volunteerRepository.findNicknameById(id); - } -} diff --git a/src/main/java/com/somemore/volunteer/service/RegisterVolunteerService.java b/src/main/java/com/somemore/volunteer/service/RegisterVolunteerService.java index 8feb930c1..e27a8c077 100644 --- a/src/main/java/com/somemore/volunteer/service/RegisterVolunteerService.java +++ b/src/main/java/com/somemore/volunteer/service/RegisterVolunteerService.java @@ -3,6 +3,7 @@ import com.somemore.volunteer.domain.Volunteer; import com.somemore.volunteer.domain.VolunteerDetail; import com.somemore.volunteer.dto.request.VolunteerRegisterRequestDto; +import com.somemore.volunteer.repository.VolunteerDetailJpaRepository; import com.somemore.volunteer.repository.VolunteerDetailRepository; import com.somemore.volunteer.repository.VolunteerRepository; import com.somemore.volunteer.usecase.RegisterVolunteerUseCase; diff --git a/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java b/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java new file mode 100644 index 000000000..1bb761f10 --- /dev/null +++ b/src/main/java/com/somemore/volunteer/service/VolunteerQueryService.java @@ -0,0 +1,85 @@ +package com.somemore.volunteer.service; + +import com.somemore.facade.validator.VolunteerDetailAccessValidatorImpl; +import com.somemore.global.exception.BadRequestException; +import com.somemore.volunteer.domain.Volunteer; +import com.somemore.volunteer.domain.VolunteerDetail; +import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.repository.VolunteerDetailRepository; +import com.somemore.volunteer.repository.VolunteerRepository; +import com.somemore.volunteer.usecase.VolunteerQueryUseCase; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class VolunteerQueryService implements VolunteerQueryUseCase { + + private final VolunteerRepository volunteerRepository; + private final VolunteerDetailRepository volunteerDetailRepository; + private final VolunteerDetailAccessValidatorImpl volunteerDetailAccessValidatorImpl; + + @Override + public VolunteerResponseDto getMyProfile(UUID volunteerId) { + + return VolunteerResponseDto.from( + findVolunteer(volunteerId), + findVolunteerDetail(volunteerId) + ); + } + + @Override + public VolunteerResponseDto getVolunteerProfile(UUID volunteerId) { + + return VolunteerResponseDto.from( + findVolunteer(volunteerId) + ); + } + + @Override + public VolunteerResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId) { + volunteerDetailAccessValidatorImpl.validateByCenterId(centerId, volunteerId); + + return VolunteerResponseDto.from( + findVolunteer(volunteerId), + findVolunteerDetail(volunteerId) + ); + } + + @Override + public UUID getVolunteerIdByOAuthId(String oAuthId) { + return volunteerRepository.findByOauthId(oAuthId) + .orElseThrow(() -> new BadRequestException(NOT_EXISTS_VOLUNTEER)) + .getId(); + } + + @Override + public String getNicknameById(UUID id) { + String nickname = volunteerRepository.findNicknameById(id); + + if (nickname == null || nickname.isBlank()) { + throw new BadRequestException(NOT_EXISTS_VOLUNTEER); + } + + return nickname; + } + + private Volunteer findVolunteer(UUID volunteerId) { + return volunteerRepository.findById(volunteerId) + .orElseThrow(() -> new BadRequestException(NOT_EXISTS_VOLUNTEER)); + + } + + private VolunteerDetail findVolunteerDetail(UUID volunteerId) { + return volunteerDetailRepository.findByVolunteerId(volunteerId) + .orElseThrow(() -> new BadRequestException(NOT_EXISTS_VOLUNTEER)); + } +} diff --git a/src/main/java/com/somemore/volunteer/usecase/FindVolunteerIdUseCase.java b/src/main/java/com/somemore/volunteer/usecase/FindVolunteerIdUseCase.java deleted file mode 100644 index 47050b561..000000000 --- a/src/main/java/com/somemore/volunteer/usecase/FindVolunteerIdUseCase.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.somemore.volunteer.usecase; - -import java.util.UUID; - -public interface FindVolunteerIdUseCase { - UUID findVolunteerIdByOAuthId(String oAuthId); - String getNicknameById(UUID id); -} diff --git a/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java b/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java new file mode 100644 index 000000000..e41b69be6 --- /dev/null +++ b/src/main/java/com/somemore/volunteer/usecase/VolunteerQueryUseCase.java @@ -0,0 +1,18 @@ +package com.somemore.volunteer.usecase; + +import com.somemore.volunteer.dto.response.VolunteerResponseDto; + +import java.util.UUID; + +public interface VolunteerQueryUseCase { + + VolunteerResponseDto getMyProfile(UUID volunteerId); + + VolunteerResponseDto getVolunteerProfile(UUID volunteerId); + + VolunteerResponseDto getVolunteerDetailedProfile(UUID volunteerId, UUID centerId); + + UUID getVolunteerIdByOAuthId(String oAuthId); + + String getNicknameById(UUID id); +} diff --git a/src/main/java/com/somemore/volunteerApply/domain/ApplyStatus.java b/src/main/java/com/somemore/volunteerApply/domain/ApplyStatus.java new file mode 100644 index 000000000..eddb7b073 --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/domain/ApplyStatus.java @@ -0,0 +1,7 @@ +package com.somemore.volunteerApply.domain; + +public enum ApplyStatus { + WAITING, + APPROVED, + REJECTED +} diff --git a/src/main/java/com/somemore/volunteerApply/domain/VolunteerApply.java b/src/main/java/com/somemore/volunteerApply/domain/VolunteerApply.java new file mode 100644 index 000000000..da51dff6d --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/domain/VolunteerApply.java @@ -0,0 +1,44 @@ +package com.somemore.volunteerApply.domain; + +import com.somemore.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "volunteer_apply") +public class VolunteerApply extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "volunteer_id", nullable = false, columnDefinition = "BINARY(16)") + private UUID volunteerId; + + @Column(name = "recruit_board_id", nullable = false) + private Long recruitBoardId; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false, length = 20) + private ApplyStatus status; + + @Column(name = "attended", nullable = false) + private Boolean attended; + + @Builder + public VolunteerApply(UUID volunteerId, Long recruitBoardId, ApplyStatus status, Boolean attended) { + this.volunteerId = volunteerId; + this.recruitBoardId = recruitBoardId; + this.status = status != null ? status : ApplyStatus.WAITING; + this.attended = attended != null ? attended : false; + } +} + +// TODO 상태 업데이트 메서드들을 만들고 빌더에서 status를 변경 불가하도록 \ No newline at end of file diff --git a/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyJpaRepository.java b/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyJpaRepository.java new file mode 100644 index 000000000..1b51573ec --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyJpaRepository.java @@ -0,0 +1,7 @@ +package com.somemore.volunteerApply.repository; + +import com.somemore.volunteerApply.domain.VolunteerApply; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VolunteerApplyJpaRepository extends JpaRepository { +} diff --git a/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyRepository.java b/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyRepository.java new file mode 100644 index 000000000..05e55486a --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyRepository.java @@ -0,0 +1,17 @@ +package com.somemore.volunteerApply.repository; + +import com.somemore.volunteerApply.domain.VolunteerApply; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface VolunteerApplyRepository { + + VolunteerApply save(VolunteerApply volunteerApply); + Optional findById(Long id); + List findVolunteerIdsByRecruitIds(List recruitIds); + Page findAllByRecruitId(Long recruitId, Pageable pageable); +} diff --git a/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyRepositoryImpl.java b/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyRepositoryImpl.java new file mode 100644 index 000000000..ed3733d02 --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/repository/VolunteerApplyRepositoryImpl.java @@ -0,0 +1,115 @@ +package com.somemore.volunteerApply.repository; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.somemore.volunteerApply.domain.QVolunteerApply; +import com.somemore.volunteerApply.domain.VolunteerApply; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@RequiredArgsConstructor +@Repository +public class VolunteerApplyRepositoryImpl implements VolunteerApplyRepository { + + private final VolunteerApplyJpaRepository volunteerApplyJpaRepository; + private final JPAQueryFactory queryFactory; + + private final QVolunteerApply volunteerApply = QVolunteerApply.volunteerApply; + + @Override + public VolunteerApply save(VolunteerApply volunteerApply) { + return volunteerApplyJpaRepository.save(volunteerApply); + } + + @Override + public Optional findById(Long id) { + return findOne(volunteerApply.id.eq(id)); + } + + @Override + public List findVolunteerIdsByRecruitIds(List recruitIds) { + + BooleanExpression exp = volunteerApply.recruitBoardId + .in(recruitIds) + .and(isNotDeleted()); + + return queryFactory + .select(volunteerApply.volunteerId) + .from(volunteerApply) + .where(exp) + .fetch(); + } + + @Override + public Page findAllByRecruitId(Long recruitId, Pageable pageable) { + + BooleanExpression exp = volunteerApply.recruitBoardId + .eq(recruitId) + .and(isNotDeleted()); + + List content = queryFactory + .selectFrom(volunteerApply) + .where(exp) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + return new PageImpl<>( + content, + pageable, + getCount(exp) + ); + } + + private Long getCount(BooleanExpression exp) { + return queryFactory + .select(volunteerApply.count()) + .from(volunteerApply) + .where(exp) + .fetchOne(); + } + + private Optional findOne(BooleanExpression condition) { + + return Optional.ofNullable( + queryFactory + .selectFrom(volunteerApply) + .where( + condition, + isNotDeleted() + ) + .fetchOne() + ); + } + + private BooleanExpression isNotDeleted() { + return volunteerApply.deleted.isFalse(); + } + + private OrderSpecifier[] toOrderSpecifiers(Sort sort) { + + return sort.stream() + .map(order -> { + String property = order.getProperty(); + + if ("created_at".equals(property)) { + return order.isAscending() + ? volunteerApply.createdAt.asc() + : volunteerApply.createdAt.desc(); + } else { + throw new IllegalStateException("Invalid sort property: " + property); + } + }) + .toArray(OrderSpecifier[]::new); + } +} diff --git a/src/main/java/com/somemore/volunteerApply/service/VolunteerApplyQueryService.java b/src/main/java/com/somemore/volunteerApply/service/VolunteerApplyQueryService.java new file mode 100644 index 000000000..5b36013cc --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/service/VolunteerApplyQueryService.java @@ -0,0 +1,25 @@ +package com.somemore.volunteerApply.service; + +import com.somemore.volunteerApply.repository.VolunteerApplyRepository; +import com.somemore.volunteerApply.usecase.VolunteerApplyQueryUseCase; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class VolunteerApplyQueryService implements VolunteerApplyQueryUseCase { + + private final VolunteerApplyRepository volunteerApplyRepository; + @Override + public List getVolunteerIdsByRecruitIds(List recruitIds) { + + return volunteerApplyRepository.findVolunteerIdsByRecruitIds(recruitIds); + } +} diff --git a/src/main/java/com/somemore/volunteerApply/usecase/VolunteerApplyQueryUseCase.java b/src/main/java/com/somemore/volunteerApply/usecase/VolunteerApplyQueryUseCase.java new file mode 100644 index 000000000..e1795ef83 --- /dev/null +++ b/src/main/java/com/somemore/volunteerApply/usecase/VolunteerApplyQueryUseCase.java @@ -0,0 +1,9 @@ +package com.somemore.volunteerApply.usecase; + +import java.util.List; +import java.util.UUID; + +public interface VolunteerApplyQueryUseCase { + + List getVolunteerIdsByRecruitIds(List recruitIds); +} diff --git a/src/test/java/com/somemore/IntegrationTestSupport.java b/src/test/java/com/somemore/IntegrationTestSupport.java index cba699570..842b0d708 100644 --- a/src/test/java/com/somemore/IntegrationTestSupport.java +++ b/src/test/java/com/somemore/IntegrationTestSupport.java @@ -1,10 +1,12 @@ package com.somemore; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @ActiveProfiles("test") @SpringBootTest +@AutoConfigureMockMvc public abstract class IntegrationTestSupport { } diff --git a/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java index 904b231ea..55bd28c5d 100644 --- a/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java +++ b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java @@ -84,7 +84,7 @@ void getCenterProfile_NotFound() throws Exception { .andDo(print()) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.status").value("400")) - .andExpect(jsonPath("$.detail").value("존재하지 않는 기관 입니다.")); + .andExpect(jsonPath("$.detail").value("존재하지 않는 기관입니다.")); verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(nonExistentCenterId); } diff --git a/src/test/java/com/somemore/common/fixture/RecruitBoardFixture.java b/src/test/java/com/somemore/common/fixture/RecruitBoardFixture.java index 1ca16287b..d9af6099e 100644 --- a/src/test/java/com/somemore/common/fixture/RecruitBoardFixture.java +++ b/src/test/java/com/somemore/common/fixture/RecruitBoardFixture.java @@ -1,14 +1,17 @@ package com.somemore.common.fixture; -import static com.somemore.common.fixture.LocalDateTimeFixture.createStartDateTime; -import static com.somemore.recruitboard.domain.VolunteerType.OTHER; - import com.somemore.recruitboard.domain.RecruitBoard; +import com.somemore.recruitboard.domain.RecruitStatus; import com.somemore.recruitboard.domain.RecruitmentInfo; import com.somemore.recruitboard.domain.VolunteerType; + +import java.lang.reflect.Field; import java.time.LocalDateTime; import java.util.UUID; +import static com.somemore.common.fixture.LocalDateTimeFixture.createStartDateTime; +import static com.somemore.recruitboard.domain.VolunteerType.OTHER; + public class RecruitBoardFixture { public static final String REGION = "경기"; @@ -28,231 +31,266 @@ private RecruitBoardFixture() { public static RecruitBoard createRecruitBoard() { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(UUID.randomUUID()) - .locationId(LOCATION_ID) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(UUID.randomUUID()) + .locationId(LOCATION_ID) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(String title) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(UUID.randomUUID()) - .locationId(LOCATION_ID) - .title(title) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(UUID.randomUUID()) + .locationId(LOCATION_ID) + .title(title) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(String title, UUID centerId, Long locationId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(centerId) - .locationId(locationId) - .title(title) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(centerId) + .locationId(locationId) + .title(title) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(String title, UUID centerId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(centerId) - .locationId(LOCATION_ID) - .title(title) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(centerId) + .locationId(LOCATION_ID) + .title(title) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(VolunteerType type, UUID centerId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(type) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(type) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(centerId) - .locationId(LOCATION_ID) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(centerId) + .locationId(LOCATION_ID) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(Boolean admitted, UUID centerId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(admitted) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(admitted) + .build(); return RecruitBoard.builder() - .centerId(centerId) - .locationId(LOCATION_ID) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(centerId) + .locationId(LOCATION_ID) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(Long locationId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(UUID.randomUUID()) - .locationId(locationId) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(UUID.randomUUID()) + .locationId(locationId) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(UUID centerId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(centerId) - .locationId(LOCATION_ID) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(centerId) + .locationId(LOCATION_ID) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(UUID centerId, Long locationId) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(centerId) - .locationId(locationId) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(centerId) + .locationId(locationId) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(String region, VolunteerType volunteerType) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(region) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(volunteerType) - .admitted(ADMITTED) - .build(); + .region(region) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(volunteerType) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(UUID.randomUUID()) - .locationId(LOCATION_ID) - .title(TITLE) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(UUID.randomUUID()) + .locationId(LOCATION_ID) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); } public static RecruitBoard createRecruitBoard(Long locationId, String title) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() - .region(REGION) - .recruitmentCount(RECRUITMENT_COUNT) - .volunteerStartDateTime(START_DATE_TIME) - .volunteerEndDateTime(END_DATE_TIME) - .volunteerType(VOLUNTEER_TYPE) - .admitted(ADMITTED) - .build(); + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); return RecruitBoard.builder() - .centerId(UUID.randomUUID()) - .locationId(locationId) - .title(title) - .content(CONTENT) - .imgUrl(IMG_URL) - .recruitmentInfo(recruitmentInfo) - .build(); + .centerId(UUID.randomUUID()) + .locationId(locationId) + .title(title) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); + } + + public static RecruitBoard createCompletedRecruitBoard() { + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region(REGION) + .recruitmentCount(RECRUITMENT_COUNT) + .volunteerStartDateTime(START_DATE_TIME) + .volunteerEndDateTime(END_DATE_TIME) + .volunteerType(VOLUNTEER_TYPE) + .admitted(ADMITTED) + .build(); + + RecruitBoard recruitBoard = RecruitBoard.builder() + .centerId(UUID.randomUUID()) + .locationId(LOCATION_ID) + .title(TITLE) + .content(CONTENT) + .imgUrl(IMG_URL) + .recruitmentInfo(recruitmentInfo) + .build(); + + setRecruitStatusCompleted(recruitBoard); + + return recruitBoard; + } + + private static void setRecruitStatusCompleted(RecruitBoard recruitBoard) { + try { + Field recruitStatusField = RecruitBoard.class.getDeclaredField("recruitStatus"); + recruitStatusField.setAccessible(true); // private 필드 접근 가능 설정 + recruitStatusField.set(recruitBoard, RecruitStatus.COMPLETED); // 필드 값 설정 + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("리플렉션으로 recruitStatus를 설정하는 것에 실패했습니다", e); + } } } diff --git a/src/test/java/com/somemore/facade/validator/VolunteerDetailAccessValidatorImplTest.java b/src/test/java/com/somemore/facade/validator/VolunteerDetailAccessValidatorImplTest.java new file mode 100644 index 000000000..d5031792e --- /dev/null +++ b/src/test/java/com/somemore/facade/validator/VolunteerDetailAccessValidatorImplTest.java @@ -0,0 +1,67 @@ +package com.somemore.facade.validator; + +import com.somemore.IntegrationTestSupport; +import com.somemore.global.exception.BadRequestException; +import com.somemore.recruitboard.domain.RecruitBoard; +import com.somemore.recruitboard.repository.RecruitBoardRepository; +import com.somemore.volunteerApply.domain.ApplyStatus; +import com.somemore.volunteerApply.domain.VolunteerApply; +import com.somemore.volunteerApply.repository.VolunteerApplyRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_VOLUNTEER_DETAIL; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@Transactional +class VolunteerDetailAccessValidatorImplTest extends IntegrationTestSupport { + + @Autowired + private VolunteerDetailAccessValidatorImpl volunteerDetailAccessValidatorImpl; + + @Autowired + private RecruitBoardRepository recruitBoardRepository; + + @Autowired + private VolunteerApplyRepository volunteerApplyRepository; + + @DisplayName("타겟 봉사자가 모집 글과 연관된 경우 검증 통과, 연관되지 않은 경우 예외를 던진다") + @Test + void validateByCenterIdThrowsExceptionWhenVolunteerNotLinked() { + // given + UUID centerId = UUID.randomUUID(); + + RecruitBoard recruitBoard = createRecruitBoard(centerId); + recruitBoardRepository.save(recruitBoard); + + UUID unrelatedVolunteerId = UUID.randomUUID(); + UUID relatedVolunteerId = UUID.randomUUID(); + + VolunteerApply volunteerApply = createVolunteerApply(recruitBoard.getId(), relatedVolunteerId); + volunteerApplyRepository.save(volunteerApply); + + // when + // then + assertThatCode(() -> volunteerDetailAccessValidatorImpl.validateByCenterId(centerId, relatedVolunteerId)) + .doesNotThrowAnyException(); + + assertThatThrownBy(() -> volunteerDetailAccessValidatorImpl.validateByCenterId(centerId, unrelatedVolunteerId)) + .isInstanceOf(BadRequestException.class) + .hasMessage(UNAUTHORIZED_VOLUNTEER_DETAIL.getMessage()); + } + + private VolunteerApply createVolunteerApply(Long recruitId, UUID volunteerId) { + return VolunteerApply.builder() + .recruitBoardId(recruitId) + .volunteerId(volunteerId) + .status(ApplyStatus.WAITING) + .attended(false) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java b/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java index a7e7bbeee..4546f5dd7 100644 --- a/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java +++ b/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java @@ -87,7 +87,7 @@ void registerInterestCenter_WithInvalidCenterId_ShouldThrowException() { }); //then - assertEquals("존재하지 않는 기관 입니다.", exception.getMessage()); + assertEquals("존재하지 않는 기관입니다.", exception.getMessage()); } private Center createCenter() { diff --git a/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java b/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java index 146393248..5fe2b731b 100644 --- a/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java +++ b/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java @@ -1,13 +1,5 @@ package com.somemore.recruitboard.repository; -import static com.somemore.common.fixture.CenterFixture.createCenter; -import static com.somemore.common.fixture.LocalDateTimeFixture.createCurrentDateTime; -import static com.somemore.common.fixture.LocationFixture.createLocation; -import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; -import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED; -import static com.somemore.recruitboard.domain.VolunteerType.ADMINISTRATIVE_SUPPORT; -import static org.assertj.core.api.Assertions.assertThat; - import com.somemore.IntegrationTestSupport; import com.somemore.center.domain.Center; import com.somemore.center.repository.CenterRepository; @@ -16,16 +8,11 @@ import com.somemore.recruitboard.domain.RecruitBoard; import com.somemore.recruitboard.domain.RecruitStatus; import com.somemore.recruitboard.domain.VolunteerType; +import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.recruitboard.repository.mapper.RecruitBoardDetail; import com.somemore.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.recruitboard.repository.mapper.RecruitBoardWithLocation; -import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,6 +23,21 @@ import org.springframework.data.domain.Sort; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static com.somemore.common.fixture.CenterFixture.createCenter; +import static com.somemore.common.fixture.LocalDateTimeFixture.createCurrentDateTime; +import static com.somemore.common.fixture.LocationFixture.createLocation; +import static com.somemore.common.fixture.RecruitBoardFixture.createCompletedRecruitBoard; +import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; +import static com.somemore.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.recruitboard.domain.VolunteerType.ADMINISTRATIVE_SUPPORT; +import static org.assertj.core.api.Assertions.assertThat; + @Transactional class RecruitBoardRepositoryImplTest extends IntegrationTestSupport { @@ -96,7 +98,7 @@ void findWithCenterByIdWithNotExistId() { // when Optional findOne = recruitBoardRepository.findWithLocationById( - deletedRecruitBoard.getId()); + deletedRecruitBoard.getId()); // then assertThat(findOne).isEmpty(); @@ -109,8 +111,8 @@ void findAllWithCenterWithoutCriteria() { Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .pageable(pageable) - .build(); + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllWithCenter(condition); @@ -123,7 +125,7 @@ void findAllWithCenterWithoutCriteria() { assertThat(result.getContent()).hasSize(5); assertThat(result.getContent().get(0).recruitBoard().getCreatedAt()) - .isAfterOrEqualTo(result.getContent().get(1).recruitBoard().getCreatedAt()); + .isAfterOrEqualTo(result.getContent().get(1).recruitBoard().getCreatedAt()); } @DisplayName("키워드로 조회할 수 있다") @@ -138,9 +140,9 @@ void findAllWithCenterByKeyword() { Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .keyword(keyword) - .pageable(pageable) - .build(); + .keyword(keyword) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllWithCenter(condition); @@ -153,7 +155,7 @@ void findAllWithCenterByKeyword() { assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().getFirst().recruitBoard().getTitle()) - .isEqualTo("키워드 조회 제목"); + .isEqualTo("키워드 조회 제목"); } @DisplayName("봉사활동 유형으로 조회할 수 있다") @@ -169,9 +171,9 @@ void findAllWithCenterByType() { Pageable pageable = getPageable(); VolunteerType type = ADMINISTRATIVE_SUPPORT; RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .type(type) - .pageable(pageable) - .build(); + .type(type) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllWithCenter(condition); @@ -184,7 +186,7 @@ void findAllWithCenterByType() { assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().getFirst().recruitBoard().getRecruitmentInfo() - .getVolunteerType()).isEqualTo(type); + .getVolunteerType()).isEqualTo(type); } @DisplayName("지역으로 조회할 수 있다") @@ -202,22 +204,56 @@ void findAllWithCenterByRegion() { Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .region(region) - .pageable(pageable) - .build(); + .region(region) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllWithCenter(condition); - // then - assertThat(result).isNotNull(); - assertThat(result.getTotalElements()).isEqualTo(1); - assertThat(result.getSize()).isEqualTo(5); - assertThat(result.getNumber()).isZero(); - assertThat(result.getContent()).hasSize(1); + } - assertThat(result.getContent().getFirst().recruitBoard().getRecruitmentInfo() - .getRegion()).isEqualTo(region); + @DisplayName("센터 ID로 완료되지 않은 모집 게시글들의 ID를 조회할 수 있다") + @Test + void findNotCompletedIdsByCenterIds() { + // given + UUID centerId = UUID.randomUUID(); + + RecruitBoard deletedRecruitingBoard = createRecruitBoard(centerId); + deletedRecruitingBoard.markAsDeleted(); + recruitBoardRepository.save(deletedRecruitingBoard); + + RecruitBoard recruitingBoard = createRecruitBoard(centerId); + recruitBoardRepository.save(recruitingBoard); + + RecruitBoard deletedClosedBoard = createRecruitBoard(centerId); + deletedClosedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); + deletedClosedBoard.markAsDeleted(); + recruitBoardRepository.save(deletedClosedBoard); + + RecruitBoard closedBoard = createRecruitBoard(centerId); + closedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); + recruitBoardRepository.save(closedBoard); + + RecruitBoard deletedCompletedRecruitBoard = createCompletedRecruitBoard(); + deletedCompletedRecruitBoard.markAsDeleted(); + recruitBoardRepository.save(deletedCompletedRecruitBoard); + + RecruitBoard completedRecruitBoard = createCompletedRecruitBoard(); + recruitBoardRepository.save(completedRecruitBoard); + + // when + List notCompletedBoardIds = recruitBoardRepository.findNotCompletedIdsByCenterId(centerId); + + // then + assertThat(notCompletedBoardIds) + .hasSize(2); + + assertThat(notCompletedBoardIds) + .doesNotContain(deletedRecruitingBoard.getId()) + .doesNotContain(deletedClosedBoard.getId()) + .doesNotContain(deletedCompletedRecruitBoard.getId()) + .doesNotContain(completedRecruitBoard.getId()); } @DisplayName("시간 인증 여부로 조회할 수 있다") @@ -234,9 +270,9 @@ void findAllWithCenterByAdmitted() { Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .admitted(false) - .pageable(pageable) - .build(); + .admitted(false) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllWithCenter(condition); @@ -249,7 +285,7 @@ void findAllWithCenterByAdmitted() { assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().getFirst().recruitBoard().getRecruitmentInfo() - .getAdmitted()).isFalse(); + .getAdmitted()).isFalse(); } @DisplayName("모집글 상태로 조회할 수 있다.") @@ -268,9 +304,9 @@ void findAllWithCenterByStatus() { Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .status(status) - .pageable(pageable) - .build(); + .status(status) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllWithCenter(condition); @@ -283,7 +319,7 @@ void findAllWithCenterByStatus() { assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().getFirst().recruitBoard().getRecruitStatus()).isEqualTo( - status); + status); } @DisplayName("위치 기반으로 반경 내에 모집글을 반환한다") @@ -293,11 +329,11 @@ void findAllNearByLocation() { Pageable pageable = getPageable(); RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() - .latitude(37.5935) - .longitude(126.9780) - .radius(5.0) - .pageable(pageable) - .build(); + .latitude(37.5935) + .longitude(126.9780) + .radius(5.0) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllNearby(condition); @@ -315,11 +351,11 @@ void findAllNearByLocation_noResult() { Pageable pageable = getPageable(); RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() - .latitude(37.6115) - .longitude(127.034) - .radius(5.0) - .pageable(pageable) - .build(); + .latitude(37.6115) + .longitude(127.034) + .radius(5.0) + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllNearby(condition); @@ -343,12 +379,12 @@ void findAllByCenterId() { Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .pageable(pageable) - .build(); + .pageable(pageable) + .build(); // when Page result = recruitBoardRepository.findAllByCenterId(center.getId(), - condition); + condition); // then assertThat(result).isNotEmpty(); @@ -363,12 +399,12 @@ void findAllByCenterIdWhenWrongCenterId() { UUID centerId = UUID.randomUUID(); Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .pageable(pageable) - .build(); + .pageable(pageable) + .build(); // when Page results = recruitBoardRepository.findAllByCenterId(centerId, - condition); + condition); // then assertThat(results).isEmpty(); diff --git a/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java b/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java index d42f6de9c..89ee5d8bd 100644 --- a/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java +++ b/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java @@ -1,7 +1,9 @@ package com.somemore.recruitboard.service.query; import static com.somemore.common.fixture.CenterFixture.createCenter; +import static com.somemore.common.fixture.LocalDateTimeFixture.createCurrentDateTime; import static com.somemore.common.fixture.LocationFixture.createLocation; +import static com.somemore.common.fixture.RecruitBoardFixture.createCompletedRecruitBoard; import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD; @@ -15,6 +17,7 @@ import com.somemore.location.domain.Location; import com.somemore.location.repository.LocationRepository; import com.somemore.recruitboard.domain.RecruitBoard; +import com.somemore.recruitboard.domain.RecruitStatus; import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; @@ -215,6 +218,49 @@ void getRecruitBoardsByCenterIdWhenNotExistsCenterId() { .hasMessage(NOT_EXISTS_CENTER.getMessage()); } + @DisplayName("센터 ID로 완료되지 않은 모집 게시글들의 ID를 조회할 수 있다") + @Test + void findNotCompletedIdsByCenterIds() { + // given + UUID centerId = UUID.randomUUID(); + + RecruitBoard deletedRecruitingBoard = createRecruitBoard(centerId); + deletedRecruitingBoard.markAsDeleted(); + recruitBoardRepository.save(deletedRecruitingBoard); + + RecruitBoard recruitingBoard = createRecruitBoard(centerId); + recruitBoardRepository.save(recruitingBoard); + + RecruitBoard deletedClosedBoard = createRecruitBoard(centerId); + deletedClosedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); + deletedClosedBoard.markAsDeleted(); + recruitBoardRepository.save(deletedClosedBoard); + + RecruitBoard closedBoard = createRecruitBoard(centerId); + closedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); + recruitBoardRepository.save(closedBoard); + + RecruitBoard deletedCompletedRecruitBoard = createCompletedRecruitBoard(); + deletedCompletedRecruitBoard.markAsDeleted(); + recruitBoardRepository.save(deletedCompletedRecruitBoard); + + RecruitBoard completedRecruitBoard = createCompletedRecruitBoard(); + recruitBoardRepository.save(completedRecruitBoard); + + // when + List notCompletedBoardIds = recruitBoardQueryService.getNotCompletedIdsByCenterIds(centerId); + + // then + assertThat(notCompletedBoardIds) + .hasSize(2); + + assertThat(notCompletedBoardIds) + .doesNotContain(deletedRecruitingBoard.getId()) + .doesNotContain(deletedClosedBoard.getId()) + .doesNotContain(deletedCompletedRecruitBoard.getId()) + .doesNotContain(completedRecruitBoard.getId()); + } + private Pageable getPageable() { Sort sort = Sort.by(Sort.Order.desc("created_at")); return PageRequest.of(0, 5, sort); diff --git a/src/test/java/com/somemore/volunteer/controller/VolunteerQueryControllerTest.java b/src/test/java/com/somemore/volunteer/controller/VolunteerQueryControllerTest.java new file mode 100644 index 000000000..5dc318dda --- /dev/null +++ b/src/test/java/com/somemore/volunteer/controller/VolunteerQueryControllerTest.java @@ -0,0 +1,62 @@ +package com.somemore.volunteer.controller; + +import com.somemore.ControllerTestSupport; +import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.usecase.VolunteerQueryUseCase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.UUID; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class VolunteerQueryControllerTest extends ControllerTestSupport { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private VolunteerQueryUseCase volunteerQueryUseCase; + + @Test + @DisplayName("타인의 프로필을 조회할 수 있다.") + void getVolunteerProfile() throws Exception { + // given + UUID volunteerId = UUID.randomUUID(); + VolunteerResponseDto responseDto = createMockVolunteerResponse(); + + given(volunteerQueryUseCase.getVolunteerProfile(volunteerId)).willReturn(responseDto); + + // when & then + mockMvc.perform(get("/profile/{volunteerId}", volunteerId) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.message").value("프로필 조회 성공")); + + verify(volunteerQueryUseCase, times(1)).getVolunteerProfile(volunteerId); + } + + private VolunteerResponseDto createMockVolunteerResponse() { + return new VolunteerResponseDto( + UUID.randomUUID().toString(), + "Test", + "http://example.com/image.jpg", + "Ima volunteer!", + "Red", + 100, + 10, + null + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/somemore/volunteer/controller/VolunteerSignControllerTest.java b/src/test/java/com/somemore/volunteer/controller/VolunteerSignControllerTest.java index 1af24e071..541f1d78c 100644 --- a/src/test/java/com/somemore/volunteer/controller/VolunteerSignControllerTest.java +++ b/src/test/java/com/somemore/volunteer/controller/VolunteerSignControllerTest.java @@ -1,11 +1,9 @@ package com.somemore.volunteer.controller; -import com.somemore.IntegrationTestSupport; +import com.somemore.ControllerTestSupport; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MockMvc; @@ -13,9 +11,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@SpringBootTest -@AutoConfigureMockMvc -class VolunteerSignControllerTest extends IntegrationTestSupport { +class VolunteerSignControllerTest extends ControllerTestSupport { @Autowired private MockMvc mockMvc; diff --git a/src/test/java/com/somemore/volunteer/repository/VolunteerDetailRepositoryImplTest.java b/src/test/java/com/somemore/volunteer/repository/VolunteerDetailRepositoryImplTest.java new file mode 100644 index 000000000..0f09c3b8a --- /dev/null +++ b/src/test/java/com/somemore/volunteer/repository/VolunteerDetailRepositoryImplTest.java @@ -0,0 +1,66 @@ +package com.somemore.volunteer.repository; + +import com.somemore.IntegrationTestSupport; +import com.somemore.volunteer.domain.Gender; +import com.somemore.volunteer.domain.VolunteerDetail; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class VolunteerDetailRepositoryTest extends IntegrationTestSupport { + + @Autowired + private VolunteerDetailRepository volunteerDetailRepository; + + @DisplayName("봉사자 ID로 봉사자 상세 정보를 조회한다.") + @Test + void findByVolunteerId() { + // given + UUID volunteerId = UUID.randomUUID(); + VolunteerDetail volunteerDetail = createVolunteerDetail(volunteerId); + + volunteerDetailRepository.save(volunteerDetail); + + // when + Optional foundDetail = volunteerDetailRepository.findByVolunteerId(volunteerId); + + // then + assertThat(foundDetail).isPresent(); + assertThat(foundDetail.get().getVolunteerId()).isEqualTo(volunteerId); + assertThat(foundDetail.get().getName()).isEqualTo(volunteerDetail.getName()); + } + + @DisplayName("봉사자 상세 정보를 저장한다.") + @Test + void saveVolunteerDetail() { + // given + UUID volunteerId = UUID.randomUUID(); + VolunteerDetail volunteerDetail = createVolunteerDetail(volunteerId); + + // when + VolunteerDetail savedDetail = volunteerDetailRepository.save(volunteerDetail); + + // then + assertThat(savedDetail).isNotNull(); + assertThat(savedDetail.getVolunteerId()).isEqualTo(volunteerId); + assertThat(savedDetail.getName()).isEqualTo(volunteerDetail.getName()); + } + + private VolunteerDetail createVolunteerDetail(UUID volunteerId) { + return VolunteerDetail.builder() + .volunteerId(volunteerId) + .name("making") + .email("making@example.com") + .gender(Gender.MALE) + .birthDate("1998-06-08") + .contactNumber("010-1234-5678") + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/somemore/volunteer/repository/VolunteerRepositoryTest.java b/src/test/java/com/somemore/volunteer/repository/VolunteerRepositoryTest.java index e9d0cf63c..c0f63d749 100644 --- a/src/test/java/com/somemore/volunteer/repository/VolunteerRepositoryTest.java +++ b/src/test/java/com/somemore/volunteer/repository/VolunteerRepositoryTest.java @@ -3,31 +3,77 @@ import com.somemore.IntegrationTestSupport; import com.somemore.auth.oauth.OAuthProvider; import com.somemore.volunteer.domain.Volunteer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; +import java.util.UUID; + import static org.assertj.core.api.Assertions.assertThat; @Transactional class VolunteerRepositoryTest extends IntegrationTestSupport { + @Autowired private VolunteerRepository volunteerRepository; - @DisplayName("봉사자의 id로 닉네임을 조회한다. (Repository)") - @Test - void findNicknameById() { - //given - String oAuthId = "example-oauth-id"; - Volunteer volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId); - + String oAuthId; + Volunteer volunteer; + + @BeforeEach + void setup() { + oAuthId = "example-oauth-id"; + volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId); volunteerRepository.save(volunteer); + } - //when + @DisplayName("봉사자의 id로 닉네임을 조회한다.") + @Test + void findNicknameById() { + // when String volunteerNickname = volunteerRepository.findNicknameById(volunteer.getId()); - //then + // then assertThat(volunteerNickname).isEqualTo(volunteer.getNickname()); } -} + + @DisplayName("올바르지 않은 봉사자의 id로 닉네임을 조회하면 null을 반환한다.") + @Test + void findNicknameByInvalidId() { + // given + UUID inValidVolunteerId = UUID.randomUUID(); + + // when + String volunteerNickname = volunteerRepository.findNicknameById(inValidVolunteerId); + + // then + assertThat(volunteerNickname).isNull(); + } + + @DisplayName("봉사자의 id로 봉사자 정보를 조회한다.") + @Test + void findById() { + // when + Optional foundVolunteer = volunteerRepository.findById(volunteer.getId()); + + // then + assertThat(foundVolunteer).isPresent(); + assertThat(foundVolunteer.get().getId()).isEqualTo(volunteer.getId()); + assertThat(foundVolunteer.get().getNickname()).isEqualTo(volunteer.getNickname()); + } + + @DisplayName("OAuth ID로 봉사자 정보를 조회한다.") + @Test + void findByOauthId() { + // when + Optional foundVolunteer = volunteerRepository.findByOauthId(oAuthId); + + // then + assertThat(foundVolunteer).isPresent(); + assertThat(foundVolunteer.get().getOauthId()).isEqualTo(oAuthId); + assertThat(foundVolunteer.get().getNickname()).isEqualTo(volunteer.getNickname()); + } +} \ No newline at end of file diff --git a/src/test/java/com/somemore/volunteer/service/FindVolunteerIdServiceTest.java b/src/test/java/com/somemore/volunteer/service/FindVolunteerIdServiceTest.java deleted file mode 100644 index 831348722..000000000 --- a/src/test/java/com/somemore/volunteer/service/FindVolunteerIdServiceTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.somemore.volunteer.service; - -import com.somemore.IntegrationTestSupport; -import com.somemore.auth.oauth.OAuthProvider; -import com.somemore.volunteer.domain.Volunteer; -import com.somemore.volunteer.repository.VolunteerRepository; -import jakarta.persistence.EntityNotFoundException; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class FindVolunteerIdServiceTest extends IntegrationTestSupport { - - @Autowired - private FindVolunteerIdService findVolunteerIdService; - - @Autowired - private VolunteerRepository volunteerRepository; - - @AfterEach - void tearDown() { - volunteerRepository.deleteAllInBatch(); - } - - @DisplayName("존재하는 OAuth ID로 봉사자 ID를 조회한다") - @Test - void findVolunteerId() { - // given - String oAuthId = "example-oauth-id"; - Volunteer volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId); - - volunteerRepository.save(volunteer); - - // when - UUID actualId = findVolunteerIdService.findVolunteerIdByOAuthId(oAuthId); - - // then - assertThat(actualId) - .isNotNull() - .isEqualTo(volunteer.getId()); - } - - @DisplayName("존재하지 않는 OAuth ID로 조회 시 예외를 던진다") - @Test - void throwExceptionWhenVolunteerNotFound() { - // given - String oAuthId = "non-existing-oauth-id"; - - // when - // then - assertThatThrownBy(() -> findVolunteerIdService.findVolunteerIdByOAuthId(oAuthId)) - .isInstanceOf(EntityNotFoundException.class); - } - - @DisplayName("봉사자의 id로 nickname을 조회한다.") - @Test - void getNicknameById() { - - //given - String oAuthId = "example-oauth-id"; - Volunteer volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId); - - volunteerRepository.save(volunteer); - - //when - String nickname = findVolunteerIdService.getNicknameById(volunteer.getId()); - - //then - assertThat(nickname).isEqualTo(volunteer.getNickname()); - } -} diff --git a/src/test/java/com/somemore/volunteer/service/RegisterVolunteerServiceTest.java b/src/test/java/com/somemore/volunteer/service/RegisterVolunteerServiceTest.java index 543718d3a..804744903 100644 --- a/src/test/java/com/somemore/volunteer/service/RegisterVolunteerServiceTest.java +++ b/src/test/java/com/somemore/volunteer/service/RegisterVolunteerServiceTest.java @@ -10,13 +10,14 @@ import com.somemore.volunteer.repository.VolunteerDetailRepository; import com.somemore.volunteer.repository.VolunteerRepository; import jakarta.persistence.EntityNotFoundException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; +@Transactional class RegisterVolunteerServiceTest extends IntegrationTestSupport { @Autowired @@ -28,12 +29,6 @@ class RegisterVolunteerServiceTest extends IntegrationTestSupport { @Autowired private VolunteerDetailRepository volunteerDetailRepository; - @AfterEach - void tearDown() { - volunteerRepository.deleteAllInBatch(); - volunteerDetailRepository.deleteAllInBatch(); - } - @DisplayName("봉사자와 상세 정보를 저장한다") @Test void registerVolunteer() { diff --git a/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java b/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java new file mode 100644 index 000000000..25fc6cbdb --- /dev/null +++ b/src/test/java/com/somemore/volunteer/service/VolunteerQueryServiceTest.java @@ -0,0 +1,171 @@ +package com.somemore.volunteer.service; + +import com.somemore.IntegrationTestSupport; +import com.somemore.auth.oauth.OAuthProvider; +import com.somemore.global.exception.BadRequestException; +import com.somemore.volunteer.domain.Volunteer; +import com.somemore.volunteer.domain.VolunteerDetail; +import com.somemore.volunteer.dto.request.VolunteerRegisterRequestDto; +import com.somemore.volunteer.dto.response.VolunteerResponseDto; +import com.somemore.volunteer.repository.VolunteerDetailRepository; +import com.somemore.volunteer.repository.VolunteerRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER; +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_VOLUNTEER_DETAIL; +import static com.somemore.volunteer.domain.Volunteer.createDefault; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@Transactional +class VolunteerQueryServiceTest extends IntegrationTestSupport { + + @Autowired + private VolunteerQueryService volunteerQueryService; + + @Autowired + private VolunteerRepository volunteerRepository; + + @Autowired + private VolunteerDetailRepository volunteerDetailRepository; + + final String oAuthId = "example-oauth-id"; + final OAuthProvider oAuthProvider = OAuthProvider.NAVER; + + + @DisplayName("존재하는 OAuth ID로 봉사자 ID를 조회한다") + @Test + void getVolunteerIdByOAuthId() { + // given + Volunteer volunteer = createDefault(oAuthProvider, oAuthId); + volunteerRepository.save(volunteer); + + // when + UUID actualId = volunteerQueryService.getVolunteerIdByOAuthId(oAuthId); + + // then + assertThat(actualId) + .isNotNull() + .isEqualTo(volunteer.getId()); + } + + @DisplayName("존재하지 않는 OAuth ID로 조회 시 예외를 던진다") + @Test + void getVolunteerIdByNonExistOAuthId() { + // when + // then + assertThatThrownBy(() -> volunteerQueryService.getVolunteerIdByOAuthId(oAuthId)) + .isInstanceOf(BadRequestException.class) + .hasMessage(NOT_EXISTS_VOLUNTEER.getMessage()); + } + + @DisplayName("봉사자의 id로 nickname을 조회한다.") + @Test + void getNicknameById() { + //given + Volunteer volunteer = createDefault(oAuthProvider, oAuthId); + volunteerRepository.save(volunteer); + + //when + String nickname = volunteerQueryService.getNicknameById(volunteer.getId()); + + //then + assertThat(nickname).isEqualTo(volunteer.getNickname()); + } + + @DisplayName("존재하지 않는 봉사자 ID로 닉네임 조회 시 예외를 던진다") + @Test + void throwExceptionWhenNicknameNotFound() { + // given + UUID volunteerId = UUID.randomUUID(); + + // when + // then + assertThatThrownBy(() -> volunteerQueryService.getNicknameById(volunteerId)) + .isInstanceOf(BadRequestException.class) + .hasMessage(NOT_EXISTS_VOLUNTEER.getMessage()); + } + + @DisplayName("내 프로필 조회 성공") + @Test + void getMyProfile() { + // given + Volunteer volunteer = createDefault(oAuthProvider, oAuthId); + volunteerRepository.save(volunteer); + UUID volunteerId = volunteer.getId(); + + VolunteerDetail volunteerDetail = createVolunteerDetail(volunteerId); + volunteerDetailRepository.save(volunteerDetail); + + // when + VolunteerResponseDto response = volunteerQueryService.getMyProfile(volunteerId); + + // then + assertThat(response).isNotNull(); + assertThat(response.volunteerId()).isEqualTo(volunteerId.toString()); + assertThat(response.nickname()).isEqualTo(volunteer.getNickname()); + } + + @DisplayName("봉사자 프로필 조회 성공") + @Test + void getVolunteerProfile() { + // given + Volunteer volunteer = createDefault(oAuthProvider, oAuthId); + volunteerRepository.save(volunteer); + UUID volunteerId = volunteer.getId(); + + VolunteerDetail volunteerDetail = createVolunteerDetail(volunteerId); + volunteerDetailRepository.save(volunteerDetail); + + // when + VolunteerResponseDto response = volunteerQueryService.getVolunteerProfile(volunteerId); + + // then + assertThat(response).isNotNull(); + assertThat(response.volunteerId()).isEqualTo(volunteerId.toString()); + assertThat(response.nickname()).isEqualTo(volunteer.getNickname()); + assertThat(response.volunteerDetailResponseDto()).isNull(); + } + + @DisplayName("권한이 없는 기관의 봉사자 상세 프로필 조회 실패") + @Test + void getVolunteerDetailedProfile() { + // given + UUID centerId = UUID.randomUUID(); + + Volunteer volunteer = createDefault(oAuthProvider, oAuthId); + volunteerRepository.save(volunteer); + UUID volunteerId = volunteer.getId(); + + VolunteerDetail volunteerDetail = createVolunteerDetail(volunteerId); + volunteerDetailRepository.save(volunteerDetail); + + // when + // then + assertThatThrownBy(() -> volunteerQueryService.getVolunteerDetailedProfile(volunteerId, centerId)) + .isInstanceOf(BadRequestException.class) + .hasMessage(UNAUTHORIZED_VOLUNTEER_DETAIL.getMessage()); + } + + private static VolunteerDetail createVolunteerDetail(UUID volunteerId) { + + VolunteerRegisterRequestDto volunteerRegisterRequestDto = + new VolunteerRegisterRequestDto( + OAuthProvider.NAVER, + "example-oauth-id", + "making", + "making@example.com", + "male", + "06-08", + "1998", + "010-1234-5678" + ); + + return VolunteerDetail.of(volunteerRegisterRequestDto, volunteerId); + } +} diff --git a/src/test/java/com/somemore/volunteerApply/repository/VolunteerApplyRepositoryImplTest.java b/src/test/java/com/somemore/volunteerApply/repository/VolunteerApplyRepositoryImplTest.java new file mode 100644 index 000000000..cb296420d --- /dev/null +++ b/src/test/java/com/somemore/volunteerApply/repository/VolunteerApplyRepositoryImplTest.java @@ -0,0 +1,104 @@ +package com.somemore.volunteerApply.repository; + +import com.somemore.IntegrationTestSupport; +import com.somemore.volunteerApply.domain.VolunteerApply; +import com.somemore.volunteerApply.domain.ApplyStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class VolunteerApplyRepositoryImplTest extends IntegrationTestSupport { + + @Autowired + private VolunteerApplyRepositoryImpl volunteerApplyRepository; + + @BeforeEach + void setUp() { + // Given + for (int i = 1; i <= 15; i++) { + VolunteerApply apply = VolunteerApply.builder() + .volunteerId(UUID.randomUUID()) + .recruitBoardId(1L) + .status(ApplyStatus.WAITING) + .attended(false) + .build(); + volunteerApplyRepository.save(apply); + } + + for (int i = 1; i <= 5; i++) { + VolunteerApply apply = VolunteerApply.builder() + .volunteerId(UUID.randomUUID()) + .recruitBoardId(2L) + .status(ApplyStatus.APPROVED) + .attended(true) + .build(); + volunteerApplyRepository.save(apply); + } + } + + @DisplayName("봉사 신청 저장 및 조회") + @Test + void saveAndFindById() { + // Given + VolunteerApply newApply = VolunteerApply.builder() + .volunteerId(UUID.randomUUID()) + .recruitBoardId(1L) + .status(ApplyStatus.APPROVED) + .attended(false) + .build(); + VolunteerApply savedApply = volunteerApplyRepository.save(newApply); + + // When + Optional foundApply = volunteerApplyRepository.findById(savedApply.getId()); + + // Then + assertThat(foundApply).isPresent(); + assertThat(foundApply.get().getId()).isEqualTo(savedApply.getId()); + assertThat(foundApply.get().getStatus()).isEqualTo(ApplyStatus.APPROVED); + } + + @DisplayName("모집글 ID 리스트로 봉사자 ID 리스트 조회") + @Test + void findVolunteerIdsByRecruitIds() { + // When + List volunteerIds = volunteerApplyRepository.findVolunteerIdsByRecruitIds(List.of(1L, 2L)); + + // Then + assertThat(volunteerIds).hasSize(20); + } + + @DisplayName("모집글 ID로 페이징된 봉사 신청 조회") + @Test + void findAllByRecruitId() { + // Given + PageRequest firstPage = PageRequest.of(0, 10, Sort.by(Sort.Order.asc("created_at"))); + PageRequest secondPage = PageRequest.of(1, 10, Sort.by(Sort.Order.asc("created_at"))); + + // When + Page firstPageResult = volunteerApplyRepository.findAllByRecruitId(1L, firstPage); + Page secondPageResult = volunteerApplyRepository.findAllByRecruitId(1L, secondPage); + + // Then + assertThat(firstPageResult.getContent()).hasSize(10); + assertThat(firstPageResult.getTotalElements()).isEqualTo(15); + assertThat(firstPageResult.getTotalPages()).isEqualTo(2); + assertThat(firstPageResult.hasNext()).isTrue(); + assertThat(firstPageResult.hasPrevious()).isFalse(); + + assertThat(secondPageResult.getContent()).hasSize(5); + assertThat(secondPageResult.hasNext()).isFalse(); + assertThat(secondPageResult.hasPrevious()).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/com/somemore/volunteerApply/service/VolunteerApplyQueryServiceTest.java b/src/test/java/com/somemore/volunteerApply/service/VolunteerApplyQueryServiceTest.java new file mode 100644 index 000000000..4c53a37cb --- /dev/null +++ b/src/test/java/com/somemore/volunteerApply/service/VolunteerApplyQueryServiceTest.java @@ -0,0 +1,57 @@ +package com.somemore.volunteerApply.service; + +import com.somemore.IntegrationTestSupport; +import com.somemore.volunteerApply.domain.VolunteerApply; +import com.somemore.volunteerApply.repository.VolunteerApplyRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class VolunteerApplyQueryServiceTest extends IntegrationTestSupport { + + @Autowired + private VolunteerApplyQueryService volunteerApplyQueryService; + + @Autowired + private VolunteerApplyRepository volunteerApplyRepository; + + @DisplayName("recruitIds로 봉사자 ID 리스트를 조회할 수 있다") + @Test + void getVolunteerIdsByRecruitIds() { + // Given + Long recruitId1 = 1L; + Long recruitId2 = 2L; + UUID volunteerId1 = UUID.randomUUID(); + UUID volunteerId2 = UUID.randomUUID(); + + VolunteerApply apply1 = createVolunteerApply(recruitId1, volunteerId1); + VolunteerApply apply2 = createVolunteerApply(recruitId2, volunteerId2); + + volunteerApplyRepository.save(apply1); + volunteerApplyRepository.save(apply2); + + // When + List volunteerIds = volunteerApplyQueryService.getVolunteerIdsByRecruitIds(List.of(recruitId1, recruitId2)); + + // Then + assertThat(volunteerIds) + .hasSize(2) + .containsExactlyInAnyOrder(volunteerId1, volunteerId2); + } + + private VolunteerApply createVolunteerApply(Long recruitId, UUID volunteerId) { + return VolunteerApply.builder() + .volunteerId(volunteerId) + .recruitBoardId(recruitId) + .status(null) + .attended(null) + .build(); + } +} \ No newline at end of file