From 4ddf2f5dde832edb2a4b4338b682f4b3168ef439 Mon Sep 17 00:00:00 2001 From: vladp Date: Mon, 15 Dec 2025 19:49:09 +0700 Subject: [PATCH 01/13] add bone for file system --- docker-compose.yaml | 2 + .../configuration/file/AsyncConfig.java | 24 ++ .../controller/file/FileUploadController.java | 120 ++++++ .../ru/soune/no_copy/dto/UserContentDTO.java | 1 - .../no_copy/dto/UserContentResponse.java | 2 +- .../no_copy/dto/file/ChunkUploadRequest.java | 17 + .../no_copy/dto/file/FileApiResponse.java | 41 ++ .../no_copy/dto/file/InitUploadRequest.java | 20 + .../dto/file/UploadProgressResponse.java | 39 ++ .../ru/soune/no_copy/entity/UserContent.java | 2 +- .../no_copy/entity/{ => file}/FileType.java | 2 +- .../entity/file/FileUploadSession.java | 67 ++++ .../no_copy/entity/file/UploadStatus.java | 9 + .../exception/ChunkSizeExceededException.java | 7 + .../exception/FileIntegrityException.java | 7 + .../exception/FileUploadException.java | 11 + .../UploadSessionNotFoundException.java | 7 + .../FileUploadSessionRepository.java | 9 + .../repository/UserContentRepository.java | 2 +- .../no_copy/service/UserContentService.java | 2 +- .../service/file/FileCleanupService.java | 80 ++++ .../service/file/FileUploadService.java | 17 + .../service/file/FileUploadServiceImpl.java | 364 ++++++++++++++++++ src/main/resources/application.yaml | 26 +- 24 files changed, 871 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java create mode 100644 src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java create mode 100644 src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java create mode 100644 src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java create mode 100644 src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java create mode 100644 src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java rename src/main/java/ru/soune/no_copy/entity/{ => file}/FileType.java (88%) create mode 100644 src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java create mode 100644 src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java create mode 100644 src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java create mode 100644 src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java create mode 100644 src/main/java/ru/soune/no_copy/exception/FileUploadException.java create mode 100644 src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java create mode 100644 src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java create mode 100644 src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java create mode 100644 src/main/java/ru/soune/no_copy/service/file/FileUploadService.java create mode 100644 src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java diff --git a/docker-compose.yaml b/docker-compose.yaml index 13e1b65..8817acc 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -37,6 +37,8 @@ services: - app - backend - api + volumes: + - ./uploads:/data/uploads grafana: image: grafana/grafana:10.3.1 diff --git a/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java b/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java new file mode 100644 index 0000000..bd75b72 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java @@ -0,0 +1,24 @@ +package ru.soune.no_copy.configuration.file; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +public class AsyncConfig { + + @Bean(name = "fileUploadTaskExecutor") + public Executor fileUploadTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(3); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(50); + executor.setThreadNamePrefix("FileUpload-"); + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java b/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java new file mode 100644 index 0000000..47b7f47 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java @@ -0,0 +1,120 @@ +package ru.soune.no_copy.controller.file; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import ru.soune.no_copy.dto.file.ChunkUploadRequest; +import ru.soune.no_copy.dto.file.FileApiResponse; +import ru.soune.no_copy.dto.file.InitUploadRequest; +import ru.soune.no_copy.dto.file.UploadProgressResponse; +import ru.soune.no_copy.entity.AuthToken; +import ru.soune.no_copy.entity.file.FileType; +import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.no_copy.exception.NotFoundAuthToken; +import ru.soune.no_copy.repository.AuthTokenRepository; +import ru.soune.no_copy.service.file.FileUploadService; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +@Slf4j +@RestController +@RequestMapping("/api/files") +@RequiredArgsConstructor +public class FileUploadController { + + private final FileUploadService fileUploadService; + + private final AuthTokenRepository authTokenRepository; + + //TODO ADD TEMPLATE JSON, RESPONSE OK,ADD MESSAGE CODE + @PostMapping("/init") + public ResponseEntity> initUpload(@Valid @RequestBody InitUploadRequest request, + @RequestHeader("Authorization") String tokenHeader) { + String token = tokenHeader.replace("Bearer ", ""); + + Optional tokenOptional = authTokenRepository.findByToken(token); + + AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); + + try { + FileUploadSession session = fileUploadService.initUpload(authToken.getUser().getId(), request.getFileName(), + request.getFileType(), request.getFileSize()); + + return ResponseEntity.ok(FileApiResponse.success(session)); + } catch (Exception e) { + log.error("Error initializing upload", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error(e.getMessage())); + } + } + + @GetMapping("/file-types") + public ResponseEntity> getFileTypes() { + return ResponseEntity.ok(Arrays.asList(FileType.values())); + } + + + @PostMapping("/chunk") + public ResponseEntity> uploadChunk( + @Valid @RequestBody ChunkUploadRequest chunkUploadRequest, + @RequestParam("file") MultipartFile chunk, @RequestHeader("Authorization") String tokenHeader) { + String token = tokenHeader.replace("Bearer ", ""); + + Optional tokenOptional = authTokenRepository.findByToken(token); + + tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); + + try { + UploadProgressResponse progress = fileUploadService.uploadChunk( + chunkUploadRequest.getUploadId(), chunkUploadRequest.getChunkNumber(), chunk); + + return ResponseEntity.ok(FileApiResponse.success(progress)); + + } catch (Exception e) { + log.error("Error uploading chunk", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error(e.getMessage())); + } + } + +// @GetMapping("/progress/{uploadId}") +// public ResponseEntity> getProgress( +// @PathVariable String uploadId, +// @RequestHeader(value = "X-User-Id", required = false, defaultValue = "1") Long userId) { +// +// try { +// UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); +// return ResponseEntity.ok(FileApiResponse.success(progress)); +// +// } catch (Exception e) { +// log.error("Error getting progress", e); +// return ResponseEntity.badRequest() +// .body(FileApiResponse.error(e.getMessage())); +// } +// } +// +// @PostMapping("/complete/{uploadId}") +// public ResponseEntity> completeUpload( +// @PathVariable String uploadId, +// @RequestHeader(value = "X-User-Id", required = false, defaultValue = "1") Long userId) { +// +// log.info("Completing upload for session {}", uploadId); +// +// try { +// UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); +// +// return ResponseEntity.ok(FileApiResponse.success( +// "File assembly in progress", progress)); +// +// } catch (Exception e) { +// log.error("Error completing upload", e); +// return ResponseEntity.badRequest() +// .body(FileApiResponse.error(e.getMessage())); +// } +// } +} diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java b/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java index 90df1d9..4c9c6f0 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java +++ b/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java @@ -1,7 +1,6 @@ package ru.soune.no_copy.dto; import lombok.Data; -import ru.soune.no_copy.entity.FileType; @Data public class UserContentDTO { diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java b/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java index 86d1d84..d2a817c 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java +++ b/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java @@ -1,7 +1,7 @@ package ru.soune.no_copy.dto; import ru.soune.no_copy.entity.ContentStatus; -import ru.soune.no_copy.entity.FileType; +import ru.soune.no_copy.entity.file.FileType; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java b/src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java new file mode 100644 index 0000000..25c68fc --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java @@ -0,0 +1,17 @@ +package ru.soune.no_copy.dto.file; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChunkUploadRequest { + + @NotBlank + private String uploadId; + + private Integer chunkNumber; +} diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java b/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java new file mode 100644 index 0000000..d271a1c --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java @@ -0,0 +1,41 @@ +package ru.soune.no_copy.dto.file; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileApiResponse { + + private boolean success; + private String message; + private T data; + private String error; + + public static FileApiResponse success(T data) { + return FileApiResponse.builder() + .success(true) + .message("Operation completed successfully") + .data(data) + .build(); + } + + public static FileApiResponse success(String message, T data) { + return FileApiResponse.builder() + .success(true) + .message(message) + .data(data) + .build(); + } + + public static FileApiResponse error(String error) { + return FileApiResponse.builder() + .success(false) + .error(error) + .build(); + } +} diff --git a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java b/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java new file mode 100644 index 0000000..9653984 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java @@ -0,0 +1,20 @@ +package ru.soune.no_copy.dto.file; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +@Data +public class InitUploadRequest { + + @NotBlank(message = "File name is required") + private String fileName; + + @NotBlank(message = "File type is required") + private String fileType; + + @NotNull(message = "File size is required") + @Positive(message = "File size must be positive") + private Long fileSize; +} diff --git a/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java b/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java new file mode 100644 index 0000000..be77f03 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java @@ -0,0 +1,39 @@ +package ru.soune.no_copy.dto.file; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.no_copy.entity.file.UploadStatus; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UploadProgressResponse { + + private String uploadId; + private String fileName; + private Integer totalChunks; + private Integer uploadedChunks; + private UploadStatus status; + private Integer progressPercentage; + private String filePath; + + public static UploadProgressResponse fromSession(FileUploadSession session) { + int progress = session.getTotalChunks() == 0 ? 0 : + (session.getChunksUploaded() * 100) / session.getTotalChunks(); + + return UploadProgressResponse.builder() + .uploadId(session.getUploadId()) + .fileName(session.getFileName()) + .totalChunks(session.getTotalChunks()) + .uploadedChunks(session.getChunksUploaded()) + .status(session.getStatus()) + .progressPercentage(progress) + .filePath(session.getFilePath()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/ru/soune/no_copy/entity/UserContent.java b/src/main/java/ru/soune/no_copy/entity/UserContent.java index 22ee0b0..a6603c1 100644 --- a/src/main/java/ru/soune/no_copy/entity/UserContent.java +++ b/src/main/java/ru/soune/no_copy/entity/UserContent.java @@ -2,9 +2,9 @@ package ru.soune.no_copy.entity; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.CreationTimestamp; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import ru.soune.no_copy.entity.file.FileType; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/entity/FileType.java b/src/main/java/ru/soune/no_copy/entity/file/FileType.java similarity index 88% rename from src/main/java/ru/soune/no_copy/entity/FileType.java rename to src/main/java/ru/soune/no_copy/entity/file/FileType.java index 4e0406d..dd7f6ab 100644 --- a/src/main/java/ru/soune/no_copy/entity/FileType.java +++ b/src/main/java/ru/soune/no_copy/entity/file/FileType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.no_copy.entity.file; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java b/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java new file mode 100644 index 0000000..08f54f3 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java @@ -0,0 +1,67 @@ +package ru.soune.no_copy.entity.file; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "file_upload_sessions") +public class FileUploadSession { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "upload_id") + private String uploadId; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "file_name", nullable = false) + private String fileName; + + @Column(name = "file_type") + private String fileType; + + @Column(name = "file_size") + private Long fileSize; + + @Column(name = "total_chunks") + private Integer totalChunks; + + @Column(name = "chunks_uploaded") + private Integer chunksUploaded; + + @Column(name = "status") + @Enumerated(EnumType.STRING) + private UploadStatus status; + + @Column(name = "file_path") + private String filePath; + + @Column(name = "checksum") + private String checksum; + + @CreatedDate + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @Column(name = "completed_at") + private LocalDateTime completedAt; + + @ElementCollection + @CollectionTable( + name = "uploaded_chunks", + joinColumns = @JoinColumn(name = "upload_id") + ) + @MapKeyColumn(name = "chunk_number") + @Column(name = "chunk_path") + private Map chunkPaths = new HashMap<>(); +} diff --git a/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java b/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java new file mode 100644 index 0000000..d1d3418 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java @@ -0,0 +1,9 @@ +package ru.soune.no_copy.entity.file; + +public enum UploadStatus { + INITIATED, + UPLOADING, + COMPLETED, + FAILED, + CANCELLED +} diff --git a/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java b/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java new file mode 100644 index 0000000..e45f12b --- /dev/null +++ b/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java @@ -0,0 +1,7 @@ +package ru.soune.no_copy.exception; + +public class ChunkSizeExceededException extends RuntimeException { + public ChunkSizeExceededException(long actualSize, long maxSize) { + super(String.format("Chunk size exceeded: %d > %d", actualSize, maxSize)); + } +} diff --git a/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java b/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java new file mode 100644 index 0000000..2fece21 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java @@ -0,0 +1,7 @@ +package ru.soune.no_copy.exception; + +public class FileIntegrityException extends RuntimeException { + public FileIntegrityException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/soune/no_copy/exception/FileUploadException.java b/src/main/java/ru/soune/no_copy/exception/FileUploadException.java new file mode 100644 index 0000000..6c74b1d --- /dev/null +++ b/src/main/java/ru/soune/no_copy/exception/FileUploadException.java @@ -0,0 +1,11 @@ +package ru.soune.no_copy.exception; + +public class FileUploadException extends RuntimeException { + public FileUploadException(String message) { + super(message); + } + + public FileUploadException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java b/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java new file mode 100644 index 0000000..bfc4bfc --- /dev/null +++ b/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java @@ -0,0 +1,7 @@ +package ru.soune.no_copy.exception; + +public class UploadSessionNotFoundException extends RuntimeException { + public UploadSessionNotFoundException(String uploadId) { + super("Upload session not found: " + uploadId); + } +} diff --git a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java b/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java new file mode 100644 index 0000000..de7b788 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java @@ -0,0 +1,9 @@ +package ru.soune.no_copy.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.soune.no_copy.entity.file.FileUploadSession; + +@Repository +public interface FileUploadSessionRepository extends JpaRepository { +} diff --git a/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java b/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java index 95f524b..42f5e72 100644 --- a/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java +++ b/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java @@ -2,7 +2,7 @@ package ru.soune.no_copy.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import ru.soune.no_copy.entity.FileType; +import ru.soune.no_copy.entity.file.FileType; import ru.soune.no_copy.entity.UserContent; import java.util.List; diff --git a/src/main/java/ru/soune/no_copy/service/UserContentService.java b/src/main/java/ru/soune/no_copy/service/UserContentService.java index eb89f77..eb2573d 100644 --- a/src/main/java/ru/soune/no_copy/service/UserContentService.java +++ b/src/main/java/ru/soune/no_copy/service/UserContentService.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.no_copy.dto.UserContentRequest; import ru.soune.no_copy.dto.UserContentUpdateRequest; -import ru.soune.no_copy.entity.FileType; +import ru.soune.no_copy.entity.file.FileType; import ru.soune.no_copy.entity.User; import ru.soune.no_copy.entity.UserContent; import ru.soune.no_copy.exception.ContentNotFoundException; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java b/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java new file mode 100644 index 0000000..65a8f05 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java @@ -0,0 +1,80 @@ +package ru.soune.no_copy.service.file; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FileCleanupService { + + @Value("${file.storage.base-path}") + private String basePath; + + @Value("${file.storage.temp-ttl-hours}") + private int tempTtlHours; + + @Scheduled(cron = "0 0 3 * * *") + public void cleanupExpiredFiles() { + log.info("Starting cleanup of expired temporary files"); + + Path tempDir = Paths.get(basePath, "temp"); + if (!Files.exists(tempDir)) { + return; + } + + try { + Files.walkFileTree(tempDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + + LocalDateTime fileTime = LocalDateTime.ofInstant( + Instant.ofEpochMilli(attrs.lastModifiedTime().toMillis()), + ZoneId.systemDefault() + ); + + LocalDateTime cutoffTime = LocalDateTime.now() + .minusHours(tempTtlHours); + + if (fileTime.isBefore(cutoffTime)) { + Files.delete(file); + log.debug("Deleted expired file: {}", file); + } + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + + if (exc == null) { + if (Files.list(dir).count() == 0 && + !dir.equals(tempDir)) { + Files.delete(dir); + log.debug("Deleted empty directory: {}", dir); + } + } + + return FileVisitResult.CONTINUE; + } + }); + + log.info("Cleanup completed successfully"); + + } catch (IOException e) { + log.error("Error during cleanup", e); + } + } +} diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java new file mode 100644 index 0000000..dbf4776 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java @@ -0,0 +1,17 @@ +package ru.soune.no_copy.service.file; + +import org.springframework.web.multipart.MultipartFile; +import ru.soune.no_copy.dto.file.UploadProgressResponse; +import ru.soune.no_copy.entity.file.FileUploadSession; + +public interface FileUploadService { + FileUploadSession initUpload(Long userId, String fileName, + String fileType, long fileSize); + + UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, + MultipartFile chunkFile); + + UploadProgressResponse getUploadProgress(String uploadId); + + void cleanupExpiredSessions(); +} diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java new file mode 100644 index 0000000..5030223 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java @@ -0,0 +1,364 @@ +package ru.soune.no_copy.service.file; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import ru.soune.no_copy.dto.file.UploadProgressResponse; +import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.no_copy.entity.file.UploadStatus; +import ru.soune.no_copy.exception.ChunkSizeExceededException; +import ru.soune.no_copy.exception.FileUploadException; +import ru.soune.no_copy.exception.UploadSessionNotFoundException; +import ru.soune.no_copy.repository.FileUploadSessionRepository; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.stream.Stream; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileUploadServiceImpl implements FileUploadService { + + private final FileUploadSessionRepository sessionRepository; + + @Value("${file.storage.base-path}") + private String basePath; + + @Value("${file.storage.chunk-size}") + private int chunkSize; + + @Value("${file.storage.max-file-size}") + private long maxFileSize; + + @PostConstruct + public void init() { + try { + Path storageRoot = Paths.get(basePath).toAbsolutePath().normalize(); + + log.info("Storage will be at: {}", storageRoot); + + if (!Files.exists(storageRoot)) { + Files.createDirectories(storageRoot); + log.info("Created: {}", storageRoot); + } + + Files.createDirectories(storageRoot.resolve("temp")); + Files.createDirectories(storageRoot.resolve("user")); + + } catch (IOException e) { + log.error("CANNOT CREATE STORAGE!", e); + throw new RuntimeException("Storage failed", e); + } + } + + @Override + @Transactional + public FileUploadSession initUpload(Long userId, String fileName, + String fileType, long fileSize) { + if (fileSize > maxFileSize) { + throw new FileUploadException( + String.format("File size %d exceeds maximum allowed size %d", + fileSize, maxFileSize)); + } + + int totalChunks = (int) Math.ceil((double) fileSize / chunkSize); + + FileUploadSession session = FileUploadSession.builder() + .userId(userId) + .fileName(fileName) + .fileType(fileType) + .fileSize(fileSize) + .totalChunks(totalChunks) + .chunksUploaded(0) + .status(UploadStatus.INITIATED) + .build(); + + return sessionRepository.save(session); + } + + @Override + @Transactional + public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, + MultipartFile chunkFile) { + + log.info("Uploading chunk {} for session {}, chunk size: {} bytes", + chunkNumber, uploadId, chunkFile.getSize()); + + FileUploadSession session = sessionRepository.findById(uploadId) + .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + + if (session.getChunkPaths().containsKey(chunkNumber)) { + log.info("Chunk {} already uploaded for session {}", chunkNumber, uploadId); + return UploadProgressResponse.fromSession(session); + } + + if (chunkFile.getSize() > chunkSize) { + throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize); + } + + try { + log.debug("Chunk {} actual size: {} bytes", chunkNumber, chunkFile.getSize()); + + String chunkPath = saveChunkToDisk(session, chunkNumber, chunkFile); + + session.getChunkPaths().put(chunkNumber, chunkPath); + session.setChunksUploaded(session.getChunksUploaded() + 1); + + log.info("Chunk {} uploaded successfully. Total uploaded: {}/{}", + chunkNumber, session.getChunksUploaded(), session.getTotalChunks()); + + if (session.getChunksUploaded().equals(session.getTotalChunks())) { + session.setStatus(UploadStatus.UPLOADING); + sessionRepository.save(session); + + log.info("All chunks uploaded for session {}. Starting assembly...", uploadId); + + assembleFileAsync(session); + } else { + sessionRepository.save(session); + } + + return UploadProgressResponse.fromSession(session); + + } catch (IOException e) { + log.error("Failed to save chunk {}", chunkNumber, e); + session.setStatus(UploadStatus.FAILED); + sessionRepository.save(session); + throw new RuntimeException("Failed to save chunk: " + e.getMessage(), e); + } + } + + private String saveChunkToDisk(FileUploadSession session, + Integer chunkNumber, + MultipartFile chunkFile) throws IOException { + + Long userId = session.getUserId(); + String uploadId = session.getUploadId(); + + Path chunkDir = Paths.get(basePath, "temp", + "user", String.valueOf(userId), uploadId); + Files.createDirectories(chunkDir); + + String chunkFileName = String.format("chunk_%03d.tmp", chunkNumber); + Path chunkPath = chunkDir.resolve(chunkFileName); + + try (InputStream inputStream = chunkFile.getInputStream(); + OutputStream outputStream = Files.newOutputStream(chunkPath, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + + byte[] buffer = new byte[8192]; + int bytesRead; + long totalBytes = 0; + + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + totalBytes += bytesRead; + } + + log.debug("Chunk {} saved to {}: {} bytes written", + chunkNumber, chunkPath, totalBytes); + + } catch (Exception e) { + log.error("Error saving chunk {} to {}", chunkNumber, chunkPath, e); + throw e; + } + + long savedSize = Files.size(chunkPath); + log.debug("Chunk {} saved size: {} bytes", chunkNumber, savedSize); + + return chunkPath.toString(); + } + + @Async("fileUploadTaskExecutor") + @Transactional + public void assembleFileAsync(FileUploadSession session) { + try { + assembleFile(session); + log.info("File assembly completed for session {}", session.getUploadId()); + + } catch (Exception e) { + log.error("Failed to assemble file for session {}", + session.getUploadId(), e); + + session.setStatus(UploadStatus.FAILED); + sessionRepository.save(session); + } + } + + private void assembleFile(FileUploadSession session) throws IOException { + Long userId = session.getUserId(); + String uploadId = session.getUploadId(); + + log.info("Assembling file for session {}: {} ({} chunks)", + uploadId, session.getFileName(), session.getTotalChunks()); + + Path finalDir = createUserDirectory(userId, session.getFileType()); + + String safeFileName = generateSafeFileName(session.getFileName()); + Path finalFilePath = finalDir.resolve(safeFileName); + + log.debug("Final file path: {}", finalFilePath); + + try (OutputStream outputStream = new BufferedOutputStream( + Files.newOutputStream(finalFilePath, StandardOpenOption.CREATE))) { + + Path chunkDir = getChunkDirectory(userId, uploadId); + long totalAssembled = 0; + + for (int i = 0; i < session.getTotalChunks(); i++) { + Path chunkPath = chunkDir.resolve(String.format("chunk_%03d.tmp", i)); + + if (!Files.exists(chunkPath)) { + throw new IOException("Missing chunk: " + chunkPath); + } + + byte[] chunkData = Files.readAllBytes(chunkPath); + outputStream.write(chunkData); + totalAssembled += chunkData.length; + + log.debug("Added chunk {}: {} bytes (total: {})", + i, chunkData.length, totalAssembled); + } + + log.info("File assembled: {} bytes total", totalAssembled); + } + + try { + validateFileIntegrity(finalFilePath, session.getFileSize()); + } catch (IOException e) { + log.warn("File size mismatch (temporarily ignored): {}", e.getMessage()); + } + + session.setFilePath(finalFilePath.toString()); + session.setChecksum(calculateChecksum(finalFilePath)); + session.setStatus(UploadStatus.COMPLETED); + session.setCompletedAt(LocalDateTime.now()); + sessionRepository.save(session); + + log.info("File assembled successfully: {} -> {}", + session.getFileName(), finalFilePath); + + try { + cleanupChunks(getChunkDirectory(userId, uploadId)); + } catch (IOException e) { + log.warn("Failed to cleanup chunks: {}", e.getMessage()); + } + } + + private Path createUserDirectory(Long userId, String fileType) throws IOException { + Path userDir = Paths.get(basePath, "user", String.valueOf(userId), extractCategory(fileType)); + + Files.createDirectories(userDir); + + return userDir; + } + + private String extractCategory(String fileType) { + if (fileType.startsWith("image/")) { + return "images"; + } else if (fileType.startsWith("video/")) { + return "videos"; + } else if (fileType.startsWith("audio/")) { + return "audio"; + } else if (fileType.contains("pdf")) { + return "documents"; + } else if (fileType.contains("text")) { + return "text"; + } else { + return "other"; + } + } + + private String generateSafeFileName(String originalName) { + String safeName = originalName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); + + String timestamp = String.valueOf(System.currentTimeMillis()); + int dotIndex = safeName.lastIndexOf('.'); + + if (dotIndex > 0) { + String name = safeName.substring(0, dotIndex); + String extension = safeName.substring(dotIndex); + return name + "_" + timestamp + extension; + } else { + return safeName + "_" + timestamp; + } + } + + private void validateFileIntegrity(Path filePath, long expectedSize) throws IOException { + long actualSize = Files.size(filePath); + log.info("File integrity check: expected={}, actual={}", expectedSize, actualSize); + + if (actualSize != expectedSize) { + throw new IOException( + String.format("File size mismatch: expected %d, got %d", + expectedSize, actualSize)); + } + } + + private String calculateChecksum(Path filePath) throws IOException { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[8192]; + + try (InputStream inputStream = Files.newInputStream(filePath)) { + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + md.update(buffer, 0, bytesRead); + } + } + + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + + return sb.toString(); + + } catch (NoSuchAlgorithmException e) { + log.warn("MD5 algorithm not available, skipping checksum", e); + return "N/A"; + } + } + + private void cleanupChunks(Path chunkDir) throws IOException { + if (Files.exists(chunkDir)) { + try (Stream walk = Files.walk(chunkDir)) { + walk.sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + log.info("Cleaned up chunk directory: {}", chunkDir); + } + } + } + + private Path getChunkDirectory(Long userId, String uploadId) { + return Paths.get(basePath, "temp", "user", String.valueOf(userId), uploadId); + } + + @Override + public UploadProgressResponse getUploadProgress(String uploadId) { + FileUploadSession session = sessionRepository.findById(uploadId) + .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + + return UploadProgressResponse.fromSession(session); + } + + @Override + public void cleanupExpiredSessions() { + log.info("Cleanup not implemented yet"); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e1e88fd..4406117 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -9,10 +9,34 @@ spring: hibernate: ddl-auto: update show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + format_sql: true + + servlet: + multipart: + max-file-size: 10MB + max-request-size: 100MB + enabled: true + resolve-lazily: false + +file: + storage: + base-path: ${FILE_STORAGE_PATH:./uploads} + chunk-size: 5242880 # 5MB + temp-ttl-hours: 24 + max-file-size: 10737418240 # 10GB server: port: ${SERVER_PORT:8080} logging: level: - root: INFO \ No newline at end of file + com.example.fileupload: DEBUG + org.springframework.web: DEBUG + org.hibernate.SQL: DEBUG + file: + name: logs/application.log + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" \ No newline at end of file From 997daae6398af616379c4dd50a235b688e4e9347 Mon Sep 17 00:00:00 2001 From: vladp Date: Tue, 16 Dec 2025 03:44:59 +0700 Subject: [PATCH 02/13] save chunks --- docker-compose.yaml | 6 +- .../controller/file/FileUploadController.java | 194 ++++-- .../no_copy/dto/file/ChunkStatusResponse.java | 23 + .../entity/file/FileUploadSession.java | 22 + .../FileUploadSessionRepository.java | 21 + .../service/file/FileUploadService.java | 8 +- .../service/file/FileUploadServiceImpl.java | 635 +++++++++++++----- src/main/resources/application.yaml | 11 +- 8 files changed, 703 insertions(+), 217 deletions(-) create mode 100644 src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java diff --git a/docker-compose.yaml b/docker-compose.yaml index 8817acc..c9e2616 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -22,6 +22,9 @@ services: build: . container_name: app-backend environment: + FILE_STORAGE_PATH: /data/uploads + MAX_FILE_SIZE: 1073741824 + FILE_CHUNK_SIZE: 5242880 POSTGRES_DB: no_copy_ POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -38,7 +41,7 @@ services: - backend - api volumes: - - ./uploads:/data/uploads + - uploads_volume:/data/uploads grafana: image: grafana/grafana:10.3.1 @@ -143,6 +146,7 @@ volumes: loki_chunks: loki_index: loki_rules: + uploads_volume: networks: app-network: diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java b/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java index 47b7f47..a36728b 100644 --- a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java +++ b/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java @@ -3,23 +3,25 @@ package ru.soune.no_copy.controller.file; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.ChunkUploadRequest; import ru.soune.no_copy.dto.file.FileApiResponse; import ru.soune.no_copy.dto.file.InitUploadRequest; import ru.soune.no_copy.dto.file.UploadProgressResponse; import ru.soune.no_copy.entity.AuthToken; import ru.soune.no_copy.entity.file.FileType; import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.no_copy.entity.file.UploadStatus; +import ru.soune.no_copy.exception.FileUploadException; import ru.soune.no_copy.exception.NotFoundAuthToken; +import ru.soune.no_copy.exception.UploadSessionNotFoundException; import ru.soune.no_copy.repository.AuthTokenRepository; +import ru.soune.no_copy.repository.FileUploadSessionRepository; import ru.soune.no_copy.service.file.FileUploadService; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; @Slf4j @RestController @@ -31,6 +33,8 @@ public class FileUploadController { private final AuthTokenRepository authTokenRepository; + private final FileUploadSessionRepository fileUploadSessionRepository; + //TODO ADD TEMPLATE JSON, RESPONSE OK,ADD MESSAGE CODE @PostMapping("/init") public ResponseEntity> initUpload(@Valid @RequestBody InitUploadRequest request, @@ -61,17 +65,26 @@ public class FileUploadController { @PostMapping("/chunk") public ResponseEntity> uploadChunk( - @Valid @RequestBody ChunkUploadRequest chunkUploadRequest, - @RequestParam("file") MultipartFile chunk, @RequestHeader("Authorization") String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); + @RequestParam("uploadId") String uploadId, + @RequestParam("chunkNumber") Integer chunkNumber, + @RequestParam("chunk") MultipartFile chunk) { - Optional tokenOptional = authTokenRepository.findByToken(token); + log.info("Uploading chunk {} for session {}, file size: {} bytes", + chunkNumber, uploadId, chunk.getSize()); - tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); + if (chunk.isEmpty()) { + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Chunk file is empty")); + } + + if (uploadId == null || uploadId.isBlank()) { + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Upload ID is required")); + } try { UploadProgressResponse progress = fileUploadService.uploadChunk( - chunkUploadRequest.getUploadId(), chunkUploadRequest.getChunkNumber(), chunk); + uploadId, chunkNumber, chunk); return ResponseEntity.ok(FileApiResponse.success(progress)); @@ -82,39 +95,130 @@ public class FileUploadController { } } -// @GetMapping("/progress/{uploadId}") -// public ResponseEntity> getProgress( -// @PathVariable String uploadId, -// @RequestHeader(value = "X-User-Id", required = false, defaultValue = "1") Long userId) { -// -// try { -// UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); -// return ResponseEntity.ok(FileApiResponse.success(progress)); -// -// } catch (Exception e) { -// log.error("Error getting progress", e); -// return ResponseEntity.badRequest() -// .body(FileApiResponse.error(e.getMessage())); -// } -// } -// -// @PostMapping("/complete/{uploadId}") -// public ResponseEntity> completeUpload( -// @PathVariable String uploadId, -// @RequestHeader(value = "X-User-Id", required = false, defaultValue = "1") Long userId) { -// -// log.info("Completing upload for session {}", uploadId); -// -// try { -// UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); -// -// return ResponseEntity.ok(FileApiResponse.success( -// "File assembly in progress", progress)); -// -// } catch (Exception e) { -// log.error("Error completing upload", e); -// return ResponseEntity.badRequest() -// .body(FileApiResponse.error(e.getMessage())); -// } -// } + @GetMapping("/progress/{uploadId}") + public ResponseEntity> getProgress( + @PathVariable String uploadId) { + + try { + UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); + return ResponseEntity.ok(FileApiResponse.success(progress)); + + } catch (UploadSessionNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(FileApiResponse.error("Upload session not found")); + } catch (Exception e) { + log.error("Error getting progress", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to get progress")); + } + } + + @PostMapping("/{uploadId}/complete") + public ResponseEntity> completeUpload( + @PathVariable String uploadId) { + + log.info("Manual completion requested for session: {}", uploadId); + + try { + UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); + + if (progress.getStatus() == UploadStatus.COMPLETED) { + return ResponseEntity.ok(FileApiResponse.success( + "Upload already completed", progress)); + } + + if (progress.getUploadedChunks() < progress.getTotalChunks()) { + return ResponseEntity.badRequest() + .body(FileApiResponse.error( + String.format("Not all chunks uploaded: %d/%d", + progress.getUploadedChunks(), progress.getTotalChunks()))); + } + + return ResponseEntity.ok(FileApiResponse.success( + "File assembly in progress", progress)); + + } catch (UploadSessionNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(FileApiResponse.error("Upload session not found")); + } catch (Exception e) { + log.error("Error completing upload", e); + return ResponseEntity.internalServerError() + .body(FileApiResponse.error("Failed to complete upload")); + } + } + + @PostMapping("/{uploadId}/cancel") + public ResponseEntity> cancelUpload( + @PathVariable String uploadId) { + + log.info("Cancelling upload session: {}", uploadId); + + try { + fileUploadService.cancelUpload(uploadId); + return ResponseEntity.ok(FileApiResponse.success( + "Upload cancelled successfully", null)); + + } catch (UploadSessionNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(FileApiResponse.error("Upload session not found")); + } catch (FileUploadException e) { + return ResponseEntity.badRequest() + .body(FileApiResponse.error(e.getMessage())); + } catch (Exception e) { + log.error("Error cancelling upload", e); + return ResponseEntity.internalServerError() + .body(FileApiResponse.error("Failed to cancel upload")); + } + } + + @PostMapping("/{uploadId}/retry") + public ResponseEntity> retryUpload( + @PathVariable String uploadId) { + + log.info("Retry requested for failed upload: {}", uploadId); + + try { + fileUploadService.retryFailedUpload(uploadId); + UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); + + return ResponseEntity.ok(FileApiResponse.success( + "Upload retry initiated", progress)); + + } catch (UploadSessionNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(FileApiResponse.error("Upload session not found")); + } catch (FileUploadException e) { + return ResponseEntity.badRequest() + .body(FileApiResponse.error(e.getMessage())); + } catch (Exception e) { + log.error("Error retrying upload", e); + return ResponseEntity.internalServerError() + .body(FileApiResponse.error("Failed to retry upload")); + } + } + + @GetMapping("/{uploadId}/chunks") + public ResponseEntity>> getChunkStatus( + @PathVariable String uploadId) { + + try { + FileUploadSession session = fileUploadSessionRepository.findById(uploadId) + .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + + Map chunkStatus = new HashMap<>(); + for (int i = 0; i < session.getTotalChunks(); i++) { + chunkStatus.put(i, session.getChunkPaths().containsKey(i)); + } + + return ResponseEntity.ok(FileApiResponse.success(chunkStatus)); + + } catch (UploadSessionNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(FileApiResponse.error("Upload session not found")); + } catch (Exception e) { + log.error("Error getting chunk status", e); + return ResponseEntity.internalServerError() + .body(FileApiResponse.error("Failed to get chunk status")); + } + } } diff --git a/src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java b/src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java new file mode 100644 index 0000000..0a5f269 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java @@ -0,0 +1,23 @@ +package ru.soune.no_copy.dto.file; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.soune.no_copy.entity.file.UploadStatus; + +import java.util.List; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChunkStatusResponse { + private String uploadId; + private Integer totalChunks; + private Integer uploadedChunks; + private List missingChunks; + private Map chunkPaths; + private UploadStatus status; +} diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java b/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java index 08f54f3..0eed4a0 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java +++ b/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java @@ -53,6 +53,18 @@ public class FileUploadSession { @Column(name = "created_at", updatable = false, nullable = false) private LocalDateTime createdAt; + @Column(name = "expires_at") + private LocalDateTime expiresAt; + + @Version + private Long version; + + @Column(name = "last_error") + private String lastError; + + @Column(name = "retry_count") + private Integer retryCount = 0; + @Column(name = "completed_at") private LocalDateTime completedAt; @@ -64,4 +76,14 @@ public class FileUploadSession { @MapKeyColumn(name = "chunk_number") @Column(name = "chunk_path") private Map chunkPaths = new HashMap<>(); + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.expiresAt = LocalDateTime.now().plusHours(24); + this.chunksUploaded = 0; + if (this.status == null) { + this.status = UploadStatus.INITIATED; + } + } } diff --git a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java b/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java index de7b788..0a9175f 100644 --- a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java +++ b/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java @@ -1,9 +1,30 @@ package ru.soune.no_copy.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.no_copy.entity.file.UploadStatus; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; @Repository public interface FileUploadSessionRepository extends JpaRepository { + + List findByUserId(Long userId); + + List findByStatus(UploadStatus status); + + @Query("SELECT s FROM FileUploadSession s WHERE s.status IN :statuses AND s.createdAt < :threshold") + List findByStatusInAndCreatedAtBefore( + @Param("statuses") Set statuses, + @Param("threshold") LocalDateTime threshold); + + @Query("SELECT s FROM FileUploadSession s WHERE s.expiresAt < :now AND s.status IN :statuses") + List findExpiredSessions( + @Param("now") LocalDateTime now, + @Param("statuses") Set statuses); } diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java index dbf4776..e401240 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java @@ -4,14 +4,20 @@ import org.springframework.web.multipart.MultipartFile; import ru.soune.no_copy.dto.file.UploadProgressResponse; import ru.soune.no_copy.entity.file.FileUploadSession; +import java.io.IOException; + public interface FileUploadService { FileUploadSession initUpload(Long userId, String fileName, String fileType, long fileSize); UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, - MultipartFile chunkFile); + MultipartFile chunkFile) throws IOException; UploadProgressResponse getUploadProgress(String uploadId); void cleanupExpiredSessions(); + + public void retryFailedUpload(String uploadId); + + public void cancelUpload(String uploadId); } diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java index 5030223..f70ffd2 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java @@ -17,14 +17,16 @@ import ru.soune.no_copy.exception.UploadSessionNotFoundException; import ru.soune.no_copy.repository.FileUploadSessionRepository; import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; +import java.nio.file.*; +import java.nio.file.attribute.PosixFilePermission; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; @Slf4j @@ -43,24 +45,36 @@ public class FileUploadServiceImpl implements FileUploadService { @Value("${file.storage.max-file-size}") private long maxFileSize; + @Value("${file.storage.max-retry-attempts:3}") + private int maxRetryAttempts; + + @Value("${file.storage.chunk-timeout-ms:300000}") // 5 минут + private long chunkTimeoutMs; + + @Value("${file.storage.session-expiry-hours:24}") + private int sessionExpiryHours; + + private Path storageRoot; + @PostConstruct public void init() { try { - Path storageRoot = Paths.get(basePath).toAbsolutePath().normalize(); + storageRoot = Paths.get(basePath).toAbsolutePath().normalize(); + log.info("Initializing file storage at: {}", storageRoot); - log.info("Storage will be at: {}", storageRoot); + createDirectoryIfNotExists(storageRoot); + createDirectoryIfNotExists(storageRoot.resolve("temp")); + createDirectoryIfNotExists(storageRoot.resolve("uploads")); - if (!Files.exists(storageRoot)) { - Files.createDirectories(storageRoot); - log.info("Created: {}", storageRoot); + if (!System.getProperty("os.name").toLowerCase().contains("win")) { + setDirectoryPermissions(storageRoot); } - Files.createDirectories(storageRoot.resolve("temp")); - Files.createDirectories(storageRoot.resolve("user")); + log.info("File storage initialized successfully"); } catch (IOException e) { - log.error("CANNOT CREATE STORAGE!", e); - throw new RuntimeException("Storage failed", e); + log.error("Failed to initialize file storage at: {}", basePath, e); + throw new RuntimeException("Storage initialization failed", e); } } @@ -68,13 +82,22 @@ public class FileUploadServiceImpl implements FileUploadService { @Transactional public FileUploadSession initUpload(Long userId, String fileName, String fileType, long fileSize) { + log.info("Initializing upload for user {}: {} ({} bytes, type: {})", + userId, fileName, fileSize, fileType); + if (fileSize > maxFileSize) { throw new FileUploadException( String.format("File size %d exceeds maximum allowed size %d", fileSize, maxFileSize)); } + if (fileSize <= 0) { + throw new FileUploadException("File size must be positive"); + } + int totalChunks = (int) Math.ceil((double) fileSize / chunkSize); + log.debug("File will be split into {} chunks (chunk size: {} bytes)", + totalChunks, chunkSize); FileUploadSession session = FileUploadSession.builder() .userId(userId) @@ -84,9 +107,15 @@ public class FileUploadServiceImpl implements FileUploadService { .totalChunks(totalChunks) .chunksUploaded(0) .status(UploadStatus.INITIATED) + .expiresAt(LocalDateTime.now().plusHours(sessionExpiryHours)) + .retryCount(0) .build(); - return sessionRepository.save(session); + FileUploadSession savedSession = sessionRepository.save(session); + log.info("Upload session created: {} for file: {}", + savedSession.getUploadId(), fileName); + + return savedSession; } @Override @@ -94,38 +123,92 @@ public class FileUploadServiceImpl implements FileUploadService { public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile) { - log.info("Uploading chunk {} for session {}, chunk size: {} bytes", + log.info("Processing chunk {} for session {}, size: {} bytes", chunkNumber, uploadId, chunkFile.getSize()); FileUploadSession session = sessionRepository.findById(uploadId) .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); - if (session.getChunkPaths().containsKey(chunkNumber)) { - log.info("Chunk {} already uploaded for session {}", chunkNumber, uploadId); - return UploadProgressResponse.fromSession(session); + validateSession(session); + + if (session.getExpiresAt().isBefore(LocalDateTime.now())) { + handleExpiredSession(session); + throw new FileUploadException("Upload session expired"); + } + + if (chunkNumber < 0 || chunkNumber >= session.getTotalChunks()) { + throw new FileUploadException( + String.format("Invalid chunk number %d. Expected 0-%d", + chunkNumber, session.getTotalChunks() - 1)); } if (chunkFile.getSize() > chunkSize) { throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize); } - try { - log.debug("Chunk {} actual size: {} bytes", chunkNumber, chunkFile.getSize()); + return processChunk(session, chunkNumber, chunkFile); + } - String chunkPath = saveChunkToDisk(session, chunkNumber, chunkFile); + private void validateSession(FileUploadSession session) { + UploadStatus status = session.getStatus(); + + if (status == UploadStatus.FAILED) { + if (session.getRetryCount() >= maxRetryAttempts) { + throw new FileUploadException( + "Upload failed after maximum retry attempts"); + } + session.setStatus(UploadStatus.UPLOADING); + session.setRetryCount(session.getRetryCount() + 1); + sessionRepository.save(session); + log.info("Retrying failed upload session: {}, attempt: {}", + session.getUploadId(), session.getRetryCount()); + } + + if (status == UploadStatus.COMPLETED) { + throw new FileUploadException("Upload already completed"); + } + + if (status == UploadStatus.CANCELLED) { + throw new FileUploadException("Upload was cancelled"); + } + + if (status == UploadStatus.INITIATED) { + session.setStatus(UploadStatus.UPLOADING); + sessionRepository.save(session); + } + } + + private void handleExpiredSession(FileUploadSession session) { + session.setStatus(UploadStatus.FAILED); + session.setLastError("Upload session expired"); + sessionRepository.save(session); + + CompletableFuture.runAsync(() -> cleanupSessionFiles(session)); + } + + private UploadProgressResponse processChunk(FileUploadSession session, + Integer chunkNumber, + MultipartFile chunkFile) { + String chunkPath = null; + + try { + if (session.getChunkPaths().containsKey(chunkNumber)) { + return handleExistingChunk(session, chunkNumber, chunkFile); + } + + chunkPath = saveChunkWithIntegrityCheck(session, chunkNumber, chunkFile); session.getChunkPaths().put(chunkNumber, chunkPath); session.setChunksUploaded(session.getChunksUploaded() + 1); + session.setStatus(UploadStatus.UPLOADING); - log.info("Chunk {} uploaded successfully. Total uploaded: {}/{}", + log.debug("Chunk {} saved successfully. Uploaded: {}/{}", chunkNumber, session.getChunksUploaded(), session.getTotalChunks()); if (session.getChunksUploaded().equals(session.getTotalChunks())) { - session.setStatus(UploadStatus.UPLOADING); + log.info("All chunks uploaded for session {}. Starting assembly...", + session.getUploadId()); sessionRepository.save(session); - - log.info("All chunks uploaded for session {}. Starting assembly...", uploadId); - assembleFileAsync(session); } else { sessionRepository.save(session); @@ -133,53 +216,99 @@ public class FileUploadServiceImpl implements FileUploadService { return UploadProgressResponse.fromSession(session); - } catch (IOException e) { - log.error("Failed to save chunk {}", chunkNumber, e); + } catch (Exception e) { + if (chunkPath != null) { + cleanupFailedChunk(chunkPath); + } + + log.error("Failed to process chunk {} for session {}: {}", + chunkNumber, session.getUploadId(), e.getMessage(), e); + session.setStatus(UploadStatus.FAILED); + session.setLastError(e.getMessage()); sessionRepository.save(session); - throw new RuntimeException("Failed to save chunk: " + e.getMessage(), e); + + throw new FileUploadException("Failed to upload chunk: " + e.getMessage(), e); } } - private String saveChunkToDisk(FileUploadSession session, - Integer chunkNumber, - MultipartFile chunkFile) throws IOException { + private UploadProgressResponse handleExistingChunk(FileUploadSession session, + Integer chunkNumber, + MultipartFile chunkFile) throws IOException { + String existingPath = session.getChunkPaths().get(chunkNumber); + Path chunkPath = Paths.get(existingPath); + + if (!Files.exists(chunkPath)) { + log.warn("Chunk file missing, re-uploading: {}", chunkPath); + session.getChunkPaths().remove(chunkNumber); + session.setChunksUploaded(session.getChunksUploaded() - 1); + sessionRepository.save(session); + return processChunk(session, chunkNumber, chunkFile); + } + + long existingSize = Files.size(chunkPath); + if (existingSize != chunkFile.getSize()) { + log.warn("Chunk size mismatch, re-uploading: {} != {}", + existingSize, chunkFile.getSize()); + Files.deleteIfExists(chunkPath); + session.getChunkPaths().remove(chunkNumber); + session.setChunksUploaded(session.getChunksUploaded() - 1); + sessionRepository.save(session); + return processChunk(session, chunkNumber, chunkFile); + } + + log.debug("Chunk {} already uploaded and valid", chunkNumber); + return UploadProgressResponse.fromSession(session); + } + + private String saveChunkWithIntegrityCheck(FileUploadSession session, + Integer chunkNumber, + MultipartFile chunkFile) throws IOException { Long userId = session.getUserId(); String uploadId = session.getUploadId(); - Path chunkDir = Paths.get(basePath, "temp", - "user", String.valueOf(userId), uploadId); + Path chunkDir = getChunkDirectory(userId, uploadId); Files.createDirectories(chunkDir); - String chunkFileName = String.format("chunk_%03d.tmp", chunkNumber); - Path chunkPath = chunkDir.resolve(chunkFileName); + String tempFileName = String.format("chunk_%04d.%s.tmp", + chunkNumber, UUID.randomUUID()); + String finalFileName = String.format("chunk_%04d.tmp", chunkNumber); - try (InputStream inputStream = chunkFile.getInputStream(); - OutputStream outputStream = Files.newOutputStream(chunkPath, - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + Path tempPath = chunkDir.resolve(tempFileName); + Path finalPath = chunkDir.resolve(finalFileName); - byte[] buffer = new byte[8192]; - int bytesRead; - long totalBytes = 0; + try { + chunkFile.transferTo(tempPath.toFile()); - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - totalBytes += bytesRead; + long savedSize = Files.size(tempPath); + long uploadedSize = chunkFile.getSize(); + + if (uploadedSize > 0 && savedSize != uploadedSize) { + throw new IOException( + String.format("Size mismatch: saved %d, uploaded %d", + savedSize, uploadedSize)); } - log.debug("Chunk {} saved to {}: {} bytes written", - chunkNumber, chunkPath, totalBytes); + Files.move(tempPath, finalPath, StandardCopyOption.ATOMIC_MOVE); - } catch (Exception e) { - log.error("Error saving chunk {} to {}", chunkNumber, chunkPath, e); - throw e; + log.debug("Chunk {} saved successfully: {} bytes", + chunkNumber, savedSize); + + return finalPath.toString(); + + } finally { + Files.deleteIfExists(tempPath); } + } - long savedSize = Files.size(chunkPath); - log.debug("Chunk {} saved size: {} bytes", chunkNumber, savedSize); - - return chunkPath.toString(); + private void cleanupFailedChunk(String chunkPath) { + try { + Files.deleteIfExists(Paths.get(chunkPath)); + log.debug("Cleaned up failed chunk: {}", chunkPath); + } catch (IOException e) { + log.warn("Failed to cleanup chunk: {}", chunkPath, e); + } } @Async("fileUploadTaskExecutor") @@ -187,130 +316,307 @@ public class FileUploadServiceImpl implements FileUploadService { public void assembleFileAsync(FileUploadSession session) { try { assembleFile(session); - log.info("File assembly completed for session {}", session.getUploadId()); + log.info("File assembly completed successfully for session: {}", + session.getUploadId()); } catch (Exception e) { - log.error("Failed to assemble file for session {}", - session.getUploadId(), e); + log.error("Failed to assemble file for session {}: {}", + session.getUploadId(), e.getMessage(), e); - session.setStatus(UploadStatus.FAILED); - sessionRepository.save(session); + handleAssemblyFailure(session, e); } } private void assembleFile(FileUploadSession session) throws IOException { - Long userId = session.getUserId(); - String uploadId = session.getUploadId(); + log.info("Starting file assembly for session: {} ({})", + session.getUploadId(), session.getFileName()); - log.info("Assembling file for session {}: {} ({} chunks)", - uploadId, session.getFileName(), session.getTotalChunks()); - - Path finalDir = createUserDirectory(userId, session.getFileType()); - - String safeFileName = generateSafeFileName(session.getFileName()); - Path finalFilePath = finalDir.resolve(safeFileName); - - log.debug("Final file path: {}", finalFilePath); - - try (OutputStream outputStream = new BufferedOutputStream( - Files.newOutputStream(finalFilePath, StandardOpenOption.CREATE))) { - - Path chunkDir = getChunkDirectory(userId, uploadId); - long totalAssembled = 0; - - for (int i = 0; i < session.getTotalChunks(); i++) { - Path chunkPath = chunkDir.resolve(String.format("chunk_%03d.tmp", i)); - - if (!Files.exists(chunkPath)) { - throw new IOException("Missing chunk: " + chunkPath); - } - - byte[] chunkData = Files.readAllBytes(chunkPath); - outputStream.write(chunkData); - totalAssembled += chunkData.length; - - log.debug("Added chunk {}: {} bytes (total: {})", - i, chunkData.length, totalAssembled); - } - - log.info("File assembled: {} bytes total", totalAssembled); - } + Path finalFilePath = null; try { - validateFileIntegrity(finalFilePath, session.getFileSize()); - } catch (IOException e) { - log.warn("File size mismatch (temporarily ignored): {}", e.getMessage()); + finalFilePath = prepareFinalFile(session); + + validateAllChunksExist(session); + + mergeChunksToFile(session, finalFilePath); + + validateFinalFile(session, finalFilePath); + + updateSessionOnSuccess(session, finalFilePath); + + cleanupSessionFiles(session); + + log.info("File assembly completed: {} -> {} ({} bytes)", + session.getFileName(), finalFilePath, session.getFileSize()); + + } catch (Exception e) { + if (finalFilePath != null) { + Files.deleteIfExists(finalFilePath); + } + throw e; + } + } + + private Path prepareFinalFile(FileUploadSession session) throws IOException { + Path userUploadsDir = storageRoot.resolve("uploads") + .resolve(String.valueOf(session.getUserId())) + .resolve(extractCategory(session.getFileType())); + + Files.createDirectories(userUploadsDir); + + String safeFileName = generateUniqueFileName(session.getFileName()); + Path finalPath = userUploadsDir.resolve(safeFileName); + + if (Files.exists(finalPath)) { + throw new IOException("File already exists: " + finalPath); } - session.setFilePath(finalFilePath.toString()); - session.setChecksum(calculateChecksum(finalFilePath)); + return finalPath; + } + + private String generateUniqueFileName(String originalName) { + String safeName = originalName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); + + String timestamp = String.valueOf(System.currentTimeMillis()); + String uuid = UUID.randomUUID().toString().substring(0, 8); + + int dotIndex = safeName.lastIndexOf('.'); + if (dotIndex > 0) { + String name = safeName.substring(0, dotIndex); + String extension = safeName.substring(dotIndex); + return String.format("%s_%s_%s%s", name, timestamp, uuid, extension); + } else { + return String.format("%s_%s_%s", safeName, timestamp, uuid); + } + } + + private void validateAllChunksExist(FileUploadSession session) throws IOException { + Path chunkDir = getChunkDirectory(session.getUserId(), session.getUploadId()); + + for (int i = 0; i < session.getTotalChunks(); i++) { + Path chunkPath = chunkDir.resolve(String.format("chunk_%04d.tmp", i)); + if (!Files.exists(chunkPath)) { + throw new IOException("Missing chunk: " + chunkPath.getFileName()); + } + + if (Files.size(chunkPath) == 0) { + throw new IOException("Empty chunk: " + chunkPath.getFileName()); + } + } + } + + private void mergeChunksToFile(FileUploadSession session, Path finalPath) + throws IOException { + + Path chunkDir = getChunkDirectory(session.getUserId(), session.getUploadId()); + byte[] buffer = new byte[8192]; // 8KB буфер + + try (BufferedOutputStream outputStream = new BufferedOutputStream( + Files.newOutputStream(finalPath, StandardOpenOption.CREATE_NEW))) { + + long totalWritten = 0; + + for (int i = 0; i < session.getTotalChunks(); i++) { + Path chunkPath = chunkDir.resolve(String.format("chunk_%04d.tmp", i)); + + try (BufferedInputStream inputStream = new BufferedInputStream( + Files.newInputStream(chunkPath))) { + + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + totalWritten += bytesRead; + } + } + + if (log.isDebugEnabled()) { + log.debug("Processed chunk {}: {} bytes (total: {})", + i, Files.size(chunkPath), totalWritten); + } + } + + outputStream.flush(); + log.info("Total bytes written to final file: {}", totalWritten); + } + } + + private void validateFinalFile(FileUploadSession session, Path finalPath) + throws IOException { + + long actualSize = Files.size(finalPath); + + if (actualSize != session.getFileSize()) { + throw new IOException( + String.format("File size mismatch: expected %d, got %d", + session.getFileSize(), actualSize)); + } + + String checksum = calculateChecksum(finalPath); + log.debug("Final file checksum: {}", checksum); + } + + private void updateSessionOnSuccess(FileUploadSession session, Path finalPath) { + session.setFilePath(finalPath.toString()); + session.setChecksum(calculateChecksum(finalPath)); session.setStatus(UploadStatus.COMPLETED); session.setCompletedAt(LocalDateTime.now()); sessionRepository.save(session); + } - log.info("File assembled successfully: {} -> {}", - session.getFileName(), finalFilePath); - + private void cleanupSessionFiles(FileUploadSession session) { try { - cleanupChunks(getChunkDirectory(userId, uploadId)); + Path chunkDir = getChunkDirectory(session.getUserId(), session.getUploadId()); + if (Files.exists(chunkDir)) { + deleteDirectoryRecursively(chunkDir); + log.info("Cleaned up temp directory for session: {}", + session.getUploadId()); + } } catch (IOException e) { - log.warn("Failed to cleanup chunks: {}", e.getMessage()); + log.warn("Failed to cleanup temp files for session {}: {}", + session.getUploadId(), e.getMessage()); } } - private Path createUserDirectory(Long userId, String fileType) throws IOException { - Path userDir = Paths.get(basePath, "user", String.valueOf(userId), extractCategory(fileType)); + private void deleteDirectoryRecursively(Path directory) throws IOException { + try (Stream walk = Files.walk(directory)) { + walk.sorted(Comparator.reverseOrder()) + .forEach(path -> { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + log.warn("Failed to delete: {}", path, e); + } + }); + } + } - Files.createDirectories(userDir); + private void handleAssemblyFailure(FileUploadSession session, Exception e) { + session.setStatus(UploadStatus.FAILED); + session.setLastError(e.getMessage()); + sessionRepository.save(session); - return userDir; + log.error("File assembly failed for session {}: {}", + session.getUploadId(), e.getMessage()); + } + + @Override + @Transactional + public void cancelUpload(String uploadId) { + FileUploadSession session = sessionRepository.findById(uploadId) + .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + + if (session.getStatus() == UploadStatus.COMPLETED) { + throw new FileUploadException("Cannot cancel completed upload"); + } + + session.setStatus(UploadStatus.CANCELLED); + sessionRepository.save(session); + + CompletableFuture.runAsync(() -> cleanupSessionFiles(session)); + + log.info("Upload cancelled: {}", uploadId); + } + + @Override + @Transactional + public void retryFailedUpload(String uploadId) { + FileUploadSession session = sessionRepository.findById(uploadId) + .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + + if (session.getStatus() != UploadStatus.FAILED) { + throw new FileUploadException("Only failed uploads can be retried"); + } + + if (session.getRetryCount() >= maxRetryAttempts) { + throw new FileUploadException("Max retry attempts exceeded"); + } + + session.setStatus(UploadStatus.UPLOADING); + session.setRetryCount(session.getRetryCount() + 1); + session.setLastError(null); + sessionRepository.save(session); + + if (session.getChunksUploaded().equals(session.getTotalChunks())) { + log.info("Retrying file assembly for session: {}", uploadId); + assembleFileAsync(session); + } + + log.info("Upload retry initiated for session: {} (attempt {})", + uploadId, session.getRetryCount()); + } + + @Override + public UploadProgressResponse getUploadProgress(String uploadId) { + FileUploadSession session = sessionRepository.findById(uploadId) + .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + + return UploadProgressResponse.fromSession(session); + } + + @Override + @Transactional + public void cleanupExpiredSessions() { + log.info("Starting cleanup of expired upload sessions"); + + LocalDateTime expiryThreshold = LocalDateTime.now().minusHours(sessionExpiryHours); + + try { + var expiredSessions = sessionRepository.findByStatusInAndCreatedAtBefore( + Set.of(UploadStatus.INITIATED, UploadStatus.UPLOADING, UploadStatus.FAILED), + expiryThreshold + ); + + int cleanedCount = 0; + for (FileUploadSession session : expiredSessions) { + session.setStatus(UploadStatus.FAILED); + session.setLastError("Session expired during cleanup"); + sessionRepository.save(session); + + CompletableFuture.runAsync(() -> cleanupSessionFiles(session)); + cleanedCount++; + } + + log.info("Cleaned up {} expired upload sessions", cleanedCount); + + } catch (Exception e) { + log.error("Error during session cleanup", e); + } + } + + private Path getChunkDirectory(Long userId, String uploadId) { + return storageRoot.resolve("temp") + .resolve("user") + .resolve(String.valueOf(userId)) + .resolve(uploadId); } private String extractCategory(String fileType) { - if (fileType.startsWith("image/")) { + if (fileType == null) { + return "other"; + } + + String lowerType = fileType.toLowerCase(); + if (lowerType.startsWith("image/")) { return "images"; - } else if (fileType.startsWith("video/")) { + } else if (lowerType.startsWith("video/")) { return "videos"; - } else if (fileType.startsWith("audio/")) { + } else if (lowerType.startsWith("audio/")) { return "audio"; - } else if (fileType.contains("pdf")) { + } else if (lowerType.contains("pdf") || + lowerType.contains("document") || + lowerType.contains("text") || + lowerType.contains("msword") || + lowerType.contains("wordprocessing") || + lowerType.contains("spreadsheet")) { return "documents"; - } else if (fileType.contains("text")) { - return "text"; } else { return "other"; } } - private String generateSafeFileName(String originalName) { - String safeName = originalName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); - - String timestamp = String.valueOf(System.currentTimeMillis()); - int dotIndex = safeName.lastIndexOf('.'); - - if (dotIndex > 0) { - String name = safeName.substring(0, dotIndex); - String extension = safeName.substring(dotIndex); - return name + "_" + timestamp + extension; - } else { - return safeName + "_" + timestamp; - } - } - - private void validateFileIntegrity(Path filePath, long expectedSize) throws IOException { - long actualSize = Files.size(filePath); - log.info("File integrity check: expected={}, actual={}", expectedSize, actualSize); - - if (actualSize != expectedSize) { - throw new IOException( - String.format("File size mismatch: expected %d, got %d", - expectedSize, actualSize)); - } - } - - private String calculateChecksum(Path filePath) throws IOException { + private String calculateChecksum(Path filePath) { try { - MessageDigest md = MessageDigest.getInstance("MD5"); + MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] buffer = new byte[8192]; try (InputStream inputStream = Files.newInputStream(filePath)) { @@ -328,37 +634,34 @@ public class FileUploadServiceImpl implements FileUploadService { return sb.toString(); - } catch (NoSuchAlgorithmException e) { - log.warn("MD5 algorithm not available, skipping checksum", e); + } catch (NoSuchAlgorithmException | IOException e) { + log.warn("Failed to calculate checksum for file: {}", filePath, e); return "N/A"; } } - private void cleanupChunks(Path chunkDir) throws IOException { - if (Files.exists(chunkDir)) { - try (Stream walk = Files.walk(chunkDir)) { - walk.sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - log.info("Cleaned up chunk directory: {}", chunkDir); - } + private void createDirectoryIfNotExists(Path directory) throws IOException { + if (!Files.exists(directory)) { + Files.createDirectories(directory); + log.debug("Created directory: {}", directory); } } - private Path getChunkDirectory(Long userId, String uploadId) { - return Paths.get(basePath, "temp", "user", String.valueOf(userId), uploadId); - } + private void setDirectoryPermissions(Path directory) throws IOException { + try { + Set permissions = new HashSet<>(); + permissions.add(PosixFilePermission.OWNER_READ); + permissions.add(PosixFilePermission.OWNER_WRITE); + permissions.add(PosixFilePermission.OWNER_EXECUTE); + permissions.add(PosixFilePermission.GROUP_READ); + permissions.add(PosixFilePermission.GROUP_EXECUTE); + permissions.add(PosixFilePermission.OTHERS_READ); - @Override - public UploadProgressResponse getUploadProgress(String uploadId) { - FileUploadSession session = sessionRepository.findById(uploadId) - .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); + Files.setPosixFilePermissions(directory, permissions); + log.debug("Set permissions for directory: {}", directory); - return UploadProgressResponse.fromSession(session); + } catch (UnsupportedOperationException e) { + log.debug("Posix permissions not supported on this system"); + } } - - @Override - public void cleanupExpiredSessions() { - log.info("Cleanup not implemented yet"); - } -} +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4406117..2880f4c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -23,10 +23,13 @@ spring: file: storage: - base-path: ${FILE_STORAGE_PATH:./uploads} - chunk-size: 5242880 # 5MB - temp-ttl-hours: 24 - max-file-size: 10737418240 # 10GB + base-path: ${FILE_STORAGE_PATH:/data/uploads} + chunk-size: ${FILE_CHUNK_SIZE:5242880} # 5MB + max-file-size: ${MAX_FILE_SIZE:10737418240} # 10GB + max-retry-attempts: ${MAX_RETRY_ATTEMPTS:3} + chunk-timeout-ms: ${CHUNK_TIMEOUT_MS:300000} # 5 минут + temp-ttl-hours: ${TEMP_TTL_HOURS:72} # 3 дня + session-expiry-hours: ${SESSION_EXPIRY_HOURS:24} server: port: ${SERVER_PORT:8080} From b3f29935f64634ef4b1ea383f6f925e174f9a01d Mon Sep 17 00:00:00 2001 From: vladp Date: Tue, 16 Dec 2025 04:18:09 +0700 Subject: [PATCH 03/13] save chunks --- docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index c9e2616..8ae2028 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -41,7 +41,7 @@ services: - backend - api volumes: - - uploads_volume:/data/uploads + - ./my-uploads:/data/uploads:rw grafana: image: grafana/grafana:10.3.1 @@ -146,7 +146,7 @@ volumes: loki_chunks: loki_index: loki_rules: - uploads_volume: +# uploads_volume: networks: app-network: From 4a6de6ad9a8a005d448c6405330e25520ae1a73c Mon Sep 17 00:00:00 2001 From: vladp Date: Tue, 16 Dec 2025 14:27:03 +0700 Subject: [PATCH 04/13] add extensions,true path for file --- .../controller/file/FileUploadController.java | 2 +- .../no_copy/dto/file/InitUploadRequest.java | 3 ++ .../entity/file/FileUploadSession.java | 3 ++ .../service/file/FileUploadService.java | 2 +- .../service/file/FileUploadServiceImpl.java | 44 +++++++++---------- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java b/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java index a36728b..4f5d1ab 100644 --- a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java +++ b/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java @@ -47,7 +47,7 @@ public class FileUploadController { try { FileUploadSession session = fileUploadService.initUpload(authToken.getUser().getId(), request.getFileName(), - request.getFileType(), request.getFileSize()); + request.getFileType(), request.getExtension(), request.getFileSize()); return ResponseEntity.ok(FileApiResponse.success(session)); } catch (Exception e) { diff --git a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java b/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java index 9653984..044c731 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java +++ b/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java @@ -14,6 +14,9 @@ public class InitUploadRequest { @NotBlank(message = "File type is required") private String fileType; + @NotBlank(message = "File extension is required") + private String extension; + @NotNull(message = "File size is required") @Positive(message = "File size must be positive") private Long fileSize; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java b/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java index 0eed4a0..38b0bd9 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java +++ b/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java @@ -62,6 +62,9 @@ public class FileUploadSession { @Column(name = "last_error") private String lastError; + @Column(name = "extension") + private String extension; + @Column(name = "retry_count") private Integer retryCount = 0; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java index e401240..7a9fe55 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java @@ -8,7 +8,7 @@ import java.io.IOException; public interface FileUploadService { FileUploadSession initUpload(Long userId, String fileName, - String fileType, long fileSize); + String fileType, String extension, long fileSize); UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile) throws IOException; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java index f70ffd2..26bebb7 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java @@ -81,7 +81,7 @@ public class FileUploadServiceImpl implements FileUploadService { @Override @Transactional public FileUploadSession initUpload(Long userId, String fileName, - String fileType, long fileSize) { + String fileType, String extension, long fileSize) { log.info("Initializing upload for user {}: {} ({} bytes, type: {})", userId, fileName, fileSize, fileType); @@ -101,6 +101,7 @@ public class FileUploadServiceImpl implements FileUploadService { FileUploadSession session = FileUploadSession.builder() .userId(userId) + .extension(extension) .fileName(fileName) .fileType(fileType) .fileSize(fileSize) @@ -364,7 +365,7 @@ public class FileUploadServiceImpl implements FileUploadService { Files.createDirectories(userUploadsDir); - String safeFileName = generateUniqueFileName(session.getFileName()); + String safeFileName = generateUniqueFileName(session.getFileName(), session.getExtension()); Path finalPath = userUploadsDir.resolve(safeFileName); if (Files.exists(finalPath)) { @@ -374,20 +375,24 @@ public class FileUploadServiceImpl implements FileUploadService { return finalPath; } - private String generateUniqueFileName(String originalName) { - String safeName = originalName.replaceAll("[^a-zA-Z0-9\\.\\-_]", "_"); + private String generateUniqueFileName(String originalName, String extension) { + int dotIndex = originalName.lastIndexOf('.'); + log.info("dotIndex: {}", dotIndex); + log.info("originalName: {}", originalName); + String nameWithoutExtension; + + if (dotIndex > 0) { + nameWithoutExtension = originalName.substring(0, dotIndex) + .replaceAll("[^a-zA-Z0-9\\-_]", "_"); + } else { + nameWithoutExtension = originalName.replaceAll("[^a-zA-Z0-9\\-_]", "_"); + } String timestamp = String.valueOf(System.currentTimeMillis()); String uuid = UUID.randomUUID().toString().substring(0, 8); - int dotIndex = safeName.lastIndexOf('.'); - if (dotIndex > 0) { - String name = safeName.substring(0, dotIndex); - String extension = safeName.substring(dotIndex); - return String.format("%s_%s_%s%s", name, timestamp, uuid, extension); - } else { - return String.format("%s_%s_%s", safeName, timestamp, uuid); - } + return String.format("%s_%s_%s.%s", + nameWithoutExtension, timestamp, uuid, extension); } private void validateAllChunksExist(FileUploadSession session) throws IOException { @@ -409,7 +414,7 @@ public class FileUploadServiceImpl implements FileUploadService { throws IOException { Path chunkDir = getChunkDirectory(session.getUserId(), session.getUploadId()); - byte[] buffer = new byte[8192]; // 8KB буфер + byte[] buffer = new byte[8192]; try (BufferedOutputStream outputStream = new BufferedOutputStream( Files.newOutputStream(finalPath, StandardOpenOption.CREATE_NEW))) { @@ -596,18 +601,13 @@ public class FileUploadServiceImpl implements FileUploadService { } String lowerType = fileType.toLowerCase(); - if (lowerType.startsWith("image/")) { + if (lowerType.startsWith("image")) { return "images"; - } else if (lowerType.startsWith("video/")) { + } else if (lowerType.startsWith("video")) { return "videos"; - } else if (lowerType.startsWith("audio/")) { + } else if (lowerType.startsWith("audio")) { return "audio"; - } else if (lowerType.contains("pdf") || - lowerType.contains("document") || - lowerType.contains("text") || - lowerType.contains("msword") || - lowerType.contains("wordprocessing") || - lowerType.contains("spreadsheet")) { + } else if (lowerType.contains("document")) { return "documents"; } else { return "other"; From 58b4a2f95f1df8318caf9e3f7a1280c073d60804 Mon Sep 17 00:00:00 2001 From: vladp Date: Tue, 16 Dec 2025 15:58:52 +0700 Subject: [PATCH 05/13] delete not use dto's --- docker-compose.yaml | 2 +- .../no_copy/dto/file/ChunkStatusResponse.java | 23 ------------------- .../no_copy/dto/file/ChunkUploadRequest.java | 17 -------------- 3 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java delete mode 100644 src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java diff --git a/docker-compose.yaml b/docker-compose.yaml index 8ae2028..d3f9924 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -41,7 +41,7 @@ services: - backend - api volumes: - - ./my-uploads:/data/uploads:rw + - ./uploads:/data/uploads:rw grafana: image: grafana/grafana:10.3.1 diff --git a/src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java b/src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java deleted file mode 100644 index 0a5f269..0000000 --- a/src/main/java/ru/soune/no_copy/dto/file/ChunkStatusResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.soune.no_copy.dto.file; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import ru.soune.no_copy.entity.file.UploadStatus; - -import java.util.List; -import java.util.Map; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ChunkStatusResponse { - private String uploadId; - private Integer totalChunks; - private Integer uploadedChunks; - private List missingChunks; - private Map chunkPaths; - private UploadStatus status; -} diff --git a/src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java b/src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java deleted file mode 100644 index 25c68fc..0000000 --- a/src/main/java/ru/soune/no_copy/dto/file/ChunkUploadRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.soune.no_copy.dto.file; - -import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ChunkUploadRequest { - - @NotBlank - private String uploadId; - - private Integer chunkNumber; -} From 9e8c7f0fa105cfbb2b66f1d7b83b6ec30a21f7bb Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 17 Dec 2025 01:19:48 +0700 Subject: [PATCH 06/13] add fileentity --- .../file/FileDownloadController.java | 172 +++++++++++ .../controller/file/FileEntityController.java | 210 +++++++++++++ .../no_copy/dto/file/FileEntityResponse.java | 33 +++ .../no_copy/dto/file/FileListResponse.java | 22 ++ .../soune/no_copy/entity/file/FileEntity.java | 78 +++++ .../soune/no_copy/entity/file/FileStatus.java | 8 + .../FileEntityNotFoundException.java | 11 + .../repository/FileEntityRepository.java | 33 +++ .../service/file/FileEntityService.java | 277 ++++++++++++++++++ .../service/file/FileUploadServiceImpl.java | 74 ++++- src/main/resources/application.yaml | 7 + 11 files changed, 921 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java create mode 100644 src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java create mode 100644 src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java create mode 100644 src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java create mode 100644 src/main/java/ru/soune/no_copy/entity/file/FileEntity.java create mode 100644 src/main/java/ru/soune/no_copy/entity/file/FileStatus.java create mode 100644 src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java create mode 100644 src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java create mode 100644 src/main/java/ru/soune/no_copy/service/file/FileEntityService.java diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java b/src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java new file mode 100644 index 0000000..6be2c46 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java @@ -0,0 +1,172 @@ +package ru.soune.no_copy.controller.file; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.soune.no_copy.dto.file.FileEntityResponse; +import ru.soune.no_copy.entity.AuthToken; +import ru.soune.no_copy.exception.NotFoundAuthToken; +import ru.soune.no_copy.repository.AuthTokenRepository; +import ru.soune.no_copy.service.file.FileEntityService; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +@RestController +@RequestMapping("/api/files") +@RequiredArgsConstructor +public class FileDownloadController { + + private final FileEntityService fileEntityService; + private final AuthTokenRepository authTokenRepository; + + /** + * Скачать файл по ID FileEntity + */ + @GetMapping("/download/{fileId}") + public ResponseEntity downloadFile( + @PathVariable String fileId, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + if (!fileInfo.isExistsOnDisk()) { + return ResponseEntity.status(404) + .body(null); + } + + Path filePath = Paths.get(fileInfo.getFilePath()); + Resource resource = new UrlResource(filePath.toUri()); + + if (!resource.exists()) { + return ResponseEntity.status(404).build(); + } + + String contentType = determineContentType(filePath); + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"") + .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileInfo.getFileSize())) + .body(resource); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error downloading file", e); + return ResponseEntity.status(500).build(); + } + } + + /** + * Получить предпросмотр файла (если поддерживается) + */ + @GetMapping("/preview/{fileId}") + public ResponseEntity previewFile( + @PathVariable String fileId, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + if (!isPreviewSupported(fileInfo.getMimeType())) { + return ResponseEntity.status(415) + .body(null); + } + + Path filePath = Paths.get(fileInfo.getFilePath()); + Resource resource = new UrlResource(filePath.toUri()); + + if (!resource.exists()) { + return ResponseEntity.status(404).build(); + } + + String contentType = determineContentType(filePath); + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, + "inline; filename=\"" + fileInfo.getOriginalFileName() + "\"") + .body(resource); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error previewing file", e); + return ResponseEntity.status(500).build(); + } + } + + /** + * Получить информацию о файле для фронтенда + */ + @GetMapping("/info/{fileId}") + public ResponseEntity getFileInfo( + @PathVariable String fileId, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + return ResponseEntity.ok(fileInfo); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error getting file info", e); + return ResponseEntity.status(500).build(); + } + } + + private String determineContentType(Path filePath) throws IOException { + String contentType = Files.probeContentType(filePath); + if (contentType == null) { + contentType = "application/octet-stream"; + } + return contentType; + } + + private boolean isPreviewSupported(String mimeType) { + if (mimeType == null) { + return false; + } + + return mimeType.startsWith("image/") || + mimeType.startsWith("text/") || + mimeType.equals("application/pdf") || + mimeType.startsWith("video/") || + mimeType.startsWith("audio/"); + } + + private Long getUserIdFromToken(String tokenHeader) { + String token = tokenHeader.replace("Bearer ", ""); + AuthToken authToken = authTokenRepository.findByToken(token) + .orElseThrow(() -> new NotFoundAuthToken("Token not found")); + return authToken.getUser().getId(); + } +} diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java b/src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java new file mode 100644 index 0000000..77987a3 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java @@ -0,0 +1,210 @@ +package ru.soune.no_copy.controller.file; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.soune.no_copy.dto.file.FileApiResponse; +import ru.soune.no_copy.dto.file.FileEntityResponse; +import ru.soune.no_copy.dto.file.FileListResponse; +import ru.soune.no_copy.entity.AuthToken; +import ru.soune.no_copy.exception.NotFoundAuthToken; +import ru.soune.no_copy.repository.AuthTokenRepository; +import ru.soune.no_copy.service.file.FileEntityService; + +@Slf4j +@RestController +@RequestMapping("/api/file-entities") +@RequiredArgsConstructor +public class FileEntityController { + + private final FileEntityService fileEntityService; + private final AuthTokenRepository authTokenRepository; + + /** + * Получить информацию о файле по ID + */ + @GetMapping("/{fileId}") + public ResponseEntity> getFileInfo( + @PathVariable String fileId, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403) + .body(FileApiResponse.error("Access denied")); + } + + return ResponseEntity.ok(FileApiResponse.success(fileInfo)); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401) + .body(FileApiResponse.error("Authentication required")); + } catch (Exception e) { + log.error("Error getting file info", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to get file info: " + e.getMessage())); + } + } + + /** + * Получить информацию о файле по ID сессии загрузки + */ + @GetMapping("/by-session/{uploadSessionId}") + public ResponseEntity> getFileByUploadSession( + @PathVariable String uploadSessionId, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(uploadSessionId); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403) + .body(FileApiResponse.error("Access denied")); + } + + return ResponseEntity.ok(FileApiResponse.success(fileInfo)); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401) + .body(FileApiResponse.error("Authentication required")); + } catch (Exception e) { + log.error("Error getting file by session", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to get file: " + e.getMessage())); + } + } + + /** + * Получить все файлы пользователя + */ + @GetMapping("/my-files") + public ResponseEntity> getUserFiles( + @RequestHeader("Authorization") String tokenHeader, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileListResponse files = fileEntityService.getUserFiles(userId, page, pageSize); + + return ResponseEntity.ok(FileApiResponse.success(files)); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401) + .body(FileApiResponse.error("Authentication required")); + } catch (Exception e) { + log.error("Error getting user files", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to get files: " + e.getMessage())); + } + } + + /** + * Поиск файлов пользователя + */ + @GetMapping("/search") + public ResponseEntity> searchFiles( + @RequestParam String query, + @RequestHeader("Authorization") String tokenHeader, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileListResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000); + + var filteredFiles = allFiles.getFiles().stream() + .filter(f -> f.getOriginalFileName().toLowerCase().contains(query.toLowerCase())) + .toList(); + + int start = (page - 1) * pageSize; + int end = Math.min(start + pageSize, filteredFiles.size()); + + FileListResponse response = FileListResponse.builder() + .files(filteredFiles.subList(start, Math.min(end, filteredFiles.size()))) + .totalCount(filteredFiles.size()) + .totalSize(allFiles.getTotalSize()) + .formattedTotalSize(allFiles.getFormattedTotalSize()) + .page(page) + .pageSize(pageSize) + .build(); + + return ResponseEntity.ok(FileApiResponse.success(response)); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401) + .body(FileApiResponse.error("Authentication required")); + } catch (Exception e) { + log.error("Error searching files", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to search files: " + e.getMessage())); + } + } + + /** + * Получить общий размер файлов пользователя + */ + @GetMapping("/storage/usage") + public ResponseEntity> getStorageUsage( + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + long usage = fileEntityService.getUserStorageUsed(userId); + + return ResponseEntity.ok(FileApiResponse.success(usage)); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401) + .body(FileApiResponse.error("Authentication required")); + } catch (Exception e) { + log.error("Error getting storage usage", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to get storage usage: " + e.getMessage())); + } + } + + /** + * Пометить файл как удаленный + */ + @DeleteMapping("/{fileId}") + public ResponseEntity> deleteFile( + @PathVariable String fileId, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403) + .body(FileApiResponse.error("Access denied")); + } + + fileEntityService.markAsDeleted(fileId); + + return ResponseEntity.ok(FileApiResponse.success( + "File marked as deleted", null)); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401) + .body(FileApiResponse.error("Authentication required")); + } catch (Exception e) { + log.error("Error deleting file", e); + return ResponseEntity.badRequest() + .body(FileApiResponse.error("Failed to delete file: " + e.getMessage())); + } + } + + private Long getUserIdFromToken(String tokenHeader) { + String token = tokenHeader.replace("Bearer ", ""); + AuthToken authToken = authTokenRepository.findByToken(token) + .orElseThrow(() -> new NotFoundAuthToken("Token not found")); + return authToken.getUser().getId(); + } +} diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java b/src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java new file mode 100644 index 0000000..ca96341 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java @@ -0,0 +1,33 @@ +package ru.soune.no_copy.dto.file; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.soune.no_copy.entity.file.FileStatus; + +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileEntityResponse { + + private String id; + private Long userId; + private String originalFileName; + private String storedFileName; + private String filePath; + private Long fileSize; + private String mimeType; + private String fileExtension; + private String checksum; + private String uploadSessionId; + private FileStatus status; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private String formattedSize; + private String downloadUrl; + private boolean existsOnDisk; +} diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java b/src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java new file mode 100644 index 0000000..8c2c503 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java @@ -0,0 +1,22 @@ +package ru.soune.no_copy.dto.file; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileListResponse { + + private List files; + private int totalCount; + private long totalSize; + private String formattedTotalSize; + private int page; + private int pageSize; +} diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileEntity.java b/src/main/java/ru/soune/no_copy/entity/file/FileEntity.java new file mode 100644 index 0000000..abe0e9d --- /dev/null +++ b/src/main/java/ru/soune/no_copy/entity/file/FileEntity.java @@ -0,0 +1,78 @@ +package ru.soune.no_copy.entity.file; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "file_entities") +@EntityListeners(AuditingEntityListener.class) +public class FileEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "original_file_name", nullable = false) + private String originalFileName; + + @Column(name = "stored_file_name", nullable = false) + private String storedFileName; + + @Column(name = "file_path", nullable = false, unique = true) + private String filePath; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "mime_type") + private String mimeType; + + @Column(name = "file_extension") + private String fileExtension; + + @Column(name = "checksum") + private String checksum; + + @Column(name = "upload_session_id") + private String uploadSessionId; + + @Enumerated(EnumType.STRING) + @Column(name = "status") + private FileStatus status; + + @CreatedDate + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + public void prePersist() { + if (this.status == null) { + this.status = FileStatus.ACTIVE; + } + if (this.createdAt == null) { + this.createdAt = LocalDateTime.now(); + } + this.updatedAt = LocalDateTime.now(); + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileStatus.java b/src/main/java/ru/soune/no_copy/entity/file/FileStatus.java new file mode 100644 index 0000000..e84fc00 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/entity/file/FileStatus.java @@ -0,0 +1,8 @@ +package ru.soune.no_copy.entity.file; + +public enum FileStatus { + ACTIVE, + DELETED, + PROCESSING, + ERROR +} diff --git a/src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java b/src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java new file mode 100644 index 0000000..8b5fd82 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java @@ -0,0 +1,11 @@ +package ru.soune.no_copy.exception; + +public class FileEntityNotFoundException extends RuntimeException { + public FileEntityNotFoundException(String fileId) { + super("FileEntity not found with ID: " + fileId); + } + + public FileEntityNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java b/src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java new file mode 100644 index 0000000..41e2781 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java @@ -0,0 +1,33 @@ +package ru.soune.no_copy.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import ru.soune.no_copy.entity.file.FileEntity; +import ru.soune.no_copy.entity.file.FileStatus; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface FileEntityRepository extends JpaRepository { + + List findByUserId(Long userId); + + List findByUserIdAndStatus(Long userId, FileStatus status); + + Optional findByFilePath(String filePath); + + Optional findByUploadSessionId(String uploadSessionId); + + boolean existsByFilePath(String filePath); + + @Query("SELECT SUM(f.fileSize) FROM FileEntity f WHERE f.userId = :userId AND f.status = 'ACTIVE'") + Long getTotalSizeByUserId(@Param("userId") Long userId); + + @Query("SELECT f FROM FileEntity f WHERE f.userId = :userId AND f.originalFileName LIKE %:keyword%") + List searchByFileName(@Param("userId") Long userId, @Param("keyword") String keyword); + + long countByUserId(Long userId); +} diff --git a/src/main/java/ru/soune/no_copy/service/file/FileEntityService.java b/src/main/java/ru/soune/no_copy/service/file/FileEntityService.java new file mode 100644 index 0000000..2a745a9 --- /dev/null +++ b/src/main/java/ru/soune/no_copy/service/file/FileEntityService.java @@ -0,0 +1,277 @@ +package ru.soune.no_copy.service.file; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.soune.no_copy.dto.file.FileEntityResponse; +import ru.soune.no_copy.dto.file.FileListResponse; +import ru.soune.no_copy.entity.file.FileEntity; +import ru.soune.no_copy.entity.file.FileStatus; +import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.no_copy.exception.FileEntityNotFoundException; +import ru.soune.no_copy.repository.FileEntityRepository; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileEntityService { + + private final FileEntityRepository fileEntityRepository; + + /** + * Создает FileEntity на основе завершенной сессии загрузки + */ + @Transactional + public FileEntity createFromUploadSession(FileUploadSession session, String checksum) { + log.info("Creating FileEntity for upload session: {}", session.getUploadId()); + + try { + Path filePath = Paths.get(session.getFilePath()); + + if (!Files.exists(filePath)) { + throw new IOException("File not found on disk: " + filePath); + } + + long fileSize = Files.size(filePath); + String originalName = session.getFileName(); + String extension = extractFileExtension(originalName); + String storedName = filePath.getFileName().toString(); + + FileEntity fileEntity = FileEntity.builder() + .userId(session.getUserId()) + .originalFileName(originalName) + .storedFileName(storedName) + .filePath(session.getFilePath()) + .fileSize(fileSize) + .mimeType(session.getFileType()) + .fileExtension(extension) + .checksum(checksum) + .uploadSessionId(session.getUploadId()) + .status(FileStatus.ACTIVE) + .build(); + + FileEntity saved = fileEntityRepository.save(fileEntity); + log.info("FileEntity created successfully: {} (size: {} bytes)", + saved.getId(), fileSize); + + return saved; + + } catch (IOException e) { + log.error("Failed to create FileEntity for session {}: {}", + session.getUploadId(), e.getMessage(), e); + throw new RuntimeException("Failed to create file entity: " + e.getMessage(), e); + } + } + + /** + * Извлекает расширение файла из имени + */ + private String extractFileExtension(String fileName) { + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex > 0 && dotIndex < fileName.length() - 1) { + return fileName.substring(dotIndex + 1).toLowerCase(); + } + return ""; + } + + /** + * Получает FileEntity по ID + */ + @Transactional(readOnly = true) + public FileEntityResponse getById(String fileId) { + FileEntity fileEntity = fileEntityRepository.findById(fileId) + .orElseThrow(() -> new FileEntityNotFoundException(fileId)); + + return convertToResponse(fileEntity); + } + + /** + * Получает FileEntity по ID сессии загрузки + */ + @Transactional(readOnly = true) + public FileEntityResponse getByUploadSessionId(String uploadSessionId) { + FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId) + .orElseThrow(() -> new FileEntityNotFoundException( + "Not found for upload session: " + uploadSessionId)); + + return convertToResponse(fileEntity); + } + + /** + * Получает FileEntity по пути файла + */ + @Transactional(readOnly = true) + public FileEntityResponse getByFilePath(String filePath) { + FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath) + .orElseThrow(() -> new FileEntityNotFoundException("Path: " + filePath)); + + return convertToResponse(fileEntity); + } + + /** + * Получает все файлы пользователя + */ + @Transactional(readOnly = true) + public FileListResponse getAllUserFiles(Long userId) { + List fileEntities = fileEntityRepository.findByUserIdAndStatus( + userId, FileStatus.ACTIVE); + + List files = fileEntities.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + + long totalSize = fileEntities.stream() + .mapToLong(FileEntity::getFileSize) + .sum(); + + return FileListResponse.builder() + .files(files) + .totalCount(files.size()) + .totalSize(totalSize) + .formattedTotalSize(formatFileSize(totalSize)) + .page(1) + .pageSize(files.size()) + .build(); + } + + /** + * Получает файлы пользователя с пагинацией + */ + @Transactional(readOnly = true) + public FileListResponse getUserFiles(Long userId, int page, int pageSize) { + List allFiles = fileEntityRepository.findByUserIdAndStatus( + userId, FileStatus.ACTIVE); + + int start = (page - 1) * pageSize; + int end = Math.min(start + pageSize, allFiles.size()); + + if (start >= allFiles.size()) { + return FileListResponse.builder() + .files(List.of()) + .totalCount(allFiles.size()) + .totalSize(0) + .formattedTotalSize("0 B") + .page(page) + .pageSize(pageSize) + .build(); + } + + List pageFiles = allFiles.subList(start, end); + + List files = pageFiles.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + + long totalSize = allFiles.stream() + .mapToLong(FileEntity::getFileSize) + .sum(); + + return FileListResponse.builder() + .files(files) + .totalCount(allFiles.size()) + .totalSize(totalSize) + .formattedTotalSize(formatFileSize(totalSize)) + .page(page) + .pageSize(pageSize) + .build(); + } + + /** + * Помечает файл как удаленный (мягкое удаление) + */ + @Transactional + public void markAsDeleted(String fileId) { + FileEntity fileEntity = fileEntityRepository.findById(fileId) + .orElseThrow(() -> new FileEntityNotFoundException(fileId)); + + fileEntity.setStatus(FileStatus.DELETED); + fileEntity.setUpdatedAt(LocalDateTime.now()); + fileEntityRepository.save(fileEntity); + + log.info("FileEntity marked as deleted: {}", fileId); + } + + /** + * Получает общий размер файлов пользователя + */ + @Transactional(readOnly = true) + public long getUserStorageUsed(Long userId) { + Long totalSize = fileEntityRepository.getTotalSizeByUserId(userId); + return totalSize != null ? totalSize : 0L; + } + + /** + * Поиск файлов пользователя по имени + */ + @Transactional(readOnly = true) + public List searchFiles(Long userId, String query) { + List files = fileEntityRepository.searchByFileName(userId, query); + + return files.stream() + .filter(f -> f.getStatus() == FileStatus.ACTIVE) + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + /** + * Проверяет, существует ли файл на диске + */ + private boolean checkFileExistsOnDisk(String filePath) { + try { + return Files.exists(Paths.get(filePath)); + } catch (Exception e) { + log.warn("Error checking file existence: {}", filePath, e); + return false; + } + } + + /** + * Конвертирует FileEntity в DTO + */ + private FileEntityResponse convertToResponse(FileEntity fileEntity) { + boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); + + return FileEntityResponse.builder() + .id(fileEntity.getId()) + .userId(fileEntity.getUserId()) + .originalFileName(fileEntity.getOriginalFileName()) + .storedFileName(fileEntity.getStoredFileName()) + .filePath(fileEntity.getFilePath()) + .fileSize(fileEntity.getFileSize()) + .mimeType(fileEntity.getMimeType()) + .fileExtension(fileEntity.getFileExtension()) + .checksum(fileEntity.getChecksum()) + .uploadSessionId(fileEntity.getUploadSessionId()) + .status(fileEntity.getStatus()) + .createdAt(fileEntity.getCreatedAt()) + .updatedAt(fileEntity.getUpdatedAt()) + .formattedSize(formatFileSize(fileEntity.getFileSize())) + .downloadUrl("/api/files/download/" + fileEntity.getId()) + .existsOnDisk(existsOnDisk) + .build(); + } + + /** + * Форматирует размер файла в читаемый вид + */ + private String formatFileSize(long size) { + if (size < 1024) { + return size + " B"; + } else if (size < 1024 * 1024) { + return String.format("%.1f KB", size / 1024.0); + } else if (size < 1024 * 1024 * 1024) { + return String.format("%.1f MB", size / (1024.0 * 1024.0)); + } else { + return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0)); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java index 26bebb7..d01a2f0 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java @@ -3,6 +3,7 @@ package ru.soune.no_copy.service.file; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -56,6 +57,9 @@ public class FileUploadServiceImpl implements FileUploadService { private Path storageRoot; + @Autowired + private FileEntityService fileEntityService; + @PostConstruct public void init() { try { @@ -328,14 +332,46 @@ public class FileUploadServiceImpl implements FileUploadService { } } +// private void assembleFile(FileUploadSession session) throws IOException { +// log.info("Starting file assembly for session: {} ({})", +// session.getUploadId(), session.getFileName()); +// +// Path finalFilePath = null; +// +// try { +// finalFilePath = prepareFinalFile(session); +// +// validateAllChunksExist(session); +// +// mergeChunksToFile(session, finalFilePath); +// +// validateFinalFile(session, finalFilePath); +// +// updateSessionOnSuccess(session, finalFilePath); +// +// cleanupSessionFiles(session); +// +// log.info("File assembly completed: {} -> {} ({} bytes)", +// session.getFileName(), finalFilePath, session.getFileSize()); +// +// } catch (Exception e) { +// if (finalFilePath != null) { +// Files.deleteIfExists(finalFilePath); +// } +// throw e; +// } +// } + private void assembleFile(FileUploadSession session) throws IOException { log.info("Starting file assembly for session: {} ({})", session.getUploadId(), session.getFileName()); Path finalFilePath = null; + String checksum = null; try { finalFilePath = prepareFinalFile(session); + log.info("Final file path: {}", finalFilePath); validateAllChunksExist(session); @@ -343,18 +379,48 @@ public class FileUploadServiceImpl implements FileUploadService { validateFinalFile(session, finalFilePath); - updateSessionOnSuccess(session, finalFilePath); + checksum = calculateChecksum(finalFilePath); + log.debug("File checksum calculated: {}", checksum); + + session.setFilePath(finalFilePath.toString()); + session.setChecksum(checksum); + session.setStatus(UploadStatus.COMPLETED); + session.setCompletedAt(LocalDateTime.now()); + sessionRepository.save(session); + log.info("Upload session updated to COMPLETED: {}", session.getUploadId()); + + try { + fileEntityService.createFromUploadSession(session, checksum); + log.info("FileEntity successfully created for session: {}", + session.getUploadId()); + } catch (Exception e) { + log.error("Failed to create FileEntity for session {} (file uploaded, but metadata not saved): {}", + session.getUploadId(), e.getMessage()); + } cleanupSessionFiles(session); - log.info("File assembly completed: {} -> {} ({} bytes)", + log.info("File assembly completed successfully: {} -> {} ({} bytes)", session.getFileName(), finalFilePath, session.getFileSize()); } catch (Exception e) { + log.error("File assembly failed for session {}: {}", + session.getUploadId(), e.getMessage(), e); + if (finalFilePath != null) { - Files.deleteIfExists(finalFilePath); + try { + Files.deleteIfExists(finalFilePath); + log.debug("Cleaned up partial file: {}", finalFilePath); + } catch (IOException ioException) { + log.warn("Failed to cleanup partial file: {}", finalFilePath, ioException); + } } - throw e; + + session.setStatus(UploadStatus.FAILED); + session.setLastError(e.getMessage()); + sessionRepository.save(session); + + throw new IOException("File assembly failed: " + e.getMessage(), e); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 2880f4c..08326e1 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -31,6 +31,13 @@ file: temp-ttl-hours: ${TEMP_TTL_HOURS:72} # 3 дня session-expiry-hours: ${SESSION_EXPIRY_HOURS:24} +security: + cors: + allowed-origins: "*" + allowed-methods: GET,POST,PUT,DELETE,OPTIONS + allowed-headers: "*" + allow-credentials: true + server: port: ${SERVER_PORT:8080} From 641e983b89e65135df7dd9496e3cfc3dfe2843b8 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 17 Dec 2025 13:41:05 +0700 Subject: [PATCH 07/13] change package --- .../NoCopyApplication.java | 2 +- .../configuration/ApplicationConfig.java | 2 +- .../configuration/JacksonConfig.java | 2 +- .../configuration/JpaConfiguration.java | 2 +- .../configuration/SubscriptionConfig.java | 2 +- .../configuration/file/AsyncConfig.java | 2 +- .../controller/AuthController.java | 18 ++++++------ .../controller/HealtCheckController.java | 3 +- .../controller/UserContentController.java | 2 +- .../controller/UserController.java | 24 ++++++++-------- .../controller/file/FileUploadController.java | 28 +++++++++---------- .../{no_copy => nocopy}/dto/AuthResponse.java | 2 +- .../dto/ChangePasswordRequest.java | 2 +- .../{no_copy => nocopy}/dto/LoginRequest.java | 2 +- .../dto/LoginResponse.java | 3 +- .../dto/RegisterRequest.java | 6 ++-- .../dto/UserContentDTO.java | 2 +- .../dto/UserContentRequest.java | 2 +- .../dto/UserContentResponse.java | 6 ++-- .../dto/UserContentUpdateRequest.java | 2 +- .../{no_copy => nocopy}/dto/UserDTO.java | 6 ++-- .../{no_copy => nocopy}/dto/UserRequest.java | 5 ++-- .../dto/file/FileApiResponse.java | 2 +- .../dto/file/InitUploadRequest.java | 2 +- .../dto/file/UploadProgressResponse.java | 6 ++-- .../{no_copy => nocopy}/entity/AuthToken.java | 2 +- .../entity/BaseEntity.java | 2 +- .../entity/ContentStatus.java | 2 +- .../entity/GenderType.java | 2 +- .../entity/ImageProtection.java | 2 +- .../entity/SubscriptionType.java | 2 +- .../{no_copy => nocopy}/entity/User.java | 2 +- .../entity/UserAction.java | 2 +- .../entity/UserContent.java | 4 +-- .../{no_copy => nocopy}/entity/Violation.java | 2 +- .../entity/file/FileType.java | 2 +- .../entity/file/FileUploadSession.java | 2 +- .../entity/file/UploadStatus.java | 2 +- .../exception/ChunkSizeExceededException.java | 2 +- .../exception/ContentNotFoundException.java | 2 +- .../exception/FileIntegrityException.java | 2 +- .../exception/FileUploadException.java | 2 +- .../exception/InvalidPasswordException.java | 2 +- .../exception/InvalidUserEmail.java | 2 +- .../exception/NotFoundAuthToken.java | 2 +- .../NotValidationPasswordException.java | 2 +- .../exception/TokenNotFoundException.java | 2 +- .../UploadSessionNotFoundException.java | 2 +- .../exception/UserAlreadyExistsException.java | 2 +- .../exception/UserNotFoundException.java | 2 +- .../handler/GlobalExceptionHandler.java | 4 +-- .../handler/JwtTokenProvider.java | 2 +- .../mapper/UserContentMapper.java | 6 ++-- .../mapper/UserMapper.java | 6 ++-- .../repository/AuthTokenRepository.java | 4 +-- .../FileUploadSessionRepository.java | 6 ++-- .../repository/UserContentRepository.java | 6 ++-- .../repository/UserRepository.java | 4 +-- .../service/AuthService.java | 21 +++++++------- .../service/UserContentService.java | 22 +++++++-------- .../service/UserService.java | 18 ++++++------ .../service/file/FileCleanupService.java | 2 +- .../service/file/FileUploadService.java | 6 ++-- .../service/file/FileUploadServiceImpl.java | 16 +++++------ .../service/AuthServiceTest.java | 20 ++++++------- 65 files changed, 163 insertions(+), 167 deletions(-) rename src/main/java/ru/soune/{no_copy => nocopy}/NoCopyApplication.java (91%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/ApplicationConfig.java (92%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/JacksonConfig.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/JpaConfiguration.java (83%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/SubscriptionConfig.java (96%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/file/AsyncConfig.java (94%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/AuthController.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/HealtCheckController.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/UserContentController.java (87%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/UserController.java (87%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/file/FileUploadController.java (91%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/AuthResponse.java (85%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/ChangePasswordRequest.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/LoginRequest.java (94%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/LoginResponse.java (76%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/RegisterRequest.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentDTO.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentRequest.java (93%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentResponse.java (70%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentUpdateRequest.java (93%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserDTO.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserRequest.java (83%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/FileApiResponse.java (96%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/InitUploadRequest.java (94%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/UploadProgressResponse.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/AuthToken.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/BaseEntity.java (95%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/ContentStatus.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/GenderType.java (57%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/ImageProtection.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/SubscriptionType.java (76%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/User.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/UserAction.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/UserContent.java (96%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/Violation.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/FileType.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/FileUploadSession.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/UploadStatus.java (72%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/ChunkSizeExceededException.java (86%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/ContentNotFoundException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/FileIntegrityException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/FileUploadException.java (87%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/InvalidPasswordException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/InvalidUserEmail.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/NotFoundAuthToken.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/NotValidationPasswordException.java (82%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/TokenNotFoundException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/UploadSessionNotFoundException.java (84%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/UserAlreadyExistsException.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/UserNotFoundException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/handler/GlobalExceptionHandler.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/handler/JwtTokenProvider.java (73%) rename src/main/java/ru/soune/{no_copy => nocopy}/mapper/UserContentMapper.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/mapper/UserMapper.java (77%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/AuthTokenRepository.java (83%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/FileUploadSessionRepository.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/UserContentRepository.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/UserRepository.java (77%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/AuthService.java (84%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/UserContentService.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/UserService.java (86%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileCleanupService.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileUploadService.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileUploadServiceImpl.java (98%) rename src/test/java/ru/soune/{no_copy => nocopy}/service/AuthServiceTest.java (89%) diff --git a/src/main/java/ru/soune/no_copy/NoCopyApplication.java b/src/main/java/ru/soune/nocopy/NoCopyApplication.java similarity index 91% rename from src/main/java/ru/soune/no_copy/NoCopyApplication.java rename to src/main/java/ru/soune/nocopy/NoCopyApplication.java index 74bec20..fd6b0f2 100644 --- a/src/main/java/ru/soune/no_copy/NoCopyApplication.java +++ b/src/main/java/ru/soune/nocopy/NoCopyApplication.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy; +package ru.soune.nocopy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/ru/soune/no_copy/configuration/ApplicationConfig.java b/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java similarity index 92% rename from src/main/java/ru/soune/no_copy/configuration/ApplicationConfig.java rename to src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java index 8b0409b..85ae0b8 100644 --- a/src/main/java/ru/soune/no_copy/configuration/ApplicationConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/ru/soune/no_copy/configuration/JacksonConfig.java b/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java similarity index 97% rename from src/main/java/ru/soune/no_copy/configuration/JacksonConfig.java rename to src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java index 0f726fb..99c5c00 100644 --- a/src/main/java/ru/soune/no_copy/configuration/JacksonConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; diff --git a/src/main/java/ru/soune/no_copy/configuration/JpaConfiguration.java b/src/main/java/ru/soune/nocopy/configuration/JpaConfiguration.java similarity index 83% rename from src/main/java/ru/soune/no_copy/configuration/JpaConfiguration.java rename to src/main/java/ru/soune/nocopy/configuration/JpaConfiguration.java index 56ae18b..f62ccde 100644 --- a/src/main/java/ru/soune/no_copy/configuration/JpaConfiguration.java +++ b/src/main/java/ru/soune/nocopy/configuration/JpaConfiguration.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/ru/soune/no_copy/configuration/SubscriptionConfig.java b/src/main/java/ru/soune/nocopy/configuration/SubscriptionConfig.java similarity index 96% rename from src/main/java/ru/soune/no_copy/configuration/SubscriptionConfig.java rename to src/main/java/ru/soune/nocopy/configuration/SubscriptionConfig.java index 7eb41ec..198a544 100644 --- a/src/main/java/ru/soune/no_copy/configuration/SubscriptionConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/SubscriptionConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java b/src/main/java/ru/soune/nocopy/configuration/file/AsyncConfig.java similarity index 94% rename from src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java rename to src/main/java/ru/soune/nocopy/configuration/file/AsyncConfig.java index bd75b72..6882014 100644 --- a/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/file/AsyncConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration.file; +package ru.soune.nocopy.configuration.file; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/ru/soune/no_copy/controller/AuthController.java b/src/main/java/ru/soune/nocopy/controller/AuthController.java similarity index 79% rename from src/main/java/ru/soune/no_copy/controller/AuthController.java rename to src/main/java/ru/soune/nocopy/controller/AuthController.java index 764b1a8..e7d46f0 100644 --- a/src/main/java/ru/soune/no_copy/controller/AuthController.java +++ b/src/main/java/ru/soune/nocopy/controller/AuthController.java @@ -1,17 +1,17 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.no_copy.dto.AuthResponse; -import ru.soune.no_copy.dto.LoginRequest; -import ru.soune.no_copy.dto.LoginResponse; -import ru.soune.no_copy.dto.RegisterRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.exception.TokenNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.service.AuthService; +import ru.soune.nocopy.dto.AuthResponse; +import ru.soune.nocopy.dto.LoginRequest; +import ru.soune.nocopy.dto.LoginResponse; +import ru.soune.nocopy.dto.RegisterRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.exception.TokenNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.AuthService; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/controller/HealtCheckController.java b/src/main/java/ru/soune/nocopy/controller/HealtCheckController.java similarity index 79% rename from src/main/java/ru/soune/no_copy/controller/HealtCheckController.java rename to src/main/java/ru/soune/nocopy/controller/HealtCheckController.java index 33b6ab9..27cedd1 100644 --- a/src/main/java/ru/soune/no_copy/controller/HealtCheckController.java +++ b/src/main/java/ru/soune/nocopy/controller/HealtCheckController.java @@ -1,6 +1,5 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; -import org.apache.coyote.Response; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/ru/soune/no_copy/controller/UserContentController.java b/src/main/java/ru/soune/nocopy/controller/UserContentController.java similarity index 87% rename from src/main/java/ru/soune/no_copy/controller/UserContentController.java rename to src/main/java/ru/soune/nocopy/controller/UserContentController.java index ea25e8f..51a4d5c 100644 --- a/src/main/java/ru/soune/no_copy/controller/UserContentController.java +++ b/src/main/java/ru/soune/nocopy/controller/UserContentController.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/ru/soune/no_copy/controller/UserController.java b/src/main/java/ru/soune/nocopy/controller/UserController.java similarity index 87% rename from src/main/java/ru/soune/no_copy/controller/UserController.java rename to src/main/java/ru/soune/nocopy/controller/UserController.java index 9f23605..39efe8e 100644 --- a/src/main/java/ru/soune/no_copy/controller/UserController.java +++ b/src/main/java/ru/soune/nocopy/controller/UserController.java @@ -1,19 +1,19 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.no_copy.dto.ChangePasswordRequest; -import ru.soune.no_copy.dto.UserDTO; -import ru.soune.no_copy.dto.UserRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.InvalidUserEmail; -import ru.soune.no_copy.exception.NotFoundAuthToken; -import ru.soune.no_copy.mapper.UserMapper; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.UserRepository; -import ru.soune.no_copy.service.UserService; +import ru.soune.nocopy.dto.ChangePasswordRequest; +import ru.soune.nocopy.dto.UserDTO; +import ru.soune.nocopy.dto.UserRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.InvalidUserEmail; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.mapper.UserMapper; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; +import ru.soune.nocopy.service.UserService; import java.util.List; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java similarity index 91% rename from src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java rename to src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java index 4f5d1ab..6eab4bb 100644 --- a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java +++ b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.controller.file; +package ru.soune.nocopy.controller.file; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -7,19 +7,19 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.FileApiResponse; -import ru.soune.no_copy.dto.file.InitUploadRequest; -import ru.soune.no_copy.dto.file.UploadProgressResponse; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.file.FileType; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; -import ru.soune.no_copy.exception.FileUploadException; -import ru.soune.no_copy.exception.NotFoundAuthToken; -import ru.soune.no_copy.exception.UploadSessionNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.FileUploadSessionRepository; -import ru.soune.no_copy.service.file.FileUploadService; +import ru.soune.nocopy.dto.file.FileApiResponse; +import ru.soune.nocopy.dto.file.InitUploadRequest; +import ru.soune.nocopy.dto.file.UploadProgressResponse; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; +import ru.soune.nocopy.exception.FileUploadException; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.exception.UploadSessionNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.FileUploadSessionRepository; +import ru.soune.nocopy.service.file.FileUploadService; import java.util.*; diff --git a/src/main/java/ru/soune/no_copy/dto/AuthResponse.java b/src/main/java/ru/soune/nocopy/dto/AuthResponse.java similarity index 85% rename from src/main/java/ru/soune/no_copy/dto/AuthResponse.java rename to src/main/java/ru/soune/nocopy/dto/AuthResponse.java index c39301a..168e09a 100644 --- a/src/main/java/ru/soune/no_copy/dto/AuthResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/AuthResponse.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/ChangePasswordRequest.java b/src/main/java/ru/soune/nocopy/dto/ChangePasswordRequest.java similarity index 89% rename from src/main/java/ru/soune/no_copy/dto/ChangePasswordRequest.java rename to src/main/java/ru/soune/nocopy/dto/ChangePasswordRequest.java index aff7140..204db58 100644 --- a/src/main/java/ru/soune/no_copy/dto/ChangePasswordRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/ChangePasswordRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/ru/soune/no_copy/dto/LoginRequest.java b/src/main/java/ru/soune/nocopy/dto/LoginRequest.java similarity index 94% rename from src/main/java/ru/soune/no_copy/dto/LoginRequest.java rename to src/main/java/ru/soune/nocopy/dto/LoginRequest.java index c85fe97..d64fa8b 100644 --- a/src/main/java/ru/soune/no_copy/dto/LoginRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/LoginRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/soune/no_copy/dto/LoginResponse.java b/src/main/java/ru/soune/nocopy/dto/LoginResponse.java similarity index 76% rename from src/main/java/ru/soune/no_copy/dto/LoginResponse.java rename to src/main/java/ru/soune/nocopy/dto/LoginResponse.java index 5fe7d73..5de74ea 100644 --- a/src/main/java/ru/soune/no_copy/dto/LoginResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/LoginResponse.java @@ -1,8 +1,7 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.AllArgsConstructor; import lombok.Data; -import ru.soune.no_copy.entity.User; @Data @AllArgsConstructor diff --git a/src/main/java/ru/soune/no_copy/dto/RegisterRequest.java b/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java similarity index 88% rename from src/main/java/ru/soune/no_copy/dto/RegisterRequest.java rename to src/main/java/ru/soune/nocopy/dto/RegisterRequest.java index e08f39b..8d9a88f 100644 --- a/src/main/java/ru/soune/no_copy/dto/RegisterRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Email; @@ -6,8 +6,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Size; import org.springframework.format.annotation.DateTimeFormat; -import ru.soune.no_copy.entity.GenderType; -import ru.soune.no_copy.entity.SubscriptionType; +import ru.soune.nocopy.entity.GenderType; +import ru.soune.nocopy.entity.SubscriptionType; import java.time.LocalDate; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java b/src/main/java/ru/soune/nocopy/dto/UserContentDTO.java similarity index 88% rename from src/main/java/ru/soune/no_copy/dto/UserContentDTO.java rename to src/main/java/ru/soune/nocopy/dto/UserContentDTO.java index 4c9c6f0..934bfda 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentDTO.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.Data; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentRequest.java b/src/main/java/ru/soune/nocopy/dto/UserContentRequest.java similarity index 93% rename from src/main/java/ru/soune/no_copy/dto/UserContentRequest.java rename to src/main/java/ru/soune/nocopy/dto/UserContentRequest.java index 06ef01e..bd28100 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java b/src/main/java/ru/soune/nocopy/dto/UserContentResponse.java similarity index 70% rename from src/main/java/ru/soune/no_copy/dto/UserContentResponse.java rename to src/main/java/ru/soune/nocopy/dto/UserContentResponse.java index d2a817c..8c14615 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentResponse.java @@ -1,7 +1,7 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; -import ru.soune.no_copy.entity.ContentStatus; -import ru.soune.no_copy.entity.file.FileType; +import ru.soune.nocopy.entity.ContentStatus; +import ru.soune.nocopy.entity.file.FileType; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentUpdateRequest.java b/src/main/java/ru/soune/nocopy/dto/UserContentUpdateRequest.java similarity index 93% rename from src/main/java/ru/soune/no_copy/dto/UserContentUpdateRequest.java rename to src/main/java/ru/soune/nocopy/dto/UserContentUpdateRequest.java index 8955969..7595b21 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentUpdateRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentUpdateRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/soune/no_copy/dto/UserDTO.java b/src/main/java/ru/soune/nocopy/dto/UserDTO.java similarity index 81% rename from src/main/java/ru/soune/no_copy/dto/UserDTO.java rename to src/main/java/ru/soune/nocopy/dto/UserDTO.java index ba5b278..45c19ee 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserDTO.java +++ b/src/main/java/ru/soune/nocopy/dto/UserDTO.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.soune.no_copy.entity.GenderType; -import ru.soune.no_copy.entity.SubscriptionType; +import ru.soune.nocopy.entity.GenderType; +import ru.soune.nocopy.entity.SubscriptionType; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/UserRequest.java b/src/main/java/ru/soune/nocopy/dto/UserRequest.java similarity index 83% rename from src/main/java/ru/soune/no_copy/dto/UserRequest.java rename to src/main/java/ru/soune/nocopy/dto/UserRequest.java index db8c8fe..c5f1662 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserRequest.java @@ -1,14 +1,13 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Size; import lombok.Data; -import ru.soune.no_copy.entity.GenderType; +import ru.soune.nocopy.entity.GenderType; import java.time.LocalDate; -import java.time.LocalDateTime; @Data public class UserRequest { diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java similarity index 96% rename from src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java index d271a1c..9feb546 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java b/src/main/java/ru/soune/nocopy/dto/file/InitUploadRequest.java similarity index 94% rename from src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java rename to src/main/java/ru/soune/nocopy/dto/file/InitUploadRequest.java index 044c731..5f43124 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/file/InitUploadRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java b/src/main/java/ru/soune/nocopy/dto/file/UploadProgressResponse.java similarity index 88% rename from src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/UploadProgressResponse.java index be77f03..f5e343d 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/UploadProgressResponse.java @@ -1,12 +1,12 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; @Data @Builder diff --git a/src/main/java/ru/soune/no_copy/entity/AuthToken.java b/src/main/java/ru/soune/nocopy/entity/AuthToken.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/AuthToken.java rename to src/main/java/ru/soune/nocopy/entity/AuthToken.java index 6418073..63cf34a 100644 --- a/src/main/java/ru/soune/no_copy/entity/AuthToken.java +++ b/src/main/java/ru/soune/nocopy/entity/AuthToken.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/BaseEntity.java b/src/main/java/ru/soune/nocopy/entity/BaseEntity.java similarity index 95% rename from src/main/java/ru/soune/no_copy/entity/BaseEntity.java rename to src/main/java/ru/soune/nocopy/entity/BaseEntity.java index b51c525..2743abf 100644 --- a/src/main/java/ru/soune/no_copy/entity/BaseEntity.java +++ b/src/main/java/ru/soune/nocopy/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/ContentStatus.java b/src/main/java/ru/soune/nocopy/entity/ContentStatus.java similarity index 89% rename from src/main/java/ru/soune/no_copy/entity/ContentStatus.java rename to src/main/java/ru/soune/nocopy/entity/ContentStatus.java index d97f66c..c2dfa41 100644 --- a/src/main/java/ru/soune/no_copy/entity/ContentStatus.java +++ b/src/main/java/ru/soune/nocopy/entity/ContentStatus.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/GenderType.java b/src/main/java/ru/soune/nocopy/entity/GenderType.java similarity index 57% rename from src/main/java/ru/soune/no_copy/entity/GenderType.java rename to src/main/java/ru/soune/nocopy/entity/GenderType.java index e85f0f2..580d825 100644 --- a/src/main/java/ru/soune/no_copy/entity/GenderType.java +++ b/src/main/java/ru/soune/nocopy/entity/GenderType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; public enum GenderType { MALE, FEMALE diff --git a/src/main/java/ru/soune/no_copy/entity/ImageProtection.java b/src/main/java/ru/soune/nocopy/entity/ImageProtection.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/ImageProtection.java rename to src/main/java/ru/soune/nocopy/entity/ImageProtection.java index 79a8c8d..eef9e95 100644 --- a/src/main/java/ru/soune/no_copy/entity/ImageProtection.java +++ b/src/main/java/ru/soune/nocopy/entity/ImageProtection.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/SubscriptionType.java b/src/main/java/ru/soune/nocopy/entity/SubscriptionType.java similarity index 76% rename from src/main/java/ru/soune/no_copy/entity/SubscriptionType.java rename to src/main/java/ru/soune/nocopy/entity/SubscriptionType.java index 4c78049..952722e 100644 --- a/src/main/java/ru/soune/no_copy/entity/SubscriptionType.java +++ b/src/main/java/ru/soune/nocopy/entity/SubscriptionType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/User.java b/src/main/java/ru/soune/nocopy/entity/User.java similarity index 98% rename from src/main/java/ru/soune/no_copy/entity/User.java rename to src/main/java/ru/soune/nocopy/entity/User.java index 63a23f2..a4fba7d 100644 --- a/src/main/java/ru/soune/no_copy/entity/User.java +++ b/src/main/java/ru/soune/nocopy/entity/User.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/soune/no_copy/entity/UserAction.java b/src/main/java/ru/soune/nocopy/entity/UserAction.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/UserAction.java rename to src/main/java/ru/soune/nocopy/entity/UserAction.java index d9d22ef..661ab3a 100644 --- a/src/main/java/ru/soune/no_copy/entity/UserAction.java +++ b/src/main/java/ru/soune/nocopy/entity/UserAction.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/UserContent.java b/src/main/java/ru/soune/nocopy/entity/UserContent.java similarity index 96% rename from src/main/java/ru/soune/no_copy/entity/UserContent.java rename to src/main/java/ru/soune/nocopy/entity/UserContent.java index a6603c1..1691926 100644 --- a/src/main/java/ru/soune/no_copy/entity/UserContent.java +++ b/src/main/java/ru/soune/nocopy/entity/UserContent.java @@ -1,10 +1,10 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import ru.soune.no_copy.entity.file.FileType; +import ru.soune.nocopy.entity.file.FileType; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/entity/Violation.java b/src/main/java/ru/soune/nocopy/entity/Violation.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/Violation.java rename to src/main/java/ru/soune/nocopy/entity/Violation.java index 6e1768c..dd504bd 100644 --- a/src/main/java/ru/soune/no_copy/entity/Violation.java +++ b/src/main/java/ru/soune/nocopy/entity/Violation.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileType.java b/src/main/java/ru/soune/nocopy/entity/file/FileType.java similarity index 88% rename from src/main/java/ru/soune/no_copy/entity/file/FileType.java rename to src/main/java/ru/soune/nocopy/entity/file/FileType.java index dd7f6ab..e8d53f7 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileType.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java b/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java similarity index 98% rename from src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java rename to src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java index 38b0bd9..46a8698 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java b/src/main/java/ru/soune/nocopy/entity/file/UploadStatus.java similarity index 72% rename from src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java rename to src/main/java/ru/soune/nocopy/entity/file/UploadStatus.java index d1d3418..577413c 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java +++ b/src/main/java/ru/soune/nocopy/entity/file/UploadStatus.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; public enum UploadStatus { INITIATED, diff --git a/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java b/src/main/java/ru/soune/nocopy/exception/ChunkSizeExceededException.java similarity index 86% rename from src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java rename to src/main/java/ru/soune/nocopy/exception/ChunkSizeExceededException.java index e45f12b..a19cd95 100644 --- a/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java +++ b/src/main/java/ru/soune/nocopy/exception/ChunkSizeExceededException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class ChunkSizeExceededException extends RuntimeException { public ChunkSizeExceededException(long actualSize, long maxSize) { diff --git a/src/main/java/ru/soune/no_copy/exception/ContentNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/ContentNotFoundException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/ContentNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/ContentNotFoundException.java index af0f040..75b8af7 100644 --- a/src/main/java/ru/soune/no_copy/exception/ContentNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/ContentNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class ContentNotFoundException extends RuntimeException { public ContentNotFoundException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java b/src/main/java/ru/soune/nocopy/exception/FileIntegrityException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java rename to src/main/java/ru/soune/nocopy/exception/FileIntegrityException.java index 2fece21..bb10cb9 100644 --- a/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java +++ b/src/main/java/ru/soune/nocopy/exception/FileIntegrityException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class FileIntegrityException extends RuntimeException { public FileIntegrityException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/FileUploadException.java b/src/main/java/ru/soune/nocopy/exception/FileUploadException.java similarity index 87% rename from src/main/java/ru/soune/no_copy/exception/FileUploadException.java rename to src/main/java/ru/soune/nocopy/exception/FileUploadException.java index 6c74b1d..5a38031 100644 --- a/src/main/java/ru/soune/no_copy/exception/FileUploadException.java +++ b/src/main/java/ru/soune/nocopy/exception/FileUploadException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class FileUploadException extends RuntimeException { public FileUploadException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/InvalidPasswordException.java b/src/main/java/ru/soune/nocopy/exception/InvalidPasswordException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/InvalidPasswordException.java rename to src/main/java/ru/soune/nocopy/exception/InvalidPasswordException.java index b9f2820..72a43e9 100644 --- a/src/main/java/ru/soune/no_copy/exception/InvalidPasswordException.java +++ b/src/main/java/ru/soune/nocopy/exception/InvalidPasswordException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class InvalidPasswordException extends RuntimeException { public InvalidPasswordException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/InvalidUserEmail.java b/src/main/java/ru/soune/nocopy/exception/InvalidUserEmail.java similarity index 79% rename from src/main/java/ru/soune/no_copy/exception/InvalidUserEmail.java rename to src/main/java/ru/soune/nocopy/exception/InvalidUserEmail.java index 7ce203b..850a7f1 100644 --- a/src/main/java/ru/soune/no_copy/exception/InvalidUserEmail.java +++ b/src/main/java/ru/soune/nocopy/exception/InvalidUserEmail.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class InvalidUserEmail extends RuntimeException { public InvalidUserEmail(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/NotFoundAuthToken.java b/src/main/java/ru/soune/nocopy/exception/NotFoundAuthToken.java similarity index 79% rename from src/main/java/ru/soune/no_copy/exception/NotFoundAuthToken.java rename to src/main/java/ru/soune/nocopy/exception/NotFoundAuthToken.java index 7a61eea..473f486 100644 --- a/src/main/java/ru/soune/no_copy/exception/NotFoundAuthToken.java +++ b/src/main/java/ru/soune/nocopy/exception/NotFoundAuthToken.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class NotFoundAuthToken extends RuntimeException { public NotFoundAuthToken(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/NotValidationPasswordException.java b/src/main/java/ru/soune/nocopy/exception/NotValidationPasswordException.java similarity index 82% rename from src/main/java/ru/soune/no_copy/exception/NotValidationPasswordException.java rename to src/main/java/ru/soune/nocopy/exception/NotValidationPasswordException.java index 6e24040..48a2fe5 100644 --- a/src/main/java/ru/soune/no_copy/exception/NotValidationPasswordException.java +++ b/src/main/java/ru/soune/nocopy/exception/NotValidationPasswordException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class NotValidationPasswordException extends RuntimeException { public NotValidationPasswordException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/TokenNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/TokenNotFoundException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/TokenNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/TokenNotFoundException.java index d2e526b..b934cf4 100644 --- a/src/main/java/ru/soune/no_copy/exception/TokenNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/TokenNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class TokenNotFoundException extends RuntimeException { public TokenNotFoundException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/UploadSessionNotFoundException.java similarity index 84% rename from src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/UploadSessionNotFoundException.java index bfc4bfc..35b0eb5 100644 --- a/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/UploadSessionNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class UploadSessionNotFoundException extends RuntimeException { public UploadSessionNotFoundException(String uploadId) { diff --git a/src/main/java/ru/soune/no_copy/exception/UserAlreadyExistsException.java b/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java similarity index 81% rename from src/main/java/ru/soune/no_copy/exception/UserAlreadyExistsException.java rename to src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java index 8815885..f390b6c 100644 --- a/src/main/java/ru/soune/no_copy/exception/UserAlreadyExistsException.java +++ b/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class UserAlreadyExistsException extends RuntimeException { public UserAlreadyExistsException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/UserNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/UserNotFoundException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/UserNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/UserNotFoundException.java index a8d8b03..919ff70 100644 --- a/src/main/java/ru/soune/no_copy/exception/UserNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/UserNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class UserNotFoundException extends RuntimeException { public UserNotFoundException(String message) { diff --git a/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java b/src/main/java/ru/soune/nocopy/handler/GlobalExceptionHandler.java similarity index 97% rename from src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java rename to src/main/java/ru/soune/nocopy/handler/GlobalExceptionHandler.java index c3a02b2..84d4b44 100644 --- a/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.handler; +package ru.soune.nocopy.handler; import lombok.AllArgsConstructor; import org.springframework.context.support.DefaultMessageSourceResolvable; @@ -8,7 +8,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import ru.soune.no_copy.exception.*; +import ru.soune.nocopy.exception.*; import java.util.Map; diff --git a/src/main/java/ru/soune/no_copy/handler/JwtTokenProvider.java b/src/main/java/ru/soune/nocopy/handler/JwtTokenProvider.java similarity index 73% rename from src/main/java/ru/soune/no_copy/handler/JwtTokenProvider.java rename to src/main/java/ru/soune/nocopy/handler/JwtTokenProvider.java index 721869c..9b18a21 100644 --- a/src/main/java/ru/soune/no_copy/handler/JwtTokenProvider.java +++ b/src/main/java/ru/soune/nocopy/handler/JwtTokenProvider.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.handler; +package ru.soune.nocopy.handler; import org.springframework.stereotype.Component; diff --git a/src/main/java/ru/soune/no_copy/mapper/UserContentMapper.java b/src/main/java/ru/soune/nocopy/mapper/UserContentMapper.java similarity index 79% rename from src/main/java/ru/soune/no_copy/mapper/UserContentMapper.java rename to src/main/java/ru/soune/nocopy/mapper/UserContentMapper.java index 5617cbd..3959f8f 100644 --- a/src/main/java/ru/soune/no_copy/mapper/UserContentMapper.java +++ b/src/main/java/ru/soune/nocopy/mapper/UserContentMapper.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.mapper; +package ru.soune.nocopy.mapper; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.factory.Mappers; -import ru.soune.no_copy.dto.UserContentUpdateRequest; -import ru.soune.no_copy.entity.UserContent; +import ru.soune.nocopy.dto.UserContentUpdateRequest; +import ru.soune.nocopy.entity.UserContent; @Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) diff --git a/src/main/java/ru/soune/no_copy/mapper/UserMapper.java b/src/main/java/ru/soune/nocopy/mapper/UserMapper.java similarity index 77% rename from src/main/java/ru/soune/no_copy/mapper/UserMapper.java rename to src/main/java/ru/soune/nocopy/mapper/UserMapper.java index 442f082..c151480 100644 --- a/src/main/java/ru/soune/no_copy/mapper/UserMapper.java +++ b/src/main/java/ru/soune/nocopy/mapper/UserMapper.java @@ -1,10 +1,10 @@ -package ru.soune.no_copy.mapper; +package ru.soune.nocopy.mapper; import org.mapstruct.Mapper; import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.factory.Mappers; -import ru.soune.no_copy.dto.UserDTO; -import ru.soune.no_copy.entity.User; +import ru.soune.nocopy.dto.UserDTO; +import ru.soune.nocopy.entity.User; @Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) diff --git a/src/main/java/ru/soune/no_copy/repository/AuthTokenRepository.java b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java similarity index 83% rename from src/main/java/ru/soune/no_copy/repository/AuthTokenRepository.java rename to src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java index af56845..74826fd 100644 --- a/src/main/java/ru/soune/no_copy/repository/AuthTokenRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java @@ -1,8 +1,8 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.soune.no_copy.entity.AuthToken; +import ru.soune.nocopy.entity.AuthToken; import java.time.LocalDate; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java b/src/main/java/ru/soune/nocopy/repository/FileUploadSessionRepository.java similarity index 88% rename from src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java rename to src/main/java/ru/soune/nocopy/repository/FileUploadSessionRepository.java index 0a9175f..2b222a3 100644 --- a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/FileUploadSessionRepository.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; import java.time.LocalDateTime; import java.util.List; diff --git a/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java b/src/main/java/ru/soune/nocopy/repository/UserContentRepository.java similarity index 81% rename from src/main/java/ru/soune/no_copy/repository/UserContentRepository.java rename to src/main/java/ru/soune/nocopy/repository/UserContentRepository.java index 42f5e72..74fcd4b 100644 --- a/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/UserContentRepository.java @@ -1,9 +1,9 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import ru.soune.no_copy.entity.file.FileType; -import ru.soune.no_copy.entity.UserContent; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.UserContent; import java.util.List; diff --git a/src/main/java/ru/soune/no_copy/repository/UserRepository.java b/src/main/java/ru/soune/nocopy/repository/UserRepository.java similarity index 77% rename from src/main/java/ru/soune/no_copy/repository/UserRepository.java rename to src/main/java/ru/soune/nocopy/repository/UserRepository.java index 472fe58..da4d7f9 100644 --- a/src/main/java/ru/soune/no_copy/repository/UserRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/UserRepository.java @@ -1,7 +1,7 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; -import ru.soune.no_copy.entity.User; +import ru.soune.nocopy.entity.User; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/service/AuthService.java b/src/main/java/ru/soune/nocopy/service/AuthService.java similarity index 84% rename from src/main/java/ru/soune/no_copy/service/AuthService.java rename to src/main/java/ru/soune/nocopy/service/AuthService.java index 1cb1966..6784449 100644 --- a/src/main/java/ru/soune/no_copy/service/AuthService.java +++ b/src/main/java/ru/soune/nocopy/service/AuthService.java @@ -1,24 +1,23 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import lombok.RequiredArgsConstructor; import org.springframework.context.MessageSource; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.LoginRequest; -import ru.soune.no_copy.dto.RegisterRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.NotValidationPasswordException; -import ru.soune.no_copy.exception.UserAlreadyExistsException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.LoginRequest; +import ru.soune.nocopy.dto.RegisterRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.NotValidationPasswordException; +import ru.soune.nocopy.exception.UserAlreadyExistsException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; import java.security.SecureRandom; import java.time.LocalDateTime; import java.util.Base64; -import java.util.Locale; import java.util.Optional; @Service diff --git a/src/main/java/ru/soune/no_copy/service/UserContentService.java b/src/main/java/ru/soune/nocopy/service/UserContentService.java similarity index 81% rename from src/main/java/ru/soune/no_copy/service/UserContentService.java rename to src/main/java/ru/soune/nocopy/service/UserContentService.java index eb2573d..e113752 100644 --- a/src/main/java/ru/soune/no_copy/service/UserContentService.java +++ b/src/main/java/ru/soune/nocopy/service/UserContentService.java @@ -1,18 +1,18 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.UserContentRequest; -import ru.soune.no_copy.dto.UserContentUpdateRequest; -import ru.soune.no_copy.entity.file.FileType; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.entity.UserContent; -import ru.soune.no_copy.exception.ContentNotFoundException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.mapper.UserContentMapper; -import ru.soune.no_copy.repository.UserContentRepository; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.UserContentRequest; +import ru.soune.nocopy.dto.UserContentUpdateRequest; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.entity.UserContent; +import ru.soune.nocopy.exception.ContentNotFoundException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.mapper.UserContentMapper; +import ru.soune.nocopy.repository.UserContentRepository; +import ru.soune.nocopy.repository.UserRepository; import java.util.List; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/service/UserService.java b/src/main/java/ru/soune/nocopy/service/UserService.java similarity index 86% rename from src/main/java/ru/soune/no_copy/service/UserService.java rename to src/main/java/ru/soune/nocopy/service/UserService.java index 4cb0a51..7c4b79f 100644 --- a/src/main/java/ru/soune/no_copy/service/UserService.java +++ b/src/main/java/ru/soune/nocopy/service/UserService.java @@ -1,17 +1,17 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import lombok.AllArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.ChangePasswordRequest; -import ru.soune.no_copy.dto.UserDTO; -import ru.soune.no_copy.dto.UserRequest; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.InvalidUserEmail; -import ru.soune.no_copy.exception.NotValidationPasswordException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.ChangePasswordRequest; +import ru.soune.nocopy.dto.UserDTO; +import ru.soune.nocopy.dto.UserRequest; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.InvalidUserEmail; +import ru.soune.nocopy.exception.NotValidationPasswordException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.repository.UserRepository; @Service @AllArgsConstructor diff --git a/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java b/src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java similarity index 98% rename from src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java rename to src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java index 65a8f05..f86aa89 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java b/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java similarity index 80% rename from src/main/java/ru/soune/no_copy/service/file/FileUploadService.java rename to src/main/java/ru/soune/nocopy/service/file/FileUploadService.java index 7a9fe55..cfb9aec 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java @@ -1,8 +1,8 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.UploadProgressResponse; -import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.nocopy.dto.file.UploadProgressResponse; +import ru.soune.nocopy.entity.file.FileUploadSession; import java.io.IOException; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java similarity index 98% rename from src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java rename to src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java index 26bebb7..49e5aca 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -8,13 +8,13 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.UploadProgressResponse; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; -import ru.soune.no_copy.exception.ChunkSizeExceededException; -import ru.soune.no_copy.exception.FileUploadException; -import ru.soune.no_copy.exception.UploadSessionNotFoundException; -import ru.soune.no_copy.repository.FileUploadSessionRepository; +import ru.soune.nocopy.dto.file.UploadProgressResponse; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; +import ru.soune.nocopy.exception.ChunkSizeExceededException; +import ru.soune.nocopy.exception.FileUploadException; +import ru.soune.nocopy.exception.UploadSessionNotFoundException; +import ru.soune.nocopy.repository.FileUploadSessionRepository; import java.io.*; import java.nio.file.*; diff --git a/src/test/java/ru/soune/no_copy/service/AuthServiceTest.java b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java similarity index 89% rename from src/test/java/ru/soune/no_copy/service/AuthServiceTest.java rename to src/test/java/ru/soune/nocopy/service/AuthServiceTest.java index 2b75fcb..8e045fd 100644 --- a/src/test/java/ru/soune/no_copy/service/AuthServiceTest.java +++ b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -8,15 +8,15 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.context.MessageSource; import org.springframework.security.crypto.password.PasswordEncoder; -import ru.soune.no_copy.dto.LoginRequest; -import ru.soune.no_copy.dto.RegisterRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.NotValidationPasswordException; -import ru.soune.no_copy.exception.UserAlreadyExistsException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.LoginRequest; +import ru.soune.nocopy.dto.RegisterRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.NotValidationPasswordException; +import ru.soune.nocopy.exception.UserAlreadyExistsException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; import java.util.Locale; import java.util.Optional; From a030837d455284aa5d338deac9b4094a9b552746 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 17 Dec 2025 22:31:30 +0700 Subject: [PATCH 08/13] update controller chunk endpoints --- .../nocopy/controller/ApiController.java | 159 ++++++++++++ .../controller/file/FileUploadController.java | 159 ------------ .../ru/soune/nocopy/dto/AuthResponse.java | 0 .../ru/soune/nocopy/dto/LoginResponse.java | 0 .../java/ru/soune/nocopy/dto/MessageCode.java | 5 + .../ru/soune/nocopy/dto/RegisterRequest.java | 0 .../soune/nocopy/dto/file/ActionResponse.java | 17 ++ .../dto/file/CanceledUploadResponse.java | 15 ++ .../nocopy/dto/file/ChunkStatusResponse.java | 28 ++ .../nocopy/dto/file/ChunkUploadResponse.java | 25 ++ .../dto/file/CompleteUploadResponse.java | 31 +++ .../nocopy/dto/file/FileTypesResponse.java | 19 ++ .../nocopy/dto/file/FileUploadRequest.java | 31 +++ .../nocopy/dto/file/InitFileResponse.java | 30 +++ .../nocopy/dto/file/RetryUploadResponse.java | 18 ++ .../soune/nocopy/dto/file/UploadProgress.java | 40 +++ .../nocopy/entity/file/FileUploadSession.java | 12 +- .../exception/UserAlreadyExistsException.java | 0 .../nocopy/handler/FileUploadHandler.java | 243 ++++++++++++++++++ .../validator/FileUploadRequestValidator.java | 66 +++++ .../service/file/FileUploadServiceImpl.java | 5 +- 21 files changed, 740 insertions(+), 163 deletions(-) delete mode 100644 src/main/java/ru/soune/nocopy/dto/AuthResponse.java delete mode 100644 src/main/java/ru/soune/nocopy/dto/LoginResponse.java delete mode 100644 src/main/java/ru/soune/nocopy/dto/RegisterRequest.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/ActionResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/CanceledUploadResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/ChunkStatusResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/CompleteUploadResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/FileTypesResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/FileUploadRequest.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/InitFileResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/RetryUploadResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/UploadProgress.java delete mode 100644 src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java create mode 100644 src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java create mode 100644 src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index 460ad8b..04e2d29 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -8,15 +8,22 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import ru.soune.nocopy.dto.BaseRequest; import ru.soune.nocopy.dto.BaseResponse; import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.RegAnswer; +import ru.soune.nocopy.dto.file.ChunkUploadResponse; +import ru.soune.nocopy.dto.file.CompleteUploadResponse; +import ru.soune.nocopy.dto.file.UploadProgress; +import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.ValidationException; +import ru.soune.nocopy.handler.FileUploadHandler; import ru.soune.nocopy.handler.LoginRequestHandler; import ru.soune.nocopy.handler.RegRequestHandler; import ru.soune.nocopy.handler.RequestHandler; +import ru.soune.nocopy.service.file.FileUploadService; import java.util.HashMap; import java.util.List; @@ -29,6 +36,7 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class ApiController { private final ApplicationContext applicationContext; + private final FileUploadService fileUploadService; private Map handlers = new HashMap<>(); @@ -36,7 +44,9 @@ public class ApiController { public void init() { RegRequestHandler regHandler = applicationContext.getBean(RegRequestHandler.class); LoginRequestHandler loginHandler = applicationContext.getBean(LoginRequestHandler.class); + FileUploadHandler fileUploadHandler = applicationContext.getBean(FileUploadHandler.class); + handlers.put(20004, fileUploadHandler); handlers.put(20002, regHandler); handlers.put(20001, loginHandler); } @@ -76,6 +86,155 @@ public class ApiController { } } + @PostMapping("/v{version}/files/chunk") + public ResponseEntity uploadChunk( + @PathVariable("version") int version, + @RequestParam("upload_id") String uploadId, + @RequestParam("chunk_number") Integer chunkNumber, + @RequestParam("chunk") MultipartFile chunk) { + + log.info("Uploading chunk {} for session {}, file size: {} bytes, version: {}", + chunkNumber, uploadId, chunk.getSize(), version); + + if (chunk.isEmpty()) { + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Chunk file is empty", null)); + } + + if (uploadId == null || uploadId.isBlank()) { + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Upload ID is required", null)); + } + + if (chunkNumber == null || chunkNumber < 0) { + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Valid chunk number is required", null)); + } + + try { + fileUploadService.uploadChunk(uploadId, chunkNumber, chunk); + + ChunkUploadResponse responseBody = ChunkUploadResponse.builder() + .uploadId(uploadId) + .chunkNumber(chunkNumber) + .chunkSize(chunk.getSize()) + .message("Chunk uploaded successfully") + .build(); + + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(), + "Chunk uploaded successfully", responseBody)); + + } catch (Exception e) { + log.error("Error uploading chunk", e); + + ChunkUploadResponse responseBody = ChunkUploadResponse.builder() + .uploadId(uploadId) + .chunkNumber(chunkNumber) + .build(); + + return ResponseEntity.ok().body(new BaseResponse( + 20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to upload chunk: " + e.getMessage(), responseBody)); + } + } + + @GetMapping("/v{version}/files/progress/{uploadId}") + public ResponseEntity getUploadProgress( + @PathVariable("version") int version, + @PathVariable String uploadId) { + + log.info("Getting progress for upload session: {}, version: {}", uploadId, version); + + try { + var progress = fileUploadService.getUploadProgress(uploadId); + + UploadProgress responseBody = UploadProgress.builder() + .uploadId(progress.getUploadId()) + .fileName(progress.getFileName()) + .totalChunks(progress.getTotalChunks()) + .uploadedChunks(progress.getUploadedChunks()) + .status(progress.getStatus().toString()) + .progressPercentage(progress.getProgressPercentage()) + .filePath(progress.getFilePath()) + .remainingChunks(progress.getTotalChunks() - progress.getUploadedChunks()) + .build(); + + return ResponseEntity.ok().body(new BaseResponse( + 20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody)); + + } catch (Exception e) { + log.error("Error getting progress for upload: {}", uploadId, e); + + UploadProgress responseBody = UploadProgress.builder() + .uploadId(uploadId) + .build(); + + return ResponseEntity.ok().body(new BaseResponse( + 20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to get upload progress: " + e.getMessage(), responseBody)); + } + } + + @PostMapping("/v{version}/files/{uploadId}/complete") + public ResponseEntity completeUpload( + @PathVariable("version") int version, + @PathVariable String uploadId) { + + log.info("Completing upload session: {}, version: {}", uploadId, version); + + try { + var progress = fileUploadService.getUploadProgress(uploadId); + + if (progress.getStatus() == UploadStatus.COMPLETED) { + CompleteUploadResponse responseBody = CompleteUploadResponse.builder() + .uploadId(progress.getUploadId()) + .status(progress.getStatus().toString()) + .uploadedChunks(progress.getUploadedChunks()) + .totalChunks(progress.getTotalChunks()) + .message("Upload already completed") + .filePath(progress.getFilePath()) + .build(); + + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(), + "Upload already completed", responseBody)); + } + + if (progress.getUploadedChunks() < progress.getTotalChunks()) { + CompleteUploadResponse responseBody = CompleteUploadResponse.builder() + .uploadId(progress.getUploadId()) + .status(progress.getStatus().toString()) + .uploadedChunks(progress.getUploadedChunks()) + .totalChunks(progress.getTotalChunks()) + .message("Not all chunks uploaded") + .build(); + + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.INCOMPLETE_UPLOAD.getCode(), + "Not all chunks uploaded", responseBody)); + } + + CompleteUploadResponse responseBody = CompleteUploadResponse.builder() + .uploadId(progress.getUploadId()) + .status(progress.getStatus().toString()) + .uploadedChunks(progress.getUploadedChunks()) + .totalChunks(progress.getTotalChunks()) + .message("File assembly in progress") + .build(); + + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(), + "File assembly in progress", responseBody)); + + } catch (Exception e) { + log.error("Error completing upload: {}", uploadId, e); + + CompleteUploadResponse responseBody = CompleteUploadResponse.builder() + .uploadId(uploadId) + .build(); + + return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to complete upload: " + e.getMessage(), responseBody)); + } + } + private ResponseEntity createValidationErrorResponse(BindingResult bindingResult, Integer msgId) { List> fieldErrors = bindingResult.getFieldErrors() .stream() diff --git a/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java index 6eab4bb..d4bb6da 100644 --- a/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java +++ b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java @@ -31,38 +31,6 @@ public class FileUploadController { private final FileUploadService fileUploadService; - private final AuthTokenRepository authTokenRepository; - - private final FileUploadSessionRepository fileUploadSessionRepository; - - //TODO ADD TEMPLATE JSON, RESPONSE OK,ADD MESSAGE CODE - @PostMapping("/init") - public ResponseEntity> initUpload(@Valid @RequestBody InitUploadRequest request, - @RequestHeader("Authorization") String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); - - Optional tokenOptional = authTokenRepository.findByToken(token); - - AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); - - try { - FileUploadSession session = fileUploadService.initUpload(authToken.getUser().getId(), request.getFileName(), - request.getFileType(), request.getExtension(), request.getFileSize()); - - return ResponseEntity.ok(FileApiResponse.success(session)); - } catch (Exception e) { - log.error("Error initializing upload", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error(e.getMessage())); - } - } - - @GetMapping("/file-types") - public ResponseEntity> getFileTypes() { - return ResponseEntity.ok(Arrays.asList(FileType.values())); - } - - @PostMapping("/chunk") public ResponseEntity> uploadChunk( @RequestParam("uploadId") String uploadId, @@ -94,131 +62,4 @@ public class FileUploadController { .body(FileApiResponse.error(e.getMessage())); } } - - @GetMapping("/progress/{uploadId}") - public ResponseEntity> getProgress( - @PathVariable String uploadId) { - - try { - UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); - return ResponseEntity.ok(FileApiResponse.success(progress)); - - } catch (UploadSessionNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(FileApiResponse.error("Upload session not found")); - } catch (Exception e) { - log.error("Error getting progress", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to get progress")); - } - } - - @PostMapping("/{uploadId}/complete") - public ResponseEntity> completeUpload( - @PathVariable String uploadId) { - - log.info("Manual completion requested for session: {}", uploadId); - - try { - UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); - - if (progress.getStatus() == UploadStatus.COMPLETED) { - return ResponseEntity.ok(FileApiResponse.success( - "Upload already completed", progress)); - } - - if (progress.getUploadedChunks() < progress.getTotalChunks()) { - return ResponseEntity.badRequest() - .body(FileApiResponse.error( - String.format("Not all chunks uploaded: %d/%d", - progress.getUploadedChunks(), progress.getTotalChunks()))); - } - - return ResponseEntity.ok(FileApiResponse.success( - "File assembly in progress", progress)); - - } catch (UploadSessionNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(FileApiResponse.error("Upload session not found")); - } catch (Exception e) { - log.error("Error completing upload", e); - return ResponseEntity.internalServerError() - .body(FileApiResponse.error("Failed to complete upload")); - } - } - - @PostMapping("/{uploadId}/cancel") - public ResponseEntity> cancelUpload( - @PathVariable String uploadId) { - - log.info("Cancelling upload session: {}", uploadId); - - try { - fileUploadService.cancelUpload(uploadId); - return ResponseEntity.ok(FileApiResponse.success( - "Upload cancelled successfully", null)); - - } catch (UploadSessionNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(FileApiResponse.error("Upload session not found")); - } catch (FileUploadException e) { - return ResponseEntity.badRequest() - .body(FileApiResponse.error(e.getMessage())); - } catch (Exception e) { - log.error("Error cancelling upload", e); - return ResponseEntity.internalServerError() - .body(FileApiResponse.error("Failed to cancel upload")); - } - } - - @PostMapping("/{uploadId}/retry") - public ResponseEntity> retryUpload( - @PathVariable String uploadId) { - - log.info("Retry requested for failed upload: {}", uploadId); - - try { - fileUploadService.retryFailedUpload(uploadId); - UploadProgressResponse progress = fileUploadService.getUploadProgress(uploadId); - - return ResponseEntity.ok(FileApiResponse.success( - "Upload retry initiated", progress)); - - } catch (UploadSessionNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(FileApiResponse.error("Upload session not found")); - } catch (FileUploadException e) { - return ResponseEntity.badRequest() - .body(FileApiResponse.error(e.getMessage())); - } catch (Exception e) { - log.error("Error retrying upload", e); - return ResponseEntity.internalServerError() - .body(FileApiResponse.error("Failed to retry upload")); - } - } - - @GetMapping("/{uploadId}/chunks") - public ResponseEntity>> getChunkStatus( - @PathVariable String uploadId) { - - try { - FileUploadSession session = fileUploadSessionRepository.findById(uploadId) - .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); - - Map chunkStatus = new HashMap<>(); - for (int i = 0; i < session.getTotalChunks(); i++) { - chunkStatus.put(i, session.getChunkPaths().containsKey(i)); - } - - return ResponseEntity.ok(FileApiResponse.success(chunkStatus)); - - } catch (UploadSessionNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(FileApiResponse.error("Upload session not found")); - } catch (Exception e) { - log.error("Error getting chunk status", e); - return ResponseEntity.internalServerError() - .body(FileApiResponse.error("Failed to get chunk status")); - } - } } diff --git a/src/main/java/ru/soune/nocopy/dto/AuthResponse.java b/src/main/java/ru/soune/nocopy/dto/AuthResponse.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/dto/LoginResponse.java b/src/main/java/ru/soune/nocopy/dto/LoginResponse.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/dto/MessageCode.java b/src/main/java/ru/soune/nocopy/dto/MessageCode.java index baa48be..36cd838 100644 --- a/src/main/java/ru/soune/nocopy/dto/MessageCode.java +++ b/src/main/java/ru/soune/nocopy/dto/MessageCode.java @@ -5,10 +5,15 @@ public enum MessageCode { REG_EMAIL_EXISTS(1, "Email already registered"), REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"), INVALID_FIELD(2, "Invalid field"), + INVALID_TOKEN(2, "Invalid token"), + INVALID_ACTION(2, "Invalid action"), + FILE_UPLOAD_ERROR(2, "File upload error"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"), + INCOMPLETE_UPLOAD(2, "Not load all chunks"), MSG_ID_NOT_FOUND(4, "Message id not found"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"), + FILE_NOT_FOUND(4, "File not found"), AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"); private final Integer code; diff --git a/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java b/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/dto/file/ActionResponse.java b/src/main/java/ru/soune/nocopy/dto/file/ActionResponse.java new file mode 100644 index 0000000..f492b6c --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/ActionResponse.java @@ -0,0 +1,17 @@ +package ru.soune.nocopy.dto.file; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ActionResponse { + private List availableActions; + private String action; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/CanceledUploadResponse.java b/src/main/java/ru/soune/nocopy/dto/file/CanceledUploadResponse.java new file mode 100644 index 0000000..99dc43e --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/CanceledUploadResponse.java @@ -0,0 +1,15 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class CanceledUploadResponse { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("message") + private String message; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/ChunkStatusResponse.java b/src/main/java/ru/soune/nocopy/dto/file/ChunkStatusResponse.java new file mode 100644 index 0000000..df78fd8 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/ChunkStatusResponse.java @@ -0,0 +1,28 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +public class ChunkStatusResponse { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("total_chunks") + private Integer totalChunks; + + @JsonProperty("chunk_status") + private Map chunkStatus; + + @JsonProperty("uploaded_chunks") + private Integer uploadedChunks; + + @JsonProperty("missing_chunks") + private Integer missingChunks; +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java b/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java new file mode 100644 index 0000000..09bce18 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/ChunkUploadResponse.java @@ -0,0 +1,25 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChunkUploadResponse { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("chunk_number") + private Integer chunkNumber; + + @JsonProperty("chunk_size") + private Long chunkSize; + + @JsonProperty("message") + private String message; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/CompleteUploadResponse.java b/src/main/java/ru/soune/nocopy/dto/file/CompleteUploadResponse.java new file mode 100644 index 0000000..0275520 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/CompleteUploadResponse.java @@ -0,0 +1,31 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CompleteUploadResponse { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("status") + private String status; + + @JsonProperty("uploaded_chunks") + private Integer uploadedChunks; + + @JsonProperty("total_chunks") + private Integer totalChunks; + + @JsonProperty("message") + private String message; + + @JsonProperty("file_path") + private String filePath; +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileTypesResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileTypesResponse.java new file mode 100644 index 0000000..e4b8af9 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/FileTypesResponse.java @@ -0,0 +1,19 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import ru.soune.nocopy.entity.file.FileType; + +import java.util.List; + +@Data +@Builder +public class FileTypesResponse { + @JsonProperty("file_types") + private List fileTypes; + + @JsonProperty("count") + private Integer count; +} + diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileUploadRequest.java b/src/main/java/ru/soune/nocopy/dto/file/FileUploadRequest.java new file mode 100644 index 0000000..3b72bb4 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/FileUploadRequest.java @@ -0,0 +1,31 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FileUploadRequest { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("file_name") + private String fileName; + + @JsonProperty("file_type") + private String fileType; + + @JsonProperty("extension") + private String extension; + + @JsonProperty("file_size") + private Long fileSize; + + @JsonProperty("chunk_number") + private Integer chunkNumber; + + @JsonProperty("action") + private String action; + + @JsonProperty("token") + private String token; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/InitFileResponse.java b/src/main/java/ru/soune/nocopy/dto/file/InitFileResponse.java new file mode 100644 index 0000000..0c16a41 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/InitFileResponse.java @@ -0,0 +1,30 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@Builder +public class InitFileResponse { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("file_name") + private String fileName; + + @JsonProperty("total_chunks") + private Integer totalChunks; + + @JsonProperty("chunk_size") + private Long chunkSize; + + @JsonProperty("status") + private String status; + + @JsonProperty("field_errors") + private List> fieldErrors; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/RetryUploadResponse.java b/src/main/java/ru/soune/nocopy/dto/file/RetryUploadResponse.java new file mode 100644 index 0000000..33d02de --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/RetryUploadResponse.java @@ -0,0 +1,18 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class RetryUploadResponse { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("message") + private String message; + + @JsonProperty("status") + private String status; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/UploadProgress.java b/src/main/java/ru/soune/nocopy/dto/file/UploadProgress.java new file mode 100644 index 0000000..7fc1ac2 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/UploadProgress.java @@ -0,0 +1,40 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UploadProgress { + @JsonProperty("upload_id") + private String uploadId; + + @JsonProperty("file_name") + private String fileName; + + @JsonProperty("total_chunks") + private Integer totalChunks; + + @JsonProperty("uploaded_chunks") + private Integer uploadedChunks; + + @JsonProperty("status") + private String status; + + @JsonProperty("progress_percentage") + private Integer progressPercentage; + + @JsonProperty("file_path") + private String filePath; + + @JsonProperty("remaining_chunks") + private Integer remainingChunks; + + @JsonProperty("message") + private String message; +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java b/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java index 46a8698..996f0de 100644 --- a/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java @@ -33,6 +33,9 @@ public class FileUploadSession { @Column(name = "file_size") private Long fileSize; + @Column(name = "chunk_size") + private Long chunkSize; + @Column(name = "total_chunks") private Integer totalChunks; @@ -84,9 +87,14 @@ public class FileUploadSession { public void prePersist() { this.createdAt = LocalDateTime.now(); this.expiresAt = LocalDateTime.now().plusHours(24); - this.chunksUploaded = 0; + if (this.chunksUploaded == null) { + this.chunksUploaded = 0; + } if (this.status == null) { this.status = UploadStatus.INITIATED; } + if (this.retryCount == null) { + this.retryCount = 0; + } } -} +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java b/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java b/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java new file mode 100644 index 0000000..26f6dba --- /dev/null +++ b/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java @@ -0,0 +1,243 @@ +package ru.soune.nocopy.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.BindingResult; +import ru.soune.nocopy.dto.*; +import ru.soune.nocopy.dto.file.*; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.exception.*; +import ru.soune.nocopy.handler.validator.FileUploadRequestValidator; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.FileUploadSessionRepository; +import ru.soune.nocopy.service.file.FileUploadService; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FileUploadHandler implements RequestHandler { + private final FileUploadService fileUploadService; + private final AuthTokenRepository authTokenRepository; + private final FileUploadSessionRepository fileUploadSessionRepository; + private final ObjectMapper objectMapper; + private final FileUploadRequestValidator fileUploadRequestValidator; + + @Override + public BaseResponse handle(BaseRequest request) { + try { + FileUploadRequest fileRequest = objectMapper.convertValue( + request.getMessageBody(), FileUploadRequest.class); + + String action = fileRequest.getAction(); + + switch (action) { + case "init": + return handleInitUpload(request, fileRequest); + case "file_types": + return handleGetFileTypes(request); + case "chunks": + return handleGetChunkStatus(request, fileRequest); + case "cancel": + return handleCancelUpload(request, fileRequest); + case "progress": + return handleGetProgress(request, fileRequest); + case "retry": + return handleRetryUpload(request, fileRequest); + default: + ActionResponse response = ActionResponse.builder() + .action(action) + .availableActions(Arrays.asList("init", "file_types", "chunks", "cancel", "progress", "retry")) + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.INVALID_ACTION.getCode(), + "Invalid action: " + action, response); + } + } catch (Exception e) { + log.error("Error in FileUploadHandler", e); + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Handler error: " + e.getMessage(), null); + } + } + + private BaseResponse handleInitUpload(BaseRequest request, FileUploadRequest fileRequest) { + try { + String token = fileRequest.getToken(); + + Optional tokenOptional = authTokenRepository.findByToken(token); + + AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token not found")); + + BindingResult bindingResult = new BeanPropertyBindingResult(fileRequest, "fileRequest"); + fileUploadRequestValidator.validate(fileRequest, bindingResult); + + if (bindingResult.hasErrors()) { + throw new ValidationException(bindingResult, request.getMsgId()); + } + + FileUploadSession session = fileUploadService.initUpload( + authToken.getUser().getId(), + fileRequest.getFileName(), + fileRequest.getFileType(), + fileRequest.getExtension(), + fileRequest.getFileSize()); + + InitFileResponse fileResponse = InitFileResponse.builder() + .uploadId(session.getUploadId()) + .fileName(session.getFileName()) + .totalChunks(session.getTotalChunks()) + .chunkSize(session.getChunkSize()) + .status(session.getStatus().toString()) + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), fileResponse); + + } catch (NotFoundAuthToken e) { + InitFileResponse initFileResponse = InitFileResponse.builder() + .build(); + + throw new NotValidFieldException("Invalid or expired token: " + fileRequest.getToken(), + new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(), + MessageCode.INVALID_TOKEN.getDescription(), initFileResponse)); + } + } + + private BaseResponse handleGetProgress(BaseRequest request, FileUploadRequest fileRequest) { + try { + ru.soune.nocopy.dto.file.UploadProgressResponse progress = + fileUploadService.getUploadProgress(fileRequest.getUploadId()); + + UploadProgressResponse responseDto = UploadProgressResponse.builder() + .uploadId(progress.getUploadId()) + .fileName(progress.getFileName()) + .totalChunks(progress.getTotalChunks()) + .uploadedChunks(progress.getUploadedChunks()) + .status(progress.getStatus()) + .progressPercentage(progress.getProgressPercentage()) + .filePath(progress.getFilePath()) + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), responseDto); + + } catch (UploadSessionNotFoundException e) { + return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(), + MessageCode.FILE_NOT_FOUND.getDescription(), null); + } catch (Exception e) { + log.error("Error getting progress", e); + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), + MessageCode.FILE_UPLOAD_ERROR.getDescription(), null); + } + } + + private BaseResponse handleCancelUpload(BaseRequest request, FileUploadRequest fileRequest) { + try { + fileUploadService.cancelUpload(fileRequest.getUploadId()); + + CanceledUploadResponse response = CanceledUploadResponse.builder() + .uploadId(fileRequest.getUploadId()) + .message("Upload cancelled successfully") + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), + "Upload cancelled successfully", response); + + } catch (UploadSessionNotFoundException e) { + return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(), + "Upload session not found", null); + } catch (FileUploadException e) { + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), e.getMessage(), null); + } catch (Exception e) { + log.error("Error cancelling upload", e); + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to cancel upload", null); + } + } + + private BaseResponse handleRetryUpload(BaseRequest request, FileUploadRequest fileRequest) { + try { + fileUploadService.retryFailedUpload(fileRequest.getUploadId()); + ru.soune.nocopy.dto.file.UploadProgressResponse progress = + fileUploadService.getUploadProgress(fileRequest.getUploadId()); + + RetryUploadResponse response = RetryUploadResponse.builder() + .uploadId(progress.getUploadId()) + .message("Upload retry initiated") + .status(progress.getStatus().toString()) + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), "Upload retry initiated", + response); + + } catch (UploadSessionNotFoundException e) { + return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(), + "Upload session not found", null); + } catch (FileUploadException e) { + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), e.getMessage(), + null); + } catch (Exception e) { + log.error("Error retrying upload", e); + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to retry upload", null); + } + } + + private BaseResponse handleGetChunkStatus(BaseRequest request, FileUploadRequest fileRequest) { + try { + FileUploadSession session = fileUploadSessionRepository.findById(fileRequest.getUploadId()) + .orElseThrow(() -> new UploadSessionNotFoundException(fileRequest.getUploadId())); + + Map chunkStatus = new HashMap<>(); + for (int i = 0; i < session.getTotalChunks(); i++) { + chunkStatus.put("chunk_" + i, session.getChunkPaths().containsKey(i)); + } + + ChunkStatusResponse response = ChunkStatusResponse.builder() + .uploadId(fileRequest.getUploadId()) + .totalChunks(session.getTotalChunks()) + .uploadedChunks(session.getChunksUploaded()) + .missingChunks(session.getTotalChunks() - session.getChunksUploaded()) + .chunkStatus(chunkStatus) + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), response); + + } catch (UploadSessionNotFoundException e) { + return new BaseResponse(request.getMsgId(), MessageCode.FILE_NOT_FOUND.getCode(), + "Upload session not found", null); + } catch (Exception e) { + log.error("Error getting chunk status", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), "Failed to get chunk status", + null); + } + } + + private BaseResponse handleGetFileTypes(BaseRequest request) { + try { + FileTypesResponse response = FileTypesResponse.builder() + .fileTypes(Arrays.asList(FileType.values())) + .count(FileType.values().length) + .build(); + + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), response); + + } catch (Exception e) { + log.error("Error getting file types", e); + return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to get file types", null); + } + } +} diff --git a/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java b/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java new file mode 100644 index 0000000..10737f7 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java @@ -0,0 +1,66 @@ +package ru.soune.nocopy.handler.validator; + +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import ru.soune.nocopy.dto.file.FileUploadRequest; +import ru.soune.nocopy.entity.file.FileType; + +import java.util.Arrays; + +@Component +public class FileUploadRequestValidator implements Validator { + @Override + public boolean supports(Class clazz) { + return FileUploadRequest.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + FileUploadRequest request = (FileUploadRequest) target; + + validateType(request.getFileType(), errors); + validateFileName(request.getFileName(), errors); + validateExtension(request.getExtension(), errors); + } + + private void validateType(String fileType, Errors errors) { + if (fileType == null || fileType.isBlank()) { + errors.rejectValue("fileType", "fileType.required", "File type is required"); + return; + } + + try { + FileType.valueOf(fileType.toUpperCase()); + } catch (IllegalArgumentException e) { + errors.rejectValue("fileType", "fileType.invalid", + "Invalid file type. Valid types: " + Arrays.toString(FileType.values())); + } + } + + private void validateFileName(String fileName, Errors errors) { + if (fileName == null || fileName.isBlank()) { + errors.rejectValue("fileName", "fileName.required", "File name is required"); + return; + } + + if (fileName.length() > 255) { + errors.rejectValue("fileName", "fileName.too.long", + "File name too long, max 255 characters"); + } + } + + private void validateExtension(String extension, Errors errors) { + if (extension == null || extension.isBlank()) { + errors.rejectValue("extension", "extension.required", "Extension is required"); + return; + } + + if (!extension.startsWith(".")) { + errors.rejectValue("extension", "extension.invalid.format", + "Extension must start with '.'"); + return; + } + } + +} diff --git a/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java index 49e5aca..a1eebb4 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java @@ -510,8 +510,8 @@ public class FileUploadServiceImpl implements FileUploadService { FileUploadSession session = sessionRepository.findById(uploadId) .orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); - if (session.getStatus() == UploadStatus.COMPLETED) { - throw new FileUploadException("Cannot cancel completed upload"); + if (session.getStatus() == UploadStatus.COMPLETED || session.getStatus() == UploadStatus.CANCELLED) { + throw new FileUploadException("Cannot cancel completed or cancelled upload"); } session.setStatus(UploadStatus.CANCELLED); @@ -539,6 +539,7 @@ public class FileUploadServiceImpl implements FileUploadService { session.setStatus(UploadStatus.UPLOADING); session.setRetryCount(session.getRetryCount() + 1); session.setLastError(null); + sessionRepository.save(session); if (session.getChunksUploaded().equals(session.getTotalChunks())) { From 7e2dd4a6546041e1251697fb4408fc94b4ab8367 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 17 Dec 2025 22:37:46 +0700 Subject: [PATCH 09/13] fix path --- .../NoCopyApplication.java | 2 +- .../configuration/ApplicationConfig.java | 2 +- .../configuration/JacksonConfig.java | 2 +- .../configuration/JpaConfiguration.java | 2 +- .../configuration/SubscriptionConfig.java | 2 +- .../configuration/file/AsyncConfig.java | 2 +- .../controller/AuthController.java | 18 ++++++------ .../controller/HealtCheckController.java | 3 +- .../controller/UserContentController.java | 2 +- .../controller/UserController.java | 24 ++++++++-------- .../file/FileDownloadController.java | 12 ++++---- .../controller/file/FileEntityController.java | 16 +++++------ .../controller/file/FileUploadController.java | 28 +++++++++---------- .../{no_copy => nocopy}/dto/AuthResponse.java | 2 +- .../dto/ChangePasswordRequest.java | 2 +- .../{no_copy => nocopy}/dto/LoginRequest.java | 2 +- .../dto/LoginResponse.java | 3 +- .../dto/RegisterRequest.java | 6 ++-- .../dto/UserContentDTO.java | 2 +- .../dto/UserContentRequest.java | 2 +- .../dto/UserContentResponse.java | 6 ++-- .../dto/UserContentUpdateRequest.java | 2 +- .../{no_copy => nocopy}/dto/UserDTO.java | 6 ++-- .../{no_copy => nocopy}/dto/UserRequest.java | 5 ++-- .../dto/file/FileApiResponse.java | 2 +- .../dto/file/FileEntityResponse.java | 4 +-- .../dto/file/FileListResponse.java | 2 +- .../dto/file/InitUploadRequest.java | 2 +- .../dto/file/UploadProgressResponse.java | 6 ++-- .../{no_copy => nocopy}/entity/AuthToken.java | 2 +- .../entity/BaseEntity.java | 2 +- .../entity/ContentStatus.java | 2 +- .../entity/GenderType.java | 2 +- .../entity/ImageProtection.java | 2 +- .../entity/SubscriptionType.java | 2 +- .../{no_copy => nocopy}/entity/User.java | 2 +- .../entity/UserAction.java | 2 +- .../entity/UserContent.java | 4 +-- .../{no_copy => nocopy}/entity/Violation.java | 2 +- .../entity/file/FileEntity.java | 2 +- .../entity/file/FileStatus.java | 2 +- .../entity/file/FileType.java | 2 +- .../entity/file/FileUploadSession.java | 2 +- .../entity/file/UploadStatus.java | 2 +- .../exception/ChunkSizeExceededException.java | 2 +- .../exception/ContentNotFoundException.java | 2 +- .../FileEntityNotFoundException.java | 2 +- .../exception/FileIntegrityException.java | 2 +- .../exception/FileUploadException.java | 2 +- .../exception/InvalidPasswordException.java | 2 +- .../exception/InvalidUserEmail.java | 2 +- .../exception/NotFoundAuthToken.java | 2 +- .../NotValidationPasswordException.java | 2 +- .../exception/TokenNotFoundException.java | 2 +- .../UploadSessionNotFoundException.java | 2 +- .../exception/UserAlreadyExistsException.java | 2 +- .../exception/UserNotFoundException.java | 2 +- .../handler/GlobalExceptionHandler.java | 4 +-- .../handler/JwtTokenProvider.java | 2 +- .../mapper/UserContentMapper.java | 6 ++-- .../mapper/UserMapper.java | 6 ++-- .../repository/AuthTokenRepository.java | 4 +-- .../repository/FileEntityRepository.java | 6 ++-- .../FileUploadSessionRepository.java | 6 ++-- .../repository/UserContentRepository.java | 6 ++-- .../repository/UserRepository.java | 4 +-- .../service/AuthService.java | 21 +++++++------- .../service/UserContentService.java | 22 +++++++-------- .../service/UserService.java | 18 ++++++------ .../service/file/FileCleanupService.java | 2 +- .../service/file/FileEntityService.java | 16 +++++------ .../service/file/FileUploadService.java | 6 ++-- .../service/file/FileUploadServiceImpl.java | 16 +++++------ .../service/AuthServiceTest.java | 20 ++++++------- 74 files changed, 194 insertions(+), 198 deletions(-) rename src/main/java/ru/soune/{no_copy => nocopy}/NoCopyApplication.java (91%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/ApplicationConfig.java (92%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/JacksonConfig.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/JpaConfiguration.java (83%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/SubscriptionConfig.java (96%) rename src/main/java/ru/soune/{no_copy => nocopy}/configuration/file/AsyncConfig.java (94%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/AuthController.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/HealtCheckController.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/UserContentController.java (87%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/UserController.java (87%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/file/FileDownloadController.java (95%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/file/FileEntityController.java (95%) rename src/main/java/ru/soune/{no_copy => nocopy}/controller/file/FileUploadController.java (91%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/AuthResponse.java (85%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/ChangePasswordRequest.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/LoginRequest.java (94%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/LoginResponse.java (76%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/RegisterRequest.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentDTO.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentRequest.java (93%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentResponse.java (70%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserContentUpdateRequest.java (93%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserDTO.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/UserRequest.java (83%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/FileApiResponse.java (96%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/FileEntityResponse.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/FileListResponse.java (92%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/InitUploadRequest.java (94%) rename src/main/java/ru/soune/{no_copy => nocopy}/dto/file/UploadProgressResponse.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/AuthToken.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/BaseEntity.java (95%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/ContentStatus.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/GenderType.java (57%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/ImageProtection.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/SubscriptionType.java (76%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/User.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/UserAction.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/UserContent.java (96%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/Violation.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/FileEntity.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/FileStatus.java (67%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/FileType.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/FileUploadSession.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/entity/file/UploadStatus.java (72%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/ChunkSizeExceededException.java (86%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/ContentNotFoundException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/FileEntityNotFoundException.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/FileIntegrityException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/FileUploadException.java (87%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/InvalidPasswordException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/InvalidUserEmail.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/NotFoundAuthToken.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/NotValidationPasswordException.java (82%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/TokenNotFoundException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/UploadSessionNotFoundException.java (84%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/UserAlreadyExistsException.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/exception/UserNotFoundException.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/handler/GlobalExceptionHandler.java (97%) rename src/main/java/ru/soune/{no_copy => nocopy}/handler/JwtTokenProvider.java (73%) rename src/main/java/ru/soune/{no_copy => nocopy}/mapper/UserContentMapper.java (79%) rename src/main/java/ru/soune/{no_copy => nocopy}/mapper/UserMapper.java (77%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/AuthTokenRepository.java (83%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/FileEntityRepository.java (89%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/FileUploadSessionRepository.java (88%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/UserContentRepository.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/repository/UserRepository.java (77%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/AuthService.java (84%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/UserContentService.java (81%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/UserService.java (86%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileCleanupService.java (98%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileEntityService.java (95%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileUploadService.java (80%) rename src/main/java/ru/soune/{no_copy => nocopy}/service/file/FileUploadServiceImpl.java (98%) rename src/test/java/ru/soune/{no_copy => nocopy}/service/AuthServiceTest.java (89%) diff --git a/src/main/java/ru/soune/no_copy/NoCopyApplication.java b/src/main/java/ru/soune/nocopy/NoCopyApplication.java similarity index 91% rename from src/main/java/ru/soune/no_copy/NoCopyApplication.java rename to src/main/java/ru/soune/nocopy/NoCopyApplication.java index 74bec20..fd6b0f2 100644 --- a/src/main/java/ru/soune/no_copy/NoCopyApplication.java +++ b/src/main/java/ru/soune/nocopy/NoCopyApplication.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy; +package ru.soune.nocopy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/ru/soune/no_copy/configuration/ApplicationConfig.java b/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java similarity index 92% rename from src/main/java/ru/soune/no_copy/configuration/ApplicationConfig.java rename to src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java index 8b0409b..85ae0b8 100644 --- a/src/main/java/ru/soune/no_copy/configuration/ApplicationConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/ApplicationConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/ru/soune/no_copy/configuration/JacksonConfig.java b/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java similarity index 97% rename from src/main/java/ru/soune/no_copy/configuration/JacksonConfig.java rename to src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java index 0f726fb..99c5c00 100644 --- a/src/main/java/ru/soune/no_copy/configuration/JacksonConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/JacksonConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; diff --git a/src/main/java/ru/soune/no_copy/configuration/JpaConfiguration.java b/src/main/java/ru/soune/nocopy/configuration/JpaConfiguration.java similarity index 83% rename from src/main/java/ru/soune/no_copy/configuration/JpaConfiguration.java rename to src/main/java/ru/soune/nocopy/configuration/JpaConfiguration.java index 56ae18b..f62ccde 100644 --- a/src/main/java/ru/soune/no_copy/configuration/JpaConfiguration.java +++ b/src/main/java/ru/soune/nocopy/configuration/JpaConfiguration.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/ru/soune/no_copy/configuration/SubscriptionConfig.java b/src/main/java/ru/soune/nocopy/configuration/SubscriptionConfig.java similarity index 96% rename from src/main/java/ru/soune/no_copy/configuration/SubscriptionConfig.java rename to src/main/java/ru/soune/nocopy/configuration/SubscriptionConfig.java index 7eb41ec..198a544 100644 --- a/src/main/java/ru/soune/no_copy/configuration/SubscriptionConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/SubscriptionConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration; +package ru.soune.nocopy.configuration; import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java b/src/main/java/ru/soune/nocopy/configuration/file/AsyncConfig.java similarity index 94% rename from src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java rename to src/main/java/ru/soune/nocopy/configuration/file/AsyncConfig.java index bd75b72..6882014 100644 --- a/src/main/java/ru/soune/no_copy/configuration/file/AsyncConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/file/AsyncConfig.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.configuration.file; +package ru.soune.nocopy.configuration.file; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/ru/soune/no_copy/controller/AuthController.java b/src/main/java/ru/soune/nocopy/controller/AuthController.java similarity index 79% rename from src/main/java/ru/soune/no_copy/controller/AuthController.java rename to src/main/java/ru/soune/nocopy/controller/AuthController.java index 764b1a8..e7d46f0 100644 --- a/src/main/java/ru/soune/no_copy/controller/AuthController.java +++ b/src/main/java/ru/soune/nocopy/controller/AuthController.java @@ -1,17 +1,17 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.no_copy.dto.AuthResponse; -import ru.soune.no_copy.dto.LoginRequest; -import ru.soune.no_copy.dto.LoginResponse; -import ru.soune.no_copy.dto.RegisterRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.exception.TokenNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.service.AuthService; +import ru.soune.nocopy.dto.AuthResponse; +import ru.soune.nocopy.dto.LoginRequest; +import ru.soune.nocopy.dto.LoginResponse; +import ru.soune.nocopy.dto.RegisterRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.exception.TokenNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.AuthService; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/controller/HealtCheckController.java b/src/main/java/ru/soune/nocopy/controller/HealtCheckController.java similarity index 79% rename from src/main/java/ru/soune/no_copy/controller/HealtCheckController.java rename to src/main/java/ru/soune/nocopy/controller/HealtCheckController.java index 33b6ab9..27cedd1 100644 --- a/src/main/java/ru/soune/no_copy/controller/HealtCheckController.java +++ b/src/main/java/ru/soune/nocopy/controller/HealtCheckController.java @@ -1,6 +1,5 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; -import org.apache.coyote.Response; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/ru/soune/no_copy/controller/UserContentController.java b/src/main/java/ru/soune/nocopy/controller/UserContentController.java similarity index 87% rename from src/main/java/ru/soune/no_copy/controller/UserContentController.java rename to src/main/java/ru/soune/nocopy/controller/UserContentController.java index ea25e8f..51a4d5c 100644 --- a/src/main/java/ru/soune/no_copy/controller/UserContentController.java +++ b/src/main/java/ru/soune/nocopy/controller/UserContentController.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/ru/soune/no_copy/controller/UserController.java b/src/main/java/ru/soune/nocopy/controller/UserController.java similarity index 87% rename from src/main/java/ru/soune/no_copy/controller/UserController.java rename to src/main/java/ru/soune/nocopy/controller/UserController.java index 9f23605..39efe8e 100644 --- a/src/main/java/ru/soune/no_copy/controller/UserController.java +++ b/src/main/java/ru/soune/nocopy/controller/UserController.java @@ -1,19 +1,19 @@ -package ru.soune.no_copy.controller; +package ru.soune.nocopy.controller; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.no_copy.dto.ChangePasswordRequest; -import ru.soune.no_copy.dto.UserDTO; -import ru.soune.no_copy.dto.UserRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.InvalidUserEmail; -import ru.soune.no_copy.exception.NotFoundAuthToken; -import ru.soune.no_copy.mapper.UserMapper; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.UserRepository; -import ru.soune.no_copy.service.UserService; +import ru.soune.nocopy.dto.ChangePasswordRequest; +import ru.soune.nocopy.dto.UserDTO; +import ru.soune.nocopy.dto.UserRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.InvalidUserEmail; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.mapper.UserMapper; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; +import ru.soune.nocopy.service.UserService; import java.util.List; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java similarity index 95% rename from src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java rename to src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java index 6be2c46..6339094 100644 --- a/src/main/java/ru/soune/no_copy/controller/file/FileDownloadController.java +++ b/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.controller.file; +package ru.soune.nocopy.controller.file; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -8,11 +8,11 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.no_copy.dto.file.FileEntityResponse; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.exception.NotFoundAuthToken; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.service.file.FileEntityService; +import ru.soune.nocopy.dto.file.FileEntityResponse; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.file.FileEntityService; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java b/src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java similarity index 95% rename from src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java rename to src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java index 77987a3..25d19d1 100644 --- a/src/main/java/ru/soune/no_copy/controller/file/FileEntityController.java +++ b/src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java @@ -1,16 +1,16 @@ -package ru.soune.no_copy.controller.file; +package ru.soune.nocopy.controller.file; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.soune.no_copy.dto.file.FileApiResponse; -import ru.soune.no_copy.dto.file.FileEntityResponse; -import ru.soune.no_copy.dto.file.FileListResponse; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.exception.NotFoundAuthToken; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.service.file.FileEntityService; +import ru.soune.nocopy.dto.file.FileApiResponse; +import ru.soune.nocopy.dto.file.FileEntityResponse; +import ru.soune.nocopy.dto.file.FileListResponse; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.file.FileEntityService; @Slf4j @RestController diff --git a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java similarity index 91% rename from src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java rename to src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java index 4f5d1ab..6eab4bb 100644 --- a/src/main/java/ru/soune/no_copy/controller/file/FileUploadController.java +++ b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.controller.file; +package ru.soune.nocopy.controller.file; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -7,19 +7,19 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.FileApiResponse; -import ru.soune.no_copy.dto.file.InitUploadRequest; -import ru.soune.no_copy.dto.file.UploadProgressResponse; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.file.FileType; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; -import ru.soune.no_copy.exception.FileUploadException; -import ru.soune.no_copy.exception.NotFoundAuthToken; -import ru.soune.no_copy.exception.UploadSessionNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.FileUploadSessionRepository; -import ru.soune.no_copy.service.file.FileUploadService; +import ru.soune.nocopy.dto.file.FileApiResponse; +import ru.soune.nocopy.dto.file.InitUploadRequest; +import ru.soune.nocopy.dto.file.UploadProgressResponse; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; +import ru.soune.nocopy.exception.FileUploadException; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.exception.UploadSessionNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.FileUploadSessionRepository; +import ru.soune.nocopy.service.file.FileUploadService; import java.util.*; diff --git a/src/main/java/ru/soune/no_copy/dto/AuthResponse.java b/src/main/java/ru/soune/nocopy/dto/AuthResponse.java similarity index 85% rename from src/main/java/ru/soune/no_copy/dto/AuthResponse.java rename to src/main/java/ru/soune/nocopy/dto/AuthResponse.java index c39301a..168e09a 100644 --- a/src/main/java/ru/soune/no_copy/dto/AuthResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/AuthResponse.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/ChangePasswordRequest.java b/src/main/java/ru/soune/nocopy/dto/ChangePasswordRequest.java similarity index 89% rename from src/main/java/ru/soune/no_copy/dto/ChangePasswordRequest.java rename to src/main/java/ru/soune/nocopy/dto/ChangePasswordRequest.java index aff7140..204db58 100644 --- a/src/main/java/ru/soune/no_copy/dto/ChangePasswordRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/ChangePasswordRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/ru/soune/no_copy/dto/LoginRequest.java b/src/main/java/ru/soune/nocopy/dto/LoginRequest.java similarity index 94% rename from src/main/java/ru/soune/no_copy/dto/LoginRequest.java rename to src/main/java/ru/soune/nocopy/dto/LoginRequest.java index c85fe97..d64fa8b 100644 --- a/src/main/java/ru/soune/no_copy/dto/LoginRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/LoginRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/soune/no_copy/dto/LoginResponse.java b/src/main/java/ru/soune/nocopy/dto/LoginResponse.java similarity index 76% rename from src/main/java/ru/soune/no_copy/dto/LoginResponse.java rename to src/main/java/ru/soune/nocopy/dto/LoginResponse.java index 5fe7d73..5de74ea 100644 --- a/src/main/java/ru/soune/no_copy/dto/LoginResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/LoginResponse.java @@ -1,8 +1,7 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.AllArgsConstructor; import lombok.Data; -import ru.soune.no_copy.entity.User; @Data @AllArgsConstructor diff --git a/src/main/java/ru/soune/no_copy/dto/RegisterRequest.java b/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java similarity index 88% rename from src/main/java/ru/soune/no_copy/dto/RegisterRequest.java rename to src/main/java/ru/soune/nocopy/dto/RegisterRequest.java index e08f39b..8d9a88f 100644 --- a/src/main/java/ru/soune/no_copy/dto/RegisterRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Email; @@ -6,8 +6,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Size; import org.springframework.format.annotation.DateTimeFormat; -import ru.soune.no_copy.entity.GenderType; -import ru.soune.no_copy.entity.SubscriptionType; +import ru.soune.nocopy.entity.GenderType; +import ru.soune.nocopy.entity.SubscriptionType; import java.time.LocalDate; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java b/src/main/java/ru/soune/nocopy/dto/UserContentDTO.java similarity index 88% rename from src/main/java/ru/soune/no_copy/dto/UserContentDTO.java rename to src/main/java/ru/soune/nocopy/dto/UserContentDTO.java index 4c9c6f0..934bfda 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentDTO.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentDTO.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.Data; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentRequest.java b/src/main/java/ru/soune/nocopy/dto/UserContentRequest.java similarity index 93% rename from src/main/java/ru/soune/no_copy/dto/UserContentRequest.java rename to src/main/java/ru/soune/nocopy/dto/UserContentRequest.java index 06ef01e..bd28100 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java b/src/main/java/ru/soune/nocopy/dto/UserContentResponse.java similarity index 70% rename from src/main/java/ru/soune/no_copy/dto/UserContentResponse.java rename to src/main/java/ru/soune/nocopy/dto/UserContentResponse.java index d2a817c..8c14615 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentResponse.java @@ -1,7 +1,7 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; -import ru.soune.no_copy.entity.ContentStatus; -import ru.soune.no_copy.entity.file.FileType; +import ru.soune.nocopy.entity.ContentStatus; +import ru.soune.nocopy.entity.file.FileType; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/UserContentUpdateRequest.java b/src/main/java/ru/soune/nocopy/dto/UserContentUpdateRequest.java similarity index 93% rename from src/main/java/ru/soune/no_copy/dto/UserContentUpdateRequest.java rename to src/main/java/ru/soune/nocopy/dto/UserContentUpdateRequest.java index 8955969..7595b21 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserContentUpdateRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserContentUpdateRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/ru/soune/no_copy/dto/UserDTO.java b/src/main/java/ru/soune/nocopy/dto/UserDTO.java similarity index 81% rename from src/main/java/ru/soune/no_copy/dto/UserDTO.java rename to src/main/java/ru/soune/nocopy/dto/UserDTO.java index ba5b278..45c19ee 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserDTO.java +++ b/src/main/java/ru/soune/nocopy/dto/UserDTO.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.soune.no_copy.entity.GenderType; -import ru.soune.no_copy.entity.SubscriptionType; +import ru.soune.nocopy.entity.GenderType; +import ru.soune.nocopy.entity.SubscriptionType; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/UserRequest.java b/src/main/java/ru/soune/nocopy/dto/UserRequest.java similarity index 83% rename from src/main/java/ru/soune/no_copy/dto/UserRequest.java rename to src/main/java/ru/soune/nocopy/dto/UserRequest.java index db8c8fe..c5f1662 100644 --- a/src/main/java/ru/soune/no_copy/dto/UserRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/UserRequest.java @@ -1,14 +1,13 @@ -package ru.soune.no_copy.dto; +package ru.soune.nocopy.dto; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Size; import lombok.Data; -import ru.soune.no_copy.entity.GenderType; +import ru.soune.nocopy.entity.GenderType; import java.time.LocalDate; -import java.time.LocalDateTime; @Data public class UserRequest { diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java similarity index 96% rename from src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java index d271a1c..9feb546 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/FileApiResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java similarity index 89% rename from src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java index ca96341..3e89725 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/FileEntityResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java @@ -1,10 +1,10 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.soune.no_copy.entity.file.FileStatus; +import ru.soune.nocopy.entity.file.FileStatus; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java similarity index 92% rename from src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java index 8c2c503..5815a03 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/FileListResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java b/src/main/java/ru/soune/nocopy/dto/file/InitUploadRequest.java similarity index 94% rename from src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java rename to src/main/java/ru/soune/nocopy/dto/file/InitUploadRequest.java index 044c731..5f43124 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/InitUploadRequest.java +++ b/src/main/java/ru/soune/nocopy/dto/file/InitUploadRequest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java b/src/main/java/ru/soune/nocopy/dto/file/UploadProgressResponse.java similarity index 88% rename from src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java rename to src/main/java/ru/soune/nocopy/dto/file/UploadProgressResponse.java index be77f03..f5e343d 100644 --- a/src/main/java/ru/soune/no_copy/dto/file/UploadProgressResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/UploadProgressResponse.java @@ -1,12 +1,12 @@ -package ru.soune.no_copy.dto.file; +package ru.soune.nocopy.dto.file; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; @Data @Builder diff --git a/src/main/java/ru/soune/no_copy/entity/AuthToken.java b/src/main/java/ru/soune/nocopy/entity/AuthToken.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/AuthToken.java rename to src/main/java/ru/soune/nocopy/entity/AuthToken.java index 6418073..63cf34a 100644 --- a/src/main/java/ru/soune/no_copy/entity/AuthToken.java +++ b/src/main/java/ru/soune/nocopy/entity/AuthToken.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/BaseEntity.java b/src/main/java/ru/soune/nocopy/entity/BaseEntity.java similarity index 95% rename from src/main/java/ru/soune/no_copy/entity/BaseEntity.java rename to src/main/java/ru/soune/nocopy/entity/BaseEntity.java index b51c525..2743abf 100644 --- a/src/main/java/ru/soune/no_copy/entity/BaseEntity.java +++ b/src/main/java/ru/soune/nocopy/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/ContentStatus.java b/src/main/java/ru/soune/nocopy/entity/ContentStatus.java similarity index 89% rename from src/main/java/ru/soune/no_copy/entity/ContentStatus.java rename to src/main/java/ru/soune/nocopy/entity/ContentStatus.java index d97f66c..c2dfa41 100644 --- a/src/main/java/ru/soune/no_copy/entity/ContentStatus.java +++ b/src/main/java/ru/soune/nocopy/entity/ContentStatus.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/GenderType.java b/src/main/java/ru/soune/nocopy/entity/GenderType.java similarity index 57% rename from src/main/java/ru/soune/no_copy/entity/GenderType.java rename to src/main/java/ru/soune/nocopy/entity/GenderType.java index e85f0f2..580d825 100644 --- a/src/main/java/ru/soune/no_copy/entity/GenderType.java +++ b/src/main/java/ru/soune/nocopy/entity/GenderType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; public enum GenderType { MALE, FEMALE diff --git a/src/main/java/ru/soune/no_copy/entity/ImageProtection.java b/src/main/java/ru/soune/nocopy/entity/ImageProtection.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/ImageProtection.java rename to src/main/java/ru/soune/nocopy/entity/ImageProtection.java index 79a8c8d..eef9e95 100644 --- a/src/main/java/ru/soune/no_copy/entity/ImageProtection.java +++ b/src/main/java/ru/soune/nocopy/entity/ImageProtection.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/SubscriptionType.java b/src/main/java/ru/soune/nocopy/entity/SubscriptionType.java similarity index 76% rename from src/main/java/ru/soune/no_copy/entity/SubscriptionType.java rename to src/main/java/ru/soune/nocopy/entity/SubscriptionType.java index 4c78049..952722e 100644 --- a/src/main/java/ru/soune/no_copy/entity/SubscriptionType.java +++ b/src/main/java/ru/soune/nocopy/entity/SubscriptionType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/User.java b/src/main/java/ru/soune/nocopy/entity/User.java similarity index 98% rename from src/main/java/ru/soune/no_copy/entity/User.java rename to src/main/java/ru/soune/nocopy/entity/User.java index 63a23f2..a4fba7d 100644 --- a/src/main/java/ru/soune/no_copy/entity/User.java +++ b/src/main/java/ru/soune/nocopy/entity/User.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/soune/no_copy/entity/UserAction.java b/src/main/java/ru/soune/nocopy/entity/UserAction.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/UserAction.java rename to src/main/java/ru/soune/nocopy/entity/UserAction.java index d9d22ef..661ab3a 100644 --- a/src/main/java/ru/soune/no_copy/entity/UserAction.java +++ b/src/main/java/ru/soune/nocopy/entity/UserAction.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/UserContent.java b/src/main/java/ru/soune/nocopy/entity/UserContent.java similarity index 96% rename from src/main/java/ru/soune/no_copy/entity/UserContent.java rename to src/main/java/ru/soune/nocopy/entity/UserContent.java index a6603c1..1691926 100644 --- a/src/main/java/ru/soune/no_copy/entity/UserContent.java +++ b/src/main/java/ru/soune/nocopy/entity/UserContent.java @@ -1,10 +1,10 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import ru.soune.no_copy.entity.file.FileType; +import ru.soune.nocopy.entity.file.FileType; import java.time.LocalDateTime; diff --git a/src/main/java/ru/soune/no_copy/entity/Violation.java b/src/main/java/ru/soune/nocopy/entity/Violation.java similarity index 97% rename from src/main/java/ru/soune/no_copy/entity/Violation.java rename to src/main/java/ru/soune/nocopy/entity/Violation.java index 6e1768c..dd504bd 100644 --- a/src/main/java/ru/soune/no_copy/entity/Violation.java +++ b/src/main/java/ru/soune/nocopy/entity/Violation.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity; +package ru.soune.nocopy.entity; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileEntity.java b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java similarity index 98% rename from src/main/java/ru/soune/no_copy/entity/file/FileEntity.java rename to src/main/java/ru/soune/nocopy/entity/file/FileEntity.java index abe0e9d..ffdd985 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileEntity.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileStatus.java b/src/main/java/ru/soune/nocopy/entity/file/FileStatus.java similarity index 67% rename from src/main/java/ru/soune/no_copy/entity/file/FileStatus.java rename to src/main/java/ru/soune/nocopy/entity/file/FileStatus.java index e84fc00..8e96247 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileStatus.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileStatus.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; public enum FileStatus { ACTIVE, diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileType.java b/src/main/java/ru/soune/nocopy/entity/file/FileType.java similarity index 88% rename from src/main/java/ru/soune/no_copy/entity/file/FileType.java rename to src/main/java/ru/soune/nocopy/entity/file/FileType.java index dd7f6ab..e8d53f7 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileType.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileType.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; import lombok.Getter; diff --git a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java b/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java similarity index 98% rename from src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java rename to src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java index 38b0bd9..46a8698 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/FileUploadSession.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileUploadSession.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java b/src/main/java/ru/soune/nocopy/entity/file/UploadStatus.java similarity index 72% rename from src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java rename to src/main/java/ru/soune/nocopy/entity/file/UploadStatus.java index d1d3418..577413c 100644 --- a/src/main/java/ru/soune/no_copy/entity/file/UploadStatus.java +++ b/src/main/java/ru/soune/nocopy/entity/file/UploadStatus.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.entity.file; +package ru.soune.nocopy.entity.file; public enum UploadStatus { INITIATED, diff --git a/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java b/src/main/java/ru/soune/nocopy/exception/ChunkSizeExceededException.java similarity index 86% rename from src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java rename to src/main/java/ru/soune/nocopy/exception/ChunkSizeExceededException.java index e45f12b..a19cd95 100644 --- a/src/main/java/ru/soune/no_copy/exception/ChunkSizeExceededException.java +++ b/src/main/java/ru/soune/nocopy/exception/ChunkSizeExceededException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class ChunkSizeExceededException extends RuntimeException { public ChunkSizeExceededException(long actualSize, long maxSize) { diff --git a/src/main/java/ru/soune/no_copy/exception/ContentNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/ContentNotFoundException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/ContentNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/ContentNotFoundException.java index af0f040..75b8af7 100644 --- a/src/main/java/ru/soune/no_copy/exception/ContentNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/ContentNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class ContentNotFoundException extends RuntimeException { public ContentNotFoundException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/FileEntityNotFoundException.java similarity index 89% rename from src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/FileEntityNotFoundException.java index 8b5fd82..2924414 100644 --- a/src/main/java/ru/soune/no_copy/exception/FileEntityNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/FileEntityNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class FileEntityNotFoundException extends RuntimeException { public FileEntityNotFoundException(String fileId) { diff --git a/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java b/src/main/java/ru/soune/nocopy/exception/FileIntegrityException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java rename to src/main/java/ru/soune/nocopy/exception/FileIntegrityException.java index 2fece21..bb10cb9 100644 --- a/src/main/java/ru/soune/no_copy/exception/FileIntegrityException.java +++ b/src/main/java/ru/soune/nocopy/exception/FileIntegrityException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class FileIntegrityException extends RuntimeException { public FileIntegrityException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/FileUploadException.java b/src/main/java/ru/soune/nocopy/exception/FileUploadException.java similarity index 87% rename from src/main/java/ru/soune/no_copy/exception/FileUploadException.java rename to src/main/java/ru/soune/nocopy/exception/FileUploadException.java index 6c74b1d..5a38031 100644 --- a/src/main/java/ru/soune/no_copy/exception/FileUploadException.java +++ b/src/main/java/ru/soune/nocopy/exception/FileUploadException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class FileUploadException extends RuntimeException { public FileUploadException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/InvalidPasswordException.java b/src/main/java/ru/soune/nocopy/exception/InvalidPasswordException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/InvalidPasswordException.java rename to src/main/java/ru/soune/nocopy/exception/InvalidPasswordException.java index b9f2820..72a43e9 100644 --- a/src/main/java/ru/soune/no_copy/exception/InvalidPasswordException.java +++ b/src/main/java/ru/soune/nocopy/exception/InvalidPasswordException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class InvalidPasswordException extends RuntimeException { public InvalidPasswordException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/InvalidUserEmail.java b/src/main/java/ru/soune/nocopy/exception/InvalidUserEmail.java similarity index 79% rename from src/main/java/ru/soune/no_copy/exception/InvalidUserEmail.java rename to src/main/java/ru/soune/nocopy/exception/InvalidUserEmail.java index 7ce203b..850a7f1 100644 --- a/src/main/java/ru/soune/no_copy/exception/InvalidUserEmail.java +++ b/src/main/java/ru/soune/nocopy/exception/InvalidUserEmail.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class InvalidUserEmail extends RuntimeException { public InvalidUserEmail(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/NotFoundAuthToken.java b/src/main/java/ru/soune/nocopy/exception/NotFoundAuthToken.java similarity index 79% rename from src/main/java/ru/soune/no_copy/exception/NotFoundAuthToken.java rename to src/main/java/ru/soune/nocopy/exception/NotFoundAuthToken.java index 7a61eea..473f486 100644 --- a/src/main/java/ru/soune/no_copy/exception/NotFoundAuthToken.java +++ b/src/main/java/ru/soune/nocopy/exception/NotFoundAuthToken.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class NotFoundAuthToken extends RuntimeException { public NotFoundAuthToken(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/NotValidationPasswordException.java b/src/main/java/ru/soune/nocopy/exception/NotValidationPasswordException.java similarity index 82% rename from src/main/java/ru/soune/no_copy/exception/NotValidationPasswordException.java rename to src/main/java/ru/soune/nocopy/exception/NotValidationPasswordException.java index 6e24040..48a2fe5 100644 --- a/src/main/java/ru/soune/no_copy/exception/NotValidationPasswordException.java +++ b/src/main/java/ru/soune/nocopy/exception/NotValidationPasswordException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class NotValidationPasswordException extends RuntimeException { public NotValidationPasswordException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/TokenNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/TokenNotFoundException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/TokenNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/TokenNotFoundException.java index d2e526b..b934cf4 100644 --- a/src/main/java/ru/soune/no_copy/exception/TokenNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/TokenNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class TokenNotFoundException extends RuntimeException { public TokenNotFoundException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/UploadSessionNotFoundException.java similarity index 84% rename from src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/UploadSessionNotFoundException.java index bfc4bfc..35b0eb5 100644 --- a/src/main/java/ru/soune/no_copy/exception/UploadSessionNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/UploadSessionNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class UploadSessionNotFoundException extends RuntimeException { public UploadSessionNotFoundException(String uploadId) { diff --git a/src/main/java/ru/soune/no_copy/exception/UserAlreadyExistsException.java b/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java similarity index 81% rename from src/main/java/ru/soune/no_copy/exception/UserAlreadyExistsException.java rename to src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java index 8815885..f390b6c 100644 --- a/src/main/java/ru/soune/no_copy/exception/UserAlreadyExistsException.java +++ b/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class UserAlreadyExistsException extends RuntimeException { public UserAlreadyExistsException(String message) { diff --git a/src/main/java/ru/soune/no_copy/exception/UserNotFoundException.java b/src/main/java/ru/soune/nocopy/exception/UserNotFoundException.java similarity index 80% rename from src/main/java/ru/soune/no_copy/exception/UserNotFoundException.java rename to src/main/java/ru/soune/nocopy/exception/UserNotFoundException.java index a8d8b03..919ff70 100644 --- a/src/main/java/ru/soune/no_copy/exception/UserNotFoundException.java +++ b/src/main/java/ru/soune/nocopy/exception/UserNotFoundException.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.exception; +package ru.soune.nocopy.exception; public class UserNotFoundException extends RuntimeException { public UserNotFoundException(String message) { diff --git a/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java b/src/main/java/ru/soune/nocopy/handler/GlobalExceptionHandler.java similarity index 97% rename from src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java rename to src/main/java/ru/soune/nocopy/handler/GlobalExceptionHandler.java index c3a02b2..84d4b44 100644 --- a/src/main/java/ru/soune/no_copy/handler/GlobalExceptionHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/GlobalExceptionHandler.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.handler; +package ru.soune.nocopy.handler; import lombok.AllArgsConstructor; import org.springframework.context.support.DefaultMessageSourceResolvable; @@ -8,7 +8,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; -import ru.soune.no_copy.exception.*; +import ru.soune.nocopy.exception.*; import java.util.Map; diff --git a/src/main/java/ru/soune/no_copy/handler/JwtTokenProvider.java b/src/main/java/ru/soune/nocopy/handler/JwtTokenProvider.java similarity index 73% rename from src/main/java/ru/soune/no_copy/handler/JwtTokenProvider.java rename to src/main/java/ru/soune/nocopy/handler/JwtTokenProvider.java index 721869c..9b18a21 100644 --- a/src/main/java/ru/soune/no_copy/handler/JwtTokenProvider.java +++ b/src/main/java/ru/soune/nocopy/handler/JwtTokenProvider.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.handler; +package ru.soune.nocopy.handler; import org.springframework.stereotype.Component; diff --git a/src/main/java/ru/soune/no_copy/mapper/UserContentMapper.java b/src/main/java/ru/soune/nocopy/mapper/UserContentMapper.java similarity index 79% rename from src/main/java/ru/soune/no_copy/mapper/UserContentMapper.java rename to src/main/java/ru/soune/nocopy/mapper/UserContentMapper.java index 5617cbd..3959f8f 100644 --- a/src/main/java/ru/soune/no_copy/mapper/UserContentMapper.java +++ b/src/main/java/ru/soune/nocopy/mapper/UserContentMapper.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.mapper; +package ru.soune.nocopy.mapper; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.factory.Mappers; -import ru.soune.no_copy.dto.UserContentUpdateRequest; -import ru.soune.no_copy.entity.UserContent; +import ru.soune.nocopy.dto.UserContentUpdateRequest; +import ru.soune.nocopy.entity.UserContent; @Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) diff --git a/src/main/java/ru/soune/no_copy/mapper/UserMapper.java b/src/main/java/ru/soune/nocopy/mapper/UserMapper.java similarity index 77% rename from src/main/java/ru/soune/no_copy/mapper/UserMapper.java rename to src/main/java/ru/soune/nocopy/mapper/UserMapper.java index 442f082..c151480 100644 --- a/src/main/java/ru/soune/no_copy/mapper/UserMapper.java +++ b/src/main/java/ru/soune/nocopy/mapper/UserMapper.java @@ -1,10 +1,10 @@ -package ru.soune.no_copy.mapper; +package ru.soune.nocopy.mapper; import org.mapstruct.Mapper; import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.factory.Mappers; -import ru.soune.no_copy.dto.UserDTO; -import ru.soune.no_copy.entity.User; +import ru.soune.nocopy.dto.UserDTO; +import ru.soune.nocopy.entity.User; @Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) diff --git a/src/main/java/ru/soune/no_copy/repository/AuthTokenRepository.java b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java similarity index 83% rename from src/main/java/ru/soune/no_copy/repository/AuthTokenRepository.java rename to src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java index af56845..74826fd 100644 --- a/src/main/java/ru/soune/no_copy/repository/AuthTokenRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java @@ -1,8 +1,8 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import ru.soune.no_copy.entity.AuthToken; +import ru.soune.nocopy.entity.AuthToken; import java.time.LocalDate; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java similarity index 89% rename from src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java rename to src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java index 41e2781..3ee41cb 100644 --- a/src/main/java/ru/soune/no_copy/repository/FileEntityRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import ru.soune.no_copy.entity.file.FileEntity; -import ru.soune.no_copy.entity.file.FileStatus; +import ru.soune.nocopy.entity.file.FileEntity; +import ru.soune.nocopy.entity.file.FileStatus; import java.util.List; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java b/src/main/java/ru/soune/nocopy/repository/FileUploadSessionRepository.java similarity index 88% rename from src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java rename to src/main/java/ru/soune/nocopy/repository/FileUploadSessionRepository.java index 0a9175f..2b222a3 100644 --- a/src/main/java/ru/soune/no_copy/repository/FileUploadSessionRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/FileUploadSessionRepository.java @@ -1,11 +1,11 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; import java.time.LocalDateTime; import java.util.List; diff --git a/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java b/src/main/java/ru/soune/nocopy/repository/UserContentRepository.java similarity index 81% rename from src/main/java/ru/soune/no_copy/repository/UserContentRepository.java rename to src/main/java/ru/soune/nocopy/repository/UserContentRepository.java index 42f5e72..74fcd4b 100644 --- a/src/main/java/ru/soune/no_copy/repository/UserContentRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/UserContentRepository.java @@ -1,9 +1,9 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import ru.soune.no_copy.entity.file.FileType; -import ru.soune.no_copy.entity.UserContent; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.UserContent; import java.util.List; diff --git a/src/main/java/ru/soune/no_copy/repository/UserRepository.java b/src/main/java/ru/soune/nocopy/repository/UserRepository.java similarity index 77% rename from src/main/java/ru/soune/no_copy/repository/UserRepository.java rename to src/main/java/ru/soune/nocopy/repository/UserRepository.java index 472fe58..da4d7f9 100644 --- a/src/main/java/ru/soune/no_copy/repository/UserRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/UserRepository.java @@ -1,7 +1,7 @@ -package ru.soune.no_copy.repository; +package ru.soune.nocopy.repository; import org.springframework.data.jpa.repository.JpaRepository; -import ru.soune.no_copy.entity.User; +import ru.soune.nocopy.entity.User; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/service/AuthService.java b/src/main/java/ru/soune/nocopy/service/AuthService.java similarity index 84% rename from src/main/java/ru/soune/no_copy/service/AuthService.java rename to src/main/java/ru/soune/nocopy/service/AuthService.java index 1cb1966..6784449 100644 --- a/src/main/java/ru/soune/no_copy/service/AuthService.java +++ b/src/main/java/ru/soune/nocopy/service/AuthService.java @@ -1,24 +1,23 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import lombok.RequiredArgsConstructor; import org.springframework.context.MessageSource; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.LoginRequest; -import ru.soune.no_copy.dto.RegisterRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.NotValidationPasswordException; -import ru.soune.no_copy.exception.UserAlreadyExistsException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.LoginRequest; +import ru.soune.nocopy.dto.RegisterRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.NotValidationPasswordException; +import ru.soune.nocopy.exception.UserAlreadyExistsException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; import java.security.SecureRandom; import java.time.LocalDateTime; import java.util.Base64; -import java.util.Locale; import java.util.Optional; @Service diff --git a/src/main/java/ru/soune/no_copy/service/UserContentService.java b/src/main/java/ru/soune/nocopy/service/UserContentService.java similarity index 81% rename from src/main/java/ru/soune/no_copy/service/UserContentService.java rename to src/main/java/ru/soune/nocopy/service/UserContentService.java index eb2573d..e113752 100644 --- a/src/main/java/ru/soune/no_copy/service/UserContentService.java +++ b/src/main/java/ru/soune/nocopy/service/UserContentService.java @@ -1,18 +1,18 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.UserContentRequest; -import ru.soune.no_copy.dto.UserContentUpdateRequest; -import ru.soune.no_copy.entity.file.FileType; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.entity.UserContent; -import ru.soune.no_copy.exception.ContentNotFoundException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.mapper.UserContentMapper; -import ru.soune.no_copy.repository.UserContentRepository; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.UserContentRequest; +import ru.soune.nocopy.dto.UserContentUpdateRequest; +import ru.soune.nocopy.entity.file.FileType; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.entity.UserContent; +import ru.soune.nocopy.exception.ContentNotFoundException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.mapper.UserContentMapper; +import ru.soune.nocopy.repository.UserContentRepository; +import ru.soune.nocopy.repository.UserRepository; import java.util.List; import java.util.Optional; diff --git a/src/main/java/ru/soune/no_copy/service/UserService.java b/src/main/java/ru/soune/nocopy/service/UserService.java similarity index 86% rename from src/main/java/ru/soune/no_copy/service/UserService.java rename to src/main/java/ru/soune/nocopy/service/UserService.java index 4cb0a51..7c4b79f 100644 --- a/src/main/java/ru/soune/no_copy/service/UserService.java +++ b/src/main/java/ru/soune/nocopy/service/UserService.java @@ -1,17 +1,17 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import lombok.AllArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.ChangePasswordRequest; -import ru.soune.no_copy.dto.UserDTO; -import ru.soune.no_copy.dto.UserRequest; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.InvalidUserEmail; -import ru.soune.no_copy.exception.NotValidationPasswordException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.ChangePasswordRequest; +import ru.soune.nocopy.dto.UserDTO; +import ru.soune.nocopy.dto.UserRequest; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.InvalidUserEmail; +import ru.soune.nocopy.exception.NotValidationPasswordException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.repository.UserRepository; @Service @AllArgsConstructor diff --git a/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java b/src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java similarity index 98% rename from src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java rename to src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java index 65a8f05..f86aa89 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileCleanupService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileEntityService.java b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java similarity index 95% rename from src/main/java/ru/soune/no_copy/service/file/FileEntityService.java rename to src/main/java/ru/soune/nocopy/service/file/FileEntityService.java index 2a745a9..5bc39a2 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -1,16 +1,16 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.soune.no_copy.dto.file.FileEntityResponse; -import ru.soune.no_copy.dto.file.FileListResponse; -import ru.soune.no_copy.entity.file.FileEntity; -import ru.soune.no_copy.entity.file.FileStatus; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.exception.FileEntityNotFoundException; -import ru.soune.no_copy.repository.FileEntityRepository; +import ru.soune.nocopy.dto.file.FileEntityResponse; +import ru.soune.nocopy.dto.file.FileListResponse; +import ru.soune.nocopy.entity.file.FileEntity; +import ru.soune.nocopy.entity.file.FileStatus; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.exception.FileEntityNotFoundException; +import ru.soune.nocopy.repository.FileEntityRepository; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java b/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java similarity index 80% rename from src/main/java/ru/soune/no_copy/service/file/FileUploadService.java rename to src/main/java/ru/soune/nocopy/service/file/FileUploadService.java index 7a9fe55..cfb9aec 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileUploadService.java @@ -1,8 +1,8 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.UploadProgressResponse; -import ru.soune.no_copy.entity.file.FileUploadSession; +import ru.soune.nocopy.dto.file.UploadProgressResponse; +import ru.soune.nocopy.entity.file.FileUploadSession; import java.io.IOException; diff --git a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java b/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java similarity index 98% rename from src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java rename to src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java index d01a2f0..af4aeb4 100644 --- a/src/main/java/ru/soune/no_copy/service/file/FileUploadServiceImpl.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileUploadServiceImpl.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.service.file; +package ru.soune.nocopy.service.file; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -9,13 +9,13 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import ru.soune.no_copy.dto.file.UploadProgressResponse; -import ru.soune.no_copy.entity.file.FileUploadSession; -import ru.soune.no_copy.entity.file.UploadStatus; -import ru.soune.no_copy.exception.ChunkSizeExceededException; -import ru.soune.no_copy.exception.FileUploadException; -import ru.soune.no_copy.exception.UploadSessionNotFoundException; -import ru.soune.no_copy.repository.FileUploadSessionRepository; +import ru.soune.nocopy.dto.file.UploadProgressResponse; +import ru.soune.nocopy.entity.file.FileUploadSession; +import ru.soune.nocopy.entity.file.UploadStatus; +import ru.soune.nocopy.exception.ChunkSizeExceededException; +import ru.soune.nocopy.exception.FileUploadException; +import ru.soune.nocopy.exception.UploadSessionNotFoundException; +import ru.soune.nocopy.repository.FileUploadSessionRepository; import java.io.*; import java.nio.file.*; diff --git a/src/test/java/ru/soune/no_copy/service/AuthServiceTest.java b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java similarity index 89% rename from src/test/java/ru/soune/no_copy/service/AuthServiceTest.java rename to src/test/java/ru/soune/nocopy/service/AuthServiceTest.java index 2b75fcb..8e045fd 100644 --- a/src/test/java/ru/soune/no_copy/service/AuthServiceTest.java +++ b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java @@ -1,4 +1,4 @@ -package ru.soune.no_copy.service; +package ru.soune.nocopy.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -8,15 +8,15 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.context.MessageSource; import org.springframework.security.crypto.password.PasswordEncoder; -import ru.soune.no_copy.dto.LoginRequest; -import ru.soune.no_copy.dto.RegisterRequest; -import ru.soune.no_copy.entity.AuthToken; -import ru.soune.no_copy.entity.User; -import ru.soune.no_copy.exception.NotValidationPasswordException; -import ru.soune.no_copy.exception.UserAlreadyExistsException; -import ru.soune.no_copy.exception.UserNotFoundException; -import ru.soune.no_copy.repository.AuthTokenRepository; -import ru.soune.no_copy.repository.UserRepository; +import ru.soune.nocopy.dto.LoginRequest; +import ru.soune.nocopy.dto.RegisterRequest; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.NotValidationPasswordException; +import ru.soune.nocopy.exception.UserAlreadyExistsException; +import ru.soune.nocopy.exception.UserNotFoundException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; import java.util.Locale; import java.util.Optional; From f3aa6324c2a9c3f322c45765148dd95971b386eb Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 18 Dec 2025 01:13:19 +0700 Subject: [PATCH 10/13] add fileentity controller --- .../nocopy/configuration/HandlerConfig.java | 28 ++ .../nocopy/controller/ApiController.java | 20 +- .../controller/file/FileEntityController.java | 210 ------------- .../controller/file/FileUploadController.java | 65 ---- .../ru/soune/nocopy/dto/LoginResponse.java | 0 .../java/ru/soune/nocopy/dto/MessageCode.java | 3 +- .../ru/soune/nocopy/dto/RegisterRequest.java | 0 .../nocopy/dto/file/DeleteFileResponse.java | 19 ++ .../nocopy/dto/file/FileEntityRequest.java | 34 ++ .../nocopy/dto/file/FileListResponse.java | 23 +- .../soune/nocopy/dto/file/FileResponse.java | 22 ++ .../nocopy/dto/file/StorageUsageResponse.java | 22 ++ .../exception/UserAlreadyExistsException.java | 0 .../nocopy/handler/FileEntityHandler.java | 292 ++++++++++++++++++ .../service/file/FileEntityService.java | 54 +--- 15 files changed, 447 insertions(+), 345 deletions(-) create mode 100644 src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java delete mode 100644 src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java delete mode 100644 src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java delete mode 100644 src/main/java/ru/soune/nocopy/dto/LoginResponse.java delete mode 100644 src/main/java/ru/soune/nocopy/dto/RegisterRequest.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/DeleteFileResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/FileResponse.java create mode 100644 src/main/java/ru/soune/nocopy/dto/file/StorageUsageResponse.java delete mode 100644 src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java create mode 100644 src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java new file mode 100644 index 0000000..96ae5bb --- /dev/null +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -0,0 +1,28 @@ +package ru.soune.nocopy.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.soune.nocopy.handler.*; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class HandlerConfig { + + @Bean + public Map handlers( + RegRequestHandler reg, + LoginRequestHandler login, + FileUploadHandler upload, + FileEntityHandler file + ) { + Map map = new HashMap<>(); + map.put(20001, login); + map.put(20002, reg); + map.put(20004, upload); + map.put(20005, file); + + return map; + } +} diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index 04e2d29..652fab7 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -1,9 +1,7 @@ package ru.soune.nocopy.controller; -import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -19,10 +17,7 @@ import ru.soune.nocopy.dto.file.UploadProgress; import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.ValidationException; -import ru.soune.nocopy.handler.FileUploadHandler; -import ru.soune.nocopy.handler.LoginRequestHandler; -import ru.soune.nocopy.handler.RegRequestHandler; -import ru.soune.nocopy.handler.RequestHandler; +import ru.soune.nocopy.handler.*; import ru.soune.nocopy.service.file.FileUploadService; import java.util.HashMap; @@ -35,21 +30,10 @@ import java.util.stream.Collectors; @RequestMapping("/api") @RequiredArgsConstructor public class ApiController { - private final ApplicationContext applicationContext; private final FileUploadService fileUploadService; - private Map handlers = new HashMap<>(); + private final Map handlers; - @PostConstruct - public void init() { - RegRequestHandler regHandler = applicationContext.getBean(RegRequestHandler.class); - LoginRequestHandler loginHandler = applicationContext.getBean(LoginRequestHandler.class); - FileUploadHandler fileUploadHandler = applicationContext.getBean(FileUploadHandler.class); - - handlers.put(20004, fileUploadHandler); - handlers.put(20002, regHandler); - handlers.put(20001, loginHandler); - } @PostMapping("/v{version}/data") public ResponseEntity handlePostRequest(@RequestBody BaseRequest request, diff --git a/src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java b/src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java deleted file mode 100644 index 25d19d1..0000000 --- a/src/main/java/ru/soune/nocopy/controller/file/FileEntityController.java +++ /dev/null @@ -1,210 +0,0 @@ -package ru.soune.nocopy.controller.file; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import ru.soune.nocopy.dto.file.FileApiResponse; -import ru.soune.nocopy.dto.file.FileEntityResponse; -import ru.soune.nocopy.dto.file.FileListResponse; -import ru.soune.nocopy.entity.AuthToken; -import ru.soune.nocopy.exception.NotFoundAuthToken; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.service.file.FileEntityService; - -@Slf4j -@RestController -@RequestMapping("/api/file-entities") -@RequiredArgsConstructor -public class FileEntityController { - - private final FileEntityService fileEntityService; - private final AuthTokenRepository authTokenRepository; - - /** - * Получить информацию о файле по ID - */ - @GetMapping("/{fileId}") - public ResponseEntity> getFileInfo( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403) - .body(FileApiResponse.error("Access denied")); - } - - return ResponseEntity.ok(FileApiResponse.success(fileInfo)); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401) - .body(FileApiResponse.error("Authentication required")); - } catch (Exception e) { - log.error("Error getting file info", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to get file info: " + e.getMessage())); - } - } - - /** - * Получить информацию о файле по ID сессии загрузки - */ - @GetMapping("/by-session/{uploadSessionId}") - public ResponseEntity> getFileByUploadSession( - @PathVariable String uploadSessionId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(uploadSessionId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403) - .body(FileApiResponse.error("Access denied")); - } - - return ResponseEntity.ok(FileApiResponse.success(fileInfo)); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401) - .body(FileApiResponse.error("Authentication required")); - } catch (Exception e) { - log.error("Error getting file by session", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to get file: " + e.getMessage())); - } - } - - /** - * Получить все файлы пользователя - */ - @GetMapping("/my-files") - public ResponseEntity> getUserFiles( - @RequestHeader("Authorization") String tokenHeader, - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "20") int pageSize) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileListResponse files = fileEntityService.getUserFiles(userId, page, pageSize); - - return ResponseEntity.ok(FileApiResponse.success(files)); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401) - .body(FileApiResponse.error("Authentication required")); - } catch (Exception e) { - log.error("Error getting user files", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to get files: " + e.getMessage())); - } - } - - /** - * Поиск файлов пользователя - */ - @GetMapping("/search") - public ResponseEntity> searchFiles( - @RequestParam String query, - @RequestHeader("Authorization") String tokenHeader, - @RequestParam(defaultValue = "1") int page, - @RequestParam(defaultValue = "20") int pageSize) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileListResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000); - - var filteredFiles = allFiles.getFiles().stream() - .filter(f -> f.getOriginalFileName().toLowerCase().contains(query.toLowerCase())) - .toList(); - - int start = (page - 1) * pageSize; - int end = Math.min(start + pageSize, filteredFiles.size()); - - FileListResponse response = FileListResponse.builder() - .files(filteredFiles.subList(start, Math.min(end, filteredFiles.size()))) - .totalCount(filteredFiles.size()) - .totalSize(allFiles.getTotalSize()) - .formattedTotalSize(allFiles.getFormattedTotalSize()) - .page(page) - .pageSize(pageSize) - .build(); - - return ResponseEntity.ok(FileApiResponse.success(response)); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401) - .body(FileApiResponse.error("Authentication required")); - } catch (Exception e) { - log.error("Error searching files", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to search files: " + e.getMessage())); - } - } - - /** - * Получить общий размер файлов пользователя - */ - @GetMapping("/storage/usage") - public ResponseEntity> getStorageUsage( - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - long usage = fileEntityService.getUserStorageUsed(userId); - - return ResponseEntity.ok(FileApiResponse.success(usage)); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401) - .body(FileApiResponse.error("Authentication required")); - } catch (Exception e) { - log.error("Error getting storage usage", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to get storage usage: " + e.getMessage())); - } - } - - /** - * Пометить файл как удаленный - */ - @DeleteMapping("/{fileId}") - public ResponseEntity> deleteFile( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403) - .body(FileApiResponse.error("Access denied")); - } - - fileEntityService.markAsDeleted(fileId); - - return ResponseEntity.ok(FileApiResponse.success( - "File marked as deleted", null)); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401) - .body(FileApiResponse.error("Authentication required")); - } catch (Exception e) { - log.error("Error deleting file", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Failed to delete file: " + e.getMessage())); - } - } - - private Long getUserIdFromToken(String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); - AuthToken authToken = authTokenRepository.findByToken(token) - .orElseThrow(() -> new NotFoundAuthToken("Token not found")); - return authToken.getUser().getId(); - } -} diff --git a/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java deleted file mode 100644 index d4bb6da..0000000 --- a/src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java +++ /dev/null @@ -1,65 +0,0 @@ -package ru.soune.nocopy.controller.file; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import ru.soune.nocopy.dto.file.FileApiResponse; -import ru.soune.nocopy.dto.file.InitUploadRequest; -import ru.soune.nocopy.dto.file.UploadProgressResponse; -import ru.soune.nocopy.entity.AuthToken; -import ru.soune.nocopy.entity.file.FileType; -import ru.soune.nocopy.entity.file.FileUploadSession; -import ru.soune.nocopy.entity.file.UploadStatus; -import ru.soune.nocopy.exception.FileUploadException; -import ru.soune.nocopy.exception.NotFoundAuthToken; -import ru.soune.nocopy.exception.UploadSessionNotFoundException; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.repository.FileUploadSessionRepository; -import ru.soune.nocopy.service.file.FileUploadService; - -import java.util.*; - -@Slf4j -@RestController -@RequestMapping("/api/files") -@RequiredArgsConstructor -public class FileUploadController { - - private final FileUploadService fileUploadService; - - @PostMapping("/chunk") - public ResponseEntity> uploadChunk( - @RequestParam("uploadId") String uploadId, - @RequestParam("chunkNumber") Integer chunkNumber, - @RequestParam("chunk") MultipartFile chunk) { - - log.info("Uploading chunk {} for session {}, file size: {} bytes", - chunkNumber, uploadId, chunk.getSize()); - - if (chunk.isEmpty()) { - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Chunk file is empty")); - } - - if (uploadId == null || uploadId.isBlank()) { - return ResponseEntity.badRequest() - .body(FileApiResponse.error("Upload ID is required")); - } - - try { - UploadProgressResponse progress = fileUploadService.uploadChunk( - uploadId, chunkNumber, chunk); - - return ResponseEntity.ok(FileApiResponse.success(progress)); - - } catch (Exception e) { - log.error("Error uploading chunk", e); - return ResponseEntity.badRequest() - .body(FileApiResponse.error(e.getMessage())); - } - } -} diff --git a/src/main/java/ru/soune/nocopy/dto/LoginResponse.java b/src/main/java/ru/soune/nocopy/dto/LoginResponse.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/dto/MessageCode.java b/src/main/java/ru/soune/nocopy/dto/MessageCode.java index 36cd838..84541c9 100644 --- a/src/main/java/ru/soune/nocopy/dto/MessageCode.java +++ b/src/main/java/ru/soune/nocopy/dto/MessageCode.java @@ -11,7 +11,8 @@ public enum MessageCode { INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INCOMPLETE_UPLOAD(2, "Not load all chunks"), MSG_ID_NOT_FOUND(4, "Message id not found"), - + FILE_ENTITY_ERROR(2, "File entity error"), + ACCESS_DENIED(2, "Access denied"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"), FILE_NOT_FOUND(4, "File not found"), AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"); diff --git a/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java b/src/main/java/ru/soune/nocopy/dto/RegisterRequest.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/dto/file/DeleteFileResponse.java b/src/main/java/ru/soune/nocopy/dto/file/DeleteFileResponse.java new file mode 100644 index 0000000..c45163e --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/DeleteFileResponse.java @@ -0,0 +1,19 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DeleteFileResponse { + @JsonProperty("file_id") + private String fileId; + + @JsonProperty("message") + private String message; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java b/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java new file mode 100644 index 0000000..4682dac --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/FileEntityRequest.java @@ -0,0 +1,34 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class FileEntityRequest { + @JsonProperty("action") + private String action; + + @JsonProperty("file_id") + private String fileId; + + @JsonProperty("upload_session_id") + private String uploadSessionId; + + @JsonProperty("query") + private String query; + + @JsonProperty("page") + private Integer page; + + @JsonProperty("page_size") + private Integer pageSize; + + @JsonProperty("token") + private String token; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java index 5815a03..bbc5a5f 100644 --- a/src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileListResponse.java @@ -1,5 +1,6 @@ package ru.soune.nocopy.dto.file; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -9,14 +10,24 @@ import java.util.List; @Data @Builder -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor public class FileListResponse { - + @JsonProperty("files") private List files; - private int totalCount; - private long totalSize; + + @JsonProperty("total_count") + private Integer totalCount; + + @JsonProperty("total_size") + private Long totalSize; + + @JsonProperty("formatted_total_size") private String formattedTotalSize; - private int page; - private int pageSize; + + @JsonProperty("page") + private Integer page; + + @JsonProperty("page_size") + private Integer pageSize; } diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileResponse.java new file mode 100644 index 0000000..4786748 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/FileResponse.java @@ -0,0 +1,22 @@ +package ru.soune.nocopy.dto.file; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileResponse { + + private List files; + private int totalCount; + private long totalSize; + private String formattedTotalSize; + private int page; + private int pageSize; +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/StorageUsageResponse.java b/src/main/java/ru/soune/nocopy/dto/file/StorageUsageResponse.java new file mode 100644 index 0000000..c4ae5ec --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/file/StorageUsageResponse.java @@ -0,0 +1,22 @@ +package ru.soune.nocopy.dto.file; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class StorageUsageResponse { + @JsonProperty("user_id") + private Long userId; + + @JsonProperty("storage_used") + private Long storageUsed; + + @JsonProperty("formatted_storage_used") + private String formattedStorageUsed; +} diff --git a/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java b/src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java new file mode 100644 index 0000000..5c1b3d0 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -0,0 +1,292 @@ +package ru.soune.nocopy.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.dto.*; +import ru.soune.nocopy.dto.file.*; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.file.FileEntityService; + +import java.util.Arrays; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FileEntityHandler implements RequestHandler { + + private final FileEntityService fileEntityService; + private final AuthTokenRepository authTokenRepository; + private final ObjectMapper objectMapper; + + @Override + public BaseResponse handle(BaseRequest request) { + try { + FileEntityRequest fileRequest = objectMapper.convertValue( + request.getMessageBody(), FileEntityRequest.class); + + String action = fileRequest.getAction(); + + switch (action) { + case "get_file_info": + return handleGetFileInfo(request, fileRequest); + case "get_file_by_session": + return handleGetFileBySession(request, fileRequest); + case "get_user_files": + return handleGetUserFiles(request, fileRequest); + case "search_files": + return handleSearchFiles(request, fileRequest); + case "get_storage_usage": + return handleGetStorageUsage(request, fileRequest); + case "delete_file": + return handleDeleteFile(request, fileRequest); + default: + ActionResponse response = ActionResponse.builder() + .action(action) + .availableActions(Arrays.asList( + "get_file_info", "get_file_by_session", "get_user_files", + "search_files", "get_storage_usage", "delete_file")) + .build(); + + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_ACTION.getCode(), + "Invalid action: " + action, + response); + } + } catch (Exception e) { + log.error("Error in FileEntityHandler", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Handler error: " + e.getMessage(), + null); + } + } + + private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) { + try { + Long userId = getUserIdFromToken(fileRequest.getToken()); + FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId()); + + if (!fileInfo.getUserId().equals(userId)) { + return new BaseResponse(request.getMsgId(), + MessageCode.ACCESS_DENIED.getCode(), + "Access denied", + null); + } + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), + fileInfo); + + } catch (NotFoundAuthToken e) { + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_TOKEN.getCode(), + "Authentication required", + null); + } catch (Exception e) { + log.error("Error getting file info", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to get file info: " + e.getMessage(), + null); + } + } + + private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) { + try { + Long userId = getUserIdFromToken(fileRequest.getToken()); + FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId()); + + if (!fileInfo.getUserId().equals(userId)) { + return new BaseResponse(request.getMsgId(), + MessageCode.ACCESS_DENIED.getCode(), + "Access denied", + null); + } + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), + fileInfo); + + } catch (NotFoundAuthToken e) { + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_TOKEN.getCode(), + "Authentication required", + null); + } catch (Exception e) { + log.error("Error getting file by session", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to get file: " + e.getMessage(), + null); + } + } + + private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) { + try { + Long userId = getUserIdFromToken(fileRequest.getToken()); + int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; + int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; + + FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize); + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), + files); + + } catch (NotFoundAuthToken e) { + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_TOKEN.getCode(), + "Authentication required", + null); + } catch (Exception e) { + log.error("Error getting user files", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to get files: " + e.getMessage(), + null); + } + } + + private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) { + try { + Long userId = getUserIdFromToken(fileRequest.getToken()); + int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; + int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; + + if (fileRequest.getQuery() == null || fileRequest.getQuery().isBlank()) { + return handleGetUserFiles(request, fileRequest); + } + + FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000); + + var filteredFiles = allFiles.getFiles().stream() + .filter(f -> f.getOriginalFileName().toLowerCase().contains(fileRequest.getQuery().toLowerCase())) + .toList(); + + int start = (page - 1) * pageSize; + int end = Math.min(start + pageSize, filteredFiles.size()); + + FileListResponse response = FileListResponse.builder() + .files(filteredFiles.subList(start, Math.min(end, filteredFiles.size()))) + .totalCount(filteredFiles.size()) + .totalSize(allFiles.getTotalSize()) + .formattedTotalSize(allFiles.getFormattedTotalSize()) + .page(page) + .pageSize(pageSize) + .build(); + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), + response); + + } catch (NotFoundAuthToken e) { + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_TOKEN.getCode(), + "Authentication required", + null); + } catch (Exception e) { + log.error("Error searching files", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to search files: " + e.getMessage(), + null); + } + } + + private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) { + try { + Long userId = getUserIdFromToken(fileRequest.getToken()); + long usage = fileEntityService.getUserStorageUsed(userId); + + StorageUsageResponse response = StorageUsageResponse.builder() + .userId(userId) + .storageUsed(usage) + .formattedStorageUsed(formatFileSize(usage)) + .build(); + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + MessageCode.SUCCESS.getDescription(), + response); + + } catch (NotFoundAuthToken e) { + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_TOKEN.getCode(), + "Authentication required", + null); + } catch (Exception e) { + log.error("Error getting storage usage", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to get storage usage: " + e.getMessage(), + null); + } + } + + private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) { + try { + Long userId = getUserIdFromToken(fileRequest.getToken()); + FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId()); + + if (!fileInfo.getUserId().equals(userId)) { + return new BaseResponse(request.getMsgId(), + MessageCode.ACCESS_DENIED.getCode(), + "Access denied", + null); + } + + fileEntityService.markAsDeleted(fileRequest.getFileId()); + + DeleteFileResponse response = DeleteFileResponse.builder() + .fileId(fileRequest.getFileId()) + .message("File marked as deleted") + .build(); + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + "File marked as deleted", + response); + + } catch (NotFoundAuthToken e) { + return new BaseResponse(request.getMsgId(), + MessageCode.INVALID_TOKEN.getCode(), + "Authentication required", + null); + } catch (Exception e) { + log.error("Error deleting file", e); + return new BaseResponse(request.getMsgId(), + MessageCode.FILE_UPLOAD_ERROR.getCode(), + "Failed to delete file: " + e.getMessage(), + null); + } + } + + private Long getUserIdFromToken(String token) { + if (token == null || token.isBlank()) { + throw new NotFoundAuthToken("Token is required"); + } + + if (token.startsWith("Bearer ")) { + token = token.replace("Bearer ", ""); + } + + AuthToken authToken = authTokenRepository.findByToken(token) + .orElseThrow(() -> new NotFoundAuthToken("Token not found")); + return authToken.getUser().getId(); + } + + private String formatFileSize(long size) { + if (size < 1024) return size + " B"; + int exp = (int) (Math.log(size) / Math.log(1024)); + String pre = "KMGTPE".charAt(exp-1) + ""; + return String.format("%.1f %sB", size / Math.pow(1024, exp), pre); + } +} diff --git a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java index 5bc39a2..7bd858e 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -5,7 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.nocopy.dto.file.FileEntityResponse; -import ru.soune.nocopy.dto.file.FileListResponse; +import ru.soune.nocopy.dto.file.FileResponse; import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileUploadSession; @@ -27,9 +27,6 @@ public class FileEntityService { private final FileEntityRepository fileEntityRepository; - /** - * Создает FileEntity на основе завершенной сессии загрузки - */ @Transactional public FileEntity createFromUploadSession(FileUploadSession session, String checksum) { log.info("Creating FileEntity for upload session: {}", session.getUploadId()); @@ -72,9 +69,7 @@ public class FileEntityService { } } - /** - * Извлекает расширение файла из имени - */ + private String extractFileExtension(String fileName) { int dotIndex = fileName.lastIndexOf('.'); if (dotIndex > 0 && dotIndex < fileName.length() - 1) { @@ -83,9 +78,6 @@ public class FileEntityService { return ""; } - /** - * Получает FileEntity по ID - */ @Transactional(readOnly = true) public FileEntityResponse getById(String fileId) { FileEntity fileEntity = fileEntityRepository.findById(fileId) @@ -94,9 +86,6 @@ public class FileEntityService { return convertToResponse(fileEntity); } - /** - * Получает FileEntity по ID сессии загрузки - */ @Transactional(readOnly = true) public FileEntityResponse getByUploadSessionId(String uploadSessionId) { FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId) @@ -106,9 +95,6 @@ public class FileEntityService { return convertToResponse(fileEntity); } - /** - * Получает FileEntity по пути файла - */ @Transactional(readOnly = true) public FileEntityResponse getByFilePath(String filePath) { FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath) @@ -117,11 +103,9 @@ public class FileEntityService { return convertToResponse(fileEntity); } - /** - * Получает все файлы пользователя - */ + @Transactional(readOnly = true) - public FileListResponse getAllUserFiles(Long userId) { + public FileResponse getAllUserFiles(Long userId) { List fileEntities = fileEntityRepository.findByUserIdAndStatus( userId, FileStatus.ACTIVE); @@ -133,7 +117,7 @@ public class FileEntityService { .mapToLong(FileEntity::getFileSize) .sum(); - return FileListResponse.builder() + return FileResponse.builder() .files(files) .totalCount(files.size()) .totalSize(totalSize) @@ -143,11 +127,9 @@ public class FileEntityService { .build(); } - /** - * Получает файлы пользователя с пагинацией - */ + @Transactional(readOnly = true) - public FileListResponse getUserFiles(Long userId, int page, int pageSize) { + public FileResponse getUserFiles(Long userId, int page, int pageSize) { List allFiles = fileEntityRepository.findByUserIdAndStatus( userId, FileStatus.ACTIVE); @@ -155,7 +137,7 @@ public class FileEntityService { int end = Math.min(start + pageSize, allFiles.size()); if (start >= allFiles.size()) { - return FileListResponse.builder() + return FileResponse.builder() .files(List.of()) .totalCount(allFiles.size()) .totalSize(0) @@ -175,7 +157,7 @@ public class FileEntityService { .mapToLong(FileEntity::getFileSize) .sum(); - return FileListResponse.builder() + return FileResponse.builder() .files(files) .totalCount(allFiles.size()) .totalSize(totalSize) @@ -185,9 +167,6 @@ public class FileEntityService { .build(); } - /** - * Помечает файл как удаленный (мягкое удаление) - */ @Transactional public void markAsDeleted(String fileId) { FileEntity fileEntity = fileEntityRepository.findById(fileId) @@ -200,18 +179,12 @@ public class FileEntityService { log.info("FileEntity marked as deleted: {}", fileId); } - /** - * Получает общий размер файлов пользователя - */ @Transactional(readOnly = true) public long getUserStorageUsed(Long userId) { Long totalSize = fileEntityRepository.getTotalSizeByUserId(userId); return totalSize != null ? totalSize : 0L; } - /** - * Поиск файлов пользователя по имени - */ @Transactional(readOnly = true) public List searchFiles(Long userId, String query) { List files = fileEntityRepository.searchByFileName(userId, query); @@ -222,9 +195,6 @@ public class FileEntityService { .collect(Collectors.toList()); } - /** - * Проверяет, существует ли файл на диске - */ private boolean checkFileExistsOnDisk(String filePath) { try { return Files.exists(Paths.get(filePath)); @@ -234,9 +204,6 @@ public class FileEntityService { } } - /** - * Конвертирует FileEntity в DTO - */ private FileEntityResponse convertToResponse(FileEntity fileEntity) { boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); @@ -260,9 +227,6 @@ public class FileEntityService { .build(); } - /** - * Форматирует размер файла в читаемый вид - */ private String formatFileSize(long size) { if (size < 1024) { return size + " B"; From d167e92d90f5f632c7f60a006233c24b3cc0c03d Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 18 Dec 2025 11:23:42 +0700 Subject: [PATCH 11/13] add fileentity controller --- .../nocopy/controller/file/FileDownloadController.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java index 6339094..8678932 100644 --- a/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java +++ b/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java @@ -26,11 +26,9 @@ import java.nio.file.Paths; public class FileDownloadController { private final FileEntityService fileEntityService; + private final AuthTokenRepository authTokenRepository; - /** - * Скачать файл по ID FileEntity - */ @GetMapping("/download/{fileId}") public ResponseEntity downloadFile( @PathVariable String fileId, @@ -73,9 +71,6 @@ public class FileDownloadController { } } - /** - * Получить предпросмотр файла (если поддерживается) - */ @GetMapping("/preview/{fileId}") public ResponseEntity previewFile( @PathVariable String fileId, @@ -117,9 +112,6 @@ public class FileDownloadController { } } - /** - * Получить информацию о файле для фронтенда - */ @GetMapping("/info/{fileId}") public ResponseEntity getFileInfo( @PathVariable String fileId, From 31b90f942c7328539f8312135742bb66656fa033 Mon Sep 17 00:00:00 2001 From: vladp Date: Thu, 18 Dec 2025 14:40:27 +0700 Subject: [PATCH 12/13] add fileentity controller --- .../nocopy/controller/ApiController.java | 155 +++++++++++++++++ .../file/FileDownloadController.java | 164 ------------------ .../nocopy/handler/FileEntityHandler.java | 11 +- .../validator/FileUploadRequestValidator.java | 9 +- .../service/file/FileEntityService.java | 34 ++-- 5 files changed, 174 insertions(+), 199 deletions(-) delete mode 100644 src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index 652fab7..9ecad18 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -2,6 +2,10 @@ package ru.soune.nocopy.controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -13,13 +17,22 @@ import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.RegAnswer; import ru.soune.nocopy.dto.file.ChunkUploadResponse; import ru.soune.nocopy.dto.file.CompleteUploadResponse; +import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.UploadProgress; +import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.entity.file.UploadStatus; +import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.ValidationException; import ru.soune.nocopy.handler.*; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileUploadService; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,6 +47,9 @@ public class ApiController { private final Map handlers; + private final FileEntityService fileEntityService; + + private final AuthTokenRepository authTokenRepository; @PostMapping("/v{version}/data") public ResponseEntity handlePostRequest(@RequestBody BaseRequest request, @@ -219,6 +235,117 @@ public class ApiController { } } + + @GetMapping("/v{version}/files/download/{fileId}") + public ResponseEntity downloadFile( + @PathVariable String fileId, + @PathVariable Integer version, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + if (!fileInfo.isExistsOnDisk()) { + return ResponseEntity.status(404) + .body(null); + } + + Path filePath = Paths.get(fileInfo.getFilePath()); + Resource resource = new UrlResource(filePath.toUri()); + + if (!resource.exists()) { + return ResponseEntity.status(404).build(); + } + + String contentType = determineContentType(filePath); + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"") + .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileInfo.getFileSize())) + .body(resource); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error downloading file", e); + return ResponseEntity.status(500).build(); + } + } + + @GetMapping("/v{version}/files/info/{fileId}") + public ResponseEntity getFileInfo( + @PathVariable String fileId, + @PathVariable Integer version, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + return ResponseEntity.ok(fileInfo); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error getting file info", e); + return ResponseEntity.status(500).build(); + } + } + + @GetMapping("/v{version}/files/preview/{fileId}") + public ResponseEntity previewFile( + @PathVariable String fileId, + @PathVariable Integer version, + @RequestHeader("Authorization") String tokenHeader) { + + try { + Long userId = getUserIdFromToken(tokenHeader); + FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); + + if (!fileInfo.getUserId().equals(userId)) { + return ResponseEntity.status(403).build(); + } + + if (!isPreviewSupported(fileInfo.getMimeType())) { + return ResponseEntity.status(415) + .body(null); + } + + Path filePath = Paths.get(fileInfo.getFilePath()); + Resource resource = new UrlResource(filePath.toUri()); + + if (!resource.exists()) { + return ResponseEntity.status(404).build(); + } + + String contentType = determineContentType(filePath); + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, + "inline; filename=\"" + fileInfo.getOriginalFileName() + "\"") + .body(resource); + + } catch (NotFoundAuthToken e) { + return ResponseEntity.status(401).build(); + } catch (Exception e) { + log.error("Error previewing file", e); + return ResponseEntity.status(500).build(); + } + } + + private ResponseEntity createValidationErrorResponse(BindingResult bindingResult, Integer msgId) { List> fieldErrors = bindingResult.getFieldErrors() .stream() @@ -245,4 +372,32 @@ public class ApiController { return errorDetail; } + + + private String determineContentType(Path filePath) throws IOException { + String contentType = Files.probeContentType(filePath); + if (contentType == null) { + contentType = "application/octet-stream"; + } + return contentType; + } + + private boolean isPreviewSupported(String mimeType) { + if (mimeType == null) { + return false; + } + + return mimeType.startsWith("image/") || + mimeType.startsWith("text/") || + mimeType.equals("application/pdf") || + mimeType.startsWith("video/") || + mimeType.startsWith("audio/"); + } + + private Long getUserIdFromToken(String tokenHeader) { + String token = tokenHeader.replace("Bearer ", ""); + AuthToken authToken = authTokenRepository.findByToken(token) + .orElseThrow(() -> new NotFoundAuthToken("Token not found")); + return authToken.getUser().getId(); + } } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java b/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java deleted file mode 100644 index 8678932..0000000 --- a/src/main/java/ru/soune/nocopy/controller/file/FileDownloadController.java +++ /dev/null @@ -1,164 +0,0 @@ -package ru.soune.nocopy.controller.file; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import ru.soune.nocopy.dto.file.FileEntityResponse; -import ru.soune.nocopy.entity.AuthToken; -import ru.soune.nocopy.exception.NotFoundAuthToken; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.service.file.FileEntityService; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -@Slf4j -@RestController -@RequestMapping("/api/files") -@RequiredArgsConstructor -public class FileDownloadController { - - private final FileEntityService fileEntityService; - - private final AuthTokenRepository authTokenRepository; - - @GetMapping("/download/{fileId}") - public ResponseEntity downloadFile( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403).build(); - } - - if (!fileInfo.isExistsOnDisk()) { - return ResponseEntity.status(404) - .body(null); - } - - Path filePath = Paths.get(fileInfo.getFilePath()); - Resource resource = new UrlResource(filePath.toUri()); - - if (!resource.exists()) { - return ResponseEntity.status(404).build(); - } - - String contentType = determineContentType(filePath); - - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header(HttpHeaders.CONTENT_DISPOSITION, - "attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"") - .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileInfo.getFileSize())) - .body(resource); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401).build(); - } catch (Exception e) { - log.error("Error downloading file", e); - return ResponseEntity.status(500).build(); - } - } - - @GetMapping("/preview/{fileId}") - public ResponseEntity previewFile( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403).build(); - } - - if (!isPreviewSupported(fileInfo.getMimeType())) { - return ResponseEntity.status(415) - .body(null); - } - - Path filePath = Paths.get(fileInfo.getFilePath()); - Resource resource = new UrlResource(filePath.toUri()); - - if (!resource.exists()) { - return ResponseEntity.status(404).build(); - } - - String contentType = determineContentType(filePath); - - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(contentType)) - .header(HttpHeaders.CONTENT_DISPOSITION, - "inline; filename=\"" + fileInfo.getOriginalFileName() + "\"") - .body(resource); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401).build(); - } catch (Exception e) { - log.error("Error previewing file", e); - return ResponseEntity.status(500).build(); - } - } - - @GetMapping("/info/{fileId}") - public ResponseEntity getFileInfo( - @PathVariable String fileId, - @RequestHeader("Authorization") String tokenHeader) { - - try { - Long userId = getUserIdFromToken(tokenHeader); - FileEntityResponse fileInfo = fileEntityService.getById(fileId); - - if (!fileInfo.getUserId().equals(userId)) { - return ResponseEntity.status(403).build(); - } - - return ResponseEntity.ok(fileInfo); - - } catch (NotFoundAuthToken e) { - return ResponseEntity.status(401).build(); - } catch (Exception e) { - log.error("Error getting file info", e); - return ResponseEntity.status(500).build(); - } - } - - private String determineContentType(Path filePath) throws IOException { - String contentType = Files.probeContentType(filePath); - if (contentType == null) { - contentType = "application/octet-stream"; - } - return contentType; - } - - private boolean isPreviewSupported(String mimeType) { - if (mimeType == null) { - return false; - } - - return mimeType.startsWith("image/") || - mimeType.startsWith("text/") || - mimeType.equals("application/pdf") || - mimeType.startsWith("video/") || - mimeType.startsWith("audio/"); - } - - private Long getUserIdFromToken(String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); - AuthToken authToken = authTokenRepository.findByToken(token) - .orElseThrow(() -> new NotFoundAuthToken("Token not found")); - return authToken.getUser().getId(); - } -} diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java index 5c1b3d0..bbc8da9 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -68,7 +68,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = getUserIdFromToken(fileRequest.getToken()); - FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId()); + FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), @@ -99,7 +99,8 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = getUserIdFromToken(fileRequest.getToken()); - FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId()); + FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(), + request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), @@ -133,7 +134,7 @@ public class FileEntityHandler implements RequestHandler { int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; - FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize); + FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize, request.getVersion()); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), @@ -164,7 +165,7 @@ public class FileEntityHandler implements RequestHandler { return handleGetUserFiles(request, fileRequest); } - FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000); + FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion()); var filteredFiles = allFiles.getFiles().stream() .filter(f -> f.getOriginalFileName().toLowerCase().contains(fileRequest.getQuery().toLowerCase())) @@ -234,7 +235,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) { try { Long userId = getUserIdFromToken(fileRequest.getToken()); - FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId()); + FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { return new BaseResponse(request.getMsgId(), diff --git a/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java b/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java index 10737f7..f40487a 100644 --- a/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java +++ b/src/main/java/ru/soune/nocopy/handler/validator/FileUploadRequestValidator.java @@ -55,12 +55,5 @@ public class FileUploadRequestValidator implements Validator { errors.rejectValue("extension", "extension.required", "Extension is required"); return; } - - if (!extension.startsWith(".")) { - errors.rejectValue("extension", "extension.invalid.format", - "Extension must start with '.'"); - return; - } } - -} +} \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java index 7bd858e..8b239a5 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -79,38 +79,38 @@ public class FileEntityService { } @Transactional(readOnly = true) - public FileEntityResponse getById(String fileId) { + public FileEntityResponse getById(String fileId, int version) { FileEntity fileEntity = fileEntityRepository.findById(fileId) .orElseThrow(() -> new FileEntityNotFoundException(fileId)); - return convertToResponse(fileEntity); + return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) - public FileEntityResponse getByUploadSessionId(String uploadSessionId) { + public FileEntityResponse getByUploadSessionId(String uploadSessionId, int version) { FileEntity fileEntity = fileEntityRepository.findByUploadSessionId(uploadSessionId) .orElseThrow(() -> new FileEntityNotFoundException( "Not found for upload session: " + uploadSessionId)); - return convertToResponse(fileEntity); + return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) - public FileEntityResponse getByFilePath(String filePath) { + public FileEntityResponse getByFilePath(String filePath, int version) { FileEntity fileEntity = fileEntityRepository.findByFilePath(filePath) .orElseThrow(() -> new FileEntityNotFoundException("Path: " + filePath)); - return convertToResponse(fileEntity); + return convertToResponse(fileEntity, version); } @Transactional(readOnly = true) - public FileResponse getAllUserFiles(Long userId) { + public FileResponse getAllUserFiles(Long userId, int version) { List fileEntities = fileEntityRepository.findByUserIdAndStatus( userId, FileStatus.ACTIVE); List files = fileEntities.stream() - .map(this::convertToResponse) + .map(file -> convertToResponse(file, version)) .collect(Collectors.toList()); long totalSize = fileEntities.stream() @@ -129,7 +129,7 @@ public class FileEntityService { @Transactional(readOnly = true) - public FileResponse getUserFiles(Long userId, int page, int pageSize) { + public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) { List allFiles = fileEntityRepository.findByUserIdAndStatus( userId, FileStatus.ACTIVE); @@ -150,7 +150,7 @@ public class FileEntityService { List pageFiles = allFiles.subList(start, end); List files = pageFiles.stream() - .map(this::convertToResponse) + .map(file -> convertToResponse(file, version)) .collect(Collectors.toList()); long totalSize = allFiles.stream() @@ -185,16 +185,6 @@ public class FileEntityService { return totalSize != null ? totalSize : 0L; } - @Transactional(readOnly = true) - public List searchFiles(Long userId, String query) { - List files = fileEntityRepository.searchByFileName(userId, query); - - return files.stream() - .filter(f -> f.getStatus() == FileStatus.ACTIVE) - .map(this::convertToResponse) - .collect(Collectors.toList()); - } - private boolean checkFileExistsOnDisk(String filePath) { try { return Files.exists(Paths.get(filePath)); @@ -204,7 +194,7 @@ public class FileEntityService { } } - private FileEntityResponse convertToResponse(FileEntity fileEntity) { + private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) { boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath()); return FileEntityResponse.builder() @@ -222,7 +212,7 @@ public class FileEntityService { .createdAt(fileEntity.getCreatedAt()) .updatedAt(fileEntity.getUpdatedAt()) .formattedSize(formatFileSize(fileEntity.getFileSize())) - .downloadUrl("/api/files/download/" + fileEntity.getId()) + .downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId()) .existsOnDisk(existsOnDisk) .build(); } From 1932784264a29ba3ae7177982eb234a7ed931447 Mon Sep 17 00:00:00 2001 From: vladp Date: Sun, 21 Dec 2025 16:19:12 +0700 Subject: [PATCH 13/13] NCBACK-15 --- .../java/ru/soune/nocopy/configuration/HandlerConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java index 96ae5bb..5b3c744 100644 --- a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -15,13 +15,15 @@ public class HandlerConfig { RegRequestHandler reg, LoginRequestHandler login, FileUploadHandler upload, - FileEntityHandler file + FileEntityHandler file, + LogoutRequestHandler logoutHandler ) { Map map = new HashMap<>(); map.put(20001, login); map.put(20002, reg); map.put(20004, upload); map.put(20005, file); + map.put(20006, logoutHandler); return map; }