2026-04-30 18:24:19 +07:00
|
|
|
package ru.soune.nocopy.service.dockview;
|
|
|
|
|
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.data.domain.Page;
|
2026-05-05 13:44:49 +07:00
|
|
|
import org.springframework.data.domain.PageRequest;
|
2026-04-30 18:24:19 +07:00
|
|
|
import org.springframework.data.domain.Pageable;
|
2026-05-05 13:44:49 +07:00
|
|
|
import org.springframework.data.domain.Sort;
|
2026-04-30 18:24:19 +07:00
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
import ru.soune.nocopy.dto.docviewer.DocViewerRequest;
|
2026-05-05 13:44:49 +07:00
|
|
|
import ru.soune.nocopy.dto.docviewer.DockViewResponse;
|
2026-04-30 18:24:19 +07:00
|
|
|
import ru.soune.nocopy.dto.docviewer.DockViewStatsResponse;
|
|
|
|
|
import ru.soune.nocopy.entity.docviewer.DockView;
|
2026-05-05 13:44:49 +07:00
|
|
|
import ru.soune.nocopy.entity.file.FileEntity;
|
|
|
|
|
import ru.soune.nocopy.entity.user.User;
|
2026-04-30 18:24:19 +07:00
|
|
|
import ru.soune.nocopy.repository.DockViewRepository;
|
2026-05-05 13:44:49 +07:00
|
|
|
import ru.soune.nocopy.repository.FileEntityRepository;
|
|
|
|
|
import ru.soune.nocopy.repository.UserRepository;
|
|
|
|
|
import ru.soune.nocopy.service.geo.GeoCountryService;
|
2026-04-30 18:24:19 +07:00
|
|
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
@Service
|
|
|
|
|
@Slf4j
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class DockViewService {
|
|
|
|
|
|
|
|
|
|
private final DockViewRepository dockViewRepository;
|
|
|
|
|
|
2026-05-05 13:44:49 +07:00
|
|
|
private final GeoCountryService geoCountryService;
|
|
|
|
|
|
|
|
|
|
private final FileEntityRepository fileEntityRepository;
|
|
|
|
|
|
|
|
|
|
private final UserRepository userRepository;
|
|
|
|
|
|
2026-04-30 18:24:19 +07:00
|
|
|
@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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:44:49 +07:00
|
|
|
public List<DockViewStatsResponse> getStatsByFileIds(List<String> fileIds) {
|
|
|
|
|
List<Object[]> statsList = dockViewRepository.findViewsStatsByFileIds(fileIds);
|
|
|
|
|
|
|
|
|
|
Map<String, Object[]> statsMap = statsList.stream()
|
|
|
|
|
.collect(Collectors.toMap(
|
|
|
|
|
stats -> (String) stats[0],
|
|
|
|
|
stats -> stats
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return fileIds.stream()
|
|
|
|
|
.map(fileId -> {
|
|
|
|
|
Object[] stats = statsMap.get(fileId);
|
|
|
|
|
|
|
|
|
|
if (stats == null) {
|
|
|
|
|
return DockViewStatsResponse.builder()
|
|
|
|
|
.fileId(fileId)
|
|
|
|
|
.totalViews(0L)
|
|
|
|
|
.uniqueIps(0L)
|
|
|
|
|
.uniqueUsers(0L)
|
|
|
|
|
.firstViewDate(null)
|
|
|
|
|
.lastViewDate(null)
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
})
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public DockViewStatsResponse getOpenLastHistory(int page, int size, String sortDirection, String sortBy,
|
|
|
|
|
List<String> fileIds) {
|
|
|
|
|
Pageable pageable = PageRequest.of(page, size,
|
|
|
|
|
Sort.by(Sort.Direction.fromString(sortDirection), sortBy));
|
|
|
|
|
|
|
|
|
|
Page<DockView> dockViews = dockViewRepository.findAllByFileId(pageable, fileIds);
|
|
|
|
|
|
|
|
|
|
if (dockViews.isEmpty()) {
|
|
|
|
|
return DockViewStatsResponse.builder()
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<DockViewResponse> history = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
for (DockView dockView: dockViews) {
|
|
|
|
|
String country = geoCountryService.getCountryByIp(dockView.getViewerIp());
|
|
|
|
|
String city = geoCountryService.getCityByIp(dockView.getViewerIp());
|
|
|
|
|
LocalDateTime createdAt = dockView.getCreatedAt();
|
|
|
|
|
String fileId = dockView.getFileId();
|
|
|
|
|
|
|
|
|
|
FileEntity file = fileEntityRepository.findByFileId(fileId);
|
|
|
|
|
Long viewerId = dockView.getViewerId();
|
|
|
|
|
User viewer = userRepository.findById(viewerId).orElse(null);
|
|
|
|
|
|
|
|
|
|
DockViewResponse response = DockViewResponse.builder()
|
|
|
|
|
.city(city)
|
|
|
|
|
.country(country)
|
|
|
|
|
.viewDate(createdAt)
|
|
|
|
|
.document(file.getOriginalFileName())
|
|
|
|
|
.viewer(viewer == null ? "guest" : viewer.getFullName())
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
history.add(response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DockViewStatsResponse.builder()
|
|
|
|
|
.history(history)
|
|
|
|
|
.pageNumber(dockViews.getNumber())
|
|
|
|
|
.pageSize(dockViews.getSize())
|
|
|
|
|
.totalPages(dockViews.getTotalPages())
|
|
|
|
|
.totalElements(dockViews.getTotalElements())
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-30 18:24:19 +07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|