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
@@ -1,6 +1,6 @@
package inha.gdgoc.domain.auth.exception;

import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.ErrorCode;
import org.springframework.http.HttpStatus;

public enum AuthErrorCode implements ErrorCode {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package inha.gdgoc.domain.auth.exception;

import inha.gdgoc.global.error.BusinessException;
import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.BusinessException;
import inha.gdgoc.global.exception.ErrorCode;

public class AuthException extends BusinessException {

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/inha/gdgoc/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public Map<String, Object> processOAuthLogin(String code, HttpServletResponse re
Optional<User> foundUser = userRepository.findByEmail(email);
if (foundUser.isEmpty()) {
return Map.of(
"exists", false,
"isExists", false,
"email", email,
"name", name
);
Expand All @@ -117,7 +117,7 @@ public Map<String, Object> processOAuthLogin(String code, HttpServletResponse re
response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString());

return Map.of(
"exists", true,
"isExists", true,
"access_token", jwtAccessToken
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
import static inha.gdgoc.domain.recruit.controller.message.RecruitMemberMessage.STUDENT_ID_DUPLICATION_CHECK_SUCCESS;

import inha.gdgoc.domain.recruit.dto.request.ApplicationRequest;
import inha.gdgoc.domain.recruit.dto.request.CheckPhoneNumberRequest;
import inha.gdgoc.domain.recruit.dto.request.CheckStudentIdRequest;
import inha.gdgoc.domain.recruit.dto.response.CheckPhoneNumberResponse;
import inha.gdgoc.domain.recruit.dto.response.CheckStudentIdResponse;
import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse;
import inha.gdgoc.domain.recruit.service.RecruitMemberService;
import inha.gdgoc.global.dto.response.ApiResponse;
import jakarta.validation.Valid;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.RequiredArgsConstructor;
Comment on lines +16 to 18
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

메서드 파라미터 검증 활성화를 위한 @validated 필요

컨트롤러에 @Validated가 없어 @RequestParam@NotBlank/@Pattern 제약이 적용되지 않습니다. 클래스 레벨에 추가해 주세요.

 @RequestMapping("/api/v1")
 @RequiredArgsConstructor
 @RestController
+@org.springframework.validation.annotation.Validated
 public class RecruitMemberController {

Also applies to: 29-32

🤖 Prompt for AI Agents
In
src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java
around lines 16-18 and 29-32, the controller is missing the class-level
@Validated annotation so parameter-level javax/jakarta validation annotations
like @NotBlank and @Pattern on @RequestParam are not being enforced; add the
org.springframework.validation.annotation.Validated annotation to the controller
class declaration (and the necessary import) so Spring activates
method-parameter validation for those request parameters.

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -38,34 +42,45 @@ public ResponseEntity<ApiResponse<Void, Void>> recruitMemberAdd(
return ResponseEntity.ok(ApiResponse.ok(MEMBER_SAVE_SUCCESS));
}

// TODO valid 핸들러 추가
// TODO DTO로 응답 리팩토링, requestparam으로 변경하기
@GetMapping("/check/studentId")
public ResponseEntity<ApiResponse<Boolean, Void>> duplicatedStudentIdDetails(
@Valid @ModelAttribute CheckStudentIdRequest studentIdRequest
@GetMapping("/studentId")
public ResponseEntity<ApiResponse<CheckStudentIdResponse, Void>> duplicatedStudentIdDetails(
@RequestParam
@NotBlank(message = "학번은 필수 입력 값입니다.")
@Pattern(regexp = "^12[0-9]{6}$", message = "유효하지 않은 학번 값입니다.")
String studentId
) {
boolean exists = recruitMemberService.isRegisteredStudentId(studentIdRequest.getStudentId());
CheckStudentIdResponse response = recruitMemberService.isRegisteredStudentId(studentId);

return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, exists));
return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, response));
}
Comment on lines +45 to 55
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

[중요] 엔드포인트 경로 변경으로 인증 요구됨 + 일관성 깨짐

@GetMapping("/studentId")는 기존 "/check/**" 네임스페이스를 벗어나 Security에서 permitAll에 포함되지 않습니다. 현재 설정상 "/api/v1/check/**"만 허용이므로 이 엔드포인트는 인증이 필요해집니다. 또한 전화번호 경로는 "/check/phoneNumber"로 유지되어 일관성도 깨집니다.

다음과 같이 경로를 복구/정렬하는 것을 권장합니다.

-    @GetMapping("/studentId")
+    @GetMapping("/check/studentId")

대안으로 SecurityConfig의 permitAll에 "/api/v1/studentId"를 추가할 수도 있으나, 네임스페이스 일관 관점에서는 위 수정이 더 적합합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/studentId")
public ResponseEntity<ApiResponse<CheckStudentIdResponse, Void>> duplicatedStudentIdDetails(
@RequestParam
@NotBlank(message = "학번은 필수 입력 값입니다.")
@Pattern(regexp = "^12[0-9]{6}$", message = "유효하지 않은 학번 값입니다.")
String studentId
) {
boolean exists = recruitMemberService.isRegisteredStudentId(studentIdRequest.getStudentId());
CheckStudentIdResponse response = recruitMemberService.isRegisteredStudentId(studentId);
return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, exists));
return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, response));
}
@GetMapping("/check/studentId")
public ResponseEntity<ApiResponse<CheckStudentIdResponse, Void>> duplicatedStudentIdDetails(
@RequestParam
@NotBlank(message = "학번은 필수 입력 값입니다.")
@Pattern(regexp = "^12[0-9]{6}$", message = "유효하지 않은 학번 값입니다.")
String studentId
) {
CheckStudentIdResponse response = recruitMemberService.isRegisteredStudentId(studentId);
return ResponseEntity.ok(ApiResponse.ok(STUDENT_ID_DUPLICATION_CHECK_SUCCESS, response));
}
🤖 Prompt for AI Agents
src/main/java/inha/gdgoc/domain/recruit/controller/RecruitMemberController.java
around lines 45-55: the endpoint mapping currently uses
@GetMapping("/studentId") which falls outside the existing "/check/**" namespace
and therefore requires authentication; change the mapping to restore consistency
with other check endpoints by using @GetMapping("/check/studentId") (so it
matches the permitAll pattern "/api/v1/check/**" and aligns with
"/check/phoneNumber"), update any related API docs/tests if present to reflect
the new path.


