Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import moadong.club.entity.ClubApplication;
import moadong.club.payload.request.ClubApplicantEditRequest;
import moadong.club.payload.request.ClubApplicationCreateRequest;
import moadong.club.payload.request.ClubApplicationEditRequest;
import moadong.club.payload.request.ClubApplyRequest;
import moadong.club.payload.response.ClubApplicationResponse;
import moadong.club.service.ClubApplyService;
import moadong.global.payload.Response;
import moadong.user.annotation.CurrentUser;
Expand All @@ -18,8 +17,6 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/club/{clubId}")
@AllArgsConstructor
Expand Down Expand Up @@ -73,4 +70,33 @@ public ResponseEntity<?> getApplyInfo(@PathVariable String clubId,
return Response.ok(clubApplyService.getClubApplyInfo(clubId, user));
}

@PutMapping("/apply/{appId}")
@Operation(summary = "지원서 변경",
description = "클럽 자원자의 지원서 정보를 수정합니다.<br>"
+ "appId - 지원서 아이디"
)
@PreAuthorize("isAuthenticated()")
@SecurityRequirement(name = "BearerAuth")
public ResponseEntity<?> editApplicantDetail(@PathVariable String clubId,
@PathVariable String appId,
@RequestBody @Validated ClubApplicantEditRequest request,
@CurrentUser CustomUserDetails user) {
clubApplyService.editApplicantDetail(clubId, appId, request, user);
return Response.ok("success edit applicant");
}

@DeleteMapping("/apply/{appId}")
@Operation(summary = "지원서 삭제",
description = "클럽 자원자의 지원서를 삭제합니다.<br>"
+ "appId - 지원서 아이디"
)
@PreAuthorize("isAuthenticated()")
@SecurityRequirement(name = "BearerAuth")
public ResponseEntity<?> removeApplicant(@PathVariable String clubId,
@PathVariable String appId,
@CurrentUser CustomUserDetails user) {
clubApplyService.deleteApplicant(clubId, appId, user);
return Response.ok("success delete applicant");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,17 @@ public class ClubApplication {
@Builder.Default
ApplicationStatus status = ApplicationStatus.SUBMITTED;

@Builder.Default
private String memo = "";

@Builder.Default
private List<ClubQuestionAnswer> answers = new ArrayList<>();

@Builder.Default
LocalDateTime createdAt = ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime();

public void updateDetail(String memo, ApplicationStatus status) {
this.memo = memo;
this.status = status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
import moadong.global.exception.RestApiException;
import moadong.global.util.AESCipher;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Builder
@Slf4j
public record ClubApplicantsResult(
String questionId,
String id,
ApplicationStatus status,
List<ClubQuestionAnswer> answers
List<ClubQuestionAnswer> answers,
LocalDateTime createdAt
) {
public static ClubApplicantsResult of(ClubApplication application, AESCipher cipher) {
List<ClubQuestionAnswer> decryptedAnswers = new ArrayList<>();
Expand All @@ -35,9 +37,10 @@ public static ClubApplicantsResult of(ClubApplication application, AESCipher cip
}

return ClubApplicantsResult.builder()
.questionId(application.getQuestionId())
.id(application.getId())
.status(application.getStatus())
.answers(decryptedAnswers)
.createdAt(application.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package moadong.club.payload.request;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import moadong.club.enums.ApplicationStatus;

public record ClubApplicantEditRequest(
@NotNull
@Size(max = 500)
String memo,

@NotNull
ApplicationStatus status
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import org.springframework.data.mongodb.repository.Query;

import java.util.List;
import java.util.Optional;

public interface ClubApplicationRepository extends MongoRepository<ClubApplication, String> {
@Query("{ 'questionId': ?0, 'status': { $exists: true, $ne: 'DRAFT' } }")
List<ClubApplication> findAllByQuestionId(String questionId);

Optional<ClubApplication> findByIdAndQuestionId(String id, String questionId);
}
34 changes: 34 additions & 0 deletions backend/src/main/java/moadong/club/service/ClubApplyService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package moadong.club.service;

import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import moadong.club.entity.*;
import moadong.club.enums.ClubApplicationQuestionType;
import moadong.club.payload.dto.ClubApplicantsResult;
import moadong.club.payload.request.ClubApplicantEditRequest;
import moadong.club.payload.request.ClubApplicationCreateRequest;
import moadong.club.payload.request.ClubApplicationEditRequest;
import moadong.club.payload.request.ClubApplyRequest;
Expand Down Expand Up @@ -123,6 +125,38 @@ public ClubApplyInfoResponse getClubApplyInfo(String clubId, CustomUserDetails u
.build();
}

@Transactional
public void editApplicantDetail(String clubId, String appId, ClubApplicantEditRequest request, CustomUserDetails user) {
Club club = clubRepository.findById(clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));

if (!user.getId().equals(club.getUserId())) {
throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
}

ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));

application.updateDetail(request.memo(), request.status());

clubApplicationRepository.save(application);
}

@Transactional
public void deleteApplicant(String clubId, String appId, CustomUserDetails user) {
Club club = clubRepository.findById(clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND));

if (!user.getId().equals(club.getUserId())) {
throw new RestApiException(ErrorCode.USER_UNAUTHORIZED);
}

ClubApplication application = clubApplicationRepository.findByIdAndQuestionId(appId, clubId)
.orElseThrow(() -> new RestApiException(ErrorCode.APPLICANT_NOT_FOUND));

clubApplicationRepository.delete(application);
}

private void validateAnswers(List<ClubApplyRequest.Answer> answers, ClubQuestion clubQuestion) {
// 미리 질문과 응답 id 만들어두기
Map<Long, ClubApplicationQuestion> questionMap = clubQuestion.getQuestions().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public enum ErrorCode {
QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "800-4", "존재하지 않은 질문입니다."),
REQUIRED_QUESTION_MISSING(HttpStatus.BAD_REQUEST, "800-5", "필수 응답 질문이 누락되었습니다."),

AES_CIPHER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "900-1", "암호화 중 오류가 발생했습니다.")
AES_CIPHER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "900-1", "암호화 중 오류가 발생했습니다."),
APPLICANT_NOT_FOUND(HttpStatus.NOT_FOUND, "900-2", "지원서가 존재하지 않습니다."),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

중복 에러 코드 검토 필요

APPLICANT_NOT_FOUND와 기존 APPLICATION_NOT_FOUND (Line 36) 사이에 기능적 중복이 있어 보입니다. 두 에러 코드 모두 같은 메시지("지원서가 존재하지 않습니다.")를 가지고 있습니다.

새로운 지원자 관리 API에서 기존 APPLICATION_NOT_FOUND를 재사용할 수 있는지 검토하거나, 두 에러 코드 간의 구체적인 차이점을 명확히 해주세요.

🤖 Prompt for AI Agents
In backend/src/main/java/moadong/global/exception/ErrorCode.java at line 43,
there is a potential functional overlap between APPLICANT_NOT_FOUND and
APPLICATION_NOT_FOUND at line 36, both having the same error message. Review
whether the existing APPLICATION_NOT_FOUND error code can be reused for the new
applicant management API instead of creating APPLICANT_NOT_FOUND. If both are
necessary, clarify and differentiate their specific use cases and error messages
to avoid confusion and redundancy.

;

private final HttpStatus httpStatus;
Expand Down
Loading