109 Commits
Author SHA1 Message Date
vladp daef30ec11 NCBACK-35 add worked logic for save protected files
Test Workflow / test (push) Successful in 4s
2026-02-16 16:05:15 +07:00
vladp a25b33fe13 NCBACK-35 add worked logic for save protected files
Test Workflow / test (push) Successful in 4s
2026-02-16 15:04:58 +07:00
vladp 2985d84b04 NCBACK-35 add cloud for save when download last chunk and after protected
Test Workflow / test (push) Successful in 3s
2026-02-13 21:11:30 +07:00
vladp 03a330b570 NCBACK-46 fix file name
Test Workflow / test (push) Successful in 3s
2026-02-12 16:03:39 +07:00
vladp b372180f83 NCBACK-42 add dadata handler
Test Workflow / test (push) Successful in 3s
2026-02-12 15:36:45 +07:00
vladp 920fc67962 dev only unige result
Test Workflow / test (push) Successful in 3s
2026-02-12 12:07:41 +07:00
vladp fb2ac0cf03 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-12 11:58:05 +07:00
vladp 8a2be0d310 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 19:50:07 +07:00
vladp 0743000c10 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 19:15:16 +07:00
vladp 640deef98d dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 18:47:58 +07:00
vladp 28b5a64a33 dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 18:28:57 +07:00
vladp 6818b74307 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 18:12:44 +07:00
vladp 6b73d01aeb dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 17:59:41 +07:00
vladp 1cb9edd5bf dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 17:53:02 +07:00
vladp 081ca05f88 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 17:41:48 +07:00
vladp 970e0fa615 dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 16:24:19 +07:00
vladp 52e1a7dd02 dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 20:50:06 +07:00
vladp 19330d853d dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 20:40:55 +07:00
vladp 1472c342f5 dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 19:14:16 +07:00
vladp 1ced86ff8c dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 18:55:21 +07:00
vladp 8149c450b2 dev add pdf docs protect
Test Workflow / test (push) Successful in 4s
2026-02-10 18:38:46 +07:00
vladp fea2d5ad89 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 16:23:49 +07:00
vladp 2ef1619ef9 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 16:10:05 +07:00
vladp 266bcaefcd dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 15:55:20 +07:00
vladp bf48725244 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:47:08 +07:00
vladp 86f7fc593a dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:26:44 +07:00
vladp 90be1d9d2b dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:03:19 +07:00
vladp 9692fe48b7 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:52:36 +07:00
vladp d50c565405 dev add utf encoding
Test Workflow / test (push) Successful in 4s
2026-02-10 12:46:45 +07:00
vladp 98fc145f1b dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:36:52 +07:00
vladp e7e870ee30 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:33:07 +07:00
vladp 8103960b45 dev add pagination for search in internet images
Test Workflow / test (push) Successful in 3s
2026-02-10 12:16:31 +07:00
vladp 792e0048cc dev add pagination for search in internet images
Test Workflow / test (push) Successful in 4s
2026-02-10 11:58:32 +07:00
vladp 33883faa98 dev split tokens
Test Workflow / test (push) Successful in 2s
2026-02-09 17:25:06 +07:00
vladp 7cc744829b dev split tariff by account types
Test Workflow / test (push) Successful in 3s
2026-02-09 17:16:08 +07:00
vladp 5a70c091f8 dev
Test Workflow / test (push) Successful in 5s
2026-02-09 15:29:55 +07:00
vladp 69aabe3619 dev
Test Workflow / test (push) Successful in 3s
2026-02-09 15:20:42 +07:00
vladp 27a8766171 dev fix name for parameters
Test Workflow / test (push) Successful in 2s
2026-02-09 14:15:29 +07:00
vladp 2bba70e583 dev fix name for parameters
Test Workflow / test (push) Successful in 3s
2026-02-09 13:50:42 +07:00
vladp 8e8193cda6 dev company users saw all company files
Test Workflow / test (push) Successful in 6s
2026-02-09 12:20:19 +07:00
vladp b96b50c01f add referal only for b2c
Test Workflow / test (push) Successful in 3s
2026-02-09 11:28:23 +07:00
vladp e926934473 add add sql scripts
Test Workflow / test (push) Successful in 3s
2026-02-09 11:24:34 +07:00
vladp a5003958a8 add set user limits
Test Workflow / test (push) Successful in 3s
2026-02-09 11:06:50 +07:00
vladp 5b73fd2c39 add document
Test Workflow / test (push) Has been cancelled
2026-02-07 11:54:34 +07:00
vladp daadc82bbb add document
Test Workflow / test (push) Successful in 3s
2026-02-06 22:21:51 +07:00
vladp 682c21ba73 fix value
Test Workflow / test (push) Successful in 2s
2026-02-06 21:22:47 +07:00
vladp 1c7a33be32 fix value
Test Workflow / test (push) Successful in 4s
2026-02-06 20:43:27 +07:00
vladp aceb70484f fix value
Test Workflow / test (push) Successful in 3s
2026-02-06 20:18:43 +07:00
backdev 3fc83e5458 Merge pull request 'NCBACK-36_1' (#19) from NCBACK-36_1 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #19
2026-02-06 20:27:56 +08:00
vladp a7e94639d2 NC_BACK-36_1
Test Workflow / test (push) Successful in 3s
2026-02-06 19:27:25 +07:00
vladp f52ce6db3c dev add referral
Test Workflow / test (push) Successful in 3s
2026-02-06 01:01:34 +07:00
vladp 80c53515fb dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 20:23:33 +07:00
vladp 511da8321b dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 20:21:27 +07:00
vladp df1fd89c29 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:47:32 +07:00
vladp 90e3134e98 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:33:37 +07:00
vladp 130fd184b9 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:02:17 +07:00
vladp f90b8a0bce dev add stats for search check
Test Workflow / test (push) Successful in 3s
2026-02-05 18:55:54 +07:00
vladp adc978bea8 dev write off for search
Test Workflow / test (push) Successful in 3s
2026-02-05 17:25:50 +07:00
vladp 7bb48f3944 dev add test find by searchapo
Test Workflow / test (push) Successful in 3s
2026-02-05 17:16:16 +07:00
milidev 27b812b0da NCBACK-36: [Referral] Referral Service
Test Workflow / test (push) Successful in 3s
2026-02-05 15:43:42 +07:00
vladp 6643070999 dev add test find by searchapo
Test Workflow / test (push) Successful in 8s
2026-02-05 12:44:27 +07:00
vladp 4395d276ed dev add test find by searchapo
Test Workflow / test (push) Successful in 4s
2026-02-05 10:47:40 +07:00
vladp e49a944b05 dev add test find by searchapo
Test Workflow / test (push) Successful in 3s
2026-02-05 03:30:15 +07:00
vladp 8e716f7d20 dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 03:27:16 +07:00
vladp 2e8080183c dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 00:46:17 +07:00
vladp dc9215523b dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 00:37:59 +07:00
vladp 72691511fa dev add
Test Workflow / test (push) Successful in 3s
2026-02-04 14:56:19 +07:00
vladp 135d94e0ef dev add
Test Workflow / test (push) Successful in 4s
2026-02-04 13:41:09 +07:00
vladp d1bacb4ac7 dev add
Test Workflow / test (push) Successful in 9s
2026-02-04 12:50:35 +07:00
vladp 7a809e6b8c dev add
Test Workflow / test (push) Successful in 4s
2026-02-04 11:47:43 +07:00
vladp 06a6196a26 dev add
Test Workflow / test (push) Successful in 10s
2026-02-03 16:01:32 +07:00
vladp 13324d4628 dev add tarifinfo by permission for user info
Test Workflow / test (push) Successful in 4s
2026-02-03 15:05:03 +07:00
vladp c38b86e1b9 dev add tariffinfo endpoints
Test Workflow / test (push) Successful in 4s
2026-02-02 15:47:58 +07:00
vladp 3ce8edd7e1 dev add tariff endpoints
Test Workflow / test (push) Successful in 3s
2026-02-02 15:15:43 +07:00
vladp 22b487da22 dev add tariff endpoints
Test Workflow / test (push) Successful in 4s
2026-02-02 15:00:18 +07:00
vladp 4395654b0d dev add apllication tariff fields
Test Workflow / test (push) Successful in 3s
2026-02-02 12:19:34 +07:00
vladp 7dd057603d dev
Test Workflow / test (push) Successful in 2s
2026-01-30 22:36:20 +07:00
vladp a4858cf6c5 dev
Test Workflow / test (push) Successful in 3s
2026-01-30 22:30:27 +07:00
vladp 7d52276b31 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 19:27:01 +07:00
vladp 8fe8c566c0 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 5s
2026-01-30 18:04:08 +07:00
vladp 36d40a0b8d dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 4s
2026-01-30 18:03:51 +07:00
vladp 32f0a07e32 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 18:02:34 +07:00
vladp 51ef63e8cd dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:55:14 +07:00
vladp b0237fd812 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:51:59 +07:00
vladp de8c53784b dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:39:19 +07:00
vladp 3cd6b5396c dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 17:36:57 +07:00
vladp 615924ea9e dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:29:25 +07:00
vladp 9a8aa498c0 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 15:09:39 +07:00
vladp 66633d4964 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 14:24:23 +07:00
vladp ce8c63475f dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 14:00:21 +07:00
vladp 30c05b6b24 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 13:02:30 +07:00
vladp 49dc19cd04 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 12:52:16 +07:00
vladp fb5642db9e dev check null company
Test Workflow / test (push) Successful in 2s
2026-01-29 21:46:37 +07:00
vladp c539b1fbc2 dev check null company
Test Workflow / test (push) Successful in 2s
2026-01-29 21:31:28 +07:00
vladp 61b8ea902b dev limit for yasearch result
Test Workflow / test (push) Successful in 1s
2026-01-29 20:13:10 +07:00
vladp 6ee7ab4d04 dev add rest for company, add link for users , update reg users
Test Workflow / test (push) Successful in 2s
2026-01-29 19:24:36 +07:00
vladp 96acf796b2 dev change status on Processing for prePresist
Test Workflow / test (push) Successful in 3s
2026-01-29 16:25:34 +07:00
vladp ff7836900b dev fix show image
Test Workflow / test (push) Successful in 2s
2026-01-29 13:08:43 +07:00
vladp ec20d68261 dev add check limits logic
Test Workflow / test (push) Successful in 3s
2026-01-28 23:25:16 +07:00
vladp 31fac0ab93 dev check to protect
Test Workflow / test (push) Successful in 3s
2026-01-28 21:18:42 +07:00
vladp a32f089989 dev add info for files
Test Workflow / test (push) Successful in 2s
2026-01-28 19:26:40 +07:00
vladp c019f23777 dev download only protected files
Test Workflow / test (push) Successful in 3s
2026-01-28 18:38:43 +07:00
vladp 02bf7868ee dev add split network and add new ports with apps
Test Workflow / test (push) Successful in 9s
2026-01-28 18:19:20 +07:00
vladp 2215adc37f dev
Test Workflow / test (push) Successful in 3s
2026-01-27 20:55:33 +07:00
vladp 6fcec8d67e dev
Test Workflow / test (push) Successful in 2s
2026-01-27 20:41:08 +07:00
vladp 4a185e6f9a dev
Test Workflow / test (push) Successful in 3s
2026-01-27 20:27:39 +07:00
vladp cf2f73955b dev
Test Workflow / test (push) Successful in 3s
2026-01-27 08:50:52 +07:00
vladp f67e172365 dev
Test Workflow / test (push) Successful in 3s
2026-01-27 08:25:38 +07:00
backdev d32e8dcd75 Merge pull request 'NCBACK-33' (#16) from NCBACK-33 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #16
2026-01-27 09:11:31 +08:00
130 changed files with 5504 additions and 678 deletions
+3
View File
@@ -14,6 +14,9 @@ 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"]
+85
View File
@@ -76,3 +76,88 @@ ALTER TABLE file_entities
ALTER COLUMN support_id
SET DEFAULT nextval('file_support_id_seq');
----------
Раздать всем лимиты,у кого их нет
INSERT INTO protect_check (user_id, check_limit, count_checked, last_check_at, version)
SELECT
id as user_id,
10 as check_limit,
0 as count_checked,
NULL as last_check_at,
0 as version
FROM users
WHERE id NOT IN (SELECT user_id FROM protect_check);
--------
Обновлять констрейнты для file_entities
ALTER TABLE file_entities DROP CONSTRAINT file_entities_status_check;
ALTER TABLE file_entities
ADD CONSTRAINT file_entities_status_check
CHECK (status IN (
'ACTIVE',
'DELETED',
'PROCESSING',
'VIOLATION',
'CHECKED',
'ERROR',
'TEMP'
));
-------
Записи для лимитов поиска
INSERT INTO protect_check (user_id, limit_check, count_checked, last_check_at, version)
SELECT
u.id,
100, -- limit_check = 100
0, -- count_checked = 0
NULL, -- last_check_at = NULL
0 -- version = 0
FROM users u
WHERE NOT EXISTS (
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);
------
Настройка Яндекс Клауд
1. YCAJEpWAtaVkVGX0sH6_EupEg - индетификатор ключа
2. YCMmVykfXrZ_nfU13Vo4yoCVGa70DnTlBgF1pUzO - секретный ключ
private static final String KEY_ID = "YCAJEmHZcWxzf4oGWvETG3mms";
private static final String SECRET_KEY = "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG";
+12 -2
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,8 +59,18 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation name: 'testlib-fat-0.2.1-all'
implementation name: 'testlib-fat-0.3.0-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation project(':referral')
//cloud
implementation("software.amazon.awssdk:aws-sdk-java:2.29.33")
implementation("software.amazon.awssdk:apache-client:2.29.33")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
implementation("org.springframework.boot:spring-boot-starter-web")
}
tasks.named('test') {
+5 -4
View File
@@ -88,17 +88,18 @@ services:
# FILE_CHUNK_SIZE: 1048576
FILE_CHUNK_SIZE: 1000000
POSTGRES_DB: no_copy_
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: 5432
POSTGRES_HOST: db
STORAGE_SERVICE_URL: http://storage:8081
SPRING_PROFILES_ACTIVE: docker
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"
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
# MAIL_HOST: postfix
# MAIL_PORT: 25
# MAIL_USERNAME: noreply@no-copy.ru
@@ -180,7 +181,7 @@ services:
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=15d' # Удерживать 15 дней
- '--storage.tsdb.retention.time=15d'
- '--web.enable-lifecycle'
healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
+24
View File
@@ -0,0 +1,24 @@
plugins {
kotlin("jvm") version "2.1.10"
}
group = "ru.soune"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-core-jvm:4.1.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
+5
View File
@@ -0,0 +1,5 @@
package ru.soune
fun main() {
println("Hello World!")
}
@@ -0,0 +1,3 @@
package ru.soune.actions
abstract class NoCopyAction
@@ -0,0 +1,5 @@
package ru.soune.actions.free
import ru.soune.actions.NoCopyAction
sealed class NoCopyFreeAction : NoCopyAction()
@@ -0,0 +1,8 @@
package ru.soune.actions.paid
import ru.soune.actions.NoCopyAction
sealed class NoCopyPaidAction : NoCopyAction() {
abstract val cost: Double
}
+26
View File
@@ -0,0 +1,26 @@
## Команды для помещения в сеть
Сеть для работы бэка и фронта : Наименование == app-network-{port}
### Проверить состояние:
docker inspect {container-name} --format='{{range $net, $_ := .NetworkSettings.Networks}}{{$net}} {{end}}'
### Создать сеть ,если нет
docker network create {name-network} 2>/dev/null || echo "Сеть уже существует"
### Переместить контейнер:
# Отключить от старой сети
docker network disconnect {network-name-old} {container-name}
# Подключить к новой сети
docker network connect {network-name-new} {container-name}
# Добавить алиас (опционально)
docker network connect --alias {alias} {network-name-new} {container-name}
+156
View File
@@ -0,0 +1,156 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя админ-панели'
)
string(
name: 'PORT',
defaultValue: '2996',
description: 'Порт для запуска экземпляра'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-admin-panel-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
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 "
docker stop no-copy-admin-panel-${params.PORT} 2>/dev/null || true
docker rm no-copy-admin-panel-${params.PORT} 2>/dev/null || true
mkdir -p /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}
rm -rf /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}/*
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Build on server') {
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 "
cd /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}
docker build -t no-copy-admin-panel:${params.BRANCH}-${params.PORT} .
"
"""
}
}
}
}
stage('Start on server') {
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 "
docker run -d \\
--name no-copy-admin-panel-${params.PORT} \\
--restart unless-stopped \\
--network app-network \\
-p ${params.PORT}:2996 \\
no-copy-admin-panel:${params.BRANCH}-${params.PORT}
sleep 5
docker ps --filter name=no-copy-admin-panel-${params.PORT}
"
"""
}
}
}
}
stage('Check status') {
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 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-admin-panel-${params.PORT}'; then
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} 2>/dev/null || echo '000')
echo 'Admin panel available on http://92.242.61.23:${params.PORT}'
else
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Admin panel ${params.BRANCH} deployed"
}
failure {
echo "Deployment failed"
}
}
}
@@ -0,0 +1,148 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'dev',
description: 'Ветка для деплоя'
)
string(
name: 'PORT',
defaultValue: '3000',
description: 'Порт для запуска экземпляра'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
description: 'Сервер для деплоя'
)
}
stages {
stage('Git pull') {
steps {
cleanWs()
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/backdev/no-copy.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
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"
else
echo "SSH connection failed"
exit 1
fi
# Основная команда
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
cd /opt/deployments/${params.BRANCH}-${params.PORT}
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 'Контейнер не найден'
docker rm app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
docker stop postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
echo '3. Запускаем БД...'
docker run -d \\
--name postgres-${params.PORT} \\
--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} \\
-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. Запускаем storage...'
docker run -d \\
--name file-storage-${params.PORT} \\
--network app-network-${params.PORT} \\
-v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null
echo '5. Ждем БД...'
sleep 15
echo '6. Собираем приложение...'
docker build --no-cache -t app-backend-${params.PORT}:latest .
echo '7. Запускаем приложение...'
docker run -d \\
--name app-backend-${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} \\
-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 \\
app-backend-${params.PORT}:latest
echo '8. Проверка...'
sleep 10
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
echo 'Приложение работает на порту ${params.PORT}'
else
echo 'Проверка health не удалась'
docker logs app-backend-${params.PORT} --tail=20
fi
"
"""
}
}
}
}
}
post {
success {
echo "Deployment successful"
echo "Application URL: http://${params.SERVER}:${params.PORT}"
}
failure {
echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}"
}
always {
echo "Deployment process finished"
}
}
}
+51 -33
View File
@@ -7,6 +7,11 @@ pipeline {
defaultValue: 'main',
description: 'Ветка для деплоя'
)
string(
name: 'PORT',
defaultValue: '2998',
description: 'Порт для запуска экземпляра'
)
}
stages {
@@ -37,15 +42,15 @@ pipeline {
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true
docker stop no-copy-frontend-${params.PORT} 2>/dev/null || true
docker rm no-copy-frontend-${params.PORT} 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
docker-compose -f docker-compose.yaml -p frontend-${params.PORT} down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
docker-compose -f docker-compose.yml -p frontend-${params.PORT} down 2>/dev/null || true
fi
"
"""
@@ -66,11 +71,11 @@ pipeline {
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}
rm -rf /opt/deployments/frontend/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}-${params.PORT}/
"""
}
}
@@ -80,6 +85,8 @@ 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',
@@ -89,16 +96,18 @@ pipeline {
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
else
echo 'Exception: docker-compose file not found'
exit 1
fi
# 1. СОЗДАЕМ JSON ФАЙЛ (ЭТО РАБОТАЕТ 100%)
mkdir -p public
echo '{\\"buildTime\\": \\"${BUILD_TIME_FRONT}\\"}' > public/build-info.json
# 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
# 3. Собираем образ
docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
"
"""
}
@@ -118,21 +127,23 @@ pipeline {
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
# Проверяем и подключаем к сети app-network если нужно
docker network inspect app-network 2>/dev/null || echo 'Warning: app-network not found'
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml up -d
fi
echo '=== Запускаем контейнер ==='
docker-compose -f docker-compose.yml -p frontend-${params.PORT} up -d
sleep 10
echo 'Status container:'
docker ps --filter name=no-copy-frontend
echo '=== Проверяем запуск ==='
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
echo '✓ Контейнер запущен: no-copy-frontend-${params.PORT}'
echo '✓ Порт: ${params.PORT}:2999'
else
echo '✗ Контейнер не запустился'
echo 'Логи:'
docker-compose -f docker-compose.yml -p frontend-${params.PORT} logs
exit 1
fi
"
"""
}
@@ -152,15 +163,21 @@ pipeline {
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container started'
# Ищем контейнер с правильным именем (с портом)
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
echo 'Container started: no-copy-frontend-${params.PORT}'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} || echo '000')
echo 'HTTP code: \$HTTP_CODE'
echo 'Frontend available on http://92.242.61.23:2998'
echo 'Frontend available on http://92.242.61.23:${params.PORT}'
# Показываем все фронтенды
echo ''
echo 'All frontend containers:'
docker ps --filter name=no-copy-frontend --format 'table {{.Names}}\\t{{.Ports}}\\t{{.Status}}'
else
echo 'Exception: container did not start'
docker ps -a | grep frontend
@@ -177,7 +194,8 @@ pipeline {
post {
success {
echo "Front branch ${params.BRANCH} deployment completed"
echo "Frontend available on http://92.242.61.23:2998"
echo "Frontend available on http://92.242.61.23:${params.PORT}"
echo "Container: no-copy-frontend-${params.PORT}"
}
failure {
echo "Deployment failed"
+2
View File
@@ -34,6 +34,7 @@ pipeline {
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
@@ -92,6 +93,7 @@ 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
@@ -1,183 +0,0 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
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 "
docker stop no-copy-frontend 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
fi
"
"""
}
}
}
}
stage('Copy to server') {
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 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/
"""
}
}
}
}
stage('Build on server') {
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 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
else
echo 'Exception:don't found docker-compose'
exit 1
fi
"
"""
}
}
}
}
stage('Start on server') {
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 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml up -d
fi
sleep 10
echo 'Status container:'
docker ps --filter name=no-copy-frontend
"
"""
}
}
}
}
stage('Check status') {
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 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container start'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP код: ' \$HTTP_CODE
echo 'Frong start on http://92.242.61.23:2998'
else
echo 'Exception: container don't start'
docker ps -a | grep frontend
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Front branch ${params.BRANCH} completed"
echo "Use on http://92.242.61.23:2998"
}
failure {
echo "Failed"
}
}
}
+156
View File
@@ -0,0 +1,156 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя легальной панели'
)
string(
name: 'PORT',
defaultValue: '2997',
description: 'Порт для запуска экземпляра'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-legal-panel-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
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 "
docker stop no-copy-legal-panel-${params.PORT} 2>/dev/null || true
docker rm no-copy-legal-panel-${params.PORT} 2>/dev/null || true
mkdir -p /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}
rm -rf /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}/*
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Build on server') {
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 "
cd /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}
docker build -t no-copy-legal-panel:${params.BRANCH}-${params.PORT} .
"
"""
}
}
}
}
stage('Start on server') {
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 "
docker run -d \\
--name no-copy-legal-panel-${params.PORT} \\
--restart unless-stopped \\
--network app-network \\
-p ${params.PORT}:2997 \\
no-copy-legal-panel:${params.BRANCH}-${params.PORT}
sleep 5
docker ps --filter name=no-copy-legal-panel-${params.PORT}
"
"""
}
}
}
}
stage('Check status') {
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 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-legal-panel-${params.PORT}'; then
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} 2>/dev/null || echo '000')
echo 'Legal panel available on http://92.242.61.23:${params.PORT}'
else
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Legal panel ${params.BRANCH} deployed on port ${params.PORT}"
}
failure {
echo "Deployment failed"
}
}
}
+13
View File
@@ -0,0 +1,13 @@
package ru.soune
interface Referral {
val userId: Long
val referralLink: String
val inviter: Long?
val currentLevel: String
val totalIncome: Int
val availableIncome: Int
val holdBalance: Int
val active: Boolean
}
@@ -0,0 +1,7 @@
package ru.soune
data class ReferralInvitee(
val email: String,
val isActive: Boolean,
val regDate: String,
)
+10
View File
@@ -0,0 +1,10 @@
package ru.soune
interface ReferralLevel {
val id: String
val name: String
val share: Int
val minInvitee: Int
val maxInvitee: Int
val next: String?
}
@@ -0,0 +1,11 @@
package ru.soune
interface ReferralLevelProvider {
fun getAvailableReferralLevels(): List<ReferralLevel>
fun getInitialReferralLevelId(): String
fun getReferralLevelById(levelId: String): ReferralLevel
}
+33
View File
@@ -0,0 +1,33 @@
package ru.soune
interface ReferralRepo {
fun getUserReferralLink(userId: Long): String
fun getUserIdByLink(link: String): Long?
fun getInviterIdForUser(userId: Long): Long?
fun getReferralLevelForUser(userId: Long): String
fun getReferralByUserId(userId: Long): Referral
fun getReferralInvitees(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
// создаем новую запись в БД
fun createReferralEntity(entity: Referral)
// прибавить к totalIncome и availableIncome значение transferAmount
fun increaseIncome(userId: Long, transferAmount: Int)
// установить active в true
fun activateUser(userId: Long): Boolean
// посчитать записи, у которых inviter == userId и active == true
fun getActiveInviteeForUser(userId: Long): Int
// посчитать записи, у которых inviter == userId
fun getTotalInviteeForUser(userId: Long): Int
fun upgradeReferralLevelForUser(userId: Long, newLevelId: String)
}
@@ -0,0 +1,68 @@
package ru.soune
class ReferralService(
private val referralLevelProvider: ReferralLevelProvider,
private val referralRepo: ReferralRepo,
) {
fun onRegister(userId: Long, inviterReferralLink: String?) {
val initialReferralLevel = referralLevelProvider.getInitialReferralLevelId()
val referralLink = referralRepo.getUserReferralLink(userId)
val inviter = inviterReferralLink?.let(referralRepo::getUserIdByLink)
val referral = object : Referral {
override val userId = userId
override val referralLink = referralLink
override val inviter = inviter
override val currentLevel = initialReferralLevel
override val totalIncome = 0
override val availableIncome = 0
override val holdBalance: Int = 0
override val active: Boolean = false
}
referralRepo.createReferralEntity(referral)
}
fun onUserAccountRefill(userId: Long, transferAmount: Int) {
val wasActivated = referralRepo.activateUser(userId)
val inviterId = referralRepo.getInviterIdForUser(userId) ?: return
val currentInviterLevel = referralRepo.getReferralLevelForUser(inviterId)
.let { referralLevelProvider.getReferralLevelById(it) }
val reward = transferAmount * currentInviterLevel.share / 100
referralRepo.increaseIncome(inviterId, reward)
if (!wasActivated) return
val currentCount = referralRepo.getActiveInviteeForUser(inviterId)
val nextLevel = currentInviterLevel.next
if (currentCount + 1 > currentInviterLevel.maxInvitee && nextLevel != null) {
referralRepo.upgradeReferralLevelForUser(inviterId, nextLevel)
}
}
fun getReferralLevels(): List<ReferralLevel> =
referralLevelProvider.getAvailableReferralLevels()
fun getReferralLevelForUser(userId: Long): ReferralLevel =
referralRepo.getReferralLevelForUser(userId)
.let { referralLevelProvider.getReferralLevelById(it) }
fun getReferralStatForUser(userId: Long): ReferralStat {
var referral = referralRepo.getReferralByUserId(userId)
return ReferralStat(
totalInvitee = referralRepo.getTotalInviteeForUser(userId),
activeInvitee = referralRepo.getActiveInviteeForUser(userId),
totalIncome = referral.totalIncome,
availableIncome = referral.availableIncome,
holdBalance = referral.holdBalance,
referralLink = referral.referralLink
)
}
fun getInviteeForUser(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
referralRepo.getReferralInvitees(userId, pageSize, pageNumber)
}
+10
View File
@@ -0,0 +1,10 @@
package ru.soune
data class ReferralStat(
val totalInvitee: Int,
val activeInvitee: Int,
val totalIncome: Int,
val availableIncome: Int,
val holdBalance: Int,
val referralLink: String,
)
+1
View File
@@ -3,4 +3,5 @@ plugins {
}
rootProject.name = 'no-copy'
include 'referral'
include 'finance'
@@ -3,9 +3,9 @@ package ru.soune.nocopy.configuration;
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.context.annotation.Bean;
@@ -33,7 +33,8 @@ public class ApplicationConfig {
ImageUniqueCheck imageUniqueCheck,
ImageLocalSearch imageLocalSearch,
AudioLocalSearch audioLocalSearch,
AudioFilePathProvider audioFilePathProvider) {
AudioFilePathProvider audioFilePathProvider,
DocumentLocalSearch documentLocalSearch) {
return new com.vrt.NoCopyFileService(
Collections.emptyList(),
@@ -42,7 +43,8 @@ public class ApplicationConfig {
imageUniqueCheck,
imageLocalSearch,
audioLocalSearch,
audioFilePathProvider
audioFilePathProvider,
documentLocalSearch
);
}
}
@@ -19,7 +19,12 @@ public class HandlerConfig {
LogoutRequestHandler logoutHandler,
ImageFoundRequestHandler imageFoundRequestHandler,
VerifyRegisterUserHandler verifyRegisterUser,
AuthRequestHandler authRequestHandler
AuthRequestHandler authRequestHandler,
CompanyHandler companyHandler,
TariffHandler tariffHandler,
TariffInfoHandler tariffInfoHandler,
ReferralHandler referralHandler,
DaDataHandler daDataHandler
) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
@@ -30,6 +35,11 @@ public class HandlerConfig {
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(30004, daDataHandler);
return map;
}
@@ -9,6 +9,7 @@ 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;
@@ -20,7 +21,13 @@ public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.defaultViewInclusion(true)
.autoDetectFields(true)
.failOnUnknownProperties(false)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.build();
JavaTimeModule javaTimeModule = new JavaTimeModule();
@@ -0,0 +1,16 @@
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);
}
}
@@ -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));
}
}
@@ -5,8 +5,7 @@ import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
@@ -26,10 +25,14 @@ 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.user.AuthToken;
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.service.FileSimilarityService;
import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService;
@@ -67,6 +70,12 @@ public class ApiController {
private final FileUtil fileUtil;
private final ProtectionsLimitService protectionsLimitService;
private final AuthTokenRepository authTokenRepository;
private final CloudStorageService cloudStorageService;
@PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) {
@@ -285,7 +294,6 @@ public class ApiController {
}
}
@GetMapping("/v{version}/files/download/{fileId}")
public ResponseEntity<?> downloadFile(
@PathVariable(required = false) String fileId,
@@ -337,12 +345,14 @@ public class ApiController {
errorData));
}
Path filePath = Paths.get(entityResponse.getFilePath());
Resource resource = new UrlResource(filePath.toUri());
Path filePath = Paths.get(entityResponse.getProtectedFilePath());
File file = cloudStorageService.readFileFromStorageByPath(
entityResponse.getProtectedFilePath());
long fileSize = Files.size(filePath);
if (!resource.exists()) {
if (!file.exists()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("resource", resource.exists());
errorData.put("file", file.exists());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
@@ -353,11 +363,11 @@ public class ApiController {
String contentType = determineContentType(filePath);
return ResponseEntity.ok()
.contentLength(fileSize)
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getOriginalFileName() + "\"")
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(entityResponse.getFileSize()))
.body(resource);
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(file);
} catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", fileId);
@@ -401,14 +411,14 @@ public class ApiController {
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 = type.toLowerCase().equals("image") ? Paths.get(fileEntity.getFilePath()) :
Paths.get(fileEntity.getProtectedFilePath());
Path path = Paths.get(fileEntity.getProtectedFilePath());
File file = path.toFile();
@@ -418,48 +428,17 @@ public class ApiController {
return ResponseEntity.ok().body(noCopyCheckResult);
}
private ResponseEntity<BaseResponse> checkForDuplicates(String uploadId) throws IOException {
Optional<FileEntity> uploadedFile = fileEntityRepository.findByUploadSessionId(uploadId);
@GetMapping("/check/file_stats")
public ResponseEntity<?> checkFileProtectStats(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
if (uploadedFile.isEmpty() || !uploadedFile.get().getMimeType().equals("image")) {
return null;
}
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
FileEntity fileEntity = uploadedFile.get();
List<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileEntity.getId());
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
if (hasDuplicate(similarFiles)) {
return handleDuplicate(fileEntity, similarFiles);
}
Map<String, Object> fileTypeStats = protectionsLimitService.getFileTypeStats(authToken.getUser().getId());
return null;
}
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;
return ResponseEntity.ok().body(fileTypeStats);
}
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
@@ -0,0 +1,97 @@
package ru.soune.nocopy.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
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.FileStorageService;
import org.springframework.http.ContentDisposition;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
@RestController
@RequestMapping("/api/files")
@Slf4j
public class FileController {
@Autowired
private FileStorageService fileStorageService;
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private CheckCounterService checkCounterService;
@Autowired
private UserRepository userRepository;
@GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(
@PathVariable String fileId) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.body(resource);
}
@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");
}
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + fileEntity.getOriginalFileName() + "\"")
.body(resource);
}
@GetMapping("check-file/status/{userId}")
public ResponseEntity<CheckStatus> getStatus(@PathVariable Long userId) {
CheckStatus status = checkCounterService.getCurrentStatus(userId);
return ResponseEntity.ok(status);
}
@PostMapping("check-file/update-limit/{userId}/{limit}")
public ResponseEntity<ProtectedFileCheck> getStatus(@PathVariable Long userId, @PathVariable Integer limit) {
User user = userRepository.findById(userId).orElseThrow();
ProtectedFileCheck protectedFileCheck = checkCounterService.updateLimit(user, limit);
return ResponseEntity
.ok()
.body(protectedFileCheck);
}
}
@@ -1,5 +1,6 @@
package ru.soune.nocopy.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@@ -15,6 +16,14 @@ 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();
@@ -32,4 +41,12 @@ public class HealtCheckController {
return info;
}
static class BuildInfo {
public String buildTimeBack;
public BuildInfo(String buildTimeBack) {
this.buildTimeBack = buildTimeBack;
}
}
}
@@ -9,8 +9,11 @@ import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.register.ChangePasswordRequest;
import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.dto.register.RegRequest;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
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;
@@ -19,7 +22,9 @@ import ru.soune.nocopy.exception.NotValidFieldException;
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 java.util.Arrays;
@@ -44,12 +49,17 @@ public class UserController {
private final AuthService authService;
private final TariffService tariffService;
private final FileStatsService fileStatsService;
@GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.isActive(),
.map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType()))
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
null, null))
.toList();
return ResponseEntity.ok(allUsers);
@@ -62,26 +72,69 @@ public class UserController {
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
if (authToken != null) {
if (tokenOptional.isPresent()) {
User user = userRepository.findByEmail(email);
//TODO add mapper
if (user != null) {
UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email);
userDTO.setFullName(user.getFullName());
userDTO.setCompany(user.getCompany());
if (user.getCompany() != null) {
userDTO.setCompany(user.getCompany().getCompanyName());
}
if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
userDTO.setTariffs(tariffService.getTariffByAccountType(user));
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();
}
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())
.tokens(personalTariffInfo.getTokens() + personalTariffInfo.getBoughtTokens())
.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);
}
} else {
return ResponseEntity.notFound().build();
}
return ResponseEntity.notFound().build();
@@ -144,10 +197,6 @@ public class UserController {
user.setActive(true);
user.setEmailVerified(true);
if (registerRequest.getCompanyName() != null) {
user.setCompany(registerRequest.getCompanyName());
}
if (registerRequest.getPhone() != null) {
user.setPhone(registerRequest.getPhone());
}
@@ -4,6 +4,7 @@ public enum MessageCode {
SUCCESS(0, "Operation successful"),
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"),
INVALID_FIELD(2, "Invalid field"),
INVALID_TOKEN(2, "Invalid token"),
TOKEN_IS_ALIVE(2, "Token is alive"),
@@ -12,6 +13,7 @@ public enum MessageCode {
DUPLICATE_FILE_UPLOAD(2, "Duplicate file upload"),
FILE_DOWNLOAD_ERROR(2, "File download error"),
USER_NOT_VERIFIED(2, "User not verified"),
PERMISSION_NOT_FOUND(2, "Permission not found"),
USER_NOT_FOUND(2, "User not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
IMAGE_FOUND_ERROR(2, "Image found error"),
@@ -27,7 +29,20 @@ public enum MessageCode {
FILE_NOT_FOUND(4, "File not found"),
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"),
SEND_EMAIL_EXCEPTION(2, "Send email exception"),
SIMILAR_FILES_FOUND(0, "Similar files found");
SIMILAR_FILES_FOUND(0, "Similar files found"),
FILE_IS_PROTECTED(0, "File is protected"),
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_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"),
INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"),
COMPANY_ALREADY_EXISTS(2, "Company already exists"),
USER_LIMIT_IS_OVER(2, "Over user limits"),
ERROR_TARIFF_INFO(2, "Erorr with tariff info");
private final Integer code;
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.company;
import lombok.Data;
import java.util.Map;
@Data
public class CompanyActionRequestDto {
private String action;
private String companyId;
private String companyName;
private Map<String, Object> companyData;
private Long userId;
private Map<String, Object> searchParams;
private PageableParams pageable;
@Data
public static class PageableParams {
private int page;
private int size;
private String sort;
private String direction;
}
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.company;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class CompanyResponseDto {
private String id;
private String companyName;
private String phone;
private String address;
private String email;
private LocalDateTime registerDate;
private LocalDateTime updateDate;
private long userCount;
}
@@ -0,0 +1,10 @@
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;
}
@@ -0,0 +1,11 @@
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;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataRequest {
@JsonProperty("inn")
private String inn;
}
@@ -0,0 +1,16 @@
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;
}
@@ -0,0 +1,11 @@
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;
}
@@ -0,0 +1,12 @@
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;
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CheckIncrementResult {
private boolean success;
private Integer countChecked;
private Integer remainingLimit;
private String message;
public static CheckIncrementResult success(Integer countChecked) {
return CheckIncrementResult.builder()
.success(true)
.countChecked(countChecked)
.message("Check count incremented successfully")
.build();
}
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class CheckStatus {
private Long userId;
private Integer countChecked;
private Integer remainingLimit;
private LocalDateTime lastCheckAt;
}
@@ -19,6 +19,7 @@ public class FileEntityResponse {
private String originalFileName;
private String storedFileName;
private String filePath;
private String protectedFilePath;
private Long fileSize;
private String mimeType;
private String fileExtension;
@@ -32,4 +33,11 @@ public class FileEntityResponse {
private boolean existsOnDisk;
private Integer supportId;
private String protectStatus;
private String ownerName;
private String ownerEmail;
private String ownerCompany;
private String fileName;
private String fileFormat;
private Integer checksCount;
private LocalDateTime fileUploadDate;
}
@@ -59,6 +59,21 @@ 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;
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileTypeStatsDto {
private String fileType;
private Integer count;
}
@@ -7,4 +7,10 @@ import lombok.Data;
public class ImageSearchRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("page")
private Integer page = 1;
@JsonProperty("page_size")
private Integer pageSize = 10;
}
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PeriodHistoryDto {
private LocalDate periodStart;
private LocalDate periodEnd;
private Integer totalChecks;
private Map<String, Integer> checksByType;
}
@@ -0,0 +1,29 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProtectionLimitsDto {
private Long userId;
private String userName;
private String userEmail;
private LocalDate lastResetDate;
private Integer totalChecksAllTime;
private Integer periodChecksCount;
private Map<String, Integer> checksByFileType;
private LocalDate periodStartDate;
private LocalDate periodEndDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastCheckAt;
}
@@ -2,6 +2,9 @@ package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Value;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import java.time.LocalDateTime;
@Value
@Builder
@@ -12,4 +15,8 @@ public class SimilarFileDTO {
Integer hammingDistance;
String similarityLevel;
Long ownerId;
Long supportId;
ProtectionStatus status;
LocalDateTime uploadDate;
String url;
}
@@ -12,6 +12,11 @@ public class YandexSearchResponse {
@JsonProperty("images")
private List<ImageResult> images;
// @JsonProperty("images")
// public void setImages(List<ImageResult> images) {
// this.images = images;
// }
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult {
@@ -33,4 +38,12 @@ public class YandexSearchResponse {
@JsonProperty("host")
private String host;
}
private int page;
private int pageSize;
private int totalResults;
private int totalPages;
}
@@ -0,0 +1,18 @@
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 String token;
private Integer amount;
private Integer pageSize;
private Integer pageNumber;
private String action;
}
@@ -0,0 +1,5 @@
package ru.soune.nocopy.dto.register;
public enum AccountType {
B2B, B2C
}
@@ -13,6 +13,9 @@ public class RegRequest {
private String companyName;
@NotEmpty(message = "Need account type")
private String accountType;
@Size(min = 11, max = 14, message = "Phone must be 11-14 digits")
private String phone;
@@ -24,4 +27,8 @@ public class RegRequest {
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
private String authToken;
private String referralLink;
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.dto.tarriff;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TariffDTO {
private Long id;
private String type;
private String name;
private double price;
private int tokens;
private int maxFilesCount;
private Long diskSize;
private Long maxUsers;
}
@@ -0,0 +1,26 @@
package ru.soune.nocopy.dto.tarriff;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TariffInfoDTO {
private String id;
private String status;
private LocalDateTime startTariff;
private LocalDateTime endTariff;
private Long tariffId;
private String tariffName;
private Integer tokens;
private Long maxFileOnDisk;
private Long currentFileOnDisk;
private Integer currentFileCounts;
private Integer maxFileCounts;
}
@@ -0,0 +1,35 @@
package ru.soune.nocopy.dto.tarriff;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TariffInfoRequest {
private String id;
@NotNull(message = "Action is required")
@JsonProperty("action")
private String action;
@JsonProperty("status")
private String status;
@JsonProperty("start_tariff")
private LocalDateTime startTariff;
@JsonProperty("end_tariff")
private LocalDateTime endTariff;
@JsonProperty("tariff_id")
private Long tariffId;
@JsonProperty("tariff_type")
private String tariffType;
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.dto.tarriff;
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 TariffInfoResponse {
@JsonProperty("id")
private String id;
@JsonProperty("status")
private String status;
@JsonProperty("start_tariff")
private LocalDateTime startTariff;
@JsonProperty("end_tariff")
private LocalDateTime endTariff;
@JsonProperty("tariff_id")
private Long tariffId;
@JsonProperty("tariff_name")
private String tariffName;
}
@@ -0,0 +1,52 @@
package ru.soune.nocopy.dto.tarriff;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TariffRequest {
private Long id;
@NotNull
@NotBlank
@JsonProperty("type")
private String type;
@NotNull
@NotBlank
@JsonProperty("tariff_name")
private String name;
@Min(value = 0, message = "Price cannot be negative")
@JsonProperty("tariff_price")
private double price;
@Min(value = 0, message = "Tokens must be at least 0")
@JsonProperty("tariff_tokens")
private int tokens;
@Min(value = 0, message = "Max files count must be at least 0")
@JsonProperty("max_files")
private int maxFilesCount;
@Min(value = 0, message = "Disk size must be at least 0")
@JsonProperty("disk_size")
private int diskSize;
@Min(value = 0, message = "Max users must be at least 0")
@JsonProperty("max_users")
private int maxUsers;
@JsonProperty("action")
private String action;
@JsonProperty("user_token")
private String userToken;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.tarriff;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.tarif.Tariff;
@Data
@Builder
public class TariffResponse {
private String tariffName;
private String tariffType;
}
@@ -4,11 +4,14 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.dto.tarriff.TariffDTO;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.entity.user.GenderType;
import ru.soune.nocopy.entity.user.SubscriptionType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Data
@AllArgsConstructor
@@ -24,4 +27,7 @@ public class UserDTO {
private LocalDate birthday;
private LocalDateTime createdAt;
private SubscriptionType subscriptionType;
private List<TariffDTO> tariffs;
private TariffInfoDTO tariffInfo;
private Long permission;
}
@@ -0,0 +1,54 @@
package ru.soune.nocopy.entity.company;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter @Setter
@Table(name = "company")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(name = "company_name")
private String companyName;
@Column(name = "address")
private String address;
@Column(name = "phone")
private String phone;
@CreatedDate
@Column(name = "register_date", updatable = false, nullable = false)
private LocalDateTime registerDate = LocalDateTime.now();
@Column(name = "update_date")
private LocalDateTime updateDate;
@Size(max = 1024)
@Column(name = "email", length = 1024)
private String email;
@OneToMany(mappedBy = "company")
@JsonIgnore
@ToString.Exclude
private List<User> users = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "tariff_info_id")
private TariffInfo tariffInfo;
}
@@ -3,13 +3,10 @@ package ru.soune.nocopy.entity.file;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.GenerationTime;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.LocalDateTime;
@Data
@@ -6,5 +6,6 @@ public enum FileStatus {
PROCESSING,
VIOLATION,
CHECKED,
ERROR
ERROR,
TEMP
}
@@ -7,11 +7,12 @@ import java.util.List;
@Getter
public enum FileType {
// IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "webp", "jfif")),
IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp")),
VIDEO("video", Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpg", "mpeg",
"3gp", "3g2", "f4v", "m2ts", "mts", "vob", "ogv", "divx")),
AUDIO("audio", List.of("wav"));
AUDIO("audio", List.of("wav")),
DOCUMENT("document", Arrays.asList("pdf"));
private final String displayName;
private final List<String> allowedExtensions;
@@ -0,0 +1,37 @@
package ru.soune.nocopy.entity.referral;
import jakarta.persistence.*;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import java.time.LocalDateTime;
@Entity
@Table(name = "referrals")
@Data
public class Referral {
@Id
private Long userId;
@Column(name = "referral_link", unique = true, nullable = false)
private String referralLink;
@Column(name = "inviter_id")
private Long inviterId = 0L;
@Column(name = "level_id")
private String levelId = "bronze";
@Column(name = "total_income")
private int totalIncome = 0;
@Column(name = "is_active")
private boolean isActive = false;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(name = "hold_balance")
private int holdBalance = 0;
}
@@ -0,0 +1,21 @@
package ru.soune.nocopy.entity.referral;
import jakarta.persistence.*;
import lombok.Data;
@Entity
@Table(name = "referral_levels")
@Data
public class ReferralLevelEntity {
@Id
private String id;
@Column(nullable = false)
private String name;
@Column(name = "min_invitees")
private int minInvitees;
@Column(name = "reward_percentage")
private int rewardPercentage;
}
@@ -0,0 +1,43 @@
package ru.soune.nocopy.entity.tarif;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "tariff")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Tariff {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "type", unique = true, nullable = false)
private String type;
@Column(name = "tariff_name")
private String name;
@Column(name = "price")
private double price;
@Column(name = "tokens")
private int tokens;
@Column(name = "max_files_count")
private int maxFilesCount;
@Column(name = "disk_size")
private Long diskSize;
@Column(name = "max_users")
private Long maxUsers;
@Column(name = "tariffAccountType")
private String tariffAccountType;
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.entity.tarif;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Table(name = "tariff_info")
@AllArgsConstructor
@NoArgsConstructor
@Getter @Setter
public class TariffInfo {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(name = "status", nullable = false)
private TariffStatus status;
@Column(name = "start_tariff")
private LocalDateTime startTariff;
@Column(name = "end_tariff")
private LocalDateTime endTariff;
@Column(name = "tokens")
private Integer tokens;
@Column(name = "bought_tokens")
private Integer boughtTokens = 0;
@ManyToOne
@JoinColumn(name = "tariff_id")
private Tariff tariff;
}
@@ -0,0 +1,5 @@
package ru.soune.nocopy.entity.tarif;
public enum TariffStatus {
ACTIVE, EXPIRED, INACTIVE
}
@@ -0,0 +1,6 @@
package ru.soune.nocopy.entity.tarif;
public enum TariffType {
START, BASIC, PRO, ENTERPRISE, DEMO, BUISNES, CORPORAT, STUDIO, FREE;
}
@@ -0,0 +1,30 @@
package ru.soune.nocopy.entity.user;
public class Permission {
public static final long LOGIN_BIT = 1L;
public static final long COMPANY_SETTINGS_BIT = 1L << 1;
public static final long DEFAULT_USER_PERMISSIONS = LOGIN_BIT;
public static final long COMPANY_ADMIN_PERMISSIONS =
LOGIN_BIT | COMPANY_SETTINGS_BIT;
public static final long SUPER_ADMIN_PERMISSIONS = -1L;
public static boolean hasPermission(long userPermissions, long permissionBit) {
return (userPermissions & permissionBit) != 0;
}
public static long addPermission(long userPermissions, long permissionBit) {
return userPermissions | permissionBit;
}
public static long removePermission(long userPermissions, long permissionBit) {
return userPermissions & ~permissionBit;
}
public static boolean canLogin(long userPermissions) {
return hasPermission(userPermissions, LOGIN_BIT);
}
public static boolean canManageCompanySettings(long userPermissions) {
return hasPermission(userPermissions, COMPANY_SETTINGS_BIT);
}
}
@@ -0,0 +1,103 @@
package ru.soune.nocopy.entity.user;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
@Entity
@Table(name = "protect_check")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
public class ProtectedFileCheck {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", unique = true, nullable = false)
private User user;
@Column(nullable = false)
@Builder.Default
private Integer countChecked = 0;
@Column(name = "last_check_at")
private LocalDateTime lastCheckAt;
@Version
private Long version;
@ElementCollection
@CollectionTable(
name = "protection_usage_by_type",
joinColumns = @JoinColumn(name = "protection_usage_id"))
@MapKeyColumn(name = "file_type")
@Column(name = "count")
@Builder.Default
private Map<String, Integer> usageByType = new HashMap<>();
@Column(name = "period_start_date")
@Builder.Default
private LocalDate periodStartDate = LocalDate.now();
@Column(name = "period_count")
@Builder.Default
private Integer periodCount = 0;
public synchronized boolean writeCheck(String fileType) {
resetPeriodIfNeeded();
countChecked++;
periodCount++;
usageByType.merge(fileType, 1, Integer::sum);
lastCheckAt = LocalDateTime.now();
return true;
}
private void resetPeriodIfNeeded() {
LocalDate today = LocalDate.now();
if (periodStartDate == null ||
ChronoUnit.DAYS.between(periodStartDate, today) >= 30) {
cleanupOldData();
periodStartDate = today;
periodCount = 0;
}
}
public void cleanupOldData() {
if (usageByType != null && !usageByType.isEmpty()) {
usageByType.clear();
periodCount = 0;
}
lastCheckAt = LocalDateTime.now();
}
public int getPeriodCount() {
resetPeriodIfNeeded();
return periodCount;
}
public LocalDate getPeriodStartDate() {
resetPeriodIfNeeded();
return periodStartDate;
}
}
@@ -6,7 +6,10 @@ import jakarta.validation.constraints.Size;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.Violation;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffStatus;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -37,9 +40,6 @@ public class User {
@Column(name = "email_verified")
private boolean emailVerified = false;
@Column(name = "company")
private String company;
@Column(nullable = false)
@Size(min = 6)
@JsonIgnore
@@ -83,4 +83,67 @@ public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<Violation> violations = new ArrayList<>();}
private List<Violation> violations = new ArrayList<>();
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private ProtectedFileCheck protectedFileCheck;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id")
@ToString.Exclude
private Company company;
@Column(name = "permissions")
private Long userPermissions = Permission.DEFAULT_USER_PERMISSIONS;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "personal_tariff_info_id")
@ToString.Exclude
private TariffInfo personalTariffInfo;
public TariffInfo getActiveTariffInfo() {
if (company != null && company.getTariffInfo() != null) {
return company.getTariffInfo();
}
return personalTariffInfo;
}
public boolean hasTariffAccess() {
TariffInfo activeTariff = getActiveTariffInfo();
if (activeTariff == null) {
return false;
}
return activeTariff.getStatus() == TariffStatus.ACTIVE &&
LocalDateTime.now().isBefore(activeTariff.getEndTariff());
}
public boolean canLogin() {
return Permission.canLogin(this.userPermissions);
}
public boolean canManageCompanySettings() {
return Permission.canManageCompanySettings(this.userPermissions);
}
public void grantPermission(long permissionBit) {
this.userPermissions = Permission.addPermission(this.userPermissions, permissionBit);
}
public void removePermission(long permissionBit) {
this.userPermissions = Permission.removePermission(this.userPermissions, permissionBit);
}
public boolean hasPermission(long permissionBit) {
return Permission.hasPermission(this.userPermissions, permissionBit);
}
public String getCompanyName() {
if (company != null) {
return company.getCompanyName();
} else {
return null;
}
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class MaxUserCountIsOver extends RuntimeException {
public MaxUserCountIsOver(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.exception;
public class RetryableException extends RuntimeException {
public RetryableException(String message) {
super(message);
}
public RetryableException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class TariffNotFoundException extends RuntimeException {
public TariffNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,411 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.company.CompanyActionRequestDto;
import ru.soune.nocopy.dto.company.CompanyResponseDto;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.ResourceNotFoundException;
import ru.soune.nocopy.exception.ValidationException;
import ru.soune.nocopy.handler.validator.RegRequestValidator;
import ru.soune.nocopy.service.CompanyService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class CompanyHandler implements RequestHandler {
private final CompanyService companyService;
private final RegRequestValidator regRequestValidator;
private final ObjectMapper objectMapper;
@Override
public BaseResponse handle(BaseRequest request) {
try {
CompanyActionRequestDto actionRequest = objectMapper.convertValue(
request.getMessageBody(),
CompanyActionRequestDto.class
);
String action = actionRequest.getAction();
return switch (action) {
case "create" -> handleCreate(actionRequest, request.getMsgId());
case "update" -> handleUpdate(actionRequest, request.getMsgId());
case "delete" -> handleDelete(actionRequest, request.getMsgId());
case "getCompany" -> handleGet(actionRequest, request.getMsgId());
case "getAll" -> handleGetAll(actionRequest, request.getMsgId());
case "addUser" -> handleAddUser(actionRequest, request.getMsgId());
case "removeUser" -> handleRemoveUser(actionRequest, request.getMsgId());
case "getUsers" -> handleGetUsers(actionRequest, request.getMsgId());
case "search" -> handleSearch(actionRequest, request.getMsgId());
case "countUser" -> handleCountUsers(actionRequest, request.getMsgId());
default -> new BaseResponse(
request.getMsgId(),
MessageCode.INVALID_ACTION.getCode(),
"Invalid action: " + action,
null
);
};
} catch (ValidationException e) {
Map<String, String> errors = e.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
error -> error.getField(),
error -> error.getDefaultMessage() != null ? error.getDefaultMessage() : "Validation error"
));
return new BaseResponse(
request.getMsgId(),
MessageCode.VALIDATION_ERROR.getCode(),
"Validation failed",
Map.of("errors", errors)
);
} catch (ResourceNotFoundException e) {
return new BaseResponse(
request.getMsgId(),
MessageCode.COMPANY_NOT_FOUND.getCode(),
e.getMessage(),
null
);
} catch (IllegalArgumentException e) {
return new BaseResponse(
request.getMsgId(),
MessageCode.VALIDATION_ERROR.getCode(),
e.getMessage(),
null
);
} catch (Exception e) {
log.error("Error handling company action", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.INTERNAL_ERROR.getCode(),
"Error processing request: " + e.getMessage(),
null
);
}
}
private BaseResponse handleCreate(CompanyActionRequestDto request, Integer msgId) {
if (companyService.companyExistsByName(request.getCompanyName())) {
return new BaseResponse(
msgId,
MessageCode.COMPANY_ALREADY_EXISTS.getCode(),
"Company with this name already exists",
null
);
}
validateCompanyCreateData(request);
Company company = companyService.addCompany(
request.getCompanyName(),
getStringParam(request, "phone"),
getStringParam(request, "address"),
getStringParam(request, "email")
);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Company created successfully",
mapToResponseDto(company)
);
}
private BaseResponse handleUpdate(CompanyActionRequestDto request, Integer msgId) {
if (request.getCompanyId() == null) {
throw new IllegalArgumentException("Company ID is required for update");
}
Map<String, Object> companyData = request.getCompanyData();
if (companyData == null) {
companyData = new HashMap<>();
}
Company company = companyService.updateCompany(
request.getCompanyId(),
(String) companyData.get("companyName"),
(String) companyData.get("phone"),
(String) companyData.get("address"),
(String) companyData.get("email")
);
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Company updated successfully",
mapToResponseDto(company)
);
}
private BaseResponse handleDelete(CompanyActionRequestDto request, Integer msgId) {
if (request.getCompanyId() == null) {
throw new IllegalArgumentException("Company ID is required for deletion");
}
companyService.deleteCompany(request.getCompanyId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Company deleted successfully",
Map.of("deletedCompanyId", request.getCompanyId())
);
}
private BaseResponse handleGet(CompanyActionRequestDto request, Integer msgId) {
Company company;
if (request.getCompanyId() != null) {
company = companyService.getCompanyById(request.getCompanyId());
} else if (request.getCompanyName() != null) {
company = companyService.getCompanyByName(request.getCompanyName());
} else {
throw new IllegalArgumentException("Either companyId or companyName is required");
}
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Company retrieved successfully",
mapToResponseDto(company)
);
}
private BaseResponse handleGetAll(CompanyActionRequestDto request, Integer msgId) {
List<Company> companies;
if (request.getPageable() != null) {
Pageable pageable = createPageable(request.getPageable());
Page<Company> companyPage = companyService.getAllCompanies(pageable);
List<CompanyResponseDto> companyDtos = companyPage.getContent().stream()
.map(this::mapToResponseDto)
.collect(Collectors.toList());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Companies retrieved successfully",
Map.of(
"companies", companyDtos,
"totalElements", companyPage.getTotalElements(),
"totalPages", companyPage.getTotalPages(),
"currentPage", companyPage.getNumber(),
"pageSize", companyPage.getSize()
)
);
} else {
companies = companyService.getAllCompanies();
List<CompanyResponseDto> companyDtos = companies.stream()
.map(this::mapToResponseDto)
.collect(Collectors.toList());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Companies retrieved successfully",
Map.of("companies", companyDtos)
);
}
}
private BaseResponse handleAddUser(CompanyActionRequestDto request, Integer msgId) {
if (request.getCompanyId() == null || request.getUserId() == null) {
throw new IllegalArgumentException("Company ID and User ID are required");
}
companyService.addUserToCompany(request.getCompanyId(), request.getUserId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"User added to company successfully",
Map.of(
"companyId", request.getCompanyId(),
"userId", request.getUserId()
)
);
}
private BaseResponse handleRemoveUser(CompanyActionRequestDto request, Integer msgId) {
if (request.getUserId() == null) {
throw new IllegalArgumentException("User ID is required");
}
companyService.deleteUserFromCompany(request.getUserId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"User removed from company successfully",
Map.of("userId", request.getUserId())
);
}
private BaseResponse handleGetUsers(CompanyActionRequestDto request, Integer msgId) {
if (request.getCompanyId() == null) {
throw new IllegalArgumentException("Company ID is required");
}
List<User> users = companyService.getCompanyUsers(request.getCompanyId());
List<Map<String, Object>> userDtos = users.stream()
.map(user -> {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
map.put("email", user.getEmail());
map.put("phone", user.getPhone());
map.put("fullName", user.getFullName());
return map;
}).toList();
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Company users retrieved successfully",
Map.of(
"companyId", request.getCompanyId(),
"users", userDtos,
"count", userDtos.size()
)
);
}
private BaseResponse handleSearch(CompanyActionRequestDto request, Integer msgId) {
String searchTerm = getStringParam(request, "name");
if (searchTerm == null) {
throw new IllegalArgumentException("Search term is required");
}
List<Company> companies = companyService.searchCompaniesByName(searchTerm);
List<CompanyResponseDto> companyDtos = companies.stream()
.map(this::mapToResponseDto)
.collect(Collectors.toList());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"Companies search completed",
Map.of(
"searchTerm", searchTerm,
"companies", companyDtos,
"count", companyDtos.size()
)
);
}
private BaseResponse handleCountUsers(CompanyActionRequestDto request, Integer msgId) {
if (request.getCompanyId() == null) {
throw new IllegalArgumentException("Company ID is required");
}
long count = companyService.countCompanyUsers(request.getCompanyId());
return new BaseResponse(
msgId,
MessageCode.SUCCESS.getCode(),
"User count retrieved",
Map.of(
"companyId", request.getCompanyId(),
"userCount", count
)
);
}
private void validateCompanyCreateData(CompanyActionRequestDto request) throws ValidationException {
BindingResult bindingResult = new BeanPropertyBindingResult(request, "companyRequest");
if (request.getCompanyName() != null) {
regRequestValidator.validateCompanyName(request.getCompanyName(), bindingResult);
}
String email = getStringParam(request, "email");
if (email != null) {
regRequestValidator.validateEmail(email, bindingResult);
}
String phone = getStringParam(request, "phone");
if (phone != null) {
regRequestValidator.validatePhone(phone, bindingResult);
}
if (bindingResult.hasErrors()) {
throw new ValidationException(bindingResult, null);
}
}
private String getStringParam(CompanyActionRequestDto request, String paramName) {
if (request.getCompanyData() != null) {
Object value = request.getCompanyData().get(paramName);
return value != null ? value.toString() : null;
}
Map<String, Object> searchParams = request.getSearchParams();
if (searchParams != null) {
Object value = searchParams.get(paramName);
return value != null ? value.toString() : null;
}
return null;
}
private CompanyResponseDto mapToResponseDto(Company company) {
CompanyResponseDto dto = new CompanyResponseDto();
dto.setId(company.getId());
dto.setCompanyName(company.getCompanyName());
dto.setPhone(company.getPhone());
dto.setAddress(company.getAddress());
dto.setEmail(company.getEmail());
dto.setRegisterDate(company.getRegisterDate());
dto.setUpdateDate(company.getUpdateDate());
try {
dto.setUserCount(companyService.countCompanyUsers(company.getId()));
} catch (Exception e) {
dto.setUserCount(0);
}
return dto;
}
private Pageable createPageable(CompanyActionRequestDto.PageableParams params) {
Sort sort = Sort.unsorted();
if (params.getSort() != null) {
Sort.Direction direction = params.getDirection() != null &&
"desc".equalsIgnoreCase(params.getDirection()) ?
Sort.Direction.DESC : Sort.Direction.ASC;
sort = Sort.by(direction, params.getSort());
}
return PageRequest.of(
params.getPage(),
params.getSize(),
sort
);
}
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.dadata.DaDataRequest;
import ru.soune.nocopy.dto.dadata.DaDataResponse;
import ru.soune.nocopy.service.dadata.DaDataService;
@Component
@RequiredArgsConstructor
@Slf4j
public class DaDataHandler implements RequestHandler {
private final DaDataService daDataService;
private ObjectMapper mapper = new ObjectMapper();
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
DaDataRequest daDataRequest = mapper.convertValue(request.getMessageBody(), DaDataRequest.class);
String inn = daDataRequest.getInn();
DaDataResponse daData = daDataService.callDaData(inn);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), daData);
}
}
@@ -8,16 +8,18 @@ import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.file.*;
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.exception.FileEntityNotFoundException;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.ImageHashRepository;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileStatsService;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Slf4j
@Component
@@ -34,7 +36,7 @@ public class FileEntityHandler implements RequestHandler {
private final FileEntityRepository fileEntityRepository;
private final ImageHashRepository imageHashRepository;
private final CloudStorageService cloudStorageService;
@Override
public BaseResponse handle(BaseRequest request) {
@@ -57,6 +59,8 @@ public class FileEntityHandler implements RequestHandler {
return handleSearchFiles(request, fileRequest);
case "storage_usage":
return handleGetStorageUsage(request, fileRequest);
case "check_protected":
return handleProtectFile(request, fileRequest);
case "delete_file":
return handleDeleteFile(request, fileRequest);
default:
@@ -259,6 +263,29 @@ public class FileEntityHandler implements RequestHandler {
}
}
private BaseResponse handleProtectFile(BaseRequest request, FileEntityRequest fileRequest) {
String fileId = fileRequest.getFileId();
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (optionalFileEntity.isEmpty()) {
return new BaseResponse(request.getMsgId(),
MessageCode.FILE_NOT_FOUND.getCode(),
"File not found exception: " + fileRequest.getFileId(),
null);
}
FileEntity fileEntity = optionalFileEntity.get();
ProtectionStatus protectionStatus = fileEntity.getProtectionStatus();
Integer code = protectionStatus == ProtectionStatus.PROTECTED ? MessageCode.FILE_IS_PROTECTED.getCode():
MessageCode.FILE_IS_NOT_PROTECTED.getCode();
String message = protectionStatus == ProtectionStatus.PROTECTED ? MessageCode.FILE_IS_PROTECTED.getDescription():
MessageCode.FILE_IS_NOT_PROTECTED.getDescription();
return new BaseResponse(request.getMsgId(), code, message, fileEntityService.getById(fileId,
request.getVersion()));
}
private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) {
try {
Long userId = authService.useUserAuthToken(fileRequest.getToken());
@@ -278,17 +305,13 @@ public class FileEntityHandler implements RequestHandler {
int fullDelete = fileRequest.getFullDelete() == null ? 0 : fileRequest.getFullDelete();
if (fileEntity.getStatus().equals(FileStatus.DELETED) || fullDelete == 1) {
fileEntityService.deleteFromDisk(fileEntity);
cloudStorageService.deleteFromStorageDisk(fileEntity);
response = DeleteFileResponse.builder()
.fileId(fileRequest.getFileId())
.message("File deleted from disk")
.build();
} else {
fileEntityService.softDeleteFileWithHash(fileEntity);
//
// imageHashRepository.deleteByFileId(fileEntity.getId());
//
// fileEntityService.markAsDeleted(fileEntity);
response = DeleteFileResponse.builder()
.fileId(fileRequest.getFileId())
@@ -1,5 +1,6 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -9,8 +10,18 @@ import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.ImageSearchRequest;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.service.search.YandexSearchService;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.search.SearchImageService;
import ru.soune.nocopy.service.search.GoogleVisionSearchService;
import ru.soune.nocopy.service.tariff.TariffConstants;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeoutException;
@Slf4j
@Component
@@ -19,10 +30,16 @@ public class ImageFoundRequestHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final YandexSearchService yandexSearchService;
private final SearchImageService searchImageService;
private final GoogleVisionSearchService googleVisionSearchService;
private final CheckCounterService checkCounterService;
private final FileEntityRepository fileEntityRepository;
private final TariffInfoService tariffInfoService;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
ImageSearchRequest imageSearchRequest = objectMapper.convertValue(request.getMessageBody(),
@@ -30,12 +47,66 @@ public class ImageFoundRequestHandler implements RequestHandler {
String fileId = imageSearchRequest.getFileId();
YandexSearchResponse response = yandexSearchService.searchByFileEntity(fileId);
FileEntity fileEntity = fileEntityRepository.findById(fileId)
.orElseThrow(() -> {
throw new NotValidFieldException("File not found", new BaseResponse(20007,
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
Map.of("fileId",fileId)));
});
// YandexSearchResponse response = yandexSearchService.searchByFileEntity(fileEntity);
//TODO uncommited when add billing
// GoogleVisionSearchResponse googleVisionSearchResponse = googleVisionSearchService.searchByFileEntity(fileId);
String searchResponseYandex;
String searchResponseGoogle;
try {
searchResponseYandex = searchImageService.searchReverseByPublicUrl(fileEntity, "yandex_reverse_image",
"visual_matches");
searchResponseGoogle = searchImageService.searchReverseByPublicUrl(fileEntity, "google_lens",
"exact_matches");
} catch (TimeoutException e) {
log.warn("Search timeout for file {}, returning empty results", fileId);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), response);
"Search completed (partial)", Map.of(
"results", List.of(),
"total", 0,
"searchStatus", "timeout"
));
} catch (IOException e) {
log.error("Search failed for file {}", fileId, e);
return new BaseResponse(request.getMsgId(), 20007,
"Search service temporarily unavailable",
Map.of("fileId", fileId));
}
tariffInfoService.writeOffTokens(fileEntity.getUserId(), TariffConstants.TOKEN_VALUE_FOR_SEARCH);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
List<YandexSearchResponse.ImageResult> allYandexImages = searchImageService.getAllImagesWithoutPagination(
searchResponseYandex, "visual_matches");
List<YandexSearchResponse.ImageResult> allGoogleImages = searchImageService.getAllImagesWithoutPagination(
searchResponseGoogle, "exact_matches");
List<YandexSearchResponse.ImageResult> allUniqueImages = searchImageService.removeDuplicateUrls(allYandexImages,
allGoogleImages);
int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1;
int pageSize = 5;
List<YandexSearchResponse.ImageResult> pagedResults = searchImageService.paginateResults(allUniqueImages, page,
pageSize * 2);
YandexSearchResponse finalResponse = new YandexSearchResponse();
finalResponse.setImages(pagedResults);
finalResponse.setPage(page);
finalResponse.setPageSize(pageSize * 2);
finalResponse.setTotalResults(allUniqueImages.size());
finalResponse.setTotalPages((int) Math.ceil((double) allUniqueImages.size() / (pageSize * 2)));
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), finalResponse);
}
}
@@ -50,6 +50,16 @@ public class LoginRequestHandler implements RequestHandler {
MessageCode.USER_NOT_VERIFIED.getDescription(), loginAnswer);
}
if (!user.canLogin()) {
LoginAnswer loginAnswer = new LoginAnswer();
loginAnswer.setFieldErrors(Arrays.asList(Map.of(
"login_permission", "not found")));
return new BaseResponse(request.getMsgId(), MessageCode.PERMISSION_NOT_FOUND.getCode(),
MessageCode.PERMISSION_NOT_FOUND.getDescription(), loginAnswer);
}
AuthToken authToken = authService.login(loginRequest);
String token = authToken.getToken();
@@ -0,0 +1,90 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.soune.ReferralInvitee;
import ru.soune.ReferralLevel;
import ru.soune.ReferralService;
import ru.soune.ReferralStat;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.referral.ReferralRequest;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.repository.AuthTokenRepository;
import java.util.List;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class ReferralHandler implements RequestHandler {
private final ReferralService referralService;
private final AuthTokenRepository authTokenRepository;
private final ObjectMapper mapper = new ObjectMapper();
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
ReferralRequest referralRequest = mapper.convertValue(request.getMessageBody(), ReferralRequest.class);
String action = referralRequest.getAction();
String token = referralRequest.getToken();
AuthToken authToken = authTokenRepository.findByToken(token).orElseThrow();
switch (action) {
case "refill":
return handleRefill(request, authToken.getUser().getId(), referralRequest.getAmount());
case "levels":
return handleReferralLevels(request);
case "level":
return handleUserLevel(request, authToken.getUser().getId());
case "userStats" :
return handleUserStats(request, authToken.getUser().getId());
case "invitees":
return handleInvitees(referralRequest, request, authToken.getUser().getId());
}
return null;
}
private BaseResponse handleRefill(BaseRequest request, Long userId, Integer amount) {
referralService.onUserAccountRefill(userId, amount);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), Map.of("account", "refill"));
}
private BaseResponse handleReferralLevels(BaseRequest request) {
List<ReferralLevel> referralLevels = referralService.getReferralLevels();
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), referralLevels);
}
private BaseResponse handleUserLevel(BaseRequest request, Long userId) {
ReferralLevel referralLevelForUser = referralService.getReferralLevelForUser(userId);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), referralLevelForUser);
}
private BaseResponse handleUserStats(BaseRequest request, Long userId) {
ReferralStat referralStatForUser = referralService.getReferralStatForUser(userId);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), referralStatForUser);
}
private BaseResponse handleInvitees(ReferralRequest referralRequest, BaseRequest request, Long userId) {
List<ReferralInvitee> inviteeForUser = referralService.getInviteeForUser(userId, referralRequest.getPageSize(),
referralRequest.getPageNumber());
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), inviteeForUser);
}
}
@@ -6,14 +6,16 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import ru.soune.ReferralService;
import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.dto.register.RegRequest;
import ru.soune.nocopy.entity.referral.Referral;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.EmailVerificationToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.ValidationException;
import ru.soune.nocopy.handler.validator.RegRequestValidator;
import ru.soune.nocopy.repository.ReferralJpaRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.mail.EmailService;
@@ -35,6 +37,10 @@ public class RegRequestHandler implements RequestHandler {
private final EmailService emailService;
private final ReferralService referralService;
private final ReferralJpaRepository referralJpaRepository;
@Override
public BaseResponse handle(BaseRequest request) throws ValidationException {
RegRequest regRequest = objectMapper.convertValue(request.getMessageBody(), RegRequest.class);
@@ -50,6 +56,19 @@ public class RegRequestHandler implements RequestHandler {
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
}
String referralLink = regRequest.getReferralLink();
if (referralLink != null) {
Referral referral = referralJpaRepository.findByReferralLink(referralLink);
if (referral == null) {
throw new NotValidFieldException("Referral link is not found:" + referralLink +
referralLink, new BaseResponse(request.getMsgId(),
MessageCode.REFERRAL_LINK_IS_NOT_EXIST.getCode(),
MessageCode.REFERRAL_LINK_IS_NOT_EXIST.getDescription(), Map.of(
"referralLink", referralLink)));
}
}
BindingResult bindingResult = new BeanPropertyBindingResult(regRequest, "regRequest");
regRequestValidator.validate(regRequest, bindingResult);
@@ -59,26 +78,32 @@ public class RegRequestHandler implements RequestHandler {
}
AuthToken authToken = authService.register(regRequest);
EmailVerificationToken emailToken = emailService.createEmailVerificationToken(authToken);
// EmailVerificationToken emailToken = emailService.createEmailVerificationToken(authToken);
try {
emailService.sendVerificationEmail(authToken.getUser(), emailToken.getToken());
} catch (Exception e) {
return new BaseResponse(request.getMsgId(), MessageCode.SEND_EMAIL_EXCEPTION.getCode(),
MessageCode.SEND_EMAIL_EXCEPTION.getDescription(), Map.of(
"message", "not send",
"email", regRequest.getEmail()
));
}
// try {
// emailService.sendVerificationEmail(authToken.getUser(), emailToken.getToken());
// } catch (Exception e) {
// return new BaseResponse(request.getMsgId(), MessageCode.SEND_EMAIL_EXCEPTION.getCode(),
// MessageCode.SEND_EMAIL_EXCEPTION.getDescription(), Map.of(
// "message", "not send",
// "email", regRequest.getEmail()
// ));
// }
String token = authToken.getToken();
authService.useUserAuthToken(token);
RegAnswer regAnswer = new RegAnswer();
regAnswer.setVerified(false);
regAnswer.setActive(false);
regAnswer.setUserId(authToken.getUser().getId());
regAnswer.setToken(token);
regAnswer.setVerified(true);
regAnswer.setActive(true);
if (regRequest.getAccountType().equals("b2c")) {
referralService.onRegister(authToken.getUser().getId(), regRequest.getReferralLink());
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), regAnswer);
@@ -0,0 +1,129 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.tarriff.TariffDTO;
import ru.soune.nocopy.dto.tarriff.TariffRequest;
import ru.soune.nocopy.dto.tarriff.TariffResponse;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.service.tariff.TariffService;
import java.util.List;
import java.util.Map;
@Component
@Slf4j
@RequiredArgsConstructor
public class TariffHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final TariffService tariffService;
private final AuthTokenRepository authTokenRepository;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
try {
TariffRequest tariffRequest = objectMapper.convertValue(request.getMessageBody(), TariffRequest.class);
String action = tariffRequest.getAction();
switch (action) {
case "add":
return handleAddTariff(request, tariffRequest);
case "remove":
return handleRemoveTariff(request, tariffRequest);
case "update":
return handleUpdateTariff(request, tariffRequest);
case "get" :
return handleGetTariffs(request, tariffRequest);
}
} catch (Exception e) {
log.error("Error add tariff", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Error processing request: " + e.getMessage(),
null);
}
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(), null);
}
private BaseResponse handleAddTariff(BaseRequest request, TariffRequest tariffRequest) {
tariffService.addTariff(tariffRequest.getName(), tariffRequest.getPrice(), tariffRequest.getType(),
tariffRequest.getTokens(), tariffRequest.getDiskSize(), tariffRequest.getMaxUsers(),
tariffRequest.getMaxFilesCount());
TariffResponse tariffResponse = TariffResponse.builder()
.tariffName(tariffRequest.getName())
.tariffType(tariffRequest.getType())
.build();
return new BaseResponse(request.getMsgId(), MessageCode.TARIFF_IS_ADD.getCode(),
MessageCode.TARIFF_IS_ADD.getDescription(),
tariffResponse);
}
private BaseResponse handleRemoveTariff(BaseRequest request, TariffRequest tariffRequest) {
TariffDTO tariff = tariffService.getTariffById(tariffRequest.getId());
if (tariff == null) {
return new BaseResponse(request.getMsgId(), MessageCode.TARIFF_IS_NOT_FOUND.getCode(),
MessageCode.TARIFF_IS_NOT_FOUND.getDescription(),
Map.of("id", tariffRequest.getId()));
}
TariffResponse tariffResponse = TariffResponse.builder()
.tariffName(tariff.getName())
.tariffType(tariff.getType())
.build();
tariffService.deleteTariff(tariffRequest.getId());
return new BaseResponse(request.getMsgId(), MessageCode.TARIFF_IS_DELETED.getCode(),
MessageCode.TARIFF_IS_DELETED.getDescription(),
tariffResponse);
}
private BaseResponse handleUpdateTariff(BaseRequest request, TariffRequest tariffRequest) {
TariffDTO tariff = tariffService.getTariffById(tariffRequest.getId());
if (tariff == null) {
return new BaseResponse(request.getMsgId(), MessageCode.TARIFF_IS_NOT_FOUND.getCode(),
MessageCode.TARIFF_IS_NOT_FOUND.getDescription(),
Map.of("id", tariffRequest.getId()));
}
TariffDTO tariffDTO = tariffService.updateTariff(tariffRequest.getId(), tariff);
TariffResponse tariffResponse = TariffResponse.builder()
.tariffName(tariffDTO.getName())
.tariffType(tariffDTO.getType())
.build();
return new BaseResponse(request.getMsgId(), MessageCode.TARIFF_IS_UPDATED.getCode(),
MessageCode.TARIFF_IS_UPDATED.getDescription(),
tariffResponse);
}
private BaseResponse handleGetTariffs(BaseRequest request, TariffRequest tariffRequest) {
AuthToken authToken = authTokenRepository.findByToken(tariffRequest.getUserToken()).orElseThrow();
User user = authToken.getUser();
List<TariffDTO> allTariffs = tariffService.getTariffByAccountType(user);
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
MessageCode.SUCCESS.getDescription(),
Map.of("tariffs", allTariffs));
}
}
@@ -0,0 +1,317 @@
package ru.soune.nocopy.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.dto.tarriff.TariffInfoRequest;
import ru.soune.nocopy.dto.tarriff.TariffInfoResponse;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffStatus;
import ru.soune.nocopy.entity.tarif.TariffType;
import ru.soune.nocopy.repository.TariffRepository;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@Slf4j
@RequiredArgsConstructor
public class TariffInfoHandler implements RequestHandler {
private final ObjectMapper objectMapper;
private final TariffInfoService tariffInfoService;
private final TariffRepository tariffRepository;
@Override
public BaseResponse handle(BaseRequest request) throws Exception {
try {
TariffInfoRequest tariffInfoRequest = objectMapper.convertValue(request.getMessageBody(), TariffInfoRequest.class);
String action = tariffInfoRequest.getAction();
switch (action) {
case "create":
return handleCreateTariffInfo(request, tariffInfoRequest);
case "create_by_type":
return handleCreateTariffInfoByType(request, tariffInfoRequest);
case "update":
return handleUpdateTariffInfo(request, tariffInfoRequest);
case "delete":
return handleDeleteTariffInfo(request, tariffInfoRequest);
case "get_by_id":
return handleGetTariffInfoById(request, tariffInfoRequest);
case "get_all":
return handleGetAllTariffInfos(request, tariffInfoRequest);
default:
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Unknown action: " + action,
null
);
}
} catch (Exception e) {
log.error("Error processing tariff info request", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Error processing request: " + e.getMessage(),
null
);
}
}
private BaseResponse handleCreateTariffInfo(BaseRequest request, TariffInfoRequest tariffInfoRequest) {
try {
Long tariffId = tariffInfoRequest.getTariffId();
Tariff tariff= tariffRepository.findById(tariffId).orElseThrow();
TariffInfoDTO tariffInfoDTO = new TariffInfoDTO(null,
tariffInfoRequest.getStatus() != null ? tariffInfoRequest.getStatus() : TariffStatus.ACTIVE.name(),
tariffInfoRequest.getStartTariff() != null ? tariffInfoRequest.getStartTariff() : LocalDateTime.now(),
tariffInfoRequest.getEndTariff() != null ? tariffInfoRequest.getEndTariff() : LocalDateTime.now().plusDays(30),
tariffInfoRequest.getTariffId(), tariff.getName(), tariff.getTokens(), tariff.getDiskSize(), 0L,
0, tariff.getMaxFilesCount());
TariffInfoDTO createdTariffInfo = tariffInfoService.createTariffInfo(tariffInfoDTO);
TariffInfoResponse response = TariffInfoResponse.builder()
.id(createdTariffInfo.getId())
.status(createdTariffInfo.getStatus())
.startTariff(createdTariffInfo.getStartTariff())
.endTariff(createdTariffInfo.getEndTariff())
.tariffId(createdTariffInfo.getTariffId())
.tariffName(createdTariffInfo.getTariffName())
.build();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Tariff info created successfully",
response
);
} catch (Exception e) {
log.error("Error creating tariff info", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Failed to create tariff info: " + e.getMessage(),
null
);
}
}
private BaseResponse handleCreateTariffInfoByType(BaseRequest request, TariffInfoRequest tariffInfoRequest) {
try {
if (tariffInfoRequest.getTariffType() == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Tariff type is required for create_by_type action",
null
);
}
tariffInfoService.createTariffInfo(
TariffType.valueOf(tariffInfoRequest.getTariffType().toUpperCase())
);
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Tariff info created successfully by type: " + tariffInfoRequest.getTariffType(),
null
);
} catch (IllegalArgumentException e) {
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Invalid tariff type: " + tariffInfoRequest.getTariffType(),
null
);
} catch (Exception e) {
log.error("Error creating tariff info by type", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Failed to create tariff info by type: " + e.getMessage(),
null
);
}
}
private BaseResponse handleUpdateTariffInfo(BaseRequest request, TariffInfoRequest tariffInfoRequest) {
try {
if (tariffInfoRequest.getId() == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Tariff info ID is required for update",
null
);
}
Long tariffId = tariffInfoRequest.getTariffId();
Tariff tariff = tariffRepository.findById(tariffId).orElseThrow();
TariffInfoDTO tariffInfoDTO = new TariffInfoDTO(
tariffInfoRequest.getId(),
tariffInfoRequest.getStatus(),
tariffInfoRequest.getStartTariff(),
tariffInfoRequest.getEndTariff(),
tariffInfoRequest.getTariffId(),
tariff.getName(), null, tariff.getDiskSize(), null, null, tariff.getMaxFilesCount()
);
TariffInfoDTO updatedTariffInfo = tariffInfoService.updateTariffInfo(
tariffInfoRequest.getId(),
tariffInfoDTO
);
TariffInfoResponse response = TariffInfoResponse.builder()
.id(updatedTariffInfo.getId())
.status(updatedTariffInfo.getStatus())
.startTariff(updatedTariffInfo.getStartTariff())
.endTariff(updatedTariffInfo.getEndTariff())
.tariffId(updatedTariffInfo.getTariffId())
.tariffName(updatedTariffInfo.getTariffName())
.build();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Tariff info updated successfully",
response
);
} catch (Exception e) {
log.error("Error updating tariff info", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Failed to update tariff info: " + e.getMessage(),
null
);
}
}
private BaseResponse handleDeleteTariffInfo(BaseRequest request, TariffInfoRequest tariffInfoRequest) {
try {
if (tariffInfoRequest.getId() == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Tariff info ID is required for delete",
null
);
}
TariffInfoDTO tariffInfo = tariffInfoService.getTariffInfoById(tariffInfoRequest.getId());
tariffInfoService.deleteTariffInfo(tariffInfoRequest.getId());
TariffInfoResponse response = TariffInfoResponse.builder()
.id(tariffInfo.getId())
.status(tariffInfo.getStatus())
.startTariff(tariffInfo.getStartTariff())
.endTariff(tariffInfo.getEndTariff())
.tariffId(tariffInfo.getTariffId())
.tariffName(tariffInfo.getTariffName())
.build();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Tariff info deleted successfully",
response
);
} catch (Exception e) {
log.error("Error deleting tariff info", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Failed to delete tariff info: " + e.getMessage(),
null
);
}
}
private BaseResponse handleGetTariffInfoById(BaseRequest request, TariffInfoRequest tariffInfoRequest) {
try {
if (tariffInfoRequest.getId() == null) {
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Tariff info ID is required",
null
);
}
TariffInfoDTO tariffInfo = tariffInfoService.getTariffInfoById(tariffInfoRequest.getId());
TariffInfoResponse response = TariffInfoResponse.builder()
.id(tariffInfo.getId())
.status(tariffInfo.getStatus())
.startTariff(tariffInfo.getStartTariff())
.endTariff(tariffInfo.getEndTariff())
.tariffId(tariffInfo.getTariffId())
.tariffName(tariffInfo.getTariffName())
.build();
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Tariff info retrieved successfully",
response
);
} catch (Exception e) {
log.error("Error getting tariff info by ID", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Failed to get tariff info: " + e.getMessage(),
null
);
}
}
private BaseResponse handleGetAllTariffInfos(BaseRequest request, TariffInfoRequest tariffInfoRequest) {
try {
List<TariffInfoDTO> allTariffInfos = tariffInfoService.getAllTariffInfos();
List<TariffInfoResponse> responses = allTariffInfos.stream()
.map(info -> TariffInfoResponse.builder()
.id(info.getId())
.status(info.getStatus())
.startTariff(info.getStartTariff())
.endTariff(info.getEndTariff())
.tariffId(info.getTariffId())
.tariffName(info.getTariffName())
.build())
.collect(Collectors.toList());
return new BaseResponse(
request.getMsgId(),
MessageCode.SUCCESS.getCode(),
"Tariff infos retrieved successfully",
Map.of("tariff_infos", responses, "count", responses.size())
);
} catch (Exception e) {
log.error("Error getting all tariff infos", e);
return new BaseResponse(
request.getMsgId(),
MessageCode.ERROR_TARIFF_INFO.getCode(),
"Failed to get tariff infos: " + e.getMessage(),
null
);
}
}
}
@@ -55,7 +55,7 @@ public class RegRequestValidator implements Validator {
}
}
private void validateCompanyName(String companyName, Errors errors) {
public void validateCompanyName(String companyName, Errors errors) {
if (companyName == null || companyName.trim().isEmpty()) {
return;
}
@@ -73,7 +73,7 @@ public class RegRequestValidator implements Validator {
}
}
private void validateEmail(String email, Errors errors) {
public void validateEmail(String email, Errors errors) {
if (email == null || email.trim().isEmpty()) {
errors.rejectValue("email", "email.is.empty", "Email must not be empty");
return;
@@ -94,7 +94,7 @@ public class RegRequestValidator implements Validator {
}
private void validatePhone(String phone, Errors errors) {
public void validatePhone(String phone, Errors errors) {
if (phone == null || phone.trim().isEmpty()) {
errors.rejectValue("phone", "phone.empty",
"Test phone numbers are not allowed");
@@ -0,0 +1,23 @@
package ru.soune.nocopy.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.company.Company;
import java.util.List;
import java.util.Optional;
@Repository
public interface CompanyRepository extends JpaRepository<Company, String> {
Optional<Company> findByCompanyName(String companyName);
boolean existsByCompanyName(String companyName);
List<Company> findByCompanyNameContainingIgnoreCase(String name);
Page<Company> findAll(Pageable pageable);
Page<Company> findByCompanyNameContainingIgnoreCase(String name, Pageable pageable);
}
@@ -15,6 +15,8 @@ import java.util.Optional;
public interface FileEntityRepository extends JpaRepository<FileEntity, String> {
List<FileEntity> findByUserId(Long userId);
FileEntity findByIdAndUserId(String id, Long userId);
Optional<FileEntity> findByUserIdAndChecksum(Long userId, String imageHash);
List<FileEntity> findByUserIdAndStatus(Long userId, FileStatus status);
@@ -49,5 +51,5 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
@Query("SELECT f.id FROM FileEntity f WHERE f.filePath = :filePath")
String findFileIdByFilePath(@Param("filePath") String filePath);
long countByUserId(Long userId);
List<FileEntity> findFileByUserIdAndStatus(Long userId, FileStatus status);
}
@@ -17,6 +17,9 @@ public interface ImageSimilarityRepository
f.original_file_name AS originalFileName,
f.file_size AS fileSize,
f.user_id AS userId,
f.support_id AS supportId,
f.created_at AS uploadDate,
f.protection_status AS protectionStatus,
h.hash64_hi AS hash64Hi,
h.hash64_lo AS hash64Lo
FROM image_hashes ref
@@ -37,6 +40,9 @@ public interface ImageSimilarityRepository
f.original_file_name AS originalFileName,
f.file_size AS fileSize,
f.user_id AS userId,
f.support_id AS supportId,
f.created_at AS uploadDate,
f.protection_status AS protectionStatus,
h.hash64_hi AS hash64Hi,
h.hash64_lo AS hash64Lo
FROM image_hashes ref
@@ -61,7 +67,10 @@ public interface ImageSimilarityRepository
f.user_id AS userId,
f.original_file_name AS originalFileName,
f.file_size AS fileSize,
f.stored_file_name AS similarFileId
f.stored_file_name AS similarFileId,
f.support_id AS supportId,
f.created_at AS uploadDate,
f.protection_status AS protectionStatus
FROM image_hashes h
JOIN file_entities f ON f.id = h.file_id
WHERE h.hash64_hi = :hash64Hi
@@ -0,0 +1,21 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Repository
public interface ProtectedFileCheckRepository extends JpaRepository<ProtectedFileCheck, Long> {
Optional<ProtectedFileCheck> findByUser_Id(Long userId);
ProtectedFileCheck findByUserId(Long userId);
boolean existsByUser_Id(Long userId);
List<ProtectedFileCheck> findByPeriodStartDateBefore(LocalDate thirtyDaysAgo);
}
@@ -0,0 +1,43 @@
package ru.soune.nocopy.repository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.referral.Referral;
import java.util.List;
@Repository
public interface ReferralJpaRepository extends JpaRepository<Referral, Long> {
Referral findByReferralLink(String link);
Referral findByUserId(Long userId);
List<Referral> findByInviterId(Long inviterId);
@Query("SELECT r FROM Referral r WHERE r.inviterId = :inviterId AND r.isActive = true")
List<Referral> findByInviterIdWhenUserActivated(Long inviterId, Pageable pageable);
@Query("SELECT r FROM Referral r WHERE r.inviterId = :inviterId")
List<Referral> findByInviterId(Long inviterId, Pageable pageable);
@Modifying
@Query("UPDATE Referral r SET r.totalIncome = r.totalIncome + :amount WHERE r.userId = :userId")
void increaseIncome(@Param("userId") Long userId, @Param("amount") int amount);
@Modifying
@Query("UPDATE Referral r SET r.levelId = :newLevelId WHERE r.userId = :userId")
void updateLevel(@Param("userId") Long userId, @Param("newLevelId") String newLevelId);
@Modifying
@Query("UPDATE Referral r SET r.isActive = true WHERE r.userId = :userId")
void activateUser(@Param("userId") Long userId);
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId AND r.isActive = true")
int countActiveInvitees(@Param("userId") Long userId);
@Query("SELECT COUNT(r) FROM Referral r WHERE r.inviterId = :userId")
int countTotalInvitees(@Param("userId") Long userId);
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.referral.ReferralLevelEntity;
import java.util.List;
@Repository
public interface ReferralLevelRepository extends JpaRepository<ReferralLevelEntity, Long> {
List<ReferralLevelEntity> findAllByOrderByMinInviteesAsc();
ReferralLevelEntity findById(String id);
}
@@ -1,5 +1,9 @@
package ru.soune.nocopy.repository;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import java.time.LocalDateTime;
public interface SimilarImageProjection {
Long getHash64Hi();
Long getHash64Lo();
@@ -7,4 +11,7 @@ public interface SimilarImageProjection {
Long getUserId();
String getOriginalFileName();
Long getFileSize();
Long getSupportId();
LocalDateTime getUploadDate();
ProtectionStatus getProtectionStatus();
}
@@ -0,0 +1,9 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tarif.TariffInfo;
@Repository
public interface TariffInfoRepository extends JpaRepository<TariffInfo, String> {
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffType;
import java.util.List;
import java.util.Optional;
@Repository
public interface TariffRepository extends JpaRepository<Tariff, Long> {
Optional<Tariff> findByType(String type);
List<Tariff> findAllByOrderByPriceAsc();
List<Tariff> findByTariffAccountType(String typeAccount);
Tariff findByName(String name);
}
@@ -4,10 +4,12 @@ import jakarta.validation.constraints.Size;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.soune.nocopy.entity.user.User;
import java.util.Optional;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
boolean existsByEmail(String email);
boolean existsByPhone(@Size(min = 11, max = 14) String phone);
List<User> findByCompanyId(String companyId);
long countByCompanyId(String companyId);
}

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