8 Commits
Author SHA1 Message Date
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 109 additions and 36 deletions
@@ -1,6 +1,5 @@
package ru.soune.nocopy.controller;
import com.google.api.client.util.IOUtils;
import com.google.common.io.ByteStreams;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
@@ -12,28 +11,23 @@ import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
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.ProtectionStatus;
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService;
import ru.soune.nocopy.service.file.ImageResizeService;
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.util.FileUtil;
import software.amazon.awssdk.services.mq.model.NotFoundException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/files")
@Slf4j
@@ -48,7 +42,7 @@ public class FileController {
private UserRepository userRepository;
private ImageResizeService imageResizeService;
private final FileEntityService fileEntityService;
private NoCopyFileService noCopyFileService;
@@ -96,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) {
try {
return MediaType.parseMediaType(fileEntity.getMimeType());
@@ -124,13 +136,6 @@ public class FileController {
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 ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected");
@@ -81,4 +81,10 @@ public class FileEntityRequest {
@JsonProperty("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 java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Data
@@ -45,4 +46,5 @@ public class FileEntityResponse {
private LocalDateTime fileUploadDate;
private String monitoring;
private Map<PermissionType, Boolean> permissions;
private List<Map<String, Object>> appealInfos;
}
@@ -36,4 +36,7 @@ public class FileListResponse {
@JsonProperty("total_size")
private Long totalSize;
@JsonProperty("statuses")
private List<String> statuses;
}
@@ -2,6 +2,7 @@ package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.file.FileAppeal;
import ru.soune.nocopy.entity.file.FileStatus;
import java.time.LocalDateTime;
@@ -16,5 +17,5 @@ public class FileModerationInfo {
private FileStatus currentStatus;
private LocalDateTime uploadDate;
private Boolean hasActiveAppeal;
private AppealInfo activeAppeal;
private FileAppeal activeAppeal;
}
@@ -283,10 +283,14 @@ public class FileEntityHandler implements RequestHandler {
String[] searchTerms = searchQuery.split("\\s+");
String filterType = fileRequest.getType();
String dateFilter = fileRequest.getDateFilter();
List<String> statuses = fileRequest.getStatuses();
Boolean isAppeal = fileRequest.getIsAppeal();
List<FileEntityResponse> filteredFiles = allFiles.getFiles().stream()
.filter(f -> matchesSearch(f, searchTerms, searchQuery, filterType))
.filter(f -> matchesDateFilter(f, dateFilter))
.filter(f -> matchesStatuses(f, statuses))
.filter(f -> matchesAppeal(f, isAppeal))
.collect(Collectors.toList());
Comparator<FileEntityResponse> comparator = getComparator(sortBy, sortOrder);
@@ -313,6 +317,8 @@ public class FileEntityHandler implements RequestHandler {
.pageSize(pageSize)
.sortBy(sortBy)
.sortOrder(sortOrder.getValue())
.statuses(List.of(FileStatus.BLOCKED.name(), FileStatus.ACTIVE.name(),
FileStatus.MODERATION.name()))
.build();
return new BaseResponse(request.getMsgId(),
@@ -352,6 +358,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) {
String fileName = getSafeString(file.getFileName());
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.FileModerationInfo;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.service.file.moderation.ModerationService;
import java.util.Arrays;
@@ -71,9 +72,11 @@ public class FileInteranlInfoHandler implements RequestHandler {
try {
int page = fileRequest.getPage() != null ? fileRequest.getPage() : 0;
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,
fileRequest.getSortOrder(), fileRequest.getSortBy());
fileRequest.getSortOrder(), fileRequest.getSortBy(), statuses, fileRequest.getIsAppeal());
List<Map<String, Object>> filesWithInfo = files.getContent().stream()
.map(file -> {
@@ -92,11 +95,8 @@ public class FileInteranlInfoHandler implements RequestHandler {
moderationInfoMap.put("hasActiveAppeal", info != null && info.getHasActiveAppeal());
if (info != null && info.getActiveAppeal() != null) {
Map<String, Object> appealInfoMap = new HashMap<>();
appealInfoMap.put("appealId", info.getActiveAppeal().getAppealId());
appealInfoMap.put("status", info.getActiveAppeal().getStatus());
appealInfoMap.put("createdAt", info.getActiveAppeal().getCreatedAt());
moderationInfoMap.put("appealInfo", appealInfoMap);
AppealResponse appealResponse = moderationService.convertToResponse(info.getActiveAppeal());
moderationInfoMap.put("appealInfo", appealResponse);
} else {
moderationInfoMap.put("appealInfo", null);
}
@@ -112,6 +112,7 @@ public class FileInteranlInfoHandler implements RequestHandler {
"totalCount", files.getTotalElements(),
"totalPages", files.getTotalPages(),
"currentPage", page,
"statuses",List.of(FileStatus.ACTIVE.name(), FileStatus.BLOCKED.name(), FileStatus.MODERATION.name()),
"pageSize", pageSize
);
@@ -76,6 +76,13 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
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);
@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 ru.soune.nocopy.dto.file.FileEntityResponse;
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.FileStatus;
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.user.User;
import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.repository.FileAppealRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@@ -29,9 +32,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@@ -47,6 +48,8 @@ public class FileEntityService {
private final CloudStorageService cloudStorageService;
private final FileAppealRepository fileAppealRepository;
@Value("${server.baseurl}")
private String baseUrl;
@@ -387,6 +390,8 @@ public class FileEntityService {
name = fileName.substring(0, lastDotIndex);
}
List<Map<String, Object>> appeals = getFileAppeals(fileEntity.getId());
return FileEntityResponse.builder()
.id(fileEntity.getId())
.userId(fileEntity.getUserId())
@@ -417,6 +422,30 @@ public class FileEntityService {
.protectedFilePath(fileEntity.getProtectedFilePath())
.thumbnailFileUrl(baseUrl + "/api/files/protected/" + fileEntity.getId() + "/thumbnail")
.permissions(fileEntity.getPermissions())
.appealInfos(appeals)
.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())
.uploadDate(file.getCreatedAt())
.hasActiveAppeal(activeAppeal != null)
.activeAppeal(activeAppeal != null ?
AppealInfo.builder()
.appealId(activeAppeal.getId())
.status(activeAppeal.getStatus().name())
.createdAt(activeAppeal.getCreatedAt())
.build() : null)
.activeAppeal(activeAppeal)
.build();
}
@@ -229,7 +224,8 @@ public class ModerationService {
}
@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()) {
sortDirection = "desc";
}
@@ -240,7 +236,8 @@ public class ModerationService {
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);
}
private AppealResponse convertToResponse(FileAppeal appeal) {
public AppealResponse convertToResponse(FileAppeal appeal) {
return AppealResponse.builder()
.appealId(appeal.getId())
.fileId(appeal.getFileId())