@@ -1,7 +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;
|
||||
import com.vrt.fileprotection.NoCopyCheckResult;
|
||||
@@ -9,24 +7,20 @@ import com.vrt.fileprotection.audio.AudioCheckResult;
|
||||
import com.vrt.fileprotection.documents.DocumentCheckResult;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
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 org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
import ru.soune.nocopy.dto.BaseRequest;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.docviewer.DocViewerRequest;
|
||||
import ru.soune.nocopy.dto.file.*;
|
||||
import ru.soune.nocopy.dto.register.RegAnswer;
|
||||
import ru.soune.nocopy.entity.company.Company;
|
||||
@@ -41,6 +35,7 @@ import ru.soune.nocopy.repository.AuthTokenRepository;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.FileSimilarityService;
|
||||
import ru.soune.nocopy.service.dockview.DockViewService;
|
||||
import ru.soune.nocopy.service.file.*;
|
||||
import ru.soune.nocopy.service.file.CheckCounterService;
|
||||
import ru.soune.nocopy.service.file.ProtectionsLimitService;
|
||||
@@ -48,14 +43,8 @@ import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.service.register.AuthService;
|
||||
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -92,7 +81,7 @@ public class ApiController {
|
||||
|
||||
private final ZipService zipService;
|
||||
|
||||
private final UserVerificationService userVerificationService;
|
||||
private final DockViewService dockViewService;
|
||||
|
||||
@PostMapping("/v{version}/data")
|
||||
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
|
||||
@@ -511,27 +500,6 @@ public class ApiController {
|
||||
MessageCode.FILE_DELETE.getDescription(),
|
||||
errorData));
|
||||
}
|
||||
//TODO
|
||||
|
||||
// if (!entityResponse.isExistsOnDisk()) {
|
||||
// Map<String, Object> errorData = new HashMap<>();
|
||||
// errorData.put("onDisk", entityResponse.isExistsOnDisk());
|
||||
//
|
||||
// return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
// MessageCode.FILE_NOT_EXIST.getCode(),
|
||||
// MessageCode.FILE_NOT_EXIST.getDescription(),
|
||||
// errorData));
|
||||
// }
|
||||
|
||||
// if (!file.exists()) {
|
||||
// Map<String, Object> errorData = new HashMap<>();
|
||||
// errorData.put("file", file.exists());
|
||||
//
|
||||
// return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
|
||||
// errorData));
|
||||
// }
|
||||
|
||||
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
|
||||
|
||||
@@ -556,16 +524,54 @@ public class ApiController {
|
||||
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
|
||||
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
|
||||
errorData));
|
||||
// } catch (IOException e) {
|
||||
// Map<String, Object> errorData = new HashMap<>();
|
||||
// errorData.put("token", tokenHeader);
|
||||
// errorData.put("fileId", fileId);
|
||||
// errorData.put("version", version);
|
||||
//
|
||||
// return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
|
||||
// errorData));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/file/link")
|
||||
public ResponseEntity<?> downloadFile(@RequestBody DocViewerRequest viewerRequest) {
|
||||
try {
|
||||
String tokenHeader = viewerRequest.getToken();
|
||||
Long userId = tokenHeader == null ? 0L: authService.useUserAuthToken(tokenHeader);
|
||||
|
||||
FileEntityResponse entityResponse = fileEntityService.getById(viewerRequest.getFileId(), 0);
|
||||
FileStatus status = entityResponse.getStatus();
|
||||
|
||||
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
|
||||
status.equals(FileStatus.REMOVED)) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("fileId", entityResponse.getId());
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(0,
|
||||
MessageCode.FILE_IS_BLOCKED.getCode(),
|
||||
MessageCode.FILE_IS_BLOCKED.getDescription(),
|
||||
errorData));
|
||||
}
|
||||
|
||||
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
|
||||
|
||||
dockViewService.createDockView(viewerRequest, userId);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
|
||||
.body(bytes);
|
||||
} catch (FileEntityNotFoundException e) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("fileId", viewerRequest.getFileId());
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(0,
|
||||
MessageCode.FILE_NOT_FOUND.getCode(),
|
||||
MessageCode.FILE_NOT_FOUND.getDescription(),
|
||||
errorData));
|
||||
} catch (NotFoundAuthToken e) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("token", viewerRequest.getFileId());
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(0,
|
||||
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
|
||||
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
|
||||
errorData));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package ru.soune.nocopy.dto.docviewer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DocViewerRequest {
|
||||
@JsonProperty("file_id")
|
||||
private String fileId;
|
||||
|
||||
@JsonProperty("token")
|
||||
private String token;
|
||||
|
||||
@JsonProperty("ip")
|
||||
private String ip;
|
||||
|
||||
@JsonProperty("user_agent")
|
||||
private String userAgent;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package ru.soune.nocopy.dto.docviewer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class DockViewStatsResponse {
|
||||
private String fileId;
|
||||
|
||||
private Long totalViews;
|
||||
|
||||
private Long uniqueIps;
|
||||
|
||||
private Long uniqueUsers;
|
||||
|
||||
private LocalDateTime firstViewDate;
|
||||
|
||||
private LocalDateTime lastViewDate;
|
||||
|
||||
private List<DockViewStatsResponse> content;
|
||||
|
||||
private Integer pageNumber;
|
||||
|
||||
private Integer pageSize;
|
||||
|
||||
private Long totalElements;
|
||||
|
||||
private Integer totalPages;
|
||||
|
||||
private Boolean isLast;
|
||||
|
||||
private Boolean isFirst;
|
||||
|
||||
private Boolean hasNext;
|
||||
|
||||
private Boolean hasPrevious;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ru.soune.nocopy.entity.docviewer;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Table(name = "dock_view")
|
||||
@RequiredArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
@Setter @Getter
|
||||
public class DockView {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "viewer_id")
|
||||
private Long viewerId;
|
||||
|
||||
@Column(name = "viewer_ip")
|
||||
private String viewerIp;
|
||||
|
||||
@Column(name = "user_agent")
|
||||
private String userAgent;
|
||||
|
||||
@Column(name = "file_id")
|
||||
private String fileId;
|
||||
|
||||
@Column(name = "created_at",updatable = false)
|
||||
private LocalDateTime createdAt = LocalDateTime.now();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package ru.soune.nocopy.repository;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
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.nocopy.entity.docviewer.DockView;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface DockViewRepository extends JpaRepository<DockView, Long> {
|
||||
|
||||
List<DockView> findAllByFileId(String fileId);
|
||||
|
||||
List<DockView> findAllByViewerId(Long viewerId);
|
||||
|
||||
Optional<DockView> findByFileIdAndViewerId(String fileId, Long viewerId);
|
||||
|
||||
List<DockView> findAllByViewerIp(String viewerIp);
|
||||
|
||||
Page<DockView> findAllByFileIdOrderByCreatedAtDesc(String fileId, Pageable pageable);
|
||||
|
||||
Page<DockView> findAllByViewerIdOrderByCreatedAtDesc(Long viewerId, Pageable pageable);
|
||||
|
||||
long countByFileId(String fileId);
|
||||
|
||||
@Query("SELECT COUNT(DISTINCT dv.viewerIp) FROM DockView dv WHERE dv.fileId = :fileId")
|
||||
long countUniqueIpsByFileId(@Param("fileId") String fileId);
|
||||
|
||||
@Query("SELECT COUNT(DISTINCT dv.viewerId) FROM DockView dv WHERE dv.fileId = :fileId")
|
||||
long countUniqueViewersByFileId(@Param("fileId") String fileId);
|
||||
|
||||
@Query("SELECT MAX(dv.createdAt) FROM DockView dv WHERE dv.fileId = :fileId")
|
||||
Optional<LocalDateTime> findLastViewDateByFileId(@Param("fileId") String fileId);
|
||||
|
||||
|
||||
@Query("SELECT dv.fileId, " +
|
||||
"COUNT(dv) as totalViews, " +
|
||||
"COUNT(DISTINCT dv.viewerIp) as uniqueIps, " +
|
||||
"MAX(dv.createdAt) as lastViewDate " +
|
||||
"FROM DockView dv " +
|
||||
"GROUP BY dv.fileId " +
|
||||
"ORDER BY lastViewDate DESC")
|
||||
Page<Object[]> findViewsStatsPaginated(Pageable pageable);
|
||||
|
||||
@Query("SELECT dv.fileId, " +
|
||||
"COUNT(dv) as totalViews, " +
|
||||
"COUNT(DISTINCT dv.viewerIp) as uniqueIps, " +
|
||||
"COUNT(DISTINCT dv.viewerId) as uniqueUsers, " +
|
||||
"MAX(dv.createdAt) as lastViewDate, " +
|
||||
"MIN(dv.createdAt) as firstViewDate " +
|
||||
"FROM DockView dv " +
|
||||
"WHERE dv.fileId = :fileId " +
|
||||
"GROUP BY dv.fileId")
|
||||
Optional<Object[]> findViewsStatsByFileId(@Param("fileId") String fileId);
|
||||
|
||||
void deleteAllByFileId(String fileId);
|
||||
|
||||
void deleteAllByCreatedAtBefore(LocalDateTime date);
|
||||
|
||||
List<DockView> findAllByFileIdAndCreatedAtBetween(
|
||||
String fileId,
|
||||
LocalDateTime startDate,
|
||||
LocalDateTime endDate
|
||||
);
|
||||
|
||||
boolean existsByFileIdAndViewerIp(String fileId, String viewerIp);
|
||||
|
||||
List<DockView> findTop10ByFileIdOrderByCreatedAtDesc(String fileId);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package ru.soune.nocopy.service.dockview;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.soune.nocopy.dto.docviewer.DocViewerRequest;
|
||||
import ru.soune.nocopy.dto.docviewer.DockViewStatsResponse;
|
||||
import ru.soune.nocopy.entity.docviewer.DockView;
|
||||
import ru.soune.nocopy.repository.DockViewRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DockViewService {
|
||||
|
||||
private final DockViewRepository dockViewRepository;
|
||||
|
||||
@Transactional
|
||||
public DockView createDockView(DocViewerRequest request, Long userId) {
|
||||
DockView dockView = new DockView();
|
||||
dockView.setViewerId(userId);
|
||||
dockView.setViewerIp(request.getIp() != null ? request.getIp() : "-");
|
||||
dockView.setUserAgent(request.getUserAgent() != null ? request.getUserAgent() : "-");
|
||||
dockView.setFileId(request.getFileId());
|
||||
dockView.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
DockView saved = dockViewRepository.save(dockView);
|
||||
|
||||
log.info("Created dock view record: fileId={}, viewerId={}, ip={}",
|
||||
saved.getFileId(), saved.getViewerId(), saved.getViewerIp());
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
|
||||
public DockViewStatsResponse getStatsByFileId(String fileId) {
|
||||
Optional<Object[]> statsOpt = dockViewRepository.findViewsStatsByFileId(fileId);
|
||||
|
||||
if (statsOpt.isEmpty()) {
|
||||
return DockViewStatsResponse.builder()
|
||||
.fileId(fileId)
|
||||
.totalViews(0L)
|
||||
.uniqueIps(0L)
|
||||
.uniqueUsers(0L)
|
||||
.firstViewDate(null)
|
||||
.lastViewDate(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
Object[] stats = statsOpt.get();
|
||||
return DockViewStatsResponse.builder()
|
||||
.fileId((String) stats[0])
|
||||
.totalViews((Long) stats[1])
|
||||
.uniqueIps((Long) stats[2])
|
||||
.uniqueUsers((Long) stats[3])
|
||||
.lastViewDate((LocalDateTime) stats[4])
|
||||
.firstViewDate((LocalDateTime) stats[5])
|
||||
.build();
|
||||
}
|
||||
|
||||
public DockViewStatsResponse getBasicStatsByFileId(String fileId) {
|
||||
long totalViews = dockViewRepository.countByFileId(fileId);
|
||||
long uniqueIps = dockViewRepository.countUniqueIpsByFileId(fileId);
|
||||
|
||||
return DockViewStatsResponse.builder()
|
||||
.fileId(fileId)
|
||||
.totalViews(totalViews)
|
||||
.uniqueIps(uniqueIps)
|
||||
.build();
|
||||
}
|
||||
|
||||
public DockViewStatsResponse getAllFilesStatsPaginated(Pageable pageable) {
|
||||
Page<Object[]> statsPage = dockViewRepository.findViewsStatsPaginated(pageable);
|
||||
|
||||
List<DockViewStatsResponse> statsList = statsPage.getContent().stream()
|
||||
.map(row -> DockViewStatsResponse.builder()
|
||||
.fileId((String) row[0])
|
||||
.totalViews((Long) row[1])
|
||||
.uniqueIps((Long) row[2])
|
||||
.lastViewDate((LocalDateTime) row[3])
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return DockViewStatsResponse.builder()
|
||||
.content(statsList)
|
||||
.pageNumber(statsPage.getNumber())
|
||||
.pageSize(statsPage.getSize())
|
||||
.totalElements(statsPage.getTotalElements())
|
||||
.totalPages(statsPage.getTotalPages())
|
||||
.isLast(statsPage.isLast())
|
||||
.isFirst(statsPage.isFirst())
|
||||
.hasNext(statsPage.hasNext())
|
||||
.hasPrevious(statsPage.hasPrevious())
|
||||
.build();
|
||||
}
|
||||
|
||||
public boolean hasViewedFromIp(String fileId, String ip) {
|
||||
return dockViewRepository.existsByFileIdAndViewerIp(fileId, ip);
|
||||
}
|
||||
|
||||
public Optional<LocalDateTime> getLastViewDate(String fileId) {
|
||||
return dockViewRepository.findLastViewDateByFileId(fileId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteAllViewsByFileId(String fileId) {
|
||||
dockViewRepository.deleteAllByFileId(fileId);
|
||||
log.info("Deleted all views for fileId: {}", fileId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void cleanOldViews() {
|
||||
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
|
||||
dockViewRepository.deleteAllByCreatedAtBefore(thirtyDaysAgo);
|
||||
log.info("Cleaned views older than: {}", thirtyDaysAgo);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user