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
+1 -1
View File
@@ -290,7 +290,7 @@ server_name dev-admin.not-copy.com;
ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem;
location /api/ { location /api/ {
proxy_pass http://localhost:3001; proxy_pass http://localhost:8082;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+1
View File
@@ -62,6 +62,7 @@ dependencies {
implementation name: 'testlib-fat-0.3.1-all' implementation name: 'testlib-fat-0.3.1-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation 'com.maxmind.geoip2:geoip2:4.2.0'
implementation project(':referral') implementation project(':referral')
+31
View File
@@ -216,6 +216,33 @@ services:
networks: networks:
- app-network - app-network
nexus:
image: sonatype/nexus3:latest
container_name: nexus
restart: unless-stopped
ports:
- "9000:8081"
volumes:
- nexus-data:/nexus-data
environment:
- INSTALL4J_ADD_VM_PARAMS=-Xms512m -Xmx1024m -XX:MaxDirectMemorySize=512m
networks:
- app-network
mongodb:
image: mongo:latest
container_name: mongo_ncp
restart: unless-stopped
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: mongo_db
MONGO_INITDB_ROOT_PASSWORD: mongo_ncp
volumes:
- mongodb_data:/data/db
networks:
- app-network
tempo: tempo:
image: grafana/tempo:2.4.1 image: grafana/tempo:2.4.1
container_name: tempo container_name: tempo
@@ -267,6 +294,8 @@ services:
volumes: volumes:
pgdata: pgdata:
mongodb_data:
name: mongodb_data
artifacts: artifacts:
tempo_data: tempo_data:
loki_data: loki_data:
@@ -275,6 +304,8 @@ volumes:
loki_rules: loki_rules:
uploads_data: uploads_data:
prometheus_data: prometheus_data:
nexus-data:
driver: local
# postfix-data: # postfix-data:
networks: networks:
app-network: app-network:
+23 -8
View File
@@ -86,10 +86,11 @@ pipeline {
passwordVariable: 'SSH_PASS' passwordVariable: 'SSH_PASS'
), ),
string(credentialsId: 'DB_USER', variable: 'DB_USER'), string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD') string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD'),
string(credentialsId: 'MONGO_USER', variable: 'MONGO_USER'),
string(credentialsId: 'MONGO_PASSWORD', variable: 'MONGO_PASSWORD')
]) { ]) {
sh """ sh """
# Тестируем подключение сначала
echo "Testing SSH connection..." echo "Testing SSH connection..."
if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then
echo "SSH connection OK" echo "SSH connection OK"
@@ -98,7 +99,6 @@ pipeline {
exit 1 exit 1
fi fi
# Основная команда
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} " sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT} mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
cd /opt/deployments/${params.BRANCH}-${params.PORT} cd /opt/deployments/${params.BRANCH}-${params.PORT}
@@ -118,6 +118,8 @@ pipeline {
docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден' docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден' docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден' docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker stop mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
docker rm mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
echo '3. Запускаем БД...' echo '3. Запускаем БД...'
docker run -d \\ docker run -d \\
@@ -134,20 +136,31 @@ pipeline {
-e BUILD_TIME_BACK='${buildTime}' \\ -e BUILD_TIME_BACK='${buildTime}' \\
postgres:17.7 postgres:17.7
echo '4. Запускаем storage...' echo '4. Запускаем MongoDB...'
docker run -d \\
--name mongodb-${params.PORT} \\
--network ${params.NETWORK} \\
--network-alias mongodb-${params.PORT} \\
-p ${params.PORT}1:27017 \\
-e MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} \\
-e MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} \\
-v mongodb_data_${params.PORT}:/data/db \\
mongo:7.0
echo '5. Запускаем storage...'
docker run -d \\ docker run -d \\
--name file-storage-${params.PORT} \\ --name file-storage-${params.PORT} \\
--network ${params.NETWORK} \\ --network ${params.NETWORK} \\
-v uploads_data_${params.PORT}:/storage:rw \\ -v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null alpine:latest tail -f /dev/null
echo '5. Ждем БД...' echo '6. Ждем БД...'
sleep 15 sleep 15
echo '6. Собираем приложение...' echo '7. Собираем приложение...'
docker build --no-cache -t app-backend-${params.PORT}:latest . docker build --no-cache -t app-backend-${params.PORT}:latest .
echo '7. Запускаем приложение...' echo '8. Запускаем приложение...'
docker run -d \\ docker run -d \\
--name app-backend-${params.PORT} \\ --name app-backend-${params.PORT} \\
--network ${params.NETWORK} \\ --network ${params.NETWORK} \\
@@ -162,9 +175,11 @@ pipeline {
-e POSTGRES_HOST=no_copy_${params.PORT} \\ -e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\ -e FILE_STORAGE_PATH=/data/uploads \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\ -e SPRING_PROFILES_ACTIVE=${springProfile} \\
-e MONGO_URI=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongodb-${params.PORT}:27017 \\
-e MONGO_DATABASE=no_copy \\
app-backend-${params.PORT}:latest app-backend-${params.PORT}:latest
echo '8. Проверка...' echo '9. Проверка...'
sleep 10 sleep 10
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
@@ -35,7 +35,19 @@ public class HandlerConfig {
ViolationNotionHandler violationNotionHandler, ViolationNotionHandler violationNotionHandler,
ComplaintEntityHandler complaintEntityHandler, ComplaintEntityHandler complaintEntityHandler,
UserInfoHandler userInfoHandler, UserInfoHandler userInfoHandler,
NotificationHandler notificationHandler NotificationHandler notificationHandler,
UserVerificationHandler userVerificationHandler,
LawCaseHandler lawCaseHandler,
TokenOperationHandler tokenOperationHandler,
StatisticUserFilesHandler statisticUserHandler,
StatisticTariffInfoFileHandler statisticTariffInfoFileHandler,
StatisticUserDynamicHandler statisticUserDynamicHandler,
StatisticProtectedFilesHandler statisticProtectedFilesHandler,
StatisticViolationHandler statisticViolationHandler,
StatisticSubscriberHandler statisticSubscriberHandler,
StatisticTokenHandler statisticTokenHandler,
StatisticIncomeHandler statisticIncomeHandler
) { ) {
Map<Integer, RequestHandler> map = new HashMap<>(); Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login); map.put(20001, login);
@@ -62,6 +74,17 @@ public class HandlerConfig {
map.put(30013, complaintEntityHandler); map.put(30013, complaintEntityHandler);
map.put(30014, userInfoHandler); map.put(30014, userInfoHandler);
map.put(30015, notificationHandler); map.put(30015, notificationHandler);
map.put(30016, userVerificationHandler);
map.put(30017, lawCaseHandler);
map.put(30018, tokenOperationHandler);
map.put(30019, statisticUserHandler);
map.put(30020, statisticTariffInfoFileHandler);
map.put(30021, statisticUserDynamicHandler);
map.put(30022, statisticProtectedFilesHandler);
map.put(30023, statisticViolationHandler);
map.put(30024, statisticSubscriberHandler);
map.put(30025, statisticTokenHandler);
map.put(30026, statisticIncomeHandler);
return map; return map;
} }
@@ -34,12 +34,12 @@ import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService; import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.file.*;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ProtectionsLimitService; import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.user.moderation.UserVerificationService;
import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import java.io.File; import java.io.File;
@@ -81,6 +81,10 @@ public class ApiController {
private final CloudStorageService cloudStorageService; private final CloudStorageService cloudStorageService;
private final ZipService zipService;
private final UserVerificationService userVerificationService;
@PostMapping("/v{version}/data") @PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request, public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) { @PathVariable("version") int version) {
@@ -169,6 +173,56 @@ public class ApiController {
} }
} }
@PostMapping("/v{version}/private/files/chunk")
public ResponseEntity<BaseResponse> uploadChunk(
@RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "last_file", required = false) Boolean last_file,
@RequestParam(value = "token") String token) {
try {
if (chunk == null || chunk.isEmpty()) {
return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty");
}
if (uploadId == null || uploadId.isBlank()) {
return buildErrorResponse(uploadId, chunkNumber, "Upload Id is required");
}
if (chunkNumber == null || chunkNumber < 0) {
return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required");
}
Long userId = authService.useUserAuthToken(token);
UploadProgressResponse uploadProgressResponse = fileUploadService.uploadPassportChunk(uploadId, chunkNumber,
chunk, userId);
if (last_file) {
zipService.createAndSplitZip(userId,
fileEntityService.getAllUserFiles(userId, List.of(FileStatus.PRIVATE)));
}
return buildSuccessResponse(uploadId, chunkNumber, chunk,
fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath()));
}catch (FileFormatException e){
return buildErrorResponse(uploadId, chunkNumber, e.getMessage());
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
}
}
@GetMapping("/check/{fileId}")
public ResponseEntity<BaseResponse> check(@PathVariable String fileId) {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
File file = new File(fileEntity.getFilePath());
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, FileProtector.Type.IMAGE);
log.info("checkResult: {}", checkResult);
return ResponseEntity.ok(new BaseResponse(1, 1, "1", checkResult));
}
@GetMapping("/v{version}/files/{fileId}/similar") @GetMapping("/v{version}/files/{fileId}/similar")
public ResponseEntity<BaseResponse> findSimilarFiles( public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version, @PathVariable("version") int version,
@@ -2,8 +2,8 @@ package ru.soune.nocopy.controller;
import com.vrt.NoCopyFileService; import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector; import com.vrt.fileprotection.FileProtector;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -17,6 +17,7 @@ import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService; import ru.soune.nocopy.service.file.FileStorageService;
import ru.soune.nocopy.service.file.ImageResizeService; import ru.soune.nocopy.service.file.ImageResizeService;
import ru.soune.nocopy.service.file.ZipService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -31,29 +32,25 @@ import java.nio.file.Paths;
@RestController @RestController
@RequestMapping("/api/files") @RequestMapping("/api/files")
@Slf4j @Slf4j
@AllArgsConstructor
public class FileController { public class FileController {
@Autowired
private FileStorageService fileStorageService; private FileStorageService fileStorageService;
@Autowired
private FileEntityRepository fileRepository; private FileEntityRepository fileRepository;
@Autowired
private CheckCounterService checkCounterService; private CheckCounterService checkCounterService;
@Autowired
private UserRepository userRepository; private UserRepository userRepository;
@Autowired
private ImageResizeService imageResizeService; private ImageResizeService imageResizeService;
@Autowired
private NoCopyFileService noCopyFileService; private NoCopyFileService noCopyFileService;
@Autowired
private FileUtil fileUtil; private FileUtil fileUtil;
private ZipService zipService;
@GetMapping("/public/{fileId}") @GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) { public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) {
try { try {
@@ -166,4 +163,21 @@ public class FileController {
.ok() .ok()
.body(fileInfo); .body(fileInfo);
} }
@GetMapping("/download/user-passport/archive/{userId}")
public ResponseEntity<byte[]> downloadPassportZip(@PathVariable Long userId) {
try {
byte[] zipBytes = zipService.assembleZipFromSplitFiles(userId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=passport_" + userId + ".zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(zipBytes.length)
.body(zipBytes);
} catch (IOException e) {
log.error("Failed to assemble passport for user {}", userId, e);
return ResponseEntity.internalServerError().build();
}
}
} }
@@ -70,7 +70,7 @@ public class PaymentController {
} }
paymentService.changeAutoRenewal(authService.useUserAuthToken(tokenHeader), renewal); paymentService.changeAutoRenewal(authService.useUserAuthToken(tokenHeader), renewal);
return ResponseEntity.ok(Map.of("message", "Auto-renewal disabled")); return ResponseEntity.ok(Map.of("message", "Auto-renewal changed:" + renewal));
} catch (UserNotFoundException e) { } catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND) return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage())); .body(Map.of("error", e.getMessage()));
@@ -59,7 +59,7 @@ public class UserController {
.map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(), .map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(), u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(), u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
null, null)) null, null, u.getVerificationStatus()))
.toList(); .toList();
return ResponseEntity.ok(allUsers); return ResponseEntity.ok(allUsers);
@@ -79,6 +79,7 @@ public class UserController {
UserDTO userDTO = userMapper.toDTO(user); UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email); userDTO.setEmail(email);
userDTO.setFullName(user.getFullName()); userDTO.setFullName(user.getFullName());
userDTO.setVerifiedStatus(user.getVerificationStatus());
if (user.getCompany() != null) { if (user.getCompany() != null) {
userDTO.setCompany(user.getCompany().getCompanyName()); userDTO.setCompany(user.getCompany().getCompanyName());
@@ -19,6 +19,7 @@ public enum MessageCode {
FILE_DELETE(2, "File was deleted"), FILE_DELETE(2, "File was deleted"),
USER_NOT_HAD_PERMISSION(2, "User not have permission for file"), USER_NOT_HAD_PERMISSION(2, "User not have permission for file"),
USER_NOT_VERIFIED(2, "User not verified"), USER_NOT_VERIFIED(2, "User not verified"),
LAW_CASE_NOT_FOUND(4, "Law case not found"),
PERMISSION_NOT_FOUND(2, "Permission not found"), PERMISSION_NOT_FOUND(2, "Permission not found"),
USER_NOT_FOUND(2, "User not found"), USER_NOT_FOUND(2, "User not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"), FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
@@ -26,8 +27,10 @@ public enum MessageCode {
IMAGE_FOUND_ERROR(2, "Image found error"), IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
INCOMPLETE_UPLOAD(2, "Not load all chunks"), INCOMPLETE_UPLOAD(2, "Not load all chunks"),
USER_NOT_HAVE_TOKEN(2, "Not have tokens"),
MSG_ID_NOT_FOUND(4, "Message id not found"), MSG_ID_NOT_FOUND(4, "Message id not found"),
FILE_ENTITY_ERROR(2, "File entity error"), FILE_ENTITY_ERROR(2, "File entity error"),
NOT_VALID_FIELD(2, "Not valid field"),
ACCESS_DENIED(2, "Access denied"), ACCESS_DENIED(2, "Access denied"),
AUTH_EMAIL_NOT_FOUND(4, "Email not found"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "), AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
@@ -45,6 +48,7 @@ public enum MessageCode {
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"), TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
VALIDATION_ERROR(2, "Validation error"), VALIDATION_ERROR(2, "Validation error"),
RESOURCE_NOT_FOUND(4, "Resource not found"), RESOURCE_NOT_FOUND(4, "Resource not found"),
OPERATION_NOT_FOUND(4, "Operation not found"),
INTERNAL_ERROR(4, "Internal server error"), INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"), COMPANY_NOT_FOUND(4, "Company not found"),
PAYMENT_NOT_FOUND(4, "Payment not found"), PAYMENT_NOT_FOUND(4, "Payment not found"),
@@ -59,7 +63,9 @@ public enum MessageCode {
USER_NOT_ACTIVE(2, "User not active"), USER_NOT_ACTIVE(2, "User not active"),
NOTION_NOT_FOUND(4, "Notion not found"), NOTION_NOT_FOUND(4, "Notion not found"),
NOT_FOUND(4, "Notion not found"), NOT_FOUND(4, "Notion not found"),
MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion"); MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion"),
USER_VERIFICATIONS_NOT_FOUND(4, "Verifications not found"),
ADMIN_USER_NOT_FOUND(4, "Verifications ,user or admin not found");
private final Integer code; private final Integer code;
@@ -0,0 +1,35 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class BaseResponse {
@JsonProperty("status")
private String status;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private Object data;
@JsonProperty("error_code")
private String errorCode;
public static BaseResponse success(Object data) {
return BaseResponse.builder()
.status("SUCCESS")
.data(data)
.build();
}
public static BaseResponse error(String message, String errorCode) {
return BaseResponse.builder()
.status("ERROR")
.message(message)
.errorCode(errorCode)
.build();
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.complaint;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class LawCasePageResponse {
private List<LawCaseResponse> content;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private boolean last;
private boolean first;
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.math.BigDecimal;
@Data
public class LawCaseRequest {
private Long id;
//required
private String name;
//required
private String description;
private BigDecimal amount;
private String priority = LawCasePriority.HIGH.toString();
private String type;
private String lawyer;
private String token;
private Integer pageSize = 5;
private Integer pageNumber = 0;
private String sortBy = "updatedAt";
private String sortDir = "desc";
private String action;
private LawCaseType filterType;
private String filterLawyer;
private LawCasePriority filterPriority;
@JsonProperty("violation_id")
private Long violationId;
}
@@ -0,0 +1,46 @@
package ru.soune.nocopy.dto.complaint;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
public class LawCaseResponse {
private Long id;
private String name;
private String description;
private BigDecimal amount;
private LawCasePriority priority;
private LawCaseType type;
private String lawyer;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private long violationId;
private List<LawCaseResponse> content;
public static LawCaseResponse fromEntity(LawCase lawCase) {
return LawCaseResponse.builder()
.id(lawCase.getId())
.name(lawCase.getName())
.description(lawCase.getDescription())
.amount(lawCase.getAmount())
.priority(lawCase.getPriority())
.type(lawCase.getType())
.lawyer(lawCase.getLawyer())
.createdAt(lawCase.getCreatedAt())
.updatedAt(lawCase.getUpdatedAt())
.violationId(lawCase.getViolationId())
.build();
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.complaint;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class PaginatedResponse<T> {
private List<T> content;
private long totalElements;
private int totalPages;
private int pageNumber;
private int pageSize;
private boolean first;
private boolean last;
}
@@ -0,0 +1,6 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class IncomeStatisticRequest {
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class IncomeStatisticResponse {
@JsonProperty("total_income")
private Long totalIncome;
@JsonProperty("total_available")
private Long totalAvailable;
@JsonProperty("total_hold")
private Long totalHold;
@JsonProperty("income_per_user")
private Percentiles incomePerUser;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Percentiles {
@JsonProperty("p5")
private Long p5;
@JsonProperty("p50")
private Long p50;
@JsonProperty("p95")
private Long p95;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class ProtectedFilesStatisticRequest {
}
@@ -0,0 +1,66 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProtectedFilesStatisticResponse {
@JsonProperty("total_files")
private Long totalFiles;
@JsonProperty("total_size_bytes")
private Long totalSizeBytes;
@JsonProperty("total_size_gb")
private Double totalSizeGb;
@JsonProperty("files_per_user_percentiles")
private Percentiles filesPerUser;
@JsonProperty("size_per_user_percentiles")
private SizePercentiles sizePerUser;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Percentiles {
@JsonProperty("p5")
private Long p5;
@JsonProperty("p50")
private Long p50;
@JsonProperty("p95")
private Long p95;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class SizePercentiles {
@JsonProperty("p5_bytes")
private Long p5;
@JsonProperty("p50_bytes")
private Long p50;
@JsonProperty("p95_bytes")
private Long p95;
@JsonProperty("p5_mb")
private Double p5Mb;
@JsonProperty("p50_mb")
private Double p50Mb;
@JsonProperty("p95_mb")
private Double p95Mb;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class SubscriberStatisticRequest {
}
@@ -0,0 +1,42 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SubscriberStatisticResponse {
@JsonProperty("monthly")
private PeriodStatistics monthly;
@JsonProperty("yearly")
private PeriodStatistics yearly;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PeriodStatistics {
@JsonProperty("current_active_subscriptions")
private Long currentActiveSubscriptions;
@JsonProperty("first_time_subscriptions")
private Long firstTimeSubscriptions;
@JsonProperty("renewals")
private Long renewals;
@JsonProperty("upgrades")
private Long upgrades;
@JsonProperty("downgrades")
private Long downgrades;
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TariffStatisticFullResponse {
@JsonProperty("tariffs")
private List<TariffStatisticResponse> tariffs;
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class TariffStatisticRequest {
@JsonProperty("company_only")
private Boolean companyOnly = false;
@JsonProperty("active_only")
private Boolean activeOnly = true;
}
@@ -0,0 +1,21 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TariffStatisticResponse {
@JsonProperty("tariff_name")
private String tariffName;
@JsonProperty("user_count")
private Long userCount;
@JsonProperty("usage_percent")
private Double usagePercent;
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class TokenStatisticRequest {
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TokenStatisticResponse {
@JsonProperty("total_bought")
private Long totalBought;
@JsonProperty("total_spent")
private Long totalSpent;
@JsonProperty("bought_per_user")
private Percentiles boughtPerUser;
@JsonProperty("spent_per_user")
private Percentiles spentPerUser;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Percentiles {
@JsonProperty("p5")
private Long p5;
@JsonProperty("p50")
private Long p50;
@JsonProperty("p95")
private Long p95;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class UserDynamicStatisticRequest {
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDynamicStatisticResponse {
@JsonProperty("total_users")
private Long totalUsers;
@JsonProperty("new_last_30_days")
private Long newLast30Days;
@JsonProperty("new_previous_30_days")
private Long newPrevious30Days;
@JsonProperty("growth_percent")
private Double growthPercent;
@JsonProperty("new_current_month")
private Long newCurrentMonth;
@JsonProperty("new_previous_month")
private Long newPreviousMonth;
@JsonProperty("month_growth_percent")
private Double monthGrowthPercent;
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserFilesStatisticRequest {
@JsonProperty("top")
int topUsers;
@JsonProperty("type")
String fileMimeType;
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserFilesStatisticResponse {
@JsonProperty("full_name")
private final String userName;
@JsonProperty("user_id")
private final long userId;
@JsonProperty("count")
private final int countFiles;
@JsonProperty("files_size")
private final long fileSize;
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class ViolationStatisticRequest {
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ViolationStatisticResponse {
@JsonProperty("total_violations")
private Long totalViolations;
@JsonProperty("total_complaints")
private Long totalComplaints;
@JsonProperty("total_law_cases")
private Long totalLawCases;
@JsonProperty("complaints_without_case")
private Long complaintsWithoutCase;
@JsonProperty("violations_with_complaint")
private Long violationsWithComplaint;
@JsonProperty("violations_without_complaint")
private Long violationsWithoutComplaint;
}
@@ -0,0 +1,34 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import java.util.List;
@Data
@Builder
public class TokenOperationPageDto {
@JsonProperty("content")
private List<TokenOperation> content;
@JsonProperty("page_number")
private int pageNumber;
@JsonProperty("page_size")
private int pageSize;
@JsonProperty("total_elements")
private long totalElements;
@JsonProperty("total_pages")
private int totalPages;
@JsonProperty("is_last")
private boolean last;
@JsonProperty("is_first")
private boolean first;
}
@@ -0,0 +1,46 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
@Data
public class TokenOperationRequest {
@JsonProperty("action")
private String action;
@JsonProperty("token")
private String token;
@JsonProperty("operation_id")
private Long operationId;
@JsonProperty("operation_type")
private OperationType operationType;
@JsonProperty("spent")
private Long spent;
@JsonProperty("page")
private Integer page = 0;
@JsonProperty("size")
private Integer size = 20;
@JsonProperty("sort_by")
private String sortBy = "createdAt";
@JsonProperty("sort_direction")
private String sortDirection = "desc";
@JsonProperty("min_spent")
private Long minSpent;
@JsonProperty("max_spent")
private Long maxSpent;
@JsonProperty("updates")
private Object updates;
}
@@ -0,0 +1,37 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TokenOperationResponse {
@JsonProperty("success")
private Boolean success;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private Object data;
@JsonProperty("total_elements")
private Long totalElements;
@JsonProperty("total_pages")
private Integer totalPages;
@JsonProperty("current_page")
private Integer currentPage;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("statistics")
private Map<String, Object> statistics;
}
@@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import ru.soune.nocopy.dto.tarriff.TariffDTO; import ru.soune.nocopy.dto.tarriff.TariffDTO;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO; import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.entity.user.GenderType; import ru.soune.nocopy.entity.user.GenderType;
import ru.soune.nocopy.entity.user.ModerationStatus;
import ru.soune.nocopy.entity.user.SubscriptionType; import ru.soune.nocopy.entity.user.SubscriptionType;
import java.time.LocalDate; import java.time.LocalDate;
@@ -30,4 +31,5 @@ public class UserDTO {
private List<TariffDTO> tariffs; private List<TariffDTO> tariffs;
private TariffInfoDTO tariffInfo; private TariffInfoDTO tariffInfo;
private Long permission; private Long permission;
private ModerationStatus verifiedStatus;
} }
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.user.moderation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserVerificationRequest {
@JsonProperty("message")
private String message;
@JsonProperty("user_id")
private Long userId;
@JsonProperty("verified")
private Boolean verified;
@JsonProperty("admin_id")
private Long adminId;
@JsonProperty("action")
private String action;
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.user.moderation;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class UserVerificationResponse {
private Long userVerificationId;
private Long userId;
private LocalDateTime updateTime;
private String moderationStatus;
}
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import ru.soune.nocopy.entity.violation.Violation; import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.service.geo.GeoCountryService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -12,6 +13,8 @@ import java.util.List;
@Builder @Builder
public class ViolationResponse { public class ViolationResponse {
private final GeoCountryService geoCountryService;
@JsonProperty("violations") @JsonProperty("violations")
private List<ViolationDto> violations; private List<ViolationDto> violations;
@@ -58,6 +61,12 @@ public class ViolationResponse {
@JsonProperty("created_date") @JsonProperty("created_date")
private LocalDateTime createdDate; private LocalDateTime createdDate;
@JsonProperty("country")
private String country;
@JsonProperty("country_code")
private String countryCode;
@JsonProperty("file_id") @JsonProperty("file_id")
private String fileId; private String fileId;
@@ -39,12 +39,6 @@ public class ComplaintEntity {
@JsonIgnore @JsonIgnore
private Violation violation; private Violation violation;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "law_case_id")
@ToString.Exclude
@JsonIgnore
private LawCase lawCase;
@Column(name = "created_at", updatable = false) @Column(name = "created_at", updatable = false)
@CreatedDate @CreatedDate
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -55,4 +49,12 @@ public class ComplaintEntity {
@Column(name = "not_moderated_file") @Column(name = "not_moderated_file")
private Boolean not_moderated_file; private Boolean not_moderated_file;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -2,6 +2,12 @@ package ru.soune.nocopy.entity.complaint;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import ru.soune.nocopy.entity.user.User;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity @Entity
@AllArgsConstructor @AllArgsConstructor
@@ -14,4 +20,48 @@ public class LawCase {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "name")
private String name;
@Column(length = 500, name = "description")
private String description;
@Column(name = "amount")
private BigDecimal amount;
@Column(name = "priority")
@Enumerated(EnumType.STRING)
private LawCasePriority priority;
@Column(name = "type")
@Enumerated(EnumType.STRING)
private LawCaseType type = LawCaseType.ACTIVE;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "lawyer")
private String lawyer;
@Column(name = "violation_id")
private Long violationId;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -0,0 +1,12 @@
package ru.soune.nocopy.entity.complaint;
public enum LawCasePriority {
CRITICAL("critical"), HIGH("high"),
MIDDLE("middle"), LOW("low");
private String name;
private LawCasePriority(String name) {
this.name = name;
}
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.entity.complaint;
import lombok.Getter;
@Getter
public enum LawCaseType {
ACTIVE("active"), CLOSED("closed"),
COMPLETED("completed");
private String name;
private LawCaseType(String name) {
this.name = name;
}
}
@@ -6,8 +6,10 @@ import lombok.*;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Data @Data
@@ -10,5 +10,6 @@ public enum FileStatus {
TEMP, TEMP,
REMOVED, REMOVED,
MODERATION, MODERATION,
PRIVATE,
BLOCKED BLOCKED
} }
@@ -0,0 +1,28 @@
package ru.soune.nocopy.entity.file.moderation;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Builder
@Table(name = "moderation_file")
@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
public class ModerationPassportFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "path", nullable = false)
private String path;
@Column(name = "part")
private Integer part;
private String status;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.entity.file.moderation;
import lombok.Getter;
@Getter
public enum PassportModerationFileStatus {
DOWNLOAD("download"),
ON_MODERATION("on_moderation"),
MODERATED("moderated");
private final String name;
PassportModerationFileStatus(String name) {
this.name = name;
}
}
@@ -26,7 +26,7 @@ public class Notification {
@Column(name = "created_at", updatable = false) @Column(name = "created_at", updatable = false)
@CreatedDate @CreatedDate
private LocalDateTime createdAt; private LocalDateTime createdAt = LocalDateTime.now();
@LastModifiedDate @LastModifiedDate
@Column(name = "updated_at") @Column(name = "updated_at")
@@ -46,4 +46,13 @@ public class Notification {
@JoinColumn(name = "user_id") @JoinColumn(name = "user_id")
@JsonIgnore @JsonIgnore
private User user; private User user;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -4,7 +4,7 @@ import lombok.Getter;
@Getter @Getter
public enum NotificationMessage { public enum NotificationMessage {
FILE_SEARCH("notification.file.search"); FILE_SEARCH("notification-file-search");
private final String messageKey; private final String messageKey;
@@ -4,24 +4,26 @@ import lombok.Getter;
@Getter @Getter
public enum NotificationType { public enum NotificationType {
SEARCH_RESULT("notification.search.result"), SEARCH_RESULT("notification-search-result"),
MONITORING_RESULT("notification.monitoring.result"), MONITORING_RESULT("notification-monitoring-result"),
OPERATION_IMPOSSIBLE("notification.operation.impossible"), OPERATION_IMPOSSIBLE("notification-operation-impossible"),
START_FAILED_NOW("notification.start.failed.now"), START_FAILED_NOW("notification-start-failed-now"),
START_FAILED_NEXT("notification.start.failed.next"), START_FAILED_NEXT("notification-start-failed-next"),
PAYOUT_RESULT("notification.payout.result"), PAYOUT_RESULT("notification-payout-result"),
PAYMENT_RESULT("notification.payment.result"), PAYMENT_RESULT("notification-payment-result"),
REFERRAL_DEPOSIT("notification.referral.deposit"), REFERRAL_DEPOSIT("notification-referral-deposit"),
REFERRAL_REGISTERED("notification.referral.registered"), REFERRAL_REGISTERED("notification-referral-registered"),
REFERRAL_ACTIVATED("notification.referral.activated"), USER_VERIFIED("notification-user-verified"),
COMPLAINT_STATUS_CHANGED("notification.complaint.status"), USER_NOT_VERIFIED("notification-user-not-verified"),
CASE_STATUS_CHANGED("notification.case.status"), REFERRAL_ACTIVATED("notification-referral-activated"),
INCOMING_MESSAGE("notification.incoming.message"), COMPLAINT_STATUS_CHANGED("notification-complaint-status"),
TARIFF_EXPIRING("notification.tariff.expiring"), CASE_STATUS_CHANGED("notification-case-status"),
TOKEN_NOT_FOUND("notification.token.not.found"), INCOMING_MESSAGE("notification-incoming-message"),
FILE_MODERATION_EVENT("notification.file.moderation"), TARIFF_EXPIRING("notification-tariff-expiring"),
FILE_ADDED_TO_SYSTEM("notification.file.added"), TOKEN_NOT_FOUND("notification-token-not-found"),
SEARCH_OPERATION_FAILED("notification.search.operation.filed"); FILE_MODERATION_EVENT("notification-file-moderation"),
FILE_ADDED_TO_SYSTEM("notification-file-added"),
SEARCH_OPERATION_FAILED("notification-search-operation-filed");
private final String messageKey; private final String messageKey;
@@ -61,4 +61,13 @@ public class Payment {
@JoinColumn(name = "tariff_id") @JoinColumn(name = "tariff_id")
@JsonIgnore @JsonIgnore
private Tariff tariff; private Tariff tariff;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -20,6 +20,7 @@ public class TariffInfo {
private String id; private String id;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
private TariffStatus status; private TariffStatus status;
@Column(name = "start_tariff") @Column(name = "start_tariff")
@@ -0,0 +1,12 @@
package ru.soune.nocopy.entity.tokenoperation;
public enum OperationType {
SEARCH("search"), MONITORING("monitoring"), CREATE_LEGAL_CASE("legal_case"),
FILE_UPLOAD("file_upload"), GLOBAL_SEARCH("global_search");
private final String name;
private OperationType(String name) {
this.name = name;
}
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.entity.tokenoperation;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Table(name = "token_operation")
@Entity
@Setter @Getter
@NoArgsConstructor
public class TokenOperation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
@Column(name = "operation_type")
private OperationType operationType;
@Column(name = "user_id")
private Long userId;
@Column(name = "spent")
private Long spent;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.entity.user;
import lombok.Getter;
@Getter
public enum ModerationStatus {
NOT_VERIFIED("not_verified") , VERIFICATION_IN_PROGRESS("verification_in_progress"),
VERIFIED("verified"), VERIFICATION_FAILED("verification_failed");
private final String value;
private ModerationStatus(String value) {
this.value = value;
}
}
@@ -106,6 +106,10 @@ public class User {
@ToString.Exclude @ToString.Exclude
private UserNotActive userNotActive; private UserNotActive userNotActive;
@Column(name = "verification_status")
@Enumerated(EnumType.STRING)
private ModerationStatus verificationStatus = ModerationStatus.NOT_VERIFIED;
public TariffInfo getActiveTariffInfo() { public TariffInfo getActiveTariffInfo() {
if (company != null && company.getTariffInfo() != null) { if (company != null && company.getTariffInfo() != null) {
return company.getTariffInfo(); return company.getTariffInfo();
@@ -0,0 +1,52 @@
package ru.soune.nocopy.entity.user.moderation;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import ru.soune.nocopy.entity.user.ModerationStatus;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_verification")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class UserVerification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", unique = true, nullable = false)
private Long userId;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "message")
private String message;
@Column(name = "admin_id")
private Long adminId;
@Enumerated(EnumType.STRING)
@Column(name = "status")
private ModerationStatus moderationStatus;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
}
@@ -43,4 +43,12 @@ public class ViolationNotion {
@Column(name = "message") @Column(name = "message")
private String message; private String message;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -77,10 +77,9 @@ public class ComplaintEntityHandler implements RequestHandler {
private BaseResponse handleGetAll(Integer msgId, ComplaintRequest req) { private BaseResponse handleGetAll(Integer msgId, ComplaintRequest req) {
Pageable pageable = PageRequest.of( Pageable pageable = PageRequest.of(
req.getPage() != null ? req.getPage() : 0, req.getPage() != null ? req.getPage() : 0,
req.getSize() != null ? req.getSize() : 20, req.getSize() != null ? req.getSize() : 5,
Sort.by(Sort.Direction.fromString(req.getSortDirection() != null ? req.getSortDirection() : "desc"), Sort.by(Sort.Direction.fromString(req.getSortDirection() != null ? req.getSortDirection() : "desc"),
req.getSortBy() != null ? req.getSortBy() : "createdAt") req.getSortBy() != null ? req.getSortBy() : "updatedAt"));
);
var page = complaintService.getAllComplaints(pageable); var page = complaintService.getAllComplaints(pageable);
Map<String, Object> data = new HashMap<>(); Map<String, Object> data = new HashMap<>();
@@ -4,6 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; 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.Component; import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.*; import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.file.*; import ru.soune.nocopy.dto.file.*;
@@ -225,9 +228,55 @@ public class FileEntityHandler implements RequestHandler {
return new BaseResponse(request.getMsgId(), return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), MessageCode.SUCCESS.getDescription(),
FileEntityResponse.builder().status(newStatus).build()); Map.of("file_status", newStatus, "file_id", fileId));
} }
// private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
// try {
// Long userId = authService.useUserAuthToken(fileRequest.getToken());
// int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
// int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
// String fileRequestSortBy = fileRequest.getSortBy();
// String sortBy = !fileRequestSortBy.equals("") ? fileRequest.getSortBy(): "createdAt";
// sortBy = sortBy.equals("fileName") ? "originalFileName" : sortBy;
//
// String sortOrder = fileRequest.getSortOrder();
// Sort.Direction direction = (sortOrder != null && sortOrder.equalsIgnoreCase("desc"))
// ? Sort.Direction.DESC
// : Sort.Direction.ASC;
// Sort sort = Sort.by(direction, sortBy);
//
// Pageable pageable = PageRequest.of(page, pageSize, sort);
// FileResponse userFiles = fileEntityService.getUserFiles(userId, pageable, request.getVersion());
//
// FileListResponse response = FileListResponse.builder()
// .files(userFiles.getFiles())
// .totalCount(userFiles.getTotalCount())
// .totalSize(userFiles.getTotalSize())
// .formattedTotalSize(fileEntityService.formatFileSize(userFiles.getTotalSize()))
// .page(page)
// .pageSize(pageSize)
// .sortBy(sortBy)
// .sortOrder(sortOrder)
// .build();
//
// return new BaseResponse(request.getMsgId(),
// MessageCode.SUCCESS.getCode(),
// MessageCode.SUCCESS.getDescription(),
// response);
//
// } catch (NotFoundAuthToken e) {
// return new BaseResponse(request.getMsgId(),
// MessageCode.INVALID_TOKEN.getCode(),
// "Authentication required", null);
// } catch (Exception e) {
// log.error("Error searching files", e);
// return new BaseResponse(request.getMsgId(),
// MessageCode.FILE_UPLOAD_ERROR.getCode(),
// "Failed to search files: " + e.getMessage(), null);
// }
// }
private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) { private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
try { try {
Long userId = authService.useUserAuthToken(fileRequest.getToken()); Long userId = authService.useUserAuthToken(fileRequest.getToken());
@@ -292,6 +341,73 @@ public class FileEntityHandler implements RequestHandler {
} }
} }
/**
*
* private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
* try {
* Long userId = authService.useUserAuthToken(fileRequest.getToken());
* int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
* int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
*
* String sortBy = fileRequest.getSortBy() != null ? fileRequest.getSortBy() : "fileName";
* SortOrder sortOrder = SortOrder.fromString(fileRequest.getSortOrder());
*
* FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion());
*
* String searchQuery = fileRequest.getQuery() != null ? fileRequest.getQuery().toLowerCase().trim() : "";
* String[] searchTerms = searchQuery.split("\\s+");
* String filterType = fileRequest.getType();
* String dateFilter = fileRequest.getDateFilter();
*
* List<FileEntityResponse> filteredFiles = allFiles.getFiles().stream()
* .filter(f -> matchesSearch(f, searchTerms, searchQuery, filterType))
* .filter(f -> matchesDateFilter(f, dateFilter))
* .collect(Collectors.toList());
*
* Comparator<FileEntityResponse> comparator = getComparator(sortBy, sortOrder);
* filteredFiles.sort(comparator);
*
* int start = (page - 1) * pageSize;
* int end = Math.min(start + pageSize, filteredFiles.size());
*
* if (start >= filteredFiles.size()) {
* return createEmptyResponse(request, page, pageSize, sortBy, sortOrder.getValue());
* }
*
* List<FileEntityResponse> paginatedFiles = filteredFiles.subList(start, end);
* long totalSize = paginatedFiles.stream()
* .mapToLong(FileEntityResponse::getFileSize)
* .sum();
*
* FileListResponse response = FileListResponse.builder()
* .files(paginatedFiles)
* .totalCount(filteredFiles.size())
* .totalSize(totalSize)
* .formattedTotalSize(fileEntityService.formatFileSize(totalSize))
* .page(page)
* .pageSize(pageSize)
* .sortBy(sortBy)
* .sortOrder(sortOrder.getValue())
* .build();
*
* return new BaseResponse(request.getMsgId(),
* MessageCode.SUCCESS.getCode(),
* MessageCode.SUCCESS.getDescription(),
* response);
*
* } catch (NotFoundAuthToken e) {
* return new BaseResponse(request.getMsgId(),
* MessageCode.INVALID_TOKEN.getCode(),
* "Authentication required", null);
* } catch (Exception e) {
* log.error("Error searching files", e);
* return new BaseResponse(request.getMsgId(),
* MessageCode.FILE_UPLOAD_ERROR.getCode(),
* "Failed to search files: " + e.getMessage(), null);
* }
* }
*/
private boolean matchesDateFilter(FileEntityResponse file, String dateFilter) { private boolean matchesDateFilter(FileEntityResponse file, String dateFilter) {
if (dateFilter == null || dateFilter.isEmpty()) { if (dateFilter == null || dateFilter.isEmpty()) {
return true; return true;
@@ -423,6 +539,12 @@ public class FileEntityHandler implements RequestHandler {
Comparator.nullsLast(Comparator.naturalOrder()) Comparator.nullsLast(Comparator.naturalOrder())
); );
break; break;
case "monitoring":
comparator = Comparator.comparing(
FileEntityResponse::getMonitoring,
Comparator.nullsLast(Comparator.naturalOrder())
);
break;
default: default:
comparator = Comparator.comparing( comparator = Comparator.comparing(
@@ -11,6 +11,7 @@ import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ImageSearchRequest; import ru.soune.nocopy.dto.file.ImageSearchRequest;
import ru.soune.nocopy.dto.file.YandexSearchResponse; import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
@@ -117,7 +118,8 @@ public class ImageFoundRequestHandler implements RequestHandler {
log.info("Results only from Yandex: {} images", allUniqueImages.size()); log.info("Results only from Yandex: {} images", allUniqueImages.size());
} }
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH); tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH,
OperationType.SEARCH);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType()); checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1; int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
@@ -0,0 +1,200 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.complaint.LawCaseRequest;
import ru.soune.nocopy.dto.complaint.LawCaseResponse;
import ru.soune.nocopy.dto.complaint.PaginatedResponse;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.complaint.LawCaseService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffConstants;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class LawCaseHandler implements RequestHandler {
private final LawCaseService lawCaseService;
private final AuthService authService;
private final ObjectMapper objectMapper;
private final UserRepository userRepository;
private final TariffInfoService tariffInfoService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
LawCaseRequest lawCaseRequest = objectMapper.convertValue(request.getMessageBody(),
LawCaseRequest.class);
Long userId = authService.useUserAuthToken(lawCaseRequest.getToken());
String action = lawCaseRequest.getAction();
return switch (action) {
case "get_all" -> getAllUserLawCases(userId, lawCaseRequest, request);
case "get_byId" -> getLawCaseById(lawCaseRequest.getId(), request);
case "create" -> createLawCase(userId, lawCaseRequest, request);
case "update_priority" -> updatePriority(lawCaseRequest, request);
case "update_type" -> updateType(lawCaseRequest, request);
case "update_amount" -> updateAmount(lawCaseRequest, request);
case "delete" -> deleteLawCase(lawCaseRequest.getId(), request);
default -> new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("actions", "get_all, get_byId, create, update_priority, update_type, " +
"update_amount, delete")
);
};
}
private BaseResponse getAllUserLawCases(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
Page<LawCase> lawCases = lawCaseService.getAllUserLawCases(
userId,
lawCaseRequest.getPageSize(),
lawCaseRequest.getPageNumber(),
lawCaseRequest.getFilterType(),
lawCaseRequest.getFilterLawyer(),
lawCaseRequest.getFilterPriority(),
lawCaseRequest.getViolationId(),
lawCaseRequest.getSortBy(),
lawCaseRequest.getSortDir());
List<LawCaseResponse> responses = lawCases.getContent().stream()
.map(LawCaseResponse::fromEntity)
.collect(Collectors.toList());
PaginatedResponse<LawCaseResponse> paginatedResponse = new PaginatedResponse<>(
responses,
lawCases.getTotalElements(),
lawCases.getTotalPages(),
lawCases.getNumber(),
lawCases.getSize(),
lawCases.isFirst(),
lawCases.isLast());
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), paginatedResponse);
}
private BaseResponse getLawCaseById(Long id, BaseRequest request) {
LawCase lawCase = lawCaseService.getById(id);
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", id));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse createLawCase(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
try {
tariffInfoService.writeOffTokens(userId, TariffConstants.LEGAL_COST, OperationType.CREATE_LEGAL_CASE);
} catch (TariffNotFoundException e) {
return new BaseResponse(request.getMsgId(), MessageCode.USER_NOT_HAVE_TOKEN.getCode(),
MessageCode.USER_NOT_HAVE_TOKEN.getDescription(),
Map.of("error", "Tariff not found", "message", e.getMessage()));
} catch (MessagingException | IOException e) {
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
validateRequiredParameters(lawCaseRequest, request);
User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("User not found"));
LawCase lawCase = lawCaseService.addLawCase(
lawCaseRequest.getAmount(),
lawCaseRequest.getDescription(),
lawCaseRequest.getName(),
LawCasePriority.valueOf(lawCaseRequest.getPriority()),
lawCaseRequest.getViolationId(), user);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse updatePriority(LawCaseRequest lawCaseRequest, BaseRequest request) {
LawCase lawCase = lawCaseService.changePriority(
lawCaseRequest.getId(),
LawCasePriority.valueOf(lawCaseRequest.getPriority()));
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse updateType(LawCaseRequest lawCaseRequest, BaseRequest request) {
LawCase lawCase = lawCaseService.changeStatus(
lawCaseRequest.getId(),
LawCaseType.valueOf(lawCaseRequest.getType()));
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse updateAmount(LawCaseRequest lawCaseRequest, BaseRequest request) {
LawCase lawCase = lawCaseService.changeDamage(
lawCaseRequest.getId(),
lawCaseRequest.getAmount());
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse deleteLawCase(Long id, BaseRequest request) {
lawCaseService.deleteById(id);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), Map.of("id", id));
}
private void validateRequiredParameters(LawCaseRequest lawCaseRequest, BaseRequest request) {
if (lawCaseRequest.getName() == null) throw new NotValidFieldException("Name is required",
new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
MessageCode.NOTION_NOT_FOUND.getDescription(), Map.of("fields", "name")));
if (lawCaseRequest.getDescription() == null) throw new NotValidFieldException("Description is required",
new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
MessageCode.NOTION_NOT_FOUND.getDescription(), Map.of("fields", "description")));
}
}
@@ -122,10 +122,27 @@ public class NotificationHandler implements RequestHandler {
response); response);
} }
case "delete": {
MarkAsReadRequest markRequest = objectMapper.convertValue(request.getMessageBody(),
MarkAsReadRequest.class);
int delete = notificationService.delete(markRequest.getNotificationIds());
MarkAsReadResponse response = MarkAsReadResponse.builder()
.success(true)
.updatedCount(delete)
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
}
default: default:
ActionResponse response = ActionResponse.builder() ActionResponse response = ActionResponse.builder()
.action(action) .action(action)
.availableActions(Arrays.asList("list", "active", "markRead")) .availableActions(Arrays.asList("list", "active", "markRead", "delete"))
.build(); .build();
return new BaseResponse(request.getMsgId(), return new BaseResponse(request.getMsgId(),
@@ -0,0 +1,30 @@
package ru.soune.nocopy.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.IncomeStatisticResponse;
import ru.soune.nocopy.service.statistic.IncomeStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticIncomeHandler implements RequestHandler {
private final IncomeStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
IncomeStatisticResponse statistics = statisticService.getIncomeStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
}
}
@@ -0,0 +1,28 @@
package ru.soune.nocopy.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.ProtectedFilesStatisticResponse;
import ru.soune.nocopy.service.statistic.ProtectedFilesStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticProtectedFilesHandler implements RequestHandler {
private final ProtectedFilesStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
ProtectedFilesStatisticResponse statistics = statisticService.getProtectedFilesStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics);
}
}
@@ -0,0 +1,29 @@
package ru.soune.nocopy.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.SubscriberStatisticResponse;
import ru.soune.nocopy.service.statistic.SubscriberStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticSubscriberHandler implements RequestHandler {
private final SubscriberStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
SubscriberStatisticResponse statistics = statisticService.getSubscriberStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
}
}
@@ -0,0 +1,48 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.TariffStatisticFullResponse;
import ru.soune.nocopy.dto.statistic.TariffStatisticRequest;
import ru.soune.nocopy.dto.statistic.TariffStatisticResponse;
import ru.soune.nocopy.repository.TariffInfoRepository;
import java.util.List;
@Component
@AllArgsConstructor
public class StatisticTariffInfoFileHandler implements RequestHandler {
private ObjectMapper objectMapper;
private TariffInfoRepository tariffInfoRepository;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
TariffStatisticRequest tariffRequest =
objectMapper.convertValue(request.getMessageBody(), TariffStatisticRequest.class);
boolean activeOnly = tariffRequest.getActiveOnly() != null ? tariffRequest.getActiveOnly() : true;
List<Object[]> results = tariffInfoRepository.getTariffStatisticsNative(activeOnly);
List<TariffStatisticResponse> tariffs = results.stream()
.map(row -> new TariffStatisticResponse(
(String) row[0],
((Number) row[1]).longValue(),
((Number) row[2]).doubleValue()
))
.toList();
TariffStatisticFullResponse statistics = new TariffStatisticFullResponse(tariffs);
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics);
}
}
@@ -0,0 +1,29 @@
package ru.soune.nocopy.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.TokenStatisticResponse;
import ru.soune.nocopy.service.statistic.TokenStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticTokenHandler implements RequestHandler {
private final TokenStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
TokenStatisticResponse statistics = statisticService.getTokenStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
}
}
@@ -0,0 +1,29 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.UserDynamicStatisticResponse;
import ru.soune.nocopy.service.statistic.UserDynamicStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticUserDynamicHandler implements RequestHandler {
private final UserDynamicStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
UserDynamicStatisticResponse statistics = statisticService.getUserDynamicStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics);
}
}
@@ -0,0 +1,38 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.UserFilesStatisticRequest;
import ru.soune.nocopy.dto.statistic.UserFilesStatisticResponse;
import ru.soune.nocopy.repository.FileEntityRepository;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticUserFilesHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final FileEntityRepository fileRepository;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
UserFilesStatisticRequest userFilesStatisticRequest =
objectMapper.convertValue(request.getMessageBody(), UserFilesStatisticRequest.class);
int topUsers = userFilesStatisticRequest.getTopUsers();
String fileMimeType = userFilesStatisticRequest.getFileMimeType();
List<UserFilesStatisticResponse> files = fileRepository.findTopUsers(topUsers, fileMimeType);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), files);
}
}
@@ -0,0 +1,34 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.statistic.ViolationStatisticResponse;
import ru.soune.nocopy.service.statistic.ViolationStatisticService;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatisticViolationHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final ViolationStatisticService statisticService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
log.debug("Handling violation statistic request, msgId: {}", request.getMsgId());
ViolationStatisticResponse statistics = statisticService.getViolationStatistics();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
}
}
@@ -0,0 +1,472 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationPageDto;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationRequest;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenOperationHandler implements RequestHandler {
private final TokenOperationService tokenOperationService;
private final ObjectMapper objectMapper;
private final AuthService authService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
log.info("Handling TokenOperation request: msg_id={}, version={}",
request.getMsgId(), request.getVersion());
try {
TokenOperationRequest tokenRequest = objectMapper.convertValue(
request.getMessageBody(),
TokenOperationRequest.class
);
String action = tokenRequest.getAction();
if (action == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("error", "Action is required")
);
}
switch (action.toLowerCase()) {
case "get_list":
return handleGetList(request.getMsgId(), tokenRequest);
case "get_by_id":
return handleGetById(request.getMsgId(), tokenRequest);
case "get_by_user":
return handleGetByUser(request.getMsgId(), tokenRequest);
case "create":
return handleCreate(request.getMsgId(), tokenRequest);
case "update":
return handleUpdate(request.getMsgId(), tokenRequest);
case "delete":
return handleDelete(request.getMsgId(), tokenRequest);
case "delete_all_by_user":
return handleDeleteAllByUser(request.getMsgId(), tokenRequest);
case "get_stats":
return handleGetStats(request.getMsgId(), tokenRequest);
case "filter":
return handleFilter(request.getMsgId(), tokenRequest);
default:
return new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("available_actions",
"get_list, get_by_id, get_by_user, create, update, " +
"delete, delete_all_by_user, get_stats, filter")
);
}
} catch (Exception e) {
log.error("Error handling TokenOperation request", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", e.getMessage())
);
}
}
private BaseResponse handleGetList(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
Page<TokenOperation> page = tokenOperationService.findByUserId(
userId,
request.getPage() != null ? request.getPage() : 0,
request.getSize() != null ? request.getSize() : 20
);
TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
.content(page.getContent())
.pageNumber(page.getNumber())
.pageSize(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.first(page.isFirst())
.build();
Map<String, Object> responseData = new HashMap<>();
responseData.put("operations", pageDto);
responseData.put("total_elements", page.getTotalElements());
responseData.put("total_pages", page.getTotalPages());
responseData.put("current_page", page.getNumber());
responseData.put("page_size", page.getSize());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
responseData
);
} catch (Exception e) {
log.error("Error getting token operations list", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error getting operations: " + e.getMessage())
);
}
}
private BaseResponse handleGetById(Integer msgId, TokenOperationRequest request) {
log.info("Getting token operation by id: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
TokenOperation operation = tokenOperationService.findById(request.getOperationId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
operation
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error getting token operation by id", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", e.getMessage())
);
}
}
private BaseResponse handleGetByUser(Integer msgId, TokenOperationRequest request) {
return handleGetList(msgId, request);
}
private BaseResponse handleCreate(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
TokenOperation operation = tokenOperationService.create(
request.getOperationType(),
userId,
request.getSpent()
);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
operation
);
} catch (Exception e) {
log.error("Error creating token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error creating operation: " + e.getMessage())
);
}
}
private BaseResponse handleUpdate(Integer msgId, TokenOperationRequest request) {
log.info("Updating token operation: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
TokenOperation existing = tokenOperationService.findById(request.getOperationId());
if (request.getOperationType() != null) {
existing.setOperationType(request.getOperationType());
}
if (request.getSpent() != null) {
existing.setSpent(request.getSpent());
}
TokenOperation updated = tokenOperationService.update(request.getOperationId(), existing);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
updated
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error updating token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error updating operation: " + e.getMessage())
);
}
}
private BaseResponse handleDelete(Integer msgId, TokenOperationRequest request) {
log.info("Deleting token operation: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
tokenOperationService.deleteById(request.getOperationId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("message", "Operation deleted successfully",
"operation_id", request.getOperationId())
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error deleting token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error deleting operation: " + e.getMessage())
);
}
}
private BaseResponse handleDeleteAllByUser(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
tokenOperationService.deleteByUserId(userId);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("message", "All operations deleted successfully for user",
"user_id", userId)
);
} catch (Exception e) {
log.error("Error deleting all operations for user", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error deleting operations: " + e.getMessage())
);
}
}
private BaseResponse handleGetStats(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
if (userId == null) {
return new BaseResponse(
msgId,
MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
try {
Map<String, Long> statsByType = new HashMap<>();
long totalSpent = 0L;
for (OperationType type : OperationType.values()) {
Long spent = tokenOperationService.getTotalSpentByUserAndType(userId, type);
statsByType.put(type.name().toLowerCase(), spent != null ? spent : 0L);
totalSpent += spent != null ? spent : 0L;
}
Map<String, Object> statistics = new HashMap<>();
statistics.put("user_id", userId);
statistics.put("total_spent", totalSpent);
statistics.put("total_operations", tokenOperationService.countByUserId(userId));
statistics.put("stats_by_type", statsByType);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
} catch (Exception e) {
log.error("Error getting statistics", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error getting statistics: " + e.getMessage())
);
}
}
private BaseResponse handleFilter(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
Page<TokenOperation> page = tokenOperationService.findByFilters(
userId,
request.getOperationType(),
request.getMinSpent(),
request.getMaxSpent(),
request.getPage() != null ? request.getPage() : 0,
request.getSize() != null ? request.getSize() : 20,
request.getSortBy() != null ? request.getSortBy() : "createdAt",
request.getSortDirection() != null ? request.getSortDirection() : "desc"
);
TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
.content(page.getContent())
.pageNumber(page.getNumber())
.pageSize(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.first(page.isFirst())
.build();
Map<String, Object> responseData = new HashMap<>();
responseData.put("operations", pageDto);
responseData.put("total_elements", page.getTotalElements());
responseData.put("total_pages", page.getTotalPages());
responseData.put("current_page", page.getNumber());
responseData.put("page_size", page.getSize());
responseData.put("applied_filters", Map.of(
"user_id",userId != null ? userId : "all",
"operation_type", request.getOperationType() != null ? request.getOperationType() : "all",
"min_spent", request.getMinSpent() != null ? request.getMinSpent() : "none",
"max_spent", request.getMaxSpent() != null ? request.getMaxSpent() : "none"
));
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
responseData
);
} catch (Exception e) {
log.error("Error filtering operations", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error filtering operations: " + e.getMessage())
);
}
}
}
@@ -0,0 +1,108 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ActionResponse;
import ru.soune.nocopy.dto.user.moderation.UserVerificationRequest;
import ru.soune.nocopy.dto.user.moderation.UserVerificationResponse;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.user.ModerationStatus;
import ru.soune.nocopy.entity.user.moderation.UserVerification;
import ru.soune.nocopy.service.notification.NotificationService;
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
import java.util.Arrays;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class UserVerificationHandler implements RequestHandler {
private final UserVerificationService userVerificationService;
private final ObjectMapper objectMapper;
private final NotificationService notificationService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
UserVerificationRequest userVerificationRequest = objectMapper.convertValue(request.getMessageBody(),
UserVerificationRequest.class);
String action = userVerificationRequest.getAction();
switch (action) {
case "get_all_verifications":
return handleVerifications(request, userVerificationRequest);
case "verified":
return handleVerified(request, userVerificationRequest);
default:
ActionResponse response = ActionResponse.builder()
.action(action)
.availableActions(Arrays.asList(
"get_all_verifications", "verified"))
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
"Invalid action: " + action,
response);
}
}
private BaseResponse handleVerifications(BaseRequest request, UserVerificationRequest userVerificationRequest) throws Exception {
List<UserVerification> verifications = userVerificationService.verifications();
if (verifications.isEmpty()) {
return new BaseResponse(request.getMsgId(), MessageCode.USER_VERIFICATIONS_NOT_FOUND.getCode(),
MessageCode.USER_VERIFICATIONS_NOT_FOUND.getDescription(), null);
}
List<UserVerificationResponse> verificationResponses = verifications.stream().map(this::convertDto).toList();
return BaseResponse.builder()
.msgId(request.getMsgId())
.messageBody(verificationResponses)
.build();
}
private BaseResponse handleVerified(BaseRequest request, UserVerificationRequest userVerificationRequest) throws Exception {
UserVerification userVerification = userVerificationService.verifyUser(userVerificationRequest.getUserId(), userVerificationRequest.getMessage(),
userVerificationRequest.getAdminId(), userVerificationRequest.getVerified());
if (userVerification == null) {
return new BaseResponse(request.getMsgId(),
MessageCode.ADMIN_USER_NOT_FOUND.getCode(),
MessageCode.ADMIN_USER_NOT_FOUND.getDescription(),
null);
}
NotificationType notificationType = userVerification.getModerationStatus() == ModerationStatus.VERIFIED?
NotificationType.USER_VERIFIED: NotificationType.USER_NOT_VERIFIED;
notificationService.addNotification(notificationType, userVerification.getUserId());
return BaseResponse.builder()
.msgId(request.getMsgId())
.messageBody(UserVerificationResponse.builder()
.userVerificationId(userVerification.getId())
.moderationStatus(userVerification.getModerationStatus().toString())
.userId(userVerification.getUserId()))
.build();
}
private UserVerificationResponse convertDto(UserVerification userVerification) {
return UserVerificationResponse.builder()
.userVerificationId(userVerification.getId())
.userId(userVerification.getUserId())
.moderationStatus(userVerification.getModerationStatus().name())
.updateTime(userVerification.getUpdatedAt())
.build();
}
}
@@ -13,6 +13,7 @@ import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.violation.Violation; import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.geo.GeoCountryService;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.violation.ViolationService; import ru.soune.nocopy.service.violation.ViolationService;
import ru.soune.nocopy.service.violation.ViolationStatus; import ru.soune.nocopy.service.violation.ViolationStatus;
@@ -21,6 +22,7 @@ import java.io.FileNotFoundException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -38,6 +40,8 @@ public class ViolationHandler implements RequestHandler {
private final FileEntityService fileEntityService; private final FileEntityService fileEntityService;
private final GeoCountryService geoCountryService;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
@@ -112,10 +116,16 @@ public class ViolationHandler implements RequestHandler {
); );
} }
List<ViolationResponse.ViolationDto> violationDtos = violationPage.getContent() List<Violation> content = violationPage.getContent();
.stream() List<ViolationResponse.ViolationDto> violationDtos = new ArrayList<>();
.map(ViolationResponse.ViolationDto::fromEntity)
.toList(); for (Violation violation : content) {
ViolationResponse.ViolationDto violationDto = ViolationResponse.ViolationDto.fromEntity(violation);
violationDto.setCountry(geoCountryService.getCountryName(violation.getPageUrl()));
violationDto.setCountryCode(geoCountryService.getCountryCode(violation.getPageUrl()));
violationDtos.add(violationDto);
}
ViolationResponse response = ViolationResponse.builder() ViolationResponse response = ViolationResponse.builder()
.violations(violationDtos) .violations(violationDtos)
@@ -25,4 +25,16 @@ public interface ComplaintEntityRepository extends JpaRepository<ComplaintEntity
@Transactional @Transactional
@Query("UPDATE ComplaintEntity c SET c.status = :status WHERE c.id = :id") @Query("UPDATE ComplaintEntity c SET c.status = :status WHERE c.id = :id")
int updateStatus(Long id, ComplaintStatus status); 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.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; 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.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.ProtectionStatus;
import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -69,4 +71,64 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
Page<FileEntity> findByStatusIn(List<FileStatus> statuses, Pageable pageable); Page<FileEntity> findByStatusIn(List<FileStatus> statuses, Pageable pageable);
List<FileEntity> findByUserIdAndStatusIn(Long userId, List<FileStatus> statuses); 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 JOIN file_entities f
ON f.id = h.file_id ON f.id = h.file_id
WHERE ref.file_id = :fileId AND f.user_id = :userId WHERE ref.file_id = :fileId AND f.user_id = :userId
AND f.status = 'ACTIVE' AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""", """,
nativeQuery = true) nativeQuery = true)
List<SimilarImageProjection> findCandidatesFromUserFiles( List<SimilarImageProjection> findCandidatesFromUserFiles(
@@ -82,7 +82,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f JOIN file_entities f
ON f.id = h.file_id ON f.id = h.file_id
WHERE ref.file_id = :fileId AND f.user_id IN :userIds 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) nativeQuery = true)
List<SimilarImageProjection> findCandidatesFromUserFiles( List<SimilarImageProjection> findCandidatesFromUserFiles(
@@ -108,7 +108,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f ON f.id = h.file_id JOIN file_entities f ON f.id = h.file_id
WHERE h.hash64_hi = :hash64Hi WHERE h.hash64_hi = :hash64Hi
AND h.hash64_lo = :hash64Lo AND h.hash64_lo = :hash64Lo
AND f.status = 'ACTIVE' AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""", """,
nativeQuery = true) nativeQuery = true)
List<SimilarImageProjection> findExactDuplicates( 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.notification.NotificationType;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
public interface NotificationRepository extends JpaRepository<Notification, Long> { public interface NotificationRepository extends JpaRepository<Notification, Long> {
@@ -29,12 +30,13 @@ public interface NotificationRepository extends JpaRepository<Notification, Long
long countByUserAndStatus(User user, NotificationStatus status); long countByUserAndStatus(User user, NotificationStatus status);
@Modifying @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") "n.status = :oldStatus")
int updateStatus(@Param("ids") List<Long> ids, int updateStatus(@Param("ids") List<Long> ids,
@Param("user") User user, @Param("user") User user,
@Param("oldStatus") NotificationStatus oldStatus, @Param("oldStatus") NotificationStatus oldStatus,
@Param("newStatus") NotificationStatus newStatus); @Param("newStatus") NotificationStatus newStatus,
@Param("updateTime") LocalDateTime updateTime);
Page<Notification> getNotificationsByUser(User user, Pageable pageable); Page<Notification> getNotificationsByUser(User user, Pageable pageable);
} }
@@ -1,6 +1,7 @@
package ru.soune.nocopy.repository; package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.payment.Payment; import ru.soune.nocopy.entity.payment.Payment;
@@ -11,4 +12,23 @@ import java.util.Optional;
public interface PaymentRepository extends JpaRepository<Payment, String> { public interface PaymentRepository extends JpaRepository<Payment, String> {
Optional<Payment> findByPaymentUuid(String paymentUuid); Optional<Payment> findByPaymentUuid(String paymentUuid);
List<Payment> findByUserId(Long userId); 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") @Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId")
int countTotalInvitees(@Param("userId") Long 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.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffTimeTerm;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -13,4 +14,57 @@ import java.util.List;
public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> { public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> {
@Query("SELECT t FROM TariffInfo t WHERE t.autoRenewal = true AND t.endTariff <= :date") @Query("SELECT t FROM TariffInfo t WHERE t.autoRenewal = true AND t.endTariff <= :date")
List<TariffInfo> findForAutoRenewal(@Param("date") LocalDateTime 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 jakarta.validation.constraints.Size;
import org.springframework.data.jpa.repository.JpaRepository; 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.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email); User findByEmail(String email);
@@ -14,4 +17,32 @@ public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByCompanyId(String companyId); List<User> findByCompanyId(String companyId);
long countByCompanyId(String companyId); long countByCompanyId(String companyId);
User findByPersonalTariffInfo(TariffInfo tariffInfo); 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, List<Violation> findByFileEntityInAndStatusAndCreatedDateBetween(List<FileEntity> files, String status,
LocalDateTime startDate, LocalDateTime endDate, LocalDateTime startDate, LocalDateTime endDate,
Sort sort); 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();
} }
@@ -0,0 +1,90 @@
package ru.soune.nocopy.service.complaint;
import lombok.RequiredArgsConstructor;
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 ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.LawCaseRepository;
import java.math.BigDecimal;
import java.util.List;
@Service
@RequiredArgsConstructor
public class LawCaseService {
private final LawCaseRepository lawCaseRepository;
public LawCase addLawCase(BigDecimal amount, String description, String name,
LawCasePriority priority, Long violationId, User user) {
LawCase lawCase = new LawCase();
BigDecimal damage = amount == null ? BigDecimal.ZERO : amount;
lawCase.setAmount(damage);
lawCase.setName(name);
lawCase.setDescription(description);
lawCase.setPriority(priority);
lawCase.setViolationId(violationId);
lawCase.setUser(user);
return lawCaseRepository.save(lawCase);
}
public LawCase getById(Long id) {
return lawCaseRepository.findById(id).orElse(null);
}
public LawCase changeStatus(Long id, LawCaseType type) {
LawCase lawCase = lawCaseRepository.findById(id).orElse(null);
if (lawCase == null) {
return null;
}
lawCase.setType(type);
return lawCaseRepository.save(lawCase);
}
public LawCase changePriority(Long id, LawCasePriority priority) {
LawCase lawCase = lawCaseRepository.findById(id).orElse(null);
if (lawCase == null) {
return null;
}
lawCase.setPriority(priority);
return lawCaseRepository.save(lawCase);
}
public LawCase changeDamage(Long id, BigDecimal damage) {
LawCase lawCase = lawCaseRepository.findById(id).orElse(null);
if (lawCase == null) {
return null;
}
lawCase.setAmount(damage);
return lawCaseRepository.save(lawCase);
}
public Page<LawCase> getAllUserLawCases(Long userId, int pageSize, int pageNumber,
LawCaseType lawCaseType, String lawyer,
LawCasePriority lawCasePriority,
Long violationId,
String propertySort, String ascDesc) {
Sort.Direction direction = ascDesc.equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC;
Sort sort = Sort.by(direction, propertySort);
Pageable pageable = PageRequest.of(pageNumber, pageSize, sort);
return lawCaseRepository.getUserLawCases(userId, lawCaseType, lawyer, lawCasePriority, violationId, pageable);
}
public void deleteById(Long id) {
lawCaseRepository.deleteById(id);
}
}
@@ -3,6 +3,8 @@ package ru.soune.nocopy.service.file;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.FileEntityResponse;
@@ -81,17 +83,61 @@ public class FileEntityService {
for (Long uId : userRepository.findByCompanyId(user.getCompany().getId()).stream() for (Long uId : userRepository.findByCompanyId(user.getCompany().getId()).stream()
.map(User::getId) .map(User::getId)
.toList()) { .toList()) {
allFiles.addAll(fileEntityRepository.findByUserIdAndStatusIn(uId, allFiles.addAll(getAllUserFiles(uId, List.of(FileStatus.ACTIVE, FileStatus.BLOCKED,
List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION))); FileStatus.MODERATION)));
} }
} else { } else {
allFiles = fileEntityRepository.findByUserIdAndStatusIn(userId, allFiles = getAllUserFiles(userId, List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION));
List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION));
} }
return allFiles; return allFiles;
} }
public List<FileEntity> getAllUserFiles(Long userId, List<FileStatus> statuses) {
User user = userRepository.findById(userId).orElseThrow();
List<FileEntity> allFiles = new ArrayList<>();
if (user.getCompany() != null) {
for (Long uId : userRepository.findByCompanyId(user.getCompany().getId()).stream()
.map(User::getId)
.toList()) {
allFiles.addAll(fileEntityRepository.findByUserIdAndStatusIn(uId, statuses));
}
} else {
allFiles = fileEntityRepository.findByUserIdAndStatusIn(userId, statuses);
}
return allFiles;
}
@Transactional(readOnly = true)
public FileResponse getUserFiles(Long userId, Pageable pageable, int version) {
User user = userRepository.findById(userId).orElseThrow();
List<User> users = user.getCompany() != null ?
userRepository.findByCompanyId(user.getCompany().getId()) : List.of(user);
List<FileStatus> statusesForSearch = List.of(FileStatus.ACTIVE, FileStatus.BLOCKED, FileStatus.MODERATION);
Page<FileEntity> files = fileEntityRepository.findFiles(users.stream().map(User::getId).toList(),
statusesForSearch, pageable);
Long totalSize = fileEntityRepository.sumFileSize(
users.stream().map(User::getId).toList(),
statusesForSearch);
List<FileEntityResponse> filesToResponse = files.stream()
.map(file -> convertToResponse(file, version))
.toList();
return FileResponse.builder()
.files(filesToResponse)
.totalCount((int) files.getTotalElements())
.totalSize(totalSize != null ? totalSize : 0L)
.formattedTotalSize(formatFileSize(totalSize != null ? totalSize : 0L))
.page(pageable.getPageNumber())
.pageSize(pageable.getPageSize())
.build();
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) { public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) {
User user = userRepository.findById(userId).orElseThrow(); User user = userRepository.findById(userId).orElseThrow();
@@ -25,8 +25,12 @@ public class FileStatsService {
private final UserRepository userRepository; private final UserRepository userRepository;
private final FileEntityService fileEntityService;
public FileInfoUserResponse getUserFileStats(Long userId) { public FileInfoUserResponse getUserFileStats(Long userId) {
List<FileEntity> userFiles = fileEntityRepository.findByUserId(userId); List<FileEntity> userFiles = fileEntityService.getAllUserFiles(userId, List.of(FileStatus.ACTIVE,
FileStatus.BLOCKED, FileStatus.MODERATION));
return calculateStats(userFiles); return calculateStats(userFiles);
} }
@@ -34,32 +38,24 @@ public class FileStatsService {
return FileInfoUserResponse.builder() return FileInfoUserResponse.builder()
.allFileSize(calculateTotalSize(files)) .allFileSize(calculateTotalSize(files))
.fileCount(calculateTotalCount(files)) .fileCount(calculateTotalCount(files))
.filesCheck(calculateByStatus(files, FileStatus.CHECKED)) // .filesCheck(calculateByStatus(files, FileStatus.CHECKED))
.filesViolation(calculateByStatus(files, FileStatus.VIOLATION)) // .filesViolation(calculateByStatus(files, FileStatus.VIOLATION))
.protectedFilesCount(protectedUserFiles(files)) .protectedFilesCount(protectedUserFiles(files))
.imagesSize(calculateMediaSize(files, FileType.IMAGE)) .imagesSize(calculateMediaSize(files, FileType.IMAGE))
.imagesCount(calculateMediaCount(files, FileType.IMAGE)) .imagesCount(calculateMediaCount(files, FileType.IMAGE))
.imagesCheck(calculateMediaByStatus(files, FileType.IMAGE, FileStatus.CHECKED))
.imagesViolations(calculateMediaByStatus(files, FileType.IMAGE, FileStatus.VIOLATION))
.protectedImageFilesCount(protectedUserFiles(files, FileType.IMAGE)) .protectedImageFilesCount(protectedUserFiles(files, FileType.IMAGE))
.videosSize(calculateMediaSize(files, FileType.VIDEO)) .videosSize(calculateMediaSize(files, FileType.VIDEO))
.videosCount(calculateMediaCount(files, FileType.VIDEO)) .videosCount(calculateMediaCount(files, FileType.VIDEO))
.videosCheck(calculateMediaByStatus(files, FileType.VIDEO, FileStatus.CHECKED))
.videosViolations(calculateMediaByStatus(files, FileType.VIDEO, FileStatus.VIOLATION))
.protectedVideoFilesCount(protectedUserFiles(files, FileType.VIDEO)) .protectedVideoFilesCount(protectedUserFiles(files, FileType.VIDEO))
.audiosSize(calculateMediaSize(files, FileType.AUDIO)) .audiosSize(calculateMediaSize(files, FileType.AUDIO))
.audiosCount(calculateMediaCount(files, FileType.AUDIO)) .audiosCount(calculateMediaCount(files, FileType.AUDIO))
.audiosCheck(calculateMediaByStatus(files, FileType.AUDIO, FileStatus.CHECKED))
.audiosViolations(calculateMediaByStatus(files, FileType.AUDIO, FileStatus.VIOLATION))
.protectedAudioFilesCount(protectedUserFiles(files, FileType.AUDIO)) .protectedAudioFilesCount(protectedUserFiles(files, FileType.AUDIO))
.documentSize(calculateMediaSize(files, FileType.DOCUMENT)) .documentSize(calculateMediaSize(files, FileType.DOCUMENT))
.documentCount(calculateMediaCount(files, FileType.DOCUMENT)) .documentCount(calculateMediaCount(files, FileType.DOCUMENT))
.documentCheck(calculateMediaByStatus(files, FileType.DOCUMENT, FileStatus.CHECKED))
.documentViolations(calculateMediaByStatus(files, FileType.DOCUMENT, FileStatus.VIOLATION))
.protectedDocumentFilesCount(protectedUserFiles(files, FileType.DOCUMENT)) .protectedDocumentFilesCount(protectedUserFiles(files, FileType.DOCUMENT))
.build(); .build();
} }
@@ -16,6 +16,9 @@ public interface FileUploadService {
UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile, Integer findSimilar); UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile, Integer findSimilar);
UploadProgressResponse uploadPassportChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile,
Long userId);
UploadProgressResponse getUploadProgress(String uploadId); UploadProgressResponse getUploadProgress(String uploadId);
FileEntity completeFileProcessing(FileUploadSession session, FileStatus status); FileEntity completeFileProcessing(FileUploadSession session, FileStatus status);
@@ -0,0 +1,146 @@
package ru.soune.nocopy.service.file;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
import ru.soune.nocopy.service.file.moderation.ModerationFileService;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service
@Slf4j
@RequiredArgsConstructor
public class ZipService {
private final ModerationFileService moderationFileService;
private static final int MAX_SIZE_MB = 20;
private static final int FIRST_PART = 1;
private static final int SECOND_PART = 2;
public void createAndSplitZip(Long userId, List<FileEntity> files) throws IOException {
byte[] zipBytes = createZip(files);
String base64String = Base64.getEncoder().encodeToString(zipBytes);
String[] parts = splitBase64(base64String);
saveSplitFiles(userId, parts[0], parts[1]);
}
public byte[] assembleZipFromSplitFiles(Long userId) throws IOException {
List<ModerationPassportFile> activeFiles = moderationFileService.getActiveFiles();
String part1Content = null;
String part2Content = null;
for (ModerationPassportFile file : activeFiles) {
Path filePath = Paths.get(file.getPath());
String content = Files.readString(filePath);
if (file.getPart() == FIRST_PART) {
part1Content = content;
} else if (file.getPart() == SECOND_PART) {
part2Content = content;
}
}
if (part1Content == null || part2Content == null) {
throw new IOException("Missing part 1 or part 2 for user: " + userId);
}
String base64String = mergeBase64(part1Content, part2Content);
return Base64.getDecoder().decode(base64String);
}
private byte[] createZip(List<FileEntity> files) throws IOException {
long totalSize = files.stream()
.map(FileEntity::getFileSize)
.reduce(0L, Long::sum);
if (totalSize > MAX_SIZE_MB * 1024 * 1024) {
throw new IllegalArgumentException(
String.format("Total size %.2f MB exceeds %d MB limit",
totalSize / (1024.0 * 1024.0), MAX_SIZE_MB)
);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
for (FileEntity file: files) {
String filePath = file.getFilePath();
String fileName = file.getStoredFileName();
Path path = Paths.get(filePath);
ZipEntry zipEntry = new ZipEntry(fileName);
zos.putNextEntry(zipEntry);
Files.copy(path, zos);
zos.closeEntry();
log.info("Added file {} to ZIP", fileName);
}
}
return baos.toByteArray();
}
private String[] splitBase64(String base64) {
StringBuilder part1 = new StringBuilder();
StringBuilder part2 = new StringBuilder();
for (int i = 0; i < base64.length(); i++) {
if (i % 2 == 0) {
part1.append(base64.charAt(i));
} else {
part2.append(base64.charAt(i));
}
}
return new String[]{part1.toString(), part2.toString()};
}
private void saveSplitFiles(Long userId, String part1, String part2) throws IOException {
Path userDir = Paths.get("/uploads/passport", String.valueOf(userId));
Files.createDirectories(userDir);
Path file1 = userDir.resolve(userId + "___1.txt");
Files.writeString(file1, part1);
Path file2 = userDir.resolve(userId + "___2.txt");
Files.writeString(file2, part2);
Map<Integer, String> files = Map.of(FIRST_PART, file1.toString(), SECOND_PART, file2.toString());
moderationFileService.addFiles(userId, files);
log.info("Saved split files: {} and {}", file1, file2);
}
private String mergeBase64(String part1, String part2) {
StringBuilder merged = new StringBuilder();
int maxLength = Math.max(part1.length(), part2.length());
for (int i = 0; i < maxLength; i++) {
if (i < part1.length()) {
merged.append(part1.charAt(i));
}
if (i < part2.length()) {
merged.append(part2.charAt(i));
}
}
return merged.toString();
}
}
@@ -19,6 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.file.UploadProgressResponse; import ru.soune.nocopy.dto.file.UploadProgressResponse;
import ru.soune.nocopy.entity.file.*; import ru.soune.nocopy.entity.file.*;
import ru.soune.nocopy.entity.notification.NotificationType; import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.exception.*; import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.FileUploadSessionRepository; import ru.soune.nocopy.repository.FileUploadSessionRepository;
@@ -30,7 +31,9 @@ import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService; import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.tariff.TariffConstants; import ru.soune.nocopy.service.tariff.TariffConstants;
import ru.soune.nocopy.service.file.moderation.ModerationFileService;
import ru.soune.nocopy.service.notification.NotificationService; import ru.soune.nocopy.service.notification.NotificationService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffInfoService; import ru.soune.nocopy.service.tariff.TariffInfoService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
@@ -91,6 +94,10 @@ public class FileUploadServiceImpl implements FileUploadService {
private final NotificationService notificationService; private final NotificationService notificationService;
private final AuthService authService;
private final ModerationFileService moderationFileService;
private final CloudStorageService cloudStorageService; private final CloudStorageService cloudStorageService;
@PostConstruct @PostConstruct
@@ -189,35 +196,28 @@ public class FileUploadServiceImpl implements FileUploadService {
@Override @Override
public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber, public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber,
MultipartFile chunkFile, Integer findSimilar) { MultipartFile chunkFile, Integer findSimilar) {
FileUploadSession session = sessionRepository.findById(uploadId) FileUploadSession session = sessionRepository.findById(uploadId)
.orElseThrow(() -> new UploadSessionNotFoundException(uploadId)); .orElseThrow(() -> new UploadSessionNotFoundException(uploadId));
validateSession(session); validateSession(session, chunkNumber, chunkFile);
if (session.getExpiresAt().isBefore(LocalDateTime.now())) {
handleExpiredSession(session);
throw new FileUploadException("Upload session expired");
}
if (chunkNumber < 0 || chunkNumber >= session.getTotalChunks()) {
throw new FileUploadException(
String.format("Invalid chunk number %d. Expected number: %d",
chunkNumber, session.getTotalChunks() - 1));
}
if (chunkNumber == 0 && chunkFile.getSize() > chunkSize || chunkNumber + 1 == (session.getTotalChunks())
&& chunkFile.getSize() > chunkSize) {
throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize);
}
if (chunkNumber > 0 && chunkNumber + 1 < session.getTotalChunks() && chunkFile.getSize() != chunkSize) {
throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize);
}
return processChunk(session, chunkNumber, chunkFile, findSimilar); return processChunk(session, chunkNumber, chunkFile, findSimilar);
} }
@Override
public UploadProgressResponse uploadPassportChunk(String uploadId, Integer chunkNumber, MultipartFile chunkFile,
Long userId) {
FileUploadSession session = sessionRepository.findById(uploadId).orElseThrow(() ->
new UploadSessionNotFoundException(uploadId));
validateSession(session, chunkNumber, chunkFile);
UploadProgressResponse uploadProgressResponse = processChunk(session, chunkNumber, chunkFile);
return uploadProgressResponse;
}
@Override @Override
public UploadProgressResponse getUploadProgress(String uploadId) { public UploadProgressResponse getUploadProgress(String uploadId) {
FileUploadSession session = sessionRepository.findById(uploadId) FileUploadSession session = sessionRepository.findById(uploadId)
@@ -243,20 +243,23 @@ public class FileUploadServiceImpl implements FileUploadService {
.fileExtension(session.getExtension()) .fileExtension(session.getExtension())
.checksum(checksum) .checksum(checksum)
.uploadSessionId(session.getUploadId()) .uploadSessionId(session.getUploadId())
.protectionStatus(ProtectionStatus.PROCESSING)
.status(status) .status(status)
.build(); .build();
if (status != FileStatus.TEMP) {
fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING);
}
FileEntity saved = fileEntityRepository.save(fileEntity); FileEntity saved = fileEntityRepository.save(fileEntity);
if (session.getFileType().equals("image")) { if (session.getFileType().equals("image") && status != FileStatus.PRIVATE) {
Map<String, Long> hash = imageHashService.calculateHash(filePath); Map<String, Long> hash = imageHashService.calculateHash(filePath);
imageHashService.create(saved, hash); imageHashService.create(saved, hash);
} }
cleanupSessionFiles(session); cleanupSessionFiles(session);
if (status != FileStatus.TEMP) { if (status != FileStatus.TEMP && status != FileStatus.PRIVATE) {
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo()); FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo());
noCopyFileService.addFile(fileInfo); noCopyFileService.addFile(fileInfo);
} }
@@ -271,7 +274,8 @@ public class FileUploadServiceImpl implements FileUploadService {
} }
} }
private void validateSession(FileUploadSession session) { private void validateSession(FileUploadSession session, Integer chunkNumber,
MultipartFile chunkFile) {
UploadStatus status = session.getStatus(); UploadStatus status = session.getStatus();
if (status == UploadStatus.FAILED) { if (status == UploadStatus.FAILED) {
@@ -298,6 +302,67 @@ public class FileUploadServiceImpl implements FileUploadService {
session.setStatus(UploadStatus.UPLOADING); session.setStatus(UploadStatus.UPLOADING);
sessionRepository.save(session); sessionRepository.save(session);
} }
if (session.getExpiresAt().isBefore(LocalDateTime.now())) {
handleExpiredSession(session);
throw new FileUploadException("Upload session expired");
}
if (chunkNumber < 0 || chunkNumber >= session.getTotalChunks()) {
throw new FileUploadException(
String.format("Invalid chunk number %d. Expected number: %d",
chunkNumber, session.getTotalChunks() - 1));
}
if (chunkNumber == 0 && chunkFile.getSize() > chunkSize || chunkNumber + 1 == (session.getTotalChunks())
&& chunkFile.getSize() > chunkSize) {
throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize);
}
if (chunkNumber > 0 && chunkNumber + 1 < session.getTotalChunks() && chunkFile.getSize() != chunkSize) {
throw new ChunkSizeExceededException(chunkFile.getSize(), chunkSize);
}
}
private UploadProgressResponse processChunk(FileUploadSession session, Integer chunkNumber, MultipartFile chunkFile) {
String chunkPath = null;
try {
chunkPath = saveChunkWithIntegrityCheck(session, chunkNumber, chunkFile);
session.getChunkPaths().put(chunkNumber, chunkPath);
session.setChunksUploaded(session.getChunksUploaded() + 1);
session.setStatus(UploadStatus.UPLOADING);
session.setExpiresAt(LocalDateTime.now().plusMinutes(1));
boolean isLastChunk = session.getChunksUploaded().equals(session.getTotalChunks());
if (isLastChunk) {
String finalFilePath = assembleFileSynchronously(session);
session.setStatus(UploadStatus.COMPLETED);
session.setFilePath(finalFilePath);
completeFileProcessingAsync(session, FileStatus.PRIVATE);
} else {
sessionRepository.save(session);
}
return UploadProgressResponse.fromSession(session);
} catch (Exception e) {
if (chunkPath != null) {
cleanupFailedChunk(chunkPath);
}
log.error("Failed to process chunk {} for session {}: {}",
chunkNumber, session.getUploadId(), e.getMessage(), e);
session.setStatus(UploadStatus.FAILED);
session.setLastError(e.getMessage());
sessionRepository.save(session);
throw new FileUploadException("Failed to upload chunk: " + e.getMessage(), e);
}
} }
@Autowired @Autowired
@@ -397,7 +462,7 @@ public class FileUploadServiceImpl implements FileUploadService {
if (status != FileStatus.TEMP) { if (status != FileStatus.TEMP) {
String fileType = session.getFileType(); String fileType = session.getFileType();
tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType)); tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType), OperationType.FILE_UPLOAD);
} }
} else { } else {
sessionRepository.save(session); sessionRepository.save(session);
@@ -0,0 +1,54 @@
package ru.soune.nocopy.service.file.moderation;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
import ru.soune.nocopy.repository.ModerationFileRepository;
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class ModerationFileService {
private final ModerationFileRepository moderationFileRepository;
private final UserVerificationService userVerificationService;
public void addFiles(Long userId, Map<Integer, String> files) {
List<ModerationPassportFile> moderationFileRepositoryByStatus =
moderationFileRepository.findByStatus(ModerationFileStatus.ACTIVE.getName());
if (!moderationFileRepositoryByStatus.isEmpty()) {
disActiveDocuments(moderationFileRepositoryByStatus);
}
for (Map.Entry<Integer, String> file : files.entrySet()) {
ModerationPassportFile moderationFile = ModerationPassportFile.builder().userId(userId)
.path(file.getValue())
.part(file.getKey())
.userId(userId)
.status(ModerationFileStatus.ACTIVE.getName())
.build();
moderationFileRepository.save(moderationFile);
}
userVerificationService.initUserVerification(userId);
}
public List<ModerationPassportFile> getActiveFiles() {
return moderationFileRepository.findByStatus(ModerationFileStatus.ACTIVE.getName());
}
private void disActiveDocuments(List<ModerationPassportFile> moderationFileRepositoryByStatus) {
for (ModerationPassportFile moderationFile : moderationFileRepositoryByStatus) {
moderationFile.setStatus(ModerationFileStatus.NOT_ACTIVE.getName());
moderationFileRepository.save(moderationFile);
}
}
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.service.file.moderation;
import lombok.Getter;
@Getter
public enum ModerationFileStatus {
ACTIVE("active"), NOT_ACTIVE("not_active");
private final String name;
ModerationFileStatus(String name) {
this.name = name;
}
}
@@ -0,0 +1,66 @@
package ru.soune.nocopy.service.geo;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CountryResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.InetAddress;
@Service
@Slf4j
public class GeoCountryService {
private final DatabaseReader dbReader;
public GeoCountryService() throws IOException {
Resource resource = new ClassPathResource("geo/GeoLite2-Country.mmdb");
this.dbReader = new DatabaseReader.Builder(resource.getInputStream()).build();
}
public String getCountryName(String input) {
try {
CountryResponse response = extractCountry(input);
return response.getCountry().getName();
} catch (Exception e) {
log.error("Failed for input: {}", input, e);
return "UNKNOWN";
}
}
public String getCountryCode(String input) {
try {
CountryResponse response = extractCountry(input);
return response.getCountry().getIsoCode();
} catch (Exception e) {
log.error("Failed for input: {}", input, e);
return "UNKNOWN";
}
}
private CountryResponse extractCountry(String input) throws IOException, GeoIp2Exception {
String domain = extractDomain(input);
InetAddress ip = InetAddress.getByName(domain);
return dbReader.country(ip);
}
private String extractDomain(String input) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException("Input is empty");
}
String cleaned = input.replaceAll("^https?://", "");
cleaned = cleaned.split("/")[0];
cleaned = cleaned.split(":")[0];
return cleaned;
}
}
@@ -15,6 +15,7 @@ import ru.soune.nocopy.entity.notification.NotificationMessage;
import ru.soune.nocopy.entity.notification.NotificationType; import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffType; import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.TariffNotFoundException; import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.repository.FileMonitoringRepository; import ru.soune.nocopy.repository.FileMonitoringRepository;
@@ -64,9 +65,10 @@ public class MonitoringSearchService {
MonitoringType monitoringType = monitoring.getMonitoringType(); MonitoringType monitoringType = monitoring.getMonitoringType();
TariffDTO tariffMonitoring = tariffService.getTariffByType(TariffType.valueOf(monitoringType.name())); TariffDTO tariffMonitoring = tariffService.getTariffByType(TariffType.valueOf(monitoringType.name()));
User user = userRepository.findById(monitoring.getUserId()).orElseThrow();
try { try {
tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens()); tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens(), OperationType.MONITORING);
boolean useYandex = searchProperties.getEngines().getOrDefault("yandex", boolean useYandex = searchProperties.getEngines().getOrDefault("yandex",
new SearchProperties.EngineConfig()).isEnabled(); new SearchProperties.EngineConfig()).isEnabled();
@@ -88,10 +90,14 @@ public class MonitoringSearchService {
allResults.addAll(yandexImages); allResults.addAll(yandexImages);
log.info("Yandex search found {} results", yandexImages.size()); log.info("Yandex search found {} results", yandexImages.size());
} catch (TimeoutException e) { } catch (TimeoutException | IOException e) {
log.warn("Yandex search timeout for monitoring file {}", monitoring.getFile().getId()); log.error("Error processing monitoring search", e);
} catch (IOException e) { monitoring.setLastRunStatus("ERROR: " + e.getMessage());
log.error("Yandex search failed for monitoring file {}", monitoring.getFile().getId(), e); updateNextRun(monitoring);
monitoringRepository.save(monitoring);
notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
} }
} else { } else {
log.info("Yandex search is disabled by settings"); log.info("Yandex search is disabled by settings");
@@ -108,10 +114,14 @@ public class MonitoringSearchService {
allResults.addAll(googleImages); allResults.addAll(googleImages);
log.info("Google search found {} results", googleImages.size()); log.info("Google search found {} results", googleImages.size());
} catch (TimeoutException e) { } catch (TimeoutException | IOException e) {
log.warn("Google search timeout for monitoring file {}", monitoring.getFile().getId()); log.error("Error processing monitoring search", e);
} catch (IOException e) { monitoring.setLastRunStatus("ERROR: " + e.getMessage());
log.error("Google search failed for monitoring file {}", monitoring.getFile().getId(), e); updateNextRun(monitoring);
monitoringRepository.save(monitoring);
notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
} }
} else { } else {
log.info("Google search is disabled by settings"); log.info("Google search is disabled by settings");
@@ -138,7 +148,6 @@ public class MonitoringSearchService {
} }
} catch (TariffNotFoundException e) { } catch (TariffNotFoundException e) {
User user = userRepository.findById(monitoring.getUserId()).orElseThrow();
TariffInfo activeTariffInfo = user.getActiveTariffInfo(); TariffInfo activeTariffInfo = user.getActiveTariffInfo();
int currentTokens = activeTariffInfo.getTokens() + activeTariffInfo.getBoughtTokens(); int currentTokens = activeTariffInfo.getTokens() + activeTariffInfo.getBoughtTokens();
@@ -153,7 +162,6 @@ public class MonitoringSearchService {
notificationService.addNotification(NotificationType.TOKEN_NOT_FOUND, user.getId(), notificationService.addNotification(NotificationType.TOKEN_NOT_FOUND, user.getId(),
NotificationMessage.FILE_SEARCH.getMessageKey()); NotificationMessage.FILE_SEARCH.getMessageKey());
} catch (Exception e) { } catch (Exception e) {
User user = userRepository.findById(monitoring.getUserId()).orElseThrow();
log.error("Error processing monitoring search", e); log.error("Error processing monitoring search", e);
monitoring.setLastRunStatus("ERROR: " + e.getMessage()); monitoring.setLastRunStatus("ERROR: " + e.getMessage());
updateNextRun(monitoring); updateNextRun(monitoring);
@@ -17,6 +17,7 @@ import ru.soune.nocopy.repository.NotificationRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.user.UserService; import ru.soune.nocopy.service.user.UserService;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -33,15 +34,12 @@ public class NotificationService {
@Transactional @Transactional
public void addNotification(NotificationType notificationType, long userId, Object... args) { public void addNotification(NotificationType notificationType, long userId, Object... args) {
Locale locale = Locale.forLanguageTag("ru");
String message = messageSource.getMessage(notificationType.getMessageKey(), args, locale);
Notification notification = Notification.builder() Notification notification = Notification.builder()
.notificationType(notificationType) .notificationType(notificationType)
.createdAt(LocalDateTime.now())
.user(userRepository.findById(userId).orElseThrow()) .user(userRepository.findById(userId).orElseThrow())
.status(NotificationStatus.NEW) .status(NotificationStatus.NEW)
.message(message) .message(notificationType.getMessageKey())
.build(); .build();
notificationRepository.save(notification); notificationRepository.save(notification);
@@ -110,7 +108,24 @@ public class NotificationService {
if (notificationIds == null || notificationIds.isEmpty()) { if (notificationIds == null || notificationIds.isEmpty()) {
return 0; return 0;
} }
return notificationRepository.updateStatus(notificationIds, user, NotificationStatus.NEW, return notificationRepository.updateStatus(notificationIds, user, NotificationStatus.NEW,
NotificationStatus.READIED); NotificationStatus.READIED, LocalDateTime.now());
}
@Transactional
public int delete(List<Long> notificationIds) {
if (notificationIds == null || notificationIds.isEmpty()) {
return 0;
}
int count = 0;
for (Long notificationId : notificationIds) {
notificationRepository.deleteById(notificationId);
count += 1;
}
return count;
} }
} }
@@ -12,6 +12,7 @@ import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.search.GlobalSearchResult; import ru.soune.nocopy.entity.search.GlobalSearchResult;
import ru.soune.nocopy.entity.search.GlobalSearchTask; import ru.soune.nocopy.entity.search.GlobalSearchTask;
import ru.soune.nocopy.entity.search.SearchStatus; import ru.soune.nocopy.entity.search.SearchStatus;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.GlobalSearchResultRepository; import ru.soune.nocopy.repository.GlobalSearchResultRepository;
import ru.soune.nocopy.repository.GlobalSearchTaskRepository; import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
@@ -59,7 +60,7 @@ public class GlobalSearchAsyncProcessor {
FileEntity file = fileEntityRepository.findById(uuid) FileEntity file = fileEntityRepository.findById(uuid)
.orElseThrow(() -> new RuntimeException("File not found: " + uuid)); .orElseThrow(() -> new RuntimeException("File not found: " + uuid));
tariffInfoService.writeOffTokens(userId, TariffConstants.TOKEN_VALUE_FOR_SEARCH); tariffInfoService.writeOffTokens(userId, TariffConstants.TOKEN_VALUE_FOR_SEARCH, OperationType.GLOBAL_SEARCH);
GlobalSearchResult result = processFile(file, taskId, userId); GlobalSearchResult result = processFile(file, taskId, userId);
globalSearchResultRepository.save(result); globalSearchResultRepository.save(result);
@@ -0,0 +1,55 @@
package ru.soune.nocopy.service.statistic;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.dto.statistic.IncomeStatisticResponse;
import ru.soune.nocopy.repository.ReferralJpaRepository;
import ru.soune.nocopy.util.PercentileCalculator;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class IncomeStatisticService {
private final ReferralJpaRepository referralRepository;
private static final int PERCENTILE_5 = 5;
private static final int PERCENTILE_50 = 50;
private static final int PERCENTILE_95 = 95;
public IncomeStatisticResponse getIncomeStatistics() {
Long totalIncome = referralRepository.getTotalIncome();
Long totalAvailable = referralRepository.getTotalAvailableIncome();
Long totalHold = referralRepository.getTotalHoldBalance();
List<Object[]> incomeData = referralRepository.getIncomePerUser();
List<Long> incomeValues = extractIncomeValues(incomeData);
PercentileCalculator.PercentilesResult incomePercentiles =
PercentileCalculator.calculatePercentiles(incomeValues, PERCENTILE_5, PERCENTILE_50,
PERCENTILE_95);
return IncomeStatisticResponse.builder()
.totalIncome(totalIncome != null ? totalIncome : 0L)
.totalAvailable(totalAvailable != null ? totalAvailable : 0L)
.totalHold(totalHold != null ? totalHold : 0L)
.incomePerUser(new IncomeStatisticResponse.Percentiles(
incomePercentiles.getP5() != null ? incomePercentiles.getP5() : 0L,
incomePercentiles.getP50() != null ? incomePercentiles.getP50() : 0L,
incomePercentiles.getP95() != null ? incomePercentiles.getP95() : 0L
))
.build();
}
private List<Long> extractIncomeValues(List<Object[]> data) {
return data.stream()
.map(row -> row[1] != null ? ((Number) row[1]).longValue() : 0L)
.filter(value -> value > 0)
.collect(Collectors.toList());
}
}

Some files were not shown because too many files have changed in this diff Show More