Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
5 changes: 5 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ dependencies {
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation platform('software.amazon.awssdk:bom:2.27.21')
implementation 'software.amazon.awssdk:s3'
implementation 'software.amazon.awssdk:sso'
implementation 'software.amazon.awssdk:ssooidc'

runtimeOnly 'com.mysql:mysql-connector-j'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.shyashyashya.refit.domain.interview.dto.request.QnaSetCreateRequest;
import com.shyashyashya.refit.domain.interview.dto.request.RawTextUpdateRequest;
import com.shyashyashya.refit.domain.interview.dto.response.GuideQuestionResponse;
import com.shyashyashya.refit.domain.interview.dto.response.PdfUploadUrlResponse;
import com.shyashyashya.refit.domain.interview.dto.response.QnaSetCreateResponse;
import com.shyashyashya.refit.domain.interview.service.GuideQuestionService;
import com.shyashyashya.refit.domain.interview.service.InterviewService;
Expand Down Expand Up @@ -115,4 +116,12 @@ public ResponseEntity<ApiResponse<QnaSetCreateResponse>> createQnaSet(
var response = ApiResponse.success(COMMON200, body);
return ResponseEntity.ok(response);
}

@Operation(summary = "면접 PDF 파일 업로드를 위한 Pre-Signed URL을 요청합니다.")
@GetMapping("/{interviewId}/pdf/upload-url")
public ResponseEntity<ApiResponse<PdfUploadUrlResponse>> getUploadUrl(@PathVariable Long interviewId) {
var body = interviewService.createPdfUploadUrl(interviewId);
var response = ApiResponse.success(COMMON200, body);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.shyashyashya.refit.domain.interview.dto.response;

import jakarta.validation.constraints.NotNull;

public record PdfUploadUrlResponse(
@NotNull String url, @NotNull String key) {}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.shyashyashya.refit.domain.interview.dto.request.KptSelfReviewUpdateRequest;
import com.shyashyashya.refit.domain.interview.dto.request.QnaSetCreateRequest;
import com.shyashyashya.refit.domain.interview.dto.request.RawTextUpdateRequest;
import com.shyashyashya.refit.domain.interview.dto.response.PdfUploadUrlResponse;
import com.shyashyashya.refit.domain.interview.dto.response.QnaSetCreateResponse;
import com.shyashyashya.refit.domain.interview.model.Interview;
import com.shyashyashya.refit.domain.interview.model.InterviewReviewStatus;
Expand All @@ -35,7 +36,9 @@
import com.shyashyashya.refit.domain.qnaset.repository.StarAnalysisRepository;
import com.shyashyashya.refit.domain.user.model.User;
import com.shyashyashya.refit.global.exception.CustomException;
import com.shyashyashya.refit.global.property.S3Property;
import com.shyashyashya.refit.global.util.RequestUserContext;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand All @@ -46,6 +49,10 @@
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

@Service
@RequiredArgsConstructor
Expand All @@ -62,6 +69,8 @@ public class InterviewService {

private final InterviewValidator interviewValidator;
private final RequestUserContext requestUserContext;
private final S3Presigner s3Presigner;
private final S3Property s3Property;

@Transactional(readOnly = true)
public InterviewDto getInterview(Long interviewId) {
Expand Down Expand Up @@ -164,6 +173,32 @@ public Page<InterviewDto> searchMyInterviews(InterviewSearchRequest request, Pag
.map(InterviewDto::from);
}

@Transactional
public PdfUploadUrlResponse createPdfUploadUrl(Long interviewId) {
User requestUser = requestUserContext.getRequestUser();
Interview interview =
interviewRepository.findById(interviewId).orElseThrow(() -> new CustomException(INTERVIEW_NOT_FOUND));
interviewValidator.validateInterviewOwner(interview, requestUser);

String extension = ".pdf";
String key = s3Property.prefix() + interviewId + extension;

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(s3Property.bucket())
.key(key)
.contentType("application/pdf")
.build();

PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofSeconds(s3Property.presignExpireSeconds()))
.putObjectRequest(putObjectRequest)
.build();

PresignedPutObjectRequest presigned = s3Presigner.presignPutObject(presignRequest);

return new PdfUploadUrlResponse(presigned.url().toString(), key);
}

public Page<InterviewSimpleDto> getMyInterviewDrafts(InterviewDraftType draftType, Pageable pageable) {
User requestUser = requestUserContext.getRequestUser();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.shyashyashya.refit.global.config;

import com.shyashyashya.refit.global.property.S3Property;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

@Configuration
@RequiredArgsConstructor
public class S3Config {

private final S3Property s3Property;

@Bean
public S3Presigner s3Presigner() {
return S3Presigner.builder()
.region(Region.of(s3Property.region()))
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.shyashyashya.refit.global.property;

import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties(prefix = "spring.s3")
@Validated
public record S3Property(
@NotBlank String region,
@NotBlank String bucket,
@NotBlank String prefix,
int presignExpireSeconds) {}
6 changes: 6 additions & 0 deletions backend/src/main/resources/application-s3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spring:
s3:
region: "ap-northeast-2"
bucket: "refit-s3-bucket"
prefix: "interview-pdf/"
presign-expire-seconds: 300
6 changes: 6 additions & 0 deletions backend/src/test/resources/application-s3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
spring:
s3:
region: "ap-northeast-2"
bucket: "refit-s3-bucket"
prefix: "interview-pdf/"
presign-expire-seconds: 300
1 change: 1 addition & 0 deletions backend/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ spring:
include:
- auth
- gemini
- s3
Loading