Skip to content

Commit 87cbaae

Browse files
authored
[FEAT] 제출 완료된 지원서 목록 조회 (#308)
* feat: 제출된 지원서 수 목록 조회 DTO 구현 * feat: 제출된 지원서 수 목록 조회 쿼리 메서드 구현 * feat: 제출된 지원서 수 목록 조회 쿼리 메서드 구현 * refactor: 카운트 쿼리 메서드의 대소문자 통일 * feat: 제출된 지원서 목록 조회 API 스펙 구현 * feat: 제출된 지원서 목록 조회 서비스 로직 구현 * feat: 제출된 지원서 목록 조회 API 엔드포인트 추가 * refactor: 제출된 지원서 수 조회 메서드에 트랜잭션 읽기 전용 설정 추가 * refactor: 카운트 쿼리에 member 조인 추가 * refactor: NPE 방지를 위한 null 체크 로직 추가 * refactor: 컬렉션 불변화 및 null safety를 위한 로직 추가 * refactor: JPAQueryFactory로 변경 * refactor: NPE 방지를 위한 null safe 처리 로직 추가
1 parent dbd1eaa commit 87cbaae

10 files changed

Lines changed: 563 additions & 2 deletions

File tree

src/main/java/org/ject/support/domain/admin/controller/SubmittedApplyApiSpec.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
import jakarta.validation.Valid;
66
import org.ject.support.domain.admin.dto.SubmittedApplyBulkDeleteRequest;
77
import org.ject.support.domain.admin.dto.SubmittedApplyCountResponse;
8+
import org.ject.support.domain.admin.dto.SubmittedApplyResponse;
9+
import org.ject.support.domain.member.JobFamily;
10+
import org.springframework.data.domain.Page;
11+
import org.springframework.data.domain.Pageable;
12+
import org.springframework.data.web.PageableDefault;
813
import org.springframework.web.bind.annotation.PathVariable;
914
import org.springframework.web.bind.annotation.RequestBody;
15+
import org.springframework.web.bind.annotation.RequestParam;
1016

1117
@Tag(name = "Submitted Apply", description = "제출된 지원서 관리 API")
1218
public interface SubmittedApplyApiSpec {
@@ -16,6 +22,12 @@ public interface SubmittedApplyApiSpec {
1622
description = "제출된 상태의 지원서 총 개수를 조회합니다.")
1723
SubmittedApplyCountResponse getSubmittedApplyCount();
1824

25+
@Operation(
26+
summary = "제출된 지원서 목록 조회",
27+
description = "제출된 지원서들의 목록을 조회합니다.")
28+
Page<SubmittedApplyResponse> findSubmittedApplies(@RequestParam(required = false) final JobFamily jobFamily,
29+
@PageableDefault(size = 15) final Pageable pageable);
30+
1931
@Operation(
2032
summary = "제출된 지원서 삭제",
2133
description = "선택한 제출된 지원서를 삭제합니다.")

src/main/java/org/ject/support/domain/admin/controller/SubmittedApplyController.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
import lombok.RequiredArgsConstructor;
55
import org.ject.support.domain.admin.dto.SubmittedApplyBulkDeleteRequest;
66
import org.ject.support.domain.admin.dto.SubmittedApplyCountResponse;
7+
import org.ject.support.domain.admin.dto.SubmittedApplyResponse;
78
import org.ject.support.domain.admin.service.SubmittedApplyService;
9+
import org.ject.support.domain.member.JobFamily;
10+
import org.springframework.data.domain.Page;
11+
import org.springframework.data.domain.Pageable;
12+
import org.springframework.data.web.PageableDefault;
813
import org.springframework.security.access.prepost.PreAuthorize;
914
import org.springframework.web.bind.annotation.DeleteMapping;
1015
import org.springframework.web.bind.annotation.GetMapping;
1116
import org.springframework.web.bind.annotation.PathVariable;
1217
import org.springframework.web.bind.annotation.RequestBody;
1318
import org.springframework.web.bind.annotation.RequestMapping;
19+
import org.springframework.web.bind.annotation.RequestParam;
1420
import org.springframework.web.bind.annotation.RestController;
1521

1622
@RestController
@@ -27,6 +33,14 @@ public SubmittedApplyCountResponse getSubmittedApplyCount() {
2733
return submittedApplyService.countSubmittedApply();
2834
}
2935

36+
@Override
37+
@GetMapping
38+
@PreAuthorize("hasRole('ROLE_ADMIN')")
39+
public Page<SubmittedApplyResponse> findSubmittedApplies(@RequestParam(required = false) final JobFamily jobFamily,
40+
@PageableDefault(size = 15) final Pageable pageable) {
41+
return submittedApplyService.findSubmittedApplies(jobFamily, pageable);
42+
}
43+
3044
@Override
3145
@DeleteMapping("/{applyId}")
3246
@PreAuthorize("hasRole('ROLE_ADMIN')")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.ject.support.domain.admin.dto;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import org.ject.support.domain.apply.dto.ApplyPortfolioDto;
7+
8+
public record ApplicationFormResponse(Map<String, String> answers,
9+
List<ApplyPortfolioDto> portfolios) {
10+
public ApplicationFormResponse {
11+
answers = Map.copyOf(
12+
Optional.ofNullable(answers)
13+
.orElse(Map.of()));
14+
portfolios = List.copyOf(
15+
Optional.ofNullable(portfolios)
16+
.orElse(List.of()));
17+
}
18+
public static ApplicationFormResponse from(final Map<String, String> answers,
19+
final List<ApplyPortfolioDto> portfolios) {
20+
return new ApplicationFormResponse(answers, portfolios);
21+
}
22+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.ject.support.domain.admin.dto;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import org.ject.support.domain.apply.domain.ApplicationForm;
7+
import org.ject.support.domain.apply.domain.Apply;
8+
import org.ject.support.domain.apply.dto.ApplyPortfolioDto;
9+
import org.ject.support.domain.member.JobFamily;
10+
11+
public record SubmittedApplyResponse(
12+
Long applyId,
13+
String name,
14+
String phoneNumber,
15+
String email,
16+
JobFamily jobFamily,
17+
boolean hasPortfolio,
18+
ApplicationFormResponse applicationFormResponse
19+
) {
20+
public static SubmittedApplyResponse from(Apply apply,
21+
Map<String, String> answers,
22+
List<ApplyPortfolioDto> portfolios) {
23+
return new SubmittedApplyResponse(
24+
apply.getId(),
25+
apply.getMember().getName(),
26+
apply.getMember().getPhoneNumber(),
27+
apply.getMember().getEmail(),
28+
apply.getMember().getJobFamily(),
29+
Optional.ofNullable(apply.getApplicationForm())
30+
.map(ApplicationForm::getPortfolios)
31+
.map(portfolioList -> !portfolioList.isEmpty())
32+
.orElse(false),
33+
ApplicationFormResponse.from(answers, portfolios)
34+
);
35+
}
36+
}

src/main/java/org/ject/support/domain/admin/service/SubmittedApplyService.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
package org.ject.support.domain.admin.service;
22

33
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Optional;
46
import lombok.RequiredArgsConstructor;
7+
import org.ject.support.common.data.PageResponse;
8+
import org.ject.support.common.util.String2MapSerializer;
59
import org.ject.support.domain.admin.dto.SubmittedApplyCountResponse;
10+
import org.ject.support.domain.admin.dto.SubmittedApplyResponse;
11+
import org.ject.support.domain.apply.domain.ApplicationForm;
612
import org.ject.support.domain.apply.domain.Apply;
713
import org.ject.support.domain.apply.domain.Apply.Status;
14+
import org.ject.support.domain.apply.dto.ApplyPortfolioDto;
815
import org.ject.support.domain.apply.exception.ApplyErrorCode;
916
import org.ject.support.domain.apply.exception.ApplyException;
1017
import org.ject.support.domain.apply.repository.ApplyRepository;
18+
import org.ject.support.domain.member.JobFamily;
1119
import org.ject.support.domain.member.entity.Member;
20+
import org.springframework.data.domain.Page;
21+
import org.springframework.data.domain.Pageable;
1222
import org.springframework.stereotype.Service;
1323
import org.springframework.transaction.annotation.Transactional;
1424

@@ -17,7 +27,37 @@
1727
public class SubmittedApplyService {
1828

1929
private final ApplyRepository applyRepository;
30+
private final String2MapSerializer string2MapSerializer;
2031

32+
@Transactional(readOnly = true)
33+
public Page<SubmittedApplyResponse> findSubmittedApplies(final JobFamily jobFamily,
34+
final Pageable pageable) {
35+
Page<Apply> applyPage = applyRepository.findSubmittedApplies(jobFamily, pageable);
36+
37+
List<SubmittedApplyResponse> content = applyPage.getContent().stream()
38+
.map(this::toSubmittedApplyResponse)
39+
.toList();
40+
41+
return PageResponse.from(content, pageable, applyPage.getTotalElements());
42+
}
43+
44+
private SubmittedApplyResponse toSubmittedApplyResponse(final Apply apply) {
45+
ApplicationForm applicationForm = apply.getApplicationForm();
46+
Map<String, String> content = Optional.ofNullable(applicationForm)
47+
.map(ApplicationForm::getContent)
48+
.map(string2MapSerializer::serializeAsMap)
49+
.orElse(Map.of());
50+
List<ApplyPortfolioDto> portfolios = Optional.ofNullable(applicationForm)
51+
.map(ApplicationForm::getPortfolios)
52+
.orElse(List.of())
53+
.stream()
54+
.map(ApplyPortfolioDto::from)
55+
.toList();
56+
57+
return SubmittedApplyResponse.from(apply, content, portfolios);
58+
}
59+
60+
@Transactional(readOnly = true)
2161
public SubmittedApplyCountResponse countSubmittedApply() {
2262
Long count = applyRepository.countByStatus(Status.SUBMITTED);
2363
return new SubmittedApplyCountResponse(count);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.ject.support.domain.apply.repository;
2+
3+
import org.ject.support.domain.apply.domain.Apply;
4+
import org.ject.support.domain.member.JobFamily;
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
7+
8+
public interface ApplyQueryRepository {
9+
Page<Apply> findSubmittedApplies(final JobFamily jobFamily,
10+
final Pageable pageable);
11+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.ject.support.domain.apply.repository;
2+
3+
import static org.ject.support.domain.apply.domain.Apply.Status.SUBMITTED;
4+
import static org.ject.support.domain.apply.domain.QApplicationForm.applicationForm;
5+
import static org.ject.support.domain.apply.domain.QApply.apply;
6+
import static org.ject.support.domain.apply.domain.QPortfolio.portfolio;
7+
import static org.ject.support.domain.member.entity.QMember.member;
8+
9+
import com.querydsl.core.types.dsl.BooleanExpression;
10+
import com.querydsl.jpa.impl.JPAQueryFactory;
11+
import java.util.List;
12+
import java.util.Optional;
13+
import lombok.RequiredArgsConstructor;
14+
import org.ject.support.common.data.PageResponse;
15+
import org.ject.support.domain.apply.domain.Apply;
16+
import org.ject.support.domain.member.JobFamily;
17+
import org.springframework.data.domain.Page;
18+
import org.springframework.data.domain.Pageable;
19+
import org.springframework.stereotype.Repository;
20+
21+
@Repository
22+
@RequiredArgsConstructor
23+
public class ApplyQueryRepositoryImpl implements ApplyQueryRepository {
24+
25+
private final JPAQueryFactory queryFactory;
26+
27+
@Override
28+
public Page<Apply> findSubmittedApplies(final JobFamily jobFamily,
29+
final Pageable pageable) {
30+
List<Apply> content = queryFactory
31+
.selectFrom(apply)
32+
.distinct()
33+
.join(apply.member, member).fetchJoin()
34+
.join(apply.applicationForm, applicationForm).fetchJoin()
35+
.leftJoin(apply.applicationForm.portfolios, portfolio).fetchJoin()
36+
.where(
37+
apply.member.isDeleted.eq(false),
38+
eqJobFamily(jobFamily),
39+
apply.status.eq(SUBMITTED)
40+
)
41+
.orderBy(apply.createdAt.desc())
42+
.offset(pageable.getOffset())
43+
.limit(pageable.getPageSize())
44+
.fetch();
45+
46+
Long total = queryFactory
47+
.select(apply.count())
48+
.from(apply)
49+
.join(apply.member, member)
50+
.where(
51+
apply.member.isDeleted.eq(false),
52+
eqJobFamily(jobFamily),
53+
apply.status.eq(SUBMITTED)
54+
).fetchOne();
55+
56+
57+
return PageResponse.from(content, pageable, total);
58+
}
59+
60+
private BooleanExpression eqJobFamily(final JobFamily jobFamily) {
61+
return Optional.ofNullable(jobFamily)
62+
.map(apply.member.jobFamily::eq)
63+
.orElse(null);
64+
}
65+
}

src/main/java/org/ject/support/domain/apply/repository/ApplyRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.util.Optional;
1010
import org.springframework.data.repository.query.Param;
1111

12-
public interface ApplyRepository extends JpaRepository<Apply, Long> {
12+
public interface ApplyRepository extends JpaRepository<Apply, Long>, ApplyQueryRepository {
1313

1414
@Query("select a from Apply a where a.member.id = :memberId")
1515
Optional<Apply> findByMemberId(Long memberId);
@@ -22,6 +22,6 @@ public interface ApplyRepository extends JpaRepository<Apply, Long> {
2222
@Query("select a from Apply a join fetch a.member m where a.id in :applyIds and a.status = :status")
2323
List<Apply> findAllByIdAndStatusWithMember(@Param("applyIds") List<Long> applyIds, @Param("status") Apply.Status status);
2424

25-
@Query("select COUNT(a) FROM Apply a WHERE a.status = :status")
25+
@Query("select count(a) from Apply a where a.status = :status")
2626
Long countByStatus(@Param("status") Apply.Status status);
2727
}

0 commit comments

Comments
 (0)