Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d49f58e
feat: 나의 최근 한달동안 기록전 면접 조회
kckc0608 Feb 13, 2026
979f7ca
test: 나의 최근 한 달 기록전 면접 리스트 조회 테스트 작성
kckc0608 Feb 13, 2026
a0bb76a
[DEV-234] feat: 데스크탑 record 페이지 개발 (#360)
forhyundaisofteer Feb 13, 2026
d236440
[DEV-220/FE] feat: 자소서 연동 mock-api 연결 (#356)
HIHJH Feb 13, 2026
1d1876e
[DEV-221/FE] feat: 스크랩 모달 UI 구현 및 회고 작성 페이지 일부 mock-api 연결 (#359)
HIHJH Feb 13, 2026
eed6c48
feat: 프론트 요청에 따라 dto 응답에 review status 를 응답하도록 추가 (#367)
kckc0608 Feb 13, 2026
4b7a8df
[DEV-257/BE] docs: 모든 API 응답의 필수 필드에 @NotNull 어노테이션 추가 (#376)
kckc0608 Feb 15, 2026
be30e1f
[DEV-258/FE] fix: 스크롤 폴더 이름 입력 안되는 이슈 수정 (#374)
HIHJH Feb 15, 2026
4b8d92f
[DEV-261/BE] refactor: Swagger의 Pageable 객체 렌더링 개선 (#382)
lja3723 Feb 15, 2026
239ea6c
[DEV-263/BE] feat: 사용자 조회 API 응답에 동의 여부 필드 추가 (#386)
kckc0608 Feb 15, 2026
9b93bcc
[DEV-176/BE] test: QnaSetController 테스트 작성 (#369)
zxc534 Feb 15, 2026
5e5e6ea
[DEV-256/BE] test: QnaSetMyController 테스트 작성 (#371)
zxc534 Feb 15, 2026
33273d4
[DEV-188/BE] feat: llm 요청에 대한 인터페이스 설계 및 구현 (#378)
zxc534 Feb 16, 2026
2291a61
refactor: 최근 한달의 정의를 30일에서 실제 월 별 1달로 수정
kckc0608 Feb 16, 2026
fb8fa88
feat: 나의 최근 한달동안 기록전 면접 조회
kckc0608 Feb 13, 2026
96d9a14
test: 나의 최근 한 달 기록전 면접 리스트 조회 테스트 작성
kckc0608 Feb 13, 2026
9391aa9
refactor: 최근 한달의 정의를 30일에서 실제 월 별 1달로 수정
kckc0608 Feb 16, 2026
0425de7
Merge branch 'DEV-224/feat/모바일-뷰에서-기록전-상태의-면접만-조회하는-API-개발' of https:…
kckc0608 Feb 16, 2026
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 @@ -11,6 +11,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -52,4 +53,15 @@ public ResponseEntity<ApiResponse<Page<InterviewSimpleDto>>> getMyInterviewDraft
var response = ApiResponse.success(COMMON200, body);
return ResponseEntity.ok(response);
}

@Operation(summary = "아직 기록하지 않은 나의 면접들을 조회합니다.", description = """
현재일 기준 최근 한 달동안 본 면접 데이터 중 상태가 '기록전' 상태인 면접을 면접일 기준 내림차순으로 조회합니다.
모바일 화면에서 기록할 면접을 조회할 때 사용됩니다.
""")
@GetMapping("/not-logged")
public ResponseEntity<ApiResponse<List<InterviewSimpleDto>>> getMyNotLoggedInterviews() {
var body = interviewService.getMyNotLoggedInterviews();
var response = ApiResponse.success(COMMON200, body);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@

public record InterviewSimpleDto(
Long interviewId,
InterviewType interviewType,
InterviewReviewStatus interviewReviewStatus,
InterviewType interviewType,
LocalDateTime interviewStartAt,
CompanyDto companyInfo,
String jobCategoryName,
LocalDateTime updatedAt) {
public static InterviewSimpleDto from(Interview interview) {
return new InterviewSimpleDto(
interview.getId(),
interview.getInterviewType(),
interview.getReviewStatus(),
interview.getInterviewType(),
interview.getStartAt(),
CompanyDto.from(interview.getCompany()),
interview.getJobCategory().getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.shyashyashya.refit.domain.interview.model.InterviewType;
import com.shyashyashya.refit.domain.user.model.User;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -19,4 +21,6 @@ Page<Interview> searchInterviews(
LocalDate startDate,
LocalDate endDate,
Pageable pageable);

List<Interview> findInterviewsNotLoggedRecentOneMonth(User user, LocalDateTime now);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.shyashyashya.refit.domain.interview.repository.InterviewCustomRepository;
import com.shyashyashya.refit.domain.user.model.User;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -59,6 +60,18 @@ public Page<Interview> searchInterviews(
return new PageImpl<>(interviews, pageable, totalSize);
}

@Override
public List<Interview> findInterviewsNotLoggedRecentOneMonth(User user, LocalDateTime now) {
return jpaQueryFactory
.selectFrom(interview)
.where(
interview.user.eq(user),
interview.reviewStatus.eq(InterviewReviewStatus.NOT_LOGGED),
interview.startAt.between(now.minusDays(30), now))
.orderBy(interview.startAt.desc())
.fetch();
}

private BooleanExpression companyNameContains(String keyword) {
if (keyword == null || keyword.isEmpty()) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.shyashyashya.refit.domain.user.model.User;
import com.shyashyashya.refit.global.exception.CustomException;
import com.shyashyashya.refit.global.util.RequestUserContext;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand Down Expand Up @@ -249,4 +250,13 @@ private Company findOrSaveCompany(InterviewCreateRequest request) {
}
});
}

public List<InterviewSimpleDto> getMyNotLoggedInterviews() {
User requestUser = requestUserContext.getRequestUser();

LocalDateTime now = LocalDateTime.now();
return interviewRepository.findInterviewsNotLoggedRecentOneMonth(requestUser, now).stream()
.map(InterviewSimpleDto::from)
.toList();
}
Comment on lines +254 to +261
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

LocalDateTime.now()를 서비스 레이어에서 직접 호출하면, 시간에 의존적인 로직에 대한 단위 테스트 작성이 어려워집니다. 시간 정보를 외부(예: 컨트롤러)에서 파라미터로 주입받도록 변경하여 테스트 용이성을 높이는 것을 제안합니다. 이렇게 변경하면 컨트롤러에서 interviewService.getMyNotLoggedInterviews(LocalDateTime.now())와 같이 호출해야 합니다.

    public List<InterviewSimpleDto> getMyNotLoggedInterviews(LocalDateTime now) {
        User requestUser = requestUserContext.getRequestUser();
        return interviewRepository.findInterviewsNotLoggedRecentOneMonth(requestUser, now).stream()
                .map(InterviewSimpleDto::from)
                .toList();
    }

Copy link
Collaborator

Choose a reason for hiding this comment

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

이 피드백은 제가 담당한 Jwt Encoder에서도 적용 가능한 것 같아요. 외부 주입으로 대체하는 것은 어떻게 생각하시나요?

Copy link
Collaborator Author

@kckc0608 kckc0608 Feb 14, 2026

Choose a reason for hiding this comment

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

저도 프로젝트하면서 이 부분이 항상 고민이었는데, AI 추천대로 컨트롤러로 빼기에는 '시간' 이라는 로직이 컨트롤러에 담기는 점이 불편하고, E2E 테스트를 할 때는 여전히 테스트가 어렵다는 문제점이 남아있어 AI 방법은 한계가 있는 것 같습니다!

GPT 활용해서 조사했을 때, Clock을 빈으로 주입받은 뒤, 주입받은 clock 을 LocalDateTime 생성 시 매개변수로 넘겨서 시간을 세팅하는 로직을 제안해주었는데, 이 방법이라면 테스트도 용이하고, 서비스 계층에서 시간을 다루는 것도 그대로 유지되어 계층간 책임이 명확하게 유지된다고 보여서 저도 좋은 것 같은데 이 방법은 어떠신가요??

}
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ void createTestData() {

given(spec)
.body(request)
.when()
.when()
.post(path)
.then()
.statusCode(200)
Expand Down Expand Up @@ -288,6 +288,81 @@ void createTestData() {
}
}

@Nested
class 나의_최근_한_달동안_기록을_시작하지_않은_면접_리스트를_조회할_때 {

private final String path = "/interview/my/not-logged";

@Test
void 다른_사람의_면접은_조회되지_않는다() {
User otherUser = createAndSaveUser("other2@example.com", "other2", industry1, jobCategory1);
createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now(), InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED, otherUser);

createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now(), InterviewType.FIRST, company2.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED);

given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result", hasSize(1))
.body("result[0].companyInfo.companyName", equalTo(company2.getName()));
}

@Test
void NOT_LOGGED_상태의_면접만_조회된다() {
createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now(), InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED);

createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now(), InterviewType.SECOND, company1.getName(), industry1.getId(), jobCategory1.getId(), "Engineer"
), InterviewReviewStatus.LOG_DRAFT);

createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now(), InterviewType.THIRD, company1.getName(), industry1.getId(), jobCategory1.getId(), "Manager"
), InterviewReviewStatus.QNA_SET_DRAFT);

createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now(), InterviewType.FIRST, company2.getName(), industry1.getId(), jobCategory1.getId(), "Manager"
), InterviewReviewStatus.DEBRIEF_COMPLETED);

given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result", hasSize(1))
.body("result[0].interviewReviewStatus", equalTo(InterviewReviewStatus.NOT_LOGGED.name()));
}

@Test
void 최근_한달_이내의_면접만_조회된다() {
createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now().minusDays(1), InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED);

createAndSaveInterview(new InterviewCreateRequest(
LocalDateTime.now().minusMonths(2), InterviewType.FIRST, company2.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED);

given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result", hasSize(1))
.body("result[0].companyInfo.companyName", equalTo(company1.getName()));
}
}

@Nested
class 면접_임시저장_데이터를_조회할_때 {

Expand Down
Loading