403 Commits
Author SHA1 Message Date
vladp b6656961de dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-20 15:55:58 +07:00
vladp 4ff5c5ec13 dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 15:26:20 +07:00
vladp 037445dcd5 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-20 12:59:03 +07:00
vladp ff9c959600 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-19 18:11:56 +07:00
vladp 795b255613 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-19 18:03:44 +07:00
vladp c5f060d269 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-19 14:48:07 +07:00
vladp 1cdc6ee1c9 dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-19 13:43:58 +07:00
vladp 0dd8400229 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-19 13:40:34 +07:00
vladp 741ca5d5a2 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-18 19:05:07 +07:00
vladp f7c2a186cf dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-18 18:21:33 +07:00
vladp c7660c30de dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-18 17:55:14 +07:00
vladp 02b8d3ee6d dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-18 17:07:44 +07:00
vladp 9024d3aaca dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-18 16:26:47 +07:00
vladp 9e5ff7ce97 dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-18 16:08:37 +07:00
vladp 9f3430adf2 dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-18 15:27:43 +07:00
vladp 41bc26ee94 dev add violation notion
Test Workflow / test (push) Successful in 5s
2026-03-17 22:48:27 +07:00
vladp 7b58e4979d dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-17 22:40:06 +07:00
vladp 921d439e0a dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-17 22:23:19 +07:00
vladp 2d8f904bfd dev add violation notion
Test Workflow / test (push) Successful in 4s
2026-03-17 16:52:35 +07:00
vladp f1e0f9594e dev add violation notion
Test Workflow / test (push) Successful in 9s
2026-03-17 16:35:27 +07:00
vladp 15d1824757 dev comment all google search
Test Workflow / test (push) Successful in 5s
2026-03-17 14:50:43 +07:00
vladp e66225cdd1 dev comment all google search
Test Workflow / test (push) Successful in 4s
2026-03-17 12:32:48 +07:00
vladp 95edcdf527 dev comment all yandex search
Test Workflow / test (push) Successful in 5s
2026-03-17 12:20:19 +07:00
vladp 28bc24647a dev comment all yandex search
Test Workflow / test (push) Successful in 4s
2026-03-17 11:28:25 +07:00
vladp 566c1634a2 dev comment all yandex search
Test Workflow / test (push) Successful in 4s
2026-03-17 11:21:16 +07:00
vladp 59125b4d19 dev comment all yandex search
Test Workflow / test (push) Successful in 4s
2026-03-17 11:08:33 +07:00
vladp 18c07b427c dev comment all yandex search
Test Workflow / test (push) Successful in 4s
2026-03-16 15:46:19 +07:00
vladp a1669826b5 dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 15:35:08 +07:00
vladp 7b3000152f dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 15:18:30 +07:00
vladp 0f7079d118 dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 15:03:02 +07:00
vladp 3a478f9195 dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 14:48:39 +07:00
vladp 73fd5e9456 dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 14:40:34 +07:00
vladp c56b2ba4e9 dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 14:28:17 +07:00
vladp 9186a6d9b0 dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 14:18:56 +07:00
vladp 68e102b6ce dev add check count for all type
Test Workflow / test (push) Successful in 4s
2026-03-16 14:12:34 +07:00
vladp 168aa3812c dev add vio
Test Workflow / test (push) Successful in 4s
2026-03-16 13:30:24 +07:00
vladp f291287b4c dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-14 21:28:24 +07:00
vladp aa665acbad dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-14 21:16:29 +07:00
vladp b5b71ddfe6 dev add vio
Test Workflow / test (push) Successful in 4s
2026-03-14 20:57:39 +07:00
vladp f4675eb739 dev add vio
Test Workflow / test (push) Successful in 6s
2026-03-14 20:39:09 +07:00
vladp e2d4312e0f dev add vio
Test Workflow / test (push) Successful in 2s
2026-03-13 14:14:37 +07:00
vladp 83f29e3382 dev add vio
Test Workflow / test (push) Successful in 2s
2026-03-13 14:06:54 +07:00
vladp bffdf17717 dev add vio
Test Workflow / test (push) Successful in 2s
2026-03-13 13:55:10 +07:00
vladp 507947d6a6 dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-13 13:48:03 +07:00
vladp 7af4b6c781 dev add vio
Test Workflow / test (push) Successful in 2s
2026-03-12 17:31:40 +07:00
vladp 835a9bf18d dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-12 17:27:22 +07:00
vladp 3a477bc2ae dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-12 15:46:37 +07:00
vladp 2ca73e4417 dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-12 02:52:38 +07:00
vladp 37b1f370e5 dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-12 01:45:57 +07:00
vladp 65bbb00abf dev add vio
Test Workflow / test (push) Successful in 14s
2026-03-12 01:32:11 +07:00
vladp 869a25043a dev add vio
Test Workflow / test (push) Successful in 5s
2026-03-12 00:41:04 +07:00
vladp ffa977f06e dev add vio
Test Workflow / test (push) Successful in 3s
2026-03-12 00:21:19 +07:00
vladp 88bfe51297 dev delete
Test Workflow / test (push) Successful in 2s
2026-03-11 16:30:26 +07:00
vladp 087b9f7b33 dev delete
Test Workflow / test (push) Successful in 2s
2026-03-11 16:20:56 +07:00
vladp a39363a417 dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-11 16:16:29 +07:00
vladp edbbeab9be dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-11 16:02:47 +07:00
vladp 5b0c6ce5bd dev add config for nginx
Test Workflow / test (push) Successful in 2s
2026-03-11 15:49:39 +07:00
vladp b04a357cca dev add config for nginx
Test Workflow / test (push) Successful in 2s
2026-03-11 15:42:06 +07:00
vladp 7fb96d4b51 dev add config for nginx
Test Workflow / test (push) Successful in 2s
2026-03-11 15:29:10 +07:00
vladp 54db3d78b4 dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-11 15:17:35 +07:00
vladp b5ca777722 dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-11 15:02:34 +07:00
vladp 832b13e088 dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-11 13:15:21 +07:00
vladp 9d61f8c88f dev add config for nginx
Test Workflow / test (push) Successful in 2s
2026-03-11 13:04:56 +07:00
vladp 1b54badd00 dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-11 12:48:54 +07:00
vladp c591ab40df dev add config for nginx
Test Workflow / test (push) Successful in 2s
2026-03-10 22:47:43 +07:00
vladp 590d42690b dev add config for nginx
Test Workflow / test (push) Successful in 3s
2026-03-10 21:19:27 +07:00
vladp b65a39b672 dev temp set urk
Test Workflow / test (push) Successful in 3s
2026-03-10 12:26:43 +07:00
vladp c87b5af628 dev temp set urk
Test Workflow / test (push) Successful in 3s
2026-03-09 12:12:06 +07:00
vladp 2f57a54137 dev temp set urk
Test Workflow / test (push) Successful in 4s
2026-03-07 02:43:35 +07:00
vladp edec60bf19 dev temp set urk
Test Workflow / test (push) Successful in 3s
2026-03-07 02:35:01 +07:00
vladp 5c8610397a dev temp set urk
Test Workflow / test (push) Successful in 3s
2026-03-07 02:25:43 +07:00
vladp 94061381c7 dev temp set urk
Test Workflow / test (push) Successful in 4s
2026-03-07 02:18:56 +07:00
vladp 60857d4077 dev temp set urk
Test Workflow / test (push) Successful in 4s
2026-03-07 01:54:33 +07:00
vladp 8b7f580b0e dev async global search
Test Workflow / test (push) Successful in 4s
2026-03-07 01:36:31 +07:00
vladp a46dd54c66 dev async global search
Test Workflow / test (push) Successful in 4s
2026-03-07 01:22:36 +07:00
vladp cd469edb7f dev async global search
Test Workflow / test (push) Successful in 3s
2026-03-07 01:11:22 +07:00
vladp 4e9a3e9008 dev async global search
Test Workflow / test (push) Successful in 3s
2026-03-07 00:59:17 +07:00
vladp 2bf0468f61 dev async global search
Test Workflow / test (push) Successful in 3s
2026-03-07 00:47:43 +07:00
vladp 1670026430 dev async global search
Test Workflow / test (push) Successful in 3s
2026-03-07 00:33:37 +07:00
vladp 66fe68342a dev async global search
Test Workflow / test (push) Successful in 6s
2026-03-07 00:24:31 +07:00
vladp 7f601fcfb3 dev async global search
Test Workflow / test (push) Successful in 6s
2026-03-06 23:52:06 +07:00
vladp cfaf8fcf32 dev async global search
Test Workflow / test (push) Successful in 4s
2026-03-06 22:42:09 +07:00
vladp 31c5d9aaab dev async global search
Test Workflow / test (push) Successful in 4s
2026-03-06 17:28:52 +07:00
vladp 52bc0e254c dev add check tokens for monitoring
Test Workflow / test (push) Successful in 5s
2026-03-06 12:48:15 +07:00
vladp 4c3558dcfa dev add check tokens for monitoring
Test Workflow / test (push) Waiting to run
2026-03-06 12:15:39 +07:00
vladp 120408e488 dev add check tokens for monitoring
Test Workflow / test (push) Waiting to run
2026-03-06 12:03:07 +07:00
vladp 3ed38dacd1 dev add check tokens for monitoring
Test Workflow / test (push) Successful in 4s
2026-03-05 14:27:14 +07:00
vladp 88e7121213 dev add check tokens for monitoring
Test Workflow / test (push) Successful in 3s
2026-03-05 12:11:09 +07:00
vladp 5c27fd52ba dev add check for file check
Test Workflow / test (push) Successful in 3s
2026-03-05 10:26:53 +07:00
vladp c9921c6fac dev add check for file check
Test Workflow / test (push) Successful in 4s
2026-03-05 10:05:18 +07:00
vladp 89478c1c9a dev fix pipline
Test Workflow / test (push) Waiting to run
2026-03-05 09:37:35 +07:00
vladp 34256c072e dev fix pipline
Test Workflow / test (push) Successful in 2s
2026-03-04 21:16:21 +07:00
vladp a89c29f763 dev add monitoring basic
Test Workflow / test (push) Successful in 2s
2026-03-04 12:20:19 +07:00
vladp f5fee52cb4 dev add monitoring basic
Test Workflow / test (push) Successful in 3s
2026-03-04 12:16:53 +07:00
vladp b8a0e6cce7 dev add monitoring basic
Test Workflow / test (push) Successful in 3s
2026-03-04 12:10:27 +07:00
vladp c7ded41c16 dev add monitoring basic
Test Workflow / test (push) Successful in 3s
2026-03-03 17:51:51 +07:00
vladp 38e7a382c7 dev add monitoring basic
Test Workflow / test (push) Successful in 2s
2026-03-03 15:43:47 +07:00
vladp 404e1a6bee dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-03-03 11:38:22 +07:00
vladp 1124cddb5a dev add monitoring basic
Test Workflow / test (push) Successful in 9s
2026-03-02 16:15:32 +07:00
vladp b0804938bd dev add monitoring basic
Test Workflow / test (push) Successful in 3s
2026-03-02 15:57:58 +07:00
vladp 597b46ccff dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-03-02 15:52:17 +07:00
vladp 2966d9f3a9 dev add monitoring basic
Test Workflow / test (push) Successful in 9s
2026-03-02 15:44:46 +07:00
vladp fa7f5a731e dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-03-02 14:12:40 +07:00
vladp 077fe0c254 dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-03-02 14:05:27 +07:00
vladp ce7906d2a8 dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-03-02 14:00:10 +07:00
vladp 8bcbc44528 dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-03-02 13:55:45 +07:00
vladp 24228609bb dev add monitoring basic
Test Workflow / test (push) Successful in 5s
2026-03-02 13:40:07 +07:00
vladp c86290c2cb dev add monitoring basic
Test Workflow / test (push) Has been cancelled
2026-02-28 11:25:41 +07:00
vladp c09a3a3a18 dev add monitoring basic
Test Workflow / test (push) Successful in 4s
2026-02-27 17:57:55 +07:00
vladp 76aa04e174 dev conver files
Test Workflow / test (push) Successful in 9s
2026-02-27 17:55:11 +07:00
vladp ff441a6ea4 dev conver files
Test Workflow / test (push) Successful in 5s
2026-02-27 01:52:56 +07:00
vladp f3a0ab8181 dev conver files
Test Workflow / test (push) Waiting to run
2026-02-27 01:47:50 +07:00
vladp a2c680d10a dev conver files
Test Workflow / test (push) Waiting to run
2026-02-27 01:18:41 +07:00
vladp b8003c11e2 dev conver files
Test Workflow / test (push) Successful in 5s
2026-02-26 00:49:13 +07:00
vladp 4f2d97d205 dev conver files
Test Workflow / test (push) Successful in 3s
2026-02-26 00:42:47 +07:00
vladp 2b5f17f170 dev conver files
Test Workflow / test (push) Successful in 4s
2026-02-26 00:34:26 +07:00
vladp 983b5eddda dev conver files
Test Workflow / test (push) Successful in 4s
2026-02-26 00:27:31 +07:00
vladp 45010bb79a dev conver files
Test Workflow / test (push) Successful in 4s
2026-02-25 20:29:58 +07:00
vladp 3cf3037120 dev conver files
Test Workflow / test (push) Successful in 3s
2026-02-25 20:26:22 +07:00
vladp 3bb6667c2f dev conver files
Test Workflow / test (push) Successful in 4s
2026-02-25 19:29:19 +07:00
vladp 694681fccc dev use authtoken
Test Workflow / test (push) Successful in 4s
2026-02-25 13:06:27 +07:00
vladp cb46ac6c88 dev use authtoken
Test Workflow / test (push) Successful in 4s
2026-02-25 12:58:39 +07:00
vladp bfe2367715 dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-25 11:31:08 +07:00
vladp 0185e7cd54 dev use authtoken
Test Workflow / test (push) Successful in 6s
2026-02-24 20:27:58 +07:00
vladp 87b97c39ea dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-24 18:35:35 +07:00
vladp ba6464147a dev use authtoken
Test Workflow / test (push) Successful in 7s
2026-02-24 16:53:33 +07:00
vladp 51d1d78f40 dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-24 15:02:01 +07:00
vladp 28ce99ec1c dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-24 14:56:41 +07:00
vladp 274861c300 dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-24 14:24:42 +07:00
vladp c5edc9a5fc dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-24 14:19:15 +07:00
vladp 81342ae05e dev use authtoken
Test Workflow / test (push) Successful in 5s
2026-02-24 12:57:48 +07:00
vladp c81a8c8d65 dev use authtoken
Test Workflow / test (push) Successful in 7s
2026-02-24 11:41:02 +07:00
vladp 5460d91a6f dev use authtoken
Test Workflow / test (push) Successful in 6s
2026-02-24 11:27:56 +07:00
vladp abe98fb609 dev add another client for google and yandex
Test Workflow / test (push) Has been cancelled
2026-02-22 02:54:39 +07:00
vladp 69be949f22 dev add another client for google and yandex
Test Workflow / test (push) Waiting to run
2026-02-22 02:49:21 +07:00
vladp b347958772 dev add another client for google and yandex
Test Workflow / test (push) Waiting to run
2026-02-22 02:40:28 +07:00
vladp a5c8d09fc4 dev add another client for google and yandex
Test Workflow / test (push) Waiting to run
2026-02-22 02:18:23 +07:00
vladp ab495eada7 dev add another client for google and yandex
Test Workflow / test (push) Waiting to run
2026-02-22 01:48:49 +07:00
vladp 47f3cda7b0 dev add another client for google and yandex
Test Workflow / test (push) Waiting to run
2026-02-22 01:21:09 +07:00
vladp 247e97cd7a dev add another client for google and yandex
Test Workflow / test (push) Waiting to run
2026-02-22 01:03:22 +07:00
vladp 7ae1c817ad dev comment google search
Test Workflow / test (push) Waiting to run
2026-02-22 00:42:13 +07:00
vladp 5d02e2e0f2 dev comment google search
Test Workflow / test (push) Waiting to run
2026-02-22 00:25:05 +07:00
vladp f926de161d dev comment google search
Test Workflow / test (push) Waiting to run
2026-02-22 00:08:05 +07:00
vladp 3e39a3bf47 dev comment google search
Test Workflow / test (push) Waiting to run
2026-02-21 23:58:24 +07:00
vladp 76b07db964 dev add retry
Test Workflow / test (push) Waiting to run
2026-02-21 23:45:54 +07:00
vladp c289032671 dev comment logic for check max users
Test Workflow / test (push) Waiting to run
2026-02-21 23:21:44 +07:00
vladp f103aca8da dev comment logic for check max users
Test Workflow / test (push) Waiting to run
2026-02-21 22:41:48 +07:00
vladp 7631ea8c8c dev comment logic for check max users
Test Workflow / test (push) Successful in 3s
2026-02-21 21:41:05 +07:00
vladp f932bb47f0 dev comment logic for check max users
Test Workflow / test (push) Successful in 4s
2026-02-21 21:35:13 +07:00
vladp ca10d4d130 dev comment logic for check max users
Test Workflow / test (push) Successful in 3s
2026-02-21 21:01:23 +07:00
vladp 339a3f61cd dev comment logic for check max users
Test Workflow / test (push) Successful in 3s
2026-02-21 16:16:58 +07:00
vladp a42e3c72fc dev comment logic for check max users
Test Workflow / test (push) Successful in 5s
2026-02-20 21:53:49 +07:00
vladp 5551d84bf7 dev comment logic for check max users
Test Workflow / test (push) Successful in 4s
2026-02-20 20:51:14 +07:00
vladp 86e0e0619f dev comment logic for check max users
Test Workflow / test (push) Successful in 4s
2026-02-20 13:06:53 +07:00
vladp f34e9df0c8 dev set timeout 30 sec
Test Workflow / test (push) Successful in 9s
2026-02-20 11:19:54 +07:00
vladp fbe80ff6d7 dev set timeout 30 sec
Test Workflow / test (push) Successful in 4s
2026-02-20 11:11:24 +07:00
vladp 0767088fc4 dev set timeout 30 sec
Test Workflow / test (push) Waiting to run
2026-02-20 10:50:44 +07:00
vladp db4cf41800 dev set timeout 30 sec
Test Workflow / test (push) Successful in 3s
2026-02-19 17:28:01 +07:00
vladp 34231d9f6f dev set timeout 30 sec
Test Workflow / test (push) Successful in 3s
2026-02-19 16:06:49 +07:00
vladp e5da30400a dev check timeout for google
Test Workflow / test (push) Successful in 3s
2026-02-19 16:01:10 +07:00
vladp 65bd103c74 dev set only yandex search
Test Workflow / test (push) Successful in 9s
2026-02-19 15:36:22 +07:00
vladp d4f26db9ce dev add api for payment
Test Workflow / test (push) Successful in 3s
2026-02-19 15:06:48 +07:00
vladp e9cdc66f72 dev add api for payment
Test Workflow / test (push) Successful in 3s
2026-02-19 14:15:59 +07:00
vladp 9112854fea dev add api for payment
Test Workflow / test (push) Successful in 4s
2026-02-19 11:27:01 +07:00
vladp 4ccf39f811 dev add api for payment
Test Workflow / test (push) Successful in 4s
2026-02-19 10:53:03 +07:00
vladp 27df74891b dev add api for payment
Test Workflow / test (push) Successful in 4s
2026-02-19 10:41:45 +07:00
vladp 22a44d833e dev add api for payment
Test Workflow / test (push) Successful in 11s
2026-02-18 19:00:44 +07:00
vladp e2f22bb27b dev add api for payment
Test Workflow / test (push) Successful in 2s
2026-02-18 18:42:55 +07:00
vladp d7e3afb4dc dev add api for payment
Test Workflow / test (push) Successful in 3s
2026-02-18 18:26:11 +07:00
vladp 3d5856d302 dev add reset password
Test Workflow / test (push) Successful in 3s
2026-02-18 16:14:17 +07:00
vladp 8811f3ce0e dev add reset password
Test Workflow / test (push) Successful in 4s
2026-02-18 11:30:15 +07:00
vladp ac11de2abb dev add reset password
Test Workflow / test (push) Successful in 10s
2026-02-17 20:17:34 +07:00
vladp 9f67f0c06d dev add check verified email
Test Workflow / test (push) Waiting to run
2026-02-17 18:48:34 +07:00
vladp c54dfc994b dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 18:11:32 +07:00
vladp a4e3d62f14 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 17:07:28 +07:00
vladp ec93afa705 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 16:15:55 +07:00
vladp 18de1ab91b dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 16:01:47 +07:00
vladp 67690db7c0 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 15:57:01 +07:00
vladp b982cb96c1 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 15:52:55 +07:00
vladp 74ba9709a9 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 15:39:26 +07:00
vladp 8de13b410b dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 14:42:58 +07:00
vladp 77bf7ea886 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 14:27:16 +07:00
vladp 78698d2873 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:57:38 +07:00
vladp 8147a6ce49 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:28:04 +07:00
vladp db02f23c67 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:24:11 +07:00
vladp 633f76cd73 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:20:37 +07:00
vladp 6abfb4aa37 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:15:35 +07:00
vladp a132ac97f2 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:09:56 +07:00
vladp 31b41dc3d7 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:03:47 +07:00
vladp 73d010b426 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 13:00:32 +07:00
vladp fba974f91b dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 12:55:25 +07:00
vladp 27b0aaaf70 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 12:46:54 +07:00
vladp 60f78599fa dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 11:11:51 +07:00
vladp e416835c51 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-17 10:54:51 +07:00
vladp 01b0c12eca dev delete config
Test Workflow / test (push) Successful in 4s
2026-02-16 20:44:14 +07:00
vladp 45267158d8 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 20:31:51 +07:00
vladp fe710c824d dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 20:17:57 +07:00
vladp 919baa418e dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 20:16:50 +07:00
vladp 5f588fcb30 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 20:11:06 +07:00
vladp a431784baa dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 19:51:38 +07:00
vladp ba802ca53f dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 19:43:12 +07:00
vladp d4f9397dff dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 19:23:22 +07:00
vladp 796f1de022 dev delete config
Test Workflow / test (push) Waiting to run
2026-02-16 19:08:49 +07:00
vladp e05bf7ef3a dev delete config
Test Workflow / test (push) Successful in 3s
2026-02-16 18:57:45 +07:00
vladp 3c00a5bd2c dev delete config
Test Workflow / test (push) Successful in 4s
2026-02-16 18:35:27 +07:00
vladp 1d3bdc506d dev delete config
Test Workflow / test (push) Successful in 9s
2026-02-16 18:16:21 +07:00
vladp 1801b7e8fd dev add check hash for all types,exclude video
Test Workflow / test (push) Successful in 4s
2026-02-16 16:51:12 +07:00
vladp e53c17976f dev add check hash for all types,exclude video
Test Workflow / test (push) Successful in 3s
2026-02-16 16:44:16 +07:00
vladp 31544084ca dev add check hash for all types,exclude video
Test Workflow / test (push) Successful in 4s
2026-02-16 16:41:29 +07:00
vladp d74571b7cb dev add check hash for all types,exclude video
Test Workflow / test (push) Successful in 4s
2026-02-16 16:15:15 +07:00
vladp 27705c5739 dev add check hash for all types,exclude video
Test Workflow / test (push) Successful in 3s
2026-02-16 16:09:00 +07:00
vladp 7a02b94bd0 dev add code respone for download files
Test Workflow / test (push) Successful in 4s
2026-02-16 10:43:20 +07:00
vladp f15a24acc2 dev add logs for search
Test Workflow / test (push) Successful in 4s
2026-02-16 10:33:13 +07:00
vladp e555436d54 dev add logs for search
Test Workflow / test (push) Successful in 4s
2026-02-16 10:20:36 +07:00
vladp 814d137937 dev add logs for search
Test Workflow / test (push) Successful in 3s
2026-02-16 10:13:42 +07:00
vladp 1e14fa3f23 dev add correct time for build
Test Workflow / test (push) Successful in 2s
2026-02-13 21:40:15 +07:00
vladp 12aecff5c9 dev add inn check for company
Test Workflow / test (push) Successful in 2s
2026-02-13 16:25:04 +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
vladp 5700adafb2 NCBACK-33
Test Workflow / test (push) Successful in 3s
2026-01-27 08:09:54 +07:00
vladp 816a984891 Merge branch 'dev' into NCBACK-33
# Conflicts:
#	src/main/java/ru/soune/nocopy/entity/file/ImageProtection.java
#	src/main/java/ru/soune/nocopy/entity/user/User.java
#	src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java
#	src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
2026-01-27 05:46:50 +07:00
vladp 21e31c7d0f dev add google search by image
Test Workflow / test (push) Successful in 3s
2026-01-27 05:41:47 +07:00
vladp e7c6df12d6 dev update file ext
Test Workflow / test (push) Successful in 3s
2026-01-26 22:33:49 +07:00
vladp aa87ae32bc dev update search methods,update find endpoints
Test Workflow / test (push) Successful in 3s
2026-01-26 21:03:45 +07:00
vladp 9ba3d8b973 dev fix pipline
Test Workflow / test (push) Successful in 4s
2026-01-26 21:00:22 +07:00
vladp 300383e523 dev
Test Workflow / test (push) Successful in 2s
2026-01-26 14:48:41 +07:00
vladp 329000d284 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:43:57 +07:00
vladp 4c8c4bb5c1 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:28:47 +07:00
vladp 82eedb02d1 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:20:00 +07:00
vladp b2993e00a1 dev
Test Workflow / test (push) Successful in 2s
2026-01-26 14:14:56 +07:00
vladp 6197b26a81 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:08:44 +07:00
vladp 0e2ef1d2e6 dev add indexes
Test Workflow / test (push) Successful in 3s
2026-01-26 14:02:10 +07:00
vladp 3e5eef3b42 dev add indexes
Test Workflow / test (push) Successful in 5s
2026-01-26 11:41:21 +07:00
vladp 89f83054ef NCBACK-25
Test Workflow / test (push) Successful in 3s
2026-01-25 22:05:19 +07:00
vladp f8234291b1 NCBACK-25 delete endpoint
Test Workflow / test (push) Successful in 3s
2026-01-25 21:38:56 +07:00
vladp 4cf36d7b17 NCBACK-25 change limits
Test Workflow / test (push) Successful in 3s
2026-01-25 21:10:17 +07:00
vladp 6dacbde522 NCBACK-25 fix check duplicate exception
Test Workflow / test (push) Successful in 3s
2026-01-25 20:45:29 +07:00
vladp 0a4ab562a4 NCBACK-25 fix check duplicate exception
Test Workflow / test (push) Successful in 3s
2026-01-25 20:08:01 +07:00
vladp 76103b9d96 NCBACK-25 fix check duplicate exception
Test Workflow / test (push) Successful in 3s
2026-01-25 19:59:16 +07:00
vladp 69a79b7ac7 NCBACK-25 merge to dev
Test Workflow / test (push) Successful in 4s
2026-01-25 18:59:55 +07:00
vladp c97f205a17 Merge branch 'NCBACK-25' into dev
# Conflicts:
#	src/main/java/ru/soune/nocopy/repository/SimilarImageProjection.java
#	src/main/java/ru/soune/nocopy/service/FileSimilarityService.java
2026-01-25 18:41:05 +07:00
vladp ab3eef1668 NCBACK-25 change path to file
Test Workflow / test (push) Successful in 3s
2026-01-25 12:10:56 +07:00
vladp 85449d73fd fix
Test Workflow / test (push) Successful in 5s
2026-01-24 12:23:05 +07:00
vladp 859239e77a fix
Test Workflow / test (push) Successful in 7s
2026-01-24 12:22:15 +07:00
vladp 63c3794aa4 fix
Test Workflow / test (push) Successful in 4s
2026-01-24 11:34:30 +07:00
vladp 349edbeb91 fix
Test Workflow / test (push) Successful in 5s
2026-01-24 11:32:02 +07:00
vladp e26e50454a Merge branch 'dev' into NCBACK-33
Test Workflow / test (push) Successful in 5s
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/dto/MessageCode.java
#	src/main/resources/application.yaml
2026-01-24 11:23:06 +07:00
vladp 8d026a485f dev
Test Workflow / test (push) Successful in 5s
2026-01-24 11:09:49 +07:00
vladp 204e13ca85 Merge branch 'NCBACK-34' into dev
Test Workflow / test (push) Successful in 4s
2026-01-24 11:07:20 +07:00
vladp 79b13731a6 NCBACK-34
Test Workflow / test (push) Successful in 4s
2026-01-24 10:59:02 +07:00
vladp 8555002415 NCBACK-33 fix check null user
Test Workflow / test (push) Successful in 1s
2026-01-24 00:45:57 +07:00
vladp 4560925183 NCBACK-25
Test Workflow / test (push) Successful in 3s
2026-01-23 16:09:51 +07:00
vladp 4314032a3b Merge remote-tracking branch 'origin/dev' into dev
Test Workflow / test (push) Successful in 3s
2026-01-23 14:58:09 +07:00
vladp d638819ee9 NCBACK-25
Test Workflow / test (push) Successful in 2s
2026-01-23 13:25:56 +07:00
vladp 51654fd060 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 2s
2026-01-23 13:22:02 +07:00
vladp e37cc06cd0 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 3s
2026-01-22 20:53:25 +07:00
vladp 5a3e26b6a3 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 4s
2026-01-22 20:52:45 +07:00
vladp 409c677ba6 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 3s
2026-01-22 19:55:09 +07:00
vladp 95deda12f3 Merge branch 'refs/heads/dev' into NCBACK-25
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/entity/file/FileEntity.java
2026-01-22 14:15:09 +07:00
milidev c2a780db2d referral module init commit
Test Workflow / test (push) Successful in 8s
2026-01-22 13:00:21 +07:00
vladp ad9cbd96f6 NCBACK-34 update dev
Test Workflow / test (push) Successful in 4s
2026-01-22 11:24:37 +07:00
vladp 211b99b986 Merge branch 'dev' into NCBACK-34
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
2026-01-22 11:23:58 +07:00
vladp d1b5b6eb5c add check user
Test Workflow / test (push) Successful in 3s
2026-01-21 12:34:40 +07:00
vladp 251bb37105 fix authService
Test Workflow / test (push) Successful in 4s
2026-01-21 12:21:14 +07:00
vladp 1fc065a46b Сохранение 2026-01-21 12:02:38 +07:00
vladp 60678ef889 Dev add support id
Test Workflow / test (push) Successful in 4s
2026-01-20 21:55:42 +07:00
backdev f0a87648cf Merge pull request 'NCBACK-27' (#12) from NCBACK-27 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #12
2026-01-20 16:20:38 +08:00
vladp c2ffcdcd6e Merge branch 'dev' into NCBACK-27
Test Workflow / test (push) Successful in 3s
# Conflicts:
#	src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java
2026-01-20 15:20:10 +07:00
vladp f9accd7af1 NCBACK-33 add verification logic
Test Workflow / test (push) Successful in 3s
2026-01-20 15:12:24 +07:00
vladp 55fa8b84e2 NCBACK-33 add email send
Test Workflow / test (push) Successful in 4s
2026-01-20 03:47:51 +07:00
vladp 4a8e561258 NCBACK-33 add email send
Test Workflow / test (push) Successful in 2s
2026-01-19 19:26:59 +07:00
vladp 0c0fadf5ec NCBACK-34 fix send exception
Test Workflow / test (push) Successful in 3s
2026-01-19 15:49:27 +07:00
vladp e979712b7c Merge branch 'dev' into NCBACK-34 2026-01-17 01:31:21 +07:00
vladp 5027920199 dev info for docker user
Test Workflow / test (push) Has been cancelled
2026-01-16 23:59:07 +07:00
vladp 4704f780e5 NCBACK-33 add start logic for verify by token
Test Workflow / test (push) Successful in 4s
2026-01-16 20:20:14 +07:00
vladp 3d679440f6 NCBACK-34 add logic for clear does not exist files,add dto for similar file search controller
Test Workflow / test (push) Successful in 4s
2026-01-16 14:30:45 +07:00
vladp 4a45f40e9f NCBACK-34 need fix delete filentity and hash
Test Workflow / test (push) Successful in 4s
2026-01-15 20:41:35 +07:00
vladp bbb0d7b17a Merge branch 'dev' into NCBACK-34 2026-01-15 16:55:47 +07:00
vladp 0ed3f958ff DEV_DB change db creds
Test Workflow / test (push) Successful in 7s
2026-01-15 16:52:33 +07:00
vladp de810e32b4 DEV_DB change db creds
Test Workflow / test (push) Successful in 4s
2026-01-15 16:42:37 +07:00
vladp 1a6823f7a9 DEV_DB change db creds
Test Workflow / test (push) Successful in 4s
2026-01-15 16:17:03 +07:00
vladp 82a57a2983 NCBACK-32 add all case for json
Test Workflow / test (push) Successful in 4s
2026-01-15 11:09:05 +07:00
vladp d3e50aa8d1 NCBACK-19 add all case for json
Test Workflow / test (push) Successful in 3s
2026-01-14 19:10:22 +07:00
vladp 5c6309e228 NCBACK-33 fix check company name
Test Workflow / test (push) Successful in 3s
2026-01-14 18:47:34 +07:00
vladp 8ac73ff045 NCBACK-34 add logic to find similar image in db
Test Workflow / test (push) Successful in 3s
2026-01-13 22:40:52 +07:00
vladp 20e1ab321d NCBACK-25 work version for protect file
Test Workflow / test (push) Successful in 3s
2026-01-13 13:28:09 +07:00
vladp 2b7a8b97e1 NCBACK-27 fix token life
Test Workflow / test (push) Successful in 3s
2026-01-10 02:09:39 +07:00
vladp 344ec075d4 Dev
Test Workflow / test (push) Successful in 2s
2026-01-10 01:19:30 +07:00
vladp 7b8546fd4d Dev
Test Workflow / test (push) Successful in 2s
2026-01-10 01:06:32 +07:00
vladp eb12e219ec Dev
Test Workflow / test (push) Successful in 2s
2026-01-10 00:54:49 +07:00
vladp 920cde3cdf Dev 2026-01-10 00:51:03 +07:00
backdev 5b4dca23fb Merge pull request 'NCBACK-3' (#14) from NCBACK-3 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #14
2026-01-10 01:43:36 +08:00
backdev 8102d72982 Merge pull request 'NCBACK-3 Add found by yandex image' (#10) from NCBACK-3 into dev
Test Workflow / test (push) Successful in 10s
Reviewed-on: #10
2026-01-10 01:03:33 +08:00
vladp d896f23872 add user_files_info for avaibale actions
Test Workflow / test (push) Successful in 2s
2026-01-09 23:05:37 +07:00
vladp 3e06040123 NCBACK-27 fix build
Test Workflow / test (push) Successful in 3s
2026-01-09 22:55:06 +07:00
vladp 18d35cb0fb Merge branch 'dev' into NCBACK-27
Test Workflow / test (push) Successful in 2s
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java
2026-01-09 12:41:32 +07:00
vladp f136ac1f0a add methods and lib for protection files
Test Workflow / test (push) Successful in 4s
2026-01-08 03:22:32 +07:00
vladp f1222ede57 add token life by hour
Test Workflow / test (push) Successful in 3s
2026-01-05 16:31:40 +07:00
vladp 0da0404488 add token life by hour
Test Workflow / test (push) Successful in 4s
2026-01-05 16:26:46 +07:00
310 changed files with 16125 additions and 1664 deletions
+11 -2
View File
@@ -1,10 +1,19 @@
# Postgres
POSTGRES_DB=no_copy_ POSTGRES_DB=no_copy_
POSTGRES_USER=postgres POSTGRES_USER=ncp_db
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=ncpDbApp
POSTGRES_PORT=5432 POSTGRES_PORT=5432
POSTGRES_HOST=postgres POSTGRES_HOST=postgres
# Redis
REDIS_HOST=redis REDIS_HOST=redis
REDIS_PORT=6379 REDIS_PORT=6379
# Backend server
SERVER_PORT=8080 SERVER_PORT=8080
# Postfix
MAIL_HOST_PROD=postfix-production
MAIL_PORT_PROD=25
MAIL_USERNAME_PROD=noreply@nocopy.com
SMTP_PASSWORD_PROD=nocopy!nocopy!
+4 -1
View File
@@ -14,6 +14,9 @@ COPY --from=build /app/build/libs/*.jar app.jar
RUN mkdir -p /data/uploads && chmod 755 /data/uploads RUN mkdir -p /data/uploads && chmod 755 /data/uploads
ENV BUILD_TIME_BACK="unknown"\
BUILD_TIME_FRONT="unknown"
EXPOSE 8080 EXPOSE 8080
CMD ["java", "-jar", "app.jar"] CMD ["java", "-jar", "app.jar"]
+492
View File
@@ -0,0 +1,492 @@
Создаем сеть:
docker network create app-network
Войти в БД:
docker exec -it postgres psql -U ncp_db -d no_copy_
Выполнить команду:
\du
Должен быть один пользователь:
List of roles
Role name | Attributes
-----------+------------------------------------------------------------
ncp_db | Superuser, Create role, Create DB, Replication, Bypass RLS
суперпользователь БД:
postgres/postgres
пользователь бд для бэка
ncp_db / ncpDbApp
Подключение к БД для приложения,если volume не существует :
Создаться автоматически.
Если уже существует volume :
docker exec -it postgres psql -U postgres -d postgres
CREATE USER ncp_db WITH PASSWORD 'ncpDbApp';
GRANT ALL PRIVILEGES ON DATABASE no_copy_ TO ncp_db;
\du
\q
-------
psql -U postgres -d no_copy_
-- база
GRANT CONNECT ON DATABASE no_copy_ TO ncp_db;
-- схема
GRANT USAGE ON SCHEMA public TO ncp_db;
-- существующие таблицы
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db;
-- существующие sequence (очень важно для id)
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db;
-- будущие таблицы
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO ncp_db;
Зайти в контейнер:
docker exec -it {name} bash
------
Создать для сущности FileEntity sequence для икремента индетификатора
CREATE SEQUENCE IF NOT EXISTS file_support_id_seq START 1;
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);
-----------------
Скрипт,для нумерации для уже сохданных файлов
WITH max_id AS (
SELECT COALESCE(MAX(support_id), 0) as max_val FROM file_entities
),
-- Нумеруем только NULL записи, начиная с max_val + 1
numbered AS (
SELECT
f.id,
(SELECT max_val FROM max_id) + ROW_NUMBER() OVER (ORDER BY f.created_at, f.id) as new_support_id
FROM file_entities f
WHERE f.support_id IS NULL
)
UPDATE file_entities
SET support_id = numbered.new_support_id
FROM numbered
WHERE file_entities.id = numbered.id;
---------------------------------
НАСТРОЙКА NGINX
--------------------------------
# admin.not-copy.com
server {
listen 80;
listen [::]:80;
server_name admin.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name admin.not-copy.com;
ssl_certificate /etc/letsencrypt/live/admin.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2995;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# workspace.not-copy.com
server {
listen 80;
listen [::]:80;
server_name workspace.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name workspace.not-copy.com;
ssl_certificate /etc/letsencrypt/live/workspace.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/workspace.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2998;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# lp.not-copy.com
server {
listen 80;
listen [::]:80;
server_name lp.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name lp.not-copy.com;
ssl_certificate /etc/letsencrypt/live/lp.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lp.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2993;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-admin.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-admin.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-admin.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-admin.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2996;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-workspace.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-workspace.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-workspace.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-workspace.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-workspace.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:3002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-lp.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-lp.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-lp.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-lp.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-lp.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2994;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Проверяем конфиг
ls -la /etc/nginx/sites-available/
ls -la /etc/nginx/sites-enabled/
# Создание конфига
sudo nano /etc/nginx/sites-available/not-copy
# Создание симлинк
sudo ln -s /etc/nginx/sites-available/not-copy /etc/nginx/sites-enabled/
# Чек
sudo nginx -t
# Рестарт
sudo systemctl reload nginx
---------------------------------------------
# ПОЛНАЯ ИНСТРУКЦИЯ: НАСТРОЙКА ПРОКСИ ДЛЯ ОБХОДА БЛОКИРОВОК SEARCHAPI.IO
## Данные для подключения к прокси-серверу
IP: 193.46.217.94
Порт HTTP: 3128
Порт SOCKS: 1080
Логин SSH: root
Пароль SSH: u7nRtsD6
## 1. Настройка прокси-сервера
```bash
# Подключаемся к серверу
ssh root@193.46.217.94
# Вводим пароль: u7nRtsD6
# Обновляем систему и устанавливаем пакеты
yum update -y
yum install -y epel-release net-tools nano
yum install -y 3proxy
# Создаем конфигурацию
mkdir -p /etc/3proxy
cat > /etc/3proxy/3proxy.cfg << 'EOF'
nserver 8.8.8.8
nserver 8.8.4.4
timeouts 1 5 30 60 180 1800 15 60
daemon
log /var/log/3proxy/3proxy.log
logformat "- +_L%t.%. %N.%p %E %U %C:%c %R:%r %O %I %h %T"
rotate 30
auth none
allow * * * * *
proxy -p3128
socks -p1080
EOF
# Создаем папку для логов и запускаем прокси
mkdir -p /var/log/3proxy
/usr/bin/3proxy /etc/3proxy/3proxy.cfg
# Проверяем, что запустилось
ps aux | grep 3proxy
netstat -tulpn | grep -E '3128|1080'
# Настраиваем файрвол
firewall-cmd --permanent --add-port=3128/tcp
firewall-cmd --permanent --add-port=1080/tcp
firewall-cmd --reload
firewall-cmd --list-ports
# Проверяем локальную работу прокси
curl -x http://127.0.0.1:3128 https://api.ipify.org
# Должен вернуть: 193.46.217.94
# Настраиваем автозапуск
echo "/usr/bin/3proxy /etc/3proxy/3proxy.cfg" >> /etc/rc.local
chmod +x /etc/rc.local
# Проверяем доступность прокси
ping -c 4 193.46.217.94
curl -x http://193.46.217.94:3128 https://api.ipify.org
# Должен вернуть: 193.46.217.94
# Проверяем работу с SearchAPI через прокси
curl -x http://193.46.217.94:3128 "https://www.searchapi.io/api/v1/search?engine=google_lens&api_key=5jyYZC8jSaxhZTwjMUhwtAXi&url=https://dev-workspace.not-copy.com/api/files/public/76d06557-6df1-4fcd-a273-050ec6a35faf&search_type=exact_matches" -o test.json
ls -la test.json
tail -20 test.json
# Файл должен быть полным (заканчиваться на '}')
ps aux | grep 3proxy # Проверка статуса
netstat -tulpn | grep -E '3128|1080' # Проверка портов
tail -f /var/log/3proxy/3proxy.log # Просмотр логов
killall 3proxy # Остановка прокси
/usr/bin/3proxy /etc/3proxy/3proxy.cfg # Запуск прокси
firewall-cmd --permanent --remove-port=3128/tcp
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="IP_ТВОЕГО_СЕРВЕРА" port port="3128" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="IP_ТВОЕГО_СЕРВЕРА" port port="1080" protocol="tcp" accept'
firewall-cmd --reload
На прокси-сервере:
bash
ps aux | grep 3proxy # Проверка статуса
netstat -tulpn | grep -E '3128|1080' # Проверка портов
tail -f /var/log/3proxy/3proxy.log # Просмотр логов
killall 3proxy # Остановка прокси
/usr/bin/3proxy /etc/3proxy/3proxy.cfg # Запуск прокси
На основном сервере:
bash
curl -x http://193.46.217.94:3128 https://api.ipify.org # Проверка прокси
telnet 193.46.217.94 3128 # Проверка порта
+16
View File
@@ -21,6 +21,9 @@ configurations {
repositories { repositories {
mavenCentral() mavenCentral()
flatDir {
dirs 'libs'
}
} }
dependencies { dependencies {
@@ -30,6 +33,12 @@ dependencies {
implementation 'org.springframework.security:spring-security-crypto:6.5.3' implementation 'org.springframework.security:spring-security-crypto:6.5.3'
implementation 'org.mapstruct:mapstruct:1.5.5.Final' implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'commons-validator:commons-validator:1.7' 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'
implementation 'org.flywaydb:flyway-core:9.22.0'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.0' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.0'
implementation 'tools.jackson.core:jackson-core:3.0.3' implementation 'tools.jackson.core:jackson-core:3.0.3'
@@ -49,6 +58,13 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:5.3.1' testImplementation 'org.mockito:mockito-core:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation name: 'testlib-fat-0.3.1-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation project(':referral')
} }
tasks.named('test') { tasks.named('test') {
+53 -12
View File
@@ -17,8 +17,31 @@ services:
command: tail -f /dev/null command: tail -f /dev/null
restart: unless-stopped restart: unless-stopped
# postfix:
# image: boky/postfix:latest
# container_name: postfix-production
# hostname: ${DOMAIN:-no-copy.ru}
# environment:
# - DOMAIN=${DOMAIN:-no-copy.ru}
# - ALLOWED_SENDER_DOMAINS=*
# - RELAYHOST=
# - DISABLE_SMTP_AUTH=
# - SMTP_USERNAME=${MAIL_USERNAME:-noreply@no-copy.ru}
# - SMTP_PASSWORD=${SMTP_PASSWORD:-nocopy!nocopy!}
# - RELAY_NETWORKS=172.16.0.0/12
# - POSTFIX_mynetworks=127.0.0.0/8 172.17.0.0/16 172.16.0.0/12
# - POSTFIX_smtpd_relay_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
# - POSTFIX_smtpd_recipient_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
# - DISABLE_SIGNING=true
# ports:
# - "25:25"
# - "587:587"
# networks:
# - app-network
# restart: unless-stopped
db: db:
image: postgres:17 image: postgres:17.7
restart: always restart: always
deploy: deploy:
resources: resources:
@@ -30,14 +53,17 @@ services:
memory: 1G memory: 1G
environment: environment:
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
POSTGRES_USER: postgres # POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres # POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_SHARED_BUFFERS: 512MB POSTGRES_SHARED_BUFFERS: 512MB
POSTGRES_EFFECTIVE_CACHE_SIZE: 1536MB POSTGRES_EFFECTIVE_CACHE_SIZE: 1536MB
ports: ports:
- "54320:5432" - "54320:5432"
volumes: volumes:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d:ro
container_name: postgres container_name: postgres
networks: networks:
app-network: app-network:
@@ -51,25 +77,36 @@ services:
resources: resources:
limits: limits:
cpus: '1.5' cpus: '1.5'
memory: 1G memory: 3G
reservations: reservations:
cpus: '0.5' cpus: '0.5'
memory: 512M memory: 2G
environment: environment:
JAVA_TOOL_OPTIONS: "-Xmx2g -Xms1g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:+UseContainerSupport -XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=75"
FILE_STORAGE_PATH: /data/uploads FILE_STORAGE_PATH: /data/uploads
MAX_FILE_SIZE: 10737418240 MAX_FILE_SIZE: 10737418240
# FILE_CHUNK_SIZE: 1048576 # FILE_CHUNK_SIZE: 1048576
FILE_CHUNK_SIZE: 1000000 FILE_CHUNK_SIZE: 1000000
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: 5432 POSTGRES_PORT: 5432
POSTGRES_HOST: db POSTGRES_HOST: db
STORAGE_SERVICE_URL: http://storage:8081
SPRING_PROFILES_ACTIVE: prod
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image" YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
STORAGE_SERVICE_URL: http://storage:8081 # POSTGRES_USER: postgres
SPRING_PROFILES_ACTIVE: docker # POSTGRES_PASSWORD: postgres
# MAIL_HOST: postfix
# MAIL_PORT: 25
# MAIL_USERNAME: noreply@no-copy.ru
# SMTP_PASSWORD: nocopy!nocopy!
# SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: "false"
# SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: "false"
# SPRING_MAIL_PROPERTIES_MAIL_DEBUG: "true"
depends_on: depends_on:
- db - db
ports: ports:
@@ -82,6 +119,10 @@ services:
- api - api
volumes: volumes:
- uploads_data:/data/uploads:rw - uploads_data:/data/uploads:rw
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d no_copy_" ]
interval: 10s
retries: 5
grafana: grafana:
image: grafana/grafana:10.3.1 image: grafana/grafana:10.3.1
@@ -140,7 +181,7 @@ services:
command: command:
- '--config.file=/etc/prometheus/prometheus.yml' - '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus' - '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=15d' # Удерживать 15 дней - '--storage.tsdb.retention.time=15d'
- '--web.enable-lifecycle' - '--web.enable-lifecycle'
healthcheck: healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ] test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
@@ -234,7 +275,7 @@ volumes:
loki_rules: loki_rules:
uploads_data: uploads_data:
prometheus_data: prometheus_data:
# postfix-data:
networks: networks:
app-network: app-network:
external: true external: true
+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,223 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'dev',
description: 'Ветка для деплоя'
)
string(
name: 'PORT',
defaultValue: '3001',
description: 'Порт для запуска экземпляра'
)
string(
name: 'PORT_DB',
defaultValue: '',
description: 'Порт для запуска БД'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
description: 'Сервер для деплоя'
)
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Имя сети (будет создана если не существует)'
)
choice(
name: 'SPRING_PROFILE',
choices: ['dev', 'prod'],
description: 'Профиль Spring для запуска'
)
}
stages {
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('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
rm -rf /opt/deployments/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
"
sshpass -p '${SSH_PASS}' scp -r ./* ${SSH_USER}@${params.SERVER}:/opt/deployments/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
def springProfile = params.SPRING_PROFILE
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
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} "
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
cd /opt/deployments/${params.BRANCH}-${params.PORT}
echo '1. Проверяем/создаем сеть ${params.NETWORK}...'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
echo 'Сеть ${params.NETWORK} создана'
else
echo 'Сеть ${params.NETWORK} уже существует'
fi
echo '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 ${params.NETWORK} \\
--network-alias no_copy_${params.PORT} \\
--network-alias database-${params.PORT} \\
--network-alias db-${params.PORT} \\
-p ${params.PORT}0:5432 \\
-e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_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 ${params.NETWORK} \\
-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 ${params.NETWORK} \\
--network-alias app \\
--network-alias app-${params.PORT} \\
-p ${params.PORT}:8080 \\
-v uploads_data_${params.PORT}:/data/uploads:rw \\
-e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\
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
"
"""
}
}
}
}
stage('Verify network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
echo 'Проверяем подключение к сети ${params.NETWORK}'
docker network inspect ${params.NETWORK} --format='{{.Name}}: {{range .Containers}}{{.Name}} {{end}}'
echo 'Проверяем алиас app для контейнера app-backend-${params.PORT}'
docker inspect app-backend-${params.PORT} | grep -A 5 'Aliases'
"
"""
}
}
}
}
}
post {
success {
script {
def appUrl = params.SPRING_PROFILE == 'prod' ? 'https://workspace.not-copy.com' : 'https://dev-workspace.not-copy.com'
echo "Deployment successful"
echo "Application URL: ${appUrl}"
}
}
failure {
echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}"
}
always {
echo "Deployment process finished"
}
}
}
+156 -69
View File
@@ -7,6 +7,16 @@ pipeline {
defaultValue: 'main', defaultValue: 'main',
description: 'Ветка для деплоя' description: 'Ветка для деплоя'
) )
string(
name: 'PORT',
defaultValue: '2998',
description: 'Порт для запуска экземпляра'
)
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Docker сеть для подключения'
)
} }
stages { stages {
@@ -36,19 +46,19 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true docker stop no-copy-frontend-${params.PORT} 2>/dev/null || true
docker rm no-copy-frontend 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 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 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 fi
" "
""" """
} }
} }
} }
@@ -65,13 +75,13 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/* rm -rf /opt/deployments/frontend/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH} 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 +90,8 @@ pipeline {
stage('Build on server') { stage('Build on server') {
steps { steps {
script { script {
def BUILD_TIME_FRONT = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([ withCredentials([
usernamePassword( usernamePassword(
credentialsId: 'server-root-password', credentialsId: 'server-root-password',
@@ -88,19 +100,21 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " 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 # 1. СОЗДАЕМ JSON ФАЙЛ (ЭТО РАБОТАЕТ 100%)
docker-compose -f docker-compose.yaml build mkdir -p public
elif [ -f 'docker-compose.yml' ]; then echo '{\\"buildTime\\": \\"${BUILD_TIME_FRONT}\\"}' > public/build-info.json
docker-compose -f docker-compose.yml build
else # 2. Меняем имя контейнера и порт
echo 'Exception: docker-compose file not found' sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
exit 1 sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
fi
" # 3. Собираем образ
""" docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
"
"""
} }
} }
} }
@@ -117,24 +131,26 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " 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 если нужно echo '=== Запускаем контейнер ==='
docker network inspect app-network 2>/dev/null || echo 'Warning: app-network not found' docker-compose -f docker-compose.yml -p frontend-${params.PORT} up -d
if [ -f 'docker-compose.yaml' ]; then sleep 10
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then echo '=== Проверяем запуск ==='
docker-compose -f docker-compose.yml up -d if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
fi echo '✓ Контейнер запущен: no-copy-frontend-${params.PORT}'
echo '✓ Порт: ${params.PORT}:2999'
sleep 10 else
echo '✗ Контейнер не запустился'
echo 'Status container:' echo 'Логи:'
docker ps --filter name=no-copy-frontend docker-compose -f docker-compose.yml -p frontend-${params.PORT} logs
" exit 1
""" fi
"
"""
} }
} }
} }
@@ -151,23 +167,93 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " 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
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP code: \$HTTP_CODE' 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'
else echo 'Frontend available on http://92.242.61.23:${params.PORT}'
echo 'Exception: container did not start'
docker ps -a | grep frontend # Показываем все фронтенды
exit 1 echo ''
fi 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
exit 1
fi
"
"""
}
}
}
}
// stage('Connect to network') {
// steps {
// script {
// withCredentials([
// usernamePassword(
// credentialsId: 'server-root-password',
// usernameVariable: 'SSH_USER',
// passwordVariable: 'SSH_PASS'
// )
// ]) {
// sh """
// sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
// echo 'Отключаем от всех сетей и подключаем к ${params.NETWORK}'
//
// # Отключаем от всех сетей (кроме bridge, host, none)
// current_networks=\$(docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}' | grep -v -E '^(bridge|host|none)\$' || true)
//
// for net in \$current_networks; do
// echo \"Отключаем от сети: \$net\"
// docker network disconnect \$net no-copy-frontend-${params.PORT} 2>/dev/null || true
// done
//
// # Подключаем к нужной сети
// docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
//
// echo 'Готово! Контейнер теперь в сети:'
// docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}'
// "
// """
// }
// }
// }
// }
stage('Connect to network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
echo 'Подключаем контейнер к сети ${params.NETWORK}'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
fi
if docker ps -a --format '{{.Names}}' | grep -q '^no-copy-frontend-${params.PORT}\$'; then
docker network disconnect ${params.NETWORK} no-copy-frontend-${params.PORT} 2>/dev/null || true
docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
echo 'Контейнер подключен к ${params.NETWORK}'
fi
"
"""
} }
} }
} }
@@ -177,7 +263,8 @@ pipeline {
post { post {
success { success {
echo "Front branch ${params.BRANCH} deployment completed" 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 { failure {
echo "Deployment failed" echo "Deployment failed"
+76 -63
View File
@@ -17,6 +17,7 @@ pipeline {
stages { stages {
stage('Git pull') { stage('Git pull') {
steps { steps {
cleanWs()
script { script {
checkout([ checkout([
$class: 'GitSCM', $class: 'GitSCM',
@@ -33,75 +34,87 @@ pipeline {
stage('Deploy with docker-compose') { stage('Deploy with docker-compose') {
steps { steps {
script { script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([ withCredentials([
usernamePassword( usernamePassword(
credentialsId: 'server-root-password', credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER', usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS' passwordVariable: 'SSH_PASS'
) ),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD')
]) { ]) {
sh """ sh """
echo "Deploying branch: ${params.BRANCH}" echo "Deploying branch: ${params.BRANCH}"
echo "Copying files to server..." echo "Copying files to server..."
sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}" sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}"
sshpass -p '$SSH_PASS' scp -r -o StrictHostKeyChecking=no ./* $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/
sshpass -p '$SSH_PASS' rsync -av --delete \\
sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER " --exclude=.git \\
cd /opt/deployments/${params.BRANCH} --exclude=.gradle \\
--exclude=build \\
echo '1. Остановка старого приложения...' --exclude=**/build \\
docker stop app-backend 2>/dev/null || echo 'Контейнер не найден' --exclude=.idea \\
docker rm app-backend 2>/dev/null || echo 'Контейнер не найден' --exclude=out \\
. $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/
echo '2. Удаление старых образов...'
docker images --filter 'reference=*app*' -q | xargs -r docker rmi -f 2>/dev/null || echo 'Нет образов для удаления' sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH}
echo '3. Создание сети если нужно...'
docker network create app-network 2>/dev/null || echo 'Сеть уже существует' echo '1. Остановка старого приложения...'
docker stop app-backend 2>/dev/null || echo 'Контейнер не найден'
echo '4. Запуск инфраструктуры...' docker rm app-backend 2>/dev/null || echo 'Контейнер не найден'
docker-compose up -d db storage
echo '2. Удаление старых образов...'
echo '5. Ожидание PostgreSQL...' docker images --filter 'reference=*app*' -q | xargs -r docker rmi -f 2>/dev/null || echo 'Нет образов для удаления'
sleep 10
echo '3. Создание сети если нужно...'
echo '6. Сборка нового образа приложения...' docker network create app-network 2>/dev/null || echo 'Сеть уже существует'
docker build --no-cache -t app-backend:latest .
echo '4. Запуск инфраструктуры...'
echo '7. Запуск приложения...' docker-compose up -d db storage
docker run -d \\
--name app-backend \\ echo '5. Ожидание PostgreSQL...'
--network app-network \\ sleep 10
--network-alias app \\
-p 80:8080 \\ echo '6. Сборка нового образа приложения...'
-v uploads_data:/data/uploads:rw \\ docker build --no-cache -t app-backend:latest .
-e POSTGRES_DB=no_copy_ \\
-e POSTGRES_USER=postgres \\ echo '7. Запуск приложения...'
-e POSTGRES_PASSWORD=postgres \\ docker run -d \\
-e POSTGRES_PORT=5432 \\ --name app-backend \\
-e POSTGRES_HOST=db \\ --network app-network \\
--restart unless-stopped \\ --network-alias app \\
app-backend:latest -p 80:8080 \\
-v uploads_data:/data/uploads:rw \\
echo '8. Запуск мониторинга...' -e POSTGRES_DB=no_copy_ \\
docker-compose up -d grafana prometheus loki tempo alloy -e POSTGRES_USER=$DB_USER \\
-e POSTGRES_PASSWORD=$DB_PASSWORD \\
echo '9. Проверка...' -e POSTGRES_PORT=5432 \\
sleep 5 -e POSTGRES_HOST=db \\
-e BUILD_TIME_BACK='${buildTime}' \\
echo 'Статус контейнеров:' --restart unless-stopped \\
docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}' app-backend:latest
echo '10. Проверка health...' echo '8. Запуск мониторинга...'
if curl -s -f http://localhost:80/health > /dev/null 2>&1; then docker-compose up -d grafana prometheus loki tempo alloy
echo 'Приложение работает'
echo 'URL: http://${params.SERVER}:80' echo '9. Проверка...'
else sleep 5
echo 'Проверка health не удалась'
docker logs app-backend --tail=20 echo 'Статус контейнеров:'
fi docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}'
"
echo '10. Проверка health...'
if curl -s -f http://localhost:80/health > /dev/null 2>&1; then
echo 'Приложение работает'
echo 'URL: http://${params.SERVER}:80'
else
echo 'Проверка health не удалась'
docker logs app-backend --tail=20
fi
"
""" """
} }
} }
@@ -121,4 +134,4 @@ pipeline {
echo "Deployment process finished" echo "Deployment process finished"
} }
} }
} }
@@ -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"
}
}
}
+19
View File
@@ -0,0 +1,19 @@
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'ncp_db') THEN
CREATE USER ncp_db WITH PASSWORD 'ncpDbApp';
END IF;
END
$$;
GRANT CONNECT ON DATABASE no_copy_ TO ncp_db;
GRANT USAGE ON SCHEMA public TO ncp_db;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO ncp_db;
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
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!")
}
+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,
)
+6
View File
@@ -1 +1,7 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
rootProject.name = 'no-copy' rootProject.name = 'no-copy'
include 'referral'
include 'finance'
@@ -0,0 +1,69 @@
package ru.soune.nocopy.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
@Slf4j
public class YooKassaClient {
@Value("${yookassa.shop-id}")
private String shopId;
@Value("${yookassa.secret-key}")
private String secretKey;
private final HttpClient httpClient = HttpClient.newHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
public boolean createAutoPayment(String paymentMethodId, double amount, String description, String userId) {
try {
String url = "https://api.yookassa.ru/v3/payments";
String auth = shopId + ":" + secretKey;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
Map<String, Object> body = new HashMap<>();
body.put("amount", Map.of("value", String.format("%.2f", amount),
"currency", "RUB"));
body.put("payment_method_id", paymentMethodId);
body.put("description", description);
body.put("capture", true);
body.put("metadata", Map.of("user_id", userId, "auto_payment", "true"));
String requestBody = mapper.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Basic " + encodedAuth)
.header("Idempotence-Key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200 || response.statusCode() == 201) {
log.info("Auto payment created: {}", response.body());
return true;
} else {
log.error("Auto payment failed: {} - {}", response.statusCode(), response.body());
return false;
}
} catch (Exception e) {
log.error("Error creating auto payment", e);
return false;
}
}
}
@@ -1,17 +1,51 @@
package ru.soune.nocopy.configuration; 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 lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Collections;
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@AllArgsConstructor
public class ApplicationConfig { public class ApplicationConfig {
@Bean @Bean
PasswordEncoder passwordEncoder() { PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Bean
public com.vrt.
NoCopyFileService noCopyFileService(
FileProtector.FileProvider fileProvider,
FileProtector.ProcessingListener processingListener,
ImageUniqueCheck imageUniqueCheck,
ImageLocalSearch imageLocalSearch,
AudioLocalSearch audioLocalSearch,
AudioFilePathProvider audioFilePathProvider,
DocumentLocalSearch documentLocalSearch) {
return new com.vrt.NoCopyFileService(
Collections.emptyList(),
fileProvider,
processingListener,
imageUniqueCheck,
imageLocalSearch,
audioLocalSearch,
audioFilePathProvider,
documentLocalSearch
);
}
} }
@@ -0,0 +1,23 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
@@ -17,7 +17,22 @@ public class HandlerConfig {
FileUploadHandler upload, FileUploadHandler upload,
FileEntityHandler file, FileEntityHandler file,
LogoutRequestHandler logoutHandler, LogoutRequestHandler logoutHandler,
ImageFoundRequestHandler imageFoundRequestHandler ImageFoundRequestHandler imageFoundRequestHandler,
VerifyRegisterUserHandler verifyRegisterUser,
AuthRequestHandler authRequestHandler,
CompanyHandler companyHandler,
TariffHandler tariffHandler,
TariffInfoHandler tariffInfoHandler,
ReferralHandler referralHandler,
ResetPasswordHandler resetPasswordHandler,
DaDataHandler daDataHandler,
PaymentHandler paymentHandler,
CostHandler costHandler,
MonitoringHandler monitoringHandler,
ViolationHandler violationHandler,
ViolationStatisticsHandler violationStatisticsHandler,
GlobalSearchHandler globalSearchHandler,
ViolationNotionHandler violationNotionHandler
) { ) {
Map<Integer, RequestHandler> map = new HashMap<>(); Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login); map.put(20001, login);
@@ -26,6 +41,21 @@ public class HandlerConfig {
map.put(20005, file); map.put(20005, file);
map.put(20006, logoutHandler); map.put(20006, logoutHandler);
map.put(20007, imageFoundRequestHandler); map.put(20007, imageFoundRequestHandler);
map.put(20008, authRequestHandler);
map.put(20009, verifyRegisterUser);
map.put(20010, resetPasswordHandler);
map.put(30000, companyHandler);
map.put(30001, tariffHandler);
map.put(30002, tariffInfoHandler);
map.put(30003, referralHandler);
map.put(30004, daDataHandler);
map.put(30005, paymentHandler);
map.put(30007, monitoringHandler);
map.put(30008, costHandler);
map.put(30009, violationHandler);
map.put(30010, violationStatisticsHandler);
map.put(30011, globalSearchHandler);
map.put(30012, violationNotionHandler);
return map; return map;
} }
@@ -0,0 +1,44 @@
package ru.soune.nocopy.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.ImageResizeService;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Component
@Slf4j
public class ImageMigration {
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private ImageResizeService imageResizeService;
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
List<FileEntity> images = fileRepository.findByThumbnailPathIsNull();
for (FileEntity file : images) {
try {
Path path = Paths.get(file.getProtectedFilePath());
if (Files.exists(path)) {
byte[] data = Files.readAllBytes(path);
imageResizeService.generateSizes(file, data);
fileRepository.save(file);
}
} catch (Exception e) {
log.error("Error migrating {}: {}", file.getId(), e.getMessage());
}
}
}
}
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -20,7 +21,13 @@ public class JacksonConfig {
@Bean @Bean
@Primary @Primary
public ObjectMapper objectMapper() { 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(); 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,22 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(30000);
requestFactory.setReadTimeout(60000);
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
}
}
@@ -21,4 +21,15 @@ public class AsyncConfig {
executor.initialize(); executor.initialize();
return executor; return executor;
} }
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("global-search-");
executor.initialize();
return executor;
}
} }
@@ -0,0 +1,15 @@
package ru.soune.nocopy.configuration.file;
import lombok.AllArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@AllArgsConstructor
public class NoCopyInitializer {
@EventListener(ApplicationReadyEvent.class)
public void initializeOnStartup() {
}
}
@@ -0,0 +1,26 @@
package ru.soune.nocopy.configuration.search;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Data
@Component
@ConfigurationProperties(prefix = "search")
public class SearchProperties {
private Map<String, EngineConfig> engines;
private ProxyConfig proxy;
@Data
public static class EngineConfig {
private boolean enabled;
}
@Data
public static class ProxyConfig {
private boolean enabled;
private String host;
private int port;
}
}
@@ -1,9 +1,16 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult;
import com.vrt.fileprotection.audio.AudioCheckResult;
import com.vrt.fileprotection.documents.DocumentCheckResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.UrlResource; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -14,27 +21,33 @@ import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.BaseRequest; import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse; import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.RegAnswer; import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.dto.file.ChunkUploadResponse; import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.dto.file.CompleteUploadResponse; import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.dto.file.UploadProgress;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.*; import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.handler.*; import ru.soune.nocopy.handler.*;
import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService; import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.util.FileUtil;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.HashMap; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -48,8 +61,22 @@ public class ApiController {
private final FileEntityService fileEntityService; private final FileEntityService fileEntityService;
private final AuthService authService;
private final FileSimilarityService fileSimilarityService;
private final FileEntityRepository fileEntityRepository;
private final FileUtil fileUtil;
private final ProtectionsLimitService protectionsLimitService;
private final AuthTokenRepository authTokenRepository; private final AuthTokenRepository authTokenRepository;
private final UserRepository userRepository;
private final CheckCounterService checkCounterService;
@PostMapping("/v{version}/data") @PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request, public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) { @PathVariable("version") int version) {
@@ -71,6 +98,12 @@ public class ApiController {
return ResponseEntity.ok().body(response); return ResponseEntity.ok().body(response);
} catch (ValidationException e) { } catch (ValidationException e) {
return createValidationErrorResponse(e.getBindingResult(), e.getMsgId()); return createValidationErrorResponse(e.getBindingResult(), e.getMsgId());
} catch (UserNotFoundException e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotFoundAuthToken e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotValidFieldException e) { } catch (NotValidFieldException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
@@ -90,57 +123,160 @@ public class ApiController {
@PathVariable("version") int version, @PathVariable("version") int version,
@RequestParam(value = "upload_id", required = false) String uploadId, @RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam(value = "chunk_number", required = false) Integer chunkNumber, @RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam(value = "chunk", required = false) MultipartFile chunk) { @RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "findSimilar", required = false, defaultValue = "0") Integer findSimilar) {
try { try {
if (chunk == null || chunk.isEmpty()) { if (chunk == null || chunk.isEmpty()) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty");
"Chunk file null or empty", ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build()));
} }
if (uploadId == null || uploadId.isBlank()) { if (uploadId == null || uploadId.isBlank()) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), return buildErrorResponse(uploadId, chunkNumber, "Upload ID is required");
"Upload ID is required", ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build()));
} }
if (chunkNumber == null || chunkNumber < 0) { if (chunkNumber == null || chunkNumber < 0) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required");
"Valid chunk number is required", ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build()));
} }
fileUploadService.uploadChunk(uploadId, chunkNumber, chunk); UploadProgressResponse uploadProgressResponse = fileUploadService.uploadChunk(uploadId, chunkNumber,
chunk, findSimilar);
ChunkUploadResponse responseBody = ChunkUploadResponse.builder() return buildSuccessResponse(uploadId, chunkNumber, chunk,
.uploadId(uploadId) fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath()));
.chunkNumber(chunkNumber) } catch (DuplicateImageException e) {
.chunkSize(chunk.getSize()) Map<String, Object> duplicateData = new HashMap<>();
.message("Chunk uploaded successfully") duplicateData.put("duplicateFileId", e.duplicateFileId());
.build(); duplicateData.put("userId", e.userId());
duplicateData.put("message", e.getMessage());
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(), if (uploadId != null) {
"Chunk uploaded successfully", responseBody)); duplicateData.put("uploadId", uploadId);
} catch (Exception e) { }
log.error("Error uploading chunk", e);
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build();
return ResponseEntity.ok().body(new BaseResponse( return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), 20004,
"Failed to upload chunk: " + e.getMessage(), responseBody)); MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(),
duplicateData
));
}catch (FileFormatException e){
return buildErrorResponse(uploadId, chunkNumber, e.getMessage());
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
} }
} }
private final NoCopyFileService noCopyFileService;
@GetMapping("/v{version}/files/{fileId}/similar")
public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version,
@PathVariable String fileId,
@RequestParam(value = "auth_token", required = false) String authToken,
@RequestParam(required = false) List<String> similarityLevels,
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
try {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
if (fileEntity == null) {
return ResponseEntity.ok().body(new BaseResponse(0, MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), null));
}
BaseResponse response;
if ("image".equals(fileEntity.getMimeType())) {
Page<SimilarFileDTO> similarFilesPage = fileSimilarityService.findSimilarFiles(
fileId, similarityLevels, pageable, authToken);
response = buildSuccessResponse(similarFilesPage, MessageCode.SIMILAR_FILES_FOUND);
} else {
List<SimilarFileDTO> results = processNonImageFile(fileEntity, authToken);
log.info("results: {}", results);
response = buildSuccessResponse(results, MessageCode.SIMILAR_FILES_FOUND);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
}
return ResponseEntity.ok(response);
} catch(IllegalArgumentException e) {
log.error("Error with file extensions : {}", e.getMessage(), e);
return ResponseEntity.ok().body(new BaseResponse(MessageCode.FILE_FOR_SEARCH_NOT_VALID.getCode(),
MessageCode.FILE_FOR_SEARCH_NOT_VALID.getCode(),
MessageCode.FILE_FOR_SEARCH_NOT_VALID.getDescription(), null));
} catch (Exception e) {
log.error("Error finding similar files: {}", e.getMessage(), e);
return ResponseEntity.ok().body(new BaseResponse(MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), null));
}
}
private List<SimilarFileDTO> processNonImageFile(FileEntity fileEntity, String authToken) throws Exception {
List<SimilarFileDTO> results = new ArrayList<>();
FileEntity duplicateByHash = fileSimilarityService.findDuplicateByHash(fileEntity.getFilePath(),
fileEntity.getMimeType(), fileEntity.getUserId());
log.info("duplicateByHash: {}", duplicateByHash);
if (duplicateByHash != null) {
results.add(fileSimilarityService.buildDTO(duplicateByHash));
return results;
}
File file = new File(fileEntity.getFilePath());
FileProtector.Type type = "document".equals(fileEntity.getMimeType()) ?
FileProtector.Type.DOC : FileProtector.Type.AUDIO;
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, type);
log.info("checkResult: {}", checkResult);
switch (checkResult) {
case DocumentCheckResult.Success success -> {
FileProtector.FileInfo info = success.getInfo();
FileEntity entity = fileEntityRepository.findByFileId(info.getId());
if (entity != null) {
results.add(fileSimilarityService.buildDTO(entity));
}
}
case AudioCheckResult.Success success -> {
FileProtector.FileInfo info = success.getInfo();
FileEntity entity = fileEntityRepository.findByFileId(info.getId());
if (entity != null) {
results.add(fileSimilarityService.buildDTO(entity));
}
}
case DocumentCheckResult.Failed failed ->
log.warn("Document check failed: {}", failed.getMessage());
case AudioCheckResult.Failed failed ->
log.warn("Audio check failed: {}", failed.getMessage());
default -> log.warn("Unexpected result type: {}", checkResult.getClass().getSimpleName());
}
return results;
}
private BaseResponse buildSuccessResponse(Page<SimilarFileDTO> page, MessageCode messageCode) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("content", page.getContent());
responseData.put("page", page.getNumber());
responseData.put("size", page.getSize());
responseData.put("totalElements", page.getTotalElements());
responseData.put("totalPages", page.getTotalPages());
responseData.put("hasNext", page.hasNext());
responseData.put("hasPrevious", page.hasPrevious());
return new BaseResponse(20004, messageCode.getCode(),
messageCode.getDescription(), responseData);
}
private BaseResponse buildSuccessResponse(List<SimilarFileDTO> list, MessageCode messageCode) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("content", list);
responseData.put("totalElements", list.size());
return new BaseResponse(20004, messageCode.getCode(),
messageCode.getDescription(), responseData);
}
@GetMapping("/v{version}/files/progress/{uploadId}") @GetMapping("/v{version}/files/progress/{uploadId}")
public ResponseEntity<BaseResponse> getUploadProgress( public ResponseEntity<BaseResponse> getUploadProgress(
@PathVariable("version") int version, @PathVariable("version") int version,
@@ -164,7 +300,6 @@ public class ApiController {
return ResponseEntity.ok().body(new BaseResponse( return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody)); 20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody));
} catch (Exception e) { } catch (Exception e) {
log.error("Error getting progress for upload: {}", uploadId, e); log.error("Error getting progress for upload: {}", uploadId, e);
@@ -238,35 +373,46 @@ public class ApiController {
} }
} }
@GetMapping("/v{version}/files/download/{fileId}") @GetMapping("/v{version}/files/download/{fileId}")
public ResponseEntity<?> downloadFile( public ResponseEntity<?> downloadFile(
@PathVariable(required = false) String fileId, @PathVariable(required = false) String fileId,
@PathVariable(required = false) Integer version, @PathVariable(required = false) Integer version,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) { @RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try { try {
if (tokenHeader == null) { if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader); errorData.put("token", tokenHeader);
errorData.put("fileId", fileId);
errorData.put("version", version);
return ResponseEntity.ok().body(new BaseResponse(20004, return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(), MessageCode.TOKEN_IS_NULL.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(), MessageCode.TOKEN_IS_NULL.getDescription(),
errorData)); errorData));
} }
Long userId = getUserIdFromToken(tokenHeader); Long userId = authService.useUserAuthToken(tokenHeader);
FileEntityResponse entityResponse = fileEntityService.getById(fileId, version); FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
if (!entityResponse.getUserId().equals(userId)) { User fileUser = userRepository.findById(entityResponse.getUserId()).orElseThrow();
User user = userRepository.findById(userId).orElseThrow();
Company companyUser = user.getCompany();
if (companyUser != null && !fileUser.getCompany().getId().equals(companyUser.getId())) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader); errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004, return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(), MessageCode.USER_NOT_HAD_PERMISSION.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(), MessageCode.USER_NOT_HAD_PERMISSION.getDescription(),
errorData));
}
if (companyUser == null && !entityResponse.getUserId().equals(userId)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.USER_NOT_HAD_PERMISSION.getCode(),
MessageCode.USER_NOT_HAD_PERMISSION.getDescription(),
errorData)); errorData));
} }
@@ -275,8 +421,8 @@ public class ApiController {
errorData.put("file_status", entityResponse.getStatus()); errorData.put("file_status", entityResponse.getStatus());
return ResponseEntity.ok().body(new BaseResponse(20004, return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(), MessageCode.FILE_DELETE.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(), MessageCode.FILE_DELETE.getDescription(),
errorData)); errorData));
} }
@@ -285,13 +431,15 @@ public class ApiController {
errorData.put("onDisk", entityResponse.isExistsOnDisk()); errorData.put("onDisk", entityResponse.isExistsOnDisk());
return ResponseEntity.ok().body(new BaseResponse(20004, return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(), MessageCode.FILE_NOT_EXIST.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(), MessageCode.FILE_NOT_EXIST.getDescription(),
errorData)); errorData));
} }
Path filePath = Paths.get(entityResponse.getFilePath());
Resource resource = new UrlResource(filePath.toUri()); Path filePath = Paths.get(entityResponse.getProtectedFilePath());
FileSystemResource resource = new FileSystemResource(filePath);
long fileSize = Files.size(filePath);
if (!resource.exists()) { if (!resource.exists()) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
@@ -302,14 +450,13 @@ public class ApiController {
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(), MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData)); errorData));
} }
String contentType = determineContentType(filePath); String contentType = determineContentType(filePath);
return ResponseEntity.ok() return ResponseEntity.ok()
.contentLength(fileSize)
.contentType(MediaType.parseMediaType(contentType)) .contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getOriginalFileName() + "\"") "attachment; filename=\"" + entityResponse.getFileName() + "\"")
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(entityResponse.getFileSize()))
.body(resource); .body(resource);
} catch (FileEntityNotFoundException e) { } catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
@@ -319,7 +466,15 @@ public class ApiController {
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getCode(),
MessageCode.FILE_NOT_FOUND.getDescription(), MessageCode.FILE_NOT_FOUND.getDescription(),
errorData)); errorData));
} catch (NotFoundAuthToken | IOException e) { } catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
errorData));
} catch (IOException e) {
Map<String, Object> errorData = new HashMap<>(); Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader); errorData.put("token", tokenHeader);
errorData.put("fileId", fileId); errorData.put("fileId", fileId);
@@ -332,6 +487,76 @@ public class ApiController {
} }
} }
@GetMapping("/check/file_stats")
public ResponseEntity<?> checkFileProtectStats(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
Map<String, Object> fileTypeStats = protectionsLimitService.getFileTypeStats(authToken.getUser().getId());
return ResponseEntity.ok().body(fileTypeStats);
}
private boolean hasDuplicate(List<SimilarFileDTO> similarFiles) {
return similarFiles.stream().anyMatch(f -> f.getHammingDistance() <= 5);
}
private ResponseEntity<BaseResponse> handleDuplicate(FileEntity fileEntity, List<SimilarFileDTO> similarFiles)
throws IOException {
fileEntityService.deleteFromDisk(fileEntity);
fileEntityService.softDeleteFileWithHash(fileEntity);
Optional<FileEntity> originalFile = fileEntityRepository.findById(similarFiles.get(0).getFileId());
if (originalFile.isPresent()) {
Map<String, String> duplicateInfo = Map.of(
"duplicate_file_id", originalFile.get().getId(),
"owner_user_id", String.valueOf(originalFile.get().getUserId()));
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
"Failed to upload chunk, duplicate",
duplicateInfo));
}
return null;
}
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
String fileId) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.chunkSize(chunk.getSize())
.fileId(fileId)
.message("Chunk uploaded successfully")
.build();
return ResponseEntity.ok().body(new BaseResponse(
20000,
MessageCode.SUCCESS.getCode(),
"Chunk uploaded successfully",
responseBody));
}
private ResponseEntity<BaseResponse> buildErrorResponse(String uploadId, Integer chunkNumber, String errorMessage) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build();
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.FILE_UPLOAD_ERROR.getCode(),
errorMessage,
responseBody));
}
private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) { private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) {
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors() List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
.stream() .stream()
@@ -367,11 +592,4 @@ public class ApiController {
} }
return contentType; return contentType;
} }
private Long getUserIdFromToken(String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
AuthToken authToken = authTokenRepository.findByToken(token)
.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
return authToken.getUser().getId();
}
} }
@@ -0,0 +1,148 @@
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.*;
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 ru.soune.nocopy.service.file.ImageResizeService;
import java.io.FileNotFoundException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/files")
@Slf4j
public class FileController {
@Autowired
private FileStorageService fileStorageService;
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private CheckCounterService checkCounterService;
@Autowired
private UserRepository userRepository;
@Autowired
private ImageResizeService imageResizeService;
@GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) {
try {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found: " + fileId));
Path filePath = Paths.get(fileEntity.getFilePath());
if (!Files.exists(filePath)) {
log.error("Not on disk: {}", fileEntity.getFilePath());
return ResponseEntity.notFound().build();
}
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
MediaType mediaType = getMediaType(fileEntity);
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.contentType(mediaType)
.contentLength(Files.size(filePath))
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(resource);
} catch (FileNotFoundException e) {
log.warn("Not on disk: {}", fileId);
return ResponseEntity.notFound().build();
} catch (IOException e) {
log.error("Read file exception: {}", fileId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private MediaType getMediaType(FileEntity fileEntity) {
try {
return MediaType.parseMediaType(fileEntity.getMimeType());
} catch (InvalidMediaTypeException e) {
String extension = fileEntity.getFileExtension().toLowerCase();
switch (extension) {
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
case "png":
return MediaType.IMAGE_PNG;
case "pdf":
return MediaType.APPLICATION_PDF;
default:
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}
@GetMapping("/protected/{fileId}/{type}")
public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId, @PathVariable String type) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
String path = imageResizeService.getPath(fileEntity, type);
Resource resource = fileStorageService.loadFileAsResource(path);
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected");
}
String fileName = fileEntity.getOriginalFileName().replace("." +
fileEntity.getFileExtension(), "") + "_nocopy_protected" +
"." + fileEntity.getFileExtension();
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20");
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename*=UTF-8''" + encodedFileName)
.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);
}
}
@@ -0,0 +1,173 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.search.GlobalSearchStartRequest;
import ru.soune.nocopy.dto.search.GlobalSearchStartResponse;
import ru.soune.nocopy.dto.search.GlobalSearchStatusResponse;
import ru.soune.nocopy.dto.violation.FileViolationSummaryDTO;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.search.GlobalSearchTask;
import ru.soune.nocopy.entity.search.SearchStatus;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.search.GlobalSearchService;
import ru.soune.nocopy.service.violation.ViolationService;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/api/v1/global-search")
@RequiredArgsConstructor
public class GlobalSearchController {
private final AuthService authService;
private final GlobalSearchService globalSearchService;
private final GlobalSearchTaskRepository globalSearchTaskRepository;
private final GlobalSearchTaskRepository searchTaskRepository;
private final ViolationService violationService;
@PostMapping("/start")
public ResponseEntity<?> startSearch(
@RequestBody GlobalSearchStartRequest request,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
GlobalSearchStartResponse response = new GlobalSearchStartResponse();
try {
Long userId = authService.useUserAuthToken(tokenHeader);
List<FileEntity> filesToProcess = globalSearchService.getFilesToProcess(request, userId);
if (filesToProcess.isEmpty()) return ResponseEntity.ok().body("Files for search not found");
String taskId = globalSearchService.startSearch(request, userId, filesToProcess);
Optional<GlobalSearchTask> taskOptional = globalSearchTaskRepository.findById(taskId);
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
GlobalSearchTask task = taskOptional.orElseThrow();
response.setTaskId(taskId);
response.setStatus(SearchStatus.ACCEPTED.name());
response.setTotalFiles(task.getTotalFiles());
} catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
return ResponseEntity.ok(response);
}
@GetMapping("/actual-task")
public ResponseEntity<?> getStatus(@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Optional<GlobalSearchTask> taskOptional;
try {
Long userId = authService.useUserAuthToken(tokenHeader);
taskOptional = searchTaskRepository.findByUserIdAndStatus(userId,
"PROCESSING");
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
} catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
return ResponseEntity.ok(taskOptional.get());
}
@GetMapping("/violation-summary")
public ResponseEntity<?> getSummary(@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
List<FileViolationSummaryDTO> fileViolationsSummary;
try {
Long userId = authService.useUserAuthToken(tokenHeader);
fileViolationsSummary =
violationService.getFileViolationsSummary(userId);
if (fileViolationsSummary.isEmpty()) return ResponseEntity.ok().body("Violations summary not found");
} catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
return ResponseEntity.ok(fileViolationsSummary);
}
@GetMapping("/status/{taskId}")
public ResponseEntity<?> getStatus(@PathVariable String taskId,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
GlobalSearchStatusResponse response;
try {
Long userId = authService.useUserAuthToken(tokenHeader);
Optional<GlobalSearchTask> taskOptional = searchTaskRepository.findByTaskIdAndUserId(taskId, userId);
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
GlobalSearchTask task = taskOptional.orElseThrow();
int progress = task.getTotalFiles() > 0 ? task.getProcessedFiles() * 100 / task.getTotalFiles() : 0;
response = new GlobalSearchStatusResponse();
response.setTaskId(taskId);
response.setStatus(task.getStatus());
response.setProgress(progress);
} catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
return ResponseEntity.ok(response);
}
}
@@ -1,8 +1,12 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController @RestController
@RequestMapping("check/api") @RequestMapping("check/api")
public class HealtCheckController { public class HealtCheckController {
@@ -11,4 +15,38 @@ public class HealtCheckController {
public HttpStatus healtCheck() { public HttpStatus healtCheck() {
return HttpStatus.OK; 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();
long mb = 1024 * 1024;
Map<String, String> info = new HashMap<>();
info.put("maxMemory (MB)", String.valueOf(runtime.maxMemory() / mb));
info.put("totalMemory (MB)", String.valueOf(runtime.totalMemory() / mb));
info.put("freeMemory (MB)", String.valueOf(runtime.freeMemory() / mb));
info.put("usedMemory (MB)", String.valueOf((runtime.totalMemory() - runtime.freeMemory()) / mb));
info.put("availableProcessors", String.valueOf(runtime.availableProcessors()));
info.put("JAVA_TOOL_OPTIONS", System.getenv("JAVA_TOOL_OPTIONS"));
info.put("JAVA_OPTS", System.getenv("JAVA_OPTS"));
return info;
}
static class BuildInfo {
public String buildTimeBack;
public BuildInfo(String buildTimeBack) {
this.buildTimeBack = buildTimeBack;
}
}
} }
@@ -0,0 +1,119 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.search.GlobalSearchStartResponse;
import ru.soune.nocopy.entity.payment.Payment;
import ru.soune.nocopy.exception.PaymentNotFoundException;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.service.payment.PaymentService;
import ru.soune.nocopy.service.register.AuthService;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/payments")
@RequiredArgsConstructor
@Slf4j
public class PaymentController {
private final PaymentService paymentService;
private final AuthService authService;
@PostMapping("/create")
public ResponseEntity<?> createPayment(@RequestParam String email, @RequestParam Long tariffId,
@RequestParam String operationType, @RequestParam String operationUuid) {
try {
Payment payment = paymentService.createPayment(email, tariffId, operationType, operationUuid);
return ResponseEntity.ok(payment);
} catch (TariffNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "Tariff not found", "message", e.getMessage()));
} catch (UserNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "User not found", "message", e.getMessage()));
} catch (Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Internal server error", "message", e.getMessage()));
}
}
@PostMapping("webhook/yookassa")
public void handleYooKassaNotification(@RequestBody Map<String, Object> notification) {
try {
paymentService.handlePaymentNotification(notification);
log.info("Notification from yookassa: " + notification);
} catch (PaymentNotFoundException e) {
log.error("Error processing payment notification", e);
}
}
@PostMapping("/auto-renewal")
public ResponseEntity<?> disableAutoRenewal(@RequestParam(value = "renewal") Boolean renewal,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
paymentService.changeAutoRenewal(authService.useUserAuthToken(tokenHeader), renewal);
return ResponseEntity.ok(Map.of("message", "Auto-renewal disabled"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
}
}
@DeleteMapping("/methods/{paymentMethodId}")
public ResponseEntity<?> deletePaymentMethod(@RequestHeader(value = "Authorization", required = false) String tokenHeader,
@PathVariable String paymentMethodId) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
paymentService.deletePaymentMethod(authService.useUserAuthToken(tokenHeader), paymentMethodId);
return ResponseEntity.ok(Map.of("message", "Payment method deleted"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/methods")
public ResponseEntity<?> getPaymentMethods(@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
return ResponseEntity.ok(paymentService.getUserPaymentMethods(
authService.useUserAuthToken(tokenHeader)));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
}
}
}
@@ -0,0 +1,209 @@
package ru.soune.nocopy.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.payout.*;
import ru.soune.nocopy.entity.payout.BankTransferPayoutMethod;
import ru.soune.nocopy.entity.payout.CardPayoutMethod;
import ru.soune.nocopy.entity.payout.PayoutRequest;
import ru.soune.nocopy.entity.payout.PayoutType;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.service.payout.PayoutMethodService;
import ru.soune.nocopy.service.payout.PayoutRequestService;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/payouts")
@RequiredArgsConstructor
@Slf4j
public class PayoutController {
private final PayoutRequestService payoutRequestService;
private final PayoutMethodService payoutMethodService;
private final AuthTokenRepository authTokenRepository;
/*
Create payout
*/
@PostMapping("/create-request")
public ResponseEntity<PayoutRequest> createPayoutRequest(@RequestHeader("Authorization") String tokenHeader,
@Valid @RequestBody PayoutRequestDTO request) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
PayoutRequest payoutRequest = payoutRequestService.createPayoutRequest(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED).body(payoutRequest);
}
/*
Completed payout
*/
@PostMapping("/requests/{requestId}/process")
public ResponseEntity<PayoutRequest> processPayoutRequest(@PathVariable Long requestId) {
PayoutRequest processed = payoutRequestService.processRequest(requestId);
return ResponseEntity.ok(processed);
}
/*
Cancel payout
*/
@PostMapping("/requests/{requestId}/cancel")
public ResponseEntity<PayoutRequest> cancelPayoutRequest(@PathVariable Long requestId,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
PayoutRequest cancelled = payoutRequestService.cancelPayoutRequest(user.getId(), requestId);
return ResponseEntity.ok(cancelled);
}
/*
Get user payouts
*/
@GetMapping("/requests")
public ResponseEntity<List<PayoutRequest>> getUserRequests(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
return ResponseEntity.ok(payoutRequestService.getUserPayoutRequests(user.getId()));
}
/*
Get payout by id
*/
@GetMapping("/requests/{requestId}")
public ResponseEntity<PayoutRequest> getPayoutRequest(@PathVariable Long requestId,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
return ResponseEntity.ok(payoutRequestService.getPayoutRequest(user.getId(), requestId));
}
/*
Get payout by id
*/
@GetMapping("user/payout-methods")
public ResponseEntity<List<PayoutMethodDTO>> getUserMethods(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
return ResponseEntity.ok(payoutMethodService.getUserMethods(user.getId()));
}
/*
Get payout-methods
*/
@GetMapping("/payout-methods")
public ResponseEntity<List<PayoutMethodTypeDTO>> getAvailableMethodTypes() {
List<PayoutMethodTypeDTO> types = Arrays.stream(PayoutType.values())
.map(type -> PayoutMethodTypeDTO.builder()
.code(type.name())
.name(type.getDisplayName())
.build())
.collect(Collectors.toList());
return ResponseEntity.ok(types);
}
/*
add card method by userId
*/
@PostMapping("/payout-method/card")
public ResponseEntity<PayoutMethodDTO> addCardMethod(@Valid @RequestBody AddCardRequest request,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
CardPayoutMethod method = payoutMethodService.addCardMethod(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(payoutMethodService.convertToDto(method));
}
/*
add bank payout-method
*/
@PostMapping("/payout-method/bank-transfer")
public ResponseEntity<PayoutMethodDTO> addBankTransferMethod(@Valid @RequestBody AddBankTransferRequest request,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
BankTransferPayoutMethod method = payoutMethodService.addBankTransferMethod(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(payoutMethodService.convertToDto(method));
}
/*
set default bank payout-method
*/
@PutMapping("/payout-method/{methodId}/default")
public ResponseEntity<Void> setDefaultMethod(@PathVariable Long methodId,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
payoutMethodService.setDefaultMethod(user.getId(), methodId);
return ResponseEntity.ok().build();
}
}
@@ -0,0 +1,58 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.configuration.search.SearchProperties;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.search.config.SearchSettingsDto;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/search/settings")
@RequiredArgsConstructor
public class SearchSettingsController {
private final SearchProperties searchProperties;
@GetMapping
public ResponseEntity<?> getSettings() {
SearchSettingsDto dto = new SearchSettingsDto();
dto.setEngines(Map.of(
"yandex", searchProperties.getEngines().get("yandex").isEnabled(),
"google", searchProperties.getEngines().get("google").isEnabled()));
dto.setProxyEnabled(searchProperties.getProxy().isEnabled());
return ResponseEntity.ok().body(BaseResponse.builder()
.messageDesc("Success")
.messageBody(dto)
.build());
}
@PutMapping
public ResponseEntity<?> updateSettings(@RequestBody SearchSettingsDto settings) {
if (settings.getEngines() != null) {
settings.getEngines().forEach((key, value) -> {
if (searchProperties.getEngines().containsKey(key)) {
searchProperties.getEngines().get(key).setEnabled(value);
}
});
}
if (settings.getProxyEnabled() != null) {
searchProperties.getProxy().setEnabled(settings.getProxyEnabled());
}
log.info("Settings updated: {}", settings);
return ResponseEntity.ok().body(BaseResponse.builder()
.messageDesc("Settings updated")
.build());
}
}
@@ -1,11 +0,0 @@
package ru.soune.nocopy.controller;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("v1/api/content")
@AllArgsConstructor
public class UserContentController {
}
@@ -2,20 +2,34 @@ package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.ChangePasswordRequest; import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.UserDTO; import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.UserRequest; import ru.soune.nocopy.dto.register.ChangePasswordRequest;
import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.User; 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; import ru.soune.nocopy.exception.InvalidUserEmail;
import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.mapper.UserMapper; import ru.soune.nocopy.mapper.UserMapper;
import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.UserService; 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;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
@RestController @RestController
@@ -31,12 +45,21 @@ public class UserController {
private final UserMapper userMapper; private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final AuthService authService;
private final TariffService tariffService;
private final FileStatsService fileStatsService;
@GetMapping("/all") @GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() { public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream() List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.getIsActive(), .map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(), u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType())) u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
null, null))
.toList(); .toList();
return ResponseEntity.ok(allUsers); return ResponseEntity.ok(allUsers);
@@ -49,26 +72,69 @@ public class UserController {
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token); Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); if (tokenOptional.isPresent()) {
User user = userRepository.findByEmail(email);
if (authToken != null) { if (user != null) {
Optional<User> user = userRepository.findByEmail(email); UserDTO userDTO = userMapper.toDTO(user);
//TODO add mapper
if (user.isPresent()) {
UserDTO userDTO = userMapper.toDTO(user.get());
userDTO.setEmail(email); userDTO.setEmail(email);
userDTO.setFullName(user.get().getFullName()); userDTO.setFullName(user.getFullName());
userDTO.setCompany(user.get().getCompany());
userDTO.setPhone(user.get().getPhone()); if (user.getCompany() != null) {
userDTO.setGenderType(user.get().getGenderType()); userDTO.setCompany(user.getCompany().getCompanyName());
userDTO.setBirthday(user.get().getBirthday()); }
userDTO.setCreatedAt(user.get().getCreatedAt());
userDTO.setSubscriptionType(user.get().getSubscriptionType()); if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
userDTO.setActive(user.get().getIsActive()); 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); return ResponseEntity.ok(userDTO);
} }
} else {
return ResponseEntity.notFound().build();
} }
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
@@ -108,4 +174,46 @@ public class UserController {
return ResponseEntity.ok(userService.updateUser(userRequest, user)); return ResponseEntity.ok(userService.updateUser(userRequest, user));
} }
@PostMapping("/create-user")
public ResponseEntity<User> addUser(@RequestBody RegRequest registerRequest) {
if (userRepository.existsByEmail(registerRequest.getEmail()) ||
userRepository.existsByPhone(registerRequest.getPhone())) {
RegAnswer regAnswer = new RegAnswer();
regAnswer.setFieldErrors(Arrays.asList(Map.of("email", registerRequest.getEmail())));
regAnswer.setFieldErrors(Arrays.asList(Map.of("phone", registerRequest.getPhone())));
throw new NotValidFieldException("User already exists with email:" + registerRequest.getEmail() + " or phone: " +
registerRequest.getPhone(), new BaseResponse(2,
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getCode(),
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
}
User user = new User();
user.setFullName(registerRequest.getFullName());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setActive(true);
user.setEmailVerified(true);
if (registerRequest.getPhone() != null) {
user.setPhone(registerRequest.getPhone());
}
User savedUser = userRepository.save(user);
AuthToken authToken = authService.generateAuthToken(savedUser);
authTokenRepository.save(authToken);
return ResponseEntity.ok().body(savedUser);
}
@DeleteMapping("/delete-user/{userId}")
public ResponseEntity<?> addUser(@PathVariable Long userId) {
userRepository.deleteById(userId);
return ResponseEntity.ok().body(Map.of("userId", userId, "deleted", "true"));
}
} }
@@ -1,10 +0,0 @@
package ru.soune.nocopy.dto;
import java.time.LocalDateTime;
public record AuthResponse (
boolean success,
String message,
String token,
LocalDateTime expiresAt
) {}
@@ -1,9 +1,11 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
@Data @Data
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseRequest { public class BaseRequest {
@JsonProperty("version") @JsonProperty("version")
Integer version; Integer version;
@@ -1,11 +1,15 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@Builder
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseResponse { public class BaseResponse {
@JsonProperty("msg_id") @JsonProperty("msg_id")
private Integer msgId; private Integer msgId;
@@ -4,12 +4,23 @@ public enum MessageCode {
SUCCESS(0, "Operation successful"), SUCCESS(0, "Operation successful"),
REG_EMAIL_EXISTS(1, "Email already registered"), REG_EMAIL_EXISTS(1, "Email already registered"),
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone 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_FIELD(2, "Invalid field"),
INVALID_TOKEN(2, "Invalid token"), MAIL_VERIFIED_NULL(2, "Mail verified null"),
INVALID_TOKEN(2, "Token not found or time expired"),
TOKEN_IS_ALIVE(2, "Token is alive"),
INVALID_ACTION(2, "Invalid action"), INVALID_ACTION(2, "Invalid action"),
FILE_UPLOAD_ERROR(2, "File upload error"), FILE_UPLOAD_ERROR(2, "File upload error"),
DUPLICATE_FILE_UPLOAD(2, "Duplicate file upload"),
FILE_DOWNLOAD_ERROR(2, "File download error"), FILE_DOWNLOAD_ERROR(2, "File download error"),
FILE_NOT_EXIST(2, "File not exist on disk"),
FILE_DELETE(2, "File was deleted"),
USER_NOT_HAD_PERMISSION(2, "User not have permission for file"),
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"), FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
TOKEN_IS_NULL(2, "Token field is null"),
IMAGE_FOUND_ERROR(2, "Image found error"), IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
INCOMPLETE_UPLOAD(2, "Not load all chunks"), INCOMPLETE_UPLOAD(2, "Not load all chunks"),
@@ -19,8 +30,32 @@ public enum MessageCode {
AUTH_EMAIL_NOT_FOUND(4, "Email not found"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "), AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
AUTH_TOKEN_MISMATCH(4, "Token mismatch"), AUTH_TOKEN_MISMATCH(4, "Token mismatch"),
AUTH_TOKEN_NOT_FOUND(4, "Token not found"),
FILE_NOT_FOUND(4, "File not found"), FILE_NOT_FOUND(4, "File not found"),
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"); AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"),
SEND_EMAIL_EXCEPTION(2, "Send email exception"),
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"),
PAYMENT_NOT_FOUND(4, "Payment not found"),
COMPANY_ALREADY_EXISTS(2, "Company already exists"),
USER_LIMIT_IS_OVER(2, "Over user limits"),
MONITORING_TYPE_NOT_FOUND(4, "Monitoring type not found"),
ERROR_TARIFF_INFO(2, "Erorr with tariff info"),
FILE_FOR_SEARCH_NOT_VALID(2, "File for search unsupported"),
NOT_VALID_FILE_TYPE_OR_COUNT_FILE(2, "Cost for file type not found, count is negative or files count" +
"more than max valid"),
USER_NOT_ACTIVE(2, "User not active"),
NOTION_NOT_FOUND(4, "Notion not found"),
MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion");
private final Integer code; private final Integer code;
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class TokenProcessRequest {
@JsonProperty("token")
private String token;
}
@@ -1,14 +0,0 @@
package ru.soune.nocopy.dto;
import lombok.Data;
@Data
public class UserContentDTO {
String fileName;
String originalFilename;
String fileType;
String fileExtension;
Long fileSize;
String filePath;
Integer protectionLevel;
}
@@ -1,17 +0,0 @@
package ru.soune.nocopy.dto;
import ru.soune.nocopy.entity.ContentStatus;
import ru.soune.nocopy.entity.file.FileType;
import java.time.LocalDateTime;
public record UserContentResponse(
Long id,
String filename,
String originalFilename,
FileType fileType,
long fileSize,
String filePath,
ContentStatus status,
LocalDateTime uploadDate
) {}
@@ -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,18 @@
package ru.soune.nocopy.dto.cost;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class CostRequest {
String action;
@JsonProperty("file_type")
String fileType;
@JsonProperty("count_files")
Integer countFilesForProtect;
@JsonProperty("auth_token")
String authToken;
}
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.cost;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class CostResponse {
private int cost;
private Boolean success;
@JsonProperty("max_files_for_check")
private Integer maxFilesForCheck;
@JsonProperty("count_file")
private Integer countFile;
@JsonProperty("tokens_count")
private int needTokensCount;
}
@@ -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;
}
@@ -20,6 +20,9 @@ public class ChunkUploadResponse {
@JsonProperty("chunk_size") @JsonProperty("chunk_size")
private Long chunkSize; private Long chunkSize;
@JsonProperty("file_id")
private String fileId;
@JsonProperty("message") @JsonProperty("message")
private String message; private String message;
} }
@@ -1,41 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileApiResponse<T> {
private boolean success;
private String message;
private T data;
private String error;
public static <T> FileApiResponse<T> success(T data) {
return FileApiResponse.<T>builder()
.success(true)
.message("Operation completed successfully")
.data(data)
.build();
}
public static <T> FileApiResponse<T> success(String message, T data) {
return FileApiResponse.<T>builder()
.success(true)
.message(message)
.data(data)
.build();
}
public static <T> FileApiResponse<T> error(String error) {
return FileApiResponse.<T>builder()
.success(false)
.error(error)
.build();
}
}
@@ -17,6 +17,9 @@ public class FileEntityRequest {
@JsonProperty("file_id") @JsonProperty("file_id")
private String fileId; private String fileId;
@JsonProperty("full_delete")
private Integer fullDelete;
@JsonProperty("upload_session_id") @JsonProperty("upload_session_id")
private String uploadSessionId; private String uploadSessionId;
@@ -31,4 +34,16 @@ public class FileEntityRequest {
@JsonProperty("token") @JsonProperty("token")
private String token; private String token;
@JsonProperty("sort_by")
private String sortBy;
@JsonProperty("sort_order")
private String sortOrder;
@JsonProperty("type")
private String type;
@JsonProperty("date_filter")
private String dateFilter;
} }
@@ -19,6 +19,8 @@ public class FileEntityResponse {
private String originalFileName; private String originalFileName;
private String storedFileName; private String storedFileName;
private String filePath; private String filePath;
private String protectedFilePath;
private String thumbnailFileUrl;
private Long fileSize; private Long fileSize;
private String mimeType; private String mimeType;
private String fileExtension; private String fileExtension;
@@ -30,4 +32,14 @@ public class FileEntityResponse {
private String formattedSize; private String formattedSize;
private String downloadUrl; private String downloadUrl;
private boolean existsOnDisk; 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;
private String monitoring;
} }
@@ -58,4 +58,31 @@ public class FileInfoUserResponse {
@JsonProperty("audios_violations") @JsonProperty("audios_violations")
private Integer audiosViolations; 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;
@JsonProperty("protected_audio_files_count")
private Long protectedAudioFilesCount;
@JsonProperty("protected_video_files_count")
private Long protectedVideoFilesCount;
@JsonProperty("protected_image_files_count")
private Long protectedImageFilesCount;
} }
@@ -30,4 +30,10 @@ public class FileListResponse {
@JsonProperty("page_size") @JsonProperty("page_size")
private Integer pageSize; private Integer pageSize;
@JsonProperty("sort_by")
private String sortBy;
@JsonProperty("sort_order")
private String sortOrder;
} }
@@ -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;
}
@@ -28,4 +28,7 @@ public class FileUploadRequest {
@JsonProperty("token") @JsonProperty("token")
private String token; private String token;
@JsonProperty("convertTo")
private String convertTo;
} }
@@ -0,0 +1,68 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class GoogleVisionSearchResponse {
@JsonProperty("bestGuessLabels")
private List<BestGuessLabel> bestGuessLabels;
@JsonProperty("fullMatchingImages")
private List<ImageResult> fullMatchingImages;
@JsonProperty("visuallySimilarImages")
private List<ImageResult> visuallySimilarImages;
@JsonProperty("pagesWithMatchingImages")
private List<PageResult> pagesWithMatchingImages;
@JsonProperty("partialMatchingImages")
private List<ImageResult> partialMatchingImages;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BestGuessLabel {
@JsonProperty("label")
private String label;
@JsonProperty("languageCode")
private String languageCode;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult {
@JsonProperty("url")
private String url;
@JsonProperty("score")
private Float score;
@JsonProperty("height")
private Integer height;
@JsonProperty("width")
private Integer width;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PageResult {
@JsonProperty("url")
private String url;
@JsonProperty("pageTitle")
private String pageTitle;
@JsonProperty("fullMatchingImages")
private List<ImageResult> fullMatchingImages;
@JsonProperty("partialMatchingImages")
private List<ImageResult> partialMatchingImages;
}
}
@@ -7,4 +7,10 @@ import lombok.Data;
public class ImageSearchRequest { public class ImageSearchRequest {
@JsonProperty("file_id") @JsonProperty("file_id")
private String fileId; 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;
}
@@ -0,0 +1,23 @@
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
public class SimilarFileDTO {
String fileId;
String originalFileName;
Long fileSize;
Integer hammingDistance;
String similarityLevel;
Long ownerId;
Long supportId;
ProtectionStatus status;
LocalDateTime uploadDate;
String url;
Boolean owner;
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SimilarityFilter {
private List<String> similarityLevels;
private Integer page;
private Integer size;
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.dto.file;
import lombok.Getter;
@Getter
public enum SortOrder {
ASC("asc"),
DESC("desc");
private final String value;
SortOrder(String value) {
this.value = value;
}
public static SortOrder fromString(String value) {
if (value == null) return ASC;
for (SortOrder order : SortOrder.values()) {
if (order.value.equalsIgnoreCase(value)) {
return order;
}
}
return ASC;
}
}
@@ -33,4 +33,12 @@ public class YandexSearchResponse {
@JsonProperty("host") @JsonProperty("host")
private String host; private String host;
} }
private int page;
private int pageSize;
private int totalResults;
private int totalPages;
} }
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class GetViolationsRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("page")
private Integer page;
@JsonProperty("page_size")
private Integer pageSize;
@JsonProperty("mark_as_read")
private boolean markAsRead;
@JsonProperty("auth_token")
private String authToken;
}
@@ -0,0 +1,40 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
import java.time.LocalDateTime;
/**
* Response for status monitoring
*/
@Data
@Builder
public class MonitoringStatusResponse {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("file_name")
private String fileName;
@JsonProperty("monitoring_type")
private MonitoringType monitoringType;
@JsonProperty("next_run")
private LocalDateTime nextRun;
@JsonProperty("last_run")
private LocalDateTime lastRun;
@JsonProperty("last_run_status")
private String lastRunStatus;
@JsonProperty("is_active")
private boolean isActive;
@JsonProperty("need_add_tokens")
private boolean needAddTokens;
}
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.monitoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.monitoring.MonitoringType;
/**
Set monitoring request
*/
@Data
public class SetMonitoringRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("monitoring_type")
private String monitoringType;
@JsonProperty("auth_token")
private String authToken;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.payment;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PaymentMethodDTO {
private String paymentMethodId;
private String lastFour;
private String cardType;
private String expiryMonth;
private String expiryYear;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.payment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentRequest {
private String email;
private String paymentUuid;
private String action;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.payment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.payment.Payment;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PaymentResponse {
List<Payment> payments;
}
@@ -0,0 +1,44 @@
package ru.soune.nocopy.dto.payout;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddBankTransferRequest {
@NotBlank(message = "Bank name is required")
@Size(min = 2, max = 200, message = "Bank name must be between 2 and 200 characters")
private String bankName;
@NotBlank(message = "BIC is required")
@Pattern(regexp = "^[0-9]{9}$", message = "BIC must contain 9 digits")
private String bic;
@Pattern(regexp = "^[0-9]{20}$", message = "Correspondent account must contain 20 digits")
private String correspondentAccount;
@NotBlank(message = "Account number is required")
@Pattern(regexp = "^[0-9]{20}$", message = "Account number must contain 20 digits")
private String accountNumber;
@NotBlank(message = "Account holder name is required")
@Size(min = 3, max = 200, message = "Account holder name must be between 3 and 200 characters")
private String accountHolder;
@Pattern(regexp = "^[0-9]{10}$|^[0-9]{12}$", message = "INN must contain 10 or 12 digits")
private String inn;
@Pattern(regexp = "^[0-9]{9}$", message = "KPP must contain 9 digits")
private String kpp;
@Builder.Default
private boolean saveAsDefault = false;
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.dto.payout;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddCardRequest {
@NotBlank(message = "Card number required")
@Pattern(regexp = "^[0-9]{16}$", message = "Card number 16 cipher")
private String cardNumber;
}
@@ -0,0 +1,21 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethodDTO {
private Long id;
private String type;
private String displayName;
private String maskedDetails;
private String cardNumber;
private boolean isDefault;
private boolean isVerified;
}
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethodResponse {
private Long id;
private String type;
private String displayName;
private String maskedDetails;
private boolean isDefault;
private boolean isVerified;
private LocalDateTime createdAt;
private String bankName;
private String cardBrand;
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethodTypeDTO {
private String code;
private String name;
}
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.payout;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutRequestDTO {
@NotNull(message = "Sum of pay is required")
@DecimalMin(value = "1000.00", message = "Min sum for pay 1000")
private BigDecimal amount;
@NotNull(message = "Способ выплаты обязателен")
private Long payoutMethodId;
}
@@ -0,0 +1,54 @@
package ru.soune.nocopy.dto.payout;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.payout.PayoutRequest;
import ru.soune.nocopy.entity.payout.PayoutRequestStatus;
import ru.soune.nocopy.entity.payout.PayoutType;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayoutResponse {
private Long id;
private BigDecimal amount;
private String formattedAmount;
private PayoutRequestStatus status;
private PayoutType payoutType;
private String payoutMethodDisplay;
private LocalDateTime createdAt;
private String formattedCreatedAt;
private LocalDateTime processedAt;
private LocalDateTime completedAt;
private String transactionId;
private String rejectionReason;
private boolean canCancel;
public static PayoutResponse fromEntity(PayoutRequest request) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
return PayoutResponse.builder()
.id(request.getId())
.amount(request.getAmount())
.formattedAmount(String.format("%.2f ₽", request.getAmount()))
.status(request.getStatus())
.payoutType(request.getPayoutType())
.payoutMethodDisplay(request.getPayoutMethod() != null ?
request.getPayoutMethod().getDisplayName() : null)
.createdAt(request.getCreatedAt())
.formattedCreatedAt(request.getCreatedAt() != null ?
request.getCreatedAt().format(formatter) : null)
.processedAt(request.getProcessAt())
.completedAt(request.getCompletedAt())
.rejectionReason(request.getRejectionReason())
.canCancel(request.getStatus() == PayoutRequestStatus.PENDING)
.build();
}
}
@@ -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
}
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;

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