Skip to content

Commit 010688b

Browse files
authored
feature/#KD-44 졸업 대상자 관련 API 구현 (#288)
* feat: 졸업 대상자 졸업 방식 신청 API 구현 * feat: 졸업 대상자 단일 생성 API 구현 * feat: FE단에 맞도록 엔티티 설계 변경 * fix: 오타 수정, 졸업 대상자 접근 권한 확인 추가 * feat: 졸업 대상자 페이징 조회 API 구현 * feat: 졸업 대상자 삭제 API 구현 * feat: 졸업 대상자 상세 조회 API 구현 * feat: 졸업 대상자 일괄 삭제 api 구현 * feat: 졸업 대상자 엑셀 파일 다운로드 api 구현 * feat: 졸업 대상자 일괄 생성 api 구현 * feat: GraduationUser 테스트 코드 작성 * feat: 이메일 수정 API 구현 * fix: SecurityConfig Public Endpoint 추가 * fix: 테스트 코드 수정 * fix: 코드래빗 리뷰 반영 * fix: 코드래빗 리뷰 반영 * refactor: POI 라이브러리 버전 업그레이드
1 parent 31b80e1 commit 010688b

File tree

45 files changed

+1879
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1879
-4
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package kgu.developers.admin.graduationUser.application;
2+
3+
import kgu.developers.admin.graduationUser.presentation.dto.GraduationUserExcelFileDto;
4+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest;
5+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest;
6+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest;
7+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse;
8+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDeleteResponse;
9+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse;
10+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse;
11+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserSummaryPageResponse;
12+
import kgu.developers.common.response.PaginatedListResponse;
13+
import kgu.developers.domain.graduationUser.application.command.GraduationUserCommandService;
14+
import kgu.developers.domain.graduationUser.application.query.GraduationUserQueryService;
15+
import kgu.developers.domain.graduationUser.domain.GraduationType;
16+
import kgu.developers.domain.graduationUser.domain.GraduationUser;
17+
import lombok.RequiredArgsConstructor;
18+
import org.springframework.data.domain.Pageable;
19+
import org.springframework.stereotype.Component;
20+
import org.springframework.transaction.annotation.Transactional;
21+
22+
import java.time.LocalDateTime;
23+
import java.time.format.DateTimeFormatter;
24+
import java.util.List;
25+
26+
@Component
27+
@Transactional
28+
@RequiredArgsConstructor
29+
public class GraduationUserAdminFacade {
30+
31+
private final GraduationUserCommandService graduationUserCommandService;
32+
private final GraduationUserQueryService graduationUserQueryService;
33+
34+
public GraduationUserPersistResponse createGraduationUser(GraduationUserCreateRequest request) {
35+
Long id = graduationUserCommandService.createGraduationUser(request.studentId(), request.name(), request.advisorProfessor(), request.capstoneCompletion(), request.department(), request.graduationDate());
36+
return GraduationUserPersistResponse.of(id);
37+
}
38+
39+
public GraduationUserBatchCreateResponse createGraduationUsers(GraduationUserBatchCreateRequest request) {
40+
41+
List<Long> ids = request.graduationUsers().stream()
42+
.map(graduationUser -> graduationUserCommandService.createGraduationUser(
43+
graduationUser.studentId(),
44+
graduationUser.name(),
45+
graduationUser.advisorProfessor(),
46+
graduationUser.capstoneCompletion(),
47+
graduationUser.department(),
48+
graduationUser.graduationDate()
49+
))
50+
.toList();
51+
52+
return GraduationUserBatchCreateResponse.from(ids);
53+
}
54+
55+
public GraduationUserSummaryPageResponse getGraduationUsersByNameAndGraduationType(Pageable pageable, String name, GraduationType graduationType) {
56+
PaginatedListResponse<GraduationUser> response = graduationUserQueryService.getGraduationUsersByNameAndGraduationType(pageable,name,graduationType);
57+
return GraduationUserSummaryPageResponse.of(response.contents(), response.pageable());
58+
}
59+
60+
public void deleteGraduationUser(Long id) {
61+
GraduationUser graduationUser = graduationUserQueryService.getById(id);
62+
graduationUserCommandService.deleteGraduationUser(graduationUser);
63+
}
64+
65+
public GraduationUserDetailResponse getGraduationUserById(Long graduationUserId) {
66+
return GraduationUserDetailResponse.from(graduationUserQueryService.getById(graduationUserId));
67+
}
68+
69+
public GraduationUserBatchDeleteResponse deleteGraduationUsers(GraduationUserBatchDeleteRequest request) {
70+
List<GraduationUser> users = request.ids().stream()
71+
.map(graduationUserQueryService::getById)
72+
.toList();
73+
74+
List<Long> deletedUsersIds = users.stream()
75+
.map(graduationUserCommandService::deleteGraduationUser)
76+
.toList();
77+
78+
return GraduationUserBatchDeleteResponse.from(deletedUsersIds);
79+
}
80+
81+
public GraduationUserExcelFileDto getGraduateUsersExcelByGraduationType(GraduationType graduationType) {
82+
83+
byte[] content = graduationUserQueryService.getGraduationUsersExcelByGraduationType(graduationType);
84+
85+
String filename = String.format("graduate_users_%s_%s.xlsx",
86+
graduationType != null ? graduationType.name().toLowerCase() : "all",
87+
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")));
88+
89+
return GraduationUserExcelFileDto.from(content, filename);
90+
}
91+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package kgu.developers.admin.graduationUser.presentation;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import jakarta.validation.Valid;
10+
import jakarta.validation.constraints.Positive;
11+
import jakarta.validation.constraints.PositiveOrZero;
12+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest;
13+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest;
14+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest;
15+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse;
16+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDeleteResponse;
17+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse;
18+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse;
19+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserSummaryPageResponse;
20+
import kgu.developers.admin.lab.presentation.response.LabPersistResponse;
21+
import kgu.developers.domain.graduationUser.domain.GraduationType;
22+
import org.springframework.core.io.Resource;
23+
import org.springframework.http.ResponseEntity;
24+
import org.springframework.web.bind.annotation.PathVariable;
25+
import org.springframework.web.bind.annotation.RequestBody;
26+
import org.springframework.web.bind.annotation.RequestParam;
27+
28+
@Tag(name = "GraduationUser", description = "졸업 대상자 관리자 API")
29+
public interface GraduationUserAdminController {
30+
31+
@Operation(summary = "졸업 대상자 페이징 조회 API", description = """
32+
- Description : 이 API는 졸업 대상자를 페이징 조회하며, 선택적으로 이름과 졸업 방식으로 필터링할 수 있습니다.
33+
- Assignee : 장영후
34+
""")
35+
@ApiResponse(
36+
responseCode = "200",
37+
content = @Content(schema = @Schema(implementation = GraduationUserSummaryPageResponse.class)))
38+
ResponseEntity<GraduationUserSummaryPageResponse> getGraduationUsersByName(
39+
@Parameter(
40+
description = "페이지 인덱스",
41+
example = "0",
42+
required = true
43+
) @PositiveOrZero @RequestParam(defaultValue = "0") int page,
44+
@Parameter(
45+
description = "응답 개수",
46+
example = "10",
47+
required = true
48+
) @Positive @RequestParam(defaultValue = "10") int size,
49+
@Parameter(
50+
description = "유저 이름",
51+
example = "홍길동"
52+
) @RequestParam(required = false) String name,
53+
@Parameter(
54+
description = "졸업 방식 카테고리입니다. 미 지정 시 전체 졸업 대상자를 조회합니다.",
55+
example = "THESIS"
56+
) @RequestParam(required = false) GraduationType graduationType
57+
);
58+
59+
@Operation(summary = "졸업 대상자 엑셀 파일 다운로드 API", description = """
60+
- Description : 이 API는 졸업 대상자를 엑셀 파일 형태로 다운로드 하며, 선택적으로 졸업 방식으로 필터링 할 수 있습니다.
61+
- Assignee : 장영후
62+
""")
63+
@ApiResponse(
64+
responseCode = "200",
65+
content = @Content(mediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
66+
ResponseEntity<Resource> getGraduateUsersExcel(
67+
@Parameter(
68+
description = "졸업 방식 카테고리입니다. 미 지정 시 전체 졸업 대상자를 조회합니다.",
69+
example = "THESIS"
70+
) @RequestParam(required = false) GraduationType graduationType
71+
);
72+
73+
@Operation(summary = "졸업 대상자 상세 조회 API", description = """
74+
- Description : 이 API는 게시글의 상세 정보를 조회합니다.
75+
- Assignee : 장영후
76+
""")
77+
@ApiResponse(
78+
responseCode = "200",
79+
content = @Content(schema = @Schema(implementation = GraduationUserDetailResponse.class)))
80+
ResponseEntity<GraduationUserDetailResponse> getGraduationUserById(
81+
@Parameter(
82+
description = "졸업 대상자 ID는 URL 경로 변수 입니다.",
83+
example = "1",
84+
required = true
85+
) @Positive @PathVariable Long graduationUserId
86+
);
87+
88+
@Operation(summary = "졸업 대상자 단일 생성 API", description = """
89+
- Description : 이 API는 단일 졸업 대상자를 생성합니다.
90+
- Assignee : 장영후
91+
""")
92+
@ApiResponse(
93+
responseCode = "201",
94+
content = @Content(schema = @Schema(implementation = GraduationUserPersistResponse.class)))
95+
ResponseEntity<GraduationUserPersistResponse> createGraduationUser(
96+
@Parameter(
97+
description = "졸업 대상자 단일 생성 request 객체 입니다.",
98+
required = true
99+
) @Valid @RequestBody GraduationUserCreateRequest request
100+
);
101+
102+
@Operation(summary = "졸업 대상자 단일 삭제 API", description = """
103+
- Description : 이 API는 해당 졸업 대상자를 삭제합니다.
104+
- Assignee : 장영후
105+
""")
106+
@ApiResponse(responseCode = "204")
107+
ResponseEntity<Void> deleteGraduationUser(
108+
@Parameter(
109+
description = "졸업 대상자 ID는 URL 경로 변수 입니다.",
110+
example = "1",
111+
required = true
112+
) @Positive @PathVariable Long id
113+
);
114+
115+
@Operation(summary = "졸업 대상자 일괄 생성 API", description = """
116+
- Description : 이 API는 입력한 여러 졸업 대상자를 일괄 생성합니다.
117+
- Assignee : 장영후
118+
""")
119+
@ApiResponse(responseCode = "200")
120+
ResponseEntity<GraduationUserBatchCreateResponse> createGraduationUsers(
121+
@Parameter(
122+
description = "생성 졸업 대상자 단체 생성 request 객체입니다.",
123+
required = true
124+
) @Valid @RequestBody GraduationUserBatchCreateRequest request
125+
);
126+
127+
@Operation(summary = "졸업 대상자 일괄 삭제 API", description = """
128+
- Description : 이 API는 선택한 여러 졸업 대상자를 일괄 삭제합니다.
129+
- Assignee : 장영후
130+
""")
131+
@ApiResponse(responseCode = "200")
132+
ResponseEntity<GraduationUserBatchDeleteResponse> deleteGraduationUsers(
133+
@Parameter(
134+
description = "삭제할 졸업 대상자 ID 목록",
135+
required = true
136+
) @Valid @RequestBody GraduationUserBatchDeleteRequest request
137+
);
138+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package kgu.developers.admin.graduationUser.presentation;
2+
3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.Positive;
5+
import jakarta.validation.constraints.PositiveOrZero;
6+
import kgu.developers.admin.graduationUser.application.GraduationUserAdminFacade;
7+
import kgu.developers.admin.graduationUser.presentation.dto.GraduationUserExcelFileDto;
8+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchCreateRequest;
9+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserBatchDeleteRequest;
10+
import kgu.developers.admin.graduationUser.presentation.request.GraduationUserCreateRequest;
11+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchCreateResponse;
12+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserBatchDeleteResponse;
13+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserDetailResponse;
14+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserPersistResponse;
15+
import kgu.developers.admin.graduationUser.presentation.response.GraduationUserSummaryPageResponse;
16+
import kgu.developers.domain.graduationUser.domain.GraduationType;
17+
import lombok.RequiredArgsConstructor;
18+
import org.springframework.core.io.ByteArrayResource;
19+
import org.springframework.core.io.Resource;
20+
import org.springframework.data.domain.PageRequest;
21+
import org.springframework.http.HttpHeaders;
22+
import org.springframework.http.MediaType;
23+
import org.springframework.http.ResponseEntity;
24+
import org.springframework.security.access.prepost.PreAuthorize;
25+
import org.springframework.web.bind.annotation.DeleteMapping;
26+
import org.springframework.web.bind.annotation.GetMapping;
27+
import org.springframework.web.bind.annotation.PathVariable;
28+
import org.springframework.web.bind.annotation.PostMapping;
29+
import org.springframework.web.bind.annotation.RequestBody;
30+
import org.springframework.web.bind.annotation.RequestMapping;
31+
import org.springframework.web.bind.annotation.RequestParam;
32+
import org.springframework.web.bind.annotation.RestController;
33+
34+
import static org.springframework.http.HttpStatus.CREATED;
35+
36+
@RestController
37+
@RequiredArgsConstructor
38+
@RequestMapping("/api/v1/admin/graduation-users")
39+
@PreAuthorize("hasRole('ROLE_ADMIN')")
40+
public class GraduationUserAdminControllerImpl implements GraduationUserAdminController {
41+
private final GraduationUserAdminFacade graduationUserAdminFacade;
42+
43+
@Override
44+
@GetMapping
45+
public ResponseEntity<GraduationUserSummaryPageResponse> getGraduationUsersByName(
46+
@PositiveOrZero @RequestParam(defaultValue = "0") int page,
47+
@Positive @RequestParam(defaultValue = "10") int size,
48+
@RequestParam (required = false) String name,
49+
@RequestParam (required = false) GraduationType graduationType
50+
) {
51+
GraduationUserSummaryPageResponse response = graduationUserAdminFacade.getGraduationUsersByNameAndGraduationType(PageRequest.of(page,size), name,
52+
graduationType);
53+
return ResponseEntity.ok(response);
54+
}
55+
56+
@Override
57+
@GetMapping("/excel")
58+
public ResponseEntity<Resource> getGraduateUsersExcel(
59+
@RequestParam(required = false) GraduationType graduationType) {
60+
61+
GraduationUserExcelFileDto excelFileDto = graduationUserAdminFacade.getGraduateUsersExcelByGraduationType(graduationType);
62+
63+
return ResponseEntity.ok()
64+
.header(HttpHeaders.CONTENT_DISPOSITION,
65+
"attachment; filename=" + excelFileDto.filename())
66+
.contentType(MediaType.APPLICATION_OCTET_STREAM)
67+
.body(new ByteArrayResource(excelFileDto.content()));
68+
}
69+
70+
@Override
71+
@GetMapping("/{graduationUserId}")
72+
public ResponseEntity<GraduationUserDetailResponse> getGraduationUserById(
73+
@PathVariable @Positive Long graduationUserId
74+
) {
75+
GraduationUserDetailResponse response = graduationUserAdminFacade.getGraduationUserById(graduationUserId);
76+
return ResponseEntity.ok(response);
77+
}
78+
79+
@Override
80+
@PostMapping
81+
public ResponseEntity<GraduationUserPersistResponse> createGraduationUser(
82+
@Valid @RequestBody GraduationUserCreateRequest request
83+
) {
84+
GraduationUserPersistResponse response = graduationUserAdminFacade.createGraduationUser(request);
85+
return ResponseEntity.status(CREATED).body(response);
86+
}
87+
88+
89+
@Override
90+
@PostMapping("/batch")
91+
public ResponseEntity<GraduationUserBatchCreateResponse> createGraduationUsers(
92+
@Valid @RequestBody GraduationUserBatchCreateRequest request
93+
) {
94+
GraduationUserBatchCreateResponse response = graduationUserAdminFacade.createGraduationUsers(request);
95+
return ResponseEntity.status(CREATED).body(response);
96+
}
97+
98+
@Override
99+
@DeleteMapping("/{graduationUserId}")
100+
public ResponseEntity<Void> deleteGraduationUser(
101+
@Positive @PathVariable Long graduationUserId
102+
) {
103+
graduationUserAdminFacade.deleteGraduationUser(graduationUserId);
104+
return ResponseEntity.noContent().build();
105+
}
106+
107+
@Override
108+
@DeleteMapping("/batch")
109+
public ResponseEntity<GraduationUserBatchDeleteResponse> deleteGraduationUsers(
110+
GraduationUserBatchDeleteRequest request
111+
) {
112+
GraduationUserBatchDeleteResponse response = graduationUserAdminFacade.deleteGraduationUsers(request);
113+
return ResponseEntity.ok(response);
114+
}
115+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package kgu.developers.admin.graduationUser.presentation.dto;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record GraduationUserExcelFileDto(
7+
byte[] content,
8+
String filename
9+
) {
10+
public static GraduationUserExcelFileDto from(byte[] content, String filename) {
11+
return GraduationUserExcelFileDto.builder()
12+
.content(content)
13+
.filename(filename)
14+
.build();
15+
}
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package kgu.developers.admin.graduationUser.presentation.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.NotEmpty;
5+
import lombok.Builder;
6+
7+
import java.util.List;
8+
9+
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
10+
11+
@Builder
12+
public record GraduationUserBatchCreateRequest(
13+
@Schema(description = "졸업 대상자 생성 리스트",
14+
example = "[{"
15+
+ "\"studentId\": \"202211461\", "
16+
+ "\"name\": \"홍길동\", "
17+
+ "\"advisorProfessor\": \"김교수\", "
18+
+ "\"capstoneCompletion\": \"true\", "
19+
+ "\"department\": \"컴퓨터공학전공\", "
20+
+ "\"graduationDate\": \"2028-02-01\"}]",
21+
requiredMode = REQUIRED)
22+
@NotEmpty
23+
List<GraduationUserCreateRequest> graduationUsers
24+
) {
25+
}

0 commit comments

Comments
 (0)