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.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.nocopy.dto.docviewer.DocViewerRequest; import ru.soune.nocopy.dto.docviewer.DockViewResponse; import ru.soune.nocopy.dto.docviewer.DockViewStatsResponse; import ru.soune.nocopy.entity.docviewer.DockView; import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.repository.DockViewRepository; import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.service.geo.GeoCountryService; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor public class DockViewService { private final DockViewRepository dockViewRepository; private final GeoCountryService geoCountryService; private final FileEntityRepository fileEntityRepository; private final UserRepository userRepository; @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 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 List getStatsByFileIds(List fileIds) { List statsList = dockViewRepository.findViewsStatsByFileIds(fileIds); Map 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 fileIds) { Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDirection), sortBy)); Page dockViews = dockViewRepository.findAllByFileIds(fileIds, pageable); if (dockViews.isEmpty()) { return DockViewStatsResponse.builder() .build(); } List 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(); } 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 statsPage = dockViewRepository.findViewsStatsPaginated(pageable); List 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 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); } }