Skip to content

Commit 0228d1e

Browse files
authored
feat/#42 자격증 및 졸업 논문 제출 API (#287)
* feat: schedule ID 유효 여부 검사 * feat: 졸업 논문 제출 API * feat: 자격증 제출 API * feat: 코드 리뷰 반영
1 parent 010688b commit 0228d1e

File tree

25 files changed

+364
-0
lines changed

25 files changed

+364
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package kgu.developers.api.certificate.presentation;
2+
3+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.RequestPart;
7+
import org.springframework.web.multipart.MultipartFile;
8+
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.Parameter;
11+
import io.swagger.v3.oas.annotations.media.Content;
12+
import io.swagger.v3.oas.annotations.media.Schema;
13+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
14+
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import kgu.developers.api.certificate.presentation.request.CertificateSubmitRequest;
16+
import kgu.developers.api.certificate.presentation.response.CertificatePersistResponse;
17+
18+
@Tag(name = "Certificate", description = "자격증 관련 API")
19+
public interface CertificateController {
20+
21+
@Operation(
22+
summary = "자격증 파일 제출 API", description = """
23+
- Description : 이 API는 자격증 파일을 제출합니다.
24+
- Assignee : 이한음
25+
"""
26+
)
27+
@ApiResponse(
28+
responseCode = "200", content = @Content(schema = @Schema(implementation = CertificatePersistResponse.class))
29+
)
30+
ResponseEntity<CertificatePersistResponse> submitCertificateAndSaveFile(
31+
@Parameter(
32+
description = "자격증 첨부 파일",
33+
content = @Content(mediaType = MULTIPART_FORM_DATA_VALUE),
34+
required = true
35+
) @RequestPart(value = "file") MultipartFile file,
36+
@RequestPart CertificateSubmitRequest request
37+
);
38+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package kgu.developers.api.certificate.presentation;
2+
3+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RequestPart;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import org.springframework.web.multipart.MultipartFile;
11+
12+
import kgu.developers.api.certificate.presentation.request.CertificateSubmitRequest;
13+
import kgu.developers.api.certificate.presentation.response.CertificatePersistResponse;
14+
import kgu.developers.domain.certificate.application.command.CertificateCommandService;
15+
import lombok.RequiredArgsConstructor;
16+
17+
@RestController
18+
@RequestMapping("/api/v1/certificate")
19+
@RequiredArgsConstructor
20+
public class CertificateControllerImpl implements CertificateController {
21+
private final CertificateCommandService certificateCommandService;
22+
23+
@Override
24+
@PostMapping(consumes = MULTIPART_FORM_DATA_VALUE)
25+
public ResponseEntity<CertificatePersistResponse> submitCertificateAndSaveFile(
26+
@RequestPart(value = "file") MultipartFile file,
27+
@RequestPart CertificateSubmitRequest request
28+
) {
29+
Long id = certificateCommandService.submitCertificate(file, request.scheduleId());
30+
return ResponseEntity.ok(
31+
CertificatePersistResponse.of(id)
32+
);
33+
}
34+
35+
36+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package kgu.developers.api.certificate.presentation.request;
2+
3+
public record CertificateSubmitRequest(
4+
Long scheduleId
5+
) {
6+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package kgu.developers.api.certificate.presentation.response;
2+
3+
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
8+
@Builder
9+
public record CertificatePersistResponse(
10+
@Schema(description = "자격증 객체 id", example = "1", requiredMode = REQUIRED)
11+
Long id
12+
) {
13+
public static CertificatePersistResponse of(Long id) {
14+
return CertificatePersistResponse.builder().id(id).build();
15+
}
16+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package kgu.developers.api.thesis.presentation;
2+
3+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.RequestPart;
7+
import org.springframework.web.multipart.MultipartFile;
8+
9+
import io.swagger.v3.oas.annotations.Operation;
10+
import io.swagger.v3.oas.annotations.Parameter;
11+
import io.swagger.v3.oas.annotations.media.Content;
12+
import io.swagger.v3.oas.annotations.media.Schema;
13+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
14+
import io.swagger.v3.oas.annotations.tags.Tag;
15+
import kgu.developers.api.thesis.presentation.request.ThesisSubmitRequest;
16+
import kgu.developers.api.thesis.presentation.response.ThesisPersistResponse;
17+
18+
@Tag(name = "Thesis", description = "졸업 논문 API")
19+
public interface ThesisController {
20+
21+
@Operation(
22+
summary = "졸업 논문 파일 제출 API", description = """
23+
- Description : 이 API는 졸업 논문 파일을 제출합니다.
24+
- Assignee : 이한음
25+
""")
26+
@ApiResponse(
27+
responseCode = "201",
28+
content = @Content(schema = @Schema(implementation = ThesisPersistResponse.class)))
29+
ResponseEntity<ThesisPersistResponse> submitThesisAndSaveFile(
30+
@Parameter(
31+
description = "졸업 논문 첨부 파일",
32+
content = @Content(mediaType = MULTIPART_FORM_DATA_VALUE),
33+
required = true
34+
) @RequestPart(value = "file") MultipartFile file,
35+
@RequestPart ThesisSubmitRequest request
36+
);
37+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package kgu.developers.api.thesis.presentation;
2+
3+
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RequestPart;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import org.springframework.web.multipart.MultipartFile;
11+
12+
import kgu.developers.api.thesis.presentation.request.ThesisSubmitRequest;
13+
import kgu.developers.api.thesis.presentation.response.ThesisPersistResponse;
14+
import kgu.developers.domain.thesis.application.command.ThesisCommandService;
15+
import lombok.RequiredArgsConstructor;
16+
17+
@RestController
18+
@RequestMapping("/api/v1/thesis")
19+
@RequiredArgsConstructor
20+
public class ThesisControllerImpl implements ThesisController {
21+
private final ThesisCommandService thesisCommandService;
22+
23+
@Override
24+
@PostMapping(consumes = MULTIPART_FORM_DATA_VALUE)
25+
public ResponseEntity<ThesisPersistResponse> submitThesisAndSaveFile(
26+
@RequestPart(value = "file") MultipartFile file,
27+
@RequestPart ThesisSubmitRequest request
28+
) {
29+
Long id = thesisCommandService.submitThesis(file, request.scheduleId());
30+
return ResponseEntity.ok(
31+
ThesisPersistResponse.of(id)
32+
);
33+
}
34+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package kgu.developers.api.thesis.presentation.request;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
5+
public record ThesisSubmitRequest(
6+
@NotNull(message = "졸업 논문 일정 id는 필수입니다.")
7+
Long scheduleId
8+
) {
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package kgu.developers.api.thesis.presentation.response;
2+
3+
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
8+
@Builder
9+
public record ThesisPersistResponse(
10+
@Schema(description = "졸업 논문 객체 id", example = "1", requiredMode = REQUIRED)
11+
Long id
12+
) {
13+
public static ThesisPersistResponse of(Long id) {
14+
return ThesisPersistResponse.builder().id(id).build();
15+
}
16+
}

aics-common/src/main/java/kgu/developers/common/config/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
8383
"/api/v1/labs",
8484
"/api/v1/comments",
8585
"/api/v1/carousels",
86+
"/api/v1/thesis",
87+
"/api/v1/certificate",
8688
"/api/v1/schedules",
8789
};
8890

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package kgu.developers.domain.certificate.application.command;
2+
3+
import org.springframework.stereotype.Service;
4+
import org.springframework.web.multipart.MultipartFile;
5+
6+
import kgu.developers.domain.certificate.domain.Certificate;
7+
import kgu.developers.domain.certificate.domain.CertificateRepository;
8+
import kgu.developers.domain.file.application.command.FileCommandService;
9+
import kgu.developers.domain.file.domain.FileDomain;
10+
import kgu.developers.domain.file.infrastructure.repository.FileStorageService;
11+
import kgu.developers.domain.schedule.application.query.ScheduleQueryService;
12+
import lombok.RequiredArgsConstructor;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
public class CertificateCommandService {
17+
private final CertificateRepository certificateRepository;
18+
private final FileStorageService fileStorageService;
19+
private final FileCommandService fileCommandService;
20+
private final ScheduleQueryService scheduleQueryService;
21+
22+
public Long submitCertificate(MultipartFile file, Long scheduleId) {
23+
String storedPath = fileStorageService.store(file, FileDomain.CERTIFICATE);
24+
Long fileId = fileCommandService.saveFile(file, storedPath).getId();
25+
scheduleQueryService.checkExistsOrThrow(scheduleId);
26+
27+
Certificate certificate = Certificate.create(scheduleId, fileId);
28+
return certificateRepository.save(certificate);
29+
}
30+
31+
}

0 commit comments

Comments
 (0)