Merge branch 'dev' into NCBACK-35

# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
This commit is contained in:
vladp
2026-04-16 16:25:36 +07:00
114 changed files with 3982 additions and 160 deletions
@@ -25,4 +25,16 @@ public interface ComplaintEntityRepository extends JpaRepository<ComplaintEntity
@Transactional
@Query("UPDATE ComplaintEntity c SET c.status = :status WHERE c.id = :id")
int updateStatus(Long id, ComplaintStatus status);
@Query("SELECT COUNT(c.id) FROM ComplaintEntity c")
Long getTotalComplaintsCount();
@Query("""
SELECT COUNT(c.id)
FROM ComplaintEntity c
WHERE NOT EXISTS (
SELECT 1 FROM LawCase lc WHERE lc.violationId = c.violation.id
)
""")
Long getComplaintsWithoutLawCaseCount();
}
@@ -6,10 +6,12 @@ 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.dto.statistic.UserFilesStatisticResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -69,4 +71,64 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
Page<FileEntity> findByStatusIn(List<FileStatus> 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")
List<FileEntity> findByStatuses(Long userId, @Param("statuses") List<String> statuses);
@Query("SELECT DISTINCT f FROM FileEntity f " +
"LEFT JOIN FileMonitoringEntity m ON f.id = m.file.id " +
"WHERE f.userId IN :userIds " +
"AND f.status IN :statuses")
Page<FileEntity> findFiles(
@Param("userIds") List<Long> userIds,
@Param("statuses") List<FileStatus> statuses,
Pageable pageable);
@Query("SELECT SUM(f.fileSize) FROM FileEntity f " +
"WHERE f.userId IN :userIds " +
"AND f.status IN :statuses")
Long sumFileSize(
@Param("userIds") List<Long> userIds,
@Param("statuses") List<FileStatus> statuses);
@Query("""
SELECT new ru.soune.nocopy.dto.statistic.UserFilesStatisticResponse(
u.fullName,
u.Id,
CAST(COUNT(f.id) AS int),
COALESCE(SUM(f.fileSize), 0L)
)
FROM FileEntity f
JOIN User u ON f.userId = u.Id
WHERE
(:mimeType IS NULL OR :mimeType = '' OR f.mimeType LIKE CONCAT(:mimeType, '%'))
GROUP BY u.Id, u.fullName
ORDER BY COUNT(f.id) DESC
LIMIT :topUsers
""")
List<UserFilesStatisticResponse> findTopUsers(
@Param("topUsers") int topUsers,
@Param("mimeType") String mimeType
);
@Query("""
SELECT
COUNT(f.id),
COALESCE(SUM(f.fileSize), 0)
FROM FileEntity f
WHERE f.protectionStatus = 'PROTECTED'
""")
List<Object[]> getProtectedFilesTotalStats();
@Query(value = """
SELECT
user_id,
COUNT(*) as file_count,
SUM(file_size) as total_size
FROM file_entities
WHERE protection_status = 'PROTECTED'
GROUP BY user_id
ORDER BY file_count
""", nativeQuery = true)
List<Object[]> getProtectedFilesPerUserStats();
}
@@ -56,7 +56,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f
ON f.id = h.file_id
WHERE ref.file_id = :fileId AND f.user_id = :userId
AND f.status = 'ACTIVE'
AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""",
nativeQuery = true)
List<SimilarImageProjection> findCandidatesFromUserFiles(
@@ -82,7 +82,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f
ON f.id = h.file_id
WHERE ref.file_id = :fileId AND f.user_id IN :userIds
AND f.status = 'ACTIVE'
AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""",
nativeQuery = true)
List<SimilarImageProjection> findCandidatesFromUserFiles(
@@ -108,7 +108,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f ON f.id = h.file_id
WHERE h.hash64_hi = :hash64Hi
AND h.hash64_lo = :hash64Lo
AND f.status = 'ACTIVE'
AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""",
nativeQuery = true)
List<SimilarImageProjection> findExactDuplicates(
@@ -0,0 +1,45 @@
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.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.util.List;
@Repository
public interface LawCaseRepository extends JpaRepository<LawCase, Long> {
@Query("SELECT l FROM LawCase l WHERE l.user.Id = :userId")
List<LawCase> getUserLawCases(@Param("userId") Long userId, Pageable pageable);
// @Query("SELECT l FROM LawCase l WHERE l.user.Id = :userId " +
// "AND (:type IS NULL OR l.type = :type) " +
// "AND (:lawyer IS NULL OR l.lawyer = :lawyer) " +
// "AND (:priority IS NULL OR l.priority = :priority)")
// List<LawCase> getUserLawCases(@Param("userId") Long userId,
// @Param("type") LawCaseType type,
// @Param("lawyer") String lawyer,
// @Param("priority") LawCasePriority priority,
// Pageable pageable);
@Query("SELECT l FROM LawCase l WHERE l.user.Id = :userId " +
"AND (:type IS NULL OR l.type = :type) " +
"AND (:lawyer IS NULL OR l.lawyer = :lawyer) " +
"AND (:violationId IS NULL OR l.violationId = :violationId) " +
"AND (:priority IS NULL OR l.priority = :priority)")
Page<LawCase> getUserLawCases(@Param("userId") Long userId,
@Param("type") LawCaseType type,
@Param("lawyer") String lawyer,
@Param("priority") LawCasePriority priority,
@Param("violationId") Long violationId,
Pageable pageable);
@Query("SELECT COUNT(lc.id) FROM LawCase lc")
Long getTotalLawCasesCount();
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
import java.util.List;
@Repository
public interface ModerationFileRepository extends CrudRepository<ModerationPassportFile, Long> {
public List<ModerationPassportFile> findByStatus(String status);
}
@@ -11,6 +11,7 @@ import ru.soune.nocopy.entity.notification.NotificationStatus;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
import java.util.List;
public interface NotificationRepository extends JpaRepository<Notification, Long> {
@@ -29,12 +30,13 @@ public interface NotificationRepository extends JpaRepository<Notification, Long
long countByUserAndStatus(User user, NotificationStatus status);
@Modifying
@Query("UPDATE Notification n SET n.status = :newStatus WHERE n.id IN :ids AND n.user = :user AND " +
@Query("UPDATE Notification n SET n.status = :newStatus , n.updatedAt = :updateTime WHERE n.id IN :ids AND n.user = :user AND " +
"n.status = :oldStatus")
int updateStatus(@Param("ids") List<Long> ids,
@Param("user") User user,
@Param("oldStatus") NotificationStatus oldStatus,
@Param("newStatus") NotificationStatus newStatus);
@Param("newStatus") NotificationStatus newStatus,
@Param("updateTime") LocalDateTime updateTime);
Page<Notification> getNotificationsByUser(User user, Pageable pageable);
}
@@ -1,6 +1,7 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.payment.Payment;
@@ -11,4 +12,23 @@ import java.util.Optional;
public interface PaymentRepository extends JpaRepository<Payment, String> {
Optional<Payment> findByPaymentUuid(String paymentUuid);
List<Payment> findByUserId(Long userId);
@Query("""
SELECT COALESCE(SUM(p.tariff.tokens), 0)
FROM Payment p
WHERE p.operationType = 'TOKEN'
AND p.status = 'SUCCEEDED'
""")
Long getTotalTokensBought();
@Query("""
SELECT
p.user.Id,
COALESCE(SUM(p.tariff.tokens), 0)
FROM Payment p
WHERE p.operationType = 'TOKEN'
AND p.status = 'SUCCEEDED'
GROUP BY p.user.Id
""")
List<Object[]> getTokensBoughtPerUser();
}
@@ -43,4 +43,22 @@ public interface ReferralJpaRepository extends JpaRepository<Referral, Long> {
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId")
int countTotalInvitees(@Param("userId") Long userId);
@Query("SELECT COALESCE(SUM(r.totalIncome), 0) FROM Referral r")
Long getTotalIncome();
@Query("SELECT COALESCE(SUM(r.availableIncome), 0) FROM Referral r")
Long getTotalAvailableIncome();
@Query("SELECT COALESCE(SUM(r.holdBalance), 0) FROM Referral r")
Long getTotalHoldBalance();
@Query("""
SELECT
r.userId,
r.totalIncome
FROM Referral r
WHERE r.totalIncome > 0
""")
List<Object[]> getIncomePerUser();
}
@@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffTimeTerm;
import java.time.LocalDateTime;
import java.util.List;
@@ -13,4 +14,57 @@ import java.util.List;
public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> {
@Query("SELECT t FROM TariffInfo t WHERE t.autoRenewal = true AND t.endTariff <= :date")
List<TariffInfo> findForAutoRenewal(@Param("date") LocalDateTime date);
@Query("""
SELECT COUNT(u.Id)
FROM User u
WHERE (:activeOnly = false OR u.isActive = true)
AND (:companyOnly = false OR u.company IS NOT NULL)
""")
Long getTotalUsersCount(
@Param("activeOnly") boolean activeOnly,
@Param("companyOnly") boolean companyOnly
);
@Query("""
SELECT COUNT(u.Id)
FROM User u
WHERE (:activeOnly = false OR u.isActive = true)
""")
Long getTotalUsersCount(@Param("activeOnly") boolean activeOnly);
@Query(value = """
SELECT
t.tariff_name,
COUNT(u.id) as user_count,
(COUNT(u.id) * 100.0) / NULLIF(
(SELECT COUNT(*) FROM users u2
WHERE (:activeOnly = false OR u2.is_active = true)
AND u2.personal_tariff_info_id IS NOT NULL
), 0
) as usage_percent
FROM users u
INNER JOIN tariff_info pti ON u.personal_tariff_info_id = pti.id
INNER JOIN tariff t ON pti.tariff_id = t.id
WHERE (:activeOnly = false OR u.is_active = true)
GROUP BY t.id, t.tariff_name
ORDER BY user_count DESC
""", nativeQuery = true)
List<Object[]> getTariffStatisticsNative(@Param("activeOnly") boolean activeOnly);
@Query("""
SELECT COUNT(ti.id)
FROM TariffInfo ti
WHERE ti.endTariff >= CURRENT_TIMESTAMP
AND (:term IS NULL OR ti.tariff.tariffTerm = :term)
""")
Long getCurrentActiveSubscriptions(@Param("term") TariffTimeTerm term);
@Query("""
SELECT ti
FROM TariffInfo ti
WHERE (:term IS NULL OR ti.tariff.tariffTerm = :term)
ORDER BY ti.startTariff
""")
List<TariffInfo> getAllTariffInfosForAnalysis(@Param("term") TariffTimeTerm term);
}
@@ -0,0 +1,56 @@
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.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import java.util.List;
@Repository
public interface TokenOperationRepository extends JpaRepository<TokenOperation, Long> {
Page<TokenOperation> findAll(Pageable pageable);
List<TokenOperation> findByUserId(Long userId);
Page<TokenOperation> findByUserId(Long userId, Pageable pageable);
Page<TokenOperation> findByOperationType(OperationType operationType, Pageable pageable);
@Query("SELECT t FROM TokenOperation t WHERE " +
"(:userId IS NULL OR t.userId = :userId) AND " +
"(:operationType IS NULL OR t.operationType = :operationType) AND " +
"(:minSpent IS NULL OR t.spent >= :minSpent) AND " +
"(:maxSpent IS NULL OR t.spent <= :maxSpent)")
Page<TokenOperation> findByFilters(
@Param("userId") Long userId,
@Param("operationType") OperationType operationType,
@Param("minSpent") Long minSpent,
@Param("maxSpent") Long maxSpent,
Pageable pageable
);
@Query("SELECT SUM(t.spent) FROM TokenOperation t WHERE t.userId = :userId AND t.operationType = :operationType")
Long sumSpentByUserAndType(@Param("userId") Long userId, @Param("operationType") OperationType operationType);
Long countByUserId(Long userId);
@Query("""
SELECT COALESCE(SUM(t.spent), 0)
FROM TokenOperation t
""")
Long getTotalTokensSpent();
@Query("""
SELECT
t.userId,
COALESCE(SUM(t.spent), 0)
FROM TokenOperation t
GROUP BY t.userId
""")
List<Object[]> getTokensSpentPerUser();
}
@@ -2,10 +2,13 @@ package ru.soune.nocopy.repository;
import jakarta.validation.constraints.Size;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
@@ -14,4 +17,32 @@ public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByCompanyId(String companyId);
long countByCompanyId(String companyId);
User findByPersonalTariffInfo(TariffInfo tariffInfo);
@Query("SELECT COUNT(u.Id) FROM User u")
Long getTotalUsersCount();
@Query(value = """
SELECT COUNT(*)
FROM users
WHERE created_at >= NOW() - INTERVAL '1 day' * :days
""", nativeQuery = true)
Long getNewUsersCount(int days);
@Query(value = """
SELECT COUNT(*)
FROM users
WHERE created_at >= DATE_TRUNC('month', NOW())
""", nativeQuery = true)
Long getNewUsersCurrentMonth();
@Query(value = """
SELECT COUNT(*)
FROM users
WHERE created_at >= DATE_TRUNC('month', NOW() - INTERVAL '1 month')
AND created_at < DATE_TRUNC('month', NOW())
""", nativeQuery = true)
Long getNewUsersPreviousMonth();
@Query("SELECT u FROM User u WHERE u.personalTariffInfo.id = :tariffInfoId")
Optional<User> findByPersonalTariffInfoId(@Param("tariffInfoId") String tariffInfoId);
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.soune.nocopy.entity.user.ModerationStatus;
import ru.soune.nocopy.entity.user.moderation.UserVerification;
import java.util.List;
public interface UserVerificationRepository extends JpaRepository<UserVerification, Long> {
List<UserVerification> findByUserId(Long userId);
List<UserVerification>findByUserIdAndModerationStatus(Long userId, ModerationStatus moderationStatus);
}
@@ -74,4 +74,10 @@ public interface ViolationRepository extends JpaRepository<Violation, Long> {
List<Violation> findByFileEntityInAndStatusAndCreatedDateBetween(List<FileEntity> files, String status,
LocalDateTime startDate, LocalDateTime endDate,
Sort sort);
@Query("SELECT COUNT(v.id) FROM Violation v")
Long getTotalViolationsCount();
@Query("SELECT COUNT(DISTINCT c.violation.id) FROM ComplaintEntity c WHERE c.violation IS NOT NULL")
Long getViolationsWithComplaintCount();
}