diff --git a/build.gradle b/build.gradle index a54e4b8e..db629452 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.5.3' id 'io.spring.dependency-management' version '1.1.7' + id 'com.github.johnrengelman.shadow' version '8.1.1' } group = 'grep' @@ -69,8 +70,8 @@ dependencies { //JWT 의존성 implementation 'io.jsonwebtoken:jjwt-api:0.12.3' - implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' - implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' // OAuth2 의존성 implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' @@ -82,9 +83,8 @@ dependencies { implementation 'dev.langchain4j:langchain4j-spring-boot-starter:1.0.0-beta3' implementation 'dev.langchain4j:langchain4j-google-ai-gemini-spring-boot-starter:1.0.0-beta3' - // 배포 관련 의존성 -// runtimeOnly 'org.postgresql:postgresql' +// runtimeOnly 'org.postgresql:postgresql' implementation 'org.springframework.boot:spring-boot-devtools' implementation 'com.google.cloud:spring-cloud-gcp-starter-secretmanager:4.9.1' implementation 'com.google.cloud:google-cloud-storage:2.38.0' diff --git a/src/main/java/grep/neogulcoder/domain/recruitment/RecruitmentErrorCode.java b/src/main/java/grep/neogulcoder/domain/recruitment/RecruitmentErrorCode.java index fb666cfd..283c3120 100644 --- a/src/main/java/grep/neogulcoder/domain/recruitment/RecruitmentErrorCode.java +++ b/src/main/java/grep/neogulcoder/domain/recruitment/RecruitmentErrorCode.java @@ -14,7 +14,8 @@ public enum RecruitmentErrorCode implements ErrorCode { NOT_FOUND(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.name(), "모집글을 찾지 못했습니다."), NOT_FOUND_STUDY_MEMBER(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.name(), "스터디에 참여하고 있지 않은 회원 입니다."), NOT_FOUND_COMMENT(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.name(), "댓글을 찾지 못했습니다"), - NOT_OWNER(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.name(), "모집글을 등록한 당사자가 아닙니다."); + NOT_OWNER(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.name(), "모집글을 등록한 당사자가 아닙니다."), + END_DATE_ERROR(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.name(), "모집글은 스터디가 시작되고 종료될 때까지 작성 가능 합니다."); private static final String BASIC_MESSAGE = "RECRUITMENT"; private final HttpStatus status; diff --git a/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/RecruitmentPostSaveController.java b/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/RecruitmentPostSaveController.java index 1a9b57f3..56b05159 100644 --- a/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/RecruitmentPostSaveController.java +++ b/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/RecruitmentPostSaveController.java @@ -1,8 +1,8 @@ package grep.neogulcoder.domain.recruitment.post.controller; import grep.neogulcoder.domain.recruitment.post.controller.dto.request.save.RecruitmentPostCreateRequest; -import grep.neogulcoder.domain.recruitment.post.controller.dto.response.save.JoinedStudyLoadInfo; import grep.neogulcoder.domain.recruitment.post.controller.dto.response.save.JoinedStudiesInfo; +import grep.neogulcoder.domain.recruitment.post.controller.dto.response.save.JoinedStudyLoadInfo; import grep.neogulcoder.domain.recruitment.post.service.RecruitmentPostSaveService; import grep.neogulcoder.global.auth.Principal; import grep.neogulcoder.global.response.ApiResponse; @@ -21,7 +21,7 @@ public class RecruitmentPostSaveController implements RecruitmentPostSaveSpecifi @PostMapping public ResponseEntity> save(@Valid @RequestBody RecruitmentPostCreateRequest request, - @AuthenticationPrincipal Principal userDetails) { + @AuthenticationPrincipal Principal userDetails) { long postId = recruitmentPostService.create(request.toServiceRequest(), userDetails.getUserId()); return ResponseEntity.ok(ApiResponse.success(postId)); } @@ -34,7 +34,7 @@ public ResponseEntity> getJoinedStudyInfo(@Authen @GetMapping("/studies/{study-id}") public ResponseEntity> getJoinedStudyLoadInfo(@PathVariable("study-id") long studyId, - @AuthenticationPrincipal Principal userDetails) { + @AuthenticationPrincipal Principal userDetails) { JoinedStudyLoadInfo response = recruitmentPostService.getJoinedStudyLoadInfo(studyId, userDetails.getUserId()); return ResponseEntity.ok(ApiResponse.success(response)); } diff --git a/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/dto/request/save/RecruitmentPostCreateRequest.java b/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/dto/request/save/RecruitmentPostCreateRequest.java index eb3b7cb8..b48001ae 100644 --- a/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/dto/request/save/RecruitmentPostCreateRequest.java +++ b/src/main/java/grep/neogulcoder/domain/recruitment/post/controller/dto/request/save/RecruitmentPostCreateRequest.java @@ -2,7 +2,6 @@ import grep.neogulcoder.domain.recruitment.post.service.request.RecruitmentPostCreateServiceRequest; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -31,7 +30,7 @@ public class RecruitmentPostCreateRequest { private int recruitmentCount; @Schema(example = "2025-07-10", description = "모집 마감일") - @FutureOrPresent(message = "모집 마감일은 현재 날짜 이전으로 설정이 불가능 합니다.") + @NotNull private LocalDateTime expiredDate; private RecruitmentPostCreateRequest() { diff --git a/src/main/java/grep/neogulcoder/domain/recruitment/post/service/RecruitmentPostSaveService.java b/src/main/java/grep/neogulcoder/domain/recruitment/post/service/RecruitmentPostSaveService.java index 6b586269..02715b89 100644 --- a/src/main/java/grep/neogulcoder/domain/recruitment/post/service/RecruitmentPostSaveService.java +++ b/src/main/java/grep/neogulcoder/domain/recruitment/post/service/RecruitmentPostSaveService.java @@ -13,8 +13,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; +import static grep.neogulcoder.domain.recruitment.RecruitmentErrorCode.*; import static grep.neogulcoder.domain.recruitment.RecruitmentErrorCode.NOT_FOUND_STUDY_MEMBER; import static grep.neogulcoder.domain.recruitment.RecruitmentErrorCode.NOT_STUDY_LEADER; @@ -28,12 +30,16 @@ public class RecruitmentPostSaveService { @Transactional public long create(RecruitmentPostCreateServiceRequest request, long userId) { - StudyMember studyMember = studyMemberRepository.findByStudyIdAndUserId(request.getStudyId(), userId); + StudyMember studyMember = studyMemberRepository.findMemberWithStudyBy(request.getStudyId(), userId); if (studyMember.hasNotRoleLeader()) { throw new BusinessException(NOT_STUDY_LEADER); } + if(studyMember.getStudy().hasEndDateBefore(request.getExpiredDate())){ + throw new BusinessException(END_DATE_ERROR); + } + return recruitmentPostRepository.save(request.toEntity(userId)).getId(); } diff --git a/src/main/java/grep/neogulcoder/domain/study/Study.java b/src/main/java/grep/neogulcoder/domain/study/Study.java index d620915b..f9129428 100644 --- a/src/main/java/grep/neogulcoder/domain/study/Study.java +++ b/src/main/java/grep/neogulcoder/domain/study/Study.java @@ -125,6 +125,10 @@ public boolean isReviewableAt(LocalDateTime currentDateTime) { (currentDateTime.isEqual(reviewableDateTime) || currentDateTime.isBefore(reviewableDateTime)); } + public boolean hasEndDateBefore(LocalDateTime dateTime) { + return this.endDate.isBefore(dateTime); + } + public static boolean isOverJoinLimit(int joinedStudyCount) { return joinedStudyCount >= MAX_JOINED_STUDY_COUNT; } diff --git a/src/main/java/grep/neogulcoder/domain/study/controller/StudyManagementController.java b/src/main/java/grep/neogulcoder/domain/study/controller/StudyManagementController.java index 29585fb7..6436bde1 100644 --- a/src/main/java/grep/neogulcoder/domain/study/controller/StudyManagementController.java +++ b/src/main/java/grep/neogulcoder/domain/study/controller/StudyManagementController.java @@ -65,7 +65,7 @@ public ResponseEntity> registerExtensionParticipation(@PathVar } @PostMapping("/invite/user") - public ResponseEntity> inviteUser(@PathVariable("studyId") Long studyId, @AuthenticationPrincipal Principal userDetails, String targetUserNickname) { + public ResponseEntity> inviteUser(@PathVariable("studyId") Long studyId, @AuthenticationPrincipal Principal userDetails, @RequestParam String targetUserNickname) { studyManagementService.inviteTargetUser(studyId, userDetails.getUserId(), targetUserNickname); return ResponseEntity.ok(ApiResponse.noContent()); } diff --git a/src/main/java/grep/neogulcoder/domain/study/repository/StudyMemberQueryRepository.java b/src/main/java/grep/neogulcoder/domain/study/repository/StudyMemberQueryRepository.java index 32d6b523..1f6963d3 100644 --- a/src/main/java/grep/neogulcoder/domain/study/repository/StudyMemberQueryRepository.java +++ b/src/main/java/grep/neogulcoder/domain/study/repository/StudyMemberQueryRepository.java @@ -56,6 +56,18 @@ public StudyMember findByStudyIdAndUserId(long studyId, long userId) { .fetchOne(); } + public StudyMember findMemberWithStudyBy(long studyId, long userId) { + return queryFactory.select(studyMember) + .from(studyMember) + .join(studyMember.study, study).fetchJoin() + .where( + studyMember.study.id.eq(studyId), + studyMember.userId.eq(userId), + studyMember.activated.isTrue() + ) + .fetchOne(); + } + public List findExtendParticipation(Long studyId) { return queryFactory .select(Projections.constructor(