diff --git a/src/main/generated/acc/hotsix/file_share/domain/QFile.java b/src/main/generated/acc/hotsix/file_share/domain/QFile.java index 573434a..0e1e085 100644 --- a/src/main/generated/acc/hotsix/file_share/domain/QFile.java +++ b/src/main/generated/acc/hotsix/file_share/domain/QFile.java @@ -7,6 +7,7 @@ import com.querydsl.core.types.PathMetadata; import javax.annotation.processing.Generated; import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; /** @@ -33,6 +34,8 @@ public class QFile extends EntityPathBase { public final StringPath link = createString("link"); + public final ListPath logs = this.createList("logs", Log.class, QLog.class, PathInits.DIRECT2); + public final StringPath name = createString("name"); public final StringPath password = createString("password"); diff --git a/src/main/generated/acc/hotsix/file_share/domain/QLog.java b/src/main/generated/acc/hotsix/file_share/domain/QLog.java new file mode 100644 index 0000000..39eaae1 --- /dev/null +++ b/src/main/generated/acc/hotsix/file_share/domain/QLog.java @@ -0,0 +1,55 @@ +package acc.hotsix.file_share.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QLog is a Querydsl query type for Log + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QLog extends EntityPathBase { + + private static final long serialVersionUID = -26769568L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QLog log = new QLog("log"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); + + public final QFile file; + + public final NumberPath logId = createNumber("logId", Long.class); + + public final EnumPath type = createEnum("type", Log.Type.class); + + public QLog(String variable) { + this(Log.class, forVariable(variable), INITS); + } + + public QLog(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QLog(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QLog(PathMetadata metadata, PathInits inits) { + this(Log.class, metadata, inits); + } + + public QLog(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.file = inits.isInitialized("file") ? new QFile(forProperty("file")) : null; + } + +} + diff --git a/src/main/java/acc/hotsix/file_share/api/FileDeleteController.java b/src/main/java/acc/hotsix/file_share/api/FileDeleteController.java index fe792bf..af89c6b 100644 --- a/src/main/java/acc/hotsix/file_share/api/FileDeleteController.java +++ b/src/main/java/acc/hotsix/file_share/api/FileDeleteController.java @@ -22,7 +22,7 @@ public class FileDeleteController { private final FileService fileService; private final FileDeleteService fileDeleteService; - @PostMapping("/files/delete/{id}") + @PostMapping("/files/{id}/delete") public ResponseEntity> deleteFile( @PathVariable("id")Long fileId, @Valid @ModelAttribute DeleteFileReq req, BindingResult bindingResult diff --git a/src/main/java/acc/hotsix/file_share/api/LogController.java b/src/main/java/acc/hotsix/file_share/api/LogController.java new file mode 100644 index 0000000..e6d2d09 --- /dev/null +++ b/src/main/java/acc/hotsix/file_share/api/LogController.java @@ -0,0 +1,40 @@ +package acc.hotsix.file_share.api; + +import acc.hotsix.file_share.application.LogService; +import acc.hotsix.file_share.dto.LogResponseDto; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@RestController +@AllArgsConstructor +@RequestMapping("/files") +public class LogController { + + private final LogService logService; + +// @GetMapping("/{file-id}/logs") +// public ResponseEntity> getFileLogs(@PathVariable("file-id") Long fileId, +// @RequestParam(defaultValue = "0") int page, +// @RequestParam(defaultValue = "10") int size) { +// +// return ResponseEntity.ok().body(logService.findLogsByFileId(fileId, page, size)); +// } + + @GetMapping("/{file-id}/logs") + public ResponseEntity> getFileLogsByType(@PathVariable("file-id") Long fileId, + @RequestParam(required = false,defaultValue = "none") String type, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + + return ResponseEntity.ok().body(logService.findLogsByFileIdAndType(fileId, type, page, size)); + } +} + +// 로그 파일id로 조회, +// 로그 타입으로 조회, 로그 생성 시각으로 조회 \ No newline at end of file diff --git a/src/main/java/acc/hotsix/file_share/application/FileDownloadService.java b/src/main/java/acc/hotsix/file_share/application/FileDownloadService.java index ba3ec6e..c96cdda 100644 --- a/src/main/java/acc/hotsix/file_share/application/FileDownloadService.java +++ b/src/main/java/acc/hotsix/file_share/application/FileDownloadService.java @@ -1,7 +1,9 @@ package acc.hotsix.file_share.application; import acc.hotsix.file_share.dao.FileRepository; +import acc.hotsix.file_share.dao.LogRepository; import acc.hotsix.file_share.domain.File; +import acc.hotsix.file_share.domain.Log; import acc.hotsix.file_share.dto.FileDownloadDto; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.S3Object; @@ -16,21 +18,26 @@ public class FileDownloadService { private final AmazonS3Client amazonS3Client; private final FileRepository fileRepository; + private final LogRepository logRepository; @Value("${cloud.aws.s3.bucket}") private String bucketName; @Transactional public FileDownloadDto downloadFile(Long fileId) { - updateDownloadCount(fileId); + File file = fileRepository.findById(fileId).get(); + file.updateDownloadCount(); + + Log log = Log.builder() + .type(Log.Type.DOWNLOAD) + .file(file) + .build(); + + logRepository.save(log); + S3Object object = amazonS3Client.getObject(bucketName, fileId.toString()); String filename = fileRepository.findNameById(fileId); return new FileDownloadDto(object.getObjectContent(), filename); } - private void updateDownloadCount(Long fileId) { - File file = fileRepository.findById(fileId).get(); - file.updateDownloadCount(); - } - } diff --git a/src/main/java/acc/hotsix/file_share/application/FileServiceImpl.java b/src/main/java/acc/hotsix/file_share/application/FileServiceImpl.java index 1b31dd8..8d5153b 100644 --- a/src/main/java/acc/hotsix/file_share/application/FileServiceImpl.java +++ b/src/main/java/acc/hotsix/file_share/application/FileServiceImpl.java @@ -1,7 +1,9 @@ package acc.hotsix.file_share.application; import acc.hotsix.file_share.dao.FileRepository; +import acc.hotsix.file_share.dao.LogRepository; import acc.hotsix.file_share.domain.File; +import acc.hotsix.file_share.domain.Log; import acc.hotsix.file_share.dto.FileMetadataResponseDto; import acc.hotsix.file_share.global.error.FileNotFoundException; import acc.hotsix.file_share.global.error.InvalidShareLinkException; @@ -21,6 +23,8 @@ public class FileServiceImpl implements FileService { private final PasswordEncoder passwordEncoder; + private final LogRepository logRepository; + // ID를 통한 파일 메타데이터 조회 public File getFileById(Long fileId) throws FileNotFoundException { Optional result = fileRepository.findById(fileId); @@ -77,6 +81,14 @@ public String getResourceByLink(String link) throws InvalidShareLinkException { public FileMetadataResponseDto getMetadataById(Long fileId) { File file = fileRepository.findById(fileId).get(); file.updateViewCount(); + + Log log = Log.builder() + .type(Log.Type.READ) + .file(file) + .build(); + + logRepository.save(log); + return FileMetadataResponseDto.toFileResponseDto(file); } } diff --git a/src/main/java/acc/hotsix/file_share/application/FileUpdateServiceImpl.java b/src/main/java/acc/hotsix/file_share/application/FileUpdateServiceImpl.java index 49be767..83c4213 100644 --- a/src/main/java/acc/hotsix/file_share/application/FileUpdateServiceImpl.java +++ b/src/main/java/acc/hotsix/file_share/application/FileUpdateServiceImpl.java @@ -1,6 +1,8 @@ package acc.hotsix.file_share.application; +import acc.hotsix.file_share.dao.LogRepository; import acc.hotsix.file_share.domain.File; +import acc.hotsix.file_share.domain.Log; import acc.hotsix.file_share.global.error.FileDuplicateException; import acc.hotsix.file_share.global.error.FileNotFoundException; import acc.hotsix.file_share.global.error.FileTypeMismatchException; @@ -20,6 +22,7 @@ public class FileUpdateServiceImpl implements FileUpdateService { private final FileService fileService; private final FileUploadService fileUploadService; + private final LogRepository logRepository; // 파일 업데이트 public void updateFile(String fileId, String newDirectory, MultipartFile file) @@ -57,5 +60,13 @@ public void updateFile(String fileId, String newDirectory, MultipartFile file) // 업데이트된 메타 데이터 저장 fileService.saveMetaData(fileMetaData); + + // 수정 로그 생성 + Log log = Log.builder() + .type(Log.Type.UPDATE) + .file(fileMetaData) + .build(); + + logRepository.save(log); } } diff --git a/src/main/java/acc/hotsix/file_share/application/FileUploadServiceImpl.java b/src/main/java/acc/hotsix/file_share/application/FileUploadServiceImpl.java index becb6d0..af7fb0d 100644 --- a/src/main/java/acc/hotsix/file_share/application/FileUploadServiceImpl.java +++ b/src/main/java/acc/hotsix/file_share/application/FileUploadServiceImpl.java @@ -1,5 +1,7 @@ package acc.hotsix.file_share.application; +import acc.hotsix.file_share.dao.LogRepository; +import acc.hotsix.file_share.domain.Log; import acc.hotsix.file_share.global.error.FileDuplicateException; import acc.hotsix.file_share.global.error.UploadFileException; import com.amazonaws.services.s3.AmazonS3Client; @@ -11,6 +13,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @@ -31,6 +34,8 @@ public class FileUploadServiceImpl implements FileUploadService { private final PasswordEncoder passwordEncoder; + private final LogRepository logRepository; + @Value("${cloud.aws.s3.bucket}") private String bucketName; @@ -63,6 +68,7 @@ public String uploadFileToS3(MultipartFile file, String key) throws UploadFileEx } // 파일 업로드 프로세스 + @Transactional public void uploadFile(MultipartFile file, String directory, String password) throws UploadFileException, FileDuplicateException { List duplicateFiles = fileService.getSameNameAndPathFileList(file.getOriginalFilename(), directory); if (duplicateFiles.size() > 0) { // 중복 파일 처리 @@ -109,5 +115,13 @@ public void uploadFile(MultipartFile file, String directory, String password) th fileService.removeFileMetaData(savedFile); throw new UploadFileException(); } + + // 파일 등록 로그 생성 + Log log = Log.builder() + .file(savedFile) + .type(Log.Type.CREATE) + .build(); + + logRepository.save(log); } } diff --git a/src/main/java/acc/hotsix/file_share/application/LogService.java b/src/main/java/acc/hotsix/file_share/application/LogService.java new file mode 100644 index 0000000..237c0c4 --- /dev/null +++ b/src/main/java/acc/hotsix/file_share/application/LogService.java @@ -0,0 +1,43 @@ +package acc.hotsix.file_share.application; + +import acc.hotsix.file_share.dao.LogRepository; +import acc.hotsix.file_share.domain.Log; +import acc.hotsix.file_share.dto.LogResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class LogService { + private final LogRepository logRepository; + +// public List findLogsByFileId(Long fileId, int page, int size) { +// Pageable pageable = PageRequest.of(page, size); +// List content = logRepository.findByFileFileId(fileId, pageable).getContent(); +// +// return content.stream() +// .map(log -> LogResponseDto.toLogResponseDto(log)) +// .collect(Collectors.toList()); +// } + + public List findLogsByFileIdAndType(Long fileId, String type, int page, int size) { + Pageable pageable = PageRequest.of(page, size); + List content; + + if(type.equals("none")) + content = logRepository.findByFileFileId(fileId, pageable).getContent(); + else + content = logRepository.findByFileFileIdAndType(fileId, Log.Type.valueOf(type), pageable).getContent(); + + return content.stream() + .map(log -> LogResponseDto.toLogResponseDto(log)) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/acc/hotsix/file_share/dao/LogRepository.java b/src/main/java/acc/hotsix/file_share/dao/LogRepository.java new file mode 100644 index 0000000..9b86870 --- /dev/null +++ b/src/main/java/acc/hotsix/file_share/dao/LogRepository.java @@ -0,0 +1,14 @@ +package acc.hotsix.file_share.dao; + +import acc.hotsix.file_share.domain.Log; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LogRepository extends JpaRepository { + Page findByFileFileId(Long fileId, Pageable pageable); + + Page findByFileFileIdAndType(Long fileId, Log.Type type, Pageable pageable); +} diff --git a/src/main/java/acc/hotsix/file_share/domain/File.java b/src/main/java/acc/hotsix/file_share/domain/File.java index 04ceec6..c3f5f8b 100644 --- a/src/main/java/acc/hotsix/file_share/domain/File.java +++ b/src/main/java/acc/hotsix/file_share/domain/File.java @@ -8,6 +8,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; +import java.util.List; @Entity @Getter @@ -52,6 +53,9 @@ public class File { private String link; + @OneToMany(mappedBy = "file", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private List logs; + @Builder public File(Long fileId, String name, LocalDateTime createdAt, String resource, String fileType, Double fileSize, String path, LocalDateTime lastModifiedAt, Long download, Long view, boolean uploaded, String password, String link) { this.fileId = fileId; diff --git a/src/main/java/acc/hotsix/file_share/domain/Log.java b/src/main/java/acc/hotsix/file_share/domain/Log.java new file mode 100644 index 0000000..b6677e8 --- /dev/null +++ b/src/main/java/acc/hotsix/file_share/domain/Log.java @@ -0,0 +1,41 @@ +package acc.hotsix.file_share.domain; + +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class Log { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long logId; + + @Enumerated(EnumType.STRING) + private Type type; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="file_id") + private File file; + + @CreatedDate + private LocalDateTime createdAt; + + public enum Type { + CREATE, READ, UPDATE, DOWNLOAD + } + + @Builder + private Log(Type type, File file) { + this.type = type; + this.file = file; + } +} diff --git a/src/main/java/acc/hotsix/file_share/dto/LogResponseDto.java b/src/main/java/acc/hotsix/file_share/dto/LogResponseDto.java new file mode 100644 index 0000000..afd1cb6 --- /dev/null +++ b/src/main/java/acc/hotsix/file_share/dto/LogResponseDto.java @@ -0,0 +1,30 @@ +package acc.hotsix.file_share.dto; + +import acc.hotsix.file_share.domain.Log; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class LogResponseDto { + private Long logId; + private Long fileId; + private String type; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + private LocalDateTime createdAt; + + public static LogResponseDto toLogResponseDto(Log log) { + return LogResponseDto.builder() + .logId(log.getLogId()) + .fileId(log.getFile().getFileId()) + .type(log.getType().toString()) + .createdAt(log.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/acc/hotsix/file_share/global/config/SecurityConfig.java b/src/main/java/acc/hotsix/file_share/global/config/SecurityConfig.java index 8810f2c..b36f99d 100644 --- a/src/main/java/acc/hotsix/file_share/global/config/SecurityConfig.java +++ b/src/main/java/acc/hotsix/file_share/global/config/SecurityConfig.java @@ -18,9 +18,11 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests((authorizeRequests) -> + .headers(headers -> headers.frameOptions(frame -> frame.disable())) + .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().permitAll()) .formLogin(AbstractHttpConfigurer::disable); + return http.build(); }