Skip to content
Closed
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 @@ -50,6 +50,7 @@ public enum ErrorCode {
BLOCK_USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "차단 대상 사용자를 찾을 수 없습니다."),
TERM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학기입니다."),
CURRENT_TERM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "현재 학기를 찾을 수 없습니다."),
MENTOR_APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "멘토 프로필이 존재하지 않습니다."),

// auth
USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."),
Expand Down Expand Up @@ -126,6 +127,7 @@ public enum ErrorCode {
UNIVERSITY_ID_REQUIRED_FOR_CATALOG(HttpStatus.BAD_REQUEST.value(), "목록에서 학교를 선택한 경우 학교 정보가 필요합니다."),
UNIVERSITY_ID_MUST_BE_NULL_FOR_OTHER(HttpStatus.BAD_REQUEST.value(), "기타 학교를 선택한 경우 학교 정보를 입력할 수 없습니다."),
INVALID_UNIVERSITY_SELECT_TYPE(HttpStatus.BAD_REQUEST.value(), "지원하지 않는 학교 선택 방식입니다."),
MENTOR_ALREADY_EXISTS(HttpStatus.BAD_REQUEST.value(), "이미 멘토 마이 페이지가 존재합니다" ),

// socket
UNAUTHORIZED_SUBSCRIBE(HttpStatus.FORBIDDEN.value(), "구독 권한이 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@
import com.example.solidconnection.common.resolver.AuthorizedUser;
import com.example.solidconnection.mentor.dto.MentorMyPageResponse;
import com.example.solidconnection.mentor.dto.MentorMyPageUpdateRequest;
import com.example.solidconnection.mentor.dto.MentorMyPageCreateRequest;
import com.example.solidconnection.mentor.service.MentorMyPageService;
import com.example.solidconnection.security.annotation.RequireRoleAccess;
import com.example.solidconnection.siteuser.domain.Role;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/mentor/my")
@RestController
public class MentorMyPageController {

private final MentorMyPageService mentorMyPageService;

@RequireRoleAccess(roles = Role.MENTOR)
@RequireRoleAccess(roles = {Role.MENTOR, Role.TEMP_MENTOR})
@GetMapping
public ResponseEntity<MentorMyPageResponse> getMentorMyPage(
@AuthorizedUser long siteUserId
Expand All @@ -31,7 +35,7 @@ public ResponseEntity<MentorMyPageResponse> getMentorMyPage(
return ResponseEntity.ok(mentorMyPageResponse);
}

@RequireRoleAccess(roles = Role.MENTOR)
@RequireRoleAccess(roles = {Role.MENTOR, Role.TEMP_MENTOR})
@PutMapping
public ResponseEntity<Void> updateMentorMyPage(
@AuthorizedUser long siteUserId,
Expand All @@ -40,4 +44,14 @@ public ResponseEntity<Void> updateMentorMyPage(
mentorMyPageService.updateMentorMyPage(siteUserId, mentorMyPageUpdateRequest);
return ResponseEntity.ok().build();
}

@RequireRoleAccess(roles = {Role.MENTOR, Role.TEMP_MENTOR})
@PostMapping
public ResponseEntity<Void> createMentorMyPage(
@AuthorizedUser long siteUserId,
@Valid @RequestBody MentorMyPageCreateRequest request
) {
mentorMyPageService.createMentorMyPage(siteUserId, request);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
Expand Down Expand Up @@ -33,17 +35,17 @@ public class Mentor extends BaseEntity {
@Column
private boolean hasBadge = false;

@Column(length = 1000, nullable = false)
@Column(length = 1000)
private String introduction;

@Column(length = 1000, nullable = false)
@Column(length = 1000)
private String passTip;

@Column
private long siteUserId;

@Column
private long universityId;
private Long universityId; // 임시 멘토일 때, null 가능

@Column(nullable = false, name = "term_id")
private long termId;
Expand All @@ -53,6 +55,25 @@ public class Mentor extends BaseEntity {
@OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Channel> channels = new ArrayList<>();

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private MentorStatus mentorStatus;

public Mentor(
String introduction,
String passTip,
long siteUserId,
Long universityId,
long termId
) {
this.introduction = introduction;
this.passTip = passTip;
this.siteUserId = siteUserId;
this.universityId = universityId;
this.termId = termId;
this.mentorStatus = MentorStatus.TEMPORARY;
}

public void increaseMenteeCount() {
this.menteeCount++;
}
Expand Down Expand Up @@ -82,4 +103,15 @@ public void updateChannels(List<Channel> channels) {
}
}
}
}

public void createChannels(List<Channel> channels) {
for(Channel channel : channels) {
channel.updateMentor(this);
this.channels.add(channel);
}
}

public void approve(){
this.mentorStatus = MentorStatus.APPROVED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public class MentorApplication extends BaseEntity {
@Column(nullable = false, name = "mentor_proof_url", length = 500)
private String mentorProofUrl;

@Column(length = 50, nullable = false)
private long termId;

private String rejectedReason;

@Column(nullable = false)
Expand All @@ -72,6 +75,7 @@ public MentorApplication(
Long universityId,
UniversitySelectType universitySelectType,
String mentorProofUrl,
long termId,
ExchangeStatus exchangeStatus
) {
validateExchangeStatus(exchangeStatus);
Expand All @@ -82,6 +86,7 @@ public MentorApplication(
this.universityId = universityId;
this.universitySelectType = universitySelectType;
this.mentorProofUrl = mentorProofUrl;
this.termId = termId;
this.exchangeStatus = exchangeStatus;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.solidconnection.mentor.domain;

public enum MentorStatus {

TEMPORARY,
APPROVED,
;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum UniversitySelectType {

CATALOG,
OTHER
OTHER,
;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public record MentorApplicationRequest(
ExchangeStatus exchangeStatus,
UniversitySelectType universitySelectType,
String country,
Long universityId
Long universityId,
String term
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.solidconnection.mentor.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import java.util.List;

public record MentorMyPageCreateRequest(
@NotBlank(message = "자기소개를 입력해주세요.")
String introduction,

@NotBlank(message = "합격 레시피를 입력해주세요.")
String passTip,

@Valid
List<ChannelRequest> channels
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import com.example.solidconnection.mentor.domain.MentorApplication;
import com.example.solidconnection.mentor.domain.MentorApplicationStatus;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MentorApplicationRepository extends JpaRepository<MentorApplication, Long> {

boolean existsBySiteUserIdAndMentorApplicationStatusIn(long siteUserId, List<MentorApplicationStatus> mentorApplicationStatuses);

Optional<MentorApplication> findBySiteUserId(long siteUserId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ public Map<Long, String> getTermIdToNameMap(List<Mentor> mentors) {
return termRepository.findAllById(termIds).stream()
.collect(Collectors.toMap(Term::getId, Term::getName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.example.solidconnection.location.region.domain.Region;
import com.example.solidconnection.mentor.domain.Mentor;
import com.example.solidconnection.mentor.domain.MentorStatus;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
Expand All @@ -15,12 +17,15 @@ public interface MentorRepository extends JpaRepository<Mentor, Long> {

Optional<Mentor> findBySiteUserId(long siteUserId);

Slice<Mentor> findAllBy(Pageable pageable);
Optional<Mentor> findByIdAndMentorStatus(long id, MentorStatus mentorStatus);

Slice<Mentor> findAllByMentorStatus(MentorStatus mentorStatus, Pageable pageable);

@Query("""
SELECT m FROM Mentor m
JOIN University u ON m.universityId = u.id
WHERE u.region = :region
AND m.mentorStatus = :mentorStatus
""")
Slice<Mentor> findAllByRegion(@Param("region") Region region, Pageable pageable);
Slice<Mentor> findAllByRegionAndMentorStatus(@Param("region") Region region, @Param("mentorStatus") MentorStatus mentorStatus, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import com.example.solidconnection.s3.domain.ImgType;
import com.example.solidconnection.s3.dto.UploadedFileUrlResponse;
import com.example.solidconnection.s3.service.S3Service;
import com.example.solidconnection.siteuser.domain.Role;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import com.example.solidconnection.term.domain.Term;
import com.example.solidconnection.term.repository.TermRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -18,6 +21,7 @@
import org.springframework.web.multipart.MultipartFile;

import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_APPLICATION_ALREADY_EXISTED;
import static com.example.solidconnection.common.exception.ErrorCode.TERM_NOT_FOUND;
import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;

@Service
Expand All @@ -26,6 +30,7 @@
public class MentorApplicationService {

private final MentorApplicationRepository mentorApplicationRepository;
private final TermRepository termRepository;
private final SiteUserRepository siteUserRepository;
private final S3Service s3Service;

Expand All @@ -35,24 +40,40 @@ public void submitMentorApplication(
MentorApplicationRequest mentorApplicationRequest,
MultipartFile file
) {
if (mentorApplicationRepository.existsBySiteUserIdAndMentorApplicationStatusIn(
siteUserId,
List.of(MentorApplicationStatus.PENDING, MentorApplicationStatus.APPROVED))
) {
throw new CustomException(MENTOR_APPLICATION_ALREADY_EXISTED);
}
ensureNoPendingOrApprovedMentorApplication(siteUserId);

SiteUser siteUser = siteUserRepository.findById(siteUserId)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
Term term = termRepository.findByName(mentorApplicationRequest.term())
.orElseThrow(() -> new CustomException(TERM_NOT_FOUND));
Comment on lines +47 to +48
Copy link

@coderabbitai coderabbitai bot Nov 5, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Term의 유효성 검증을 확인해 주세요.

현재 Term을 이름으로만 조회하고 있습니다. Term이 현재 활성화 상태인지, 신청 가능한 기간인지 등의 추가 검증이 필요한지 확인이 필요합니다.

다음 스크립트로 Term 도메인의 상태 관리 로직을 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# Term 엔티티의 상태 필드와 검증 로직 확인
ast-grep --pattern $'class Term {
  $$$
}'

# Term 관련 검증 로직 확인
rg -n "Term.*valid\|Term.*active\|Term.*status" --type java -C 3

Length of output: 0


더 체계적으로 검증하겠습니다. 새로운 스크립트로 Term 엔티티와 검증 로직을 찾아보겠습니다.

</run_scripts]


🏁 Script executed:

#!/bin/bash
# Term 엔티티 파일 위치 찾기
echo "=== Term 클래스 찾기 ==="
fd -e java | xargs grep -l "^class Term"

# Term 엔티티 전체 구조 확인
echo "=== Term 엔티티 구조 ==="
fd -e java | xargs grep -l "class Term" | head -1 | xargs cat

# MentorApplicationService 전체 맥락 확인
echo "=== MentorApplicationService 전체 코드 ==="
fd "MentorApplicationService.java" -e java | xargs cat

Length of output: 4769


현재 Term의 활성화 상태 검증을 추가해주세요.

검증 결과, Term 엔티티는 isCurrent 필드를 통해 활성화 상태를 관리하도록 설계되어 있으나, MentorApplicationService에서는 Term 이름으로만 조회하고 있습니다. 이는 비활성 또는 지난 학기의 Term으로 멘토 신청이 가능하게 하므로 문제입니다.

필요한 변경 사항:

  1. termRepository.findByName() 조회 후 term.isCurrent() 상태를 확인하거나, TermRepository에 findCurrentByName() 같은 전용 메서드를 추가해주세요.

  2. 현재 활성화되지 않은 Term으로 신청하는 경우 적절한 예외(예: TERM_NOT_ACTIVE 또는 유사한 에러코드)를 던져주세요.

이렇게 하면 애플리케이션이 올바른 시기의 Term으로만 멘토 신청을 받을 수 있습니다.

🤖 Prompt for AI Agents
In
src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java
around lines 47-48, after retrieving Term with
termRepository.findByName(...).orElseThrow(...), verify the Term is active by
checking term.isCurrent() (or replace the lookup with a new repository method
like findCurrentByName that only returns current Terms); if the Term is not
active throw a CustomException with an appropriate error code such as
TERM_NOT_ACTIVE so that mentor applications are only accepted for the currently
active Term.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

이건 멘토 지원하는 유저의 멘토 자격을 확인하기 위함이라 최신 학기가 강제되어야 하는 부분이 아니야

Copy link

Choose a reason for hiding this comment

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

@sukangpunch, 이해했습니다! 말씀하신 대로 이 필드는 유저가 교환학생을 다녀온 학기를 기록하기 위한 것이므로, 반드시 현재 활성 학기여야 할 필요가 없네요. 과거 학기 정보로도 멘토 자격 검증이 가능하니 현재 구현이 적절합니다.

설명해 주셔서 감사합니다! 👍

UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.MENTOR_PROOF);

MentorApplication mentorApplication = new MentorApplication(
siteUser.getId(),
mentorApplicationRequest.country(),
mentorApplicationRequest.universityId(),
mentorApplicationRequest.universitySelectType(),
uploadedFile.fileUrl(),
term.getId(),
mentorApplicationRequest.exchangeStatus()
);
mentorApplicationRepository.save(mentorApplication);

promoteRoleMenteeToTempMentor(siteUser);
}

private void ensureNoPendingOrApprovedMentorApplication(long siteUserId) {
if (mentorApplicationRepository.existsBySiteUserIdAndMentorApplicationStatusIn(
siteUserId,
List.of(MentorApplicationStatus.PENDING, MentorApplicationStatus.APPROVED))
) {
throw new CustomException(MENTOR_APPLICATION_ALREADY_EXISTED);
}
}

private void promoteRoleMenteeToTempMentor(SiteUser siteUser) {
if(siteUser.getRole() == Role.MENTEE){
siteUser.changeRole(Role.TEMP_MENTOR);
}
}
}
}
Loading
Loading