diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java index 4026b7f2..bb4470f8 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java @@ -7,56 +7,57 @@ @Getter @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public enum ErrorStatus { - /** - * 204 NO_CONTENT - */ - NO_CONTENT_EXCEPTION("참여한 모임이 없습니다."), - - /** - * 400 BAD_REQUEST - */ - VALIDATION_EXCEPTION("CF-001"), - VALIDATION_REQUEST_MISSING_EXCEPTION("요청값이 입력되지 않았습니다."), - INVALID_INPUT_VALUE("요청값이 올바르지 않습니다."), - INVALID_INPUT_VALUE_FILTER("요청값 또는 토큰이 올바르지 않습니다."), - NOT_FOUND_MEETING("모임이 없습니다."), - NOT_FOUND_POST("존재하지 않는 게시글입니다."), - NOT_FOUND_USER("존재하지 않는 유저입니다."), - NOT_FOUND_COMMENT("존재하지 않는 댓글입니다."), - FULL_MEETING_CAPACITY("정원이 꽉 찼습니다."), - ALREADY_APPLIED_MEETING("이미 지원한 모임입니다."), - ALREADY_REPORTED_COMMENT("이미 신고한 댓글입니다."), - ALREADY_REPORTED_POST("이미 신고한 게시글입니다."), - NOT_IN_APPLY_PERIOD("모임 지원 기간이 아닙니다."), - MISSING_GENERATION_PART("내 프로필에서 기수/파트 정보를 입력해주세요."), - NOT_ACTIVE_GENERATION("활동 기수가 아닙니다."), - NOT_TARGET_PART("지원 가능한 파트가 아닙니다."), - NOT_FOUND_APPLY("신청상태가 아닌 모임입니다."), - ALREADY_PROCESSED_APPLY("이미 해당 상태로 처리된 신청 정보입니다."), - MAX_IMAGE_UPLOAD_EXCEEDED("이미지는 최대 10개까지만 업로드 가능합니다."), - LEADER_CANNOT_APPLY("모임장은 신청할 수 없습니다."), - CO_LEADER_CANNOT_APPLY("공동 모임장은 신청할 수 없습니다."), - LEADER_CANNOT_BE_CO_LEADER_APPLY("모임장은 공동 모임장이 될 수 없습니다."), - - /** - * 401 UNAUTHORIZED - */ - UNAUTHORIZED_TOKEN("유효하지 않은 토큰입니다."), - UNAUTHORIZED_USER("존재하지 않거나 유효하지 않은 유저입니다."), - - /** - * 403 FORBIDDEN - */ - FORBIDDEN_EXCEPTION("권한이 없습니다."), - - /** - * 500 SERVER_ERROR - */ - NOTIFICATION_SERVER_ERROR("알림 서버에 에러가 발생했습니다."), - CSV_ERROR("csv 처리 과정에 에러가 발생했습니다."), - S3_STORAGE_ERROR("s3 스토리지에 에러가 발생했습니다."), - INTERNAL_SERVER_ERROR("예상치 못한 서버 에러가 발생했습니다."); - - private final String errorCode; - -} \ No newline at end of file + /** + * 204 NO_CONTENT + */ + NO_CONTENT_EXCEPTION("참여한 모임이 없습니다."), + + /** + * 400 BAD_REQUEST + */ + VALIDATION_EXCEPTION("CF-001"), + VALIDATION_REQUEST_MISSING_EXCEPTION("요청값이 입력되지 않았습니다."), + INVALID_INPUT_VALUE("요청값이 올바르지 않습니다."), + INVALID_INPUT_VALUE_FILTER("요청값 또는 토큰이 올바르지 않습니다."), + NOT_FOUND_MEETING("모임이 없습니다."), + NOT_FOUND_POST("존재하지 않는 게시글입니다."), + NOT_FOUND_USER("존재하지 않는 유저입니다."), + NOT_FOUND_COMMENT("존재하지 않는 댓글입니다."), + FULL_MEETING_CAPACITY("정원이 꽉 찼습니다."), + ALREADY_APPLIED_MEETING("이미 지원한 모임입니다."), + ALREADY_REPORTED_COMMENT("이미 신고한 댓글입니다."), + ALREADY_REPORTED_POST("이미 신고한 게시글입니다."), + NOT_IN_APPLY_PERIOD("모임 지원 기간이 아닙니다."), + MISSING_GENERATION_PART("내 프로필에서 기수/파트 정보를 입력해주세요."), + NOT_ACTIVE_GENERATION("활동 기수가 아닙니다."), + NOT_TARGET_PART("지원 가능한 파트가 아닙니다."), + NOT_FOUND_APPLY("신청상태가 아닌 모임입니다."), + ALREADY_PROCESSED_APPLY("이미 해당 상태로 처리된 신청 정보입니다."), + MAX_IMAGE_UPLOAD_EXCEEDED("이미지는 최대 10개까지만 업로드 가능합니다."), + LEADER_CANNOT_APPLY("모임장은 신청할 수 없습니다."), + CO_LEADER_CANNOT_APPLY("공동 모임장은 신청할 수 없습니다."), + LEADER_CANNOT_BE_CO_LEADER_APPLY("모임장은 공동 모임장이 될 수 없습니다."), + NOT_ALLOW_MEETING_APPLY("허용되지 않는 모임 신청입니다."), + + /** + * 401 UNAUTHORIZED + */ + UNAUTHORIZED_TOKEN("유효하지 않은 토큰입니다."), + UNAUTHORIZED_USER("존재하지 않거나 유효하지 않은 유저입니다."), + + /** + * 403 FORBIDDEN + */ + FORBIDDEN_EXCEPTION("권한이 없습니다."), + + /** + * 500 SERVER_ERROR + */ + NOTIFICATION_SERVER_ERROR("알림 서버에 에러가 발생했습니다."), + CSV_ERROR("csv 처리 과정에 에러가 발생했습니다."), + S3_STORAGE_ERROR("s3 스토리지에 에러가 발생했습니다."), + INTERNAL_SERVER_ERROR("예상치 못한 서버 에러가 발생했습니다."); + + private final String errorCode; + +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java index 7d383c25..aa3d12e2 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Api.java @@ -1,16 +1,5 @@ package org.sopt.makers.crew.main.meeting.v2; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import jakarta.websocket.server.PathParam; - import java.security.Principal; import java.util.List; @@ -35,6 +24,17 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.websocket.server.PathParam; + @Tag(name = "모임") public interface MeetingV2Api { @Operation(summary = "플레이그라운드 마이페이지 내 모임 정보 조회") @@ -58,12 +58,22 @@ ResponseEntity createMeeting( @Valid @RequestBody MeetingV2CreateMeetingBodyDto requestBody, Principal principal); - @Operation(summary = "모임 지원") + @Operation(summary = "일반 모임 지원") + @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "지원 완료"), + @ApiResponse(responseCode = "400", description = + "\"모임이 없습니다\" or \"기수/파트를 설정해주세요\" or \"정원이 꽉찼습니다\" or \"활동 기수가 아닙니다\" " + + "or \"지원 가능한 파트가 아닙니다\" or \"지원 가능한 기간이 아닙니다\"", content = @Content),}) + ResponseEntity applyGeneralMeeting( + @RequestBody MeetingV2ApplyMeetingDto requestBody, + Principal principal); + + @Operation(hidden = true, summary = "행사 모임 지원") @ApiResponses(value = {@ApiResponse(responseCode = "201", description = "지원 완료"), @ApiResponse(responseCode = "400", description = "\"모임이 없습니다\" or \"기수/파트를 설정해주세요\" or \"정원이 꽉찼습니다\" or \"활동 기수가 아닙니다\" " + "or \"지원 가능한 파트가 아닙니다\" or \"지원 가능한 기간이 아닙니다\"", content = @Content),}) - ResponseEntity applyMeeting(@RequestBody MeetingV2ApplyMeetingDto requestBody, + ResponseEntity applyEventMeeting( + @RequestBody MeetingV2ApplyMeetingDto requestBody, Principal principal); @Operation(summary = "모임 지원 취소") diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java index 5fb75b3c..5d555f3b 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/MeetingV2Controller.java @@ -1,15 +1,10 @@ package org.sopt.makers.crew.main.meeting.v2; -import io.swagger.v3.oas.annotations.Parameter; -import jakarta.validation.Valid; - import java.security.Principal; import java.util.List; -import lombok.RequiredArgsConstructor; - -import org.sopt.makers.crew.main.global.util.UserUtil; import org.sopt.makers.crew.main.external.s3.service.S3Service; +import org.sopt.makers.crew.main.global.util.UserUtil; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesCsvQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingByOrgUserQueryDto; @@ -41,6 +36,10 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + @RestController @RequestMapping("/meeting/v2") @RequiredArgsConstructor @@ -78,11 +77,21 @@ public ResponseEntity createMeeting( @Override @PostMapping("/apply") - public ResponseEntity applyMeeting( + public ResponseEntity applyGeneralMeeting( + @Valid @RequestBody MeetingV2ApplyMeetingDto requestBody, + Principal principal) { + Integer userId = UserUtil.getUserId(principal); + return ResponseEntity.status(HttpStatus.CREATED) + .body(meetingV2Service.applyGeneralMeeting(requestBody, userId)); + } + + @PostMapping("${custom.paths.eventApply}") + public ResponseEntity applyEventMeeting( @Valid @RequestBody MeetingV2ApplyMeetingDto requestBody, Principal principal) { Integer userId = UserUtil.getUserId(principal); - return ResponseEntity.status(HttpStatus.CREATED).body(meetingV2Service.applyMeeting(requestBody, userId)); + return ResponseEntity.status(HttpStatus.CREATED) + .body(meetingV2Service.applyEventMeeting(requestBody, userId)); } @Override diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java index 9d254128..e902b6f4 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2Service.java @@ -26,7 +26,9 @@ MeetingV2GetAllMeetingByOrgUserDto getAllMeetingByOrgUser( MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBodyDto requestBody, Integer userId); - MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); + MeetingV2ApplyMeetingResponseDto applyGeneralMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); + + MeetingV2ApplyMeetingResponseDto applyEventMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId); void applyMeetingCancel(Integer meetingId, Integer userId); diff --git a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java index 38a01284..9bf1ccb5 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java +++ b/main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceImpl.java @@ -1,8 +1,8 @@ package org.sopt.makers.crew.main.meeting.v2.service; +import static org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus.*; import static org.sopt.makers.crew.main.global.constant.CrewConst.*; import static org.sopt.makers.crew.main.global.exception.ErrorStatus.*; -import static org.sopt.makers.crew.main.entity.apply.enums.EnApplyStatus.*; import java.io.FileOutputStream; import java.io.IOException; @@ -22,19 +22,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; - -import org.sopt.makers.crew.main.entity.meeting.CoLeader; -import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository; -import org.sopt.makers.crew.main.entity.meeting.CoLeaders; -import org.sopt.makers.crew.main.global.dto.MeetingResponseDto; -import org.sopt.makers.crew.main.global.exception.BadRequestException; -import org.sopt.makers.crew.main.global.exception.ServerException; -import org.sopt.makers.crew.main.global.pagination.dto.PageMetaDto; -import org.sopt.makers.crew.main.global.pagination.dto.PageOptionsDto; -import org.sopt.makers.crew.main.global.util.CustomPageable; -import org.sopt.makers.crew.main.global.util.Time; -import org.sopt.makers.crew.main.global.util.UserPartUtil; import org.sopt.makers.crew.main.entity.apply.Applies; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; @@ -43,8 +30,12 @@ import org.sopt.makers.crew.main.entity.comment.Comment; import org.sopt.makers.crew.main.entity.comment.CommentRepository; import org.sopt.makers.crew.main.entity.like.LikeRepository; +import org.sopt.makers.crew.main.entity.meeting.CoLeader; +import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository; +import org.sopt.makers.crew.main.entity.meeting.CoLeaders; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; +import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; import org.sopt.makers.crew.main.entity.post.Post; import org.sopt.makers.crew.main.entity.post.PostRepository; @@ -53,6 +44,14 @@ import org.sopt.makers.crew.main.entity.user.enums.UserPart; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; import org.sopt.makers.crew.main.external.s3.service.S3Service; +import org.sopt.makers.crew.main.global.dto.MeetingResponseDto; +import org.sopt.makers.crew.main.global.exception.BadRequestException; +import org.sopt.makers.crew.main.global.exception.ServerException; +import org.sopt.makers.crew.main.global.pagination.dto.PageMetaDto; +import org.sopt.makers.crew.main.global.pagination.dto.PageOptionsDto; +import org.sopt.makers.crew.main.global.util.CustomPageable; +import org.sopt.makers.crew.main.global.util.Time; +import org.sopt.makers.crew.main.global.util.UserPartUtil; import org.sopt.makers.crew.main.meeting.v2.dto.ApplyMapper; import org.sopt.makers.crew.main.meeting.v2.dto.MeetingMapper; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; @@ -81,6 +80,8 @@ import com.opencsv.CSVWriter; +import lombok.RequiredArgsConstructor; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -215,8 +216,36 @@ public MeetingV2CreateMeetingResponseDto createMeeting(MeetingV2CreateMeetingBod @Override @Transactional - public MeetingV2ApplyMeetingResponseDto applyMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId) { + public MeetingV2ApplyMeetingResponseDto applyGeneralMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId) { Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); + + validateMeetingCategoryNotEvent(meeting); + + User user = userRepository.findByIdOrThrow(userId); + CoLeaders coLeaders = new CoLeaders(coLeaderRepository.findAllByMeetingId(meeting.getId())); + + List applies = applyRepository.findAllByMeetingId(meeting.getId()); + + validateMeetingCapacity(meeting, applies); + validateUserAlreadyApplied(userId, applies); + validateApplyPeriod(meeting); + validateUserActivities(user); + validateUserJoinableParts(user, meeting); + coLeaders.validateCoLeader(meeting.getId(), user.getId()); + meeting.validateIsNotMeetingLeader(userId); + + Apply apply = applyMapper.toApplyEntity(requestBody, EnApplyType.APPLY, meeting, user, userId); + Apply savedApply = applyRepository.save(apply); + return MeetingV2ApplyMeetingResponseDto.of(savedApply.getId()); + } + + @Override + @Transactional + public MeetingV2ApplyMeetingResponseDto applyEventMeeting(MeetingV2ApplyMeetingDto requestBody, Integer userId) { + Meeting meeting = meetingRepository.findByIdOrThrow(requestBody.getMeetingId()); + + validateMeetingCategoryEvent(meeting); + User user = userRepository.findByIdOrThrow(userId); CoLeaders coLeaders = new CoLeaders(coLeaderRepository.findAllByMeetingId(meeting.getId())); @@ -477,6 +506,18 @@ private List filterUserActivities(User user, Meeting meeting) { return user.getActivities(); } + private void validateMeetingCategoryNotEvent(Meeting meeting) { + if (meeting.getCategory() == MeetingCategory.EVENT) { + throw new BadRequestException(NOT_IN_APPLY_PERIOD.getErrorCode()); + } + } + + private void validateMeetingCategoryEvent(Meeting meeting) { + if (meeting.getCategory() != MeetingCategory.EVENT) { + throw new BadRequestException(NOT_IN_APPLY_PERIOD.getErrorCode()); + } + } + private void validateMeetingCapacity(Meeting meeting, List applies) { List approvedApplies = applies.stream() .filter(apply -> EnApplyStatus.APPROVE.equals(apply.getStatus())) diff --git a/main/src/main/resources/application-dev.yml b/main/src/main/resources/application-dev.yml index 835f3e2c..9c73fe89 100644 --- a/main/src/main/resources/application-dev.yml +++ b/main/src/main/resources/application-dev.yml @@ -65,7 +65,7 @@ push-notification: push-server-url: ${DEV_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} + secret-key: ${NOTICE_SECRET_KEY} playground: server: @@ -94,4 +94,8 @@ management: prometheus: metrics: export: - enabled: true \ No newline at end of file + enabled: true + +custom: + paths: + eventApply: ${DEV_EVENT_APPLY_PATH} diff --git a/main/src/main/resources/application-prod.yml b/main/src/main/resources/application-prod.yml index 311a8aa7..bba3a8ea 100644 --- a/main/src/main/resources/application-prod.yml +++ b/main/src/main/resources/application-prod.yml @@ -56,7 +56,7 @@ springdoc: path: /api-docs/json groups: enabled: true - + push-notification: web-url: ${PROD_WEB_PAGE_URL} x-api-key: ${PROD_PUSH_API_KEY} @@ -64,7 +64,7 @@ push-notification: push-server-url: ${PROD_PUSH_SERVER_URL} notice: - secret-key : ${NOTICE_SECRET_KEY} + secret-key: ${NOTICE_SECRET_KEY} playground: server: @@ -94,4 +94,8 @@ management: prometheus: metrics: export: - enabled: true \ No newline at end of file + enabled: true + +custom: + paths: + eventApply: ${PROD_EVENT_APPLY_PATH} diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index 7ec64714..a7e232fc 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -94,3 +94,7 @@ management: metrics: export: enabled: true + +custom: + paths: + eventApply: ${DEV_EVENT_APPLY_PATH} diff --git a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java index 7ee97255..ce4aff6a 100644 --- a/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java +++ b/main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.sopt.makers.crew.main.entity.apply.Apply; import org.sopt.makers.crew.main.entity.apply.ApplyRepository; @@ -21,21 +22,21 @@ import org.sopt.makers.crew.main.entity.apply.enums.EnApplyType; import org.sopt.makers.crew.main.entity.meeting.CoLeader; import org.sopt.makers.crew.main.entity.meeting.CoLeaderRepository; -import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; -import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; -import org.sopt.makers.crew.main.global.annotation.IntegratedTest; import org.sopt.makers.crew.main.entity.meeting.Meeting; import org.sopt.makers.crew.main.entity.meeting.MeetingRepository; +import org.sopt.makers.crew.main.entity.meeting.enums.EnMeetingStatus; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingCategory; import org.sopt.makers.crew.main.entity.meeting.enums.MeetingJoinablePart; +import org.sopt.makers.crew.main.entity.meeting.vo.ImageUrlVO; import org.sopt.makers.crew.main.entity.user.User; import org.sopt.makers.crew.main.entity.user.UserRepository; import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO; +import org.sopt.makers.crew.main.global.annotation.IntegratedTest; import org.sopt.makers.crew.main.global.dto.MeetingCreatorDto; import org.sopt.makers.crew.main.global.dto.MeetingResponseDto; +import org.sopt.makers.crew.main.global.exception.BadRequestException; import org.sopt.makers.crew.main.global.exception.ForbiddenException; import org.sopt.makers.crew.main.global.exception.NotFoundException; -import org.sopt.makers.crew.main.global.exception.BadRequestException; import org.sopt.makers.crew.main.meeting.v2.dto.ApplyMapper; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingGetAppliesQueryDto; import org.sopt.makers.crew.main.meeting.v2.dto.query.MeetingV2GetAllMeetingQueryDto; @@ -1395,7 +1396,8 @@ void applyMeeting_ShouldSaveSuccessfully() { MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); // when - MeetingV2ApplyMeetingResponseDto response = meetingV2Service.applyMeeting(applyDto, applicant.getId()); + MeetingV2ApplyMeetingResponseDto response = meetingV2Service.applyGeneralMeeting(applyDto, + applicant.getId()); // then Apply apply = applyRepository.findById(response.getApplyId()).orElseThrow(); @@ -1469,7 +1471,7 @@ void applyMeeting_ShouldFailWhenCapacityExceeded() { MeetingV2ApplyMeetingDto applyDto2 = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기 2"); // when & then - Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto2, applicant2.getId())) + Assertions.assertThatThrownBy(() -> meetingV2Service.applyGeneralMeeting(applyDto2, applicant2.getId())) .isInstanceOf(BadRequestException.class) .hasMessageContaining("정원이 꽉 찼습니다."); } @@ -1534,7 +1536,7 @@ void applyMeeting_Fail_WhenAlreadyApplied() { // when & then MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); - Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto, applicant.getId())) + Assertions.assertThatThrownBy(() -> meetingV2Service.applyGeneralMeeting(applyDto, applicant.getId())) .isInstanceOf(BadRequestException.class) .hasMessageContaining("이미 지원한 모임입니다."); } @@ -1587,7 +1589,7 @@ void applyMeeting_Fail_WhenOutsideApplicationPeriod() { // when & then MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); - Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto, applicant.getId())) + Assertions.assertThatThrownBy(() -> meetingV2Service.applyGeneralMeeting(applyDto, applicant.getId())) .isInstanceOf(BadRequestException.class) .hasMessageContaining("지원 기간이 아닙니다."); } @@ -1660,7 +1662,7 @@ void applyMeeting_Fail_WhenIsCoLeader() { // when & then MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); - Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto, savedCoLeader1.getId())) + Assertions.assertThatThrownBy(() -> meetingV2Service.applyGeneralMeeting(applyDto, savedCoLeader1.getId())) .isInstanceOf(BadRequestException.class) .hasMessage("공동 모임장은 신청할 수 없습니다."); } @@ -1705,10 +1707,126 @@ void applyMeeting_Fail_WhenIsLeader() { // when & then MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); - Assertions.assertThatThrownBy(() -> meetingV2Service.applyMeeting(applyDto, leader.getId())) + Assertions.assertThatThrownBy(() -> meetingV2Service.applyGeneralMeeting(applyDto, leader.getId())) .isInstanceOf(BadRequestException.class) .hasMessage("모임장은 신청할 수 없습니다."); } + + @Test + @DisplayName("행사 카테고리에 일반 모임 신청 API로 호출 시 예외가 발생되어야 한다.") + void applyGeneralMeeting_WhenEventCategory_ShouldThrowException() { + // given + User leader = User.builder() + .name("모임장") + .orgId(1) + .activities(List.of(new UserActivityVO("안드로이드", 35))) + .profileImage("testProfileImage.jpg") + .phone("010-1234-5678") + .build(); + + userRepository.save(leader); + + Meeting meeting = Meeting.builder() + .user(leader) + .userId(leader.getId()) + .title("모임 지원 테스트") + .category(MeetingCategory.EVENT) + .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) + .endDate(LocalDateTime.of(2029, 4, 27, 23, 59, 59)) + .capacity(20) + .desc("모임 지원 테스트입니다.") + .processDesc("테스트 진행 방식입니다.") + .mStartDate(LocalDateTime.of(2024, 11, 24, 0, 0, 0)) + .mEndDate(LocalDateTime.of(2024, 12, 24, 0, 0, 0)) + .leaderDesc("모임 리더 설명입니다.") + .note("유의사항입니다.") + .isMentorNeeded(false) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(35) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + meetingRepository.save(meeting); + + User applicant = User.builder() + .name("지원자 1") + .orgId(2) + .activities(List.of(new UserActivityVO("웹", 35))) + .profileImage("applicantProfile.jpg") + .phone("010-1234-5678") + .build(); + + userRepository.save(applicant); + + MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); + + // when & then + Assertions.assertThatThrownBy(() -> + meetingV2Service.applyGeneralMeeting(applyDto, applicant.getId()) + ).isInstanceOf(BadRequestException.class) + .hasMessageContaining("모임 지원 기간이 아닙니다."); + } + + @ParameterizedTest + @EnumSource(value = MeetingCategory.class, names = "EVENT", mode = EnumSource.Mode.EXCLUDE) + @DisplayName("행사 카테고리가 아닌 다른 카테고리로 행사 모임 신청 API로 호출 시 예외가 발생되어야 한다.") + void applyEventMeeting_WhenCategoryIsNotEvent_ShouldThrowException(MeetingCategory category) { + // given + User leader = User.builder() + .name("모임장") + .orgId(1) + .activities(List.of(new UserActivityVO("안드로이드", 35))) + .profileImage("testProfileImage.jpg") + .phone("010-1234-5678") + .build(); + + userRepository.save(leader); + + Meeting meeting = Meeting.builder() + .user(leader) + .userId(leader.getId()) + .title("모임 지원 테스트") + .category(category) + .imageURL(List.of(new ImageUrlVO(0, "testImage.jpg"))) + .startDate(LocalDateTime.of(2024, 4, 23, 0, 0, 0)) + .endDate(LocalDateTime.of(2029, 4, 27, 23, 59, 59)) + .capacity(20) + .desc("모임 지원 테스트입니다.") + .processDesc("테스트 진행 방식입니다.") + .mStartDate(LocalDateTime.of(2024, 11, 24, 0, 0, 0)) + .mEndDate(LocalDateTime.of(2024, 12, 24, 0, 0, 0)) + .leaderDesc("모임 리더 설명입니다.") + .note("유의사항입니다.") + .isMentorNeeded(false) + .canJoinOnlyActiveGeneration(false) + .createdGeneration(35) + .targetActiveGeneration(null) + .joinableParts(MeetingJoinablePart.values()) + .build(); + + meetingRepository.save(meeting); + + User applicant = User.builder() + .name("지원자 1") + .orgId(2) + .activities(List.of(new UserActivityVO("웹", 35))) + .profileImage("applicantProfile.jpg") + .phone("010-1234-5678") + .build(); + + userRepository.save(applicant); + + MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기"); + + // when & then + Assertions.assertThatThrownBy(() -> + meetingV2Service.applyEventMeeting(applyDto, applicant.getId()) + ).isInstanceOf(BadRequestException.class) + .hasMessageContaining("모임 지원 기간이 아닙니다."); + } + } @Nested @@ -1839,7 +1957,7 @@ void modifyMeeting_Success() { .containsExactly( tuple("승인신청자", 1003), tuple("대기신청자", 1004) - ); + ); } }