9 Commits
Author SHA1 Message Date
backdev 97b1fb7d85 Merge branch 'NCBACK-162' into dev
Test Workflow / test (push) Has been cancelled
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/FileController.java
2026-06-03 10:45:18 +07:00
backdev 22e9d6c9db Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 16:07:08 +07:00
backdev 8bad5a4619 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:53:03 +07:00
backdev 3b04cc8907 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:39:38 +07:00
backdev ebbd69b452 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:26:08 +07:00
backdev 47bed4149a Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:06:45 +07:00
backdev e788b062b8 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 14:09:25 +07:00
backdev db606284cb Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 13:01:41 +07:00
backdev de32f9b4d1 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-01 16:56:19 +07:00
10 changed files with 110 additions and 26 deletions
@@ -11,6 +11,7 @@ import org.springframework.http.*;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ru.soune.nocopy.dto.file.CheckStatus; import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.user.ProtectedFileCheck; import ru.soune.nocopy.entity.user.ProtectedFileCheck;
@@ -19,8 +20,10 @@ import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ZipService; import ru.soune.nocopy.service.file.ZipService;
import ru.soune.nocopy.service.file.*;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import software.amazon.awssdk.services.mq.model.NotFoundException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException; import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.io.*; import java.io.*;
@@ -39,6 +42,8 @@ public class FileController {
private UserRepository userRepository; private UserRepository userRepository;
private final FileEntityService fileEntityService;
private NoCopyFileService noCopyFileService; private NoCopyFileService noCopyFileService;
private FileUtil fileUtil; private FileUtil fileUtil;
@@ -85,6 +90,24 @@ public class FileController {
} }
} }
@GetMapping("/public/file-info/{fileId}")
public ResponseEntity<FileEntityResponse> getPublicFileInfo(@PathVariable String fileId) {
return handleGetFileInfo(fileId);
}
private ResponseEntity<FileEntityResponse> handleGetFileInfo(String fileId) {
try {
FileEntityResponse fileInfo = fileEntityService.getById(fileId, 1);
return ResponseEntity
.ok()
.body(fileInfo);
} catch (NotFoundException e) {
return ResponseEntity
.notFound().build();
}
}
private MediaType getMediaType(FileEntity fileEntity) { private MediaType getMediaType(FileEntity fileEntity) {
try { try {
return MediaType.parseMediaType(fileEntity.getMimeType()); return MediaType.parseMediaType(fileEntity.getMimeType());
@@ -113,13 +136,6 @@ public class FileController {
String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath(); String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath();
// InputStream file = cloudStorageService.readFileFromStorage(filePath);
// if (file == null || !file.exists()) {
// log.error("File not found in storage: {}", fileEntity.getFilePath());
// return ResponseEntity.notFound().build();
// }
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED || if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) { fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected"); throw new RuntimeException("File is not protected");
@@ -78,4 +78,10 @@ public class FileEntityRequest {
@JsonProperty("permissions") @JsonProperty("permissions")
private Map<String, Boolean> permissions; private Map<String, Boolean> permissions;
@JsonProperty("is_appeal")
private Boolean isAppeal;
@JsonProperty("statuses")
private List<String> statuses;
} }
@@ -8,6 +8,7 @@ import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.permission.PermissionType; import ru.soune.nocopy.entity.file.permission.PermissionType;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map; import java.util.Map;
@Data @Data
@@ -45,4 +46,5 @@ public class FileEntityResponse {
private LocalDateTime fileUploadDate; private LocalDateTime fileUploadDate;
private String monitoring; private String monitoring;
private Map<PermissionType, Boolean> permissions; private Map<PermissionType, Boolean> permissions;
private List<Map<String, Object>> appealInfos;
} }
@@ -36,4 +36,7 @@ public class FileListResponse {
@JsonProperty("total_size") @JsonProperty("total_size")
private Long totalSize; private Long totalSize;
@JsonProperty("statuses")
private List<String> statuses;
} }
@@ -2,6 +2,7 @@ package ru.soune.nocopy.dto.file;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import ru.soune.nocopy.entity.file.FileAppeal;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -16,5 +17,5 @@ public class FileModerationInfo {
private FileStatus currentStatus; private FileStatus currentStatus;
private LocalDateTime uploadDate; private LocalDateTime uploadDate;
private Boolean hasActiveAppeal; private Boolean hasActiveAppeal;
private AppealInfo activeAppeal; private FileAppeal activeAppeal;
} }
@@ -279,10 +279,14 @@ public class FileEntityHandler implements RequestHandler {
String[] searchTerms = searchQuery.split("\\s+"); String[] searchTerms = searchQuery.split("\\s+");
String filterType = fileRequest.getType(); String filterType = fileRequest.getType();
String dateFilter = fileRequest.getDateFilter(); String dateFilter = fileRequest.getDateFilter();
List<String> statuses = fileRequest.getStatuses();
Boolean isAppeal = fileRequest.getIsAppeal();
List<FileEntityResponse> filteredFiles = allFiles.getFiles().stream() List<FileEntityResponse> filteredFiles = allFiles.getFiles().stream()
.filter(f -> matchesSearch(f, searchTerms, searchQuery, filterType)) .filter(f -> matchesSearch(f, searchTerms, searchQuery, filterType))
.filter(f -> matchesDateFilter(f, dateFilter)) .filter(f -> matchesDateFilter(f, dateFilter))
.filter(f -> matchesStatuses(f, statuses))
.filter(f -> matchesAppeal(f, isAppeal))
.collect(Collectors.toList()); .collect(Collectors.toList());
Comparator<FileEntityResponse> comparator = getComparator(sortBy, sortOrder); Comparator<FileEntityResponse> comparator = getComparator(sortBy, sortOrder);
@@ -309,6 +313,8 @@ public class FileEntityHandler implements RequestHandler {
.pageSize(pageSize) .pageSize(pageSize)
.sortBy(sortBy) .sortBy(sortBy)
.sortOrder(sortOrder.getValue()) .sortOrder(sortOrder.getValue())
.statuses(List.of(FileStatus.BLOCKED.name(), FileStatus.ACTIVE.name(),
FileStatus.MODERATION.name()))
.build(); .build();
return new BaseResponse(request.getMsgId(), return new BaseResponse(request.getMsgId(),
@@ -348,6 +354,22 @@ public class FileEntityHandler implements RequestHandler {
} }
} }
private boolean matchesStatuses(FileEntityResponse file, List<String> statuses) {
if (statuses == null || statuses.isEmpty()) {
return true;
}
return statuses.contains(file.getStatus().name());
}
private boolean matchesAppeal(FileEntityResponse file, Boolean isAppeal) {
if (isAppeal == null) {
return true;
}
return !file.getAppealInfos().isEmpty() == isAppeal;
}
private boolean matchesSearch(FileEntityResponse file, String[] searchTerms, String fullQuery, String type) { private boolean matchesSearch(FileEntityResponse file, String[] searchTerms, String fullQuery, String type) {
String fileName = getSafeString(file.getFileName()); String fileName = getSafeString(file.getFileName());
String originalFileName = getSafeString(file.getOriginalFileName()); String originalFileName = getSafeString(file.getOriginalFileName());
@@ -14,6 +14,7 @@ import ru.soune.nocopy.dto.file.AppealResponse;
import ru.soune.nocopy.dto.file.FileEntityRequest; import ru.soune.nocopy.dto.file.FileEntityRequest;
import ru.soune.nocopy.dto.file.FileModerationInfo; import ru.soune.nocopy.dto.file.FileModerationInfo;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.service.file.moderation.ModerationService; import ru.soune.nocopy.service.file.moderation.ModerationService;
import java.util.Arrays; import java.util.Arrays;
@@ -71,9 +72,11 @@ public class FileInteranlInfoHandler implements RequestHandler {
try { try {
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 0; int page = fileRequest.getPage() != null ? fileRequest.getPage() : 0;
int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
List<String> statuses = fileRequest.getStatuses() == null ? List.of(FileStatus.MODERATION.name(),
FileStatus.BLOCKED.name(), FileStatus.ACTIVE.name()): fileRequest.getStatuses();
Page<FileEntity> files = moderationService.getFilesForModeration(page, pageSize, Page<FileEntity> files = moderationService.getFilesForModeration(page, pageSize,
fileRequest.getSortOrder(), fileRequest.getSortBy()); fileRequest.getSortOrder(), fileRequest.getSortBy(), statuses, fileRequest.getIsAppeal());
List<Map<String, Object>> filesWithInfo = files.getContent().stream() List<Map<String, Object>> filesWithInfo = files.getContent().stream()
.map(file -> { .map(file -> {
@@ -92,11 +95,8 @@ public class FileInteranlInfoHandler implements RequestHandler {
moderationInfoMap.put("hasActiveAppeal", info != null && info.getHasActiveAppeal()); moderationInfoMap.put("hasActiveAppeal", info != null && info.getHasActiveAppeal());
if (info != null && info.getActiveAppeal() != null) { if (info != null && info.getActiveAppeal() != null) {
Map<String, Object> appealInfoMap = new HashMap<>(); AppealResponse appealResponse = moderationService.convertToResponse(info.getActiveAppeal());
appealInfoMap.put("appealId", info.getActiveAppeal().getAppealId()); moderationInfoMap.put("appealInfo", appealResponse);
appealInfoMap.put("status", info.getActiveAppeal().getStatus());
appealInfoMap.put("createdAt", info.getActiveAppeal().getCreatedAt());
moderationInfoMap.put("appealInfo", appealInfoMap);
} else { } else {
moderationInfoMap.put("appealInfo", null); moderationInfoMap.put("appealInfo", null);
} }
@@ -112,6 +112,7 @@ public class FileInteranlInfoHandler implements RequestHandler {
"totalCount", files.getTotalElements(), "totalCount", files.getTotalElements(),
"totalPages", files.getTotalPages(), "totalPages", files.getTotalPages(),
"currentPage", page, "currentPage", page,
"statuses",List.of(FileStatus.ACTIVE.name(), FileStatus.BLOCKED.name(), FileStatus.MODERATION.name()),
"pageSize", pageSize "pageSize", pageSize
); );
@@ -76,6 +76,13 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
Page<FileEntity> findByStatusIn(List<FileStatus> statuses, Pageable pageable); Page<FileEntity> findByStatusIn(List<FileStatus> statuses, Pageable pageable);
@Query("SELECT f FROM FileEntity f WHERE f.status IN :statuses")
Page<FileEntity> findByStatusInAndSorted(@Param("statuses") List<String> statuses, Pageable pageable);
@Query("SELECT f FROM FileEntity f JOIN FileAppeal fa ON fa.fileId = f.id WHERE f.status IN :statuses")
Page<FileEntity> findByStatusInAndSortedWithAppeal(@Param("statuses") List<String> statuses, Pageable pageable);
List<FileEntity> findByUserIdAndStatusIn(Long userId, List<FileStatus> statuses); List<FileEntity> findByUserIdAndStatusIn(Long userId, List<FileStatus> statuses);
@Query("SELECT f FROM FileEntity f WHERE f.userId = :userId AND f.status IN :statuses") @Query("SELECT f FROM FileEntity f WHERE f.userId = :userId AND f.status IN :statuses")
@@ -9,6 +9,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.dto.file.FileResponse; import ru.soune.nocopy.dto.file.FileResponse;
import ru.soune.nocopy.entity.file.FileAppeal;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.ProtectionStatus;
@@ -16,11 +17,13 @@ import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.entity.monitoring.MonitoringType; import ru.soune.nocopy.entity.monitoring.MonitoringType;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.FileEntityNotFoundException; import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.repository.FileAppealRepository;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileMonitoringRepository; import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@@ -29,9 +32,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -47,6 +48,8 @@ public class FileEntityService {
private final CloudStorageService cloudStorageService; private final CloudStorageService cloudStorageService;
private final FileAppealRepository fileAppealRepository;
@Value("${server.baseurl}") @Value("${server.baseurl}")
private String baseUrl; private String baseUrl;
@@ -387,6 +390,8 @@ public class FileEntityService {
name = fileName.substring(0, lastDotIndex); name = fileName.substring(0, lastDotIndex);
} }
List<Map<String, Object>> appeals = getFileAppeals(fileEntity.getId());
return FileEntityResponse.builder() return FileEntityResponse.builder()
.id(fileEntity.getId()) .id(fileEntity.getId())
.userId(fileEntity.getUserId()) .userId(fileEntity.getUserId())
@@ -417,6 +422,30 @@ public class FileEntityService {
.protectedFilePath(fileEntity.getProtectedFilePath()) .protectedFilePath(fileEntity.getProtectedFilePath())
.thumbnailFileUrl(baseUrl + "/api/files/protected/" + fileEntity.getId() + "/thumbnail") .thumbnailFileUrl(baseUrl + "/api/files/protected/" + fileEntity.getId() + "/thumbnail")
.permissions(fileEntity.getPermissions()) .permissions(fileEntity.getPermissions())
.appealInfos(appeals)
.build(); .build();
} }
private List<Map<String, Object>> getFileAppeals(String fileId) {
List<FileAppeal> appeals = fileAppealRepository.findByFileId(fileId);
if (appeals == null || appeals.isEmpty()) {
return Collections.emptyList();
}
return appeals.stream()
.map(appeal -> {
Map<String, Object> appealMap = new HashMap<>();
appealMap.put("id", appeal.getId());
appealMap.put("userId", appeal.getUserId());
appealMap.put("appealReason", appeal.getAppealReason());
appealMap.put("additionalInfo", appeal.getAdditionalInfo());
appealMap.put("status", appeal.getStatus() != null ? appeal.getStatus().name() : null);
appealMap.put("adminComment", appeal.getAdminComment());
appealMap.put("moderatorId", appeal.getModeratorId());
appealMap.put("createdAt", appeal.getCreatedAt());
appealMap.put("resolvedAt", appeal.getResolvedAt());
return appealMap;
})
.collect(Collectors.toList());
}
} }
@@ -207,12 +207,7 @@ public class ModerationService {
.currentStatus(file.getStatus()) .currentStatus(file.getStatus())
.uploadDate(file.getCreatedAt()) .uploadDate(file.getCreatedAt())
.hasActiveAppeal(activeAppeal != null) .hasActiveAppeal(activeAppeal != null)
.activeAppeal(activeAppeal != null ? .activeAppeal(activeAppeal)
AppealInfo.builder()
.appealId(activeAppeal.getId())
.status(activeAppeal.getStatus().name())
.createdAt(activeAppeal.getCreatedAt())
.build() : null)
.build(); .build();
} }
@@ -229,7 +224,8 @@ public class ModerationService {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Page<FileEntity> getFilesForModeration(int page, int size, String sortDirection, String sortBy) { public Page<FileEntity> getFilesForModeration(int page, int size, String sortDirection, String sortBy,
List<String> statuses, Boolean isAppeal) {
if (sortDirection == null || sortDirection.isEmpty()) { if (sortDirection == null || sortDirection.isEmpty()) {
sortDirection = "desc"; sortDirection = "desc";
} }
@@ -240,7 +236,8 @@ public class ModerationService {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDirection), sortBy)); Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDirection), sortBy));
return fileEntityRepository.findByStatusIn(Arrays.asList(FileStatus.MODERATION, FileStatus.BLOCKED), pageable); return isAppeal != null ? fileEntityRepository.findByStatusInAndSortedWithAppeal(statuses, pageable):
fileEntityRepository.findByStatusInAndSorted(statuses, pageable);
} }
@@ -268,7 +265,7 @@ public class ModerationService {
.map(this::convertToResponse); .map(this::convertToResponse);
} }
private AppealResponse convertToResponse(FileAppeal appeal) { public AppealResponse convertToResponse(FileAppeal appeal) {
return AppealResponse.builder() return AppealResponse.builder()
.appealId(appeal.getId()) .appealId(appeal.getId())
.fileId(appeal.getFileId()) .fileId(appeal.getFileId())