diff --git a/src/main/java/com/seveneleven/devlens/global/response/ErrorCode.java b/src/main/java/com/seveneleven/devlens/global/response/ErrorCode.java index 78ad9ece..64ca95f5 100644 --- a/src/main/java/com/seveneleven/devlens/global/response/ErrorCode.java +++ b/src/main/java/com/seveneleven/devlens/global/response/ErrorCode.java @@ -19,6 +19,13 @@ public enum ErrorCode { // 4000번대 코드 : 파일 s3 버킷 업/다운로드 관련 S3_UPLOAD_FAIL_ERROR(4000, HttpStatus.INTERNAL_SERVER_ERROR, "S3 파일 업로드에 실패했습니다."), + FILE_NOT_EXIST_ERROR(4001, HttpStatus.BAD_REQUEST, "빈 파일이거나 존재하지 않는 파일입니다."), + INVALID_FILE_NAME_ERROR(4002, HttpStatus.BAD_REQUEST, "파일명이 비었거나 유효하지 않습니다."), + INVALID_FILE_CATEGORY_ERROR(4003, HttpStatus.BAD_REQUEST, "파일 카테고리가 유효하지 않습니다."), + FORMAT_NOT_PERMITTED_ERROR(4004, HttpStatus.BAD_REQUEST, "유효하지 않은 파일 타입입니다."), + MIME_NOT_PERMITTED_ERROR(4005, HttpStatus.BAD_REQUEST, "유효하지 않은 MIME 타입입니다."), + FILE_SIZE_EXCEED_ERROR(4006, HttpStatus.BAD_REQUEST, "파일 사이즈가 초과되었습니다."), + // 5000번대 코드 : 서버 내부 오류 관련 INTERNAL_SERVER_ERROR(5001, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."), diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/FileValidator.java b/src/main/java/com/seveneleven/devlens/global/util/file/FileValidator.java index a2a68273..079c8c2d 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/FileValidator.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/FileValidator.java @@ -1,11 +1,12 @@ package com.seveneleven.devlens.global.util.file; +import com.seveneleven.devlens.global.exception.BusinessException; +import com.seveneleven.devlens.global.response.ErrorCode; +import com.seveneleven.devlens.global.util.file.constant.FileCategory; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.multipart.MultipartFile; -import java.util.List; -import java.util.Map; -import java.util.Set; - public class FileValidator { private static final long MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB 제한 @@ -17,19 +18,19 @@ public class FileValidator { * @param fileCategoryName 파일 카테고리명 */ public static void validateFile(MultipartFile file, String fileCategoryName) { - if (file == null || file.isEmpty()) { - throw new IllegalArgumentException("File cannot be null or empty."); + if (ObjectUtils.isEmpty(file)) { + throw new BusinessException(ErrorCode.FILE_NOT_EXIST_ERROR); } String fileName = file.getOriginalFilename(); - if (fileName == null || fileName.isBlank()) { - throw new IllegalArgumentException("Filename cannot be null or empty."); + if (StringUtils.isBlank(fileName)) { + throw new BusinessException(ErrorCode.INVALID_FILE_NAME_ERROR); } FileCategory fileCategory = validateFileCategory(fileCategoryName); // 파일 카테고리 검증 validateFileExtension(fileName, fileCategory); // 파일 확장자 검증 validateMimeType(file.getContentType(), fileCategory); // MIME 타입 검증 - //validateFileSize(file.getSize()); // 파일 크기 검증 + validateFileSize(file.getSize()); // 파일 크기 검증 } /** @@ -39,7 +40,7 @@ private static FileCategory validateFileCategory(String fileCategoryName) { try{ return FileCategory.valueOf(fileCategoryName); } catch (IllegalArgumentException e){ - throw new IllegalArgumentException("Invalid file category: " + fileCategoryName); + throw new BusinessException(ErrorCode.INVALID_FILE_CATEGORY_ERROR); } } @@ -49,7 +50,7 @@ private static FileCategory validateFileCategory(String fileCategoryName) { private static void validateFileExtension(String fileName, FileCategory fileCategory) { String fileExtension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); if (!fileCategory.getAllowedExtensions().contains(fileExtension)) { - throw new IllegalArgumentException("Invalid file extension for category: " + fileCategory.name()); + throw new BusinessException(ErrorCode.FORMAT_NOT_PERMITTED_ERROR); } } @@ -58,7 +59,7 @@ private static void validateFileExtension(String fileName, FileCategory fileCate */ private static void validateMimeType(String mimeType, FileCategory fileCategory) { if (!fileCategory.getAllowedMimeTypes().contains(mimeType)) { - throw new IllegalArgumentException("Invalid file type for category: " + fileCategory.name()); + throw new BusinessException(ErrorCode.MIME_NOT_PERMITTED_ERROR); } } @@ -67,7 +68,7 @@ private static void validateMimeType(String mimeType, FileCategory fileCategory) */ private static void validateFileSize(long fileSize) { if (fileSize > MAX_FILE_SIZE) { - throw new IllegalArgumentException("File size exceeds limit of 20MB."); + throw new BusinessException(ErrorCode.FILE_SIZE_EXCEED_ERROR); } } } \ No newline at end of file diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/Service/FileService.java b/src/main/java/com/seveneleven/devlens/global/util/file/Service/FileService.java index 2e124ad1..a4131a15 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/Service/FileService.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/Service/FileService.java @@ -1,81 +1,81 @@ -package com.seveneleven.devlens.global.util.file.Service; - -import com.seveneleven.devlens.global.exception.BusinessException; -import com.seveneleven.devlens.global.response.APIResponse; -import com.seveneleven.devlens.global.response.ErrorCode; -import com.seveneleven.devlens.global.util.file.FileValidator; -import com.seveneleven.devlens.global.util.file.dto.FileMetadataDto; -import com.seveneleven.devlens.global.util.file.entity.FileMetadata; -import com.seveneleven.devlens.global.util.file.repository.FileMetadataRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDateTime; - -@Service -@RequiredArgsConstructor -public class FileService { - private final FileMetadataRepository fileMetadataRepository; - private final S3ClientService s3ClientService; - - /** - * 1. 파일 업로드 - * @param file 업로드할 파일 - * @param uploaderId 업로더 id - * @param fileCategory 파일 카테고리 - * @param referenceId 파일 참조 ID - * @return FileMetadataDto 업로드한 파일 메타데이터 - */ - @Transactional - public APIResponse uploadFile(MultipartFile file, Long uploaderId, String fileCategory, Long referenceId) throws Exception { - //1. 파일 검증 - FileValidator.validateFile(file, fileCategory); - - //2. 고유 파일 이름(UUID) 및 S3 키 생성 - //파일명이 없거나 비어있으면 Unknown-File로 설정 - String originalFilename = (file.getOriginalFilename() != null && !file.getOriginalFilename().isBlank()) - ? file.getOriginalFilename() : "Unknown-File"; - //UUID 생성 - String uniqueFileName = s3ClientService.generateUniqueFileName(originalFilename); - //S3 키 생성 - String s3Key = s3ClientService.generateS3Key(uploaderId, fileCategory, uniqueFileName); - - //3. S3 업로드 및 FileMetadata 데이터 생성 - String filePath = null; - try{ - //S3 업로드 - filePath = s3ClientService.uploadFile(file, s3Key); - - //업로드하는 파일 메타데이터 생성 - FileMetadata metadata = FileMetadata.builder() - .fileCategory(fileCategory) - .fileDisplayTitle(file.getOriginalFilename()) - .fileTitle(uniqueFileName) - .writtenBy(uploaderId) //TODO) Audit - .writtenAt(LocalDateTime.now()) //TODO) Audit - .contentType(file.getContentType()) - .fileFormat(file.getOriginalFilename().substring(originalFilename.lastIndexOf('.') + 1)) - .fileSize(file.getSize() / 1024.0) // KB - .filePath(filePath) - .referenceId(referenceId) // 필요 시 설정 - .createdBy(1L) //TODO) Audit - .createdAt(LocalDateTime.now()) //TODO) Audit - .build(); - - //FileMetaData 저장 - FileMetadata savedMetadata = fileMetadataRepository.save(metadata); - - //DTO로 변환 후 반환 - return APIResponse.create(FileMetadataDto.toDto(savedMetadata)); - - } catch (Exception e){ - //저장 실패시 S3에서 삭제 - s3ClientService.deleteFile(s3Key); - - throw new BusinessException(e.getMessage(), ErrorCode.S3_UPLOAD_FAIL_ERROR); - } - } - -} +//package com.seveneleven.devlens.global.util.file.Service; +// +//import com.seveneleven.devlens.global.exception.BusinessException; +//import com.seveneleven.devlens.global.response.APIResponse; +//import com.seveneleven.devlens.global.response.ErrorCode; +//import com.seveneleven.devlens.global.util.file.FileValidator; +//import com.seveneleven.devlens.global.util.file.dto.FileMetadataDto; +//import com.seveneleven.devlens.global.util.file.entity.FileMetadata; +//import com.seveneleven.devlens.global.util.file.repository.FileMetadataRepository; +//import lombok.RequiredArgsConstructor; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.time.LocalDateTime; +// +//@Service +//@RequiredArgsConstructor +//public class FileService { +// private final FileMetadataRepository fileMetadataRepository; +// private final S3ClientService s3ClientService; +// +// /** +// * 1. 파일 업로드 +// * @param file 업로드할 파일 +// * @param uploaderId 업로더 id +// * @param fileCategory 파일 카테고리 +// * @param referenceId 파일 참조 ID +// * @return FileMetadataDto 업로드한 파일 메타데이터 +// */ +// @Transactional +// public APIResponse uploadFile(MultipartFile file, Long uploaderId, String fileCategory, Long referenceId) throws Exception { +// //1. 파일 검증 +// FileValidator.validateFile(file, fileCategory); +// +// //2. 고유 파일 이름(UUID) 및 S3 키 생성 +// //파일명이 없거나 비어있으면 Unknown-File로 설정 +// String originalFilename = (file.getOriginalFilename() != null && !file.getOriginalFilename().isBlank()) +// ? file.getOriginalFilename() : "Unknown-File"; +// //UUID 생성 +// String uniqueFileName = s3ClientService.generateUniqueFileName(originalFilename); +// //S3 키 생성 +// String s3Key = s3ClientService.generateS3Key(uploaderId, fileCategory, uniqueFileName); +// +// //3. S3 업로드 및 FileMetadata 데이터 생성 +// String filePath = null; +// try{ +// //S3 업로드 +// filePath = s3ClientService.uploadFile(file, s3Key); +// +// //업로드하는 파일 메타데이터 생성 +// FileMetadata metadata = FileMetadata.builder() +// .fileCategory(fileCategory) +// .fileDisplayTitle(file.getOriginalFilename()) +// .fileTitle(uniqueFileName) +// .writtenBy(uploaderId) //TODO) Audit +// .writtenAt(LocalDateTime.now()) //TODO) Audit +// .contentType(file.getContentType()) +// .fileFormat(file.getOriginalFilename().substring(originalFilename.lastIndexOf('.') + 1)) +// .fileSize(file.getSize() / 1024.0) // KB +// .filePath(filePath) +// .referenceId(referenceId) // 필요 시 설정 +// .createdBy(1L) //TODO) Audit +// .createdAt(LocalDateTime.now()) //TODO) Audit +// .build(); +// +// //FileMetaData 저장 +// FileMetadata savedMetadata = fileMetadataRepository.save(metadata); +// +// //DTO로 변환 후 반환 +// return APIResponse.create(FileMetadataDto.toDto(savedMetadata)); +// +// } catch (Exception e){ +// //저장 실패시 S3에서 삭제 +// s3ClientService.deleteFile(s3Key); +// +// throw new BusinessException(e.getMessage(), ErrorCode.S3_UPLOAD_FAIL_ERROR); +// } +// } +// +//} diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/Service/S3ClientService.java b/src/main/java/com/seveneleven/devlens/global/util/file/Service/S3ClientService.java index be7e6db8..dfd4a7f7 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/Service/S3ClientService.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/Service/S3ClientService.java @@ -1,92 +1,92 @@ -package com.seveneleven.devlens.global.util.file.Service; - -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.ObjectMetadata; -import com.amazonaws.services.s3.model.PutObjectRequest; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.UUID; - -@Service -@RequiredArgsConstructor -public class S3ClientService { - - private final AmazonS3 amazonS3; - - @Value("${cloud.aws.s3.bucket}") - private String bucket; - - /** - * 고유한 파일 이름 생성(UUID) - * S3에 저장되는 파일은 고유 이름을 가져야 함. - */ - public String generateUniqueFileName(String originalFileName) { - String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")); - return UUID.randomUUID().toString() + fileExtension; - } - - /** - * S3 키 생성 - * 키 = S3 저장시 파일 경로 - */ - public String generateS3Key(Long uploaderId, String category, String fileName) { - return new StringBuilder() - .append(uploaderId) - .append("/") - .append(category) - .append("/") - .append(fileName) - .toString(); - } - - /** - * 파일 업로드 - */ - public String uploadFile(MultipartFile file, String s3Key) throws Exception { - try { - //S3 저장할 파일의 메타데이터 설정 - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentType(file.getContentType()); - metadata.setContentLength(file.getSize()); - - //S3에 파일을 업로드하기 위한 객체 설정 - amazonS3.putObject(new PutObjectRequest(bucket, s3Key, file.getInputStream(), metadata)); - - //업로드한 파일 URL 반환 - return amazonS3.getUrl(bucket, s3Key).toString(); - - } catch (IOException e) { - throw new RuntimeException("FAILED TO UPLOAD FILE TO S3", e); - } - } - - /** - * 파일 삭제 - */ - public void deleteFile(String s3Key) { - amazonS3.deleteObject(bucket, s3Key); - } - - /** - * S3 파일 다운로드 (바이트 배열로 반환) - */ - public byte[] downloadFile(String s3Key) { - try (S3Object s3Object = amazonS3.getObject(bucket, s3Key); - S3ObjectInputStream inputStream = (s3Object != null ? s3Object.getObjectContent() : null)) { - - if (s3Object == null || inputStream == null) { - throw new RuntimeException("S3 OBJECT IS NULL : " + s3Key); - } - - return inputStream.readAllBytes(); - } catch (IOException e) { - throw new RuntimeException("FAILED TO DOWNLOAD FILE FROM S3", e); - } - } -} \ No newline at end of file +//package com.seveneleven.devlens.global.util.file.Service; +// +//import com.amazonaws.services.s3.AmazonS3; +//import com.amazonaws.services.s3.model.ObjectMetadata; +//import com.amazonaws.services.s3.model.PutObjectRequest; +//import com.amazonaws.services.s3.model.S3Object; +//import com.amazonaws.services.s3.model.S3ObjectInputStream; +//import lombok.RequiredArgsConstructor; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Service; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.io.IOException; +//import java.util.UUID; +// +//@Service +//@RequiredArgsConstructor +//public class S3ClientService { +// +// private final AmazonS3 amazonS3; +// +// @Value("${cloud.aws.s3.bucket}") +// private String bucket; +// +// /** +// * 고유한 파일 이름 생성(UUID) +// * S3에 저장되는 파일은 고유 이름을 가져야 함. +// */ +// public String generateUniqueFileName(String originalFileName) { +// String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")); +// return UUID.randomUUID().toString() + fileExtension; +// } +// +// /** +// * S3 키 생성 +// * 키 = S3 저장시 파일 경로 +// */ +// public String generateS3Key(Long uploaderId, String category, String fileName) { +// return new StringBuilder() +// .append(uploaderId) +// .append("/") +// .append(category) +// .append("/") +// .append(fileName) +// .toString(); +// } +// +// /** +// * 파일 업로드 +// */ +// public String uploadFile(MultipartFile file, String s3Key) throws Exception { +// try { +// //S3 저장할 파일의 메타데이터 설정 +// ObjectMetadata metadata = new ObjectMetadata(); +// metadata.setContentType(file.getContentType()); +// metadata.setContentLength(file.getSize()); +// +// //S3에 파일을 업로드하기 위한 객체 설정 +// amazonS3.putObject(new PutObjectRequest(bucket, s3Key, file.getInputStream(), metadata)); +// +// //업로드한 파일 URL 반환 +// return amazonS3.getUrl(bucket, s3Key).toString(); +// +// } catch (IOException e) { +// throw new RuntimeException("FAILED TO UPLOAD FILE TO S3", e); +// } +// } +// +// /** +// * 파일 삭제 +// */ +// public void deleteFile(String s3Key) { +// amazonS3.deleteObject(bucket, s3Key); +// } +// +// /** +// * S3 파일 다운로드 (바이트 배열로 반환) +// */ +// public byte[] downloadFile(String s3Key) { +// try (S3Object s3Object = amazonS3.getObject(bucket, s3Key); +// S3ObjectInputStream inputStream = (s3Object != null ? s3Object.getObjectContent() : null)) { +// +// if (s3Object == null || inputStream == null) { +// throw new RuntimeException("S3 OBJECT IS NULL : " + s3Key); +// } +// +// return inputStream.readAllBytes(); +// } catch (IOException e) { +// throw new RuntimeException("FAILED TO DOWNLOAD FILE FROM S3", e); +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/FileCategory.java b/src/main/java/com/seveneleven/devlens/global/util/file/constant/FileCategory.java similarity index 93% rename from src/main/java/com/seveneleven/devlens/global/util/file/FileCategory.java rename to src/main/java/com/seveneleven/devlens/global/util/file/constant/FileCategory.java index cc178132..6f464437 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/FileCategory.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/constant/FileCategory.java @@ -1,8 +1,14 @@ -package com.seveneleven.devlens.global.util.file; +package com.seveneleven.devlens.global.util.file.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Set; +@Getter +@RequiredArgsConstructor public enum FileCategory { COMPANY_LOGO_IMAGE( Set.of(".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"), @@ -56,11 +62,6 @@ public enum FileCategory { private final Set allowedExtensions; private final List allowedMimeTypes; - FileCategory(Set allowedExtensions, List allowedMimeTypes) { - this.allowedExtensions = allowedExtensions; - this.allowedMimeTypes = allowedMimeTypes; - } - public Set getAllowedExtensions() { return allowedExtensions; } diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/constant/FileHistoryType.java b/src/main/java/com/seveneleven/devlens/global/util/file/constant/FileHistoryType.java new file mode 100644 index 00000000..f73f987b --- /dev/null +++ b/src/main/java/com/seveneleven/devlens/global/util/file/constant/FileHistoryType.java @@ -0,0 +1,13 @@ +package com.seveneleven.devlens.global.util.file.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum FileHistoryType { + REGISTER("파일 등록 이력"), + DELETE("파일 삭제 이력"); + + private final String description; +} diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/constant/LinkCategory.java b/src/main/java/com/seveneleven/devlens/global/util/file/constant/LinkCategory.java new file mode 100644 index 00000000..78e829ee --- /dev/null +++ b/src/main/java/com/seveneleven/devlens/global/util/file/constant/LinkCategory.java @@ -0,0 +1,14 @@ +package com.seveneleven.devlens.global.util.file.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LinkCategory { + CHECK_APPROVE_REQUEST_LINK("승인 요청 첨부 링크"), + CHECK_REJECTION_REASON_LINK("승인 요청 반려 사유 첨부 링크"), + POST_ATTACHMENT_LINK("게시물 첨부 링크"); + + private final String description; +} diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/constant/LinkHistoryType.java b/src/main/java/com/seveneleven/devlens/global/util/file/constant/LinkHistoryType.java new file mode 100644 index 00000000..40cdf1f5 --- /dev/null +++ b/src/main/java/com/seveneleven/devlens/global/util/file/constant/LinkHistoryType.java @@ -0,0 +1,13 @@ +package com.seveneleven.devlens.global.util.file.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LinkHistoryType { + REGISTER("링크 등록 이력"), + DELETE("링크 삭제 이력"); + + private final String description; +} diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/controller/FileController.java b/src/main/java/com/seveneleven/devlens/global/util/file/controller/FileController.java index 6bd4ea9f..625d1fe3 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/controller/FileController.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/controller/FileController.java @@ -1,56 +1,56 @@ -package com.seveneleven.devlens.global.util.file.controller; - -import com.seveneleven.devlens.global.response.APIResponse; -import com.seveneleven.devlens.global.util.file.Service.FileService; -import com.seveneleven.devlens.global.util.file.dto.FileMetadataDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -@RestController -@RequestMapping("/api/files") -@RequiredArgsConstructor -public class FileController { - private final FileService fileService; - - /** - * 수행 순서 - * 1. 컨트롤러에서 파일을 받는다. - * 2. 컨트롤러에서 서비스단으로 전달 - * 3. 서비스단에서 로컬 환경에 이미지 저장 - * 4. 로컬 환경 파일 S3에 전송 - * 5. S3로 부터 저장 경로 받기 - * 6. 로컬 환경 파일 삭제 - * 7. 저장 경로 컨트롤러단으로 전달 - * 8. 프론트에게 Response 보내기 - * 9. 이력 등록 - */ - - /** - * 1. 파일 업로드 - * @param file 업로드할 파일 - * @return 업로드된 파일 메타데이터, 성공 메시지 - */ - @PostMapping(value = "/upload", consumes = "multipart/form-data") - @Operation( - summary = "Upload a file", - description = "Upload a file to the server", - responses = { - @ApiResponse(responseCode = "200", description = "File uploaded successfully"), - @ApiResponse(responseCode = "400", description = "Invalid file upload request") - } - ) - public ResponseEntity uploadFile(@RequestParam("file") @Schema(type = "string", format = "binary", description = "File to upload") MultipartFile file) throws Exception { - APIResponse uploadResponse = fileService.uploadFile(file, 1L, "POST_ATTACHMENT", 1L); - - return ResponseEntity.status(uploadResponse.getCode()).body(uploadResponse.getData()); - } -} +//package com.seveneleven.devlens.global.util.file.controller; +// +//import com.seveneleven.devlens.global.response.APIResponse; +//import com.seveneleven.devlens.global.util.file.Service.FileService; +//import com.seveneleven.devlens.global.util.file.dto.FileMetadataDto; +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.media.Schema; +//import io.swagger.v3.oas.annotations.responses.ApiResponse; +//import lombok.RequiredArgsConstructor; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.PostMapping; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RequestParam; +//import org.springframework.web.bind.annotation.RestController; +//import org.springframework.web.multipart.MultipartFile; +// +//@RestController +//@RequestMapping("/api/files") +//@RequiredArgsConstructor +//public class FileController { +// private final FileService fileService; +// +// /** +// * 수행 순서 +// * 1. 컨트롤러에서 파일을 받는다. +// * 2. 컨트롤러에서 서비스단으로 전달 +// * 3. 서비스단에서 로컬 환경에 이미지 저장 +// * 4. 로컬 환경 파일 S3에 전송 +// * 5. S3로 부터 저장 경로 받기 +// * 6. 로컬 환경 파일 삭제 +// * 7. 저장 경로 컨트롤러단으로 전달 +// * 8. 프론트에게 Response 보내기 +// * 9. 이력 등록 +// */ +// +// /** +// * 1. 파일 업로드 +// * @param file 업로드할 파일 +// * @return 업로드된 파일 메타데이터, 성공 메시지 +// */ +// @PostMapping(value = "/upload", consumes = "multipart/form-data") +// @Operation( +// summary = "Upload a file", +// description = "Upload a file to the server", +// responses = { +// @ApiResponse(responseCode = "200", description = "File uploaded successfully"), +// @ApiResponse(responseCode = "400", description = "Invalid file upload request") +// } +// ) +// public ResponseEntity uploadFile(@RequestParam("file") @Schema(type = "string", format = "binary", description = "File to upload") MultipartFile file) throws Exception { +// APIResponse uploadResponse = fileService.uploadFile(file, 1L, "POST_ATTACHMENT", 1L); +// +// return ResponseEntity.status(uploadResponse.getCode()).body(uploadResponse.getData()); +// } +//} diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/dto/FileMetadataDto.java b/src/main/java/com/seveneleven/devlens/global/util/file/dto/FileMetadataDto.java index 9a175b54..1bb4a290 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/dto/FileMetadataDto.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/dto/FileMetadataDto.java @@ -1,46 +1,46 @@ -package com.seveneleven.devlens.global.util.file.dto; - -import com.seveneleven.devlens.global.util.file.entity.FileMetadata; -import lombok.*; - -import java.time.LocalDateTime; - -@Getter -@Setter -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor -public class FileMetadataDto { - //TODO) validation - private Long id; //통합 파일 id - private String fileCategory; //파일 카테고리 - private Long referenceId; //참조 ID - private String fileDisplayTitle; //유저에게 보여질 파일 이름 - private String fileTitle; //저장된 파일 이름(UUID) - private Long writtenBy; //등록자 ID - private LocalDateTime writtenAt; //등록일시 - private String contentType; //MIME 타입 - private String fileFormat; //파일 확장자(포맷) - private Double fileSize; //파일 사이즈 - private String filePath; //파일 경로 - - //Entity -> DTO - public static FileMetadataDto toDto(FileMetadata metadata) { - FileMetadataDto dto = new FileMetadataDto(); - dto.setId(metadata.getId()); - dto.setFileCategory(metadata.getFileCategory()); - dto.setReferenceId(metadata.getReferenceId()); - dto.setFileDisplayTitle(metadata.getFileDisplayTitle()); - dto.setFileTitle(metadata.getFileTitle()); - dto.setWrittenBy(metadata.getWrittenBy()); - dto.setWrittenAt(LocalDateTime.now()); - dto.setFileSize(metadata.getFileSize()); - dto.setFilePath(metadata.getFilePath()); - dto.setContentType(metadata.getContentType()); - - return dto; - } - - -} +//package com.seveneleven.devlens.global.util.file.dto; +// +//import com.seveneleven.devlens.global.util.file.entity.FileMetadata; +//import lombok.*; +// +//import java.time.LocalDateTime; +// +//@Getter +// +// +//@Builder(toBuilder = true) +//@NoArgsConstructor +//@AllArgsConstructor +//public class FileMetadataDto { +// //TODO) validation +// private Long id; //통합 파일 id +// private String fileCategory; //파일 카테고리 +// private Long referenceId; //참조 ID +// private String fileDisplayTitle; //유저에게 보여질 파일 이름 +// private String fileTitle; //저장된 파일 이름(UUID) +// private Long writtenBy; //등록자 ID +// private LocalDateTime writtenAt; //등록일시 +// private String contentType; //MIME 타입 +// private String fileFormat; //파일 확장자(포맷) +// private Double fileSize; //파일 사이즈 +// private String filePath; //파일 경로 +// +// //Entity -> DTO +// public static FileMetadataDto toDto(FileMetadata metadata) { +// FileMetadataDto dto = new FileMetadataDto(); +// dto.setId(metadata.getId()); +// dto.setFileCategory(metadata.getFileCategory()); +// dto.setReferenceId(metadata.getReferenceId()); +// dto.setFileDisplayTitle(metadata.getFileDisplayTitle()); +// dto.setFileTitle(metadata.getFileTitle()); +// dto.setWrittenBy(metadata.getWrittenBy()); +// dto.setWrittenAt(LocalDateTime.now()); +// dto.setFileSize(metadata.getFileSize()); +// dto.setFilePath(metadata.getFilePath()); +// dto.setContentType(metadata.getContentType()); +// +// return dto; +// } +// +// +//} diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadata.java b/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadata.java index c73e03c8..a01aaa96 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadata.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadata.java @@ -1,65 +1,88 @@ package com.seveneleven.devlens.global.util.file.entity; +import com.seveneleven.devlens.global.util.file.constant.FileCategory; import jakarta.persistence.*; import lombok.*; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Entity @Getter -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "file_metadata") public class FileMetadata { + // Magic Numbers + private static final int MAX_FILE_CATEGORY_NAME_LENGTH = 100; + private static final int MAX_FILE_DISPLAY_TITLE_LENGTH = 300; + private static final int MAX_FILE_TITLE_LENGTH = 50; + private static final int MAX_FILE_FORMAT_LENGTH = 50; + private static final int FILE_SIZE_PRECISION = 10; + private static final int MAX_FILE_PATH_LENGTH = 1000; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "file_metadata_id") + @Column(name = "id") private Long id; //통합 파일 ID - @Column(name = "file_category", nullable = false) + @Column(name = "category", length = MAX_FILE_CATEGORY_NAME_LENGTH, nullable = false) + @Enumerated(EnumType.STRING) //파일 카테고리 (회사이미지, 계정프로필이미지, 프로젝트 정보이미지, 게시뭂 파일, 체크승인요청파일, 체크 승인요청거절사유파일) - private String fileCategory; + private FileCategory category; @Column(name = "reference_id", nullable = false) private Long referenceId; //참조 ID - @Column(name = "file_display_title", length = 300, nullable = false) - private String fileDisplayTitle; //유저에게 보이는 파일명 - - @Column(name = "file_title", length = 50, nullable = false) - private String fileTitle; //저장된 파일 이름(UUID) + @Column(name = "display_title", length = MAX_FILE_DISPLAY_TITLE_LENGTH, nullable = false) + private String displayTitle; //파일명 - //TODO) 회원 ID JoinColumn, Audit - @CreatedBy - @Column(name = "written_by", updatable = false, nullable = false) - private Long writtenBy; //등록자 ID - - @CreatedDate - @Column(name = "written_at", updatable = false, nullable = false) - private LocalDateTime writtenAt; //등록 일시 + @Column(name = "title", length = MAX_FILE_TITLE_LENGTH, nullable = false) + private String title; //S3 저장명(UUID) @Column(name = "content_type", nullable = false) private String contentType; //MIME 타입 - @Column(name = "file_format", length = 50, nullable = false) + @Column(name = "format", length = MAX_FILE_FORMAT_LENGTH, nullable = false) private String fileFormat; //파일 확장자 (포맷) - @Column(name = "file_size", precision = 10, nullable = false) + @Column(name = "size", precision = FILE_SIZE_PRECISION, nullable = false) private Double fileSize; //파일 크기 (KB 단위) - @Column(name = "file_path", length = 1000, nullable = false) + @Column(name = "path", length = MAX_FILE_PATH_LENGTH, nullable = false) private String filePath; //파일 경로 //시스템 컬럼 @CreatedBy @Column(name = "created_by", nullable = false, updatable = false) - private Long createdBy; //최초 작성자 ID + private Long createdBy; //최초 등록자 ID @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; //최초 등록 일시 + + + public static FileMetadata registerFile(FileCategory category, + Long referenceId, + String displayTitle, + String title, + String contentType, + String fileFormat, + Double fileSize, + String filePath) { + + FileMetadata fileMetadata = new FileMetadata(); + fileMetadata.category = category; + fileMetadata.referenceId = referenceId; + fileMetadata.displayTitle = displayTitle; + fileMetadata.title = title; + fileMetadata.contentType = contentType; + fileMetadata.fileFormat = fileFormat; + fileMetadata.fileSize = fileSize; + fileMetadata.filePath = filePath; + + return fileMetadata; + } } \ No newline at end of file diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadataHistory.java b/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadataHistory.java index 1b1f0cd7..e24f0635 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadataHistory.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/entity/FileMetadataHistory.java @@ -1,74 +1,170 @@ package com.seveneleven.devlens.global.util.file.entity; +import com.seveneleven.devlens.domain.member.constant.Role; +import com.seveneleven.devlens.global.util.file.constant.FileCategory; +import com.seveneleven.devlens.global.util.file.constant.FileHistoryType; import jakarta.persistence.*; import lombok.*; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Entity @Getter -@Setter -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "file_metadata_history") public class FileMetadataHistory { + // Magic Numbers + private static final int MAX_REFERENCE_IDENTIFIER_LENGTH = 300; + private static final int MAX_FILE_DISPLAY_TITLE_LENGTH = 300; + private static final int MAX_FILE_TITLE_LENGTH = 50; + private static final int MAX_WRITER_NAME_LENGTH = 200; + private static final int MAX_WRITER_AUTHORITY_LENGTH = 50; + private static final int MAX_FILE_FORMAT_LENGTH = 50; + private static final int FILE_SIZE_PRECISION = 10; + private static final int MAX_FILE_PATH_LENGTH = 1000; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "file_metadata_history_id") + @Column(name = "id") private Long id; // 통합 파일 이력 ID - @Column(name = "history_type", nullable = false) - private String historyType; // 이력 유형(등록/삭제) + @Column(name = "type", nullable = false) + @Enumerated(EnumType.STRING) + private FileHistoryType type; // 이력 유형(등록/삭제) @Column(name = "file_category", nullable = false) + @Enumerated(EnumType.STRING) // 파일 카테고리(회사로고, 계정프로필이미지, 프로젝트 정보이미지, 게시물파일, 체크승인요청파일, 체크승인요청거절사유파일) - private String fileCategory; + private FileCategory fileCategory; + + @Column(name = "reference_id", nullable = false) + private Long referenceId; //참조 ID - @Column(name = "reference_identifier", length = 300, nullable = false) - private String referenceIdentifier; // 참조 구분자 - //회사로고 - 회사명, 계정프로필 - 계정이메일, 프로젝트 이미지 - 프로젝트명, 게시물 파일 - 게시물 제목 + // 회사로고 - 회사명, 계정프로필 - 계정이메일, 프로젝트 이미지 - 프로젝트명, 게시물 파일 - 게시물 제목 // 체크승인요청파일 - 요청일시, 체크 승인요청 거절사유 - 처리일시 + @Column(name = "reference_identifier", length = MAX_REFERENCE_IDENTIFIER_LENGTH, nullable = false) + private String referenceIdentifier; //참조 구분자 - @Column(name = "file_display_title", length = 300, nullable = false) - private String fileDisplayTitle; //유저에게 보이는 파일명 + @Column(name = "file_display_title", length = MAX_FILE_DISPLAY_TITLE_LENGTH, nullable = false) + private String fileDisplayTitle; //파일명 - @Column(name = "file_title", length = 50, nullable = false) + @Column(name = "file_title", length = MAX_FILE_TITLE_LENGTH, nullable = false) private String fileTitle; //S3 저장명(UUID) @Column(name = "writer_email", nullable = false) - private String writerEmail; // 등록자 이메일 + private String writerEmail; // 파일 등록자 이메일 - @Column(name = "writer_name", length = 200, nullable = false) - private String writerName; // 등록자 이름 + @Column(name = "writer_name", length = MAX_WRITER_NAME_LENGTH, nullable = false) + private String writerName; // 파일 등록자 이름 - @Column(name = "writer_authority", length = 50, nullable = false) - private String writerAuthority; // 등록자 권한 + @Column(name = "writer_authority", length = MAX_WRITER_AUTHORITY_LENGTH, nullable = false) + @Enumerated(EnumType.STRING) + private Role writerAuthority; // 파일 등록자 권한 @Column(name = "written_at", nullable = false) - private LocalDateTime writtenAt; // 등록 일시 + private LocalDateTime writtenAt; // 파일 등록 일시 @Column(name = "content_type", nullable = false) private String contentType; //MIME 타입 - @Column(name = "file_format", length = 50, nullable = false) + @Column(name = "file_format", length = MAX_FILE_FORMAT_LENGTH, nullable = false) private String fileFormat; // 파일 확장자(포맷) - @Column(name = "file_size", precision = 10, nullable = false) - private Float fileSize; // 파일 크기 (KB) + @Column(name = "file_size", precision = FILE_SIZE_PRECISION, nullable = false) + private Double fileSize; // 파일 크기 (KB) - @Column(name = "file_path", length = 1000, nullable = false) + @Column(name = "file_path", length = MAX_FILE_PATH_LENGTH, nullable = false) private String filePath; // 파일 경로 //시스템컬럼 @CreatedBy @Column(name = "created_by", nullable = false, updatable = false) - private Long createdBy; // 최초 작성자 ID + private Long createdBy; // 이력 등록자 ID @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; // 최초 등록 일시 + + // 공통 생성 메서드 + private static FileMetadataHistory createHistory(FileHistoryType type, + FileCategory fileCategory, + Long referenceId, + String referenceIdentifier, + String fileDisplayTitle, + String fileTitle, + String writerEmail, + String writerName, + Role writerAuthority, + LocalDateTime writtenAt, + String contentType, + String fileFormat, + Double fileSize, + String filePath) { + + FileMetadataHistory fileMetadataHistory = new FileMetadataHistory(); + fileMetadataHistory.type = type; + fileMetadataHistory.fileCategory = fileCategory; + fileMetadataHistory.referenceId = referenceId; + fileMetadataHistory.referenceIdentifier = referenceIdentifier; + fileMetadataHistory.fileDisplayTitle = fileDisplayTitle; + fileMetadataHistory.fileTitle = fileTitle; + fileMetadataHistory.writerEmail = writerEmail; + fileMetadataHistory.writerName = writerName; + fileMetadataHistory.writerAuthority = writerAuthority; + fileMetadataHistory.writtenAt = writtenAt; + fileMetadataHistory.contentType = contentType; + fileMetadataHistory.fileFormat = fileFormat; + fileMetadataHistory.fileSize = fileSize; + fileMetadataHistory.filePath = filePath; + + return fileMetadataHistory; + } + + + // 파일 등록 이력 생성 + public static FileMetadataHistory createRegisterHistory(FileCategory fileCategory, + Long referenceId, + String referenceIdentifier, + String fileDisplayTitle, + String fileTitle, + String writerEmail, + String writerName, + Role writerAuthority, + LocalDateTime writtenAt, + String contentType, + String fileFormat, + Double fileSize, + String filePath) { + + return createHistory(FileHistoryType.REGISTER, + fileCategory, referenceId, referenceIdentifier, + fileDisplayTitle, fileTitle, writerEmail, writerName, + writerAuthority, writtenAt, contentType, fileFormat, fileSize, filePath); + } + + // 파일 삭제 이력 생성 + public static FileMetadataHistory createDeleteHistory(FileCategory fileCategory, + Long referenceId, + String referenceIdentifier, + String fileDisplayTitle, + String fileTitle, + String writerEmail, + String writerName, + Role writerAuthority, + LocalDateTime writtenAt, + String contentType, + String fileFormat, + Double fileSize, + String filePath) { + + return createHistory(FileHistoryType.DELETE, + fileCategory, referenceId, referenceIdentifier, + fileDisplayTitle, fileTitle, writerEmail, writerName, + writerAuthority, writtenAt, contentType, fileFormat, fileSize, filePath); + } + } \ No newline at end of file diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/entity/Link.java b/src/main/java/com/seveneleven/devlens/global/util/file/entity/Link.java index 54581ad2..20c9139e 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/entity/Link.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/entity/Link.java @@ -1,47 +1,58 @@ package com.seveneleven.devlens.global.util.file.entity; +import com.seveneleven.devlens.global.util.file.constant.LinkCategory; import jakarta.persistence.*; import lombok.*; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Entity @Getter -@Setter -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "link") public class Link { + //Magic Number + private static final int MAX_LINK_LENGTH = 1000; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "link_id") - private Long id; // 통합 링크 ID + @Column(name = "id") + private Long id; //통합 링크 ID - @Column(name = "link_type_code", nullable = false) - private String linkTypeCode; // 첨부 유형 코드 + @Column(name = "category", nullable = false) + @Enumerated(EnumType.STRING) + //링크 카테고리 - 승인요청, 승인거절사유, 게시물 + private LinkCategory category; @Column(name = "reference_id", nullable = false) - private Long referenceId; // 참조 ID - - @CreatedBy - @Column(name = "written_by", nullable = false) - private Long writtenBy; // 등록자 ID - - @Column(name = "written_at", nullable = false) - private LocalDateTime writtenAt; // 등록 일시 + private Long referenceId; //참조 ID - @Column(name = "link", length = 1000) - private String link; // 링크 + @Column(name = "link", length = MAX_LINK_LENGTH, nullable = false) + private String link; //링크 + //시스템 컬럼 @CreatedBy @Column(name = "created_by", nullable = false) - private Long createdBy; // 최초 작성자 ID + private Long createdBy; //최초 등록자 ID @CreatedDate @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; // 최초 등록 일시 + private LocalDateTime createdAt; //최초 등록 일시 + + + public static Link registerLink(LinkCategory category, + Long referenceId, + String link) { + + Link linkData = new Link(); + linkData.category = category; + linkData.referenceId = referenceId; + linkData.link = link; + + return linkData; + } } diff --git a/src/main/java/com/seveneleven/devlens/global/util/file/entity/LinkHistory.java b/src/main/java/com/seveneleven/devlens/global/util/file/entity/LinkHistory.java index fc5c9b0d..dd361023 100644 --- a/src/main/java/com/seveneleven/devlens/global/util/file/entity/LinkHistory.java +++ b/src/main/java/com/seveneleven/devlens/global/util/file/entity/LinkHistory.java @@ -1,56 +1,125 @@ package com.seveneleven.devlens.global.util.file.entity; +import com.seveneleven.devlens.domain.member.constant.Role; +import com.seveneleven.devlens.global.util.file.constant.LinkCategory; +import com.seveneleven.devlens.global.util.file.constant.LinkHistoryType; import jakarta.persistence.*; import lombok.*; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Entity @Getter -@Setter -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "link_history") public class LinkHistory { + //Magic Number + private static final int MAX_REFERENCE_IDENTIFIER_LENGTH = 300; + private static final int MAX_WRITER_NAME_LENGTH = 200; + private static final int MAX_WRITER_AUTHORITY_LENGTH = 50; + private static final int MAX_LINK_LENGTH = 1000; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "link_id") + @Column(name = "id") private Long id; // 통합 링크 이력 ID - @Column(name = "history_type_code", nullable = false) - private String historyTypeCode; // 이력 유형 코드 + @Column(name = "type", nullable = false) + @Enumerated(EnumType.STRING) + private LinkHistoryType type; // 이력 유형(등록/삭제) + + @Column(name = "link_category", nullable = false) + @Enumerated(EnumType.STRING) + private LinkCategory linkCategory; //링크 카테고리 - @Column(name = "link_type_code", nullable = false) - private String linkTypeCode; // 첨부 유형 코드 + @Column(name = "reference_id", nullable = false) + private Long referenceId; //참조 ID - @Column(name = "reference_identifier", length = 300, nullable = false) + @Column(name = "reference_identifier", length = MAX_REFERENCE_IDENTIFIER_LENGTH, nullable = false) private String referenceIdentifier; // 참조 구분자 @Column(name = "writer_email", nullable = false) private String writerEmail; // 등록자 이메일 - @Column(name = "writer_name", length = 200, nullable = false) + @Column(name = "writer_name", length = MAX_WRITER_NAME_LENGTH, nullable = false) private String writerName; // 등록자 이름 - @Column(name = "writer_authority", length = 50, nullable = false) - private String writerAuthority; // 등록자 권한 + @Column(name = "writer_authority", length = MAX_WRITER_AUTHORITY_LENGTH, nullable = false) + @Enumerated(EnumType.STRING) + private Role writerAuthority; // 등록자 권한 - @CreatedDate @Column(name = "written_at", nullable = false) - private LocalDateTime writedAt; // 등록 일시 + private LocalDateTime writtenAt; // 링크 등록 일시 - @Column(name = "link", length = 1000) + @Column(name = "link", length = MAX_LINK_LENGTH) private String link; // 링크 + //시스템 컬럼 @CreatedBy @Column(name = "created_by", nullable = false, updatable = false) - private Long createdBy; // 최초 작성자 ID + private Long createdBy; // 이력 작성자 ID @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; // 최초 등록 일시 + private LocalDateTime createdAt; // 이력 등록 일시 + + //공통 생성 메서드 + private static LinkHistory createHistory(LinkHistoryType type, + LinkCategory linkCategory, + Long referenceId, + String referenceIdentifier, + String writerEmail, + String writerName, + Role writerAuthority, + LocalDateTime writtenAt, + String link){ + + LinkHistory history = new LinkHistory(); + history.type = type; + history.linkCategory = linkCategory; + history.referenceId = referenceId; + history.referenceIdentifier = referenceIdentifier; + history.writerEmail = writerEmail; + history.writerName = writerName; + history.writerAuthority = writerAuthority; + history.writtenAt = writtenAt; + history.link = link; + + return history; + } + + //파일 등록 이력 생성 + public static LinkHistory createRegisterHistory(LinkCategory linkCategory, + Long referenceId, + String referenceIdentifier, + String writerEmail, + String writerName, + Role writerAuthority, + LocalDateTime writtenAt, + String link){ + + return createHistory(LinkHistoryType.REGISTER, + linkCategory, referenceId, referenceIdentifier, + writerEmail, writerName, writerAuthority, writtenAt, link); + } + + //파일 삭제 이력 생성 + public static LinkHistory createDeleteHistory(LinkCategory linkCategory, + Long referenceId, + String referenceIdentifier, + String writerEmail, + String writerName, + Role writerAuthority, + LocalDateTime writtenAt, + String link){ + + return createHistory(LinkHistoryType.DELETE, + linkCategory, referenceId, referenceIdentifier, + writerEmail, writerName, writerAuthority, writtenAt, link); + } + }