5 Commits
Author SHA1 Message Date
vladp 4ee7e439c0 NC_YA
Test Workflow / test (push) Successful in 3s
2026-02-06 04:24:08 +07:00
vladp 503463e1d0 NC_YA
Test Workflow / test (push) Successful in 3s
2026-02-06 04:19:43 +07:00
vladp 5f61c82dee NC_YA
Test Workflow / test (push) Successful in 3s
2026-02-06 04:14:17 +07:00
vladp 2139ca0e8c NC_YA
Test Workflow / test (push) Successful in 7s
2026-02-06 03:50:26 +07:00
vladp 01332c7db5 NC_YA
Test Workflow / test (push) Successful in 4s
2026-02-06 03:31:41 +07:00
379 changed files with 1893 additions and 20914 deletions
-3
View File
@@ -14,9 +14,6 @@ COPY --from=build /app/build/libs/*.jar app.jar
RUN mkdir -p /data/uploads && chmod 755 /data/uploads
ENV BUILD_TIME_BACK="unknown"\
BUILD_TIME_FRONT="unknown"
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
-379
View File
@@ -122,382 +122,3 @@ SELECT 1
FROM protect_check pc
WHERE pc.user_id = u.id
);
------
Скрипты рефералки
INSERT INTO referrals (user_id, referral_link, inviter_id, level_id, total_income, is_active, created_at, hold_balance)
SELECT
u.id as user_id,
CONCAT('ref-', u.id, '-', LOWER(SUBSTRING(MD5(RANDOM()::text) FROM 1 FOR 8))) as referral_link,
NULL as inviter_id,
'bronze' as level_id,
0 as total_income,
false as is_active,
NOW() as created_at,
0 as hold_balance
FROM users u
LEFT JOIN referrals r ON u.id = r.user_id
WHERE
u.company_id IS NULL
AND r.user_id IS NULL
ORDER BY u.id;
-------
INSERT INTO referral_levels (id, name, min_invitees, reward_percentage) VALUES
('bronze', 'BRONZE', 0, 15),
('silver', 'SILVER', 6, 18),
('gold', 'GOLD', 16, 22),
('platinum', 'PLATINUM', 50, 25);
-----------------
Скрипт,для нумерации для уже сохданных файлов
WITH max_id AS (
SELECT COALESCE(MAX(support_id), 0) as max_val FROM file_entities
),
-- Нумеруем только NULL записи, начиная с max_val + 1
numbered AS (
SELECT
f.id,
(SELECT max_val FROM max_id) + ROW_NUMBER() OVER (ORDER BY f.created_at, f.id) as new_support_id
FROM file_entities f
WHERE f.support_id IS NULL
)
UPDATE file_entities
SET support_id = numbered.new_support_id
FROM numbered
WHERE file_entities.id = numbered.id;
---------------------------------
НАСТРОЙКА NGINX
--------------------------------
# admin.not-copy.com
server {
listen 80;
listen [::]:80;
server_name admin.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name admin.not-copy.com;
ssl_certificate /etc/letsencrypt/live/admin.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2995;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# workspace.not-copy.com
server {
listen 80;
listen [::]:80;
server_name workspace.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name workspace.not-copy.com;
ssl_certificate /etc/letsencrypt/live/workspace.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/workspace.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2998;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# lp.not-copy.com
server {
listen 80;
listen [::]:80;
server_name lp.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name lp.not-copy.com;
ssl_certificate /etc/letsencrypt/live/lp.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lp.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2993;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-admin.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-admin.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-admin.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-admin.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:8082;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2996;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-workspace.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-workspace.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-workspace.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-workspace.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-workspace.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:3002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-lp.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-lp.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-lp.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-lp.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-lp.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2994;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Проверяем конфиг
ls -la /etc/nginx/sites-available/
ls -la /etc/nginx/sites-enabled/
# Создание конфига
sudo nano /etc/nginx/sites-available/not-copy
# Создание симлинк
sudo ln -s /etc/nginx/sites-available/not-copy /etc/nginx/sites-enabled/
# Чек
sudo nginx -t
# Рестарт
sudo systemctl reload nginx
---------------------------------------------
# ПОЛНАЯ ИНСТРУКЦИЯ: НАСТРОЙКА ПРОКСИ ДЛЯ ОБХОДА БЛОКИРОВОК SEARCHAPI.IO
## Данные для подключения к прокси-серверу
IP: 193.46.217.94
Порт HTTP: 3128
Порт SOCKS: 1080
Логин SSH: root
Пароль SSH: u7nRtsD6
## 1. Настройка прокси-сервера
```bash
# Подключаемся к серверу
ssh root@193.46.217.94
# Вводим пароль: u7nRtsD6
# Обновляем систему и устанавливаем пакеты
yum update -y
yum install -y epel-release net-tools nano
yum install -y 3proxy
# Создаем конфигурацию
mkdir -p /etc/3proxy
cat > /etc/3proxy/3proxy.cfg << 'EOF'
nserver 8.8.8.8
nserver 8.8.4.4
timeouts 1 5 30 60 180 1800 15 60
daemon
log /var/log/3proxy/3proxy.log
logformat "- +_L%t.%. %N.%p %E %U %C:%c %R:%r %O %I %h %T"
rotate 30
auth none
allow * * * * *
proxy -p3128
socks -p1080
EOF
# Создаем папку для логов и запускаем прокси
mkdir -p /var/log/3proxy
/usr/bin/3proxy /etc/3proxy/3proxy.cfg
# Проверяем, что запустилось
ps aux | grep 3proxy
netstat -tulpn | grep -E '3128|1080'
# Настраиваем файрвол
firewall-cmd --permanent --add-port=3128/tcp
firewall-cmd --permanent --add-port=1080/tcp
firewall-cmd --reload
firewall-cmd --list-ports
# Проверяем локальную работу прокси
curl -x http://127.0.0.1:3128 https://api.ipify.org
# Должен вернуть: 193.46.217.94
# Настраиваем автозапуск
echo "/usr/bin/3proxy /etc/3proxy/3proxy.cfg" >> /etc/rc.local
chmod +x /etc/rc.local
# Проверяем доступность прокси
ping -c 4 193.46.217.94
curl -x http://193.46.217.94:3128 https://api.ipify.org
# Должен вернуть: 193.46.217.94
# Проверяем работу с SearchAPI через прокси
curl -x http://193.46.217.94:3128 "https://www.searchapi.io/api/v1/search?engine=google_lens&api_key=5jyYZC8jSaxhZTwjMUhwtAXi&url=https://dev-workspace.not-copy.com/api/files/public/76d06557-6df1-4fcd-a273-050ec6a35faf&search_type=exact_matches" -o test.json
ls -la test.json
tail -20 test.json
# Файл должен быть полным (заканчиваться на '}')
ps aux | grep 3proxy # Проверка статуса
netstat -tulpn | grep -E '3128|1080' # Проверка портов
tail -f /var/log/3proxy/3proxy.log # Просмотр логов
killall 3proxy # Остановка прокси
/usr/bin/3proxy /etc/3proxy/3proxy.cfg # Запуск прокси
firewall-cmd --permanent --remove-port=3128/tcp
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="IP_ТВОЕГО_СЕРВЕРА" port port="3128" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="IP_ТВОЕГО_СЕРВЕРА" port port="1080" protocol="tcp" accept'
firewall-cmd --reload
На прокси-сервере:
bash
ps aux | grep 3proxy # Проверка статуса
netstat -tulpn | grep -E '3128|1080' # Проверка портов
tail -f /var/log/3proxy/3proxy.log # Просмотр логов
killall 3proxy # Остановка прокси
/usr/bin/3proxy /etc/3proxy/3proxy.cfg # Запуск прокси
На основном сервере:
bash
curl -x http://193.46.217.94:3128 https://api.ipify.org # Проверка прокси
telnet 193.46.217.94 3128 # Проверка порта
------
Настройка Яндекс Клауд
1. YCAJEpWAtaVkVGX0sH6_EupEg - индетификатор ключа
2. YCMmVykfXrZ_nfU13Vo4yoCVGa70DnTlBgF1pUzO - секретный ключ
private static final String KEY_ID = "YCAJEmHZcWxzf4oGWvETG3mms";
private static final String SECRET_KEY = "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG";
+2 -25
View File
@@ -34,7 +34,7 @@ dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'commons-validator:commons-validator:1.7'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '4.0.1'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.17.2'
implementation group: 'com.google.cloud', name: 'google-cloud-vision', version: '3.55.0'
implementation group: 'com.google.api-client', name: 'google-api-client', version: '2.7.2'
@@ -59,32 +59,9 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation name: 'testlib-fat-0.3.1-all'
implementation name: 'testlib-fat-0.2.1-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation 'com.maxmind.geoip2:geoip2:4.2.0'
implementation 'org.sejda.imageio:webp-imageio:0.1.6'
implementation project(':referral')
//cloud
implementation("software.amazon.awssdk:aws-sdk-java:2.29.33")
implementation("software.amazon.awssdk:apache-client:2.29.33")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.0'
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
//cache
implementation("org.springframework.boot:spring-boot-starter-cache:4.0.6")
implementation 'com.github.ben-manes.caffeine:caffeine'
//security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation("io.jsonwebtoken:jjwt:0.13.0")
}
+73
View File
@@ -0,0 +1,73 @@
version: '3.9'
services:
storage:
image: alpine:latest
container_name: file-storage-local
networks:
- app-network-local
volumes:
- uploads_data_local:/storage:rw
command: tail -f /dev/null
restart: unless-stopped
db:
image: postgres:17.7
restart: always
environment:
POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_SHARED_BUFFERS: 512MB
POSTGRES_EFFECTIVE_CACHE_SIZE: 1536MB
ports:
- "54320:5432"
volumes:
- pgdata_local:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d:ro
container_name: postgres-local
networks:
app-network-local:
aliases:
- database
app:
build: .
container_name: app-backend-local
environment:
JAVA_TOOL_OPTIONS: "-Xmx2g -Xms1g"
FILE_STORAGE_PATH: /data/uploads
POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
POSTGRES_HOST: db
# Используем local профиль
SPRING_PROFILES_ACTIVE: local
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
depends_on:
- db
ports:
- "8080:8080"
networks:
app-network-local:
aliases:
- app
- backend
volumes:
- uploads_data_local:/data/uploads:rw
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/actuator/health" ]
interval: 30s
timeout: 10s
retries: 3
volumes:
pgdata_local:
uploads_data_local:
networks:
app-network-local:
driver: bridge
+49
View File
@@ -0,0 +1,49 @@
version: '3.9'
services:
storage:
image: alpine:latest
container_name: file-storage
networks:
- app-network
volumes:
- uploads_data:/storage:rw
command: tail -f /dev/null
restart: unless-stopped
app:
build: .
container_name: app-backend
environment:
JAVA_TOOL_OPTIONS: "-Xmx2g -Xms1g"
FILE_STORAGE_PATH: /data/uploads
# Данные для Yandex Cloud БД
YC_DB_USER: ${YC_DB_USER}
YC_DB_PASSWORD: ${YC_DB_PASSWORD}
# Используем prod профиль
SPRING_PROFILES_ACTIVE: prod
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
ports:
- "80:8080"
networks:
app-network:
aliases:
- app
- backend
volumes:
- uploads_data:/data/uploads:rw
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/actuator/health" ]
interval: 30s
timeout: 10s
retries: 3
volumes:
uploads_data:
networks:
app-network:
external: true
driver: bridge
+1 -32
View File
@@ -181,7 +181,7 @@ services:
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=15d'
- '--storage.tsdb.retention.time=15d' # Удерживать 15 дней
- '--web.enable-lifecycle'
healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
@@ -216,33 +216,6 @@ services:
networks:
- app-network
nexus:
image: sonatype/nexus3:latest
container_name: nexus
restart: unless-stopped
ports:
- "9000:8081"
volumes:
- nexus-data:/nexus-data
environment:
- INSTALL4J_ADD_VM_PARAMS=-Xms512m -Xmx1024m -XX:MaxDirectMemorySize=512m
networks:
- app-network
mongodb:
image: mongo:latest
container_name: mongo_ncp
restart: unless-stopped
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: mongo_db
MONGO_INITDB_ROOT_PASSWORD: mongo_ncp
volumes:
- mongodb_data:/data/db
networks:
- app-network
tempo:
image: grafana/tempo:2.4.1
container_name: tempo
@@ -294,8 +267,6 @@ services:
volumes:
pgdata:
mongodb_data:
name: mongodb_data
artifacts:
tempo_data:
loki_data:
@@ -304,8 +275,6 @@ volumes:
loki_rules:
uploads_data:
prometheus_data:
nexus-data:
driver: local
# postfix-data:
networks:
app-network:
+2
View File
@@ -0,0 +1,2 @@
YC_DB_USER=test
YC_DB_PASSWORD=testtest
+18 -110
View File
@@ -9,29 +9,14 @@ pipeline {
)
string(
name: 'PORT',
defaultValue: '3001',
defaultValue: '3000',
description: 'Порт для запуска экземпляра'
)
string(
name: 'PORT_DB',
defaultValue: '',
description: 'Порт для запуска БД'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
description: 'Сервер для деплоя'
)
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Имя сети (будет создана если не существует)'
)
choice(
name: 'SPRING_PROFILE',
choices: ['dev', 'prod'],
description: 'Профиль Spring для запуска'
)
}
stages {
@@ -51,34 +36,9 @@ pipeline {
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
rm -rf /opt/deployments/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
"
sshpass -p '${SSH_PASS}' scp -r ./* ${SSH_USER}@${params.SERVER}:/opt/deployments/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
def springProfile = params.SPRING_PROFILE
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
@@ -86,11 +46,10 @@ pipeline {
passwordVariable: 'SSH_PASS'
),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD'),
string(credentialsId: 'MONGO_USER', variable: 'MONGO_USER'),
string(credentialsId: 'MONGO_PASSWORD', variable: 'MONGO_PASSWORD')
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD')
]) {
sh """
# Тестируем подключение сначала
echo "Testing SSH connection..."
if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then
echo "SSH connection OK"
@@ -99,17 +58,12 @@ pipeline {
exit 1
fi
# Основная команда
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
cd /opt/deployments/${params.BRANCH}-${params.PORT}
echo '1. Проверяем/создаем сеть ${params.NETWORK}...'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
echo 'Сеть ${params.NETWORK} создана'
else
echo 'Сеть ${params.NETWORK} уже существует'
fi
echo '1. Создаем изолированную сеть для порта ${params.PORT}...'
docker network create app-network-${params.PORT} 2>/dev/null || echo 'Сеть уже существует'
echo '2. Останавливаем старые контейнеры порта ${params.PORT}...'
docker stop app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
@@ -118,68 +72,49 @@ pipeline {
docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker stop mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
docker rm mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
echo '3. Запускаем БД...'
docker run -d \\
--name postgres-${params.PORT} \\
--network ${params.NETWORK} \\
--network app-network-${params.PORT} \\
--network-alias no_copy_${params.PORT} \\
--network-alias database-${params.PORT} \\
--network-alias db-${params.PORT} \\
-p ${params.PORT}0:5432 \\
-e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_DB=no_copy_${params.PORT} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-v pgdata_${params.PORT}:/var/lib/postgresql/data \\
-e BUILD_TIME_BACK='${buildTime}' \\
postgres:17.7
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...'
echo '4. Запускаем storage...'
docker run -d \\
--name file-storage-${params.PORT} \\
--network ${params.NETWORK} \\
--network app-network-${params.PORT} \\
-v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null
echo '6. Ждем БД...'
echo '5. Ждем БД...'
sleep 15
echo '7. Собираем приложение...'
echo '6. Собираем приложение...'
docker build --no-cache -t app-backend-${params.PORT}:latest .
echo '8. Запускаем приложение...'
echo '7. Запускаем приложение...'
docker run -d \\
--name app-backend-${params.PORT} \\
--network ${params.NETWORK} \\
--network-alias app \\
--network-alias app-${params.PORT} \\
--network app-network-${params.PORT} \\
-p ${params.PORT}:8080 \\
-v uploads_data_${params.PORT}:/data/uploads:rw \\
-e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_DB=no_copy_${params.PORT} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\
-e MONGO_URI=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongodb-${params.PORT}:27017 \\
-e MONGO_DATABASE=no_copy \\
app-backend-${params.PORT}:latest
echo '9. Проверка...'
echo '8. Проверка...'
sleep 10
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
@@ -194,39 +129,12 @@ pipeline {
}
}
}
stage('Verify network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
echo 'Проверяем подключение к сети ${params.NETWORK}'
docker network inspect ${params.NETWORK} --format='{{.Name}}: {{range .Containers}}{{.Name}} {{end}}'
echo 'Проверяем алиас app для контейнера app-backend-${params.PORT}'
docker inspect app-backend-${params.PORT} | grep -A 5 'Aliases'
"
"""
}
}
}
}
}
post {
success {
script {
def appUrl = params.SPRING_PROFILE == 'prod' ? 'https://workspace.not-copy.com' : 'https://dev-workspace.not-copy.com'
echo "Deployment successful"
echo "Application URL: ${appUrl}"
}
echo "Deployment successful"
echo "Application URL: http://${params.SERVER}:${params.PORT}"
}
failure {
echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}"
+14 -82
View File
@@ -12,11 +12,6 @@ pipeline {
defaultValue: '2998',
description: 'Порт для запуска экземпляра'
)
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Docker сеть для подключения'
)
}
stages {
@@ -90,8 +85,6 @@ pipeline {
stage('Build on server') {
steps {
script {
def BUILD_TIME_FRONT = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
@@ -100,20 +93,23 @@ pipeline {
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
# 1. СОЗДАЕМ JSON ФАЙЛ (ЭТО РАБОТАЕТ 100%)
mkdir -p public
echo '{\\"buildTime\\": \\"${BUILD_TIME_FRONT}\\"}' > public/build-info.json
echo '=== Редактируем docker-compose.yml ==='
# 2. Меняем имя контейнера и порт
sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
# Меняем имя контейнера
sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
# 3. Собираем образ
docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
"
# Меняем порт (2998 на нужный порт)
sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
echo 'Проверяем изменения:'
grep -n 'container_name\\|ports' docker-compose.yml
echo '=== Строим образ ==='
docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
"
"""
}
}
@@ -194,70 +190,6 @@ pipeline {
}
}
}
// stage('Connect to network') {
// steps {
// script {
// withCredentials([
// usernamePassword(
// credentialsId: 'server-root-password',
// usernameVariable: 'SSH_USER',
// passwordVariable: 'SSH_PASS'
// )
// ]) {
// sh """
// sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
// echo 'Отключаем от всех сетей и подключаем к ${params.NETWORK}'
//
// # Отключаем от всех сетей (кроме bridge, host, none)
// current_networks=\$(docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}' | grep -v -E '^(bridge|host|none)\$' || true)
//
// for net in \$current_networks; do
// echo \"Отключаем от сети: \$net\"
// docker network disconnect \$net no-copy-frontend-${params.PORT} 2>/dev/null || true
// done
//
// # Подключаем к нужной сети
// docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
//
// echo 'Готово! Контейнер теперь в сети:'
// docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}'
// "
// """
// }
// }
// }
// }
stage('Connect to network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
echo 'Подключаем контейнер к сети ${params.NETWORK}'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
fi
if docker ps -a --format '{{.Names}}' | grep -q '^no-copy-frontend-${params.PORT}\$'; then
docker network disconnect ${params.NETWORK} no-copy-frontend-${params.PORT} 2>/dev/null || true
docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
echo 'Контейнер подключен к ${params.NETWORK}'
fi
"
"""
}
}
}
}
}
post {
-2
View File
@@ -34,7 +34,6 @@ pipeline {
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
@@ -93,7 +92,6 @@ pipeline {
-e POSTGRES_PASSWORD=$DB_PASSWORD \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=db \\
-e BUILD_TIME_BACK='${buildTime}' \\
--restart unless-stopped \\
app-backend:latest
+2 -3
View File
@@ -2,13 +2,12 @@ package ru.soune
interface Referral {
val userId: Long
val userId: String
val referralLink: String
val inviter: Long?
val inviter: String?
val currentLevel: String
val totalIncome: Int
val availableIncome: Int
val holdBalance: Int
val spentBalance: Int
val active: Boolean
}
+11 -11
View File
@@ -2,32 +2,32 @@ package ru.soune
interface ReferralRepo {
fun getUserReferralLink(userId: Long): String
fun getUserReferralLink(userId: String): String
fun getUserIdByLink(link: String): Long?
fun getUserIdByLink(link: String): String
fun getInviterIdForUser(userId: Long): Long?
fun getInviterIdForUser(userId: String): String?
fun getReferralLevelForUser(userId: Long): String
fun getReferralLevelForUser(userId: String): String
fun getReferralByUserId(userId: Long): Referral
fun getReferralByUserId(userId: String): Referral
fun getReferralInvitees(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
fun getReferralInvitees(userId: String, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
// создаем новую запись в БД
fun createReferralEntity(entity: Referral)
// прибавить к totalIncome и availableIncome значение transferAmount
fun increaseIncome(userId: Long, transferAmount: Int)
fun increaseIncome(userId: String, transferAmount: Int)
// установить active в true
fun activateUser(userId: Long): Boolean
fun activateUser(userId: String): Boolean
// посчитать записи, у которых inviter == userId и active == true
fun getActiveInviteeForUser(userId: Long): Int
fun getActiveInviteeForUser(userId: String): Int
// посчитать записи, у которых inviter == userId
fun getTotalInviteeForUser(userId: Long): Int
fun getTotalInviteeForUser(userId: String): Int
fun upgradeReferralLevelForUser(userId: Long, newLevelId: String)
fun upgradeReferralLevelForUser(userId: String, newLevelId: String)
}
+5 -8
View File
@@ -5,7 +5,7 @@ class ReferralService(
private val referralRepo: ReferralRepo,
) {
fun onRegister(userId: Long, inviterReferralLink: String?) {
fun onRegister(userId: String, inviterReferralLink: String?) {
val initialReferralLevel = referralLevelProvider.getInitialReferralLevelId()
val referralLink = referralRepo.getUserReferralLink(userId)
val inviter = inviterReferralLink?.let(referralRepo::getUserIdByLink)
@@ -18,14 +18,13 @@ class ReferralService(
override val totalIncome = 0
override val availableIncome = 0
override val holdBalance: Int = 0
override val spentBalance: Int = 0
override val active: Boolean = false
}
referralRepo.createReferralEntity(referral)
}
fun onUserAccountRefill(userId: Long, transferAmount: Int) {
fun onUserAccountRefill(userId: String, transferAmount: Int) {
val wasActivated = referralRepo.activateUser(userId)
val inviterId = referralRepo.getInviterIdForUser(userId) ?: return
val currentInviterLevel = referralRepo.getReferralLevelForUser(inviterId)
@@ -48,11 +47,11 @@ class ReferralService(
referralLevelProvider.getAvailableReferralLevels()
fun getReferralLevelForUser(userId: Long): ReferralLevel =
fun getReferralLevelForUser(userId: String): ReferralLevel =
referralRepo.getReferralLevelForUser(userId)
.let { referralLevelProvider.getReferralLevelById(it) }
fun getReferralStatForUser(userId: Long): ReferralStat {
fun getReferralStatForUser(userId: String): ReferralStat {
var referral = referralRepo.getReferralByUserId(userId)
return ReferralStat(
totalInvitee = referralRepo.getTotalInviteeForUser(userId),
@@ -60,11 +59,9 @@ class ReferralService(
totalIncome = referral.totalIncome,
availableIncome = referral.availableIncome,
holdBalance = referral.holdBalance,
referralLink = referral.referralLink,
spentBalance = referral.spentBalance,
)
}
fun getInviteeForUser(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
fun getInviteeForUser(userId: String, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
referralRepo.getReferralInvitees(userId, pageSize, pageNumber)
}
-2
View File
@@ -6,6 +6,4 @@ data class ReferralStat(
val totalIncome: Int,
val availableIncome: Int,
val holdBalance: Int,
val referralLink: String,
val spentBalance: Int,
)
@@ -2,12 +2,10 @@ package ru.soune.nocopy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableCaching
public class NoCopyApplication {
public static void main(String[] args) {
@@ -1,69 +0,0 @@
package ru.soune.nocopy.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
@Slf4j
public class YooKassaClient {
@Value("${yookassa.shop-id}")
private String shopId;
@Value("${yookassa.secret-key}")
private String secretKey;
private final HttpClient httpClient = HttpClient.newHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
public boolean createAutoPayment(String paymentMethodId, double amount, String description, String userId) {
try {
String url = "https://api.yookassa.ru/v3/payments";
String auth = shopId + ":" + secretKey;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
Map<String, Object> body = new HashMap<>();
body.put("amount", Map.of("value", String.format("%.2f", amount),
"currency", "RUB"));
body.put("payment_method_id", paymentMethodId);
body.put("description", description);
body.put("capture", true);
body.put("metadata", Map.of("user_id", userId, "auto_payment", "true"));
String requestBody = mapper.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Basic " + encodedAuth)
.header("Idempotence-Key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200 || response.statusCode() == 201) {
log.info("Auto payment created: {}", response.body());
return true;
} else {
log.error("Auto payment failed: {} - {}", response.statusCode(), response.body());
return false;
}
} catch (Exception e) {
log.error("Error creating auto payment", e);
return false;
}
}
}
@@ -1,29 +1,23 @@
package ru.soune.nocopy.configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.vrt.AudioFilePathProvider;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.audio.AudioLocalSearch;
import com.vrt.fileprotection.documents.DocumentLocalSearch;
import com.vrt.fileprotection.image.ImageLocalSearch;
import com.vrt.fileprotection.image.ImageUniqueCheck;
import com.vrt.fileprotection.image.phash.PerceptualHashHelper;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableAutoConfiguration
@AllArgsConstructor
@EnableCaching
public class ApplicationConfig {
@Bean
@@ -39,8 +33,7 @@ public class ApplicationConfig {
ImageUniqueCheck imageUniqueCheck,
ImageLocalSearch imageLocalSearch,
AudioLocalSearch audioLocalSearch,
AudioFilePathProvider audioFilePathProvider,
DocumentLocalSearch documentLocalSearch) {
AudioFilePathProvider audioFilePathProvider) {
return new com.vrt.NoCopyFileService(
Collections.emptyList(),
@@ -49,25 +42,8 @@ public class ApplicationConfig {
imageUniqueCheck,
imageLocalSearch,
audioLocalSearch,
audioFilePathProvider,
documentLocalSearch
audioFilePathProvider
);
}
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager(
"reverseImageSearch", "parsedImages");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(2_000)
.expireAfterWrite(1, TimeUnit.HOURS)
.expireAfterAccess(30, TimeUnit.MINUTES)
.softValues()
.recordStats()
);
return cacheManager;
}
}
@@ -1,23 +0,0 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
@@ -9,148 +9,33 @@ import java.util.Map;
@Configuration
public class HandlerConfig {
/*
RegRequestHandler
LoginRequestHandler
FileUploadHandler
FileEntityHandler
ImageFoundRequestHandler
VerifyRegisterUserHandler
TariffHandler
ReferralHandler
ResetPasswordHandler
MonitoringHandler
GlobalSearchHandler
ComplaintEntityHandler
ViolationNotionHandler
NotificationHandler
TokenOperationHandler
DockViewFileHandler
*/
/*
Убрать
LogoutRequestHandler logoutHandler
*/
@Bean
public Map<Integer, RequestHandler> handlers(
RegRequestHandler reg,
LoginRequestHandler login,
FileUploadHandler upload,
FileEntityHandler file,
LogoutRequestHandler logoutHandler,
ImageFoundRequestHandler imageFoundRequestHandler,
VerifyRegisterUserHandler verifyRegisterUser,
AuthRequestHandler authRequestHandler,
CompanyHandler companyHandler,
TariffHandler tariffHandler,
TariffInfoHandler tariffInfoHandler,
ReferralHandler referralHandler,
PaymentHandler paymentHandler,
CostHandler costHandler,
MonitoringHandler monitoringHandler,
ViolationHandler violationHandler,
ViolationStatisticsHandler violationStatisticsHandler,
GlobalSearchHandler globalSearchHandler,
ViolationNotionHandler violationNotionHandler,
ComplaintEntityHandler complaintEntityHandler,
UserInfoHandler userInfoHandler,
NotificationHandler notificationHandler,
UserVerificationHandler userVerificationHandler,
LawCaseHandler lawCaseHandler,
TokenOperationHandler tokenOperationHandler,
MonitoringStatisticHandler monitoringStatisticHandler,
DockViewFileHandler dockViewFileHandler
TariffInfoHandler tariffInfoHandler
) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
map.put(20002, reg);
map.put(20004, upload);
map.put(20005, file);
map.put(20006, logoutHandler);
map.put(20007, imageFoundRequestHandler);
map.put(20008, authRequestHandler);
map.put(20009, verifyRegisterUser);
map.put(30000, companyHandler);
map.put(30001, tariffHandler);
map.put(30002, tariffInfoHandler);
map.put(30003, referralHandler);
map.put(30005, paymentHandler);
map.put(30007, monitoringHandler);
map.put(30008, costHandler);
map.put(30009, violationHandler);
map.put(30010, violationStatisticsHandler);
map.put(30011, globalSearchHandler);
map.put(30012, violationNotionHandler);
map.put(30013, complaintEntityHandler);
map.put(30014, userInfoHandler);
map.put(30015, notificationHandler);
map.put(30016, userVerificationHandler);
map.put(30017, lawCaseHandler);
map.put(30018, tokenOperationHandler);
map.put(30027, monitoringStatisticHandler);
map.put(30028, dockViewFileHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> internalInfoHandler(
StatisticComplaintHandler statisticComplaintHandler,
StatisticUserFilesHandler statisticUserHandler,
StatisticTariffInfoFileHandler statisticTariffInfoFileHandler,
StatisticUserDynamicHandler statisticUserDynamicHandler,
StatisticProtectedFilesHandler statisticProtectedFilesHandler,
StatisticViolationHandler statisticViolationHandler,
StatisticSubscriberHandler statisticSubscriberHandler,
StatisticTokenHandler statisticTokenHandler,
StatisticIncomeHandler statisticIncomeHandler,
FileInteranlInfoHandler fileInfo,
TariffInformationHandler tariffInformationHandler,
UserInfoHandler userInfoHandler
)
{
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(30014, userInfoHandler);
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);
map.put(30029, statisticComplaintHandler);
map.put(40001, fileInfo);
map.put(40004, tariffInformationHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> internalControlHandler(
ControlComplaintHandler controlComplaintHandler,
FileInternalControlHandler fileInternalControlHandler,
TariffControlHandler tariffControlHandler,
UserInfoHandler userInfoHandler
)
{
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(30014, userInfoHandler);
map.put(30030, controlComplaintHandler);
map.put(40002, fileInternalControlHandler);
map.put(40003, tariffControlHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> authHandler(
RegRequestHandler reg,
LoginRequestHandler login,
DaDataHandler daDataHandler,
ResetPasswordHandler resetPasswordHandler
)
{
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
map.put(20002, reg);
map.put(30004, daDataHandler);
map.put(20010, resetPasswordHandler);
return map;
}
@@ -1,44 +0,0 @@
package ru.soune.nocopy.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.ImageResizeService;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Component
@Slf4j
public class ImageMigration {
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private ImageResizeService imageResizeService;
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
List<FileEntity> images = fileRepository.findByThumbnailPathIsNull();
for (FileEntity file : images) {
try {
Path path = Paths.get(file.getProtectedFilePath());
if (Files.exists(path)) {
byte[] data = Files.readAllBytes(path);
imageResizeService.generateSizes(file, data);
fileRepository.save(file);
}
} catch (Exception e) {
log.error("Error migrating {}: {}", file.getId(), e.getMessage());
}
}
}
}
@@ -9,7 +9,6 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -21,13 +20,7 @@ public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.defaultViewInclusion(true)
.autoDetectFields(true)
.failOnUnknownProperties(false)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.build();
ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
@@ -1,16 +0,0 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.soune.ReferralLevelProvider;
import ru.soune.ReferralRepo;
import ru.soune.ReferralService;
@Configuration
public class ReferralConfig {
@Bean
public ReferralService referralService(ReferralLevelProvider referralLevelProvider, ReferralRepo referralRepo) {
return new ReferralService(referralLevelProvider, referralRepo);
}
}
@@ -21,15 +21,4 @@ public class AsyncConfig {
executor.initialize();
return executor;
}
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("global-search-");
executor.initialize();
return executor;
}
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.configuration.file;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@Configuration
@ConfigurationProperties(prefix = "file.storage")
@Data
public class FileStorageConfig {
private String basePath;
private long maxFileSize;
private int chunkSize;
private int authTokenLifeHours;
@PostConstruct
public void init() throws IOException {
Files.createDirectories(Paths.get(basePath));
}
}
@@ -4,12 +4,16 @@ import lombok.AllArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.service.file.FileProcessingOrchestrator;
@Component
@AllArgsConstructor
public class NoCopyInitializer {
private final FileProcessingOrchestrator orchestrator;
@EventListener(ApplicationReadyEvent.class)
public void initializeOnStartup() {
orchestrator.initializeProcessingQueue();
}
}
@@ -1,26 +0,0 @@
package ru.soune.nocopy.configuration.search;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Data
@Component
@ConfigurationProperties(prefix = "search")
public class SearchProperties {
private Map<String, EngineConfig> engines;
private ProxyConfig proxy;
@Data
public static class EngineConfig {
private boolean enabled;
}
@Data
public static class ProxyConfig {
private boolean enabled;
private String host;
private int port;
}
}
@@ -1,40 +0,0 @@
package ru.soune.nocopy.configuration.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import ru.soune.nocopy.util.JwtAuthFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("api/v{version}/files/public-download/**").permitAll()
.requestMatchers("/api/files/public/**").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/file/link/**").permitAll()
.requestMatchers("/check/api/**").permitAll()
.requestMatchers("/internal/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@@ -3,16 +3,15 @@ package ru.soune.nocopy.controller;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult;
import com.vrt.fileprotection.audio.AudioCheckResult;
import com.vrt.fileprotection.documents.DocumentCheckResult;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
@@ -20,35 +19,33 @@ import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.docviewer.DocViewerRequest;
import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.FileEntity;
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.permission.PermissionType;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.handler.*;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.dockview.DockViewService;
import ru.soune.nocopy.service.file.*;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.util.FileUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@@ -60,36 +57,22 @@ public class ApiController {
private final Map<Integer, RequestHandler> handlers;
private final Map<Integer, RequestHandler> internalInfoHandler;
private final Map<Integer, RequestHandler> internalControlHandler;
private final Map<Integer, RequestHandler> authHandler;
private final FileEntityService fileEntityService;
private final AuthService authService;
private final FileSimilarityService fileSimilarityService;
private final FileEntityRepository fileEntityRepository;
private final NoCopyFileService noCopyFileService;
private final HttpServletRequest httpServletRequest;
private final FileUtil fileUtil;
private final ProtectionsLimitService protectionsLimitService;
private final AuthTokenRepository authTokenRepository;
private final UserRepository userRepository;
private final CheckCounterService checkCounterService;
private final CloudStorageService cloudStorageService;
private final ZipService zipService;
private final DockViewService dockViewService;
@PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) {
@@ -131,136 +114,6 @@ public class ApiController {
}
}
@PostMapping("/auth")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request) {
Integer msgId = request.getMsgId();
BaseResponse response;
try {
RequestHandler handler = authHandler.get(msgId);
if (handler == null) {
response = new BaseResponse(msgId,
MessageCode.MSG_ID_NOT_FOUND.getCode(),
MessageCode.MSG_ID_NOT_FOUND.getDescription(),
new HashMap<>());
} else {
response = handler.handle(request);
}
return ResponseEntity.ok().body(response);
} catch (ValidationException e) {
return createValidationErrorResponse(e.getBindingResult(), e.getMsgId());
} catch (UserNotFoundException e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotFoundAuthToken e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotValidFieldException e) {
throw e;
} catch (Exception e) {
log.error("Handler execution failed for msgId: {}", msgId, e);
BaseResponse errorResponse = new BaseResponse(msgId,
MessageCode.INVALID_JSON_BODY.getCode(),
MessageCode.INVALID_JSON_BODY.getDescription(),
new HashMap<>());
return ResponseEntity.ok().body(errorResponse);
}
}
@PostMapping("/internal/data-info")
public ResponseEntity<?> handleInternalInfoPostRequest(@RequestBody BaseRequest request) {
Integer msgId = request.getMsgId();
BaseResponse response;
RequestHandler internalHandler = internalInfoHandler.get(msgId);
try {
if (internalHandler == null) {
response = new BaseResponse(msgId,
MessageCode.MSG_ID_NOT_FOUND.getCode(),
MessageCode.MSG_ID_NOT_FOUND.getDescription(),
new HashMap<>());
} else {
response = internalHandler.handle(request);
}
return ResponseEntity.ok().body(response);
} catch (Exception e) {
log.error("Handler execution failed for msgId: {}", msgId, e);
BaseResponse errorResponse = new BaseResponse(msgId,
MessageCode.INVALID_JSON_BODY.getCode(),
MessageCode.INVALID_JSON_BODY.getDescription(),
new HashMap<>());
return ResponseEntity.ok().body(errorResponse);
}
}
@GetMapping("/internal/data-download/{fileId}")
public ResponseEntity<byte[]> getPublicFile(@PathVariable String fileId) {
try {
FileEntity fileEntity = fileEntityRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found in DB: " + fileId));
MediaType mediaType = getMediaType(fileEntity);
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
byte[] fileBytes;
try (InputStream inputStream = cloudStorageService.readFileFromStorage(fileEntity.getFilePath())) {
fileBytes = IOUtils.toByteArray(inputStream);
}
return ResponseEntity.ok()
.contentType(mediaType)
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(fileBytes);
} catch (FileNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (NoSuchKeyException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/internal/data-control")
public ResponseEntity<?> handleInternalControlPostRequest(@RequestBody BaseRequest request) {
Integer msgId = request.getMsgId();
BaseResponse response;
RequestHandler internalHandler = internalControlHandler.get(msgId);
try {
if (internalHandler == null) {
response = new BaseResponse(msgId,
MessageCode.MSG_ID_NOT_FOUND.getCode(),
MessageCode.MSG_ID_NOT_FOUND.getDescription(),
new HashMap<>());
} else {
response = internalHandler.handle(request);
}
return ResponseEntity.ok().body(response);
} catch (Exception e) {
log.error("Handler execution failed for msgId: {}", msgId, e);
BaseResponse errorResponse = new BaseResponse(msgId,
MessageCode.INVALID_JSON_BODY.getCode(),
MessageCode.INVALID_JSON_BODY.getDescription(),
new HashMap<>());
return ResponseEntity.ok().body(errorResponse);
}
}
@PostMapping("/v{version}/files/chunk")
public ResponseEntity<BaseResponse> uploadChunk(
@PathVariable("version") int version,
@@ -300,181 +153,46 @@ public class ApiController {
MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(),
duplicateData
));
} 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());
}
}
@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) {
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 = (Long) httpServletRequest.getAttribute("userId");
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")
public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version,
@PathVariable String fileId,
@RequestParam(value = "auth_token",required = false) String authToken,
@RequestParam(required = false) List<String> similarityLevels,
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
SimilarityFilter filter = SimilarityFilter.builder()
.similarityLevels(similarityLevels)
.build();
Page<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable, authToken);
try {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
String messageDesc;
MessageCode success;
if (fileEntity == null) {
return ResponseEntity.ok().body(new BaseResponse(0, MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), null));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
BaseResponse response;
if ("image".equals(fileEntity.getMimeType())) {
Page<SimilarFileDTO> similarFilesPage = fileSimilarityService.findSimilarFiles(
fileId, similarityLevels, pageable, userId);
response = buildSuccessResponse(similarFilesPage, MessageCode.SIMILAR_FILES_FOUND);
} else {
List<SimilarFileDTO> results = processNonImageFile(fileEntity, true);
log.info("results: {}", results);
response = buildSuccessResponse(results, MessageCode.SIMILAR_FILES_FOUND);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
}
return ResponseEntity.ok(response);
} catch(IllegalArgumentException e) {
log.error("Error with file extensions : {}", e.getMessage(), e);
return ResponseEntity.ok().body(new BaseResponse(MessageCode.FILE_FOR_SEARCH_NOT_VALID.getCode(),
MessageCode.FILE_FOR_SEARCH_NOT_VALID.getCode(),
MessageCode.FILE_FOR_SEARCH_NOT_VALID.getDescription(), null));
} catch (Exception e) {
log.error("Error finding similar files: {}", e.getMessage(), e);
return ResponseEntity.ok().body(new BaseResponse(MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), null));
}
}
private List<SimilarFileDTO> processNonImageFile(FileEntity fileEntity, boolean cloud) throws Exception {
List<SimilarFileDTO> results = new ArrayList<>();
File file = null;
FileEntity duplicateByHash = fileSimilarityService.findDuplicateByHash(fileEntity.getFilePath(),
fileEntity.getMimeType(), fileEntity.getUserId(), cloud);
log.info("duplicateByHash: {}", duplicateByHash);
if (duplicateByHash != null) {
results.add(fileSimilarityService.buildDTO(duplicateByHash));
return results;
if (similarFiles.isEmpty()) {
messageDesc = MessageCode.FILE_NOT_FOUND.getDescription();
success = MessageCode.SUCCESS;
} else {
messageDesc = MessageCode.SIMILAR_FILES_FOUND.getDescription();
success = MessageCode.SUCCESS;
}
try {
file = cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
FileProtector.Type type = "document".equals(fileEntity.getMimeType()) ?
FileProtector.Type.DOC : FileProtector.Type.AUDIO;
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, type);
log.info("checkResult: {}", checkResult);
switch (checkResult) {
case DocumentCheckResult.Success success -> {
FileProtector.FileInfo info = success.getInfo();
FileEntity entity = fileEntityRepository.findByFileId(info.getId());
if (entity != null) {
results.add(fileSimilarityService.buildDTO(entity));
}
}
case AudioCheckResult.Success success -> {
FileProtector.FileInfo info = success.getInfo();
FileEntity entity = fileEntityRepository.findByFileId(info.getId());
if (entity != null) {
results.add(fileSimilarityService.buildDTO(entity));
}
}
case DocumentCheckResult.Failed failed ->
log.warn("Document check failed: {}", failed.getMessage());
case AudioCheckResult.Failed failed ->
log.warn("Audio check failed: {}", failed.getMessage());
default -> log.warn("Unexpected result type: {}", checkResult.getClass().getSimpleName());
}
} catch (Exception e) {
throw new Exception("Files for search not valid");
} finally {
if (file != null && file.exists()) {
file.delete();
}
}
return results;
}
private BaseResponse buildSuccessResponse(Page<SimilarFileDTO> page, MessageCode messageCode) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("content", page.getContent());
responseData.put("page", page.getNumber());
responseData.put("size", page.getSize());
responseData.put("totalElements", page.getTotalElements());
responseData.put("totalPages", page.getTotalPages());
responseData.put("hasNext", page.hasNext());
responseData.put("hasPrevious", page.hasPrevious());
responseData.put("content", similarFiles.getContent());
responseData.put("page", similarFiles.getNumber());
responseData.put("size", similarFiles.getSize());
responseData.put("totalElements", similarFiles.getTotalElements());
responseData.put("totalPages", similarFiles.getTotalPages());
responseData.put("hasNext", similarFiles.hasNext());
responseData.put("hasPrevious", similarFiles.hasPrevious());
return new BaseResponse(20004, messageCode.getCode(),
messageCode.getDescription(), responseData);
}
private BaseResponse buildSuccessResponse(List<SimilarFileDTO> list, MessageCode messageCode) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("content", list);
responseData.put("totalElements", list.size());
return new BaseResponse(20004, messageCode.getCode(),
messageCode.getDescription(), responseData);
return ResponseEntity.ok()
.body(new BaseResponse(20004, success.getCode(), messageDesc, responseData));
}
@GetMapping("/v{version}/files/progress/{uploadId}")
@@ -573,104 +291,34 @@ public class ApiController {
}
}
@GetMapping("/v{version}/files/public-download/{fileId}")
public ResponseEntity<?> downloadFile(
@PathVariable(required = false) String fileId, @PathVariable(required = false) Integer version) {
FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
FileStatus status = entityResponse.getStatus();
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
status.equals(FileStatus.REMOVED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_IS_BLOCKED.getCode(),
MessageCode.FILE_IS_BLOCKED.getDescription(),
errorData));
}
if (!entityResponse.getPermissions().get(PermissionType.DOWNLOAD)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getCode(),
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getDescription(),
errorData));
}
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(bytes);
}
@GetMapping("/v{version}/files/download/{fileId}")
public ResponseEntity<?> downloadFile(
@PathVariable(required = false) String fileId,
@PathVariable(required = false) Integer version,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
if (tokenHeader == null) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
errorData.put("fileId", fileId);
errorData.put("version", version);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.TOKEN_IS_NULL.getCode(),
MessageCode.TOKEN_IS_NULL.getDescription(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
Long userId = authService.useUserAuthToken(tokenHeader);
FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
FileStatus status = entityResponse.getStatus();
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
status.equals(FileStatus.REMOVED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_IS_BLOCKED.getCode(),
MessageCode.FILE_IS_BLOCKED.getDescription(),
errorData));
}
if (!entityResponse.getPermissions().get(PermissionType.DOWNLOAD)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getCode(),
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getDescription(),
errorData));
}
User fileUser = userRepository.findById(entityResponse.getUserId()).orElseThrow();
User user = userRepository.findById(userId).orElseThrow();
Company companyUser = user.getCompany();
if (companyUser != null && !fileUser.getCompany().getId().equals(companyUser.getId())) {
if (!entityResponse.getUserId().equals(userId)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.USER_NOT_HAD_PERMISSION.getCode(),
MessageCode.USER_NOT_HAD_PERMISSION.getDescription(),
errorData));
}
if (companyUser == null && !entityResponse.getUserId().equals(userId)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.USER_NOT_HAD_PERMISSION.getCode(),
MessageCode.USER_NOT_HAD_PERMISSION.getDescription(),
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
@@ -679,18 +327,45 @@ public class ApiController {
errorData.put("file_status", entityResponse.getStatus());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DELETE.getCode(),
MessageCode.FILE_DELETE.getDescription(),
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
if (!entityResponse.isExistsOnDisk()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("onDisk", entityResponse.isExistsOnDisk());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
Path filePath = Paths.get(entityResponse.getProtectedFilePath());
// Resource resource = new UrlResource(filePath.toUri());
FileSystemResource resource = new FileSystemResource(filePath);
long fileSize = Files.size(filePath);
if (!resource.exists()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("resource", resource.exists());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
String contentType = determineContentType(filePath);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(fileSize)
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(bytes);
"attachment; filename=\"" + entityResponse.getOriginalFileName() + "\"")
.body(resource);
} catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", fileId);
@@ -699,87 +374,98 @@ public class ApiController {
MessageCode.FILE_NOT_FOUND.getCode(),
MessageCode.FILE_NOT_FOUND.getDescription(),
errorData));
} catch (NotFoundAuthToken e) {
} catch (NotFoundAuthToken | IOException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
errorData.put("fileId", fileId);
errorData.put("version", version);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
errorData));
}
}
@PostMapping("/file/link")
public ResponseEntity<?> downloadFile(@RequestBody DocViewerRequest viewerRequest) {
try {
Long userId = httpServletRequest.getAttribute("userId") == null ? 0L:
(Long) httpServletRequest.getAttribute("userId");
@GetMapping("/protect/{fileId}")
public ResponseEntity<?> protect( @PathVariable(required = false) String fileId) {
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (viewerRequest.getFileId() == null) {
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.NOT_VALID_FIELD.getCode(),
MessageCode.NOT_VALID_FIELD.getDescription(),
"file_id is required"));
}
FileEntityResponse entityResponse = fileEntityService.getById(viewerRequest.getFileId(), 0);
FileStatus status = entityResponse.getStatus();
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
status.equals(FileStatus.REMOVED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.FILE_IS_BLOCKED.getCode(),
MessageCode.FILE_IS_BLOCKED.getDescription(),
errorData));
}
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
dockViewService.createDockView(viewerRequest, userId);
String filename = entityResponse.getFileName();
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8)
.replace("+", "%20");
String safeFilename = filename.replaceAll("[^\\x00-\\x7F]", "_");
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + safeFilename + "\"; filename*=UTF-8''" + encodedFilename)
.body(bytes);
} catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", viewerRequest.getFileId());
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.FILE_NOT_FOUND.getCode(),
MessageCode.FILE_NOT_FOUND.getDescription(),
errorData));
} catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", viewerRequest.getFileId());
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
errorData));
if (!optionalFileEntity.isPresent()) {
return ResponseEntity.notFound().build();
}
FileEntity fileEntity = optionalFileEntity.get();
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity);
noCopyFileService.addFile(fileInfo);
fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING);
fileEntityRepository.save(fileEntity);
return ResponseEntity.ok().build();
}
@GetMapping("/check/{fileId}/{type}")
public ResponseEntity<?> check(@PathVariable(required = false) String fileId,
@PathVariable(required = false) String type) {
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (!optionalFileEntity.isPresent()) {
return ResponseEntity.notFound().build();
}
FileEntity fileEntity = optionalFileEntity.get();
Path path = Paths.get(fileEntity.getProtectedFilePath());
File file = path.toFile();
NoCopyCheckResult noCopyCheckResult = noCopyFileService.checkFile(file,
FileProtector.Type.valueOf(type.toUpperCase()));
return ResponseEntity.ok().body(noCopyCheckResult);
}
@GetMapping("/check/file_stats")
public ResponseEntity<?> checkFileProtectStats() {
Long userId = (Long) httpServletRequest.getAttribute("userId");
public ResponseEntity<?> checkFileProtectStats(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Map<String, Object> fileTypeStats = protectionsLimitService.getFileTypeStats(userId);
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
Map<String, Object> fileTypeStats = protectionsLimitService.getFileTypeStats(authToken.getUser().getId());
return ResponseEntity.ok().body(fileTypeStats);
}
private boolean hasDuplicate(List<SimilarFileDTO> similarFiles) {
return similarFiles.stream().anyMatch(f -> f.getHammingDistance() <= 5);
}
private ResponseEntity<BaseResponse> handleDuplicate(FileEntity fileEntity, List<SimilarFileDTO> similarFiles)
throws IOException {
fileEntityService.deleteFromDisk(fileEntity);
fileEntityService.softDeleteFileWithHash(fileEntity);
Optional<FileEntity> originalFile = fileEntityRepository.findById(similarFiles.get(0).getFileId());
if (originalFile.isPresent()) {
Map<String, String> duplicateInfo = Map.of(
"duplicate_file_id", originalFile.get().getId(),
"owner_user_id", String.valueOf(originalFile.get().getUserId()));
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
"Failed to upload chunk, duplicate",
duplicateInfo));
}
return null;
}
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
String fileId) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
@@ -837,22 +523,12 @@ public class ApiController {
return errorDetail;
}
private MediaType getMediaType(FileEntity fileEntity) {
try {
return MediaType.parseMediaType(fileEntity.getMimeType());
} catch (InvalidMediaTypeException e) {
String extension = fileEntity.getFileExtension().toLowerCase();
switch (extension) {
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
case "png":
return MediaType.IMAGE_PNG;
case "pdf":
return MediaType.APPLICATION_PDF;
default:
return MediaType.APPLICATION_OCTET_STREAM;
}
private String determineContentType(Path filePath) throws IOException {
String contentType = Files.probeContentType(filePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
return contentType;
}
}
@@ -1,15 +1,12 @@
package ru.soune.nocopy.controller;
import com.google.common.io.ByteStreams;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus;
@@ -18,125 +15,61 @@ import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ZipService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.util.FileUtil;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import ru.soune.nocopy.service.file.FileStorageService;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
@RestController
@RequestMapping("/api/files")
@Slf4j
@AllArgsConstructor
public class FileController {
@Autowired
private FileStorageService fileStorageService;
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private CheckCounterService checkCounterService;
@Autowired
private UserRepository userRepository;
private NoCopyFileService noCopyFileService;
private FileUtil fileUtil;
private ZipService zipService;
private CloudStorageService cloudStorageService;
@GetMapping("/public/{fileId}")
public ResponseEntity<StreamingResponseBody> getPublicFile(@PathVariable String fileId) {
try {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found in DB: " + fileId));
MediaType mediaType = getMediaType(fileEntity);
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = cloudStorageService.readFileFromStorage(fileEntity.getFilePath())) {
ByteStreams.copy(inputStream, outputStream);
} catch (NoSuchKeyException e) {
log.error("File disappeared from S3 during streaming: {}", fileId, e);
throw new IOException("File not found in storage", e);
}
};
return ResponseEntity.ok()
.contentType(mediaType)
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(responseBody);
} catch (FileNotFoundException e) {
log.warn("File not found in DB: {}", fileId);
return ResponseEntity.notFound().build();
} catch (NoSuchKeyException e) {
log.warn("File not found in S3: {}", fileId);
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (Exception e) {
log.error("Error reading file: {}", fileId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private MediaType getMediaType(FileEntity fileEntity) {
try {
return MediaType.parseMediaType(fileEntity.getMimeType());
} catch (InvalidMediaTypeException e) {
String extension = fileEntity.getFileExtension().toLowerCase();
switch (extension) {
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
case "png":
return MediaType.IMAGE_PNG;
case "pdf":
return MediaType.APPLICATION_PDF;
default:
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}
@GetMapping("/protected/{fileId}/{type}")
public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId, @PathVariable String type) throws IOException {
public ResponseEntity<Resource> getPublicFile(
@PathVariable String fileId) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath();
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
// InputStream file = cloudStorageService.readFileFromStorage(filePath);
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + fileEntity.getOriginalFileName() + "\"")
.body(resource);
}
// if (file == null || !file.exists()) {
// log.error("File not found in storage: {}", fileEntity.getFilePath());
// return ResponseEntity.notFound().build();
// }
@GetMapping("/protected/{fileId}")
public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getProtectedFilePath());
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected");
}
String fileName = fileEntity.getOriginalFileName().replace("." +
fileEntity.getFileExtension(), "") + "_nocopy_protected" +
"." + fileEntity.getFileExtension();
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20");
InputStreamResource resource = new InputStreamResource(cloudStorageService.readFileFromStorage(filePath));
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename*=UTF-8''" + encodedFileName)
"inline; filename=\"" + fileEntity.getOriginalFileName() + "\"")
.body(resource);
}
@@ -156,33 +89,4 @@ public class FileController {
.ok()
.body(protectedFileCheck);
}
@PostMapping("protect/{fileId}/{convertTo}")
public ResponseEntity<FileProtector.FileInfo> convert(@PathVariable String fileId, @PathVariable String convertTo) {
FileEntity file = fileRepository.findByFileId(fileId);
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(file, convertTo);
noCopyFileService.addFile(fileInfo);
return ResponseEntity
.ok()
.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();
}
}
}
@@ -1,116 +0,0 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.search.GlobalSearchStartRequest;
import ru.soune.nocopy.dto.search.GlobalSearchStartResponse;
import ru.soune.nocopy.dto.search.GlobalSearchStatusResponse;
import ru.soune.nocopy.dto.violation.FileViolationSummaryDTO;
import ru.soune.nocopy.dto.violation.ViolationRequest;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.search.GlobalSearchTask;
import ru.soune.nocopy.entity.search.SearchStatus;
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
import ru.soune.nocopy.service.search.GlobalSearchService;
import ru.soune.nocopy.service.violation.ViolationService;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/api/v1/global-search")
@RequiredArgsConstructor
public class GlobalSearchController {
private final GlobalSearchService globalSearchService;
private final GlobalSearchTaskRepository searchTaskRepository;
private final ViolationService violationService;
private final HttpServletRequest httpServletRequest;
@PostMapping("/start")
public ResponseEntity<?> startSearch(
@RequestBody GlobalSearchStartRequest request) {
GlobalSearchStartResponse response = new GlobalSearchStartResponse();
Long userId = (Long) httpServletRequest.getAttribute("userId");
List<FileEntity> filesToProcess = globalSearchService.getFilesToProcess(request, userId);
if (filesToProcess.isEmpty()) return ResponseEntity.ok().body("Files for search not found");
String taskId = globalSearchService.startSearch(request, userId, filesToProcess);
response.setTaskId(taskId);
response.setStatus(SearchStatus.ACCEPTED.name());
response.setTotalFiles(filesToProcess.size());
return ResponseEntity.ok(response);
}
@GetMapping("/actual-task")
public ResponseEntity<?> getStatus() {
Optional<GlobalSearchTask> taskOptional;
Long userId = (Long) httpServletRequest.getAttribute("userId");
taskOptional = searchTaskRepository.findByUserIdAndStatus(userId,
"PROCESSING");
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
return ResponseEntity.ok(taskOptional.get());
}
@GetMapping("/violation-summary")
public ResponseEntity<?> getSummary() {
List<FileViolationSummaryDTO> fileViolationsSummary;
Long userId = (Long) httpServletRequest.getAttribute("userId");
fileViolationsSummary = violationService.getFileViolationsSummary(userId);
if (fileViolationsSummary.isEmpty()) return ResponseEntity.ok().body("Violations summary not found");
return ResponseEntity.ok(fileViolationsSummary);
}
@PostMapping("/violation-summary-with-files")
public ResponseEntity<?> getViolations(@RequestBody ViolationRequest request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
Page<FileViolationSummaryDTO> result = violationService.getFileViolationsSummary(
userId, request.getFileName(), request.getSortDirection(),
request.getPage(), request.getSize(), request.getStartDate(), request.getEndDate());
return ResponseEntity.ok(result);
}
@GetMapping("/status/{taskId}")
public ResponseEntity<?> getStatus(@PathVariable String taskId) {
GlobalSearchStatusResponse response;
Long userId = (Long) httpServletRequest.getAttribute("userId");
Optional<GlobalSearchTask> taskOptional = searchTaskRepository.findByTaskIdAndUserId(taskId, userId);
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
GlobalSearchTask task = taskOptional.orElseThrow();
int progress = task.getTotalFiles() > 0 ? task.getProcessedFiles() * 100 / task.getTotalFiles() : 0;
response = new GlobalSearchStatusResponse();
response.setTaskId(taskId);
response.setStatus(task.getStatus());
response.setProgress(progress);
return ResponseEntity.ok(response);
}
}
@@ -1,6 +1,5 @@
package ru.soune.nocopy.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@@ -16,14 +15,6 @@ public class HealtCheckController {
return HttpStatus.OK;
}
@Value("${BUILD_TIME_BACK:unknown}")
private String buildTimeBack;
@GetMapping("/build")
public BuildInfo getBuildInfo() {
return new BuildInfo(buildTimeBack);
}
@GetMapping("/api/debug/memory")
public Map<String, String> getMemoryInfo() {
Runtime runtime = Runtime.getRuntime();
@@ -41,12 +32,4 @@ public class HealtCheckController {
return info;
}
static class BuildInfo {
public String buildTimeBack;
public BuildInfo(String buildTimeBack) {
this.buildTimeBack = buildTimeBack;
}
}
}
@@ -1,83 +0,0 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.soune.nocopy.dto.docviewer.DockViewResponse;
import ru.soune.nocopy.entity.docviewer.DockView;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.migration.S3MigrationTool;
import ru.soune.nocopy.repository.DockViewRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.geo.GeoCountryService;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/admin/migration")
@RequiredArgsConstructor
@Slf4j
public class MigrationController {
private final S3MigrationTool migrationTool;
private final DockViewRepository dockViewRepository;
private final GeoCountryService geoCountryService;
private final UserRepository userRepository;
private final FileEntityRepository fileEntityRepository;
@PostMapping("/start")
public ResponseEntity<String> startMigration() {
log.info("Migration triggered via API");
CompletableFuture.runAsync(() -> {
try {
migrationTool.migrationFilesToS3();
} catch (Exception e) {
log.error("Migration failed", e);
}
});
return ResponseEntity.ok("Migration started. Check logs for progress.");
}
@PostMapping("/status")
public ResponseEntity<String> getStatus() {
return ResponseEntity.ok("Check application logs and migration_failed.txt for details");
}
@PostMapping("/fill-dock-view")
public ResponseEntity<String> fillDockView() {
List<DockView> dockViews = dockViewRepository.findAll();
for (DockView dockView: dockViews) {
String viewerIp = dockView.getViewerIp();
Long viewerId = dockView.getViewerId();
String geoEnName = geoCountryService.getCityByIp(viewerIp, "en") + "/" +
geoCountryService.getCountryByIp(viewerIp, "en");
String geoRuName = geoCountryService.getCityByIp(viewerIp, "ru") + "/" +
geoCountryService.getCountryByIp(viewerIp, "ru");
User user = userRepository.findById(viewerId).orElse(null);
FileEntity file = fileEntityRepository.findByFileId(dockView.getFileId());
dockView.setGeoNameRu(geoRuName);
dockView.setGeoNameEn(geoEnName);
dockView.setViewerName(user == null ? "guest" : user.getFullName());
dockView.setDocName(file.getOriginalFileName());
dockViewRepository.save(dockView);
}
return ResponseEntity.ok("fill old dock view");
}
}
@@ -1,123 +0,0 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.entity.payment.Payment;
import ru.soune.nocopy.exception.PaymentNotFoundException;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.service.payment.PaymentService;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/payments")
@RequiredArgsConstructor
@Slf4j
public class PaymentController {
private final PaymentService paymentService;
private final HttpServletRequest httpServletRequest;
@PostMapping("/create")
public ResponseEntity<?> createPayment(@RequestParam String email, @RequestParam Long tariffId,
@RequestParam String operationType, @RequestParam String operationUuid) {
try {
Payment payment = paymentService.createPayment(email, tariffId, operationType, operationUuid);
return ResponseEntity.ok(payment);
} catch (TariffNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "Tariff not found", "message", e.getMessage()));
} catch (UserNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "User not found", "message", e.getMessage()));
} catch (Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Internal server error", "message", e.getMessage()));
}
}
@PostMapping("webhook/yookassa")
public void handleYooKassaNotification(@RequestBody Map<String, Object> notification) {
try {
paymentService.handlePaymentNotification(notification);
log.info("Notification from yookassa: " + notification);
} catch (PaymentNotFoundException e) {
log.error("Error processing payment notification", e);
}
}
@PostMapping("/auto-renewal")
public ResponseEntity<?> disableAutoRenewal(@RequestParam(value = "renewal") Boolean renewal,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
paymentService.changeAutoRenewal(userId, renewal);
return ResponseEntity.ok(Map.of("message", "Auto-renewal changed:" + renewal));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
}
}
@DeleteMapping("/methods/{paymentMethodId}")
public ResponseEntity<?> deletePaymentMethod(@RequestHeader(value = "Authorization", required = false) String tokenHeader,
@PathVariable String paymentMethodId) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
paymentService.deletePaymentMethod(userId, paymentMethodId);
return ResponseEntity.ok(Map.of("message", "Payment method deleted"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/methods")
public ResponseEntity<?> getPaymentMethods(@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
return ResponseEntity.ok(paymentService.getUserPaymentMethods(userId));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
}
}
}
@@ -1,201 +0,0 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.payout.*;
import ru.soune.nocopy.entity.payout.BankTransferPayoutMethod;
import ru.soune.nocopy.entity.payout.CardPayoutMethod;
import ru.soune.nocopy.entity.payout.PayoutRequest;
import ru.soune.nocopy.entity.payout.PayoutType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.InsufficientFundsException;
import ru.soune.nocopy.exception.InvalidPayoutMethodException;
import ru.soune.nocopy.exception.PendingPayoutExistsException;
import ru.soune.nocopy.service.payout.PayoutMethodService;
import ru.soune.nocopy.service.payout.PayoutRequestService;
import ru.soune.nocopy.service.user.UserService;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/payouts")
@RequiredArgsConstructor
@Slf4j
public class PayoutController {
private final PayoutRequestService payoutRequestService;
private final PayoutMethodService payoutMethodService;
private final HttpServletRequest httpServletRequest;
private final UserService userService;
/*
Create payout
*/
@PostMapping("/create-request")
public ResponseEntity<?> createPayoutRequest(@Valid @RequestBody PayoutRequestDTO request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found with id: " + userId);
}
try {
PayoutRequest payoutRequest = payoutRequestService.createPayoutRequest(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED).body(payoutRequest);
} catch (InsufficientFundsException | PendingPayoutExistsException | InvalidPayoutMethodException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
}
}
/*
Completed payout
*/
@PostMapping("/requests/{requestId}/process")
public ResponseEntity<PayoutRequest> processPayoutRequest(@PathVariable Long requestId) {
PayoutRequest processed = payoutRequestService.processRequest(requestId);
return ResponseEntity.ok(processed);
}
/*
Cancel payout
*/
@PostMapping("/requests/{requestId}/cancel")
public ResponseEntity<PayoutRequest> cancelPayoutRequest(@PathVariable Long requestId) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
PayoutRequest cancelled = payoutRequestService.cancelPayoutRequest(user.getId(), requestId);
return ResponseEntity.ok(cancelled);
}
/*
Get user payouts
*/
@GetMapping("/requests")
public ResponseEntity<List<PayoutRequest>> getUserRequests() {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(payoutRequestService.getUserPayoutRequests(user.getId()));
}
/*
Get payout by id
*/
@GetMapping("/requests/{requestId}")
public ResponseEntity<PayoutRequest> getPayoutRequest(@PathVariable Long requestId) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(payoutRequestService.getPayoutRequest(user.getId(), requestId));
}
/*
Get payout by id
*/
@GetMapping("user/payout-methods")
public ResponseEntity<List<PayoutMethodDTO>> getUserMethods() {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(payoutMethodService.getUserMethods(user.getId()));
}
/*
Get payout-methods
*/
@GetMapping("/payout-methods")
public ResponseEntity<List<PayoutMethodTypeDTO>> getAvailableMethodTypes() {
List<PayoutMethodTypeDTO> types = Arrays.stream(PayoutType.values())
.map(type -> PayoutMethodTypeDTO.builder()
.code(type.name())
.name(type.getDisplayName())
.build())
.collect(Collectors.toList());
return ResponseEntity.ok(types);
}
/*
add card method by userId
*/
@PostMapping("/payout-method/card")
public ResponseEntity<PayoutMethodDTO> addCardMethod(@Valid @RequestBody AddCardRequest request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
CardPayoutMethod method = payoutMethodService.addCardMethod(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(payoutMethodService.convertToDto(method));
}
/*
add bank payout-method
*/
@PostMapping("/payout-method/bank-transfer")
public ResponseEntity<PayoutMethodDTO> addBankTransferMethod(@Valid @RequestBody AddBankTransferRequest request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
BankTransferPayoutMethod method = payoutMethodService.addBankTransferMethod(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(payoutMethodService.convertToDto(method));
}
/*
set default bank payout-method
*/
@PutMapping("/payout-method/{methodId}/default")
public ResponseEntity<Void> setDefaultMethod(@PathVariable Long methodId) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
payoutMethodService.setDefaultMethod(user.getId(), methodId);
return ResponseEntity.ok().build();
}
}
@@ -1,58 +0,0 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.configuration.search.SearchProperties;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.search.config.SearchSettingsDto;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/search/settings")
@RequiredArgsConstructor
public class SearchSettingsController {
private final SearchProperties searchProperties;
@GetMapping
public ResponseEntity<?> getSettings() {
SearchSettingsDto dto = new SearchSettingsDto();
dto.setEngines(Map.of(
"yandex", searchProperties.getEngines().get("yandex").isEnabled(),
"google", searchProperties.getEngines().get("google").isEnabled()));
dto.setProxyEnabled(searchProperties.getProxy().isEnabled());
return ResponseEntity.ok().body(BaseResponse.builder()
.messageDesc("Success")
.messageBody(dto)
.build());
}
@PutMapping
public ResponseEntity<?> updateSettings(@RequestBody SearchSettingsDto settings) {
if (settings.getEngines() != null) {
settings.getEngines().forEach((key, value) -> {
if (searchProperties.getEngines().containsKey(key)) {
searchProperties.getEngines().get(key).setEnabled(value);
}
});
}
if (settings.getProxyEnabled() != null) {
searchProperties.getProxy().setEnabled(settings.getProxyEnabled());
}
log.info("Settings updated: {}", settings);
return ResponseEntity.ok().body(BaseResponse.builder()
.messageDesc("Settings updated")
.build());
}
}
@@ -1,6 +1,5 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -15,19 +14,23 @@ import ru.soune.nocopy.dto.user.UserDTO;
import ru.soune.nocopy.dto.user.UserRequest;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.InvalidUserEmail;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.mapper.UserMapper;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.FileStatsService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffService;
import ru.soune.nocopy.service.user.UserService;
import ru.soune.nocopy.util.JwtUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("v1/api/user")
@@ -38,79 +41,99 @@ public class UserController {
private final UserService userService;
private final AuthTokenRepository authTokenRepository;
private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final AuthService authService;
private final TariffService tariffService;
private final FileStatsService fileStatsService;
private final JwtUtil jwtUtil;
private final HttpServletRequest httpServletRequest;
@GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
null, null))
.toList();
return ResponseEntity.ok(allUsers);
}
@GetMapping
public ResponseEntity<UserDTO> getUser(@RequestParam("email") String email) {
User user = userRepository.findByEmail(email);
public ResponseEntity<UserDTO> getUser(@RequestParam("email") String email,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
if (user != null) {
UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email);
userDTO.setFullName(user.getFullName());
userDTO.setVerifiedStatus(user.getVerificationStatus());
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
if (user.getCompany() != null) {
userDTO.setCompany(user.getCompany().getCompanyName());
}
if (tokenOptional.isPresent()) {
User user = userRepository.findByEmail(email);
if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
userDTO.setTariffs(tariffService.getTariffByAccountType(user));
if (user != null) {
UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email);
userDTO.setFullName(user.getFullName());
Long fileOnDisk = user.canManageCompanySettings() ?
fileStatsService.calculateCompanyFileSize(user.getId()):
fileStatsService.calculateUserFileSize(user.getId());
Integer filesCount = user.canManageCompanySettings() ?
fileStatsService.filesCompanyCount(user.getId()):
fileStatsService.filesUserCount(user.getId());
TariffInfo personalTariffInfo;
if (user.getCompany() == null) {
personalTariffInfo = user.getPersonalTariffInfo();
} else {
personalTariffInfo = user.getCompany().getTariffInfo();
if (user.getCompany() != null) {
userDTO.setCompany(user.getCompany().getCompanyName());
}
Tariff tariff = personalTariffInfo.getTariff();
if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
userDTO.setTariffs(tariffService.getAllTariffs());
TariffInfoDTO infoDTO = TariffInfoDTO.builder().tokens(personalTariffInfo.getTokens())
.id(personalTariffInfo.getId())
.tariffName(tariff.getName())
.status(personalTariffInfo.getStatus().name())
.tariffId(tariff.getId())
.currentFileCounts(filesCount)
.maxFileCounts(tariff.getMaxFilesCount())
.currentFileOnDisk(fileOnDisk)
.maxFileOnDisk(tariff.getDiskSize())
.startTariff(personalTariffInfo.getStartTariff())
.endTariff(personalTariffInfo.getEndTariff())
.tokens(personalTariffInfo.getTokens() + personalTariffInfo.getBoughtTokens())
.build();
Long fileOnDisk = user.canManageCompanySettings() ?
fileStatsService.calculateCompanyFileSize(user.getId()):
fileStatsService.calculateUserFileSize(user.getId());
userDTO.setTariffInfo(infoDTO);
Integer filesCount = user.canManageCompanySettings() ?
fileStatsService.filesCompanyCount(user.getId()):
fileStatsService.filesUserCount(user.getId());
TariffInfo personalTariffInfo;
if (user.getCompany() == null) {
personalTariffInfo = user.getPersonalTariffInfo();
} else {
personalTariffInfo = user.getCompany().getTariffInfo();
}
Tariff tariff = personalTariffInfo.getTariff();
TariffInfoDTO infoDTO = TariffInfoDTO.builder().tokens(personalTariffInfo.getTokens())
.id(personalTariffInfo.getId())
.tariffName(tariff.getName())
.status(personalTariffInfo.getStatus().name())
.tariffId(tariff.getId())
.currentFileCounts(filesCount)
.maxFileCounts(tariff.getMaxFilesCount())
.currentFileOnDisk(fileOnDisk)
.maxFileOnDisk(tariff.getDiskSize())
.startTariff(personalTariffInfo.getStartTariff())
.endTariff(personalTariffInfo.getEndTariff())
.build();
userDTO.setTariffInfo(infoDTO);
}
userDTO.setPhone(user.getPhone());
userDTO.setGenderType(user.getGenderType());
userDTO.setBirthday(user.getBirthday());
userDTO.setCreatedAt(user.getCreatedAt());
userDTO.setSubscriptionType(user.getSubscriptionType());
userDTO.setActive(user.isActive());
userDTO.setPermission(user.getUserPermissions());
return ResponseEntity.ok(userDTO);
}
userDTO.setPhone(user.getPhone());
userDTO.setGenderType(user.getGenderType());
userDTO.setBirthday(user.getBirthday());
userDTO.setCreatedAt(user.getCreatedAt());
userDTO.setActive(user.isActive());
userDTO.setPermission(user.getUserPermissions());
return ResponseEntity.ok(userDTO);
} else {
return ResponseEntity.notFound().build();
}
return ResponseEntity.notFound().build();
@@ -118,13 +141,15 @@ public class UserController {
//TODO fix mapper,doesnot exist all fields
@PostMapping("/change-password")
public ResponseEntity<UserDTO> updateUser(@RequestBody ChangePasswordRequest changePasswordRequest) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
public ResponseEntity<UserDTO> updateUser(@RequestBody ChangePasswordRequest changePasswordRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
if (user == null) {
throw new UserNotFoundException("User not found by id: " + userId);
}
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
if (!changePasswordRequest.getEmail().equals(user.getEmail())) {
throw new InvalidUserEmail("Email is not valid: " + changePasswordRequest.getEmail() + "not found");
@@ -136,19 +161,21 @@ public class UserController {
}
@PatchMapping("/user-update")
public ResponseEntity<UserDTO> updateUser(@RequestBody UserRequest userRequest) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
public ResponseEntity<UserDTO> updateUser(@RequestBody UserRequest userRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
if (user == null) {
throw new UserNotFoundException("User not found by id: " + userId);
}
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
return ResponseEntity.ok(userService.updateUser(userRequest, user));
}
@PostMapping("/create-user")
public ResponseEntity<?> addUser(@RequestBody RegRequest registerRequest) {
public ResponseEntity<User> addUser(@RequestBody RegRequest registerRequest) {
if (userRepository.existsByEmail(registerRequest.getEmail()) ||
userRepository.existsByPhone(registerRequest.getPhone())) {
@@ -175,11 +202,11 @@ public class UserController {
User savedUser = userRepository.save(user);
String authToken = jwtUtil.generateToken(savedUser.getId(), savedUser.getEmail());
AuthToken authToken = authService.generateAuthToken(savedUser);
return ResponseEntity.ok().body(Map.of(
"user", savedUser,
"token", authToken));
authTokenRepository.save(authToken);
return ResponseEntity.ok().body(savedUser);
}
@DeleteMapping("/delete-user/{userId}")
@@ -15,7 +15,4 @@ public class BaseRequest {
@JsonProperty("message_body")
Object messageBody;
@JsonProperty(value = "admin_id", required = false)
Long adminId;
}
@@ -3,12 +3,10 @@ package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
@Builder
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseResponse {
@JsonProperty("msg_id")
@@ -2,42 +2,26 @@ package ru.soune.nocopy.dto;
public enum MessageCode {
SUCCESS(0, "Operation successful"),
SUCCESS_MODERATION(0, "File moderated successfully"),
REG_EMAIL_EXISTS(1, "Email already registered"),
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
REFERRAL_LINK_IS_NOT_EXIST(1, "Refferal link is not exist"),
FILE_IS_BLOCKED(1, "File is blocked,deleted or removed"),
INVALID_FIELD(2, "Invalid field"),
MAIL_VERIFIED_NULL(2, "Mail verified null"),
INVALID_TOKEN(2, "Token not found or time expired"),
INVALID_TOKEN(2, "Invalid token"),
TOKEN_IS_ALIVE(2, "Token is alive"),
INVALID_ACTION(2, "Invalid action"),
FILE_UPLOAD_ERROR(2, "File upload error"),
DUPLICATE_FILE_UPLOAD(2, "Duplicate file upload"),
FILE_DOWNLOAD_ERROR(2, "File download error"),
FILE_NOT_EXIST(2, "File not exist on disk"),
FILE_DELETE(2, "File was deleted"),
USER_NOT_HAD_PERMISSION(2, "User not have permission for file"),
NOT_OWNER_FILE_STATUS_VIOLATION(2, "Violation in not owner file status"),
USER_NOT_VERIFIED(2, "User not verified"),
LAW_CASE_NOT_FOUND(4, "Law case not found"),
PERMISSION_NOT_FOUND(2, "Permission not found"),
INVALID_CREDENTIALS(2, "Invalid credentials"),
USER_NOT_FOUND(2, "User not found"),
LAW_CASE_ALREADY_EXIST(2, "Law case already exists"),
COMPLAINT_ALREADY_EXIST(2, "Complaint already exists"),
COMPLAINT_NOT_FOUND(4, "Complaint not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
TOKEN_IS_NULL(2, "Token field is null"),
IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
INCOMPLETE_UPLOAD(2, "Not load all chunks"),
USER_NOT_HAVE_TOKEN(2, "Not have tokens"),
MSG_ID_NOT_FOUND(4, "Message id not found"),
FILE_ENTITY_ERROR(2, "File entity error"),
NOT_VALID_FIELD(2, "Not valid field"),
ACCESS_DENIED(2, "Access denied"),
AUTH_EMAIL_NOT_FOUND(4, "Email not COMPLAINT_OR_LAW_CASE_IN_WORK"),
AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
AUTH_TOKEN_MISMATCH(4, "Token mismatch"),
AUTH_TOKEN_NOT_FOUND(4, "Token not found"),
@@ -49,34 +33,14 @@ public enum MessageCode {
FILE_IS_NOT_PROTECTED(0, "File is not protected"),
TARIFF_IS_ADD(0, "Tariff is added"),
TARIFF_IS_DELETED(0, "Tariff is deleted"),
TARIFF_IS_NOT_DELETED(0, "Tariff is not deleted,use some users"),
TARIFF_IS_UPDATED(0, "Tariff is updated"),
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
VALIDATION_ERROR(2, "Validation error"),
RESOURCE_NOT_FOUND(4, "Resource not found"),
OPERATION_NOT_FOUND(4, "Operation not found"),
INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"),
PAYMENT_NOT_FOUND(4, "Payment not found"),
COMPANY_ALREADY_EXISTS(2, "Company already exists"),
USER_LIMIT_IS_OVER(2, "Over user limits"),
MONITORING_TYPE_NOT_FOUND(4, "Monitoring type not found"),
ERROR_TARIFF_INFO(2, "Erorr with tariff info"),
FILE_FOR_SEARCH_NOT_VALID(2, "File for search unsupported"),
ILLEGAL_STATE(2, "File is not in moderation state"),
NOT_VALID_FILE_TYPE_OR_COUNT_FILE(2, "Cost for file type not found, count is negative or files count" +
"more than max valid"),
USER_NOT_ACTIVE(2, "User not active"),
NOTION_NOT_FOUND(4, "Notion not found"),
NOT_FOUND(4, "Notion not found"),
MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion"),
USER_VERIFICATIONS_NOT_FOUND(4, "Verifications not found"),
COMPLAINT_OR_LAW_CASE_IN_WORK(2, "Complaint or lawcase in work"),
NOT_VALID_STATUS(2, "Not valid status"),
PERMISSION_NOT_VALID(2, "Not valid permission"),
NOT_HAVE_DOWNLOAD_PERMISSION(2, "Not haver permission for download"),
ADMIN_USER_NOT_FOUND(4, "Verifications ,user or admin not found");
ERROR_TARIFF_INFO(2, "Erorr with tariff info");
private final Integer code;
@@ -6,4 +6,6 @@ import lombok.Data;
@Data
public class TokenProcessRequest {
@JsonProperty("token")
private String token;
}
@@ -1,35 +0,0 @@
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();
}
}
@@ -1,38 +0,0 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class ComplaintRequest {
@JsonProperty("action")
private String action;
@JsonProperty("id")
private Long id;
@JsonProperty("violation_id")
private Long violationId;
@JsonProperty("text")
private String complaintText;
@JsonProperty("email")
private String email;
@JsonProperty("status")
private String status;
@JsonProperty("page")
private Integer page;
@JsonProperty("size")
private Integer size;
@JsonProperty("sort_by")
private String sortBy;
@JsonProperty("sort_direction")
private String sortDirection;
}
@@ -1,42 +0,0 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComplaintResponse {
@JsonProperty("id")
private Long id;
@JsonProperty("status")
private String status;
@JsonProperty("complaint_text")
private String complaintText;
@JsonProperty("violation_id")
private Long violationId;
@JsonProperty("email")
private String email;
@JsonProperty("created_at")
private String createdAt;
@JsonProperty("updated_at")
private String updatedAt;
@JsonProperty("not_moderated")
private Boolean notModerated;
@JsonProperty("file_id")
private String fileId;
}
@@ -1,17 +0,0 @@
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;
}
@@ -1,32 +0,0 @@
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 Integer pageSize = 5;
private Integer pageNumber = 0;
private String sortBy = "updatedAt";
private String sortDir = "desc";
private String action;
private String filterType;
private String filterLawyer;
private String filterPriority;
@JsonProperty("violation_id")
private Long violationId;
}
@@ -1,71 +0,0 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
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;
@JsonProperty("created_at")
private LocalDateTime createdAt;
@JsonProperty("updated_at")
private LocalDateTime updatedAt;
@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("violation_id")
private long violationId;
@JsonProperty("file_id")
private String fileId;
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();
}
}
@@ -1,17 +0,0 @@
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;
}
@@ -1,15 +0,0 @@
package ru.soune.nocopy.dto.cost;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class CostRequest {
String action;
@JsonProperty("file_type")
String fileType;
@JsonProperty("count_files")
Integer countFilesForProtect;
}
@@ -1,22 +0,0 @@
package ru.soune.nocopy.dto.cost;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class CostResponse {
private int cost;
private Boolean success;
@JsonProperty("max_files_for_check")
private Integer maxFilesForCheck;
@JsonProperty("count_file")
private Integer countFile;
@JsonProperty("tokens_count")
private int needTokensCount;
}
@@ -1,10 +0,0 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataAddress {
private String value;
}
@@ -1,11 +0,0 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataData {
private String inn;
private DaDataAddress address;
}
@@ -1,11 +0,0 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataRequest {
@JsonProperty("inn")
private String inn;
}
@@ -1,16 +0,0 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataResponse {
@JsonProperty("companyName")
private String companyName;
@JsonProperty("inn")
private String inn;
@JsonProperty("address")
private String address;
}
@@ -1,11 +0,0 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataSuggestion {
private String value;
private DaDataData data;
}
@@ -1,12 +0,0 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataWrapper {
private List<DaDataSuggestion> suggestions;
}
@@ -1,36 +0,0 @@
package ru.soune.nocopy.dto.docviewer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class DocViewerRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("ip")
private String ip;
@JsonProperty("user_agent")
private String userAgent;
@JsonProperty("page")
private int page = 0;
@JsonProperty("size")
private int size = 10;
@JsonProperty("sort_by")
private String sortBy = "createdAt";
@JsonProperty("sort_direction")
private String sortDirection = "desc";
@JsonProperty("action")
private String action;
@JsonProperty("locale")
private String locale;
}
@@ -1,31 +0,0 @@
package ru.soune.nocopy.dto.docviewer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DockViewResponse {
@JsonProperty("city")
private String city;
@JsonProperty("country")
private String country;
@JsonProperty("viewer")
private String viewer;
@JsonProperty("document")
private String document;
@JsonProperty("view_date")
private LocalDateTime viewDate;
}
@@ -1,55 +0,0 @@
package ru.soune.nocopy.dto.docviewer;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DockViewStatsResponse {
private String fileId;
private Long totalViews;
private Long uniqueIps;
private Long uniqueUsers;
private int documentsCount;
private int uniqueCountryCount;
private int uniqueCityCount;
private LocalDateTime firstViewDate;
private LocalDateTime lastViewDate;
private List<DockViewStatsResponse> content;
private List<DockViewResponse> history;
private Integer pageNumber;
private Integer pageSize;
private Long totalElements;
private Integer totalPages;
private Boolean isLast;
private Boolean isFirst;
private Boolean hasNext;
private Boolean hasPrevious;
}
@@ -1,14 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class AppealInfo {
private String appealId;
private String status;
private LocalDateTime createdAt;
}
@@ -1,12 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class AppealRequest {
private String fileId;
private String appealReason;
private String additionalInfo;
}
@@ -1,20 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class AppealResponse {
private String appealId;
private String fileId;
private String appealReason;
private String additionalInfo;
private String status;
private String adminComment;
private String fileName;
private LocalDateTime createdAt;
private LocalDateTime resolvedAt;
}
@@ -5,11 +5,6 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.permission.PermissionType;
import java.util.List;
import java.util.Map;
@Data
@Builder
@@ -22,9 +17,6 @@ public class FileEntityRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("file_ids")
private List<String> fileIds;
@JsonProperty("full_delete")
private Integer fullDelete;
@@ -40,42 +32,6 @@ public class FileEntityRequest {
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("sort_by")
private String sortBy;
@JsonProperty("sort_order")
private String sortOrder;
@JsonProperty("type")
private String type;
@JsonProperty("file_status")
private FileStatus fileStatus;
@JsonProperty("date_filter")
private String dateFilter;
@JsonProperty("comment")
private String comment;
@JsonProperty("appeal_reason")
private String appealReason;
@JsonProperty("additional_info")
private String additionalInfo;
@JsonProperty("appeal_id")
private String appealId;
@JsonProperty("approve")
private Boolean approve;
@JsonProperty("reason_text")
private String reasonText;
@JsonProperty("search_query")
private String searchQuery;
@JsonProperty("permissions")
private Map<String, Boolean> permissions;
@JsonProperty("token")
private String token;
}
@@ -5,10 +5,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.permission.PermissionType;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@Builder
@@ -22,7 +20,6 @@ public class FileEntityResponse {
private String storedFileName;
private String filePath;
private String protectedFilePath;
private String thumbnailFileUrl;
private Long fileSize;
private String mimeType;
private String fileExtension;
@@ -43,6 +40,4 @@ public class FileEntityResponse {
private String fileFormat;
private Integer checksCount;
private LocalDateTime fileUploadDate;
private String monitoring;
private Map<PermissionType, Boolean> permissions;
}
@@ -59,21 +59,6 @@ public class FileInfoUserResponse {
@JsonProperty("audios_violations")
private Integer audiosViolations;
@JsonProperty("document_size")
private Long documentSize;
@JsonProperty("document_quantity")
private Integer documentCount;
@JsonProperty("document_check")
private Integer documentCheck;
@JsonProperty("document_violations")
private Integer documentViolations;
@JsonProperty("protected_document_files_count")
private Long protectedDocumentFilesCount;
@JsonProperty("protected_files_count")
private Long protectedFilesCount;
@@ -16,6 +16,12 @@ public class FileListResponse {
@JsonProperty("files")
private List<FileEntityResponse> files;
@JsonProperty("total_count")
private Integer totalCount;
@JsonProperty("total_size")
private Long totalSize;
@JsonProperty("formatted_total_size")
private String formattedTotalSize;
@@ -24,16 +30,4 @@ public class FileListResponse {
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("sort_by")
private String sortBy;
@JsonProperty("sort_order")
private String sortOrder;
@JsonProperty("total_count")
private Integer totalCount;
@JsonProperty("total_size")
private Long totalSize;
}
@@ -1,20 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.file.FileStatus;
import java.time.LocalDateTime;
@Data
@Builder
public class FileModerationInfo {
private String fileId;
private String fileName;
private String ownerName;
private String ownerEmail;
private FileStatus currentStatus;
private LocalDateTime uploadDate;
private Boolean hasActiveAppeal;
private AppealInfo activeAppeal;
}
@@ -26,6 +26,6 @@ public class FileUploadRequest {
@JsonProperty("action")
private String action;
@JsonProperty("convertTo")
private String convertTo;
@JsonProperty("token")
private String token;
}
@@ -7,13 +7,4 @@ import lombok.Data;
public class ImageSearchRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("check_similar_first")
private Boolean checkSimilarFirst;
@JsonProperty("page")
private Integer page = 1;
@JsonProperty("page_size")
private Integer pageSize = 10;
}
@@ -1,21 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.file.FileStatus;
@Data
@Builder
public class ModerationActionRequest {
private String fileId;
private FileStatus newStatus;
private String comment;
private Long adminId;
}
@@ -19,6 +19,4 @@ public class SimilarFileDTO {
ProtectionStatus status;
LocalDateTime uploadDate;
String url;
Boolean owner;
String fileStatus;
}
@@ -1,25 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.Getter;
@Getter
public enum SortOrder {
ASC("asc"),
DESC("desc");
private final String value;
SortOrder(String value) {
this.value = value;
}
public static SortOrder fromString(String value) {
if (value == null) return ASC;
for (SortOrder order : SortOrder.values()) {
if (order.value.equalsIgnoreCase(value)) {
return order;
}
}
return ASC;
}
}
@@ -12,6 +12,15 @@ public class YandexSearchResponse {
@JsonProperty("images")
private List<ImageResult> images;
@JsonProperty("images")
public void setImages(List<ImageResult> images) {
if (images != null && images.size() > 5) {
this.images = images.subList(0, 5);
} else {
this.images = images;
}
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult {
@@ -33,12 +42,4 @@ public class YandexSearchResponse {
@JsonProperty("host")
private String host;
}
private int page;
private int pageSize;
private int totalResults;
private int totalPages;
}
@@ -1,20 +0,0 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class GetViolationsRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("page")
private Integer page;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("mark_as_read")
private boolean markAsRead;
}
@@ -1,40 +0,0 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import java.time.LocalDateTime;
/**
* Response for status monitoring
*/
@Data
@Builder
public class MonitoringStatusResponse {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("file_name")
private String fileName;
@JsonProperty("monitoring_type")
private MonitoringType monitoringType;
@JsonProperty("next_run")
private LocalDateTime nextRun;
@JsonProperty("last_run")
private LocalDateTime lastRun;
@JsonProperty("last_run_status")
private String lastRunStatus;
@JsonProperty("is_active")
private boolean isActive;
@JsonProperty("need_add_tokens")
private boolean needAddTokens;
}
@@ -1,17 +0,0 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
/**
Set monitoring request
*/
@Data
public class SetMonitoringRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("monitoring_type")
private String monitoringType;
}
@@ -1,7 +0,0 @@
package ru.soune.nocopy.dto.notification;
import lombok.Data;
@Data
public class ActivateNotificationRequest {
}
@@ -1,13 +0,0 @@
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;
}
@@ -1,10 +0,0 @@
package ru.soune.nocopy.dto.notification;
import lombok.Data;
import java.util.List;
@Data
public class MarkAsReadRequest {
private List<Long> notificationIds;
}
@@ -1,11 +0,0 @@
package ru.soune.nocopy.dto.notification;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class MarkAsReadResponse {
private boolean success;
private int updatedCount;
}
@@ -1,19 +0,0 @@
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;
private Object context;
}
@@ -1,17 +0,0 @@
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;
}
@@ -1,15 +0,0 @@
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;
}
@@ -1,16 +0,0 @@
package ru.soune.nocopy.dto.payment;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PaymentMethodDTO {
private String paymentMethodId;
private String lastFour;
private String cardType;
private String expiryMonth;
private String expiryYear;
}
@@ -1,16 +0,0 @@
package ru.soune.nocopy.dto.payment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentRequest {
private String email;
private String paymentUuid;
private String action;
}
@@ -1,17 +0,0 @@
package ru.soune.nocopy.dto.payment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.payment.Payment;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentResponse {
List<Payment> payments;
}
@@ -1,44 +0,0 @@
package ru.soune.nocopy.dto.payout;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddBankTransferRequest {
@NotBlank(message = "Bank name is required")
@Size(min = 2, max = 200, message = "Bank name must be between 2 and 200 characters")
private String bankName;
@NotBlank(message = "BIC is required")
@Pattern(regexp = "^[0-9]{9}$", message = "BIC must contain 9 digits")
private String bic;
@Pattern(regexp = "^[0-9]{20}$", message = "Correspondent account must contain 20 digits")
private String correspondentAccount;
@NotBlank(message = "Account number is required")
@Pattern(regexp = "^[0-9]{20}$", message = "Account number must contain 20 digits")
private String accountNumber;
@NotBlank(message = "Account holder name is required")
@Size(min = 3, max = 200, message = "Account holder name must be between 3 and 200 characters")
private String accountHolder;
@Pattern(regexp = "^[0-9]{10}$|^[0-9]{12}$", message = "INN must contain 10 or 12 digits")
private String inn;
@Pattern(regexp = "^[0-9]{9}$", message = "KPP must contain 9 digits")
private String kpp;
@Builder.Default
private boolean saveAsDefault = false;
}
@@ -1,19 +0,0 @@
package ru.soune.nocopy.dto.payout;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddCardRequest {
@NotBlank(message = "Card number required")
@Pattern(regexp = "^[0-9]{16}$", message = "Card number 16 cipher")
private String cardNumber;
}
@@ -1,21 +0,0 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethodDTO {
private Long id;
private String type;
private String displayName;
private String maskedDetails;
private String cardNumber;
private boolean isDefault;
private boolean isVerified;
}
@@ -1,23 +0,0 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethodResponse {
private Long id;
private String type;
private String displayName;
private String maskedDetails;
private boolean isDefault;
private boolean isVerified;
private LocalDateTime createdAt;
private String bankName;
private String cardBrand;
}
@@ -1,14 +0,0 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethodTypeDTO {
private String code;
private String name;
}
@@ -1,23 +0,0 @@
package ru.soune.nocopy.dto.payout;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutRequestDTO {
@NotNull(message = "Sum of pay is required")
@DecimalMin(value = "1000.00", message = "Min sum for pay 1000")
private BigDecimal amount;
@NotNull(message = "Способ выплаты обязателен")
private Long payoutMethodId;
}
@@ -1,54 +0,0 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.payout.PayoutRequest;
import ru.soune.nocopy.entity.payout.PayoutRequestStatus;
import ru.soune.nocopy.entity.payout.PayoutType;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutResponse {
private Long id;
private BigDecimal amount;
private String formattedAmount;
private PayoutRequestStatus status;
private PayoutType payoutType;
private String payoutMethodDisplay;
private LocalDateTime createdAt;
private String formattedCreatedAt;
private LocalDateTime processedAt;
private LocalDateTime completedAt;
private String transactionId;
private String rejectionReason;
private boolean canCancel;
public static PayoutResponse fromEntity(PayoutRequest request) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
return PayoutResponse.builder()
.id(request.getId())
.amount(request.getAmount())
.formattedAmount(String.format("%.2f ₽", request.getAmount()))
.status(request.getStatus())
.payoutType(request.getPayoutType())
.payoutMethodDisplay(request.getPayoutMethod() != null ?
request.getPayoutMethod().getDisplayName() : null)
.createdAt(request.getCreatedAt())
.formattedCreatedAt(request.getCreatedAt() != null ?
request.getCreatedAt().format(formatter) : null)
.processedAt(request.getProcessAt())
.completedAt(request.getCompletedAt())
.rejectionReason(request.getRejectionReason())
.canCancel(request.getStatus() == PayoutRequestStatus.PENDING)
.build();
}
}
@@ -1,17 +0,0 @@
package ru.soune.nocopy.dto.referral;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ReferralRequest {
private Integer amount;
private Integer pageSize;
private Integer pageNumber;
private String action;
}
@@ -1,7 +1,6 @@
package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
import lombok.Data;
@@ -14,8 +13,6 @@ public class RegRequest {
private String companyName;
private String inn;
@NotEmpty(message = "Need account type")
private String accountType;
@@ -32,15 +29,4 @@ public class RegRequest {
private String password;
private String authToken;
private String referralLink;
@JsonProperty("mail_verified")
private String mailVerified;
@JsonProperty("ip")
private String ip;
@JsonProperty("user_agent")
private String userAgent;
}
@@ -1,24 +0,0 @@
package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResetPasswordRequest {
@JsonProperty("email")
private String email;
@JsonProperty("verifyToken")
private String verifyToken;
@JsonProperty("action")
private String action;
@JsonProperty("password")
private String password;
}
@@ -1,13 +0,0 @@
package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class ResetPasswordResponse {
@JsonProperty("user_id")
private Long userId;
@JsonProperty("authToken")
private String authToken;
}
@@ -1,19 +0,0 @@
package ru.soune.nocopy.dto.search;
import lombok.Data;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import java.util.List;
@Data
public class GlobalSearchFileResult {
private String fileId;
private String fileName;
private String thumbnail;
private String fileStatus;
private List<YandexSearchResponse.ImageResult> images;
private int page;
private int pageSize;
private int totalResults;
private int totalPages;
}
@@ -1,14 +0,0 @@
package ru.soune.nocopy.dto.search;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Builder
@Data
public class GlobalSearchStartRequest {
private String searchType;
private List<String> fileIds;
}
@@ -1,12 +0,0 @@
package ru.soune.nocopy.dto.search;
import lombok.Data;
@Data
public class GlobalSearchStartResponse {
private String taskId;
private String status;
private Integer totalFiles;
}

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