114 Commits
Author SHA1 Message Date
backdev 4dd6c4a3c0 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 16:02:52 +07:00
backdev da84490198 dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 14:23:35 +07:00
backdev b723247b9c dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 14:04:57 +07:00
backdev cc43b92f69 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 02:06:39 +07:00
backdev d92a9a7d86 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 01:50:18 +07:00
backdev 6dec7838ce dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 01:06:19 +07:00
backdev 83ed7c5b01 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 21:11:47 +07:00
backdev e66ae81d18 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 19:34:43 +07:00
backdev 6c3742913c dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 19:09:27 +07:00
backdev dfc9a22bc5 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 18:11:36 +07:00
backdev d0be6580e4 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 11:50:25 +07:00
backdev 1dd6a6b7e2 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 11:01:15 +07:00
backdev c33ffe516a dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:43:13 +07:00
backdev ac7cc54b5e dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:22:42 +07:00
backdev ea7dd5e5df dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:11:31 +07:00
backdev c2a649a252 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 09:26:57 +07:00
backdev f680f766cd dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 09:26:03 +07:00
backdev 82827c9a2a dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 03:45:23 +07:00
backdev e6e8461b9b dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 03:27:47 +07:00
backdev 1b2337ea2f dev fix thumbnal path
Test Workflow / test (push) Has been cancelled
2026-04-21 03:11:52 +07:00
backdev 5fc9563a4e dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:59:21 +07:00
backdev fdc17b8325 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:21:33 +07:00
backdev 3b68b3337c dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:10:17 +07:00
backdev 631066b217 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 01:52:48 +07:00
backdev 61dcada973 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 01:43:40 +07:00
backdev 7492b8aa7f dev fix file public endpoint
Test Workflow / test (push) Has been cancelled
2026-04-21 01:31:30 +07:00
backdev 9ad4830edb dev fix file public endpoint
Test Workflow / test (push) Has been cancelled
2026-04-21 01:06:50 +07:00
backdev 6366cbc96b dev change
Test Workflow / test (push) Has been cancelled
2026-04-21 00:36:22 +07:00
backdev 4e0e953253 dev change
Test Workflow / test (push) Has been cancelled
2026-04-20 19:50:32 +07:00
backdev 1476509b48 dev change
Test Workflow / test (push) Has been cancelled
2026-04-20 18:50:47 +07:00
backdev 9cde6f3bb6 dev change logic for get files for search
Test Workflow / test (push) Has been cancelled
2026-04-20 18:31:32 +07:00
backdev 9cd5965b37 dev change logic for get files for search
Test Workflow / test (push) Has been cancelled
2026-04-20 18:16:46 +07:00
backdev 2ab91dee58 dev fix save to storage
Test Workflow / test (push) Has been cancelled
2026-04-20 14:26:10 +07:00
vladp b0cc5720a6 dev add statistic files protected
Test Workflow / test (push) Successful in 5s
2026-04-16 17:32:49 +07:00
vladp ed89d315ae Merge branch 'dev' into NCBACK-35
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
2026-04-16 16:25:36 +07:00
vladp 74f9be8bf7 dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 01:08:40 +07:00
vladp bbf8e3ee3d dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 00:54:44 +07:00
vladp db3dcb8166 dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 00:37:24 +07:00
vladp 42a5368003 dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 00:28:27 +07:00
vladp be62dd458a dev add statistic files protected
Test Workflow / test (push) Successful in 3s
2026-04-15 18:28:42 +07:00
vladp 2d80b40b27 dev add statistic files protected
Test Workflow / test (push) Successful in 3s
2026-04-15 18:12:32 +07:00
vladp 1b921b525a dev add statistic rest
Test Workflow / test (push) Successful in 2s
2026-04-15 17:46:53 +07:00
vladp b56fb63a37 dev add statistic rest
Test Workflow / test (push) Successful in 2s
2026-04-15 17:41:33 +07:00
vladp bb41898abf dev add statistic rest
Test Workflow / test (push) Successful in 3s
2026-04-15 17:32:07 +07:00
vladp 9974e22abe dev add
Test Workflow / test (push) Successful in 3s
2026-04-15 14:26:46 +07:00
vladp 064a888188 dev add
Test Workflow / test (push) Successful in 3s
2026-04-15 12:49:03 +07:00
vladp 484475eadc dev add nexus in docker-compose
Test Workflow / test (push) Successful in 2s
2026-04-15 10:58:43 +07:00
vladp c60aeec6f5 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 10:25:25 +07:00
vladp 0bb952c4e6 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 10:17:47 +07:00
vladp 11ee4e18ed dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 10:10:46 +07:00
vladp 684888aa2c dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 09:59:38 +07:00
vladp eb43621b74 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 09:51:18 +07:00
vladp dd73227db1 dev add geo country service
Test Workflow / test (push) Successful in 2s
2026-04-15 09:46:29 +07:00
vladp a7939845c7 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 09:34:32 +07:00
vladp 40cb53a3b0 dev update mongodb for ci/cd
Test Workflow / test (push) Successful in 2s
2026-04-14 18:35:21 +07:00
vladp bac45ec738 dev update mongodb for ci/cd
Test Workflow / test (push) Successful in 2s
2026-04-14 18:26:23 +07:00
vladp 01cfc16583 dev add mongodb container
Test Workflow / test (push) Successful in 3s
2026-04-14 17:32:43 +07:00
vladp f71252bb5e dev change mod user status enum
Test Workflow / test (push) Successful in 2s
2026-04-14 16:44:58 +07:00
vladp 934f3459fb dev change mod user status enum
Test Workflow / test (push) Successful in 3s
2026-04-14 12:57:14 +07:00
vladp 75643ae84c dev change sort user all files
Test Workflow / test (push) Successful in 3s
2026-04-14 11:06:07 +07:00
vladp 7df791f9b8 dev change sort user all files
Test Workflow / test (push) Successful in 16s
2026-04-13 19:19:34 +07:00
vladp b518504e58 dev change sort user all files
Test Workflow / test (push) Successful in 5s
2026-04-13 19:09:30 +07:00
vladp 3558588da9 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:47:28 +07:00
vladp 6e8ada2429 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:38:10 +07:00
vladp 64dce8601a dev change sort user all files
Test Workflow / test (push) Successful in 12s
2026-04-13 18:25:33 +07:00
vladp 86fa3b555a dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:20:38 +07:00
vladp e089d370cf dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:03:03 +07:00
vladp 28ddf49af1 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 17:54:46 +07:00
vladp 93946cc2e3 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 17:45:48 +07:00
vladp 671625e0cf dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 17:30:32 +07:00
vladp 40c9b2a796 dev change sort user all files
Test Workflow / test (push) Successful in 5s
2026-04-13 17:16:32 +07:00
vladp b859b175f0 dev change user status after download passports
Test Workflow / test (push) Successful in 4s
2026-04-13 15:41:25 +07:00
vladp d827e2ca16 dev change sort for monitoring
Test Workflow / test (push) Successful in 4s
2026-04-13 15:35:49 +07:00
vladp 1ae2819245 dev change defolt sort
Test Workflow / test (push) Successful in 11s
2026-04-13 15:06:00 +07:00
vladp dbefc21e6d dev add token spent logic
Test Workflow / test (push) Successful in 5s
2026-04-13 15:04:22 +07:00
vladp e32e2d990b dev add token spent logic
Test Workflow / test (push) Successful in 5s
2026-04-13 14:20:13 +07:00
vladp 3896477783 dev add token spent logic
Test Workflow / test (push) Successful in 5s
2026-04-13 14:04:19 +07:00
vladp b5d4509769 dev add token spent logic
Test Workflow / test (push) Waiting to run
2026-04-13 12:15:44 +07:00
vladp aa6c0c1b94 dev add token spent logic
Test Workflow / test (push) Successful in 4s
2026-04-10 20:13:59 +07:00
vladp 31140e810b dev delete async
Test Workflow / test (push) Successful in 3s
2026-04-09 11:03:31 +07:00
vladp aef2c1dfe0 dev add verified notifications
Test Workflow / test (push) Successful in 1m29s
2026-04-08 21:58:28 +07:00
vladp c1360fce87 dev add verified notifications
Test Workflow / test (push) Successful in 6s
2026-04-08 11:31:49 +07:00
vladp 9e4035bf83 dev add violation link
Test Workflow / test (push) Successful in 3s
2026-04-07 18:09:14 +07:00
vladp b5de6c04f7 dev add violation link
Test Workflow / test (push) Successful in 2s
2026-04-07 16:59:30 +07:00
vladp a8a7a78ebf dev add violation link
Test Workflow / test (push) Successful in 2s
2026-04-07 16:43:08 +07:00
vladp 29ed74745f dev add violation link
Test Workflow / test (push) Successful in 4s
2026-04-07 15:35:32 +07:00
vladp 9e81b45922 dev change key
Test Workflow / test (push) Successful in 4s
2026-04-07 14:42:02 +07:00
vladp c7c3bbe07a Revert "dev change key"
Test Workflow / test (push) Successful in 2s
This reverts commit 4fdd14f81d.
2026-04-07 14:26:11 +07:00
vladp 0f71f5d58b Revert "dev change key"
This reverts commit 4ffd830f09.
2026-04-07 14:26:11 +07:00
vladp 4ffd830f09 dev change key
Test Workflow / test (push) Successful in 3s
2026-04-07 13:54:43 +07:00
vladp 4fdd14f81d dev change key
Test Workflow / test (push) Successful in 3s
2026-04-07 13:45:08 +07:00
vladp 8f2127bed5 dev change key
Test Workflow / test (push) Successful in 3s
2026-04-07 12:57:30 +07:00
vladp b71ba527d2 dev change key
Test Workflow / test (push) Successful in 2s
2026-04-07 12:01:25 +07:00
vladp e71ce9bc10 dev add delete method
Test Workflow / test (push) Successful in 2s
2026-04-07 11:48:20 +07:00
vladp d8f6a08a2f dev add delete method
Test Workflow / test (push) Successful in 11s
2026-04-07 10:08:53 +07:00
vladp 077902f4ab dev add lawcase logic
Test Workflow / test (push) Successful in 4s
2026-04-06 14:51:00 +07:00
vladp 5f063354e6 dev add updateTime
Test Workflow / test (push) Successful in 4s
2026-04-06 12:37:49 +07:00
vladp 48a83591da dev add load and download file for moderatuion user
Test Workflow / test (push) Has been cancelled
2026-04-04 18:00:50 +07:00
vladp 724e72c71a dev add load and download file for moderatuion user
Test Workflow / test (push) Waiting to run
2026-04-04 17:38:20 +07:00
vladp f4fc1f26a7 dev add load and download file for moderatuion user
Test Workflow / test (push) Waiting to run
2026-04-04 15:31:51 +07:00
vladp be1ad7f066 dev add notification entity
Test Workflow / test (push) Has been cancelled
2026-04-04 11:26:35 +07:00
vladp 7cd2aa7d32 Merge branch 'dev' into NCBACK-35
# Conflicts:
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
2026-04-04 11:23:05 +07:00
vladp 6bffd5b385 dev add notification entity
Test Workflow / test (push) Waiting to run
2026-04-04 00:52:01 +07:00
vladp 283f0b9e6f dev add notification entity
Test Workflow / test (push) Waiting to run
2026-04-03 15:42:45 +07:00
vladp a9eda37aad dev add notification entity
Test Workflow / test (push) Successful in 5s
2026-04-03 12:29:09 +07:00
vladp ecd42ce263 dev add notification entity
Test Workflow / test (push) Successful in 4s
2026-04-02 12:43:11 +07:00
vladp 9d33c7532a dev add notification entity
Test Workflow / test (push) Successful in 3s
2026-04-02 12:18:47 +07:00
vladp 0f2908b037 dev add notification entity
Test Workflow / test (push) Successful in 4s
2026-04-02 11:57:34 +07:00
vladp 37abdeca9a dev add notification entity
Test Workflow / test (push) Successful in 3s
2026-04-02 11:26:59 +07:00
vladp e87c14198b dev add notification entity
Test Workflow / test (push) Successful in 3s
2026-04-02 11:24:26 +07:00
vladp 8bc95efa2b dev add notification entity
Test Workflow / test (push) Successful in 4s
2026-04-02 11:01:09 +07:00
vladp 051f1ef5d4 dev add notification entity
Test Workflow / test (push) Successful in 2s
2026-04-01 15:32:36 +07:00
vladp 549d248cb7 dev add notification entity
Test Workflow / test (push) Successful in 14s
2026-04-01 11:43:06 +07:00
vladp 9a3a8bde05 dev add notification entity
Test Workflow / test (push) Waiting to run
2026-03-31 15:31:59 +07:00
138 changed files with 4870 additions and 291 deletions
+2 -1
View File
@@ -290,7 +290,7 @@ server_name dev-admin.not-copy.com;
ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem;
location /api/ { location /api/ {
proxy_pass http://localhost:3001; proxy_pass http://localhost:8082;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -323,6 +323,7 @@ server_name dev-workspace.not-copy.com;
ssl_certificate_key /etc/letsencrypt/live/dev-workspace.not-copy.com/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/dev-workspace.not-copy.com/privkey.pem;
location /api/ { location /api/ {
proxy_pass http://localhost:3001; proxy_pass http://localhost:3001;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
+1
View File
@@ -62,6 +62,7 @@ dependencies {
implementation name: 'testlib-fat-0.3.1-all' implementation name: 'testlib-fat-0.3.1-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation 'com.maxmind.geoip2:geoip2:4.2.0'
implementation project(':referral') implementation project(':referral')
+31
View File
@@ -216,6 +216,33 @@ services:
networks: networks:
- app-network - app-network
nexus:
image: sonatype/nexus3:latest
container_name: nexus
restart: unless-stopped
ports:
- "9000:8081"
volumes:
- nexus-data:/nexus-data
environment:
- INSTALL4J_ADD_VM_PARAMS=-Xms512m -Xmx1024m -XX:MaxDirectMemorySize=512m
networks:
- app-network
mongodb:
image: mongo:latest
container_name: mongo_ncp
restart: unless-stopped
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: mongo_db
MONGO_INITDB_ROOT_PASSWORD: mongo_ncp
volumes:
- mongodb_data:/data/db
networks:
- app-network
tempo: tempo:
image: grafana/tempo:2.4.1 image: grafana/tempo:2.4.1
container_name: tempo container_name: tempo
@@ -267,6 +294,8 @@ services:
volumes: volumes:
pgdata: pgdata:
mongodb_data:
name: mongodb_data
artifacts: artifacts:
tempo_data: tempo_data:
loki_data: loki_data:
@@ -275,6 +304,8 @@ volumes:
loki_rules: loki_rules:
uploads_data: uploads_data:
prometheus_data: prometheus_data:
nexus-data:
driver: local
# postfix-data: # postfix-data:
networks: networks:
app-network: app-network:
+23 -8
View File
@@ -86,10 +86,11 @@ pipeline {
passwordVariable: 'SSH_PASS' passwordVariable: 'SSH_PASS'
), ),
string(credentialsId: 'DB_USER', variable: 'DB_USER'), string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD') string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD'),
string(credentialsId: 'MONGO_USER', variable: 'MONGO_USER'),
string(credentialsId: 'MONGO_PASSWORD', variable: 'MONGO_PASSWORD')
]) { ]) {
sh """ sh """
# Тестируем подключение сначала
echo "Testing SSH connection..." echo "Testing SSH connection..."
if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then
echo "SSH connection OK" echo "SSH connection OK"
@@ -98,7 +99,6 @@ pipeline {
exit 1 exit 1
fi fi
# Основная команда
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} " sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT} mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
cd /opt/deployments/${params.BRANCH}-${params.PORT} cd /opt/deployments/${params.BRANCH}-${params.PORT}
@@ -118,6 +118,8 @@ pipeline {
docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден' docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден' docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден' docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker stop mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
docker rm mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
echo '3. Запускаем БД...' echo '3. Запускаем БД...'
docker run -d \\ docker run -d \\
@@ -134,20 +136,31 @@ pipeline {
-e BUILD_TIME_BACK='${buildTime}' \\ -e BUILD_TIME_BACK='${buildTime}' \\
postgres:17.7 postgres:17.7
echo '4. Запускаем storage...' echo '4. Запускаем MongoDB...'
docker run -d \\
--name mongodb-${params.PORT} \\
--network ${params.NETWORK} \\
--network-alias mongodb-${params.PORT} \\
-p ${params.PORT}1:27017 \\
-e MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} \\
-e MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} \\
-v mongodb_data_${params.PORT}:/data/db \\
mongo:7.0
echo '5. Запускаем storage...'
docker run -d \\ docker run -d \\
--name file-storage-${params.PORT} \\ --name file-storage-${params.PORT} \\
--network ${params.NETWORK} \\ --network ${params.NETWORK} \\
-v uploads_data_${params.PORT}:/storage:rw \\ -v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null alpine:latest tail -f /dev/null
echo '5. Ждем БД...' echo '6. Ждем БД...'
sleep 15 sleep 15
echo '6. Собираем приложение...' echo '7. Собираем приложение...'
docker build --no-cache -t app-backend-${params.PORT}:latest . docker build --no-cache -t app-backend-${params.PORT}:latest .
echo '7. Запускаем приложение...' echo '8. Запускаем приложение...'
docker run -d \\ docker run -d \\
--name app-backend-${params.PORT} \\ --name app-backend-${params.PORT} \\
--network ${params.NETWORK} \\ --network ${params.NETWORK} \\
@@ -162,9 +175,11 @@ pipeline {
-e POSTGRES_HOST=no_copy_${params.PORT} \\ -e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\ -e FILE_STORAGE_PATH=/data/uploads \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\ -e SPRING_PROFILES_ACTIVE=${springProfile} \\
-e MONGO_URI=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongodb-${params.PORT}:27017 \\
-e MONGO_DATABASE=no_copy \\
app-backend-${params.PORT}:latest app-backend-${params.PORT}:latest
echo '8. Проверка...' echo '9. Проверка...'
sleep 10 sleep 10
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
@@ -12,21 +12,17 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.util.FileUtil;
import java.util.List; import java.util.Collections;
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@AllArgsConstructor @AllArgsConstructor
public class ApplicationConfig { public class ApplicationConfig {
private final FileEntityRepository fileEntityRepository; // private final FileEntityRepository fileEntityRepository;
private final FileUtil fileUtil; // private final FileUtil fileUtil;
@Bean @Bean
PasswordEncoder passwordEncoder() { PasswordEncoder passwordEncoder() {
@@ -44,11 +40,11 @@ public class ApplicationConfig {
AudioFilePathProvider audioFilePathProvider, AudioFilePathProvider audioFilePathProvider,
DocumentLocalSearch documentLocalSearch) { DocumentLocalSearch documentLocalSearch) {
List<FileProtector.FileInfo> initialFiles = loadProcessingFile(); // List<FileProtector.FileInfo> initialFiles = loadProcessingFile();
//TODO инициализация файлов при создании //TODO инициализация файлов при создании
return new com.vrt.NoCopyFileService( return new com.vrt.NoCopyFileService(
initialFiles, Collections.emptyList(),
fileProvider, fileProvider,
processingListener, processingListener,
imageUniqueCheck, imageUniqueCheck,
@@ -59,12 +55,12 @@ public class ApplicationConfig {
); );
} }
private List<FileProtector.FileInfo> loadProcessingFile() { // private List<FileProtector.FileInfo> loadProcessingFile() {
List<FileEntity> byProtectionStatus = // List<FileEntity> byProtectionStatus =
fileEntityRepository.findByProtectionStatus(ProtectionStatus.PROCESSING); // fileEntityRepository.findByProtectionStatus(ProtectionStatus.PROCESSING);
//
return byProtectionStatus.stream().map(fileEntity -> // return byProtectionStatus.stream().map(fileEntity ->
fileUtil.createFileInfo(fileEntity, null)).toList(); // fileUtil.createFileInfo(fileEntity, null)).toList();
} // }
} }
@@ -34,7 +34,20 @@ public class HandlerConfig {
GlobalSearchHandler globalSearchHandler, GlobalSearchHandler globalSearchHandler,
ViolationNotionHandler violationNotionHandler, ViolationNotionHandler violationNotionHandler,
ComplaintEntityHandler complaintEntityHandler, ComplaintEntityHandler complaintEntityHandler,
UserInfoHandler userInfoHandler UserInfoHandler userInfoHandler,
NotificationHandler notificationHandler,
UserVerificationHandler userVerificationHandler,
LawCaseHandler lawCaseHandler,
TokenOperationHandler tokenOperationHandler,
StatisticUserFilesHandler statisticUserHandler,
StatisticTariffInfoFileHandler statisticTariffInfoFileHandler,
StatisticUserDynamicHandler statisticUserDynamicHandler,
StatisticProtectedFilesHandler statisticProtectedFilesHandler,
StatisticViolationHandler statisticViolationHandler,
StatisticSubscriberHandler statisticSubscriberHandler,
StatisticTokenHandler statisticTokenHandler,
StatisticIncomeHandler statisticIncomeHandler
) { ) {
Map<Integer, RequestHandler> map = new HashMap<>(); Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login); map.put(20001, login);
@@ -60,6 +73,18 @@ public class HandlerConfig {
map.put(30012, violationNotionHandler); map.put(30012, violationNotionHandler);
map.put(30013, complaintEntityHandler); map.put(30013, complaintEntityHandler);
map.put(30014, userInfoHandler); map.put(30014, userInfoHandler);
map.put(30015, notificationHandler);
map.put(30016, userVerificationHandler);
map.put(30017, lawCaseHandler);
map.put(30018, tokenOperationHandler);
map.put(30019, statisticUserHandler);
map.put(30020, statisticTariffInfoFileHandler);
map.put(30021, statisticUserDynamicHandler);
map.put(30022, statisticProtectedFilesHandler);
map.put(30023, statisticViolationHandler);
map.put(30024, statisticSubscriberHandler);
map.put(30025, statisticTokenHandler);
map.put(30026, statisticIncomeHandler);
return map; return map;
} }
@@ -1,5 +1,7 @@
package ru.soune.nocopy.controller; 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.NoCopyFileService;
import com.vrt.fileprotection.FileProtector; import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult; import com.vrt.fileprotection.NoCopyCheckResult;
@@ -8,16 +10,20 @@ import com.vrt.fileprotection.documents.DocumentCheckResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault; import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError; import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; 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.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse; import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.MessageCode;
@@ -26,7 +32,6 @@ import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.company.Company; import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.entity.user.AuthToken; import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
@@ -36,23 +41,22 @@ import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService; import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.file.*;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ProtectionsLimitService; import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService; import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.user.moderation.UserVerificationService;
import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.util.FileUtil; import ru.soune.nocopy.util.FileUtil;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashMap; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -86,6 +90,10 @@ public class ApiController {
private final CloudStorageService cloudStorageService; private final CloudStorageService cloudStorageService;
private final ZipService zipService;
private final UserVerificationService userVerificationService;
@PostMapping("/v{version}/data") @PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request, public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) { @PathVariable("version") int version) {
@@ -174,7 +182,55 @@ public class ApiController {
} }
} }
private final NoCopyFileService noCopyFileService; @PostMapping("/v{version}/private/files/chunk")
public ResponseEntity<BaseResponse> uploadChunk(
@RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "last_file", required = false) Boolean last_file,
@RequestParam(value = "token") String token) {
try {
if (chunk == null || chunk.isEmpty()) {
return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty");
}
if (uploadId == null || uploadId.isBlank()) {
return buildErrorResponse(uploadId, chunkNumber, "Upload Id is required");
}
if (chunkNumber == null || chunkNumber < 0) {
return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required");
}
Long userId = authService.useUserAuthToken(token);
UploadProgressResponse uploadProgressResponse = fileUploadService.uploadPassportChunk(uploadId, chunkNumber,
chunk, userId);
if (last_file) {
zipService.createAndSplitZip(userId,
fileEntityService.getAllUserFiles(userId, List.of(FileStatus.PRIVATE)));
}
return buildSuccessResponse(uploadId, chunkNumber, chunk,
fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath()));
}catch (FileFormatException e){
return buildErrorResponse(uploadId, chunkNumber, e.getMessage());
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
}
}
@GetMapping("/check/{fileId}")
public ResponseEntity<BaseResponse> check(@PathVariable String fileId) {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
File file = new File(fileEntity.getFilePath());
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, FileProtector.Type.IMAGE);
log.info("checkResult: {}", checkResult);
return ResponseEntity.ok(new BaseResponse(1, 1, "1", checkResult));
}
@GetMapping("/v{version}/files/{fileId}/similar") @GetMapping("/v{version}/files/{fileId}/similar")
public ResponseEntity<BaseResponse> findSimilarFiles( public ResponseEntity<BaseResponse> findSimilarFiles(
@@ -446,40 +502,35 @@ public class ApiController {
MessageCode.FILE_DELETE.getDescription(), MessageCode.FILE_DELETE.getDescription(),
errorData)); errorData));
} }
//TODO
if (!entityResponse.isExistsOnDisk()) { // if (!entityResponse.isExistsOnDisk()) {
Map<String, Object> errorData = new HashMap<>(); // Map<String, Object> errorData = new HashMap<>();
errorData.put("onDisk", entityResponse.isExistsOnDisk()); // 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, // if (!file.exists()) {
MessageCode.FILE_NOT_EXIST.getCode(), // Map<String, Object> errorData = new HashMap<>();
MessageCode.FILE_NOT_EXIST.getDescription(), // errorData.put("file", file.exists());
errorData)); //
} // return ResponseEntity.ok().body(new BaseResponse(20004,
// MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
// MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
// errorData));
// }
Path filePath = Paths.get(entityResponse.getProtectedFilePath()); byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
File file = cloudStorageService.readFileFromStorageByPath(
entityResponse.getProtectedFilePath());
long fileSize = Files.size(filePath);
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));
}
String contentType = determineContentType(filePath);
return ResponseEntity.ok() return ResponseEntity.ok()
.contentLength(fileSize) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getFileName() + "\"") "attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(file); .body(bytes);
} catch (FileEntityNotFoundException e) { } catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", fileId); errorData.put("fileId", fileId);
@@ -496,16 +547,16 @@ public class ApiController {
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(), MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
errorData)); errorData));
} catch (IOException e) { // } catch (IOException e) {
Map<String, Object> errorData = new HashMap<>(); // Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader); // errorData.put("token", tokenHeader);
errorData.put("fileId", fileId); // errorData.put("fileId", fileId);
errorData.put("version", version); // errorData.put("version", version);
//
return ResponseEntity.ok().body(new BaseResponse(20004, // return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(), // MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(), // MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
errorData)); // errorData));
} }
} }
@@ -578,13 +629,4 @@ public class ApiController {
return errorDetail; 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; 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.NoCopyFileService;
import com.vrt.fileprotection.FileProtector; import com.vrt.fileprotection.FileProtector;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ru.soune.nocopy.dto.file.CheckStatus; import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.ProtectionStatus;
@@ -17,13 +21,15 @@ import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService; import ru.soune.nocopy.service.file.FileStorageService;
import ru.soune.nocopy.service.file.ImageResizeService; import ru.soune.nocopy.service.file.ImageResizeService;
import ru.soune.nocopy.service.file.ZipService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.util.FileUtil; 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.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -31,42 +37,32 @@ import java.nio.file.Paths;
@RestController @RestController
@RequestMapping("/api/files") @RequestMapping("/api/files")
@Slf4j @Slf4j
@AllArgsConstructor
public class FileController { public class FileController {
@Autowired
private FileStorageService fileStorageService; private FileStorageService fileStorageService;
@Autowired
private FileEntityRepository fileRepository; private FileEntityRepository fileRepository;
@Autowired
private CheckCounterService checkCounterService; private CheckCounterService checkCounterService;
@Autowired
private UserRepository userRepository; private UserRepository userRepository;
@Autowired
private ImageResizeService imageResizeService; private ImageResizeService imageResizeService;
@Autowired
private NoCopyFileService noCopyFileService; private NoCopyFileService noCopyFileService;
@Autowired
private FileUtil fileUtil; private FileUtil fileUtil;
private ZipService zipService;
private CloudStorageService cloudStorageService;
@GetMapping("/public/{fileId}") @GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) { public ResponseEntity<StreamingResponseBody> getPublicFile(@PathVariable String fileId) {
try { try {
FileEntity fileEntity = fileRepository.findById(fileId) FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found: " + fileId)); .orElseThrow(() -> new FileNotFoundException("Not found in DB: " + 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());
MediaType mediaType = getMediaType(fileEntity); MediaType mediaType = getMediaType(fileEntity);
@@ -74,18 +70,28 @@ public class FileController {
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8) .filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build(); .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() return ResponseEntity.ok()
.contentType(mediaType) .contentType(mediaType)
.contentLength(Files.size(filePath))
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600") .header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(resource); .body(responseBody);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.warn("Not on disk: {}", fileId); log.warn("File not found in DB: {}", fileId);
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} catch (IOException e) { } catch (NoSuchKeyException e) {
log.error("Read file exception: {}", fileId, 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(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} }
} }
@@ -116,9 +122,14 @@ public class FileController {
FileEntity fileEntity = fileRepository.findById(fileId) FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found")); .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 || if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) { fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
@@ -131,6 +142,8 @@ public class FileController {
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8) String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20"); .replace("+", "%20");
InputStreamResource resource = new InputStreamResource(cloudStorageService.readFileFromStorage(filePath));
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension())) .contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
@@ -166,4 +179,21 @@ public class FileController {
.ok() .ok()
.body(fileInfo); .body(fileInfo);
} }
@GetMapping("/download/user-passport/archive/{userId}")
public ResponseEntity<byte[]> downloadPassportZip(@PathVariable Long userId) {
try {
byte[] zipBytes = zipService.assembleZipFromSplitFiles(userId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=passport_" + userId + ".zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(zipBytes.length)
.body(zipBytes);
} catch (IOException e) {
log.error("Failed to assemble passport for user {}", userId, e);
return ResponseEntity.internalServerError().build();
}
}
} }
@@ -70,7 +70,7 @@ public class PaymentController {
} }
paymentService.changeAutoRenewal(authService.useUserAuthToken(tokenHeader), renewal); paymentService.changeAutoRenewal(authService.useUserAuthToken(tokenHeader), renewal);
return ResponseEntity.ok(Map.of("message", "Auto-renewal disabled")); return ResponseEntity.ok(Map.of("message", "Auto-renewal changed:" + renewal));
} catch (UserNotFoundException e) { } catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND) return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage())); .body(Map.of("error", e.getMessage()));
@@ -59,7 +59,7 @@ public class UserController {
.map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(), .map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(), u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(), u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
null, null)) null, null, u.getVerificationStatus()))
.toList(); .toList();
return ResponseEntity.ok(allUsers); return ResponseEntity.ok(allUsers);
@@ -79,6 +79,7 @@ public class UserController {
UserDTO userDTO = userMapper.toDTO(user); UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email); userDTO.setEmail(email);
userDTO.setFullName(user.getFullName()); userDTO.setFullName(user.getFullName());
userDTO.setVerifiedStatus(user.getVerificationStatus());
if (user.getCompany() != null) { if (user.getCompany() != null) {
userDTO.setCompany(user.getCompany().getCompanyName()); userDTO.setCompany(user.getCompany().getCompanyName());
@@ -19,6 +19,7 @@ public enum MessageCode {
FILE_DELETE(2, "File was deleted"), FILE_DELETE(2, "File was deleted"),
USER_NOT_HAD_PERMISSION(2, "User not have permission for file"), USER_NOT_HAD_PERMISSION(2, "User not have permission for file"),
USER_NOT_VERIFIED(2, "User not verified"), USER_NOT_VERIFIED(2, "User not verified"),
LAW_CASE_NOT_FOUND(4, "Law case not found"),
PERMISSION_NOT_FOUND(2, "Permission not found"), PERMISSION_NOT_FOUND(2, "Permission not found"),
USER_NOT_FOUND(2, "User not found"), USER_NOT_FOUND(2, "User not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"), FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
@@ -26,8 +27,10 @@ public enum MessageCode {
IMAGE_FOUND_ERROR(2, "Image found error"), IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
INCOMPLETE_UPLOAD(2, "Not load all chunks"), INCOMPLETE_UPLOAD(2, "Not load all chunks"),
USER_NOT_HAVE_TOKEN(2, "Not have tokens"),
MSG_ID_NOT_FOUND(4, "Message id not found"), MSG_ID_NOT_FOUND(4, "Message id not found"),
FILE_ENTITY_ERROR(2, "File entity error"), FILE_ENTITY_ERROR(2, "File entity error"),
NOT_VALID_FIELD(2, "Not valid field"),
ACCESS_DENIED(2, "Access denied"), ACCESS_DENIED(2, "Access denied"),
AUTH_EMAIL_NOT_FOUND(4, "Email not found"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "), AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
@@ -45,6 +48,7 @@ public enum MessageCode {
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"), TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
VALIDATION_ERROR(2, "Validation error"), VALIDATION_ERROR(2, "Validation error"),
RESOURCE_NOT_FOUND(4, "Resource not found"), RESOURCE_NOT_FOUND(4, "Resource not found"),
OPERATION_NOT_FOUND(4, "Operation not found"),
INTERNAL_ERROR(4, "Internal server error"), INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"), COMPANY_NOT_FOUND(4, "Company not found"),
PAYMENT_NOT_FOUND(4, "Payment not found"), PAYMENT_NOT_FOUND(4, "Payment not found"),
@@ -59,7 +63,9 @@ public enum MessageCode {
USER_NOT_ACTIVE(2, "User not active"), USER_NOT_ACTIVE(2, "User not active"),
NOTION_NOT_FOUND(4, "Notion not found"), NOTION_NOT_FOUND(4, "Notion not found"),
NOT_FOUND(4, "Notion not found"), NOT_FOUND(4, "Notion not found"),
MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion"); MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion"),
USER_VERIFICATIONS_NOT_FOUND(4, "Verifications not found"),
ADMIN_USER_NOT_FOUND(4, "Verifications ,user or admin not found");
private final Integer code; private final Integer code;
@@ -0,0 +1,35 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class BaseResponse {
@JsonProperty("status")
private String status;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private Object data;
@JsonProperty("error_code")
private String errorCode;
public static BaseResponse success(Object data) {
return BaseResponse.builder()
.status("SUCCESS")
.data(data)
.build();
}
public static BaseResponse error(String message, String errorCode) {
return BaseResponse.builder()
.status("ERROR")
.message(message)
.errorCode(errorCode)
.build();
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.complaint;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class LawCasePageResponse {
private List<LawCaseResponse> content;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private boolean last;
private boolean first;
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.math.BigDecimal;
@Data
public class LawCaseRequest {
private Long id;
//required
private String name;
//required
private String description;
private BigDecimal amount;
private String priority = LawCasePriority.HIGH.toString();
private String type;
private String lawyer;
private String token;
private Integer pageSize = 5;
private Integer pageNumber = 0;
private String sortBy = "updatedAt";
private String sortDir = "desc";
private String action;
private LawCaseType filterType;
private String filterLawyer;
private LawCasePriority filterPriority;
@JsonProperty("violation_id")
private Long violationId;
}
@@ -0,0 +1,46 @@
package ru.soune.nocopy.dto.complaint;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
public class LawCaseResponse {
private Long id;
private String name;
private String description;
private BigDecimal amount;
private LawCasePriority priority;
private LawCaseType type;
private String lawyer;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private long violationId;
private List<LawCaseResponse> content;
public static LawCaseResponse fromEntity(LawCase lawCase) {
return LawCaseResponse.builder()
.id(lawCase.getId())
.name(lawCase.getName())
.description(lawCase.getDescription())
.amount(lawCase.getAmount())
.priority(lawCase.getPriority())
.type(lawCase.getType())
.lawyer(lawCase.getLawyer())
.createdAt(lawCase.getCreatedAt())
.updatedAt(lawCase.getUpdatedAt())
.violationId(lawCase.getViolationId())
.build();
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.complaint;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class PaginatedResponse<T> {
private List<T> content;
private long totalElements;
private int totalPages;
private int pageNumber;
private int pageSize;
private boolean first;
private boolean last;
}
@@ -0,0 +1,8 @@
package ru.soune.nocopy.dto.notification;
import lombok.Data;
@Data
public class ActivateNotificationRequest {
private String authToken;
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.dto.notification;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class ActiveNotificationsResponse {
private List<NotificationDto> notifications;
private long unreadCount;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.notification;
import lombok.Data;
import java.util.List;
@Data
public class MarkAsReadRequest {
private List<Long> notificationIds;
private String authToken;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.notification;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class MarkAsReadResponse {
private boolean success;
private int updatedCount;
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.dto.notification;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.notification.NotificationStatus;
import ru.soune.nocopy.entity.notification.NotificationType;
import java.time.LocalDateTime;
@Data
@Builder
public class NotificationDto {
private Long id;
private String message;
private NotificationType type;
private NotificationStatus status;
private LocalDateTime createdAt;
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.dto.notification;
import lombok.Data;
import ru.soune.nocopy.entity.notification.NotificationStatus;
import ru.soune.nocopy.entity.notification.NotificationType;
import java.util.List;
@Data
public class NotificationListRequest {
private int page = 0;
private int size = 10;
private String sortBy = "createdAt";
private String sortDirection = "DESC";
private List<NotificationType> types;
private List<NotificationStatus> statuses;
private String authToken;
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.notification;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class NotificationListResponse {
private List<NotificationDto> notifications;
private int currentPage;
private int totalPages;
private long totalElements;
}
@@ -0,0 +1,6 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class IncomeStatisticRequest {
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class IncomeStatisticResponse {
@JsonProperty("total_income")
private Long totalIncome;
@JsonProperty("total_available")
private Long totalAvailable;
@JsonProperty("total_hold")
private Long totalHold;
@JsonProperty("income_per_user")
private Percentiles incomePerUser;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Percentiles {
@JsonProperty("p5")
private Long p5;
@JsonProperty("p50")
private Long p50;
@JsonProperty("p95")
private Long p95;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class ProtectedFilesStatisticRequest {
}
@@ -0,0 +1,66 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProtectedFilesStatisticResponse {
@JsonProperty("total_files")
private Long totalFiles;
@JsonProperty("total_size_bytes")
private Long totalSizeBytes;
@JsonProperty("total_size_gb")
private Double totalSizeGb;
@JsonProperty("files_per_user_percentiles")
private Percentiles filesPerUser;
@JsonProperty("size_per_user_percentiles")
private SizePercentiles sizePerUser;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Percentiles {
@JsonProperty("p5")
private Long p5;
@JsonProperty("p50")
private Long p50;
@JsonProperty("p95")
private Long p95;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class SizePercentiles {
@JsonProperty("p5_bytes")
private Long p5;
@JsonProperty("p50_bytes")
private Long p50;
@JsonProperty("p95_bytes")
private Long p95;
@JsonProperty("p5_mb")
private Double p5Mb;
@JsonProperty("p50_mb")
private Double p50Mb;
@JsonProperty("p95_mb")
private Double p95Mb;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class SubscriberStatisticRequest {
}
@@ -0,0 +1,42 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SubscriberStatisticResponse {
@JsonProperty("monthly")
private PeriodStatistics monthly;
@JsonProperty("yearly")
private PeriodStatistics yearly;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PeriodStatistics {
@JsonProperty("current_active_subscriptions")
private Long currentActiveSubscriptions;
@JsonProperty("first_time_subscriptions")
private Long firstTimeSubscriptions;
@JsonProperty("renewals")
private Long renewals;
@JsonProperty("upgrades")
private Long upgrades;
@JsonProperty("downgrades")
private Long downgrades;
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TariffStatisticFullResponse {
@JsonProperty("tariffs")
private List<TariffStatisticResponse> tariffs;
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class TariffStatisticRequest {
@JsonProperty("company_only")
private Boolean companyOnly = false;
@JsonProperty("active_only")
private Boolean activeOnly = true;
}
@@ -0,0 +1,21 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TariffStatisticResponse {
@JsonProperty("tariff_name")
private String tariffName;
@JsonProperty("user_count")
private Long userCount;
@JsonProperty("usage_percent")
private Double usagePercent;
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class TokenStatisticRequest {
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TokenStatisticResponse {
@JsonProperty("total_bought")
private Long totalBought;
@JsonProperty("total_spent")
private Long totalSpent;
@JsonProperty("bought_per_user")
private Percentiles boughtPerUser;
@JsonProperty("spent_per_user")
private Percentiles spentPerUser;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Percentiles {
@JsonProperty("p5")
private Long p5;
@JsonProperty("p50")
private Long p50;
@JsonProperty("p95")
private Long p95;
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class UserDynamicStatisticRequest {
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDynamicStatisticResponse {
@JsonProperty("total_users")
private Long totalUsers;
@JsonProperty("new_last_30_days")
private Long newLast30Days;
@JsonProperty("new_previous_30_days")
private Long newPrevious30Days;
@JsonProperty("growth_percent")
private Double growthPercent;
@JsonProperty("new_current_month")
private Long newCurrentMonth;
@JsonProperty("new_previous_month")
private Long newPreviousMonth;
@JsonProperty("month_growth_percent")
private Double monthGrowthPercent;
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserFilesStatisticRequest {
@JsonProperty("top")
int topUsers;
@JsonProperty("type")
String fileMimeType;
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserFilesStatisticResponse {
@JsonProperty("full_name")
private final String userName;
@JsonProperty("user_id")
private final long userId;
@JsonProperty("count")
private final int countFiles;
@JsonProperty("files_size")
private final long fileSize;
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.dto.statistic;
import lombok.Data;
@Data
public class ViolationStatisticRequest {
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.dto.statistic;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ViolationStatisticResponse {
@JsonProperty("total_violations")
private Long totalViolations;
@JsonProperty("total_complaints")
private Long totalComplaints;
@JsonProperty("total_law_cases")
private Long totalLawCases;
@JsonProperty("complaints_without_case")
private Long complaintsWithoutCase;
@JsonProperty("violations_with_complaint")
private Long violationsWithComplaint;
@JsonProperty("violations_without_complaint")
private Long violationsWithoutComplaint;
}
@@ -0,0 +1,34 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import java.util.List;
@Data
@Builder
public class TokenOperationPageDto {
@JsonProperty("content")
private List<TokenOperation> content;
@JsonProperty("page_number")
private int pageNumber;
@JsonProperty("page_size")
private int pageSize;
@JsonProperty("total_elements")
private long totalElements;
@JsonProperty("total_pages")
private int totalPages;
@JsonProperty("is_last")
private boolean last;
@JsonProperty("is_first")
private boolean first;
}
@@ -0,0 +1,46 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
@Data
public class TokenOperationRequest {
@JsonProperty("action")
private String action;
@JsonProperty("token")
private String token;
@JsonProperty("operation_id")
private Long operationId;
@JsonProperty("operation_type")
private OperationType operationType;
@JsonProperty("spent")
private Long spent;
@JsonProperty("page")
private Integer page = 0;
@JsonProperty("size")
private Integer size = 20;
@JsonProperty("sort_by")
private String sortBy = "createdAt";
@JsonProperty("sort_direction")
private String sortDirection = "desc";
@JsonProperty("min_spent")
private Long minSpent;
@JsonProperty("max_spent")
private Long maxSpent;
@JsonProperty("updates")
private Object updates;
}
@@ -0,0 +1,37 @@
package ru.soune.nocopy.dto.tokenoperation;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TokenOperationResponse {
@JsonProperty("success")
private Boolean success;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private Object data;
@JsonProperty("total_elements")
private Long totalElements;
@JsonProperty("total_pages")
private Integer totalPages;
@JsonProperty("current_page")
private Integer currentPage;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("statistics")
private Map<String, Object> statistics;
}
@@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
import ru.soune.nocopy.dto.tarriff.TariffDTO; import ru.soune.nocopy.dto.tarriff.TariffDTO;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO; import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.entity.user.GenderType; import ru.soune.nocopy.entity.user.GenderType;
import ru.soune.nocopy.entity.user.ModerationStatus;
import ru.soune.nocopy.entity.user.SubscriptionType; import ru.soune.nocopy.entity.user.SubscriptionType;
import java.time.LocalDate; import java.time.LocalDate;
@@ -30,4 +31,5 @@ public class UserDTO {
private List<TariffDTO> tariffs; private List<TariffDTO> tariffs;
private TariffInfoDTO tariffInfo; private TariffInfoDTO tariffInfo;
private Long permission; private Long permission;
private ModerationStatus verifiedStatus;
} }
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.user.moderation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class UserVerificationRequest {
@JsonProperty("message")
private String message;
@JsonProperty("user_id")
private Long userId;
@JsonProperty("verified")
private Boolean verified;
@JsonProperty("admin_id")
private Long adminId;
@JsonProperty("action")
private String action;
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.user.moderation;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class UserVerificationResponse {
private Long userVerificationId;
private Long userId;
private LocalDateTime updateTime;
private String moderationStatus;
}
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import ru.soune.nocopy.entity.violation.Violation; import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.service.geo.GeoCountryService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -12,6 +13,8 @@ import java.util.List;
@Builder @Builder
public class ViolationResponse { public class ViolationResponse {
private final GeoCountryService geoCountryService;
@JsonProperty("violations") @JsonProperty("violations")
private List<ViolationDto> violations; private List<ViolationDto> violations;
@@ -58,6 +61,12 @@ public class ViolationResponse {
@JsonProperty("created_date") @JsonProperty("created_date")
private LocalDateTime createdDate; private LocalDateTime createdDate;
@JsonProperty("country")
private String country;
@JsonProperty("country_code")
private String countryCode;
@JsonProperty("file_id") @JsonProperty("file_id")
private String fileId; private String fileId;
@@ -39,12 +39,6 @@ public class ComplaintEntity {
@JsonIgnore @JsonIgnore
private Violation violation; private Violation violation;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "law_case_id")
@ToString.Exclude
@JsonIgnore
private LawCase lawCase;
@Column(name = "created_at", updatable = false) @Column(name = "created_at", updatable = false)
@CreatedDate @CreatedDate
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -55,4 +49,12 @@ public class ComplaintEntity {
@Column(name = "not_moderated_file") @Column(name = "not_moderated_file")
private Boolean not_moderated_file; private Boolean not_moderated_file;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -2,6 +2,12 @@ package ru.soune.nocopy.entity.complaint;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import ru.soune.nocopy.entity.user.User;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity @Entity
@AllArgsConstructor @AllArgsConstructor
@@ -14,4 +20,48 @@ public class LawCase {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "name")
private String name;
@Column(length = 500, name = "description")
private String description;
@Column(name = "amount")
private BigDecimal amount;
@Column(name = "priority")
@Enumerated(EnumType.STRING)
private LawCasePriority priority;
@Column(name = "type")
@Enumerated(EnumType.STRING)
private LawCaseType type = LawCaseType.ACTIVE;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "lawyer")
private String lawyer;
@Column(name = "violation_id")
private Long violationId;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -0,0 +1,12 @@
package ru.soune.nocopy.entity.complaint;
public enum LawCasePriority {
CRITICAL("critical"), HIGH("high"),
MIDDLE("middle"), LOW("low");
private String name;
private LawCasePriority(String name) {
this.name = name;
}
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.entity.complaint;
import lombok.Getter;
@Getter
public enum LawCaseType {
ACTIVE("active"), CLOSED("closed"),
COMPLETED("completed");
private String name;
private LawCaseType(String name) {
this.name = name;
}
}
@@ -6,8 +6,10 @@ import lombok.*;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Data @Data
@@ -10,5 +10,6 @@ public enum FileStatus {
TEMP, TEMP,
REMOVED, REMOVED,
MODERATION, MODERATION,
PRIVATE,
BLOCKED BLOCKED
} }
@@ -0,0 +1,28 @@
package ru.soune.nocopy.entity.file.moderation;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Builder
@Table(name = "moderation_file")
@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
public class ModerationPassportFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "path", nullable = false)
private String path;
@Column(name = "part")
private Integer part;
private String status;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.entity.file.moderation;
import lombok.Getter;
@Getter
public enum PassportModerationFileStatus {
DOWNLOAD("download"),
ON_MODERATION("on_moderation"),
MODERATED("moderated");
private final String name;
PassportModerationFileStatus(String name) {
this.name = name;
}
}
@@ -1,6 +1,14 @@
package ru.soune.nocopy.entity.monitoring; package ru.soune.nocopy.entity.monitoring;
import lombok.Getter;
@Getter
public enum MonitoringType { public enum MonitoringType {
MONITORING_DAILY, MONITORING_WEEKLY, MONITORING_MONTHLY, NONE MONITORING_DAILY(1), MONITORING_WEEKLY(7), MONITORING_MONTHLY(30), NONE(0);
private final int days;
MonitoringType(int days) {
this.days = days;
}
} }
@@ -0,0 +1,58 @@
package ru.soune.nocopy.entity.notification;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
@Builder
@Table(name = "notifications", indexes = {
@Index(name = "idx_user_status", columnList = "user_id, status"),
@Index(name = "idx_user_created", columnList = "user_id, created_at DESC")
})
public class Notification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "created_at", updatable = false)
@CreatedDate
private LocalDateTime createdAt = LocalDateTime.now();
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "message")
private String message;
@Enumerated(EnumType.STRING)
@Column(name = "notification_type")
private NotificationType notificationType;
@Column(name = "status")
private NotificationStatus status = NotificationStatus.NEW;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
@JsonIgnore
private User user;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.entity.notification;
import lombok.Getter;
@Getter
public enum NotificationMessage {
FILE_SEARCH("notification-file-search");
private final String messageKey;
NotificationMessage(String messageKey) {
this.messageKey = messageKey;
}
}
@@ -0,0 +1,5 @@
package ru.soune.nocopy.entity.notification;
public enum NotificationStatus {
NEW, READIED
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.entity.notification;
import lombok.Getter;
@Getter
public enum NotificationType {
SEARCH_RESULT("notification-search-result"),
MONITORING_RESULT("notification-monitoring-result"),
OPERATION_IMPOSSIBLE("notification-operation-impossible"),
START_FAILED_NOW("notification-start-failed-now"),
START_FAILED_NEXT("notification-start-failed-next"),
PAYOUT_RESULT("notification-payout-result"),
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"),
INCOMING_MESSAGE("notification-incoming-message"),
TARIFF_EXPIRING("notification-tariff-expiring"),
TOKEN_NOT_FOUND("notification-token-not-found"),
FILE_MODERATION_EVENT("notification-file-moderation"),
FILE_ADDED_TO_SYSTEM("notification-file-added"),
SEARCH_OPERATION_FAILED("notification-search-operation-filed");
private final String messageKey;
NotificationType(String messageKey) {
this.messageKey = messageKey;
}
}
@@ -61,4 +61,13 @@ public class Payment {
@JoinColumn(name = "tariff_id") @JoinColumn(name = "tariff_id")
@JsonIgnore @JsonIgnore
private Tariff tariff; private Tariff tariff;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -20,6 +20,7 @@ public class TariffInfo {
private String id; private String id;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
private TariffStatus status; private TariffStatus status;
@Column(name = "start_tariff") @Column(name = "start_tariff")
@@ -2,14 +2,21 @@ package ru.soune.nocopy.entity.tarif;
import lombok.Getter; import lombok.Getter;
import java.util.List;
@Getter @Getter
public enum TariffTimeTerm { public enum TariffTimeTerm {
MONTHLY(30),YEAR(365); MONTHLY(30, "MONTHLY"),YEAR(365, "YEAR");
private final Integer days; private final Integer days;
private final String name;
TariffTimeTerm(Integer days) { TariffTimeTerm(Integer days, String name) {
this.days = days; this.days = days;
this.name = name;
} }
public static List<String> validTerms() {
return List.of(MONTHLY.name(), YEAR.name());
}
} }
@@ -0,0 +1,12 @@
package ru.soune.nocopy.entity.tokenoperation;
public enum OperationType {
SEARCH("search"), MONITORING("monitoring"), CREATE_LEGAL_CASE("legal_case"),
FILE_UPLOAD("file_upload"), GLOBAL_SEARCH("global_search");
private final String name;
private OperationType(String name) {
this.name = name;
}
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.entity.tokenoperation;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Table(name = "token_operation")
@Entity
@Setter @Getter
@NoArgsConstructor
public class TokenOperation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
@Column(name = "operation_type")
private OperationType operationType;
@Column(name = "user_id")
private Long userId;
@Column(name = "spent")
private Long spent;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.entity.user;
import lombok.Getter;
@Getter
public enum ModerationStatus {
NOT_VERIFIED("not_verified") , VERIFICATION_IN_PROGRESS("verification_in_progress"),
VERIFIED("verified"), VERIFICATION_FAILED("verification_failed");
private final String value;
private ModerationStatus(String value) {
this.value = value;
}
}
@@ -106,6 +106,10 @@ public class User {
@ToString.Exclude @ToString.Exclude
private UserNotActive userNotActive; private UserNotActive userNotActive;
@Column(name = "verification_status")
@Enumerated(EnumType.STRING)
private ModerationStatus verificationStatus = ModerationStatus.NOT_VERIFIED;
public TariffInfo getActiveTariffInfo() { public TariffInfo getActiveTariffInfo() {
if (company != null && company.getTariffInfo() != null) { if (company != null && company.getTariffInfo() != null) {
return company.getTariffInfo(); return company.getTariffInfo();
@@ -0,0 +1,52 @@
package ru.soune.nocopy.entity.user.moderation;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import ru.soune.nocopy.entity.user.ModerationStatus;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_verification")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class UserVerification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", unique = true, nullable = false)
private Long userId;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "message")
private String message;
@Column(name = "admin_id")
private Long adminId;
@Enumerated(EnumType.STRING)
@Column(name = "status")
private ModerationStatus moderationStatus;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
}
@@ -43,4 +43,12 @@ public class ViolationNotion {
@Column(name = "message") @Column(name = "message")
private String message; private String message;
@PrePersist
public void prePersist() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
this.updatedAt = LocalDateTime.now();
}
} }
@@ -77,10 +77,9 @@ public class ComplaintEntityHandler implements RequestHandler {
private BaseResponse handleGetAll(Integer msgId, ComplaintRequest req) { private BaseResponse handleGetAll(Integer msgId, ComplaintRequest req) {
Pageable pageable = PageRequest.of( Pageable pageable = PageRequest.of(
req.getPage() != null ? req.getPage() : 0, req.getPage() != null ? req.getPage() : 0,
req.getSize() != null ? req.getSize() : 20, req.getSize() != null ? req.getSize() : 5,
Sort.by(Sort.Direction.fromString(req.getSortDirection() != null ? req.getSortDirection() : "desc"), Sort.by(Sort.Direction.fromString(req.getSortDirection() != null ? req.getSortDirection() : "desc"),
req.getSortBy() != null ? req.getSortBy() : "createdAt") req.getSortBy() != null ? req.getSortBy() : "updatedAt"));
);
var page = complaintService.getAllComplaints(pageable); var page = complaintService.getAllComplaints(pageable);
Map<String, Object> data = new HashMap<>(); Map<String, Object> data = new HashMap<>();
@@ -4,6 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.*; import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.file.*; import ru.soune.nocopy.dto.file.*;
@@ -225,9 +228,55 @@ public class FileEntityHandler implements RequestHandler {
return new BaseResponse(request.getMsgId(), return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), MessageCode.SUCCESS.getDescription(),
FileEntityResponse.builder().status(newStatus).build()); Map.of("file_status", newStatus, "file_id", fileId));
} }
// private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
// try {
// Long userId = authService.useUserAuthToken(fileRequest.getToken());
// int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
// int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
// String fileRequestSortBy = fileRequest.getSortBy();
// String sortBy = !fileRequestSortBy.equals("") ? fileRequest.getSortBy(): "createdAt";
// sortBy = sortBy.equals("fileName") ? "originalFileName" : sortBy;
//
// String sortOrder = fileRequest.getSortOrder();
// Sort.Direction direction = (sortOrder != null && sortOrder.equalsIgnoreCase("desc"))
// ? Sort.Direction.DESC
// : Sort.Direction.ASC;
// Sort sort = Sort.by(direction, sortBy);
//
// Pageable pageable = PageRequest.of(page, pageSize, sort);
// FileResponse userFiles = fileEntityService.getUserFiles(userId, pageable, request.getVersion());
//
// FileListResponse response = FileListResponse.builder()
// .files(userFiles.getFiles())
// .totalCount(userFiles.getTotalCount())
// .totalSize(userFiles.getTotalSize())
// .formattedTotalSize(fileEntityService.formatFileSize(userFiles.getTotalSize()))
// .page(page)
// .pageSize(pageSize)
// .sortBy(sortBy)
// .sortOrder(sortOrder)
// .build();
//
// return new BaseResponse(request.getMsgId(),
// MessageCode.SUCCESS.getCode(),
// MessageCode.SUCCESS.getDescription(),
// response);
//
// } catch (NotFoundAuthToken e) {
// return new BaseResponse(request.getMsgId(),
// MessageCode.INVALID_TOKEN.getCode(),
// "Authentication required", null);
// } catch (Exception e) {
// log.error("Error searching files", e);
// return new BaseResponse(request.getMsgId(),
// MessageCode.FILE_UPLOAD_ERROR.getCode(),
// "Failed to search files: " + e.getMessage(), null);
// }
// }
private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) { private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
try { try {
Long userId = authService.useUserAuthToken(fileRequest.getToken()); Long userId = authService.useUserAuthToken(fileRequest.getToken());
@@ -292,6 +341,73 @@ public class FileEntityHandler implements RequestHandler {
} }
} }
/**
*
* private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
* try {
* Long userId = authService.useUserAuthToken(fileRequest.getToken());
* int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
* int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;
*
* String sortBy = fileRequest.getSortBy() != null ? fileRequest.getSortBy() : "fileName";
* SortOrder sortOrder = SortOrder.fromString(fileRequest.getSortOrder());
*
* FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion());
*
* String searchQuery = fileRequest.getQuery() != null ? fileRequest.getQuery().toLowerCase().trim() : "";
* String[] searchTerms = searchQuery.split("\\s+");
* String filterType = fileRequest.getType();
* String dateFilter = fileRequest.getDateFilter();
*
* List<FileEntityResponse> filteredFiles = allFiles.getFiles().stream()
* .filter(f -> matchesSearch(f, searchTerms, searchQuery, filterType))
* .filter(f -> matchesDateFilter(f, dateFilter))
* .collect(Collectors.toList());
*
* Comparator<FileEntityResponse> comparator = getComparator(sortBy, sortOrder);
* filteredFiles.sort(comparator);
*
* int start = (page - 1) * pageSize;
* int end = Math.min(start + pageSize, filteredFiles.size());
*
* if (start >= filteredFiles.size()) {
* return createEmptyResponse(request, page, pageSize, sortBy, sortOrder.getValue());
* }
*
* List<FileEntityResponse> paginatedFiles = filteredFiles.subList(start, end);
* long totalSize = paginatedFiles.stream()
* .mapToLong(FileEntityResponse::getFileSize)
* .sum();
*
* FileListResponse response = FileListResponse.builder()
* .files(paginatedFiles)
* .totalCount(filteredFiles.size())
* .totalSize(totalSize)
* .formattedTotalSize(fileEntityService.formatFileSize(totalSize))
* .page(page)
* .pageSize(pageSize)
* .sortBy(sortBy)
* .sortOrder(sortOrder.getValue())
* .build();
*
* return new BaseResponse(request.getMsgId(),
* MessageCode.SUCCESS.getCode(),
* MessageCode.SUCCESS.getDescription(),
* response);
*
* } catch (NotFoundAuthToken e) {
* return new BaseResponse(request.getMsgId(),
* MessageCode.INVALID_TOKEN.getCode(),
* "Authentication required", null);
* } catch (Exception e) {
* log.error("Error searching files", e);
* return new BaseResponse(request.getMsgId(),
* MessageCode.FILE_UPLOAD_ERROR.getCode(),
* "Failed to search files: " + e.getMessage(), null);
* }
* }
*/
private boolean matchesDateFilter(FileEntityResponse file, String dateFilter) { private boolean matchesDateFilter(FileEntityResponse file, String dateFilter) {
if (dateFilter == null || dateFilter.isEmpty()) { if (dateFilter == null || dateFilter.isEmpty()) {
return true; return true;
@@ -423,6 +539,12 @@ public class FileEntityHandler implements RequestHandler {
Comparator.nullsLast(Comparator.naturalOrder()) Comparator.nullsLast(Comparator.naturalOrder())
); );
break; break;
case "monitoring":
comparator = Comparator.comparing(
FileEntityResponse::getMonitoring,
Comparator.nullsLast(Comparator.naturalOrder())
);
break;
default: default:
comparator = Comparator.comparing( comparator = Comparator.comparing(
@@ -11,6 +11,7 @@ import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ImageSearchRequest; import ru.soune.nocopy.dto.file.ImageSearchRequest;
import ru.soune.nocopy.dto.file.YandexSearchResponse; import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
@@ -117,7 +118,8 @@ public class ImageFoundRequestHandler implements RequestHandler {
log.info("Results only from Yandex: {} images", allUniqueImages.size()); log.info("Results only from Yandex: {} images", allUniqueImages.size());
} }
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH); tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH,
OperationType.SEARCH);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType()); checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1; int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
@@ -0,0 +1,200 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.complaint.LawCaseRequest;
import ru.soune.nocopy.dto.complaint.LawCaseResponse;
import ru.soune.nocopy.dto.complaint.PaginatedResponse;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.complaint.LawCaseService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffConstants;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class LawCaseHandler implements RequestHandler {
private final LawCaseService lawCaseService;
private final AuthService authService;
private final ObjectMapper objectMapper;
private final UserRepository userRepository;
private final TariffInfoService tariffInfoService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
LawCaseRequest lawCaseRequest = objectMapper.convertValue(request.getMessageBody(),
LawCaseRequest.class);
Long userId = authService.useUserAuthToken(lawCaseRequest.getToken());
String action = lawCaseRequest.getAction();
return switch (action) {
case "get_all" -> getAllUserLawCases(userId, lawCaseRequest, request);
case "get_byId" -> getLawCaseById(lawCaseRequest.getId(), request);
case "create" -> createLawCase(userId, lawCaseRequest, request);
case "update_priority" -> updatePriority(lawCaseRequest, request);
case "update_type" -> updateType(lawCaseRequest, request);
case "update_amount" -> updateAmount(lawCaseRequest, request);
case "delete" -> deleteLawCase(lawCaseRequest.getId(), request);
default -> new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("actions", "get_all, get_byId, create, update_priority, update_type, " +
"update_amount, delete")
);
};
}
private BaseResponse getAllUserLawCases(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
Page<LawCase> lawCases = lawCaseService.getAllUserLawCases(
userId,
lawCaseRequest.getPageSize(),
lawCaseRequest.getPageNumber(),
lawCaseRequest.getFilterType(),
lawCaseRequest.getFilterLawyer(),
lawCaseRequest.getFilterPriority(),
lawCaseRequest.getViolationId(),
lawCaseRequest.getSortBy(),
lawCaseRequest.getSortDir());
List<LawCaseResponse> responses = lawCases.getContent().stream()
.map(LawCaseResponse::fromEntity)
.collect(Collectors.toList());
PaginatedResponse<LawCaseResponse> paginatedResponse = new PaginatedResponse<>(
responses,
lawCases.getTotalElements(),
lawCases.getTotalPages(),
lawCases.getNumber(),
lawCases.getSize(),
lawCases.isFirst(),
lawCases.isLast());
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), paginatedResponse);
}
private BaseResponse getLawCaseById(Long id, BaseRequest request) {
LawCase lawCase = lawCaseService.getById(id);
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", id));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse createLawCase(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
try {
tariffInfoService.writeOffTokens(userId, TariffConstants.LEGAL_COST, OperationType.CREATE_LEGAL_CASE);
} catch (TariffNotFoundException e) {
return new BaseResponse(request.getMsgId(), MessageCode.USER_NOT_HAVE_TOKEN.getCode(),
MessageCode.USER_NOT_HAVE_TOKEN.getDescription(),
Map.of("error", "Tariff not found", "message", e.getMessage()));
} catch (MessagingException | IOException e) {
return new BaseResponse(request.getMsgId(), MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
validateRequiredParameters(lawCaseRequest, request);
User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("User not found"));
LawCase lawCase = lawCaseService.addLawCase(
lawCaseRequest.getAmount(),
lawCaseRequest.getDescription(),
lawCaseRequest.getName(),
LawCasePriority.valueOf(lawCaseRequest.getPriority()),
lawCaseRequest.getViolationId(), user);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse updatePriority(LawCaseRequest lawCaseRequest, BaseRequest request) {
LawCase lawCase = lawCaseService.changePriority(
lawCaseRequest.getId(),
LawCasePriority.valueOf(lawCaseRequest.getPriority()));
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse updateType(LawCaseRequest lawCaseRequest, BaseRequest request) {
LawCase lawCase = lawCaseService.changeStatus(
lawCaseRequest.getId(),
LawCaseType.valueOf(lawCaseRequest.getType()));
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse updateAmount(LawCaseRequest lawCaseRequest, BaseRequest request) {
LawCase lawCase = lawCaseService.changeDamage(
lawCaseRequest.getId(),
lawCaseRequest.getAmount());
if (lawCase == null) {
return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
}
private BaseResponse deleteLawCase(Long id, BaseRequest request) {
lawCaseService.deleteById(id);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), Map.of("id", id));
}
private void validateRequiredParameters(LawCaseRequest lawCaseRequest, BaseRequest request) {
if (lawCaseRequest.getName() == null) throw new NotValidFieldException("Name is required",
new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
MessageCode.NOTION_NOT_FOUND.getDescription(), Map.of("fields", "name")));
if (lawCaseRequest.getDescription() == null) throw new NotValidFieldException("Description is required",
new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
MessageCode.NOTION_NOT_FOUND.getDescription(), Map.of("fields", "description")));
}
}
@@ -0,0 +1,164 @@
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.file.ActionResponse;
import ru.soune.nocopy.service.notification.NotificationService;
import com.fasterxml.jackson.databind.type.TypeFactory;
import ru.soune.nocopy.dto.notification.*;
import ru.soune.nocopy.entity.notification.Notification;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.service.register.AuthService;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class NotificationHandler implements RequestHandler {
private final NotificationService notificationService;
private final ObjectMapper objectMapper;
private final AuthService authService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
Map<String, Object> bodyMap = objectMapper.convertValue(request.getMessageBody(),
TypeFactory.defaultInstance().constructMapType(Map.class, String.class, Object.class));
String action = (String) bodyMap.get("action");
if (action == null) {
ActionResponse response = ActionResponse.builder()
.action(action)
.availableActions(Arrays.asList("list", "active", "markRead"))
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_FIELD.getCode(),
"Action is required",
response);
}
switch (action) {
case "list": {
NotificationListRequest listRequest = objectMapper.convertValue(request.getMessageBody(),
NotificationListRequest.class);
User user = authService.getUser(listRequest.getAuthToken());
Page<Notification> page = notificationService.getAllNotifications(
user,
listRequest.getPage(),
listRequest.getSize(),
listRequest.getSortBy(),
listRequest.getSortDirection(),
listRequest.getTypes(),
listRequest.getStatuses());
List<NotificationDto> dtos = page.getContent().stream()
.map(this::toDto)
.collect(Collectors.toList());
NotificationListResponse response = NotificationListResponse.builder()
.notifications(dtos)
.currentPage(page.getNumber())
.totalPages(page.getTotalPages())
.totalElements(page.getTotalElements())
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
}
case "active": {
ActivateNotificationRequest activateRequest = objectMapper.convertValue(request.getMessageBody(),
ActivateNotificationRequest.class);
User user = authService.getUser(activateRequest.getAuthToken());
List<Notification> recent = notificationService.getActiveNotifications(user);
long unreadCount = notificationService.getUnreadCount(user);
ActiveNotificationsResponse response = ActiveNotificationsResponse.builder()
.notifications(recent.stream().map(this::toDto).collect(Collectors.toList()))
.unreadCount(unreadCount)
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
}
case "markRead": {
MarkAsReadRequest markRequest = objectMapper.convertValue(request.getMessageBody(),
MarkAsReadRequest.class);
User user = authService.getUser(markRequest.getAuthToken());
int updated = notificationService.markAsRead(user, markRequest.getNotificationIds());
MarkAsReadResponse response = MarkAsReadResponse.builder()
.success(true)
.updatedCount(updated)
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
}
case "delete": {
MarkAsReadRequest markRequest = objectMapper.convertValue(request.getMessageBody(),
MarkAsReadRequest.class);
int delete = notificationService.delete(markRequest.getNotificationIds());
MarkAsReadResponse response = MarkAsReadResponse.builder()
.success(true)
.updatedCount(delete)
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
response);
}
default:
ActionResponse response = ActionResponse.builder()
.action(action)
.availableActions(Arrays.asList("list", "active", "markRead", "delete"))
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
"Invalid action: " + action,
response);
}
}
private NotificationDto toDto(Notification notification) {
return NotificationDto.builder()
.id(notification.getId())
.message(notification.getMessage())
.type(notification.getNotificationType())
.status(notification.getStatus())
.createdAt(notification.getCreatedAt())
.build();
}
}
@@ -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
);
}
}
@@ -123,6 +123,16 @@ public class TariffHandler implements RequestHandler {
private BaseResponse handleGetTariffs(BaseRequest request, TariffRequest tariffRequest) { private BaseResponse handleGetTariffs(BaseRequest request, TariffRequest tariffRequest) {
AuthToken authToken = authTokenRepository.findByToken(tariffRequest.getUserToken()).orElseThrow(); AuthToken authToken = authTokenRepository.findByToken(tariffRequest.getUserToken()).orElseThrow();
User user = authToken.getUser(); User user = authToken.getUser();
if (tariffRequest.getTariffTerm() == null || !TariffTimeTerm.validTerms().contains(
tariffRequest.getTariffTerm())) {
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Not valid tariff_term",
Map.of("Valid term", TariffTimeTerm.validTerms()));
}
List<TariffDTO> allTariffs = tariffService.getTariffByAccountTypeAndTariffTerm(user, List<TariffDTO> allTariffs = tariffService.getTariffByAccountTypeAndTariffTerm(user,
TariffTimeTerm.valueOf(tariffRequest.getTariffTerm().toUpperCase())); TariffTimeTerm.valueOf(tariffRequest.getTariffTerm().toUpperCase()));
@@ -0,0 +1,472 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationPageDto;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationRequest;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenOperationHandler implements RequestHandler {
private final TokenOperationService tokenOperationService;
private final ObjectMapper objectMapper;
private final AuthService authService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
log.info("Handling TokenOperation request: msg_id={}, version={}",
request.getMsgId(), request.getVersion());
try {
TokenOperationRequest tokenRequest = objectMapper.convertValue(
request.getMessageBody(),
TokenOperationRequest.class
);
String action = tokenRequest.getAction();
if (action == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("error", "Action is required")
);
}
switch (action.toLowerCase()) {
case "get_list":
return handleGetList(request.getMsgId(), tokenRequest);
case "get_by_id":
return handleGetById(request.getMsgId(), tokenRequest);
case "get_by_user":
return handleGetByUser(request.getMsgId(), tokenRequest);
case "create":
return handleCreate(request.getMsgId(), tokenRequest);
case "update":
return handleUpdate(request.getMsgId(), tokenRequest);
case "delete":
return handleDelete(request.getMsgId(), tokenRequest);
case "delete_all_by_user":
return handleDeleteAllByUser(request.getMsgId(), tokenRequest);
case "get_stats":
return handleGetStats(request.getMsgId(), tokenRequest);
case "filter":
return handleFilter(request.getMsgId(), tokenRequest);
default:
return new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
MessageCode.INVALID_ACTION.getDescription(),
Map.of("available_actions",
"get_list, get_by_id, get_by_user, create, update, " +
"delete, delete_all_by_user, get_stats, filter")
);
}
} catch (Exception e) {
log.error("Error handling TokenOperation request", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", e.getMessage())
);
}
}
private BaseResponse handleGetList(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
Page<TokenOperation> page = tokenOperationService.findByUserId(
userId,
request.getPage() != null ? request.getPage() : 0,
request.getSize() != null ? request.getSize() : 20
);
TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
.content(page.getContent())
.pageNumber(page.getNumber())
.pageSize(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.first(page.isFirst())
.build();
Map<String, Object> responseData = new HashMap<>();
responseData.put("operations", pageDto);
responseData.put("total_elements", page.getTotalElements());
responseData.put("total_pages", page.getTotalPages());
responseData.put("current_page", page.getNumber());
responseData.put("page_size", page.getSize());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
responseData
);
} catch (Exception e) {
log.error("Error getting token operations list", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error getting operations: " + e.getMessage())
);
}
}
private BaseResponse handleGetById(Integer msgId, TokenOperationRequest request) {
log.info("Getting token operation by id: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
TokenOperation operation = tokenOperationService.findById(request.getOperationId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
operation
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error getting token operation by id", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", e.getMessage())
);
}
}
private BaseResponse handleGetByUser(Integer msgId, TokenOperationRequest request) {
return handleGetList(msgId, request);
}
private BaseResponse handleCreate(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
TokenOperation operation = tokenOperationService.create(
request.getOperationType(),
userId,
request.getSpent()
);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
operation
);
} catch (Exception e) {
log.error("Error creating token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error creating operation: " + e.getMessage())
);
}
}
private BaseResponse handleUpdate(Integer msgId, TokenOperationRequest request) {
log.info("Updating token operation: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
TokenOperation existing = tokenOperationService.findById(request.getOperationId());
if (request.getOperationType() != null) {
existing.setOperationType(request.getOperationType());
}
if (request.getSpent() != null) {
existing.setSpent(request.getSpent());
}
TokenOperation updated = tokenOperationService.update(request.getOperationId(), existing);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
updated
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error updating token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error updating operation: " + e.getMessage())
);
}
}
private BaseResponse handleDelete(Integer msgId, TokenOperationRequest request) {
log.info("Deleting token operation: {}", request.getOperationId());
if (request.getOperationId() == null) {
return new BaseResponse(
msgId,
MessageCode.OPERATION_NOT_FOUND.getCode(),
MessageCode.OPERATION_NOT_FOUND.getDescription(),
Map.of("error", "Operation ID is required")
);
}
try {
tokenOperationService.deleteById(request.getOperationId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("message", "Operation deleted successfully",
"operation_id", request.getOperationId())
);
} catch (jakarta.persistence.EntityNotFoundException e) {
return new BaseResponse(
msgId,
MessageCode.NOT_FOUND.getCode(),
MessageCode.NOT_FOUND.getDescription(),
Map.of("error", "Operation not found with id: " + request.getOperationId())
);
} catch (Exception e) {
log.error("Error deleting token operation", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error deleting operation: " + e.getMessage())
);
}
}
private BaseResponse handleDeleteAllByUser(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
tokenOperationService.deleteByUserId(userId);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("message", "All operations deleted successfully for user",
"user_id", userId)
);
} catch (Exception e) {
log.error("Error deleting all operations for user", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error deleting operations: " + e.getMessage())
);
}
}
private BaseResponse handleGetStats(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
if (userId == null) {
return new BaseResponse(
msgId,
MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
try {
Map<String, Long> statsByType = new HashMap<>();
long totalSpent = 0L;
for (OperationType type : OperationType.values()) {
Long spent = tokenOperationService.getTotalSpentByUserAndType(userId, type);
statsByType.put(type.name().toLowerCase(), spent != null ? spent : 0L);
totalSpent += spent != null ? spent : 0L;
}
Map<String, Object> statistics = new HashMap<>();
statistics.put("user_id", userId);
statistics.put("total_spent", totalSpent);
statistics.put("total_operations", tokenOperationService.countByUserId(userId));
statistics.put("stats_by_type", statsByType);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
statistics
);
} catch (Exception e) {
log.error("Error getting statistics", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error getting statistics: " + e.getMessage())
);
}
}
private BaseResponse handleFilter(Integer msgId, TokenOperationRequest request) {
if (request.getToken() == null) {
return new BaseResponse(
msgId,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
Map.of("error", "User ID is required")
);
}
Long userId = authService.useUserAuthToken(request.getToken());
try {
Page<TokenOperation> page = tokenOperationService.findByFilters(
userId,
request.getOperationType(),
request.getMinSpent(),
request.getMaxSpent(),
request.getPage() != null ? request.getPage() : 0,
request.getSize() != null ? request.getSize() : 20,
request.getSortBy() != null ? request.getSortBy() : "createdAt",
request.getSortDirection() != null ? request.getSortDirection() : "desc"
);
TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
.content(page.getContent())
.pageNumber(page.getNumber())
.pageSize(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.first(page.isFirst())
.build();
Map<String, Object> responseData = new HashMap<>();
responseData.put("operations", pageDto);
responseData.put("total_elements", page.getTotalElements());
responseData.put("total_pages", page.getTotalPages());
responseData.put("current_page", page.getNumber());
responseData.put("page_size", page.getSize());
responseData.put("applied_filters", Map.of(
"user_id",userId != null ? userId : "all",
"operation_type", request.getOperationType() != null ? request.getOperationType() : "all",
"min_spent", request.getMinSpent() != null ? request.getMinSpent() : "none",
"max_spent", request.getMaxSpent() != null ? request.getMaxSpent() : "none"
));
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
responseData
);
} catch (Exception e) {
log.error("Error filtering operations", e);
return new BaseResponse(
msgId,
MessageCode.INTERNAL_ERROR.getCode(),
MessageCode.INTERNAL_ERROR.getDescription(),
Map.of("error", "Error filtering operations: " + e.getMessage())
);
}
}
}
@@ -0,0 +1,108 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ActionResponse;
import ru.soune.nocopy.dto.user.moderation.UserVerificationRequest;
import ru.soune.nocopy.dto.user.moderation.UserVerificationResponse;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.user.ModerationStatus;
import ru.soune.nocopy.entity.user.moderation.UserVerification;
import ru.soune.nocopy.service.notification.NotificationService;
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
import java.util.Arrays;
import java.util.List;
@Component
@RequiredArgsConstructor
@Slf4j
public class UserVerificationHandler implements RequestHandler {
private final UserVerificationService userVerificationService;
private final ObjectMapper objectMapper;
private final NotificationService notificationService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
UserVerificationRequest userVerificationRequest = objectMapper.convertValue(request.getMessageBody(),
UserVerificationRequest.class);
String action = userVerificationRequest.getAction();
switch (action) {
case "get_all_verifications":
return handleVerifications(request, userVerificationRequest);
case "verified":
return handleVerified(request, userVerificationRequest);
default:
ActionResponse response = ActionResponse.builder()
.action(action)
.availableActions(Arrays.asList(
"get_all_verifications", "verified"))
.build();
return new BaseResponse(request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
"Invalid action: " + action,
response);
}
}
private BaseResponse handleVerifications(BaseRequest request, UserVerificationRequest userVerificationRequest) throws Exception {
List<UserVerification> verifications = userVerificationService.verifications();
if (verifications.isEmpty()) {
return new BaseResponse(request.getMsgId(), MessageCode.USER_VERIFICATIONS_NOT_FOUND.getCode(),
MessageCode.USER_VERIFICATIONS_NOT_FOUND.getDescription(), null);
}
List<UserVerificationResponse> verificationResponses = verifications.stream().map(this::convertDto).toList();
return BaseResponse.builder()
.msgId(request.getMsgId())
.messageBody(verificationResponses)
.build();
}
private BaseResponse handleVerified(BaseRequest request, UserVerificationRequest userVerificationRequest) throws Exception {
UserVerification userVerification = userVerificationService.verifyUser(userVerificationRequest.getUserId(), userVerificationRequest.getMessage(),
userVerificationRequest.getAdminId(), userVerificationRequest.getVerified());
if (userVerification == null) {
return new BaseResponse(request.getMsgId(),
MessageCode.ADMIN_USER_NOT_FOUND.getCode(),
MessageCode.ADMIN_USER_NOT_FOUND.getDescription(),
null);
}
NotificationType notificationType = userVerification.getModerationStatus() == ModerationStatus.VERIFIED?
NotificationType.USER_VERIFIED: NotificationType.USER_NOT_VERIFIED;
notificationService.addNotification(notificationType, userVerification.getUserId());
return BaseResponse.builder()
.msgId(request.getMsgId())
.messageBody(UserVerificationResponse.builder()
.userVerificationId(userVerification.getId())
.moderationStatus(userVerification.getModerationStatus().toString())
.userId(userVerification.getUserId()))
.build();
}
private UserVerificationResponse convertDto(UserVerification userVerification) {
return UserVerificationResponse.builder()
.userVerificationId(userVerification.getId())
.userId(userVerification.getUserId())
.moderationStatus(userVerification.getModerationStatus().name())
.updateTime(userVerification.getUpdatedAt())
.build();
}
}
@@ -13,6 +13,7 @@ import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.violation.Violation; import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.geo.GeoCountryService;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.violation.ViolationService; import ru.soune.nocopy.service.violation.ViolationService;
import ru.soune.nocopy.service.violation.ViolationStatus; import ru.soune.nocopy.service.violation.ViolationStatus;
@@ -21,6 +22,7 @@ import java.io.FileNotFoundException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -38,6 +40,8 @@ public class ViolationHandler implements RequestHandler {
private final FileEntityService fileEntityService; private final FileEntityService fileEntityService;
private final GeoCountryService geoCountryService;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
@@ -112,10 +116,16 @@ public class ViolationHandler implements RequestHandler {
); );
} }
List<ViolationResponse.ViolationDto> violationDtos = violationPage.getContent() List<Violation> content = violationPage.getContent();
.stream() List<ViolationResponse.ViolationDto> violationDtos = new ArrayList<>();
.map(ViolationResponse.ViolationDto::fromEntity)
.toList(); for (Violation violation : content) {
ViolationResponse.ViolationDto violationDto = ViolationResponse.ViolationDto.fromEntity(violation);
violationDto.setCountry(geoCountryService.getCountryName(violation.getPageUrl()));
violationDto.setCountryCode(geoCountryService.getCountryCode(violation.getPageUrl()));
violationDtos.add(violationDto);
}
ViolationResponse response = ViolationResponse.builder() ViolationResponse response = ViolationResponse.builder()
.violations(violationDtos) .violations(violationDtos)
@@ -25,4 +25,16 @@ public interface ComplaintEntityRepository extends JpaRepository<ComplaintEntity
@Transactional @Transactional
@Query("UPDATE ComplaintEntity c SET c.status = :status WHERE c.id = :id") @Query("UPDATE ComplaintEntity c SET c.status = :status WHERE c.id = :id")
int updateStatus(Long id, ComplaintStatus status); int updateStatus(Long id, ComplaintStatus status);
@Query("SELECT COUNT(c.id) FROM ComplaintEntity c")
Long getTotalComplaintsCount();
@Query("""
SELECT COUNT(c.id)
FROM ComplaintEntity c
WHERE NOT EXISTS (
SELECT 1 FROM LawCase lc WHERE lc.violationId = c.violation.id
)
""")
Long getComplaintsWithoutLawCaseCount();
} }
@@ -6,10 +6,12 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.soune.nocopy.dto.statistic.UserFilesStatisticResponse;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus; import ru.soune.nocopy.entity.file.ProtectionStatus;
import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -58,12 +60,6 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
List<FileEntity> findFileByUserIdAndStatus(Long userId, FileStatus status); 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") @Query("SELECT f FROM FileEntity f WHERE f.mimeType = :mimeType AND f.userId IN :userIds")
List<FileEntity> findByMimeTypeAndUserIds(String mimeType, List<Long> 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") @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); List<FileEntity> findByStatuses(Long userId, @Param("statuses") List<String> statuses);
@Query("SELECT DISTINCT f FROM FileEntity f " +
"LEFT JOIN FileMonitoringEntity m ON f.id = m.file.id " +
"WHERE f.userId IN :userIds " +
"AND f.status IN :statuses")
Page<FileEntity> findFiles(
@Param("userIds") List<Long> userIds,
@Param("statuses") List<FileStatus> statuses,
Pageable pageable);
@Query("SELECT SUM(f.fileSize) FROM FileEntity f " +
"WHERE f.userId IN :userIds " +
"AND f.status IN :statuses")
Long sumFileSize(
@Param("userIds") List<Long> userIds,
@Param("statuses") List<FileStatus> statuses);
@Query("""
SELECT new ru.soune.nocopy.dto.statistic.UserFilesStatisticResponse(
u.fullName,
u.Id,
CAST(COUNT(f.id) AS int),
COALESCE(SUM(f.fileSize), 0L)
)
FROM FileEntity f
JOIN User u ON f.userId = u.Id
WHERE
(:mimeType IS NULL OR :mimeType = '' OR f.mimeType LIKE CONCAT(:mimeType, '%'))
GROUP BY u.Id, u.fullName
ORDER BY COUNT(f.id) DESC
LIMIT :topUsers
""")
List<UserFilesStatisticResponse> findTopUsers(
@Param("topUsers") int topUsers,
@Param("mimeType") String mimeType
);
@Query("""
SELECT
COUNT(f.id),
COALESCE(SUM(f.fileSize), 0)
FROM FileEntity f
WHERE f.protectionStatus = 'PROTECTED'
""")
List<Object[]> getProtectedFilesTotalStats();
@Query(value = """
SELECT
user_id,
COUNT(*) as file_count,
SUM(file_size) as total_size
FROM file_entities
WHERE protection_status = 'PROTECTED'
GROUP BY user_id
ORDER BY file_count
""", nativeQuery = true)
List<Object[]> getProtectedFilesPerUserStats();
} }
@@ -56,7 +56,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f JOIN file_entities f
ON f.id = h.file_id ON f.id = h.file_id
WHERE ref.file_id = :fileId AND f.user_id = :userId WHERE ref.file_id = :fileId AND f.user_id = :userId
AND f.status = 'ACTIVE' AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""", """,
nativeQuery = true) nativeQuery = true)
List<SimilarImageProjection> findCandidatesFromUserFiles( List<SimilarImageProjection> findCandidatesFromUserFiles(
@@ -82,7 +82,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f JOIN file_entities f
ON f.id = h.file_id ON f.id = h.file_id
WHERE ref.file_id = :fileId AND f.user_id IN :userIds WHERE ref.file_id = :fileId AND f.user_id IN :userIds
AND f.status = 'ACTIVE' AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""", """,
nativeQuery = true) nativeQuery = true)
List<SimilarImageProjection> findCandidatesFromUserFiles( List<SimilarImageProjection> findCandidatesFromUserFiles(
@@ -108,7 +108,7 @@ public interface ImageSimilarityRepository
JOIN file_entities f ON f.id = h.file_id JOIN file_entities f ON f.id = h.file_id
WHERE h.hash64_hi = :hash64Hi WHERE h.hash64_hi = :hash64Hi
AND h.hash64_lo = :hash64Lo AND h.hash64_lo = :hash64Lo
AND f.status = 'ACTIVE' AND (f.status = 'ACTIVE' OR f.status = 'MODERATION')
""", """,
nativeQuery = true) nativeQuery = true)
List<SimilarImageProjection> findExactDuplicates( List<SimilarImageProjection> findExactDuplicates(
@@ -0,0 +1,45 @@
package ru.soune.nocopy.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.util.List;
@Repository
public interface LawCaseRepository extends JpaRepository<LawCase, Long> {
@Query("SELECT l FROM LawCase l WHERE l.user.Id = :userId")
List<LawCase> getUserLawCases(@Param("userId") Long userId, Pageable pageable);
// @Query("SELECT l FROM LawCase l WHERE l.user.Id = :userId " +
// "AND (:type IS NULL OR l.type = :type) " +
// "AND (:lawyer IS NULL OR l.lawyer = :lawyer) " +
// "AND (:priority IS NULL OR l.priority = :priority)")
// List<LawCase> getUserLawCases(@Param("userId") Long userId,
// @Param("type") LawCaseType type,
// @Param("lawyer") String lawyer,
// @Param("priority") LawCasePriority priority,
// Pageable pageable);
@Query("SELECT l FROM LawCase l WHERE l.user.Id = :userId " +
"AND (:type IS NULL OR l.type = :type) " +
"AND (:lawyer IS NULL OR l.lawyer = :lawyer) " +
"AND (:violationId IS NULL OR l.violationId = :violationId) " +
"AND (:priority IS NULL OR l.priority = :priority)")
Page<LawCase> getUserLawCases(@Param("userId") Long userId,
@Param("type") LawCaseType type,
@Param("lawyer") String lawyer,
@Param("priority") LawCasePriority priority,
@Param("violationId") Long violationId,
Pageable pageable);
@Query("SELECT COUNT(lc.id) FROM LawCase lc")
Long getTotalLawCasesCount();
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
import java.util.List;
@Repository
public interface ModerationFileRepository extends CrudRepository<ModerationPassportFile, Long> {
public List<ModerationPassportFile> findByStatus(String status);
}
@@ -0,0 +1,42 @@
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.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.soune.nocopy.entity.notification.Notification;
import ru.soune.nocopy.entity.notification.NotificationStatus;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
import java.util.List;
public interface NotificationRepository extends JpaRepository<Notification, Long> {
Page<Notification> findByUser(User user, Pageable pageable);
Page<Notification> findByUserAndNotificationTypeIn(User user, List<NotificationType> types, Pageable pageable);
Page<Notification> findByUserAndStatusIn(User user, List<NotificationStatus> statuses, Pageable pageable);
Page<Notification> findByUserAndNotificationTypeInAndStatusIn(User user, List<NotificationType> types,
List<NotificationStatus> statuses,
Pageable pageable);
List<Notification> findTop5ByUserAndStatusOrderByCreatedAtDesc(User user, NotificationStatus status);
long countByUserAndStatus(User user, NotificationStatus status);
@Modifying
@Query("UPDATE Notification n SET n.status = :newStatus , n.updatedAt = :updateTime WHERE n.id IN :ids AND n.user = :user AND " +
"n.status = :oldStatus")
int updateStatus(@Param("ids") List<Long> ids,
@Param("user") User user,
@Param("oldStatus") NotificationStatus oldStatus,
@Param("newStatus") NotificationStatus newStatus,
@Param("updateTime") LocalDateTime updateTime);
Page<Notification> getNotificationsByUser(User user, Pageable pageable);
}
@@ -1,6 +1,7 @@
package ru.soune.nocopy.repository; package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.payment.Payment; import ru.soune.nocopy.entity.payment.Payment;
@@ -11,4 +12,23 @@ import java.util.Optional;
public interface PaymentRepository extends JpaRepository<Payment, String> { public interface PaymentRepository extends JpaRepository<Payment, String> {
Optional<Payment> findByPaymentUuid(String paymentUuid); Optional<Payment> findByPaymentUuid(String paymentUuid);
List<Payment> findByUserId(Long userId); List<Payment> findByUserId(Long userId);
@Query("""
SELECT COALESCE(SUM(p.tariff.tokens), 0)
FROM Payment p
WHERE p.operationType = 'TOKEN'
AND p.status = 'SUCCEEDED'
""")
Long getTotalTokensBought();
@Query("""
SELECT
p.user.Id,
COALESCE(SUM(p.tariff.tokens), 0)
FROM Payment p
WHERE p.operationType = 'TOKEN'
AND p.status = 'SUCCEEDED'
GROUP BY p.user.Id
""")
List<Object[]> getTokensBoughtPerUser();
} }
@@ -43,4 +43,22 @@ public interface ReferralJpaRepository extends JpaRepository<Referral, Long> {
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId") @Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId")
int countTotalInvitees(@Param("userId") Long userId); int countTotalInvitees(@Param("userId") Long userId);
@Query("SELECT COALESCE(SUM(r.totalIncome), 0) FROM Referral r")
Long getTotalIncome();
@Query("SELECT COALESCE(SUM(r.availableIncome), 0) FROM Referral r")
Long getTotalAvailableIncome();
@Query("SELECT COALESCE(SUM(r.holdBalance), 0) FROM Referral r")
Long getTotalHoldBalance();
@Query("""
SELECT
r.userId,
r.totalIncome
FROM Referral r
WHERE r.totalIncome > 0
""")
List<Object[]> getIncomePerUser();
} }
@@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffTimeTerm;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -13,4 +14,57 @@ import java.util.List;
public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> { public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> {
@Query("SELECT t FROM TariffInfo t WHERE t.autoRenewal = true AND t.endTariff <= :date") @Query("SELECT t FROM TariffInfo t WHERE t.autoRenewal = true AND t.endTariff <= :date")
List<TariffInfo> findForAutoRenewal(@Param("date") LocalDateTime date); List<TariffInfo> findForAutoRenewal(@Param("date") LocalDateTime date);
@Query("""
SELECT COUNT(u.Id)
FROM User u
WHERE (:activeOnly = false OR u.isActive = true)
AND (:companyOnly = false OR u.company IS NOT NULL)
""")
Long getTotalUsersCount(
@Param("activeOnly") boolean activeOnly,
@Param("companyOnly") boolean companyOnly
);
@Query("""
SELECT COUNT(u.Id)
FROM User u
WHERE (:activeOnly = false OR u.isActive = true)
""")
Long getTotalUsersCount(@Param("activeOnly") boolean activeOnly);
@Query(value = """
SELECT
t.tariff_name,
COUNT(u.id) as user_count,
(COUNT(u.id) * 100.0) / NULLIF(
(SELECT COUNT(*) FROM users u2
WHERE (:activeOnly = false OR u2.is_active = true)
AND u2.personal_tariff_info_id IS NOT NULL
), 0
) as usage_percent
FROM users u
INNER JOIN tariff_info pti ON u.personal_tariff_info_id = pti.id
INNER JOIN tariff t ON pti.tariff_id = t.id
WHERE (:activeOnly = false OR u.is_active = true)
GROUP BY t.id, t.tariff_name
ORDER BY user_count DESC
""", nativeQuery = true)
List<Object[]> getTariffStatisticsNative(@Param("activeOnly") boolean activeOnly);
@Query("""
SELECT COUNT(ti.id)
FROM TariffInfo ti
WHERE ti.endTariff >= CURRENT_TIMESTAMP
AND (:term IS NULL OR ti.tariff.tariffTerm = :term)
""")
Long getCurrentActiveSubscriptions(@Param("term") TariffTimeTerm term);
@Query("""
SELECT ti
FROM TariffInfo ti
WHERE (:term IS NULL OR ti.tariff.tariffTerm = :term)
ORDER BY ti.startTariff
""")
List<TariffInfo> getAllTariffInfosForAnalysis(@Param("term") TariffTimeTerm term);
} }
@@ -0,0 +1,56 @@
package ru.soune.nocopy.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import java.util.List;
@Repository
public interface TokenOperationRepository extends JpaRepository<TokenOperation, Long> {
Page<TokenOperation> findAll(Pageable pageable);
List<TokenOperation> findByUserId(Long userId);
Page<TokenOperation> findByUserId(Long userId, Pageable pageable);
Page<TokenOperation> findByOperationType(OperationType operationType, Pageable pageable);
@Query("SELECT t FROM TokenOperation t WHERE " +
"(:userId IS NULL OR t.userId = :userId) AND " +
"(:operationType IS NULL OR t.operationType = :operationType) AND " +
"(:minSpent IS NULL OR t.spent >= :minSpent) AND " +
"(:maxSpent IS NULL OR t.spent <= :maxSpent)")
Page<TokenOperation> findByFilters(
@Param("userId") Long userId,
@Param("operationType") OperationType operationType,
@Param("minSpent") Long minSpent,
@Param("maxSpent") Long maxSpent,
Pageable pageable
);
@Query("SELECT SUM(t.spent) FROM TokenOperation t WHERE t.userId = :userId AND t.operationType = :operationType")
Long sumSpentByUserAndType(@Param("userId") Long userId, @Param("operationType") OperationType operationType);
Long countByUserId(Long userId);
@Query("""
SELECT COALESCE(SUM(t.spent), 0)
FROM TokenOperation t
""")
Long getTotalTokensSpent();
@Query("""
SELECT
t.userId,
COALESCE(SUM(t.spent), 0)
FROM TokenOperation t
GROUP BY t.userId
""")
List<Object[]> getTokensSpentPerUser();
}
@@ -2,10 +2,13 @@ package ru.soune.nocopy.repository;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email); User findByEmail(String email);
@@ -14,4 +17,32 @@ public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByCompanyId(String companyId); List<User> findByCompanyId(String companyId);
long countByCompanyId(String companyId); long countByCompanyId(String companyId);
User findByPersonalTariffInfo(TariffInfo tariffInfo); User findByPersonalTariffInfo(TariffInfo tariffInfo);
@Query("SELECT COUNT(u.Id) FROM User u")
Long getTotalUsersCount();
@Query(value = """
SELECT COUNT(*)
FROM users
WHERE created_at >= NOW() - INTERVAL '1 day' * :days
""", nativeQuery = true)
Long getNewUsersCount(int days);
@Query(value = """
SELECT COUNT(*)
FROM users
WHERE created_at >= DATE_TRUNC('month', NOW())
""", nativeQuery = true)
Long getNewUsersCurrentMonth();
@Query(value = """
SELECT COUNT(*)
FROM users
WHERE created_at >= DATE_TRUNC('month', NOW() - INTERVAL '1 month')
AND created_at < DATE_TRUNC('month', NOW())
""", nativeQuery = true)
Long getNewUsersPreviousMonth();
@Query("SELECT u FROM User u WHERE u.personalTariffInfo.id = :tariffInfoId")
Optional<User> findByPersonalTariffInfoId(@Param("tariffInfoId") String tariffInfoId);
} }
@@ -0,0 +1,12 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.soune.nocopy.entity.user.ModerationStatus;
import ru.soune.nocopy.entity.user.moderation.UserVerification;
import java.util.List;
public interface UserVerificationRepository extends JpaRepository<UserVerification, Long> {
List<UserVerification> findByUserId(Long userId);
List<UserVerification>findByUserIdAndModerationStatus(Long userId, ModerationStatus moderationStatus);
}
@@ -74,4 +74,10 @@ public interface ViolationRepository extends JpaRepository<Violation, Long> {
List<Violation> findByFileEntityInAndStatusAndCreatedDateBetween(List<FileEntity> files, String status, List<Violation> findByFileEntityInAndStatusAndCreatedDateBetween(List<FileEntity> files, String status,
LocalDateTime startDate, LocalDateTime endDate, LocalDateTime startDate, LocalDateTime endDate,
Sort sort); Sort sort);
@Query("SELECT COUNT(v.id) FROM Violation v")
Long getTotalViolationsCount();
@Query("SELECT COUNT(DISTINCT c.violation.id) FROM ComplaintEntity c WHERE c.violation IS NOT NULL")
Long getViolationsWithComplaintCount();
} }
@@ -7,11 +7,16 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity; import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.entity.notification.NotificationType;
import ru.soune.nocopy.repository.FileMonitoringRepository; import ru.soune.nocopy.repository.FileMonitoringRepository;
import ru.soune.nocopy.service.monitoring.MonitoringSearchService; import ru.soune.nocopy.service.monitoring.MonitoringSearchService;
import ru.soune.nocopy.service.notification.NotificationService;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Component @Component
@@ -22,6 +27,8 @@ public class MonitoringScheduler {
private final MonitoringSearchService monitoringSearchService; private final MonitoringSearchService monitoringSearchService;
private final NotificationService notificationService;
@Scheduled(cron = "0 1 2 * * *", zone = "Europe/Moscow") @Scheduled(cron = "0 1 2 * * *", zone = "Europe/Moscow")
@Transactional @Transactional
public void processScheduledMonitoring() { public void processScheduledMonitoring() {
@@ -29,6 +36,8 @@ public class MonitoringScheduler {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
List<FileMonitoringEntity> readyForRun = monitoringRepository.findReadyForRun(now); List<FileMonitoringEntity> readyForRun = monitoringRepository.findReadyForRun(now);
Map<Long, List<FileMonitoringEntity>> groupingFilesMonitoring = readyForRun.stream()
.collect(Collectors.groupingBy(FileMonitoringEntity::getUserId));
log.info("Found {} files ready for monitoring", readyForRun.size()); log.info("Found {} files ready for monitoring", readyForRun.size());
@@ -43,5 +52,10 @@ public class MonitoringScheduler {
monitoringRepository.save(monitoring); monitoringRepository.save(monitoring);
} }
} }
Set<Long> usersForNotifications = groupingFilesMonitoring.keySet();
for (Long userId : usersForNotifications) {
notificationService.addNotification(NotificationType.MONITORING_RESULT, userId);
}
} }
} }

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