Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 타임 블록 생성 #34

Merged
merged 39 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d266b92
[ADD] 타임 블록 생성 dto 추가
Chan531 Jul 9, 2024
578a15e
[CHORE] 임시 커밋
Chan531 Jul 9, 2024
78845ab
Merge branch 'develop' into feat/#22-time-block
Chan531 Jul 10, 2024
b83a623
[INIT] URI generator 설정
Chan531 Jul 10, 2024
e091064
[FEAT] 멤버 찾기
Chan531 Jul 10, 2024
34006f5
[FEAT] 팀 찾기
Chan531 Jul 10, 2024
46cb801
[FEAT] 멤버 팀 관리자 찾기
Chan531 Jul 10, 2024
3e24e65
[FEAT] 멤버 팀 관리자 찾기
Chan531 Jul 10, 2024
2e97285
[CHORE] 병합을 위한 임시 커밋
Chan531 Jul 10, 2024
acb9948
Merge branch 'develop' into feat/#22-time-block
Chan531 Jul 10, 2024
8fe9f34
[FIX] Team 칼럼 변경
Chan531 Jul 10, 2024
ead0e2a
[CHORE] 불필요한 import문 삭제
Chan531 Jul 10, 2024
5bfb04a
[ADD] Position 권한 순위 추가
Chan531 Jul 10, 2024
9eefa41
[ADD] TimeBlock 생성자 추가
Chan531 Jul 10, 2024
8996748
[ADD] TimeBlock 생성 dto 추가
Chan531 Jul 10, 2024
ae668d5
[ADD] TimeBlock 생성 관련 메세지 추가
Chan531 Jul 10, 2024
925828a
[ADD] TimeBlock 관련 상수 추가
Chan531 Jul 10, 2024
8a8e2b5
[FEAT] 타임 블록 생성
Chan531 Jul 10, 2024
e78e55c
[ADD] s3 PresignedUrl 관련 메세지 추가
Chan531 Jul 10, 2024
b4db18a
[ADD] s3 PresignedUrl 생성 dto 추가
Chan531 Jul 10, 2024
e0e448a
[ADD] s3 PresignedUrl 관련 상수 추가
Chan531 Jul 10, 2024
a4e52f9
[FEAT] s3 PresignedUrl 생성
Chan531 Jul 10, 2024
6bc33c9
[FEAT] 문서 생성
Chan531 Jul 10, 2024
cb256b5
[FIX] 충돌 문제 해결
Chan531 Jul 10, 2024
944211e
Merge branch 'develop' into feat/#22-time-block
Chan531 Jul 10, 2024
43b1ddf
[FIX] 데이터를 못 받는 버그 수정
Chan531 Jul 10, 2024
084f408
[ADD] Document 생성자 추가
Chan531 Jul 10, 2024
e02c056
[FEAT] 타임 블록 생성 및 문서 생성
Chan531 Jul 10, 2024
d4ebc0a
[REFACTOR] 생성, 저장에 따른 네이밍 변경
Chan531 Jul 11, 2024
0e97557
[FIX] 에러 메세지 수정
Chan531 Jul 11, 2024
02e635e
[REFACTOR] Response에 null 체크 로직 추가
Chan531 Jul 11, 2024
1501a60
[REFACTOR] TimeBlock 생성 방식 변경
Chan531 Jul 11, 2024
d3bf33e
[REFACTOR] Document 컬럼 및 생성 방식 변경
Chan531 Jul 11, 2024
ea1e0bb
[DEL] 사용하지 않는 클래스 삭제
Chan531 Jul 11, 2024
5ff3401
[REFACTOR] 엔티티 생성 방식 변경에 의한 리팩토링
Chan531 Jul 11, 2024
e970725
[INIT] 삭제된 문서 관리 entity 추가
Chan531 Jul 11, 2024
724438d
[REFACTOR] save 메소드 타입 변경
Chan531 Jul 11, 2024
7269d1d
[REFACTOR] Document에 fileName 컬럼 추가로 인한 리팩토링
Chan531 Jul 11, 2024
ce8dc19
[REFACTOR] 객체 생성 방식 변경
Chan531 Jul 11, 2024
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
11 changes: 10 additions & 1 deletion src/main/java/com/tiki/server/common/entity/Position.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package com.tiki.server.common.entity;

import lombok.Getter;