// TODO DTO로 응답 리팩토링
@GetMapping("/check/phoneNumber")
public ResponseEntity<ApiResponse<Boolean, Void>> duplicatedPhoneNumberDetails(
@Valid @ModelAttribute CheckPhoneNumberRequest phoneNumberRequest
public ResponseEntity<ApiResponse<CheckPhoneNumberResponse, Void>> duplicatedPhoneNumberDetails(
@RequestParam
@NotBlank(message = "전화번호는 필수 입력 값입니다.")
@Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "전화번호 형식은 010-XXXX-XXXX 이어야 합니다.")
String phoneNumber
) {
boolean exists = recruitMemberService.isRegisteredPhoneNumber(phoneNumberRequest.getPhoneNumber());
CheckPhoneNumberResponse response = recruitMemberService
.isRegisteredPhoneNumber(phoneNumber);

return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, exists));
return ResponseEntity.ok(ApiResponse.ok(PHONE_NUMBER_DUPLICATION_CHECK_SUCCESS, response));
}

// TODO 코어 멤버 인증 리팩토링 (Authentication), requestparam으로 변경하기
@GetMapping("/recruit/member")
public ResponseEntity<ApiResponse<SpecifiedMemberResponse, Void>> getSpecifiedMember (
@RequestParam Long userId
@Operation(summary = "특정 멤버 가입 신청서 조회", security = { @SecurityRequirement(name = "BearerAuth") })
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/recruit/members/{memberId}")
public ResponseEntity<ApiResponse<SpecifiedMemberResponse, Void>> getSpecifiedMember(
@PathVariable Long memberId
) {
SpecifiedMemberResponse response = recruitMemberService.findSpecifiedMember(userId);
SpecifiedMemberResponse response = recruitMemberService.findSpecifiedMember(memberId);

return ResponseEntity.ok(ApiResponse.ok(MEMBER_RETRIEVED_SUCCESS, response));
}

// TODO 전체 응답 조회 및 검색

// TODO 입금 완료

// TODO 입금 미완료
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public RecruitMember toEntity() {
.birth(birth)
.major(major)
.doubleMajor(doubleMajor)
.isPayed(isPayed)
.isPayed(false)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package inha.gdgoc.domain.recruit.dto.response;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import inha.gdgoc.domain.recruit.entity.Answer;
import inha.gdgoc.domain.recruit.enums.InputType;
import java.util.List;
import java.util.Map;

public record AnswerResponse(
Long id,
InputType inputType,
Object responseValue
) {
public static AnswerResponse from(Answer answer, ObjectMapper om) {
return new AnswerResponse(
answer.getId(),
answer.getInputType(),
toFriendlyValue(answer.getResponseValue(), om)
);
}

private static Object toFriendlyValue(String json, ObjectMapper om) {
if (json == null || json.isBlank()) return null;
try {
JsonNode node = om.readTree(json);

if (node.isTextual()) {
return node.asText();
}
if (node.isArray()) {
return om.convertValue(node, new TypeReference<List<Object>>() {});
}
if (node.isObject()) {
return om.convertValue(node, new TypeReference<Map<String, Object>>() {});
}
if (node.isNumber()) {
return node.numberValue();
}
if (node.isBoolean()) {
return node.booleanValue();
}
return null;
} catch (Exception e) {
return json;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package inha.gdgoc.domain.recruit.dto.response;

import com.fasterxml.jackson.databind.ObjectMapper;
import inha.gdgoc.domain.recruit.entity.Answer;
import java.util.List;

public record AnswersResponse(
List<AnswerResponse> answers
) {
public static AnswersResponse from(List<Answer> entities, ObjectMapper objectMapper) {
return new AnswersResponse(
entities.stream()
.map(a -> AnswerResponse.from(a, objectMapper))
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package inha.gdgoc.domain.recruit.dto.response;

public record CheckPhoneNumberResponse(boolean isExists) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package inha.gdgoc.domain.recruit.dto.response;

public record CheckStudentIdResponse(boolean isExists) {

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package inha.gdgoc.domain.recruit.dto.response;

import com.fasterxml.jackson.databind.ObjectMapper;
import inha.gdgoc.domain.recruit.entity.Answer;
import inha.gdgoc.domain.recruit.entity.RecruitMember;
import java.util.List;

public record SpecifiedMemberResponse(
String name,
String major,
String studentId,
boolean isPayed
boolean isPayed,
AnswersResponse answers
) {

public static SpecifiedMemberResponse from(RecruitMember member) {
public static SpecifiedMemberResponse from(
RecruitMember member,
List<Answer> answers,
ObjectMapper objectMapper
) {
return new SpecifiedMemberResponse(
member.getName(),
member.getMajor(),
member.getStudentId(),
Boolean.TRUE.equals(member.getIsPayed())
Boolean.TRUE.equals(member.getIsPayed()),
AnswersResponse.from(answers, objectMapper)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package inha.gdgoc.domain.recruit.exception;

import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package inha.gdgoc.domain.recruit.exception;

import inha.gdgoc.global.error.BusinessException;
import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.BusinessException;
import inha.gdgoc.global.exception.ErrorCode;

public class RecruitMemberException extends BusinessException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package inha.gdgoc.domain.recruit.repository;

import inha.gdgoc.domain.recruit.entity.Answer;
import inha.gdgoc.domain.recruit.entity.RecruitMember;
import inha.gdgoc.domain.recruit.enums.SurveyType;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AnswerRepository extends JpaRepository<Answer, Long> {

List<Answer> findByRecruitMemberAndSurveyType(
RecruitMember recruitMember,
SurveyType surveyType
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import inha.gdgoc.domain.recruit.dto.request.ApplicationRequest;
import inha.gdgoc.domain.recruit.dto.response.CheckPhoneNumberResponse;
import inha.gdgoc.domain.recruit.dto.response.CheckStudentIdResponse;
import inha.gdgoc.domain.recruit.dto.response.SpecifiedMemberResponse;
import inha.gdgoc.domain.recruit.entity.Answer;
import inha.gdgoc.domain.recruit.entity.RecruitMember;
Expand Down Expand Up @@ -45,18 +47,24 @@ public void addRecruitMember(ApplicationRequest applicationRequest) {
answerRepository.saveAll(answers);
}

public boolean isRegisteredStudentId(String studentId) {
return recruitMemberRepository.existsByStudentId(studentId);
public CheckStudentIdResponse isRegisteredStudentId(String studentId) {
boolean exists = recruitMemberRepository.existsByStudentId(studentId);

return new CheckStudentIdResponse(exists);
}

public boolean isRegisteredPhoneNumber(String phoneNumber) {
return recruitMemberRepository.existsByPhoneNumber(phoneNumber);
public CheckPhoneNumberResponse isRegisteredPhoneNumber(String phoneNumber) {
boolean exists = recruitMemberRepository.existsByPhoneNumber(phoneNumber);

return new CheckPhoneNumberResponse(exists);
}

public SpecifiedMemberResponse findSpecifiedMember(Long id) {
RecruitMember member = recruitMemberRepository.findById(id)
.orElseThrow(() -> new RecruitMemberException(RECRUIT_MEMBER_NOT_FOUND));
List<Answer> answers = answerRepository
.findByRecruitMemberAndSurveyType(member, SurveyType.RECRUIT);

return SpecifiedMemberResponse.from(member);
return SpecifiedMemberResponse.from(member, answers, objectMapper);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package inha.gdgoc.domain.resource.exception;

import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.ErrorCode;
import org.springframework.http.HttpStatus;

public enum ResourceErrorCode implements ErrorCode {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package inha.gdgoc.domain.resource.exception;

import inha.gdgoc.global.error.BusinessException;
import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.BusinessException;
import inha.gdgoc.global.exception.ErrorCode;

public class ResourceException extends BusinessException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package inha.gdgoc.domain.study.exception;

import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.ErrorCode;
import org.springframework.http.HttpStatus;

public enum StudyAttendeeErrorCode implements ErrorCode {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package inha.gdgoc.domain.study.exception;

import inha.gdgoc.global.error.BusinessException;
import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.BusinessException;
import inha.gdgoc.global.exception.ErrorCode;

public class StudyAttendeeException extends BusinessException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package inha.gdgoc.domain.study.exception;

import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.ErrorCode;
import org.springframework.http.HttpStatus;

public enum StudyErrorCode implements ErrorCode {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package inha.gdgoc.domain.study.exception;

import inha.gdgoc.global.error.BusinessException;
import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.BusinessException;
import inha.gdgoc.global.exception.ErrorCode;

public class StudyException extends BusinessException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package inha.gdgoc.domain.user.exception;

import inha.gdgoc.global.error.ErrorCode;
import inha.gdgoc.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

Expand Down
Loading
Loading