Compare commits
96
Commits
4ffd830f09
...
NCBACK-35
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd6c4a3c0 | ||
|
|
da84490198 | ||
|
|
b723247b9c | ||
|
|
cc43b92f69 | ||
|
|
d92a9a7d86 | ||
|
|
6dec7838ce | ||
|
|
83ed7c5b01 | ||
|
|
e66ae81d18 | ||
|
|
6c3742913c | ||
|
|
dfc9a22bc5 | ||
|
|
d0be6580e4 | ||
|
|
1dd6a6b7e2 | ||
|
|
c33ffe516a | ||
|
|
ac7cc54b5e | ||
|
|
ea7dd5e5df | ||
|
|
c2a649a252 | ||
|
|
f680f766cd | ||
|
|
82827c9a2a | ||
|
|
e6e8461b9b | ||
|
|
1b2337ea2f | ||
|
|
5fc9563a4e | ||
|
|
fdc17b8325 | ||
|
|
3b68b3337c | ||
|
|
631066b217 | ||
|
|
61dcada973 | ||
|
|
7492b8aa7f | ||
|
|
9ad4830edb | ||
|
|
6366cbc96b | ||
|
|
4e0e953253 | ||
|
|
1476509b48 | ||
|
|
9cde6f3bb6 | ||
|
|
9cd5965b37 | ||
|
|
2ab91dee58 | ||
|
|
b0cc5720a6 | ||
|
|
ed89d315ae | ||
|
|
74f9be8bf7 | ||
|
|
bbf8e3ee3d | ||
|
|
db3dcb8166 | ||
|
|
42a5368003 | ||
|
|
be62dd458a | ||
|
|
2d80b40b27 | ||
|
|
1b921b525a | ||
|
|
b56fb63a37 | ||
|
|
bb41898abf | ||
|
|
9974e22abe | ||
|
|
064a888188 | ||
|
|
484475eadc | ||
|
|
c60aeec6f5 | ||
|
|
0bb952c4e6 | ||
|
|
11ee4e18ed | ||
|
|
684888aa2c | ||
|
|
eb43621b74 | ||
|
|
dd73227db1 | ||
|
|
a7939845c7 | ||
|
|
40cb53a3b0 | ||
|
|
bac45ec738 | ||
|
|
01cfc16583 | ||
|
|
f71252bb5e | ||
|
|
934f3459fb | ||
|
|
75643ae84c | ||
|
|
7df791f9b8 | ||
|
|
b518504e58 | ||
|
|
3558588da9 | ||
|
|
6e8ada2429 | ||
|
|
64dce8601a | ||
|
|
86fa3b555a | ||
|
|
e089d370cf | ||
|
|
28ddf49af1 | ||
|
|
93946cc2e3 | ||
|
|
671625e0cf | ||
|
|
40c9b2a796 | ||
|
|
b859b175f0 | ||
|
|
d827e2ca16 | ||
|
|
1ae2819245 | ||
|
|
dbefc21e6d | ||
|
|
e32e2d990b | ||
|
|
3896477783 | ||
|
|
b5d4509769 | ||
|
|
aa6c0c1b94 | ||
|
|
31140e810b | ||
|
|
aef2c1dfe0 | ||
|
|
c1360fce87 | ||
|
|
9e4035bf83 | ||
|
|
b5de6c04f7 | ||
|
|
a8a7a78ebf | ||
|
|
29ed74745f | ||
|
|
9e81b45922 | ||
|
|
c7c3bbe07a | ||
|
|
0f71f5d58b | ||
|
|
be1ad7f066 | ||
|
|
7cd2aa7d32 | ||
|
|
69e758a710 | ||
|
|
d7874aa37a | ||
|
|
daef30ec11 | ||
|
|
a25b33fe13 | ||
|
|
2985d84b04 |
@@ -290,7 +290,7 @@ server_name dev-admin.not-copy.com;
|
||||
ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3001;
|
||||
proxy_pass http://localhost:8082;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
@@ -323,6 +323,7 @@ server_name dev-workspace.not-copy.com;
|
||||
ssl_certificate_key /etc/letsencrypt/live/dev-workspace.not-copy.com/privkey.pem;
|
||||
|
||||
location /api/ {
|
||||
|
||||
proxy_pass http://localhost:3001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -490,3 +491,13 @@ killall 3proxy # Остановка прокси
|
||||
bash
|
||||
curl -x http://193.46.217.94:3128 https://api.ipify.org # Проверка прокси
|
||||
telnet 193.46.217.94 3128 # Проверка порта
|
||||
|
||||
------
|
||||
|
||||
Настройка Яндекс Клауд
|
||||
|
||||
1. YCAJEpWAtaVkVGX0sH6_EupEg - индетификатор ключа
|
||||
2. YCMmVykfXrZ_nfU13Vo4yoCVGa70DnTlBgF1pUzO - секретный ключ
|
||||
|
||||
private static final String KEY_ID = "YCAJEmHZcWxzf4oGWvETG3mms";
|
||||
private static final String SECRET_KEY = "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG";
|
||||
@@ -62,9 +62,16 @@ dependencies {
|
||||
implementation name: 'testlib-fat-0.3.1-all'
|
||||
|
||||
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
|
||||
implementation 'com.maxmind.geoip2:geoip2:4.2.0'
|
||||
|
||||
implementation project(':referral')
|
||||
|
||||
//cloud
|
||||
implementation("software.amazon.awssdk:aws-sdk-java:2.29.33")
|
||||
implementation("software.amazon.awssdk:apache-client:2.29.33")
|
||||
|
||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
||||
@@ -216,6 +216,33 @@ services:
|
||||
networks:
|
||||
- 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:
|
||||
image: grafana/tempo:2.4.1
|
||||
container_name: tempo
|
||||
@@ -267,6 +294,8 @@ services:
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
mongodb_data:
|
||||
name: mongodb_data
|
||||
artifacts:
|
||||
tempo_data:
|
||||
loki_data:
|
||||
@@ -275,6 +304,8 @@ volumes:
|
||||
loki_rules:
|
||||
uploads_data:
|
||||
prometheus_data:
|
||||
nexus-data:
|
||||
driver: local
|
||||
# postfix-data:
|
||||
networks:
|
||||
app-network:
|
||||
|
||||
@@ -86,10 +86,11 @@ pipeline {
|
||||
passwordVariable: 'SSH_PASS'
|
||||
),
|
||||
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 """
|
||||
# Тестируем подключение сначала
|
||||
echo "Testing SSH connection..."
|
||||
if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then
|
||||
echo "SSH connection OK"
|
||||
@@ -98,7 +99,6 @@ pipeline {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Основная команда
|
||||
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
|
||||
mkdir -p /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 stop 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. Запускаем БД...'
|
||||
docker run -d \\
|
||||
@@ -134,20 +136,31 @@ pipeline {
|
||||
-e BUILD_TIME_BACK='${buildTime}' \\
|
||||
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 \\
|
||||
--name file-storage-${params.PORT} \\
|
||||
--network ${params.NETWORK} \\
|
||||
-v uploads_data_${params.PORT}:/storage:rw \\
|
||||
alpine:latest tail -f /dev/null
|
||||
|
||||
echo '5. Ждем БД...'
|
||||
echo '6. Ждем БД...'
|
||||
sleep 15
|
||||
|
||||
echo '6. Собираем приложение...'
|
||||
echo '7. Собираем приложение...'
|
||||
docker build --no-cache -t app-backend-${params.PORT}:latest .
|
||||
|
||||
echo '7. Запускаем приложение...'
|
||||
echo '8. Запускаем приложение...'
|
||||
docker run -d \\
|
||||
--name app-backend-${params.PORT} \\
|
||||
--network ${params.NETWORK} \\
|
||||
@@ -162,9 +175,11 @@ pipeline {
|
||||
-e POSTGRES_HOST=no_copy_${params.PORT} \\
|
||||
-e FILE_STORAGE_PATH=/data/uploads \\
|
||||
-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
|
||||
|
||||
echo '8. Проверка...'
|
||||
echo '9. Проверка...'
|
||||
sleep 10
|
||||
|
||||
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
|
||||
|
||||
@@ -37,7 +37,17 @@ public class HandlerConfig {
|
||||
UserInfoHandler userInfoHandler,
|
||||
NotificationHandler notificationHandler,
|
||||
UserVerificationHandler userVerificationHandler,
|
||||
LawCaseHandler lawCaseHandler
|
||||
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.put(20001, login);
|
||||
@@ -66,6 +76,15 @@ public class HandlerConfig {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ru.soune.nocopy.controller;
|
||||
|
||||
import com.google.api.client.util.IOUtils;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.vrt.NoCopyFileService;
|
||||
import com.vrt.fileprotection.FileProtector;
|
||||
import com.vrt.fileprotection.NoCopyCheckResult;
|
||||
@@ -8,16 +10,20 @@ import com.vrt.fileprotection.documents.DocumentCheckResult;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
import ru.soune.nocopy.dto.BaseRequest;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
@@ -36,12 +42,17 @@ import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
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.ProtectionsLimitService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.service.register.AuthService;
|
||||
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -65,6 +76,8 @@ public class ApiController {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
private final NoCopyFileService noCopyFileService;
|
||||
|
||||
private final FileUtil fileUtil;
|
||||
|
||||
private final ProtectionsLimitService protectionsLimitService;
|
||||
@@ -75,6 +88,8 @@ public class ApiController {
|
||||
|
||||
private final CheckCounterService checkCounterService;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
private final ZipService zipService;
|
||||
|
||||
private final UserVerificationService userVerificationService;
|
||||
@@ -172,7 +187,7 @@ public class ApiController {
|
||||
@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") Boolean last_file,
|
||||
@RequestParam(value = "last_file", required = false) Boolean last_file,
|
||||
@RequestParam(value = "token") String token) {
|
||||
try {
|
||||
if (chunk == null || chunk.isEmpty()) {
|
||||
@@ -205,7 +220,17 @@ public class ApiController {
|
||||
}
|
||||
}
|
||||
|
||||
private final NoCopyFileService noCopyFileService;
|
||||
@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")
|
||||
public ResponseEntity<BaseResponse> findSimilarFiles(
|
||||
@@ -477,39 +502,35 @@ public class ApiController {
|
||||
MessageCode.FILE_DELETE.getDescription(),
|
||||
errorData));
|
||||
}
|
||||
//TODO
|
||||
|
||||
if (!entityResponse.isExistsOnDisk()) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("onDisk", entityResponse.isExistsOnDisk());
|
||||
// if (!entityResponse.isExistsOnDisk()) {
|
||||
// Map<String, Object> errorData = new HashMap<>();
|
||||
// errorData.put("onDisk", entityResponse.isExistsOnDisk());
|
||||
//
|
||||
// return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
// MessageCode.FILE_NOT_EXIST.getCode(),
|
||||
// MessageCode.FILE_NOT_EXIST.getDescription(),
|
||||
// errorData));
|
||||
// }
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
MessageCode.FILE_NOT_EXIST.getCode(),
|
||||
MessageCode.FILE_NOT_EXIST.getDescription(),
|
||||
errorData));
|
||||
}
|
||||
// if (!file.exists()) {
|
||||
// Map<String, Object> errorData = new HashMap<>();
|
||||
// errorData.put("file", file.exists());
|
||||
//
|
||||
// return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
|
||||
// errorData));
|
||||
// }
|
||||
|
||||
|
||||
Path filePath = Paths.get(entityResponse.getProtectedFilePath());
|
||||
FileSystemResource resource = new FileSystemResource(filePath);
|
||||
long fileSize = Files.size(filePath);
|
||||
|
||||
if (!resource.exists()) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("resource", resource.exists());
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
|
||||
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
|
||||
errorData));
|
||||
}
|
||||
String contentType = determineContentType(filePath);
|
||||
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentLength(fileSize)
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
|
||||
.body(resource);
|
||||
.body(bytes);
|
||||
} catch (FileEntityNotFoundException e) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("fileId", fileId);
|
||||
@@ -526,16 +547,16 @@ public class ApiController {
|
||||
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
|
||||
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
|
||||
errorData));
|
||||
} catch (IOException e) {
|
||||
Map<String, Object> errorData = new HashMap<>();
|
||||
errorData.put("token", tokenHeader);
|
||||
errorData.put("fileId", fileId);
|
||||
errorData.put("version", version);
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
|
||||
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
|
||||
errorData));
|
||||
// } catch (IOException e) {
|
||||
// Map<String, Object> errorData = new HashMap<>();
|
||||
// errorData.put("token", tokenHeader);
|
||||
// errorData.put("fileId", fileId);
|
||||
// errorData.put("version", version);
|
||||
//
|
||||
// return ResponseEntity.ok().body(new BaseResponse(20004,
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
|
||||
// MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
|
||||
// errorData));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,33 +573,6 @@ public class ApiController {
|
||||
return ResponseEntity.ok().body(fileTypeStats);
|
||||
}
|
||||
|
||||
private boolean hasDuplicate(List<SimilarFileDTO> similarFiles) {
|
||||
return similarFiles.stream().anyMatch(f -> f.getHammingDistance() <= 5);
|
||||
}
|
||||
|
||||
private ResponseEntity<BaseResponse> handleDuplicate(FileEntity fileEntity, List<SimilarFileDTO> similarFiles)
|
||||
throws IOException {
|
||||
fileEntityService.deleteFromDisk(fileEntity);
|
||||
|
||||
fileEntityService.softDeleteFileWithHash(fileEntity);
|
||||
|
||||
Optional<FileEntity> originalFile = fileEntityRepository.findById(similarFiles.get(0).getFileId());
|
||||
|
||||
if (originalFile.isPresent()) {
|
||||
Map<String, String> duplicateInfo = Map.of(
|
||||
"duplicate_file_id", originalFile.get().getId(),
|
||||
"owner_user_id", String.valueOf(originalFile.get().getUserId()));
|
||||
|
||||
return ResponseEntity.ok().body(new BaseResponse(
|
||||
20004,
|
||||
MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
|
||||
"Failed to upload chunk, duplicate",
|
||||
duplicateInfo));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
|
||||
String fileId) {
|
||||
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
|
||||
@@ -635,13 +629,4 @@ public class ApiController {
|
||||
|
||||
return errorDetail;
|
||||
}
|
||||
|
||||
|
||||
private String determineContentType(Path filePath) throws IOException {
|
||||
String contentType = Files.probeContentType(filePath);
|
||||
if (contentType == null) {
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package ru.soune.nocopy.controller;
|
||||
|
||||
import com.google.api.client.util.IOUtils;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.vrt.NoCopyFileService;
|
||||
import com.vrt.fileprotection.FileProtector;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
import ru.soune.nocopy.dto.file.CheckStatus;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.file.ProtectionStatus;
|
||||
@@ -18,13 +22,14 @@ import ru.soune.nocopy.service.file.CheckCounterService;
|
||||
import ru.soune.nocopy.service.file.FileStorageService;
|
||||
import ru.soune.nocopy.service.file.ImageResizeService;
|
||||
import ru.soune.nocopy.service.file.ZipService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -51,19 +56,13 @@ public class FileController {
|
||||
|
||||
private ZipService zipService;
|
||||
|
||||
private CloudStorageService cloudStorageService;
|
||||
|
||||
@GetMapping("/public/{fileId}")
|
||||
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) {
|
||||
public ResponseEntity<StreamingResponseBody> getPublicFile(@PathVariable String fileId) {
|
||||
try {
|
||||
FileEntity fileEntity = fileRepository.findById(fileId)
|
||||
.orElseThrow(() -> new FileNotFoundException("Not found: " + fileId));
|
||||
|
||||
Path filePath = Paths.get(fileEntity.getFilePath());
|
||||
if (!Files.exists(filePath)) {
|
||||
log.error("Not on disk: {}", fileEntity.getFilePath());
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
|
||||
.orElseThrow(() -> new FileNotFoundException("Not found in DB: " + fileId));
|
||||
|
||||
MediaType mediaType = getMediaType(fileEntity);
|
||||
|
||||
@@ -71,18 +70,28 @@ public class FileController {
|
||||
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
|
||||
.build();
|
||||
|
||||
StreamingResponseBody responseBody = outputStream -> {
|
||||
try (InputStream inputStream = cloudStorageService.readFileFromStorage(fileEntity.getFilePath())) {
|
||||
ByteStreams.copy(inputStream, outputStream);
|
||||
} catch (NoSuchKeyException e) {
|
||||
log.error("File disappeared from S3 during streaming: {}", fileId, e);
|
||||
throw new IOException("File not found in storage", e);
|
||||
}
|
||||
};
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(mediaType)
|
||||
.contentLength(Files.size(filePath))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
|
||||
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
|
||||
.body(resource);
|
||||
|
||||
.body(responseBody);
|
||||
} catch (FileNotFoundException e) {
|
||||
log.warn("Not on disk: {}", fileId);
|
||||
log.warn("File not found in DB: {}", fileId);
|
||||
return ResponseEntity.notFound().build();
|
||||
} catch (IOException e) {
|
||||
log.error("Read file exception: {}", fileId, e);
|
||||
} catch (NoSuchKeyException e) {
|
||||
log.warn("File not found in S3: {}", fileId);
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||
} catch (Exception e) {
|
||||
log.error("Error reading file: {}", fileId, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
@@ -113,9 +122,14 @@ public class FileController {
|
||||
FileEntity fileEntity = fileRepository.findById(fileId)
|
||||
.orElseThrow(() -> new RuntimeException("File not found"));
|
||||
|
||||
String path = imageResizeService.getPath(fileEntity, type);
|
||||
String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath();
|
||||
|
||||
Resource resource = fileStorageService.loadFileAsResource(path);
|
||||
// InputStream file = cloudStorageService.readFileFromStorage(filePath);
|
||||
|
||||
// if (file == null || !file.exists()) {
|
||||
// log.error("File not found in storage: {}", fileEntity.getFilePath());
|
||||
// return ResponseEntity.notFound().build();
|
||||
// }
|
||||
|
||||
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
|
||||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
|
||||
@@ -128,6 +142,8 @@ public class FileController {
|
||||
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
|
||||
.replace("+", "%20");
|
||||
|
||||
InputStreamResource resource = new InputStreamResource(cloudStorageService.readFileFromStorage(filePath));
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
|
||||
@@ -70,7 +70,7 @@ public class PaymentController {
|
||||
}
|
||||
|
||||
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) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.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(),
|
||||
u.getPhone(), u.getGenderType(),
|
||||
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
|
||||
null, null))
|
||||
null, null, u.getVerificationStatus()))
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(allUsers);
|
||||
@@ -79,6 +79,7 @@ public class UserController {
|
||||
UserDTO userDTO = userMapper.toDTO(user);
|
||||
userDTO.setEmail(email);
|
||||
userDTO.setFullName(user.getFullName());
|
||||
userDTO.setVerifiedStatus(user.getVerificationStatus());
|
||||
|
||||
if (user.getCompany() != null) {
|
||||
userDTO.setCompany(user.getCompany().getCompanyName());
|
||||
|
||||
@@ -48,6 +48,7 @@ public enum MessageCode {
|
||||
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
|
||||
VALIDATION_ERROR(2, "Validation error"),
|
||||
RESOURCE_NOT_FOUND(4, "Resource not found"),
|
||||
OPERATION_NOT_FOUND(4, "Operation not found"),
|
||||
INTERNAL_ERROR(4, "Internal server error"),
|
||||
COMPANY_NOT_FOUND(4, "Company not found"),
|
||||
PAYMENT_NOT_FOUND(4, "Payment not found"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -14,17 +15,19 @@ public class LawCaseRequest {
|
||||
//required
|
||||
private String description;
|
||||
private BigDecimal amount;
|
||||
private String priority = LawCasePriority.MIDDLE.toString();
|
||||
private String priority = LawCasePriority.HIGH.toString();
|
||||
private String type;
|
||||
private String lawyer;
|
||||
private String token;
|
||||
private Integer pageSize = 10;
|
||||
private Integer pageSize = 5;
|
||||
private Integer pageNumber = 0;
|
||||
private String sortBy = "createdAt";
|
||||
private String sortDir = "asc";
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class LawCaseResponse {
|
||||
private int pageSize;
|
||||
private long totalElements;
|
||||
private int totalPages;
|
||||
private long violationId;
|
||||
private List<LawCaseResponse> content;
|
||||
|
||||
public static LawCaseResponse fromEntity(LawCase lawCase) {
|
||||
@@ -39,6 +40,7 @@ public class LawCaseResponse {
|
||||
.lawyer(lawCase.getLawyer())
|
||||
.createdAt(lawCase.getCreatedAt())
|
||||
.updatedAt(lawCase.getUpdatedAt())
|
||||
.violationId(lawCase.getViolationId())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TariffInfoDTO;
|
||||
import ru.soune.nocopy.entity.user.GenderType;
|
||||
import ru.soune.nocopy.entity.user.ModerationStatus;
|
||||
import ru.soune.nocopy.entity.user.SubscriptionType;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -30,4 +31,5 @@ public class UserDTO {
|
||||
private List<TariffDTO> tariffs;
|
||||
private TariffInfoDTO tariffInfo;
|
||||
private Long permission;
|
||||
private ModerationStatus verifiedStatus;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import ru.soune.nocopy.entity.violation.Violation;
|
||||
import ru.soune.nocopy.service.geo.GeoCountryService;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -12,6 +13,8 @@ import java.util.List;
|
||||
@Builder
|
||||
public class ViolationResponse {
|
||||
|
||||
private final GeoCountryService geoCountryService;
|
||||
|
||||
@JsonProperty("violations")
|
||||
private List<ViolationDto> violations;
|
||||
|
||||
@@ -58,6 +61,12 @@ public class ViolationResponse {
|
||||
@JsonProperty("created_date")
|
||||
private LocalDateTime createdDate;
|
||||
|
||||
@JsonProperty("country")
|
||||
private String country;
|
||||
|
||||
@JsonProperty("country_code")
|
||||
private String countryCode;
|
||||
|
||||
@JsonProperty("file_id")
|
||||
private String fileId;
|
||||
|
||||
|
||||
@@ -54,6 +54,9 @@ public class LawCase {
|
||||
@Column(name = "lawyer")
|
||||
private String lawyer;
|
||||
|
||||
@Column(name = "violation_id")
|
||||
private Long violationId;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
if (this.createdAt == null) {
|
||||
|
||||
@@ -6,8 +6,10 @@ import lombok.*;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Data
|
||||
|
||||
@@ -13,6 +13,8 @@ public enum NotificationType {
|
||||
PAYMENT_RESULT("notification-payment-result"),
|
||||
REFERRAL_DEPOSIT("notification-referral-deposit"),
|
||||
REFERRAL_REGISTERED("notification-referral-registered"),
|
||||
USER_VERIFIED("notification-user-verified"),
|
||||
USER_NOT_VERIFIED("notification-user-not-verified"),
|
||||
REFERRAL_ACTIVATED("notification-referral-activated"),
|
||||
COMPLAINT_STATUS_CHANGED("notification-complaint-status"),
|
||||
CASE_STATUS_CHANGED("notification-case-status"),
|
||||
|
||||
@@ -20,6 +20,7 @@ public class TariffInfo {
|
||||
private String id;
|
||||
|
||||
@Column(name = "status", nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private TariffStatus status;
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -7,9 +7,9 @@ public enum ModerationStatus {
|
||||
NOT_VERIFIED("not_verified") , VERIFICATION_IN_PROGRESS("verification_in_progress"),
|
||||
VERIFIED("verified"), VERIFICATION_FAILED("verification_failed");
|
||||
|
||||
private final String name;
|
||||
private final String value;
|
||||
|
||||
private ModerationStatus(String name) {
|
||||
this.name = name;
|
||||
private ModerationStatus(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ public class User {
|
||||
private UserNotActive userNotActive;
|
||||
|
||||
@Column(name = "verification_status")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private ModerationStatus verificationStatus = ModerationStatus.NOT_VERIFIED;
|
||||
|
||||
public TariffInfo getActiveTariffInfo() {
|
||||
|
||||
@@ -77,10 +77,9 @@ public class ComplaintEntityHandler implements RequestHandler {
|
||||
private BaseResponse handleGetAll(Integer msgId, ComplaintRequest req) {
|
||||
Pageable pageable = PageRequest.of(
|
||||
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"),
|
||||
req.getSortBy() != null ? req.getSortBy() : "createdAt")
|
||||
);
|
||||
req.getSortBy() != null ? req.getSortBy() : "updatedAt"));
|
||||
|
||||
var page = complaintService.getAllComplaints(pageable);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
|
||||
@@ -4,6 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.nocopy.dto.*;
|
||||
import ru.soune.nocopy.dto.file.*;
|
||||
@@ -15,6 +18,7 @@ import ru.soune.nocopy.entity.file.moderation.ModerationLog;
|
||||
import ru.soune.nocopy.exception.*;
|
||||
import ru.soune.nocopy.repository.FileAppealRepository;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.repository.ModerationLogRepository;
|
||||
import ru.soune.nocopy.service.file.moderation.ModerationService;
|
||||
import ru.soune.nocopy.service.register.AuthService;
|
||||
@@ -40,6 +44,8 @@ public class FileEntityHandler implements RequestHandler {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
private final ModerationService moderationService;
|
||||
|
||||
private final FileAppealRepository fileAppealRepository;
|
||||
@@ -225,6 +231,52 @@ public class FileEntityHandler implements RequestHandler {
|
||||
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) {
|
||||
try {
|
||||
Long userId = authService.useUserAuthToken(fileRequest.getToken());
|
||||
@@ -289,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) {
|
||||
if (dateFilter == null || dateFilter.isEmpty()) {
|
||||
return true;
|
||||
@@ -420,6 +539,12 @@ public class FileEntityHandler implements RequestHandler {
|
||||
Comparator.nullsLast(Comparator.naturalOrder())
|
||||
);
|
||||
break;
|
||||
case "monitoring":
|
||||
comparator = Comparator.comparing(
|
||||
FileEntityResponse::getMonitoring,
|
||||
Comparator.nullsLast(Comparator.naturalOrder())
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
comparator = Comparator.comparing(
|
||||
@@ -578,7 +703,7 @@ public class FileEntityHandler implements RequestHandler {
|
||||
int fullDelete = fileRequest.getFullDelete() == null ? 0 : fileRequest.getFullDelete();
|
||||
|
||||
if (fileEntity.getStatus().equals(FileStatus.DELETED) || fullDelete == 1) {
|
||||
fileEntityService.deleteFromDisk(fileEntity);
|
||||
cloudStorageService.deleteFromStorageDisk(fileEntity);
|
||||
response = DeleteFileResponse.builder()
|
||||
.fileId(fileRequest.getFileId())
|
||||
.message("File deleted from disk")
|
||||
|
||||
@@ -11,6 +11,7 @@ import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.file.ImageSearchRequest;
|
||||
import ru.soune.nocopy.dto.file.YandexSearchResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.tokenoperation.OperationType;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
|
||||
|
||||
@@ -15,6 +15,7 @@ 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;
|
||||
@@ -79,6 +80,7 @@ public class LawCaseHandler implements RequestHandler {
|
||||
lawCaseRequest.getFilterType(),
|
||||
lawCaseRequest.getFilterLawyer(),
|
||||
lawCaseRequest.getFilterPriority(),
|
||||
lawCaseRequest.getViolationId(),
|
||||
lawCaseRequest.getSortBy(),
|
||||
lawCaseRequest.getSortDir());
|
||||
|
||||
@@ -93,8 +95,7 @@ public class LawCaseHandler implements RequestHandler {
|
||||
lawCases.getNumber(),
|
||||
lawCases.getSize(),
|
||||
lawCases.isFirst(),
|
||||
lawCases.isLast()
|
||||
);
|
||||
lawCases.isLast());
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), paginatedResponse);
|
||||
@@ -114,7 +115,7 @@ public class LawCaseHandler implements RequestHandler {
|
||||
|
||||
private BaseResponse createLawCase(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
|
||||
try {
|
||||
tariffInfoService.writeOffTokens(userId, TariffConstants.LEGAL_COST);
|
||||
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(),
|
||||
@@ -132,8 +133,8 @@ public class LawCaseHandler implements RequestHandler {
|
||||
lawCaseRequest.getAmount(),
|
||||
lawCaseRequest.getDescription(),
|
||||
lawCaseRequest.getName(),
|
||||
LawCasePriority.valueOf(lawCaseRequest.getPriority()));
|
||||
lawCase.setUser(user);
|
||||
LawCasePriority.valueOf(lawCaseRequest.getPriority()),
|
||||
lawCaseRequest.getViolationId(), user);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
|
||||
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,10 @@ 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;
|
||||
@@ -25,6 +28,8 @@ public class UserVerificationHandler implements RequestHandler {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final NotificationService notificationService;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||
UserVerificationRequest userVerificationRequest = objectMapper.convertValue(request.getMessageBody(),
|
||||
@@ -76,6 +81,11 @@ public class UserVerificationHandler implements RequestHandler {
|
||||
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()
|
||||
|
||||
@@ -13,6 +13,7 @@ import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.violation.Violation;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
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.violation.ViolationService;
|
||||
import ru.soune.nocopy.service.violation.ViolationStatus;
|
||||
@@ -21,6 +22,7 @@ import java.io.FileNotFoundException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,6 +40,8 @@ public class ViolationHandler implements RequestHandler {
|
||||
|
||||
private final FileEntityService fileEntityService;
|
||||
|
||||
private final GeoCountryService geoCountryService;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
@@ -112,10 +116,16 @@ public class ViolationHandler implements RequestHandler {
|
||||
);
|
||||
}
|
||||
|
||||
List<ViolationResponse.ViolationDto> violationDtos = violationPage.getContent()
|
||||
.stream()
|
||||
.map(ViolationResponse.ViolationDto::fromEntity)
|
||||
.toList();
|
||||
List<Violation> content = violationPage.getContent();
|
||||
List<ViolationResponse.ViolationDto> violationDtos = new ArrayList<>();
|
||||
|
||||
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()
|
||||
.violations(violationDtos)
|
||||
|
||||
@@ -25,4 +25,16 @@ public interface ComplaintEntityRepository extends JpaRepository<ComplaintEntity
|
||||
@Transactional
|
||||
@Query("UPDATE ComplaintEntity c SET c.status = :status WHERE c.id = :id")
|
||||
int updateStatus(Long id, ComplaintStatus status);
|
||||
|
||||
@Query("SELECT COUNT(c.id) FROM ComplaintEntity c")
|
||||
Long getTotalComplaintsCount();
|
||||
|
||||
@Query("""
|
||||
SELECT COUNT(c.id)
|
||||
FROM ComplaintEntity c
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM LawCase lc WHERE lc.violationId = c.violation.id
|
||||
)
|
||||
""")
|
||||
Long getComplaintsWithoutLawCaseCount();
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.soune.nocopy.dto.statistic.UserFilesStatisticResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.file.FileStatus;
|
||||
import ru.soune.nocopy.entity.file.ProtectionStatus;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -58,12 +60,6 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
|
||||
|
||||
List<FileEntity> findFileByUserIdAndStatus(Long userId, FileStatus status);
|
||||
|
||||
@Query("SELECT f FROM FileEntity f WHERE f.userId = :userId AND f.mimeType = :mimeType")
|
||||
List<FileEntity> findByUserIdAndMimeType(Long userId, String mimeType);
|
||||
|
||||
@Query("SELECT f FROM FileEntity f WHERE f.mimeType = :mimeType")
|
||||
List<FileEntity> findByMimeType(String mimeType);
|
||||
|
||||
@Query("SELECT f FROM FileEntity f WHERE f.mimeType = :mimeType AND f.userId IN :userIds")
|
||||
List<FileEntity> findByMimeTypeAndUserIds(String mimeType, List<Long> userIds);
|
||||
|
||||
@@ -78,4 +74,61 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
|
||||
|
||||
@Query("SELECT f FROM FileEntity f WHERE f.userId = :userId AND f.status IN :statuses")
|
||||
List<FileEntity> findByStatuses(Long userId, @Param("statuses") List<String> statuses);
|
||||
|
||||
@Query("SELECT DISTINCT f FROM FileEntity f " +
|
||||
"LEFT JOIN FileMonitoringEntity m ON f.id = m.file.id " +
|
||||
"WHERE f.userId IN :userIds " +
|
||||
"AND f.status IN :statuses")
|
||||
Page<FileEntity> findFiles(
|
||||
@Param("userIds") List<Long> userIds,
|
||||
@Param("statuses") List<FileStatus> statuses,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT SUM(f.fileSize) FROM FileEntity f " +
|
||||
"WHERE f.userId IN :userIds " +
|
||||
"AND f.status IN :statuses")
|
||||
Long sumFileSize(
|
||||
@Param("userIds") List<Long> userIds,
|
||||
@Param("statuses") List<FileStatus> statuses);
|
||||
|
||||
@Query("""
|
||||
SELECT new ru.soune.nocopy.dto.statistic.UserFilesStatisticResponse(
|
||||
u.fullName,
|
||||
u.Id,
|
||||
CAST(COUNT(f.id) AS int),
|
||||
COALESCE(SUM(f.fileSize), 0L)
|
||||
)
|
||||
FROM FileEntity f
|
||||
JOIN User u ON f.userId = u.Id
|
||||
WHERE
|
||||
(:mimeType IS NULL OR :mimeType = '' OR f.mimeType LIKE CONCAT(:mimeType, '%'))
|
||||
GROUP BY u.Id, u.fullName
|
||||
ORDER BY COUNT(f.id) DESC
|
||||
LIMIT :topUsers
|
||||
""")
|
||||
List<UserFilesStatisticResponse> findTopUsers(
|
||||
@Param("topUsers") int topUsers,
|
||||
@Param("mimeType") String mimeType
|
||||
);
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
COUNT(f.id),
|
||||
COALESCE(SUM(f.fileSize), 0)
|
||||
FROM FileEntity f
|
||||
WHERE f.protectionStatus = 'PROTECTED'
|
||||
""")
|
||||
List<Object[]> getProtectedFilesTotalStats();
|
||||
|
||||
@Query(value = """
|
||||
SELECT
|
||||
user_id,
|
||||
COUNT(*) as file_count,
|
||||
SUM(file_size) as total_size
|
||||
FROM file_entities
|
||||
WHERE protection_status = 'PROTECTED'
|
||||
GROUP BY user_id
|
||||
ORDER BY file_count
|
||||
""", nativeQuery = true)
|
||||
List<Object[]> getProtectedFilesPerUserStats();
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public interface ImageSimilarityRepository
|
||||
JOIN file_entities f
|
||||
ON f.id = h.file_id
|
||||
WHERE ref.file_id = :fileId AND f.user_id = :userId
|
||||
AND f.status = 'ACTIVE'
|
||||
AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
|
||||
""",
|
||||
nativeQuery = true)
|
||||
List<SimilarImageProjection> findCandidatesFromUserFiles(
|
||||
@@ -82,7 +82,7 @@ public interface ImageSimilarityRepository
|
||||
JOIN file_entities f
|
||||
ON f.id = h.file_id
|
||||
WHERE ref.file_id = :fileId AND f.user_id IN :userIds
|
||||
AND f.status = 'ACTIVE'
|
||||
AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
|
||||
""",
|
||||
nativeQuery = true)
|
||||
List<SimilarImageProjection> findCandidatesFromUserFiles(
|
||||
@@ -108,7 +108,7 @@ public interface ImageSimilarityRepository
|
||||
JOIN file_entities f ON f.id = h.file_id
|
||||
WHERE h.hash64_hi = :hash64Hi
|
||||
AND h.hash64_lo = :hash64Lo
|
||||
AND f.status = 'ACTIVE'
|
||||
AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
|
||||
""",
|
||||
nativeQuery = true)
|
||||
List<SimilarImageProjection> findExactDuplicates(
|
||||
|
||||
@@ -31,10 +31,15 @@ public interface LawCaseRepository extends JpaRepository<LawCase, Long> {
|
||||
@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();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ru.soune.nocopy.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.soune.nocopy.entity.payment.Payment;
|
||||
|
||||
@@ -11,4 +12,23 @@ import java.util.Optional;
|
||||
public interface PaymentRepository extends JpaRepository<Payment, String> {
|
||||
Optional<Payment> findByPaymentUuid(String paymentUuid);
|
||||
List<Payment> findByUserId(Long userId);
|
||||
|
||||
@Query("""
|
||||
SELECT COALESCE(SUM(p.tariff.tokens), 0)
|
||||
FROM Payment p
|
||||
WHERE p.operationType = 'TOKEN'
|
||||
AND p.status = 'SUCCEEDED'
|
||||
""")
|
||||
Long getTotalTokensBought();
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
p.user.Id,
|
||||
COALESCE(SUM(p.tariff.tokens), 0)
|
||||
FROM Payment p
|
||||
WHERE p.operationType = 'TOKEN'
|
||||
AND p.status = 'SUCCEEDED'
|
||||
GROUP BY p.user.Id
|
||||
""")
|
||||
List<Object[]> getTokensBoughtPerUser();
|
||||
}
|
||||
|
||||
@@ -43,4 +43,22 @@ public interface ReferralJpaRepository extends JpaRepository<Referral, Long> {
|
||||
|
||||
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId")
|
||||
int countTotalInvitees(@Param("userId") Long userId);
|
||||
|
||||
@Query("SELECT COALESCE(SUM(r.totalIncome), 0) FROM Referral r")
|
||||
Long getTotalIncome();
|
||||
|
||||
@Query("SELECT COALESCE(SUM(r.availableIncome), 0) FROM Referral r")
|
||||
Long getTotalAvailableIncome();
|
||||
|
||||
@Query("SELECT COALESCE(SUM(r.holdBalance), 0) FROM Referral r")
|
||||
Long getTotalHoldBalance();
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
r.userId,
|
||||
r.totalIncome
|
||||
FROM Referral r
|
||||
WHERE r.totalIncome > 0
|
||||
""")
|
||||
List<Object[]> getIncomePerUser();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.soune.nocopy.entity.tarif.TariffInfo;
|
||||
import ru.soune.nocopy.entity.tarif.TariffTimeTerm;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -13,4 +14,57 @@ import java.util.List;
|
||||
public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> {
|
||||
@Query("SELECT t FROM TariffInfo t WHERE t.autoRenewal = true AND t.endTariff <= :date")
|
||||
List<TariffInfo> findForAutoRenewal(@Param("date") LocalDateTime date);
|
||||
|
||||
@Query("""
|
||||
SELECT COUNT(u.Id)
|
||||
FROM User u
|
||||
WHERE (:activeOnly = false OR u.isActive = true)
|
||||
AND (:companyOnly = false OR u.company IS NOT NULL)
|
||||
""")
|
||||
Long getTotalUsersCount(
|
||||
@Param("activeOnly") boolean activeOnly,
|
||||
@Param("companyOnly") boolean companyOnly
|
||||
);
|
||||
|
||||
@Query("""
|
||||
SELECT COUNT(u.Id)
|
||||
FROM User u
|
||||
WHERE (:activeOnly = false OR u.isActive = true)
|
||||
""")
|
||||
Long getTotalUsersCount(@Param("activeOnly") boolean activeOnly);
|
||||
|
||||
@Query(value = """
|
||||
SELECT
|
||||
t.tariff_name,
|
||||
COUNT(u.id) as user_count,
|
||||
(COUNT(u.id) * 100.0) / NULLIF(
|
||||
(SELECT COUNT(*) FROM users u2
|
||||
WHERE (:activeOnly = false OR u2.is_active = true)
|
||||
AND u2.personal_tariff_info_id IS NOT NULL
|
||||
), 0
|
||||
) as usage_percent
|
||||
FROM users u
|
||||
INNER JOIN tariff_info pti ON u.personal_tariff_info_id = pti.id
|
||||
INNER JOIN tariff t ON pti.tariff_id = t.id
|
||||
WHERE (:activeOnly = false OR u.is_active = true)
|
||||
GROUP BY t.id, t.tariff_name
|
||||
ORDER BY user_count DESC
|
||||
""", nativeQuery = true)
|
||||
List<Object[]> getTariffStatisticsNative(@Param("activeOnly") boolean activeOnly);
|
||||
|
||||
@Query("""
|
||||
SELECT COUNT(ti.id)
|
||||
FROM TariffInfo ti
|
||||
WHERE ti.endTariff >= CURRENT_TIMESTAMP
|
||||
AND (:term IS NULL OR ti.tariff.tariffTerm = :term)
|
||||
""")
|
||||
Long getCurrentActiveSubscriptions(@Param("term") TariffTimeTerm term);
|
||||
|
||||
@Query("""
|
||||
SELECT ti
|
||||
FROM TariffInfo ti
|
||||
WHERE (:term IS NULL OR ti.tariff.tariffTerm = :term)
|
||||
ORDER BY ti.startTariff
|
||||
""")
|
||||
List<TariffInfo> getAllTariffInfosForAnalysis(@Param("term") TariffTimeTerm term);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package ru.soune.nocopy.repository;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.soune.nocopy.entity.tokenoperation.OperationType;
|
||||
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface TokenOperationRepository extends JpaRepository<TokenOperation, Long> {
|
||||
Page<TokenOperation> findAll(Pageable pageable);
|
||||
|
||||
List<TokenOperation> findByUserId(Long userId);
|
||||
|
||||
Page<TokenOperation> findByUserId(Long userId, Pageable pageable);
|
||||
|
||||
Page<TokenOperation> findByOperationType(OperationType operationType, Pageable pageable);
|
||||
|
||||
@Query("SELECT t FROM TokenOperation t WHERE " +
|
||||
"(:userId IS NULL OR t.userId = :userId) AND " +
|
||||
"(:operationType IS NULL OR t.operationType = :operationType) AND " +
|
||||
"(:minSpent IS NULL OR t.spent >= :minSpent) AND " +
|
||||
"(:maxSpent IS NULL OR t.spent <= :maxSpent)")
|
||||
Page<TokenOperation> findByFilters(
|
||||
@Param("userId") Long userId,
|
||||
@Param("operationType") OperationType operationType,
|
||||
@Param("minSpent") Long minSpent,
|
||||
@Param("maxSpent") Long maxSpent,
|
||||
Pageable pageable
|
||||
);
|
||||
|
||||
@Query("SELECT SUM(t.spent) FROM TokenOperation t WHERE t.userId = :userId AND t.operationType = :operationType")
|
||||
Long sumSpentByUserAndType(@Param("userId") Long userId, @Param("operationType") OperationType operationType);
|
||||
|
||||
Long countByUserId(Long userId);
|
||||
|
||||
@Query("""
|
||||
SELECT COALESCE(SUM(t.spent), 0)
|
||||
FROM TokenOperation t
|
||||
""")
|
||||
Long getTotalTokensSpent();
|
||||
|
||||
@Query("""
|
||||
SELECT
|
||||
t.userId,
|
||||
COALESCE(SUM(t.spent), 0)
|
||||
FROM TokenOperation t
|
||||
GROUP BY t.userId
|
||||
""")
|
||||
List<Object[]> getTokensSpentPerUser();
|
||||
}
|
||||
@@ -2,10 +2,13 @@ package ru.soune.nocopy.repository;
|
||||
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import ru.soune.nocopy.entity.tarif.TariffInfo;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User findByEmail(String email);
|
||||
@@ -14,4 +17,32 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||
List<User> findByCompanyId(String companyId);
|
||||
long countByCompanyId(String companyId);
|
||||
User findByPersonalTariffInfo(TariffInfo tariffInfo);
|
||||
|
||||
@Query("SELECT COUNT(u.Id) FROM User u")
|
||||
Long getTotalUsersCount();
|
||||
|
||||
@Query(value = """
|
||||
SELECT COUNT(*)
|
||||
FROM users
|
||||
WHERE created_at >= NOW() - INTERVAL '1 day' * :days
|
||||
""", nativeQuery = true)
|
||||
Long getNewUsersCount(int days);
|
||||
|
||||
@Query(value = """
|
||||
SELECT COUNT(*)
|
||||
FROM users
|
||||
WHERE created_at >= DATE_TRUNC('month', NOW())
|
||||
""", nativeQuery = true)
|
||||
Long getNewUsersCurrentMonth();
|
||||
|
||||
@Query(value = """
|
||||
SELECT COUNT(*)
|
||||
FROM users
|
||||
WHERE created_at >= DATE_TRUNC('month', NOW() - INTERVAL '1 month')
|
||||
AND created_at < DATE_TRUNC('month', NOW())
|
||||
""", nativeQuery = true)
|
||||
Long getNewUsersPreviousMonth();
|
||||
|
||||
@Query("SELECT u FROM User u WHERE u.personalTariffInfo.id = :tariffInfoId")
|
||||
Optional<User> findByPersonalTariffInfoId(@Param("tariffInfoId") String tariffInfoId);
|
||||
}
|
||||
|
||||
@@ -74,4 +74,10 @@ public interface ViolationRepository extends JpaRepository<Violation, Long> {
|
||||
List<Violation> findByFileEntityInAndStatusAndCreatedDateBetween(List<FileEntity> files, String status,
|
||||
LocalDateTime startDate, LocalDateTime endDate,
|
||||
Sort sort);
|
||||
|
||||
@Query("SELECT COUNT(v.id) FROM Violation v")
|
||||
Long getTotalViolationsCount();
|
||||
|
||||
@Query("SELECT COUNT(DISTINCT c.violation.id) FROM ComplaintEntity c WHERE c.violation IS NOT NULL")
|
||||
Long getViolationsWithComplaintCount();
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import ru.soune.nocopy.entity.file.ImageHashEntity;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
import ru.soune.nocopy.exception.DuplicateImageException;
|
||||
import ru.soune.nocopy.repository.*;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
@@ -41,6 +42,8 @@ public class FileSimilarityService {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@Value("${server.baseurl}")
|
||||
private String baseUrl;
|
||||
|
||||
@@ -114,7 +117,7 @@ public class FileSimilarityService {
|
||||
}
|
||||
|
||||
public FileEntity findDuplicateByHash(String path, String mimeType, Long userId) throws Exception {
|
||||
String hash = calculateFileHash(path);
|
||||
String hash = calculateFileHash(path, false);
|
||||
|
||||
User user = userRepository.findById(userId).orElseThrow();
|
||||
Company company = user.getCompany();
|
||||
@@ -129,15 +132,15 @@ public class FileSimilarityService {
|
||||
List<FileEntity> fileEntityList = fileEntityRepository.findByMimeTypeAndUserIds(mimeType, userIds);
|
||||
|
||||
for (FileEntity file : fileEntityList) {
|
||||
if (file.getProtectedFilePath() == null) continue;
|
||||
File existingFile = new File(file.getProtectedFilePath());
|
||||
File newFile = new File(path);
|
||||
// if (file.getProtectedFilePath() == null) continue;
|
||||
// File existingFile = cloudStorageService.readFileFromStorageByPath(file.getFilePath());
|
||||
// File newFile = new File(path);
|
||||
|
||||
if (existingFile.length() == newFile.length()) {
|
||||
if (calculateFileHash(file.getProtectedFilePath()).equals(hash)) {
|
||||
// if (existingFile.length() == newFile.length()) {
|
||||
if (calculateFileHash(file.getFilePath(), true).equals(hash)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -196,9 +199,13 @@ public class FileSimilarityService {
|
||||
}
|
||||
|
||||
|
||||
private String calculateFileHash(String path) throws Exception {
|
||||
private String calculateFileHash(String path, boolean cloud) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
if (cloud) {
|
||||
byte[] data = cloudStorageService.readFileFromStorageBytes(path);
|
||||
digest.update(data);
|
||||
} else {
|
||||
try (InputStream is = new FileInputStream(path)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
@@ -206,6 +213,7 @@ public class FileSimilarityService {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] hashBytes = digest.digest();
|
||||
return Base64.getEncoder().encodeToString(hashBytes);
|
||||
|
||||
@@ -8,9 +8,11 @@ import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.file.ImageHashEntity;
|
||||
import ru.soune.nocopy.repository.ImageHashRepository;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
@@ -22,18 +24,50 @@ public class ImageHashService {
|
||||
|
||||
private final ImageHashRepository repository;
|
||||
|
||||
public Map<String, Long> calculateHash(Path imagePath) throws IOException {
|
||||
File file = imagePath.toFile();
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
PHash pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(file);
|
||||
// public Map<String, Long> calculateHash(Path imagePath) throws IOException {
|
||||
//// File file = cloudStorageService.readFileFromStorageByPath(imagePath.toString());
|
||||
// File file = imagePath.toFile();
|
||||
// if (file == null) {
|
||||
// file = cloudStorageService.readFileFromStorageByPath(imagePath.toString());
|
||||
// }
|
||||
//
|
||||
// PHash pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(file);
|
||||
//
|
||||
// Long firstPart = pHash.getFirstPart();
|
||||
// Long secondPart = pHash.getSecondPart();
|
||||
// Map<String, Long> hash = Map.of(
|
||||
// "hi", firstPart,
|
||||
// "low", secondPart);
|
||||
//
|
||||
// return hash;
|
||||
// }
|
||||
|
||||
public Map<String, Long> calculateHash(Path imagePath) throws IOException {
|
||||
PHash pHash;
|
||||
|
||||
if (imagePath.toFile().exists()) {
|
||||
pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(imagePath.toFile());
|
||||
} else {
|
||||
byte[] data = cloudStorageService.readFileFromStorageBytes(imagePath.toString());
|
||||
|
||||
Path tempFile = Files.createTempFile("perceptual_hash_", ".tmp");
|
||||
try {
|
||||
Files.write(tempFile, data);
|
||||
pHash = PerceptualHashHelper.INSTANCE.generateDCTPerceptualHash(tempFile.toFile());
|
||||
} finally {
|
||||
Files.deleteIfExists(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
Long firstPart = pHash.getFirstPart();
|
||||
Long secondPart = pHash.getSecondPart();
|
||||
Map<String, Long> hash = Map.of(
|
||||
"hi", firstPart,
|
||||
"low", secondPart);
|
||||
|
||||
return hash;
|
||||
return Map.of(
|
||||
"hi", firstPart,
|
||||
"low", secondPart
|
||||
);
|
||||
}
|
||||
|
||||
public void create(FileEntity file, Map<String, Long> stringIntegerMap) {
|
||||
|
||||
@@ -9,6 +9,7 @@ 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;
|
||||
@@ -21,7 +22,7 @@ public class LawCaseService {
|
||||
private final LawCaseRepository lawCaseRepository;
|
||||
|
||||
public LawCase addLawCase(BigDecimal amount, String description, String name,
|
||||
LawCasePriority priority) {
|
||||
LawCasePriority priority, Long violationId, User user) {
|
||||
LawCase lawCase = new LawCase();
|
||||
|
||||
BigDecimal damage = amount == null ? BigDecimal.ZERO : amount;
|
||||
@@ -30,6 +31,8 @@ public class LawCaseService {
|
||||
lawCase.setName(name);
|
||||
lawCase.setDescription(description);
|
||||
lawCase.setPriority(priority);
|
||||
lawCase.setViolationId(violationId);
|
||||
lawCase.setUser(user);
|
||||
|
||||
return lawCaseRepository.save(lawCase);
|
||||
}
|
||||
@@ -67,25 +70,16 @@ public class LawCaseService {
|
||||
return lawCaseRepository.save(lawCase);
|
||||
}
|
||||
|
||||
public List<LawCase> getAllUserLawCases(Long userId, int pageSize, int pageNumber, 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, pageable);
|
||||
}
|
||||
|
||||
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, pageable);
|
||||
return lawCaseRepository.getUserLawCases(userId, lawCaseType, lawyer, lawCasePriority, violationId, pageable);
|
||||
}
|
||||
|
||||
public void deleteById(Long id) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
|
||||
import ru.soune.nocopy.repository.ProtectedFileCheckRepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
@@ -15,7 +16,10 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@@ -34,6 +38,8 @@ public class FileCleanupService {
|
||||
public void cleanupExpiredFiles() {
|
||||
log.info("Starting cleanup of expired temporary files");
|
||||
|
||||
cleanupTempFilesNightly();
|
||||
|
||||
Path tempDir = Paths.get(basePath, "temp");
|
||||
if (!Files.exists(tempDir)) {
|
||||
return;
|
||||
@@ -83,4 +89,50 @@ public class FileCleanupService {
|
||||
log.error("Error during cleanup", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupTempFilesNightly() {
|
||||
log.info("Starting nightly cleanup of temporary files");
|
||||
|
||||
String tempDir = System.getProperty("java.io.tmpdir");
|
||||
Path tempPath = Paths.get(tempDir);
|
||||
|
||||
if (!Files.exists(tempPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Instant cutoffTime = Instant.now().minus(24, ChronoUnit.HOURS); // 24 часа
|
||||
int deletedCount = 0;
|
||||
|
||||
try (Stream<Path> files = Files.list(tempPath)) {
|
||||
List<Path> tempFiles = files
|
||||
.filter(path -> {
|
||||
String filename = path.getFileName().toString();
|
||||
return filename.contains("_") &&
|
||||
!filename.endsWith(".tmp") &&
|
||||
Files.isRegularFile(path);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Path filePath : tempFiles) {
|
||||
try {
|
||||
File file = filePath.toFile();
|
||||
Instant lastModified = Instant.ofEpochMilli(file.lastModified());
|
||||
|
||||
if (lastModified.isBefore(cutoffTime)) {
|
||||
boolean deleted = Files.deleteIfExists(filePath);
|
||||
if (deleted) {
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error deleting file: {}", filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Nightly cleanup deleted {} temporary files", deletedCount);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to list temp directory", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package ru.soune.nocopy.service.file;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.transaction.annotation.Transactional;
|
||||
import ru.soune.nocopy.dto.file.FileEntityResponse;
|
||||
@@ -17,12 +19,15 @@ import ru.soune.nocopy.exception.FileEntityNotFoundException;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.repository.FileMonitoringRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -40,6 +45,8 @@ public class FileEntityService {
|
||||
|
||||
private final ImageResizeService imageResizeService;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@Value("${server.baseurl}")
|
||||
private String baseUrl;
|
||||
|
||||
@@ -104,6 +111,34 @@ public class FileEntityService {
|
||||
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)
|
||||
public FileResponse getUserFiles(Long userId, int page, int pageSize, int version) {
|
||||
User user = userRepository.findById(userId).orElseThrow();
|
||||
@@ -172,16 +207,8 @@ public class FileEntityService {
|
||||
fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
public void deleteFromDisk(FileEntity fileEntity) throws IOException {
|
||||
Path path = Paths.get(fileEntity.getFilePath());
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Files.delete(path);
|
||||
|
||||
fileEntityRepository.delete(fileEntity);
|
||||
public void deleteFromDisk(String deleteFilePath) throws IOException {
|
||||
Files.deleteIfExists(Paths.get(deleteFilePath));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@@ -207,18 +234,27 @@ public class FileEntityService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
|
||||
public FileEntity writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("File not found: " + id));
|
||||
|
||||
String extension = determineFileExtension(fileExt, fileEntity);
|
||||
Path protectedFilePath = prepareProtectedPath(fileEntity, extension);
|
||||
|
||||
if (Files.exists(protectedFilePath)) {
|
||||
Files.delete(protectedFilePath);
|
||||
}
|
||||
Files.createDirectories(protectedFilePath.getParent());
|
||||
|
||||
Files.write(protectedFilePath, data);
|
||||
Files.deleteIfExists(protectedFilePath);
|
||||
|
||||
try (FileChannel channel = FileChannel.open(protectedFilePath,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.WRITE)) {
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data);
|
||||
while (buffer.hasRemaining()) {
|
||||
channel.write(buffer);
|
||||
}
|
||||
channel.force(true);
|
||||
}
|
||||
|
||||
if (imageResizeService.isImage(extension)) {
|
||||
imageResizeService.generateSizes(fileEntity, data);
|
||||
@@ -230,14 +266,14 @@ public class FileEntityService {
|
||||
fileEntity.setFileExtension(extension);
|
||||
fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED);
|
||||
|
||||
fileEntityRepository.save(fileEntity);
|
||||
return fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
public void clearTempFiles(long userId) throws IOException {
|
||||
public void clearTempFiles(long userId) {
|
||||
List<FileEntity> fileByUserIdAndStatus = fileEntityRepository.findFileByUserIdAndStatus(userId, FileStatus.TEMP);
|
||||
|
||||
for (FileEntity fileEntity : fileByUserIdAndStatus) {
|
||||
deleteFromDisk(fileEntity);
|
||||
cloudStorageService.deleteFromStorageDisk(fileEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,24 +285,6 @@ public class FileEntityService {
|
||||
return fileEntityRepository.findFileIdByFilePath(filePath);
|
||||
}
|
||||
|
||||
public File getFileById(String id) {
|
||||
try {
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
|
||||
File file = new File(fileEntity.getFilePath());
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("File not found on disk: " + fileEntity.getFilePath());
|
||||
}
|
||||
|
||||
return file;
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting file: {}", id, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Path prepareProtectedPath(FileEntity fileEntity, String extension) throws IOException {
|
||||
Path originalPath = Paths.get(fileEntity.getFilePath());
|
||||
|
||||
@@ -292,7 +310,7 @@ public class FileEntityService {
|
||||
return protectedPath;
|
||||
}
|
||||
|
||||
public void updateSignature(String signature, String fileId) throws IOException {
|
||||
public FileEntity updateSignature(String signature, String fileId) throws IOException {
|
||||
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
|
||||
fileEntity.setSignature(signature);
|
||||
fileEntity.setProtectionStatus(ProtectionStatus.PROTECTED);
|
||||
@@ -300,18 +318,30 @@ public class FileEntityService {
|
||||
fileEntity.setProtectedAt(LocalDateTime.now());
|
||||
fileEntity.setProtectedFilePath(prepareProtectedPath(fileEntity, fileEntity.getFileExtension()).toString());
|
||||
|
||||
fileEntityRepository.save(fileEntity);
|
||||
return fileEntityRepository.save(fileEntity);
|
||||
}
|
||||
|
||||
private boolean checkFileExistsOnDisk(String filePath) {
|
||||
try {
|
||||
return Files.exists(Paths.get(filePath));
|
||||
} catch (Exception e) {
|
||||
log.warn("Error checking file existence: {}", filePath, e);
|
||||
return false;
|
||||
public String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
// private boolean checkFileExistsOnDisk(String filePath) {
|
||||
// try {
|
||||
// return Files.exists(Paths.get(filePath));
|
||||
// } catch (Exception e) {
|
||||
// log.warn("Error checking file existence: {}", filePath, e);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
private String determineFileExtension(String fileExt, FileEntity fileEntity) {
|
||||
if (fileExt != null) {
|
||||
return fileExt;
|
||||
@@ -323,7 +353,7 @@ public class FileEntityService {
|
||||
private final FileMonitoringRepository fileMonitoringRepository;
|
||||
|
||||
private FileEntityResponse convertToResponse(FileEntity fileEntity, int version) {
|
||||
boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath());
|
||||
// boolean existsOnDisk = checkFileExistsOnDisk(fileEntity.getFilePath());
|
||||
User user = userRepository.findById(fileEntity.getUserId()).get();
|
||||
String fileName = fileEntity.getOriginalFileName();
|
||||
String name = fileName;
|
||||
@@ -355,7 +385,7 @@ public class FileEntityService {
|
||||
.updatedAt(fileEntity.getUpdatedAt())
|
||||
.formattedSize(formatFileSize(fileEntity.getFileSize()))
|
||||
.downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId())
|
||||
.existsOnDisk(existsOnDisk)
|
||||
// .existsOnDisk(existsOnDisk)
|
||||
.supportId(fileEntity.getSupportId())
|
||||
.protectStatus(fileEntity.getProtectionStatus().toString())
|
||||
.fileName(name + "_nocopy_protected" + "." + fileEntity.getFileExtension())
|
||||
@@ -370,16 +400,4 @@ public class FileEntityService {
|
||||
.thumbnailFileUrl(baseUrl + "/api/files/protected/" + fileEntity.getId() + "/thumbnail")
|
||||
.build();
|
||||
}
|
||||
|
||||
public String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,12 @@ public class FileStatsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final FileEntityService fileEntityService;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -34,32 +38,24 @@ public class FileStatsService {
|
||||
return FileInfoUserResponse.builder()
|
||||
.allFileSize(calculateTotalSize(files))
|
||||
.fileCount(calculateTotalCount(files))
|
||||
.filesCheck(calculateByStatus(files, FileStatus.CHECKED))
|
||||
.filesViolation(calculateByStatus(files, FileStatus.VIOLATION))
|
||||
// .filesCheck(calculateByStatus(files, FileStatus.CHECKED))
|
||||
// .filesViolation(calculateByStatus(files, FileStatus.VIOLATION))
|
||||
.protectedFilesCount(protectedUserFiles(files))
|
||||
|
||||
.imagesSize(calculateMediaSize(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))
|
||||
|
||||
.videosSize(calculateMediaSize(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))
|
||||
|
||||
.audiosSize(calculateMediaSize(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))
|
||||
|
||||
.documentSize(calculateMediaSize(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))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ package ru.soune.nocopy.service.file;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.soune.nocopy.dto.file.UploadProgressResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.entity.file.FileStatus;
|
||||
import ru.soune.nocopy.entity.file.FileUploadSession;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface FileUploadService {
|
||||
FileUploadSession initUpload(Long userId, String fileName,
|
||||
String fileType, String extension, String convertTo, long fileSize);
|
||||
@@ -22,5 +21,5 @@ public interface FileUploadService {
|
||||
|
||||
UploadProgressResponse getUploadProgress(String uploadId);
|
||||
|
||||
void completeFileProcessingAsync(FileUploadSession session, FileStatus status);
|
||||
FileEntity completeFileProcessing(FileUploadSession session, FileStatus status);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package ru.soune.nocopy.service.file;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
@@ -18,9 +20,13 @@ import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ImageResizeService {
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
private static final int THUMBNAIL_SIZE = 150;
|
||||
|
||||
private static final int MEDIUM_SIZE = 600;
|
||||
|
||||
@Value("${file.storage.base-path}")
|
||||
@@ -50,11 +56,15 @@ public class ImageResizeService {
|
||||
if (original.getWidth() > THUMBNAIL_SIZE) {
|
||||
String thumbPath = resizeAndSave(original, baseName + "_thumb", THUMBNAIL_SIZE);
|
||||
file.setThumbnailPath(thumbPath);
|
||||
cloudStorageService.saveFilesToStorage(file.getMimeType(), file.getFileExtension(),
|
||||
thumbPath);
|
||||
}
|
||||
|
||||
if (original.getWidth() > MEDIUM_SIZE) {
|
||||
String mediumPath = resizeAndSave(original, baseName + "_medium", MEDIUM_SIZE);
|
||||
file.setMediumPath(mediumPath);
|
||||
cloudStorageService.saveFilesToStorage(file.getMimeType(), file.getFileExtension(),
|
||||
mediumPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package ru.soune.nocopy.service.file.cloud;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.core.ResponseBytes;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.core.sync.ResponseTransformer;
|
||||
import software.amazon.awssdk.http.apache.ApacheHttpClient;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CloudStorageService {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
@Value("${yandex.cloud.key}")
|
||||
private String key;
|
||||
|
||||
@Value("${yandex.cloud.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${yandex.cloud.region}")
|
||||
private String region;
|
||||
|
||||
@Value("${yandex.cloud.s3-endpoint}")
|
||||
private String s3endpoint;
|
||||
|
||||
@Value("${yandex.cloud.bucket}")
|
||||
private String bucket;
|
||||
|
||||
public void saveFilesToStorage(String mimeType, String extension, String sendToCloudFilePath) {
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
String mimeTypeFromFile = mimeType.equals("document") ? "application" : mimeType;
|
||||
String contentType = mimeTypeFromFile + "/" + extension;
|
||||
String filePath = "files" + sendToCloudFilePath;
|
||||
|
||||
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key(filePath)
|
||||
.contentType(contentType)
|
||||
.build();
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(Paths.get(sendToCloudFilePath));
|
||||
s3Client.putObject(putObjectRequest,
|
||||
RequestBody.fromBytes(bytes));
|
||||
Files.deleteIfExists(Paths.get(sendToCloudFilePath));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read file: {}", filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public File readFileFromStorageByPath(String filePath) throws IOException {
|
||||
GetObjectRequest objectRequest = GetObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key("files" + filePath)
|
||||
.build();
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
String tempDir = System.getProperty("java.io.tmpdir");
|
||||
|
||||
String fileName = System.currentTimeMillis() + "_" + UUID.randomUUID() + "_" +
|
||||
filePath.replace("/", "_");
|
||||
File tempFile = new File(tempDir, fileName);
|
||||
|
||||
s3Client.getObject(objectRequest, tempFile.toPath());
|
||||
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
public byte[] readFileFromStorageBytes(String filePath) {
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
GetObjectRequest objectRequest = GetObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key("files" + filePath)
|
||||
.build();
|
||||
|
||||
try {
|
||||
ResponseBytes<GetObjectResponse> responseBytes = s3Client.getObjectAsBytes(objectRequest);
|
||||
return responseBytes.asByteArray();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to read file from S3: {}", filePath, e);
|
||||
throw new RuntimeException("Failed to read file from S3", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public InputStream readFileFromStorage(String filePath) {
|
||||
GetObjectRequest objectRequest = GetObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key("files" + filePath)
|
||||
.build();
|
||||
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
return s3Client.getObject(objectRequest, ResponseTransformer.toInputStream());
|
||||
}
|
||||
|
||||
public void deleteFileFromStorage(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
log.warn("Attempted to delete file with empty path");
|
||||
return;
|
||||
}
|
||||
|
||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||
|
||||
S3Client s3Client = S3Client.builder()
|
||||
.httpClient(ApacheHttpClient.create())
|
||||
.region(Region.of(region))
|
||||
.endpointOverride(URI.create(s3endpoint))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||
.build();
|
||||
|
||||
String cloudKey = "files" + filePath;
|
||||
|
||||
try {
|
||||
try {
|
||||
s3Client.headObject(builder -> builder.bucket(bucket).key(cloudKey).build());
|
||||
} catch (Exception e) {
|
||||
log.warn("File does not exist in cloud storage: {}", cloudKey);
|
||||
return;
|
||||
}
|
||||
|
||||
s3Client.deleteObject(builder -> builder.bucket(bucket).key(cloudKey).build());
|
||||
|
||||
log.info("File successfully deleted from cloud storage: {}", cloudKey);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to delete file from cloud storage: {}", cloudKey, e);
|
||||
throw new RuntimeException("Failed to delete file from cloud storage: " + cloudKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteFromStorageDisk(FileEntity fileEntity) {
|
||||
deleteFileFromStorage(fileEntity.getFilePath());
|
||||
|
||||
if (fileEntity.getProtectedFilePath() != null) {
|
||||
deleteFileFromStorage(fileEntity.getProtectedFilePath());
|
||||
}
|
||||
|
||||
fileEntityRepository.delete(fileEntity);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
import ru.soune.nocopy.util.FileUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -22,6 +23,8 @@ public class AudioLocalSearchImpl implements AudioLocalSearch {
|
||||
|
||||
private final FileUtil fileUtil;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@Override
|
||||
public @Nullable FileProtector.FileInfo findBySignature(@NotNull String signature) {
|
||||
FileEntity fileEntity = fileEntityService.findBySignature(signature);
|
||||
@@ -34,7 +37,10 @@ public class AudioLocalSearchImpl implements AudioLocalSearch {
|
||||
@Override
|
||||
public void updateSignature(@NotNull String signature, @NotNull FileProtector.FileInfo fileInfo) {
|
||||
try {
|
||||
fileEntityService.updateSignature(signature, fileInfo.getId());
|
||||
FileEntity fileEntity = fileEntityService.updateSignature(signature, fileInfo.getId());
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,23 @@ public class DocumentLocalSearchImpl implements DocumentLocalSearch {
|
||||
FileEntity fileEntity = fileEntityRepository.findByIdAndUserId(fileId, Long.valueOf(ownerId));
|
||||
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, null);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
log.info("fileInfo: {}", fileInfo);
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.vrt.fileprotection.FileProtector;
|
||||
import com.vrt.fileprotection.NoCopyCheckResult;
|
||||
import com.vrt.fileprotection.audio.AudioCheckResult;
|
||||
import com.vrt.fileprotection.documents.DocumentCheckResult;
|
||||
import com.vrt.fileprotection.image.ImageCheckResult;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -19,6 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import ru.soune.nocopy.dto.file.UploadProgressResponse;
|
||||
import ru.soune.nocopy.entity.file.*;
|
||||
import ru.soune.nocopy.entity.notification.NotificationType;
|
||||
import ru.soune.nocopy.entity.tokenoperation.OperationType;
|
||||
import ru.soune.nocopy.exception.*;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.repository.FileUploadSessionRepository;
|
||||
@@ -28,6 +29,8 @@ import ru.soune.nocopy.service.ImageHashService;
|
||||
import ru.soune.nocopy.service.cost.CostService;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.FileUploadService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
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.register.AuthService;
|
||||
@@ -95,6 +98,8 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
|
||||
private final ModerationFileService moderationFileService;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
@@ -190,7 +195,6 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public UploadProgressResponse uploadChunk(String uploadId, Integer chunkNumber,
|
||||
MultipartFile chunkFile, Integer findSimilar) {
|
||||
FileUploadSession session = sessionRepository.findById(uploadId)
|
||||
@@ -222,10 +226,9 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
return UploadProgressResponse.fromSession(session);
|
||||
}
|
||||
|
||||
@Async("fileUploadTaskExecutor")
|
||||
@Transactional
|
||||
@Override
|
||||
public void completeFileProcessingAsync(FileUploadSession session, FileStatus status) {
|
||||
public FileEntity completeFileProcessing(FileUploadSession session, FileStatus status) {
|
||||
try {
|
||||
Path filePath = Paths.get(session.getFilePath());
|
||||
String checksum = calculateChecksum(filePath);
|
||||
@@ -240,30 +243,28 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
.fileExtension(session.getExtension())
|
||||
.checksum(checksum)
|
||||
.uploadSessionId(session.getUploadId())
|
||||
.protectionStatus(ProtectionStatus.PROCESSING)
|
||||
.status(status)
|
||||
.build();
|
||||
|
||||
if (status != FileStatus.TEMP) {
|
||||
fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING);
|
||||
}
|
||||
|
||||
FileEntity saved = fileEntityRepository.save(fileEntity);
|
||||
|
||||
if (session.getFileType().equals("image") && status != FileStatus.PRIVATE) {
|
||||
Map<String, Long> hash = imageHashService.calculateHash(filePath);
|
||||
|
||||
imageHashService.create(saved, hash);
|
||||
}
|
||||
|
||||
cleanupSessionFiles(session);
|
||||
|
||||
if (status != FileStatus.TEMP && status != FileStatus.PRIVATE) {
|
||||
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo());
|
||||
|
||||
noCopyFileService.addFile(fileInfo);
|
||||
}
|
||||
|
||||
log.info("File processing completed for session: {}", session.getUploadId());
|
||||
|
||||
return saved;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to complete file processing for session {}: {}",
|
||||
session.getUploadId(), e.getMessage(), e);
|
||||
throw new FileUploadException("Failed to complete file processing", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +337,7 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
session.setStatus(UploadStatus.COMPLETED);
|
||||
session.setFilePath(finalFilePath);
|
||||
|
||||
completeFileProcessingAsync(session, FileStatus.PRIVATE);
|
||||
completeFileProcessing(session, FileStatus.PRIVATE);
|
||||
} else {
|
||||
sessionRepository.save(session);
|
||||
}
|
||||
@@ -378,18 +379,19 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
chunkNumber, session.getChunksUploaded(), session.getTotalChunks());
|
||||
|
||||
boolean isLastChunk = session.getChunksUploaded().equals(session.getTotalChunks());
|
||||
|
||||
//TODO CHECK
|
||||
if (isLastChunk) {
|
||||
String finalFilePath = assembleFileSynchronously(session);
|
||||
|
||||
if (findSimilar == 0) {
|
||||
// if (session.getFileType().startsWith("image")) {
|
||||
// checkForDuplicatesSynchronously(finalFilePath);
|
||||
// } else if (session.getFileType().startsWith("audio") || session.getFileType().startsWith("document")) {
|
||||
// fileSimilarityService.hasDuplicatesByHash(finalFilePath ,session.getFileType(),
|
||||
// session.getUserId());
|
||||
if (session.getFileType().startsWith("image")) {
|
||||
checkForDuplicatesSynchronously(finalFilePath);
|
||||
} else if (session.getFileType().startsWith("audio") || session.getFileType().startsWith("document")) {
|
||||
fileSimilarityService.hasDuplicatesByHash(finalFilePath ,session.getFileType(),
|
||||
session.getUserId());
|
||||
|
||||
File file = new File(finalFilePath);
|
||||
// File file = cloudStorageService.readFileFromStorageByPath(finalFilePath);
|
||||
|
||||
if (session.getFileType().startsWith("audio") && !isWavFile(file)) {
|
||||
session.setStatus(UploadStatus.FAILED);
|
||||
@@ -400,12 +402,9 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
throw new FileFormatException("Failed to upload chunk: .wav file not have RIFF header or " +
|
||||
"another problems with .wav file");
|
||||
}
|
||||
FileProtector.Type type = FileProtector.Type.IMAGE;
|
||||
|
||||
if (session.getFileType().startsWith("audio")) type = FileProtector.Type.AUDIO;
|
||||
if (session.getFileType().startsWith("document")) type = FileProtector.Type.DOC;
|
||||
// FileProtector.Type type = "document".equals(session.getFileType()) ?
|
||||
// FileProtector.Type.DOC : FileProtector.Type.valueOf(session.getFileType().toUpperCase());
|
||||
FileProtector.Type type = "document".equals(session.getFileType()) ?
|
||||
FileProtector.Type.DOC : FileProtector.Type.valueOf(session.getFileType().toUpperCase());
|
||||
|
||||
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, type);
|
||||
|
||||
@@ -415,49 +414,46 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
throw new DuplicateImageException("Duplicate", info.getId(),
|
||||
Long.valueOf(info.getOwnerId()));
|
||||
}
|
||||
|
||||
case AudioCheckResult.Success success -> {
|
||||
FileProtector.FileInfo info = success.getInfo();
|
||||
throw new DuplicateImageException("Duplicate", info.getId(),
|
||||
Long.valueOf(info.getOwnerId()));
|
||||
}
|
||||
|
||||
case ImageCheckResult.Success success -> {
|
||||
FileProtector.FileInfo info = success.getInfo();
|
||||
if (info != null) {
|
||||
throw new DuplicateImageException("Duplicate", info.getId(),
|
||||
Long.valueOf(info.getOwnerId()));
|
||||
}
|
||||
}
|
||||
|
||||
case DocumentCheckResult.Failed failed ->
|
||||
log.warn("Document check failed: {}", failed.getMessage());
|
||||
case AudioCheckResult.Failed failed ->
|
||||
log.warn("Audio check failed: {}", failed.getMessage());
|
||||
case ImageCheckResult.Failed failed ->
|
||||
log.warn("Image check failed: {}", failed.getMessage());
|
||||
default -> log.warn("Unexpected result type: {}", checkResult.getClass().getSimpleName());
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
session.setStatus(UploadStatus.COMPLETED);
|
||||
session.setFilePath(finalFilePath);
|
||||
|
||||
notificationService.addNotification(NotificationType.FILE_ADDED_TO_SYSTEM, session.getUserId());
|
||||
|
||||
FileStatus status = findSimilar == 0 ? FileStatus.MODERATION: FileStatus.TEMP;
|
||||
|
||||
if (status == FileStatus.TEMP) {
|
||||
fileEntityService.clearTempFiles(session.getUserId());
|
||||
}
|
||||
|
||||
completeFileProcessingAsync(session, status);
|
||||
FileEntity fileEntity = completeFileProcessing(session, status);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getFilePath());
|
||||
|
||||
if (status != FileStatus.TEMP) {
|
||||
String fileType = session.getFileType();
|
||||
|
||||
tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType));
|
||||
tariffInfoService.writeOffTokens(session.getUserId(), costService.protectFileCost(fileType),
|
||||
OperationType.FILE_UPLOAD);
|
||||
notificationService.addNotification(NotificationType.FILE_ADDED_TO_SYSTEM, session.getUserId());
|
||||
}
|
||||
|
||||
if (status != FileStatus.TEMP) {
|
||||
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity, session.getConvertTo());
|
||||
|
||||
noCopyFileService.addFile(fileInfo);
|
||||
}
|
||||
} else {
|
||||
sessionRepository.save(session);
|
||||
@@ -592,6 +588,7 @@ public class FileUploadServiceImpl implements FileUploadService {
|
||||
private void checkForDuplicatesSynchronously(String filePath)
|
||||
throws IOException , DuplicateImageException {
|
||||
Path path = Paths.get(filePath);
|
||||
|
||||
Map<String, Long> hash = imageHashService.calculateHash(path);
|
||||
|
||||
List<SimilarImageProjection> duplicates = fileSimilarityService.findDuplicatedByHash(
|
||||
|
||||
@@ -11,7 +11,7 @@ import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.repository.FileUploadSessionRepository;
|
||||
import ru.soune.nocopy.service.file.FileEntityService;
|
||||
import ru.soune.nocopy.service.file.FileUploadService;
|
||||
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -32,27 +32,53 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
private final CloudStorageService cloudStorageService;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public File getImageFile(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public File getVideoFile(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public File getAudioFile(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable File getDocument(@NotNull String id) {
|
||||
return fileEntityService.getFileById(id);
|
||||
FileEntity fileEntity = fileEntityRepository.findById(id).orElseThrow(() ->
|
||||
new RuntimeException("File not found: " + id));
|
||||
try {
|
||||
return cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +113,20 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public com.vrt.fileprotection.OperationResult writeAudioFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
|
||||
Path filePath = Paths.get(fileEntity.getProtectedFilePath());
|
||||
|
||||
if (!Files.exists(filePath)) {
|
||||
log.error("File not found after write: {}", filePath);
|
||||
return OperationResult.Companion.failure("File not found");
|
||||
}
|
||||
|
||||
log.info("File ready for upload: {} (size: {} bytes)", filePath, Files.size(filePath));
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
@@ -99,7 +138,11 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public com.vrt.fileprotection.OperationResult writeVideoFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
@@ -111,7 +154,11 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public com.vrt.fileprotection.OperationResult writeImageFile(@NotNull String id, @NotNull byte[] data, @Nullable String fileExt) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, fileExt);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
@@ -122,7 +169,10 @@ public class ProtectionFileProviderImpl implements FileProtector.FileProvider {
|
||||
@Override
|
||||
public @NotNull OperationResult writeDocumentFile(@NotNull String id, @NotNull byte[] data) {
|
||||
try {
|
||||
fileEntityService.writeProtectedFile(id, data, null);
|
||||
FileEntity fileEntity = fileEntityService.writeProtectedFile(id, data, null);
|
||||
|
||||
cloudStorageService.saveFilesToStorage(fileEntity.getMimeType(), fileEntity.getFileExtension(),
|
||||
fileEntity.getProtectedFilePath());
|
||||
return OperationResult.Companion.success();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create protected file: {}", id, e);
|
||||
|
||||
@@ -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.tarif.TariffInfo;
|
||||
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.exception.TariffNotFoundException;
|
||||
import ru.soune.nocopy.repository.FileMonitoringRepository;
|
||||
@@ -64,9 +65,10 @@ public class MonitoringSearchService {
|
||||
|
||||
MonitoringType monitoringType = monitoring.getMonitoringType();
|
||||
TariffDTO tariffMonitoring = tariffService.getTariffByType(TariffType.valueOf(monitoringType.name()));
|
||||
User user = userRepository.findById(monitoring.getUserId()).orElseThrow();
|
||||
|
||||
try {
|
||||
tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens());
|
||||
tariffInfoService.writeOffTokens(monitoring.getUserId(), tariffMonitoring.getTokens(), OperationType.MONITORING);
|
||||
|
||||
boolean useYandex = searchProperties.getEngines().getOrDefault("yandex",
|
||||
new SearchProperties.EngineConfig()).isEnabled();
|
||||
@@ -88,10 +90,14 @@ public class MonitoringSearchService {
|
||||
allResults.addAll(yandexImages);
|
||||
log.info("Yandex search found {} results", yandexImages.size());
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
log.warn("Yandex search timeout for monitoring file {}", monitoring.getFile().getId());
|
||||
} catch (IOException e) {
|
||||
log.error("Yandex search failed for monitoring file {}", monitoring.getFile().getId(), e);
|
||||
} catch (TimeoutException | IOException e) {
|
||||
log.error("Error processing monitoring search", e);
|
||||
monitoring.setLastRunStatus("ERROR: " + e.getMessage());
|
||||
updateNextRun(monitoring);
|
||||
|
||||
monitoringRepository.save(monitoring);
|
||||
|
||||
notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
|
||||
}
|
||||
} else {
|
||||
log.info("Yandex search is disabled by settings");
|
||||
@@ -108,10 +114,14 @@ public class MonitoringSearchService {
|
||||
allResults.addAll(googleImages);
|
||||
log.info("Google search found {} results", googleImages.size());
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
log.warn("Google search timeout for monitoring file {}", monitoring.getFile().getId());
|
||||
} catch (IOException e) {
|
||||
log.error("Google search failed for monitoring file {}", monitoring.getFile().getId(), e);
|
||||
} catch (TimeoutException | IOException e) {
|
||||
log.error("Error processing monitoring search", e);
|
||||
monitoring.setLastRunStatus("ERROR: " + e.getMessage());
|
||||
updateNextRun(monitoring);
|
||||
|
||||
monitoringRepository.save(monitoring);
|
||||
|
||||
notificationService.addNotification(NotificationType.SEARCH_OPERATION_FAILED, user.getId());
|
||||
}
|
||||
} else {
|
||||
log.info("Google search is disabled by settings");
|
||||
@@ -138,7 +148,6 @@ public class MonitoringSearchService {
|
||||
}
|
||||
|
||||
} catch (TariffNotFoundException e) {
|
||||
User user = userRepository.findById(monitoring.getUserId()).orElseThrow();
|
||||
TariffInfo activeTariffInfo = user.getActiveTariffInfo();
|
||||
int currentTokens = activeTariffInfo.getTokens() + activeTariffInfo.getBoughtTokens();
|
||||
|
||||
@@ -153,7 +162,6 @@ public class MonitoringSearchService {
|
||||
notificationService.addNotification(NotificationType.TOKEN_NOT_FOUND, user.getId(),
|
||||
NotificationMessage.FILE_SEARCH.getMessageKey());
|
||||
} catch (Exception e) {
|
||||
User user = userRepository.findById(monitoring.getUserId()).orElseThrow();
|
||||
log.error("Error processing monitoring search", e);
|
||||
monitoring.setLastRunStatus("ERROR: " + e.getMessage());
|
||||
updateNextRun(monitoring);
|
||||
|
||||
@@ -12,6 +12,7 @@ import ru.soune.nocopy.entity.notification.NotificationType;
|
||||
import ru.soune.nocopy.entity.search.GlobalSearchResult;
|
||||
import ru.soune.nocopy.entity.search.GlobalSearchTask;
|
||||
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.GlobalSearchResultRepository;
|
||||
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
|
||||
@@ -59,7 +60,7 @@ public class GlobalSearchAsyncProcessor {
|
||||
FileEntity file = fileEntityRepository.findById(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);
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
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.ProtectedFilesStatisticResponse;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
import ru.soune.nocopy.util.PercentileCalculator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ProtectedFilesStatisticService {
|
||||
private static final int PERCENTILE_5 = 5;
|
||||
|
||||
private static final int PERCENTILE_50 = 50;
|
||||
|
||||
private static final int PERCENTILE_95 = 95;
|
||||
|
||||
private static final double BYTES_TO_MB = 1048576.0;
|
||||
|
||||
private static final double BYTES_TO_GB = 1073741824.0;
|
||||
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
public ProtectedFilesStatisticResponse getProtectedFilesStatistics() {
|
||||
List<Object[]> totalStats = fileEntityRepository.getProtectedFilesTotalStats();
|
||||
Long totalFiles = extractTotalFiles(totalStats);
|
||||
Long totalSize = extractTotalSize(totalStats);
|
||||
|
||||
List<Object[]> userStats = fileEntityRepository.getProtectedFilesPerUserStats();
|
||||
|
||||
List<Long> fileCounts = extractFileCounts(userStats);
|
||||
List<Long> fileSizes = extractFileSizes(userStats);
|
||||
|
||||
PercentileCalculator.PercentilesResult filesPercentiles =
|
||||
PercentileCalculator.calculatePercentiles(fileCounts, PERCENTILE_5, PERCENTILE_50, PERCENTILE_95);
|
||||
PercentileCalculator.PercentilesResult sizePercentiles =
|
||||
PercentileCalculator.calculatePercentiles(fileSizes, PERCENTILE_5, PERCENTILE_50, PERCENTILE_95);
|
||||
|
||||
return buildResponse(totalFiles, totalSize, filesPercentiles, sizePercentiles);
|
||||
}
|
||||
|
||||
private Long extractTotalFiles(List<Object[]> totalStats) {
|
||||
if (totalStats.isEmpty() || totalStats.get(0) == null) {
|
||||
return 0L;
|
||||
}
|
||||
Object[] row = totalStats.get(0);
|
||||
return row[0] != null ? ((Number) row[0]).longValue() : 0L;
|
||||
}
|
||||
|
||||
private Long extractTotalSize(List<Object[]> totalStats) {
|
||||
if (totalStats.isEmpty() || totalStats.get(0) == null) {
|
||||
return 0L;
|
||||
}
|
||||
Object[] row = totalStats.get(0);
|
||||
return row[1] != null ? ((Number) row[1]).longValue() : 0L;
|
||||
}
|
||||
|
||||
private List<Long> extractFileCounts(List<Object[]> userStats) {
|
||||
return userStats.stream()
|
||||
.map(row -> row[1] != null ? ((Number) row[1]).longValue() : 0L)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Long> extractFileSizes(List<Object[]> userStats) {
|
||||
return userStats.stream()
|
||||
.map(row -> row[2] != null ? ((Number) row[2]).longValue() : 0L)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ProtectedFilesStatisticResponse buildResponse(
|
||||
Long totalFiles,
|
||||
Long totalSize,
|
||||
PercentileCalculator.PercentilesResult filesPercentiles,
|
||||
PercentileCalculator.PercentilesResult sizePercentiles) {
|
||||
|
||||
ProtectedFilesStatisticResponse.Percentiles filesPerUser =
|
||||
new ProtectedFilesStatisticResponse.Percentiles(
|
||||
filesPercentiles.getP5(),
|
||||
filesPercentiles.getP50(),
|
||||
filesPercentiles.getP95());
|
||||
|
||||
ProtectedFilesStatisticResponse.SizePercentiles sizePerUser =
|
||||
new ProtectedFilesStatisticResponse.SizePercentiles(
|
||||
sizePercentiles.getP5(),
|
||||
sizePercentiles.getP50(),
|
||||
sizePercentiles.getP95(),
|
||||
convertBytesToMb(sizePercentiles.getP5()),
|
||||
convertBytesToMb(sizePercentiles.getP50()),
|
||||
convertBytesToMb(sizePercentiles.getP95()));
|
||||
|
||||
return ProtectedFilesStatisticResponse.builder()
|
||||
.totalFiles(totalFiles)
|
||||
.totalSizeBytes(totalSize)
|
||||
.totalSizeGb(convertBytesToGb(totalSize))
|
||||
.filesPerUser(filesPerUser)
|
||||
.sizePerUser(sizePerUser)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Double convertBytesToMb(Long bytes) {
|
||||
if (bytes == null || bytes == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return bytes / BYTES_TO_MB;
|
||||
}
|
||||
|
||||
private Double convertBytesToGb(Long bytes) {
|
||||
if (bytes == null || bytes == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return bytes / BYTES_TO_GB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
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.SubscriberStatisticResponse;
|
||||
import ru.soune.nocopy.entity.tarif.TariffInfo;
|
||||
import ru.soune.nocopy.entity.tarif.TariffTimeTerm;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
import ru.soune.nocopy.repository.TariffInfoRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class SubscriberStatisticService {
|
||||
|
||||
private final TariffInfoRepository tariffInfoRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public SubscriberStatisticResponse getSubscriberStatistics() {
|
||||
return SubscriberStatisticResponse.builder()
|
||||
.monthly(buildPeriodStatistics(TariffTimeTerm.MONTHLY))
|
||||
.yearly(buildPeriodStatistics(TariffTimeTerm.YEAR))
|
||||
.build();
|
||||
}
|
||||
|
||||
private SubscriberStatisticResponse.PeriodStatistics buildPeriodStatistics(TariffTimeTerm term) {
|
||||
Long currentActive = tariffInfoRepository.getCurrentActiveSubscriptions(term);
|
||||
|
||||
List<TariffInfo> allTariffs = tariffInfoRepository.getAllTariffInfosForAnalysis(term);
|
||||
|
||||
Map<Long, List<TariffInfo>> tariffsByUser = groupTariffsByUser(allTariffs);
|
||||
|
||||
Long firstTime = countFirstTimeSubscriptions(tariffsByUser);
|
||||
Long renewals = countRenewals(tariffsByUser);
|
||||
UpDownGradeResult gradeResult = analyzeUpgradesAndDowngrades(tariffsByUser);
|
||||
|
||||
return SubscriberStatisticResponse.PeriodStatistics.builder()
|
||||
.currentActiveSubscriptions(currentActive != null ? currentActive : 0L)
|
||||
.firstTimeSubscriptions(firstTime)
|
||||
.renewals(renewals)
|
||||
.upgrades(gradeResult.upgrades)
|
||||
.downgrades(gradeResult.downgrades)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Map<Long, List<TariffInfo>> groupTariffsByUser(List<TariffInfo> tariffs) {
|
||||
Map<Long, List<TariffInfo>> result = new HashMap<>();
|
||||
|
||||
for (TariffInfo ti : tariffs) {
|
||||
Optional<User> userOpt = userRepository.findByPersonalTariffInfoId(ti.getId());
|
||||
if (userOpt.isPresent()) {
|
||||
Long userId = userOpt.get().getId();
|
||||
result.computeIfAbsent(userId, k -> new ArrayList<>()).add(ti);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long countFirstTimeSubscriptions(Map<Long, List<TariffInfo>> tariffsByUser) {
|
||||
return (long) tariffsByUser.size();
|
||||
}
|
||||
|
||||
private Long countRenewals(Map<Long, List<TariffInfo>> tariffsByUser) {
|
||||
return tariffsByUser.values().stream()
|
||||
.filter(list -> list.size() > 1)
|
||||
.mapToLong(list -> list.size() - 1)
|
||||
.sum();
|
||||
}
|
||||
|
||||
private UpDownGradeResult analyzeUpgradesAndDowngrades(Map<Long, List<TariffInfo>> tariffsByUser) {
|
||||
long upgrades = 0L;
|
||||
long downgrades = 0L;
|
||||
|
||||
for (List<TariffInfo> userTariffs : tariffsByUser.values()) {
|
||||
if (userTariffs.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
userTariffs.sort(Comparator.comparing(TariffInfo::getStartTariff,
|
||||
Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
|
||||
for (int i = 1; i < userTariffs.size(); i++) {
|
||||
TariffInfo previous = userTariffs.get(i - 1);
|
||||
TariffInfo current = userTariffs.get(i);
|
||||
|
||||
if (previous.getTariff() == null || current.getTariff() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double previousPrice = previous.getTariff().getPrice();
|
||||
double currentPrice = current.getTariff().getPrice();
|
||||
|
||||
if (currentPrice > previousPrice) {
|
||||
upgrades++;
|
||||
} else if (currentPrice < previousPrice) {
|
||||
downgrades++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new UpDownGradeResult(upgrades, downgrades);
|
||||
}
|
||||
|
||||
private static class UpDownGradeResult {
|
||||
final Long upgrades;
|
||||
final Long downgrades;
|
||||
|
||||
UpDownGradeResult(Long upgrades, Long downgrades) {
|
||||
this.upgrades = upgrades;
|
||||
this.downgrades = downgrades;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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.TokenStatisticResponse;
|
||||
import ru.soune.nocopy.repository.PaymentRepository;
|
||||
import ru.soune.nocopy.repository.TokenOperationRepository;
|
||||
import ru.soune.nocopy.util.PercentileCalculator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TokenStatisticService {
|
||||
private final PaymentRepository paymentRepository;
|
||||
|
||||
private final TokenOperationRepository tokenOperationRepository;
|
||||
|
||||
private static final int PERCENTILE_5 = 5;
|
||||
|
||||
private static final int PERCENTILE_50 = 50;
|
||||
|
||||
private static final int PERCENTILE_95 = 95;
|
||||
|
||||
public TokenStatisticResponse getTokenStatistics() {
|
||||
Long totalBought = paymentRepository.getTotalTokensBought();
|
||||
Long totalSpent = tokenOperationRepository.getTotalTokensSpent();
|
||||
|
||||
List<Object[]> boughtData = paymentRepository.getTokensBoughtPerUser();
|
||||
List<Long> boughtValues = extractTokenValues(boughtData);
|
||||
|
||||
List<Object[]> spentData = tokenOperationRepository.getTokensSpentPerUser();
|
||||
List<Long> spentValues = extractTokenValues(spentData);
|
||||
|
||||
PercentileCalculator.PercentilesResult boughtPercentiles =
|
||||
PercentileCalculator.calculatePercentiles(boughtValues, PERCENTILE_5, PERCENTILE_50, PERCENTILE_95);
|
||||
PercentileCalculator.PercentilesResult spentPercentiles =
|
||||
PercentileCalculator.calculatePercentiles(spentValues, PERCENTILE_5, PERCENTILE_50, PERCENTILE_95);
|
||||
|
||||
return TokenStatisticResponse.builder()
|
||||
.totalBought(totalBought != null ? totalBought : 0L)
|
||||
.totalSpent(totalSpent != null ? totalSpent : 0L)
|
||||
.boughtPerUser(new TokenStatisticResponse.Percentiles(
|
||||
boughtPercentiles.getP5(),
|
||||
boughtPercentiles.getP50(),
|
||||
boughtPercentiles.getP95()
|
||||
))
|
||||
.spentPerUser(new TokenStatisticResponse.Percentiles(
|
||||
spentPercentiles.getP5(),
|
||||
spentPercentiles.getP50(),
|
||||
spentPercentiles.getP95()
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<Long> extractTokenValues(List<Object[]> data) {
|
||||
return data.stream()
|
||||
.map(row -> row[1] != null ? ((Number) row[1]).longValue() : 0L)
|
||||
.filter(value -> value > 0)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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.UserDynamicStatisticResponse;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class UserDynamicStatisticService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public UserDynamicStatisticResponse getUserDynamicStatistics() {
|
||||
Long totalUsers = userRepository.getTotalUsersCount();
|
||||
Long newLast30Days = userRepository.getNewUsersCount(30);
|
||||
Long totalUpTo60Days = userRepository.getNewUsersCount(60);
|
||||
Long newPrevious30Days = totalUpTo60Days - newLast30Days;
|
||||
Double growthPercent = calculateGrowthPercent(newLast30Days, newPrevious30Days);
|
||||
Long newCurrentMonth = userRepository.getNewUsersCurrentMonth();
|
||||
Long newPreviousMonth = userRepository.getNewUsersPreviousMonth();
|
||||
|
||||
Double monthGrowthPercent = calculateGrowthPercent(newCurrentMonth, newPreviousMonth);
|
||||
|
||||
return UserDynamicStatisticResponse.builder()
|
||||
.totalUsers(totalUsers != null ? totalUsers : 0L)
|
||||
.newLast30Days(newLast30Days != null ? newLast30Days : 0L)
|
||||
.newPrevious30Days(newPrevious30Days != null ? newPrevious30Days : 0L)
|
||||
.growthPercent(growthPercent)
|
||||
.newCurrentMonth(newCurrentMonth != null ? newCurrentMonth : 0L)
|
||||
.newPreviousMonth(newPreviousMonth != null ? newPreviousMonth : 0L)
|
||||
.monthGrowthPercent(monthGrowthPercent)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Double calculateGrowthPercent(Long current, Long previous) {
|
||||
if (previous == null || previous == 0) {
|
||||
return current != null && current > 0 ? 100.0 : 0.0;
|
||||
}
|
||||
|
||||
double percent = ((current - previous) * 100.0) / previous;
|
||||
|
||||
return Math.round(percent * 100.0) / 100.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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.ViolationStatisticResponse;
|
||||
import ru.soune.nocopy.repository.ComplaintEntityRepository;
|
||||
import ru.soune.nocopy.repository.LawCaseRepository;
|
||||
import ru.soune.nocopy.repository.ViolationRepository;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ViolationStatisticService {
|
||||
private final ViolationRepository violationRepository;
|
||||
|
||||
private final ComplaintEntityRepository complaintRepository;
|
||||
|
||||
private final LawCaseRepository lawCaseRepository;
|
||||
|
||||
public ViolationStatisticResponse getViolationStatistics() {
|
||||
Long totalViolations = violationRepository.getTotalViolationsCount();
|
||||
Long totalComplaints = complaintRepository.getTotalComplaintsCount();
|
||||
Long totalLawCases = lawCaseRepository.getTotalLawCasesCount();
|
||||
Long complaintsWithoutCase = complaintRepository.getComplaintsWithoutLawCaseCount();
|
||||
Long violationsWithComplaint = violationRepository.getViolationsWithComplaintCount();
|
||||
|
||||
Long violationsWithoutComplaint = totalViolations - violationsWithComplaint;
|
||||
|
||||
return ViolationStatisticResponse.builder()
|
||||
.totalViolations(totalViolations != null ? totalViolations : 0L)
|
||||
.totalComplaints(totalComplaints != null ? totalComplaints : 0L)
|
||||
.totalLawCases(totalLawCases != null ? totalLawCases : 0L)
|
||||
.complaintsWithoutCase(complaintsWithoutCase != null ? complaintsWithoutCase : 0L)
|
||||
.violationsWithComplaint(violationsWithComplaint != null ? violationsWithComplaint : 0L)
|
||||
.violationsWithoutComplaint(violationsWithoutComplaint)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ import ru.soune.nocopy.entity.tarif.Tariff;
|
||||
import ru.soune.nocopy.entity.tarif.TariffInfo;
|
||||
import ru.soune.nocopy.entity.tarif.TariffStatus;
|
||||
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.exception.TariffNotFoundException;
|
||||
import ru.soune.nocopy.repository.TariffInfoRepository;
|
||||
import ru.soune.nocopy.repository.TariffRepository;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.service.mail.EmailService;
|
||||
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -35,6 +37,8 @@ public class TariffInfoService {
|
||||
|
||||
private EmailService emailService;
|
||||
|
||||
private TokenOperationService tokenOperationService;
|
||||
|
||||
public TariffInfo createTariffInfo(LocalDateTime startTariff, LocalDateTime endTariff, TariffStatus tariffStatus,
|
||||
Integer tokens) {
|
||||
TariffInfo tariffInfo = new TariffInfo();
|
||||
@@ -48,7 +52,7 @@ public class TariffInfoService {
|
||||
}
|
||||
|
||||
// @Transactional
|
||||
public void writeOffTokens(long userId, Integer value) throws MessagingException, IOException {
|
||||
public void writeOffTokens(long userId, Integer value, OperationType operationType) throws MessagingException, IOException {
|
||||
User user = userRepository.findById(userId).orElseThrow();
|
||||
|
||||
TariffInfo activeTariffInfo;
|
||||
@@ -60,6 +64,8 @@ public class TariffInfoService {
|
||||
}
|
||||
|
||||
writeOffTokensForTariff(activeTariffInfo, value);
|
||||
|
||||
tokenOperationService.create(operationType, userId, Long.valueOf(value));
|
||||
}
|
||||
|
||||
public void writeOffTokensForTariff(TariffInfo activeTariffInfo, Integer value) throws MessagingException, IOException {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package ru.soune.nocopy.service.tokenoperation;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.soune.nocopy.entity.tokenoperation.OperationType;
|
||||
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
|
||||
import ru.soune.nocopy.repository.TokenOperationRepository;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class TokenOperationService {
|
||||
|
||||
private final TokenOperationRepository repository;
|
||||
|
||||
@Transactional
|
||||
public TokenOperation create(OperationType operationType, Long userId, Long spent) {
|
||||
TokenOperation tokenOperation = new TokenOperation();
|
||||
|
||||
tokenOperation.setOperationType(operationType);
|
||||
tokenOperation.setUserId(userId);
|
||||
tokenOperation.setSpent(spent);
|
||||
tokenOperation.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
return repository.save(tokenOperation);
|
||||
}
|
||||
|
||||
public TokenOperation findById(Long id) {
|
||||
log.debug("Finding token operation by id: {}", id);
|
||||
return repository.findById(id)
|
||||
.orElseThrow(() -> new EntityNotFoundException("TokenOperation not found with id: " + id));
|
||||
}
|
||||
|
||||
public Page<TokenOperation> findAll(int page, int size, String sortBy, String direction) {
|
||||
log.debug("Finding all operations with pagination: page={}, size={}, sortBy={}, dir={}",
|
||||
page, size, sortBy, direction);
|
||||
|
||||
Sort sort = direction.equalsIgnoreCase("desc")
|
||||
? Sort.by(sortBy).descending()
|
||||
: Sort.by(sortBy).ascending();
|
||||
|
||||
Pageable pageable = PageRequest.of(page, size, sort);
|
||||
return repository.findAll(pageable);
|
||||
}
|
||||
|
||||
public Page<TokenOperation> findByFilters(
|
||||
Long userId,
|
||||
OperationType operationType,
|
||||
Long minSpent,
|
||||
Long maxSpent,
|
||||
int page,
|
||||
int size,
|
||||
String sortBy,
|
||||
String direction) {
|
||||
|
||||
log.debug("Finding by filters: userId={}, type={}, minSpent={}, maxSpent={}",
|
||||
userId, operationType, minSpent, maxSpent);
|
||||
|
||||
Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);
|
||||
Pageable pageable = PageRequest.of(page, size, sort);
|
||||
|
||||
return repository.findByFilters(userId, operationType, minSpent, maxSpent, pageable);
|
||||
}
|
||||
|
||||
public Page<TokenOperation> findByUserId(Long userId, int page, int size) {
|
||||
log.debug("Finding operations for user id: {}", userId);
|
||||
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
|
||||
return repository.findByUserId(userId, pageable);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public TokenOperation update(Long id, TokenOperation updatedOperation) {
|
||||
log.info("Updating token operation with id: {}", id);
|
||||
|
||||
TokenOperation existing = findById(id);
|
||||
|
||||
existing.setOperationType(updatedOperation.getOperationType());
|
||||
existing.setUserId(updatedOperation.getUserId());
|
||||
existing.setSpent(updatedOperation.getSpent());
|
||||
|
||||
return repository.save(existing);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteById(Long id) {
|
||||
log.info("Deleting token operation with id: {}", id);
|
||||
|
||||
if (!repository.existsById(id)) {
|
||||
throw new EntityNotFoundException("TokenOperation not found with id: " + id);
|
||||
}
|
||||
repository.deleteById(id);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteByUserId(Long userId) {
|
||||
log.warn("Deleting all operations for user id: {}", userId);
|
||||
List<TokenOperation> userOps = repository.findByUserId(userId);
|
||||
repository.deleteAll(userOps);
|
||||
}
|
||||
|
||||
public Long getTotalSpentByUserAndType(Long userId, OperationType type) {
|
||||
Long sum = repository.sumSpentByUserAndType(userId, type);
|
||||
return sum != null ? sum : 0L;
|
||||
}
|
||||
|
||||
public Long countByUserId(Long userId) {
|
||||
return repository.countByUserId(userId);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import ru.soune.nocopy.entity.user.moderation.UserVerification;
|
||||
import ru.soune.nocopy.repository.UserRepository;
|
||||
import ru.soune.nocopy.repository.UserVerificationRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@@ -25,13 +26,15 @@ public class UserVerificationService {
|
||||
|
||||
UserVerification userVerification = UserVerification.builder()
|
||||
.moderationStatus(ModerationStatus.VERIFICATION_IN_PROGRESS)
|
||||
.userId(user.getId())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
userVerificationRepository.save(userVerification);
|
||||
|
||||
user.setVerificationStatus(ModerationStatus.VERIFICATION_IN_PROGRESS);
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
userVerificationRepository.save(userVerification);
|
||||
}
|
||||
|
||||
public UserVerification verifyUser(Long userId, String message, Long adminId, boolean verified) {
|
||||
|
||||
@@ -191,7 +191,6 @@ public class ViolationService {
|
||||
}
|
||||
|
||||
Set<String> inProgressStatuses = Set.of(
|
||||
ViolationStatus.SHOWED.name(),
|
||||
ViolationStatus.LEGAL_IN_WORK.name(),
|
||||
ViolationStatus.COMPLAINT_IN_WORK.name(),
|
||||
ViolationStatus.COMPLAINT_AND_LEGAL_IN_WORK.name()
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package ru.soune.nocopy.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UtilityClass
|
||||
public class PercentileCalculator {
|
||||
|
||||
/**
|
||||
* Рассчитать несколько перцентилей за один проход
|
||||
*
|
||||
* @param values список значений
|
||||
* @param percentiles массив перцентилей
|
||||
* @return PercentilesResult с соответствующими значениями
|
||||
*/
|
||||
public PercentilesResult calculatePercentiles(List<Long> values, int... percentiles) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return new PercentilesResult(0L, 0L, 0L);
|
||||
}
|
||||
|
||||
List<Long> sorted = values.stream()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Long p5 = percentiles.length > 0 ? calculatePercentileFromSorted(sorted, percentiles[0]) : 0L;
|
||||
Long p50 = percentiles.length > 1 ? calculatePercentileFromSorted(sorted, percentiles[1]) : 0L;
|
||||
Long p95 = percentiles.length > 2 ? calculatePercentileFromSorted(sorted, percentiles[2]) : 0L;
|
||||
|
||||
return new PercentilesResult(p5, p50, p95);
|
||||
}
|
||||
|
||||
public PercentilesResult calculateP5P50P95(List<Long> values) {
|
||||
return calculatePercentiles(values, 5, 50, 95);
|
||||
}
|
||||
|
||||
private Long calculatePercentileFromSorted(List<Long> sorted, double percentile) {
|
||||
if (sorted.isEmpty()) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
if (percentile <= 0) {
|
||||
return sorted.get(0);
|
||||
}
|
||||
if (percentile >= 100) {
|
||||
return sorted.get(sorted.size() - 1);
|
||||
}
|
||||
|
||||
double index = (percentile / 100.0) * (sorted.size() - 1);
|
||||
int lowerIndex = (int) Math.floor(index);
|
||||
int upperIndex = (int) Math.ceil(index);
|
||||
|
||||
if (lowerIndex == upperIndex) {
|
||||
return sorted.get(lowerIndex);
|
||||
}
|
||||
|
||||
double weight = index - lowerIndex;
|
||||
long lowerValue = sorted.get(lowerIndex);
|
||||
long upperValue = sorted.get(upperIndex);
|
||||
|
||||
return lowerValue + Math.round(weight * (upperValue - lowerValue));
|
||||
}
|
||||
|
||||
public static class PercentilesResult {
|
||||
private final Long p5;
|
||||
private final Long p50;
|
||||
private final Long p95;
|
||||
|
||||
public PercentilesResult(Long p5, Long p50, Long p95) {
|
||||
this.p5 = p5;
|
||||
this.p50 = p50;
|
||||
this.p95 = p95;
|
||||
}
|
||||
|
||||
public Long getP5() { return p5; }
|
||||
public Long getP50() { return p50; }
|
||||
public Long getP95() { return p95; }
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,13 @@ yandex:
|
||||
api-key: ${YANDEX_API_KEY:AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q}
|
||||
folder-id: ${YANDEX_FOLDER_ID:b1gokpdbm6qfpsou8pcd}
|
||||
search-url: ${YANDEX_SEARCH_URL:https://searchapi.api.cloud.yandex.net/v2/image/search_by_image}
|
||||
cloud:
|
||||
key: "YCAJEmHZcWxzf4oGWvETG3mms"
|
||||
secret-key: "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG"
|
||||
region: "ru-central1"
|
||||
s3-endpoint: "https://storage.yandexcloud.net"
|
||||
bucket: "no-copy-storage"
|
||||
|
||||
|
||||
searchapi:
|
||||
api-key: ${SEARCHAPI_API_KEY:5jyYZC8jSaxhZTwjMUhwtAXi}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user