@Getter
public enum Position {
ADMIN, EXECUTIVE, MEMBER,
ADMIN(1), EXECUTIVE(2), MEMBER(3);
paragon0107 marked this conversation as resolved.
Show resolved Hide resolved

private final int authorization;

Position(int authorization) {
this.authorization = authorization;
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/tiki/server/common/support/UriGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.tiki.server.common.support;

import java.net.URI;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

@Component
public class UriGenerator {

public static URI getUri(String path, long id) {
return ServletUriComponentsBuilder
paragon0107 marked this conversation as resolved.
Show resolved Hide resolved
.fromCurrentRequest()
.path(path + id)
.buildAndExpand()
.toUri();
}

public static URI getUri(String path) {
return ServletUriComponentsBuilder
.fromCurrentRequest()
.path(path)
.buildAndExpand()
.toUri();
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/tiki/server/document/adapter/DocumentSaver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.tiki.server.document.adapter;

import com.tiki.server.common.support.RepositoryAdapter;
import com.tiki.server.document.entity.Document;
import com.tiki.server.document.repository.DocumentRepository;

import lombok.RequiredArgsConstructor;

@RepositoryAdapter
@RequiredArgsConstructor
public class DocumentSaver {

private final DocumentRepository documentRepository;

public void save(Document document) {
documentRepository.save(document);
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/tiki/server/document/entity/DeletedDocument.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.tiki.server.document.entity;

import static jakarta.persistence.GenerationType.IDENTITY;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
public class DeletedDocument {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "deleted_document_id")
private Long id;

private String fileName;

private String fileUrl;

@Column(name = "block_id")
private long timeBlockId;

private LocalDate deletedDate;

@Builder
public static DeletedDocument of(String fileName, String fileUrl, long timeBlockId, LocalDate deletedDate) {
return DeletedDocument.builder()
.fileName(fileName)
.fileUrl(fileUrl)
.timeBlockId(timeBlockId)
.deletedDate(deletedDate)
.build();
}
}
26 changes: 16 additions & 10 deletions src/main/java/com/tiki/server/document/entity/Document.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
package com.tiki.server.document.entity;

import static jakarta.persistence.EnumType.STRING;
import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;

import java.time.LocalDate;
import java.time.LocalDateTime;
import static lombok.AccessLevel.PRIVATE;
import static lombok.AccessLevel.PROTECTED;

import com.tiki.server.common.entity.BaseTime;
import com.tiki.server.timeblock.entity.TimeBlock;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@Builder(access = PRIVATE)
@AllArgsConstructor(access = PRIVATE)
@NoArgsConstructor(access = PROTECTED)
public class Document extends BaseTime {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "document_id")
private Long id;

private String fileName;

private String fileUrl;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "block_id")
private TimeBlock timeBlock;

@Enumerated(value = STRING)
private DocumentStatus status;

private LocalDate deletedDate;
public static Document of(String fileName, String fileUrl, TimeBlock timeBlock) {
return Document.builder()
.fileName(fileName)
.fileUrl(fileUrl)
.timeBlock(timeBlock)
.build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.tiki.server.external.constant;

public class ExternalConstant {

public static final Long PRE_SIGNED_URL_EXPIRE_MINUTE = 10L;
public static final String FILE_SAVE_PREFIX = "file/";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.tiki.server.external.controller;

import static com.tiki.server.common.dto.SuccessResponse.*;
import static com.tiki.server.external.message.SuccessMessage.PRESIGNED_URL_GET_SUCCESS;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.tiki.server.common.dto.SuccessResponse;
import com.tiki.server.external.dto.request.PreSignedUrlRequest;
import com.tiki.server.external.dto.response.PreSignedUrlResponse;
import com.tiki.server.external.util.S3Service;

import lombok.RequiredArgsConstructor;
import lombok.val;

@RestController
@RequestMapping("api/v1/file")
@RequiredArgsConstructor
public class S3Controller {

private final S3Service s3Service;

@GetMapping("/upload")
public ResponseEntity<SuccessResponse<PreSignedUrlResponse>> getPreSignedUrl(@RequestBody PreSignedUrlRequest request) {
val response = s3Service.getUploadPreSignedUrl(request);
return ResponseEntity.ok(success(PRESIGNED_URL_GET_SUCCESS.getMessage(), response));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tiki.server.external.dto.request;

import lombok.NonNull;

public record PreSignedUrlRequest(
@NonNull String fileFormat
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.tiki.server.external.dto.response;

import static lombok.AccessLevel.PRIVATE;

import lombok.Builder;
import lombok.NonNull;

@Builder(access = PRIVATE)
public record PreSignedUrlResponse(
@NonNull String fileName,
@NonNull String url
) {

public static PreSignedUrlResponse of(String fileName, String url) {
paragon0107 marked this conversation as resolved.
Show resolved Hide resolved
return PreSignedUrlResponse.builder()
.fileName(fileName)
.url(url)
.build();
}
}
6 changes: 4 additions & 2 deletions src/main/java/com/tiki/server/external/message/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.tiki.server.external.message;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

import org.springframework.http.HttpStatus;

Expand All @@ -11,8 +12,9 @@
@AllArgsConstructor
public enum ErrorCode {

/* 400 BAD_REQUEST : 잘못된 요청 */
INVALID_FILE_SIZE(BAD_REQUEST, "파일의 용량은 30MB를 초과할 수 없습니다.");
/* 500 INTERNAL_SERVER_ERROR : 서버 에러 */
PRESIGNED_URL_GET_ERROR(INTERNAL_SERVER_ERROR, "S3 PRESIGNED URL 불러오기 실패"),
FILE_DELETE_ERROR(INTERNAL_SERVER_ERROR, "S3 버킷의 파일 삭제 실패");

private final HttpStatus httpStatus;
private final String message;
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/tiki/server/external/message/SuccessMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tiki.server.external.message;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum SuccessMessage {

PRESIGNED_URL_GET_SUCCESS("S3 PRESIGNED URL 불러오기 성공");

private final String message;
}
69 changes: 35 additions & 34 deletions src/main/java/com/tiki/server/external/util/S3Service.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.tiki.server.external.util;

import static com.tiki.server.external.constant.ExternalConstant.FILE_SAVE_PREFIX;
import static com.tiki.server.external.constant.ExternalConstant.PRE_SIGNED_URL_EXPIRE_MINUTE;
import static com.tiki.server.external.message.ErrorCode.*;

import java.io.IOException;
import java.time.Duration;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.tiki.server.external.config.AWSConfig;
import com.tiki.server.external.dto.request.PreSignedUrlRequest;
import com.tiki.server.external.dto.response.PreSignedUrlResponse;
import com.tiki.server.external.exception.ExternalException;
import com.tiki.server.external.message.ErrorCode;

Expand All @@ -18,16 +23,13 @@
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

@Component
@RequiredArgsConstructor
public class S3Service {

private static final Long MAX_FILE_SIZE = 30 * 1024 * 1024L;
private static final String FILE_NAME_START_POINT = ".";
private static final String CONTENT_DISPOSITION = "inline";
private static final String DELIMITER = "/";

private final AWSConfig awsConfig;

@Value("${aws-property.bucket}")
Expand All @@ -36,49 +38,48 @@ public class S3Service {
@Value("${aws-property.s3-url}")
private String s3URL;

public String uploadFile(String directoryPath, MultipartFile file) {
validateFileSize(file);
public PreSignedUrlResponse getUploadPreSignedUrl(PreSignedUrlRequest request) {
try {
val key = directoryPath + DELIMITER + generateFileName(file);
val s3Client = awsConfig.getS3Client();
val request = createRequest(key, file.getContentType());
val requestBody = RequestBody.fromBytes(file.getBytes());
s3Client.putObject(request, requestBody);
return s3URL + key;
} catch (IOException exception) {
throw new IllegalArgumentException();
val fileName = generateFileName(request.fileFormat());
val key = FILE_SAVE_PREFIX + fileName;
val preSigner = awsConfig.getS3PreSigner();
val putObjectRequest = createPutObjectRequest(key);
val putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest);
val url = preSigner.presignPutObject(putObjectPresignRequest).url().toString();
return PreSignedUrlResponse.of(fileName, url);
} catch (RuntimeException e) {
throw new ExternalException(PRESIGNED_URL_GET_ERROR);
}
}

public void deleteFile(String key) throws IOException {
val s3Client = awsConfig.getS3Client();
s3Client.deleteObject((DeleteObjectRequest.Builder builder) ->
builder.bucket(bucket)
.key(key)
.build()
);
try {
val s3Client = awsConfig.getS3Client();
s3Client.deleteObject((DeleteObjectRequest.Builder builder) ->
builder.bucket(bucket)
.key(key)
.build()
);
} catch (RuntimeException e) {
throw new ExternalException(FILE_DELETE_ERROR);
}
}

private PutObjectRequest createRequest(String key, String contentType) {
private PutObjectRequest createPutObjectRequest(String key) {
return PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType(contentType)
.contentDisposition(CONTENT_DISPOSITION)
.build();
}

private String generateFileName(MultipartFile file) {
return UUID.randomUUID() + getFileFormat(file.getName());
}

private String getFileFormat(String fileName) {
return fileName.substring(fileName.lastIndexOf(FILE_NAME_START_POINT));
private PutObjectPresignRequest createPutObjectPresignRequest(PutObjectRequest putObjectRequest) {
return PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(PRE_SIGNED_URL_EXPIRE_MINUTE))
.putObjectRequest(putObjectRequest)
.build();
}

private void validateFileSize(MultipartFile file) {
if (file.getSize() > MAX_FILE_SIZE) {
throw new ExternalException(INVALID_FILE_SIZE);
}
private String generateFileName(String fileFormat) {
return UUID.randomUUID() + fileFormat;
}
}
Loading
Loading