217 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
182 changed files with 8268 additions and 688 deletions
+339
View File
@@ -151,3 +151,342 @@ INSERT INTO referral_levels (id, name, min_invitees, reward_percentage) VALUES
('silver', 'SILVER', 6, 18), ('silver', 'SILVER', 6, 18),
('gold', 'GOLD', 16, 22), ('gold', 'GOLD', 16, 22),
('platinum', 'PLATINUM', 50, 25); ('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 # Проверка порта
+1 -1
View File
@@ -59,7 +59,7 @@ dependencies {
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.0-all' implementation name: 'testlib-fat-0.3.1-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0' implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
+85 -10
View File
@@ -9,14 +9,29 @@ pipeline {
) )
string( string(
name: 'PORT', name: 'PORT',
defaultValue: '3000', defaultValue: '3001',
description: 'Порт для запуска экземпляра' description: 'Порт для запуска экземпляра'
) )
string(
name: 'PORT_DB',
defaultValue: '',
description: 'Порт для запуска БД'
)
string( string(
name: 'SERVER', name: 'SERVER',
defaultValue: '92.242.61.23', defaultValue: '92.242.61.23',
description: 'Сервер для деплоя' description: 'Сервер для деплоя'
) )
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Имя сети (будет создана если не существует)'
)
choice(
name: 'SPRING_PROFILE',
choices: ['dev', 'prod'],
description: 'Профиль Spring для запуска'
)
} }
stages { stages {
@@ -36,10 +51,34 @@ pipeline {
} }
} }
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
rm -rf /opt/deployments/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
"
sshpass -p '${SSH_PASS}' scp -r ./* ${SSH_USER}@${params.SERVER}:/opt/deployments/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Deploy with docker-compose') { stage('Deploy with docker-compose') {
steps { steps {
script { script {
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim() def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
def springProfile = params.SPRING_PROFILE
withCredentials([ withCredentials([
usernamePassword( usernamePassword(
credentialsId: 'server-root-password', credentialsId: 'server-root-password',
@@ -61,10 +100,16 @@ pipeline {
# Основная команда # Основная команда
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} " sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
cd /opt/deployments/${params.BRANCH}-${params.PORT} cd /opt/deployments/${params.BRANCH}-${params.PORT}
echo '1. Создаем изолированную сеть для порта ${params.PORT}...' echo '1. Проверяем/создаем сеть ${params.NETWORK}...'
docker network create app-network-${params.PORT} 2>/dev/null || echo 'Сеть уже существует' 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}...' echo '2. Останавливаем старые контейнеры порта ${params.PORT}...'
docker stop app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден' docker stop app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
@@ -77,12 +122,12 @@ pipeline {
echo '3. Запускаем БД...' echo '3. Запускаем БД...'
docker run -d \\ docker run -d \\
--name postgres-${params.PORT} \\ --name postgres-${params.PORT} \\
--network app-network-${params.PORT} \\ --network ${params.NETWORK} \\
--network-alias no_copy_${params.PORT} \\ --network-alias no_copy_${params.PORT} \\
--network-alias database-${params.PORT} \\ --network-alias database-${params.PORT} \\
--network-alias db-${params.PORT} \\ --network-alias db-${params.PORT} \\
-p ${params.PORT}0:5432 \\ -p ${params.PORT}0:5432 \\
-e POSTGRES_DB=no_copy_${params.PORT} \\ -e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_USER=${DB_USER} \\ -e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\ -e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-v pgdata_${params.PORT}:/var/lib/postgresql/data \\ -v pgdata_${params.PORT}:/var/lib/postgresql/data \\
@@ -92,7 +137,7 @@ pipeline {
echo '4. Запускаем storage...' echo '4. Запускаем storage...'
docker run -d \\ docker run -d \\
--name file-storage-${params.PORT} \\ --name file-storage-${params.PORT} \\
--network app-network-${params.PORT} \\ --network ${params.NETWORK} \\
-v uploads_data_${params.PORT}:/storage:rw \\ -v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null alpine:latest tail -f /dev/null
@@ -105,15 +150,18 @@ pipeline {
echo '7. Запускаем приложение...' echo '7. Запускаем приложение...'
docker run -d \\ docker run -d \\
--name app-backend-${params.PORT} \\ --name app-backend-${params.PORT} \\
--network app-network-${params.PORT} \\ --network ${params.NETWORK} \\
--network-alias app \\
--network-alias app-${params.PORT} \\
-p ${params.PORT}:8080 \\ -p ${params.PORT}:8080 \\
-v uploads_data_${params.PORT}:/data/uploads:rw \\ -v uploads_data_${params.PORT}:/data/uploads:rw \\
-e POSTGRES_DB=no_copy_${params.PORT} \\ -e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_USER=${DB_USER} \\ -e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\ -e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-e POSTGRES_PORT=5432 \\ -e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=no_copy_${params.PORT} \\ -e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\ -e FILE_STORAGE_PATH=/data/uploads \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\
app-backend-${params.PORT}:latest app-backend-${params.PORT}:latest
echo '8. Проверка...' echo '8. Проверка...'
@@ -131,12 +179,39 @@ pipeline {
} }
} }
} }
stage('Verify network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
echo 'Проверяем подключение к сети ${params.NETWORK}'
docker network inspect ${params.NETWORK} --format='{{.Name}}: {{range .Containers}}{{.Name}} {{end}}'
echo 'Проверяем алиас app для контейнера app-backend-${params.PORT}'
docker inspect app-backend-${params.PORT} | grep -A 5 'Aliases'
"
"""
}
}
}
}
} }
post { post {
success { success {
script {
def appUrl = params.SPRING_PROFILE == 'prod' ? 'https://workspace.not-copy.com' : 'https://dev-workspace.not-copy.com'
echo "Deployment successful" echo "Deployment successful"
echo "Application URL: http://${params.SERVER}:${params.PORT}" echo "Application URL: ${appUrl}"
}
} }
failure { failure {
echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}" echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}"
@@ -12,6 +12,11 @@ pipeline {
defaultValue: '2998', defaultValue: '2998',
description: 'Порт для запуска экземпляра' description: 'Порт для запуска экземпляра'
) )
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Docker сеть для подключения'
)
} }
stages { stages {
@@ -189,6 +194,70 @@ pipeline {
} }
} }
} }
// stage('Connect to network') {
// steps {
// script {
// withCredentials([
// usernamePassword(
// credentialsId: 'server-root-password',
// usernameVariable: 'SSH_USER',
// passwordVariable: 'SSH_PASS'
// )
// ]) {
// sh """
// sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
// echo 'Отключаем от всех сетей и подключаем к ${params.NETWORK}'
//
// # Отключаем от всех сетей (кроме bridge, host, none)
// current_networks=\$(docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}' | grep -v -E '^(bridge|host|none)\$' || true)
//
// for net in \$current_networks; do
// echo \"Отключаем от сети: \$net\"
// docker network disconnect \$net no-copy-frontend-${params.PORT} 2>/dev/null || true
// done
//
// # Подключаем к нужной сети
// docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
//
// echo 'Готово! Контейнер теперь в сети:'
// docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}'
// "
// """
// }
// }
// }
// }
stage('Connect to network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
echo 'Подключаем контейнер к сети ${params.NETWORK}'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
fi
if docker ps -a --format '{{.Names}}' | grep -q '^no-copy-frontend-${params.PORT}\$'; then
docker network disconnect ${params.NETWORK} no-copy-frontend-${params.PORT} 2>/dev/null || true
docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
echo 'Контейнер подключен к ${params.NETWORK}'
fi
"
"""
}
}
}
}
} }
post { post {
+1 -1
View File
@@ -34,7 +34,7 @@ pipeline {
stage('Deploy with docker-compose') { stage('Deploy with docker-compose') {
steps { steps {
script { script {
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim() 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',
@@ -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;
}
}
}
@@ -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);
}
}
@@ -24,7 +24,15 @@ public class HandlerConfig {
TariffHandler tariffHandler, TariffHandler tariffHandler,
TariffInfoHandler tariffInfoHandler, TariffInfoHandler tariffInfoHandler,
ReferralHandler referralHandler, ReferralHandler referralHandler,
DaDataHandler daDataHandler 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);
@@ -35,11 +43,19 @@ public class HandlerConfig {
map.put(20007, imageFoundRequestHandler); map.put(20007, imageFoundRequestHandler);
map.put(20008, authRequestHandler); map.put(20008, authRequestHandler);
map.put(20009, verifyRegisterUser); map.put(20009, verifyRegisterUser);
map.put(20010, resetPasswordHandler);
map.put(30000, companyHandler); map.put(30000, companyHandler);
map.put(30001, tariffHandler); map.put(30001, tariffHandler);
map.put(30002, tariffInfoHandler); map.put(30002, tariffInfoHandler);
map.put(30003, referralHandler); map.put(30003, referralHandler);
map.put(30004, daDataHandler); 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());
}
}
}
}
@@ -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;
}
} }
@@ -1,25 +0,0 @@
package ru.soune.nocopy.configuration.file;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@Configuration
@ConfigurationProperties(prefix = "file.storage")
@Data
public class FileStorageConfig {
private String basePath;
private long maxFileSize;
private int chunkSize;
private int authTokenLifeHours;
@PostConstruct
public void init() throws IOException {
Files.createDirectories(Paths.get(basePath));
}
}
@@ -4,16 +4,12 @@ import lombok.AllArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ru.soune.nocopy.service.file.FileProcessingOrchestrator;
@Component @Component
@AllArgsConstructor @AllArgsConstructor
public class NoCopyInitializer { public class NoCopyInitializer {
private final FileProcessingOrchestrator orchestrator;
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void initializeOnStartup() { public void initializeOnStartup() {
orchestrator.initializeProcessingQueue();
} }
} }
@@ -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;
}
}
@@ -3,6 +3,8 @@ package ru.soune.nocopy.controller;
import com.vrt.NoCopyFileService; import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector; import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult; import com.vrt.fileprotection.NoCopyCheckResult;
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.FileSystemResource; import org.springframework.core.io.FileSystemResource;
@@ -21,16 +23,19 @@ import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.*; import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.dto.register.RegAnswer; import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.entity.user.AuthToken; import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.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.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService; 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.file.ProtectionsLimitService;
import ru.soune.nocopy.service.register.AuthService; import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
@@ -42,10 +47,7 @@ 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.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -65,14 +67,16 @@ public class ApiController {
private final FileEntityRepository fileEntityRepository; private final FileEntityRepository fileEntityRepository;
private final NoCopyFileService noCopyFileService;
private final FileUtil fileUtil; private final FileUtil fileUtil;
private final ProtectionsLimitService protectionsLimitService; 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) {
@@ -153,46 +157,124 @@ public class ApiController {
MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(), MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(),
duplicateData duplicateData
)); ));
}catch (FileFormatException e){
return buildErrorResponse(uploadId, chunkNumber, e.getMessage());
} catch (Exception e) { } catch (Exception e) {
log.error("Error uploading chunk", e); log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage()); return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
} }
} }
private final NoCopyFileService noCopyFileService;
@GetMapping("/v{version}/files/{fileId}/similar") @GetMapping("/v{version}/files/{fileId}/similar")
public ResponseEntity<BaseResponse> findSimilarFiles( public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version, @PathVariable("version") int version,
@PathVariable String fileId, @PathVariable String fileId,
@RequestParam(value = "auth_token",required = false) String authToken, @RequestParam(value = "auth_token", required = false) String authToken,
@RequestParam(required = false) List<String> similarityLevels, @RequestParam(required = false) List<String> similarityLevels,
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) { @PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
SimilarityFilter filter = SimilarityFilter.builder()
.similarityLevels(similarityLevels)
.build();
Page<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable, authToken);
String messageDesc; try {
MessageCode success; FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
if (similarFiles.isEmpty()) { if (fileEntity == null) {
messageDesc = MessageCode.FILE_NOT_FOUND.getDescription(); return ResponseEntity.ok().body(new BaseResponse(0, MessageCode.INVALID_FIELD.getCode(),
success = MessageCode.SUCCESS; MessageCode.INVALID_FIELD.getDescription(), null));
} else {
messageDesc = MessageCode.SIMILAR_FILES_FOUND.getDescription();
success = MessageCode.SUCCESS;
} }
Map<String, Object> responseData = new HashMap<>(); BaseResponse response;
responseData.put("content", similarFiles.getContent()); if ("image".equals(fileEntity.getMimeType())) {
responseData.put("page", similarFiles.getNumber()); Page<SimilarFileDTO> similarFilesPage = fileSimilarityService.findSimilarFiles(
responseData.put("size", similarFiles.getSize()); fileId, similarityLevels, pageable, authToken);
responseData.put("totalElements", similarFiles.getTotalElements()); response = buildSuccessResponse(similarFilesPage, MessageCode.SIMILAR_FILES_FOUND);
responseData.put("totalPages", similarFiles.getTotalPages()); } else {
responseData.put("hasNext", similarFiles.hasNext()); List<SimilarFileDTO> results = processNonImageFile(fileEntity, authToken);
responseData.put("hasPrevious", similarFiles.hasPrevious()); log.info("results: {}", results);
response = buildSuccessResponse(results, MessageCode.SIMILAR_FILES_FOUND);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
}
return ResponseEntity.ok() return ResponseEntity.ok(response);
.body(new BaseResponse(20004, success.getCode(), messageDesc, responseData));
} 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}")
@@ -297,28 +379,40 @@ public class ApiController {
@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 = authService.useUserAuthToken(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));
} }
@@ -327,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));
} }
@@ -337,14 +431,13 @@ 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.getProtectedFilePath()); Path filePath = Paths.get(entityResponse.getProtectedFilePath());
// Resource resource = new UrlResource(filePath.toUri());
FileSystemResource resource = new FileSystemResource(filePath); FileSystemResource resource = new FileSystemResource(filePath);
long fileSize = Files.size(filePath); long fileSize = Files.size(filePath);
@@ -373,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);
@@ -386,45 +487,6 @@ public class ApiController {
} }
} }
@GetMapping("/protect/{fileId}")
public ResponseEntity<?> protect( @PathVariable(required = false) String fileId) {
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (!optionalFileEntity.isPresent()) {
return ResponseEntity.notFound().build();
}
FileEntity fileEntity = optionalFileEntity.get();
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity);
noCopyFileService.addFile(fileInfo);
fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING);
fileEntityRepository.save(fileEntity);
return ResponseEntity.ok().build();
}
@GetMapping("/check/{fileId}/{type}")
public ResponseEntity<?> check(@PathVariable(required = false) String fileId,
@PathVariable(required = false) String type) {
Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
if (!optionalFileEntity.isPresent()) {
return ResponseEntity.notFound().build();
}
FileEntity fileEntity = optionalFileEntity.get();
Path path = Paths.get(fileEntity.getProtectedFilePath());
File file = path.toFile();
NoCopyCheckResult noCopyCheckResult = noCopyFileService.checkFile(file,
FileProtector.Type.valueOf(type.toUpperCase()));
return ResponseEntity.ok().body(noCopyCheckResult);
}
@GetMapping("/check/file_stats") @GetMapping("/check/file_stats")
public ResponseEntity<?> checkFileProtectStats(@RequestHeader("Authorization") String tokenHeader) { public ResponseEntity<?> checkFileProtectStats(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", ""); String token = tokenHeader.replace("Bearer ", "");
@@ -3,9 +3,7 @@ package ru.soune.nocopy.controller;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.*;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.file.CheckStatus; import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileEntity;
@@ -16,10 +14,16 @@ import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService; import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService; import ru.soune.nocopy.service.file.FileStorageService;
import org.springframework.http.ContentDisposition; import ru.soune.nocopy.service.file.ImageResizeService;
import java.io.FileNotFoundException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController @RestController
@RequestMapping("/api/files") @RequestMapping("/api/files")
@@ -38,43 +42,90 @@ public class FileController {
@Autowired @Autowired
private UserRepository userRepository; private UserRepository userRepository;
@GetMapping("/public/{fileId}") @Autowired
public ResponseEntity<Resource> getPublicFile( private ImageResizeService imageResizeService;
@PathVariable String fileId) throws IOException {
@GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) {
try {
FileEntity fileEntity = fileRepository.findById(fileId) FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found")); .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()); Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
MediaType mediaType = getMediaType(fileEntity);
ContentDisposition contentDisposition = ContentDisposition.inline() ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8) .filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build(); .build();
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension())) .contentType(mediaType)
.contentLength(Files.size(filePath))
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(resource); .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();
}
} }
@GetMapping("/protected/{fileId}") 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( public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId) throws IOException { @PathVariable String fileId, @PathVariable String type) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId) FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found")); .orElseThrow(() -> new RuntimeException("File not found"));
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getProtectedFilePath()); String path = imageResizeService.getPath(fileEntity, type);
Resource resource = fileStorageService.loadFileAsResource(path);
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED || if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) { fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected"); 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() return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension())) .contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + fileEntity.getOriginalFileName() + "\"") "inline; filename*=UTF-8''" + encodedFileName)
.body(resource); .body(resource);
} }
@@ -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);
}
}
@@ -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());
}
}
@@ -3,10 +3,12 @@ package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat; 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) @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseResponse { public class BaseResponse {
@JsonProperty("msg_id") @JsonProperty("msg_id")
@@ -6,16 +6,21 @@ public enum MessageCode {
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"), 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"), 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"), 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"), USER_NOT_VERIFIED(2, "User not verified"),
PERMISSION_NOT_FOUND(2, "Permission not found"), PERMISSION_NOT_FOUND(2, "Permission not found"),
USER_NOT_FOUND(2, "User not found"), USER_NOT_FOUND(2, "User not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"), FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
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"),
@@ -40,9 +45,17 @@ public enum MessageCode {
RESOURCE_NOT_FOUND(4, "Resource not found"), RESOURCE_NOT_FOUND(4, "Resource not found"),
INTERNAL_ERROR(4, "Internal server error"), INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"), COMPANY_NOT_FOUND(4, "Company not found"),
PAYMENT_NOT_FOUND(4, "Payment not found"),
COMPANY_ALREADY_EXISTS(2, "Company already exists"), COMPANY_ALREADY_EXISTS(2, "Company already exists"),
USER_LIMIT_IS_OVER(2, "Over user limits"), USER_LIMIT_IS_OVER(2, "Over user limits"),
ERROR_TARIFF_INFO(2, "Erorr with tariff info"); 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,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;
}
@@ -34,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;
} }
@@ -20,6 +20,7 @@ public class FileEntityResponse {
private String storedFileName; private String storedFileName;
private String filePath; private String filePath;
private String protectedFilePath; private String protectedFilePath;
private String thumbnailFileUrl;
private Long fileSize; private Long fileSize;
private String mimeType; private String mimeType;
private String fileExtension; private String fileExtension;
@@ -40,4 +41,5 @@ public class FileEntityResponse {
private String fileFormat; private String fileFormat;
private Integer checksCount; private Integer checksCount;
private LocalDateTime fileUploadDate; private LocalDateTime fileUploadDate;
private String monitoring;
} }
@@ -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;
} }
@@ -28,4 +28,7 @@ public class FileUploadRequest {
@JsonProperty("token") @JsonProperty("token")
private String token; private String token;
@JsonProperty("convertTo")
private String convertTo;
} }
@@ -19,4 +19,5 @@ public class SimilarFileDTO {
ProtectionStatus status; ProtectionStatus status;
LocalDateTime uploadDate; LocalDateTime uploadDate;
String url; String url;
Boolean owner;
} }
@@ -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;
}
}
@@ -12,11 +12,6 @@ public class YandexSearchResponse {
@JsonProperty("images") @JsonProperty("images")
private List<ImageResult> images; private List<ImageResult> images;
// @JsonProperty("images")
// public void setImages(List<ImageResult> images) {
// this.images = images;
// }
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult { public static class ImageResult {
@@ -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();
}
}
@@ -1,6 +1,7 @@
package ru.soune.nocopy.dto.register; package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import lombok.Data; import lombok.Data;
@@ -13,6 +14,8 @@ public class RegRequest {
private String companyName; private String companyName;
private String inn;
@NotEmpty(message = "Need account type") @NotEmpty(message = "Need account type")
private String accountType; private String accountType;
@@ -31,4 +34,7 @@ public class RegRequest {
private String authToken; private String authToken;
private String referralLink; private String referralLink;
@JsonProperty("mail_verified")
private String mailVerified;
} }
@@ -0,0 +1,24 @@
package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResetPasswordRequest {
@JsonProperty("email")
private String email;
@JsonProperty("verifyToken")
private String verifyToken;
@JsonProperty("action")
private String action;
@JsonProperty("password")
private String password;
}
@@ -0,0 +1,13 @@
package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class ResetPasswordResponse {
@JsonProperty("user_id")
private Long userId;
@JsonProperty("authToken")
private String authToken;
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.dto.search;
import lombok.Data;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import java.util.List;
@Data
public class GlobalSearchFileResult {
private String fileId;
private String fileName;
private String thumbnail;
private String fileStatus;
private List<YandexSearchResponse.ImageResult> images;
private int page;
private int pageSize;
private int totalResults;
private int totalPages;
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.dto.search;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Builder
@Data
public class GlobalSearchStartRequest {
private String searchType;
private List<String> fileIds;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.search;
import lombok.Data;
@Data
public class GlobalSearchStartResponse {
private String taskId;
private String status;
private Integer totalFiles;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.search;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class GlobalSearchStatisticsRequest {
@JsonProperty("token")
private String token;
@JsonProperty("limit")
private Integer limit = 10;
@JsonProperty("days")
private Integer days;
}
@@ -0,0 +1,87 @@
package ru.soune.nocopy.dto.search;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.search.GlobalSearchTask;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Data
@Builder
public class GlobalSearchStatisticsResponse {
@JsonProperty("total_searches")
private long totalSearches;
@JsonProperty("searches_by_status")
private StatusCountDto searchesByStatus;
@JsonProperty("recent_searches")
private List<SearchStatDto> recentSearches;
@Data
@Builder
public static class StatusCountDto {
@JsonProperty("processing")
private long processing;
@JsonProperty("completed")
private long completed;
@JsonProperty("failed")
private long failed;
@JsonProperty("total")
private long total;
}
@Data
@Builder
public static class SearchStatDto {
@JsonProperty("task_id")
private String taskId;
@JsonProperty("search_type")
private String searchType;
@JsonProperty("status")
private String status;
@JsonProperty("total_files")
private Integer totalFiles;
@JsonProperty("violations_found")
private long violationsFound;
@JsonProperty("created_at")
private String createdAt;
@JsonProperty("duration_seconds")
private Long durationSeconds;
@JsonProperty("file_ids")
private List<String> fileIds;
public static SearchStatDto fromEntity(GlobalSearchTask task, long violationsCount, List<String> fileIds ) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
Long duration = null;
if (task.getUpdatedAt() != null && task.getCreatedAt() != null) {
duration = java.time.Duration.between(task.getCreatedAt(), task.getUpdatedAt()).getSeconds();
}
return SearchStatDto.builder()
.taskId(task.getTaskId())
.searchType(task.getSearchType())
.status(task.getStatus())
.totalFiles(task.getTotalFiles())
.violationsFound(violationsCount)
.fileIds(fileIds)
.createdAt(task.getCreatedAt() != null ? task.getCreatedAt().format(formatter) : null)
.durationSeconds(duration)
.build();
}
}
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.dto.search;
import lombok.Data;
import java.util.List;
@Data
public class GlobalSearchStatusResponse {
private String taskId;
private String status;
private String searchType;
private Integer progress;
private List<GlobalSearchFileResult> results;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.search.config;
import lombok.Data;
import java.util.Map;
@Data
public class SearchSettingsDto {
private Map<String, Boolean> engines;
private Boolean proxyEnabled;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.violation;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class DeleteNotionResponse {
private Long notionId;
private String message;
private boolean deleted;
}
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.violation;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.file.FileStatus;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
public class FileViolationSummaryDTO {
private String fileId;
private Integer supportId;
private String fileName;
private Long fileSize;
private String mimeType;
private FileStatus status;
private LocalDateTime createdAt;
private long violationCount;
private LocalDateTime latestViolationDate;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.violation;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class ViolationInfoDTO {
private Long violationId;
private String url;
private String pageUrl;
private String host;
private LocalDateTime createdDate;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.violation;
import lombok.Data;
@Data
public class ViolationNotionRequest {
private String token;
private String action;
private Long violationId;
private Long notionId;
private String message;
}
@@ -0,0 +1,31 @@
package ru.soune.nocopy.dto.violation;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class ViolationNotionResponse {
private Long id;
private String message;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UserInfo user;
private ViolationInfo violation;
@Data
@Builder
public static class UserInfo {
private Long id;
private String fullName;
private String email;
}
@Data
@Builder
public static class ViolationInfo {
private Long id;
private String url;
private String host;
}
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.violation;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class ViolationNotionsListResponse {
private Long violationId;
private String url;
private String host;
private int totalCount;
private List<ViolationNotionResponse> notions;
}
@@ -0,0 +1,39 @@
package ru.soune.nocopy.dto.violation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class ViolationRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("page")
private Integer page = 0;
@JsonProperty("size")
private Integer size = 10;
@JsonProperty("sort_direction")
private String sortDirection = "desc";
@JsonProperty("status")
private String status;
@JsonProperty("start_date")
private String startDate;
@JsonProperty("end_date")
private String endDate;
@JsonProperty("group_by")
private String groupBy;
@JsonProperty("action")
private String action;
@JsonProperty("token")
private String token;
}
@@ -0,0 +1,77 @@
package ru.soune.nocopy.dto.violation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.violation.Violation;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
public class ViolationResponse {
@JsonProperty("violations")
private List<ViolationDto> violations;
@JsonProperty("total_elements")
private long totalElements;
@JsonProperty("total_pages")
private int totalPages;
@JsonProperty("current_page")
private int currentPage;
@JsonProperty("page_size")
private int pageSize;
@JsonProperty("has_next")
private boolean hasNext;
@JsonProperty("has_previous")
private boolean hasPrevious;
@Data
@Builder
public static class ViolationDto {
@JsonProperty("id")
private Long id;
@JsonProperty("url")
private String url;
@JsonProperty("page_url")
private String pageUrl;
@JsonProperty("page_title")
private String pageTitle;
@JsonProperty("host")
private String host;
@JsonProperty("status")
private String status;
@JsonProperty("created_date")
private LocalDateTime createdDate;
@JsonProperty("file_id")
private String fileId;
public static ViolationDto fromEntity(Violation violation) {
return ViolationDto.builder()
.id(violation.getId())
.url(violation.getUrl())
.pageUrl(violation.getPageUrl())
.pageTitle(violation.getPageTitle())
.host(violation.getHost())
.status(violation.getStatus())
.createdDate(violation.getCreatedDate())
.fileId(violation.getFileEntity() != null ? violation.getFileEntity().getId() : null)
.build();
}
}
}
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.violation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class ViolationStatisticsRequest {
@JsonProperty("token")
private String token;
@JsonProperty("file_id")
private String fileId;
@JsonProperty("start_date")
private String startDate;
@JsonProperty("end_date")
private String endDate;
}
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.violation;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ViolationStatisticsResponse {
@JsonProperty("total_violations")
private long totalViolations;
@JsonProperty("new_violations")
private long newViolations;
@JsonProperty("in_progress_violations")
private long inProgressViolations;
@JsonProperty("resolved_violations")
private long resolvedViolations;
}
@@ -32,6 +32,9 @@ public class Company {
@Column(name = "phone") @Column(name = "phone")
private String phone; private String phone;
@Column(name = "inn", unique = true)
private String inn;
@CreatedDate @CreatedDate
@Column(name = "register_date", updatable = false, nullable = false) @Column(name = "register_date", updatable = false, nullable = false)
private LocalDateTime registerDate = LocalDateTime.now(); private LocalDateTime registerDate = LocalDateTime.now();
@@ -8,6 +8,7 @@ import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicInteger;
@Data @Data
@Builder @Builder
@@ -17,6 +18,9 @@ import java.time.LocalDateTime;
@Table(name = "file_entities") @Table(name = "file_entities")
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
public class FileEntity { public class FileEntity {
private static AtomicInteger supportIdCounter = new AtomicInteger(0);
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private String id; private String id;
@@ -77,12 +81,22 @@ public class FileEntity {
@JsonIgnore @JsonIgnore
private String signature; private String signature;
@Column(name = "thumbnail_path")
private String thumbnailPath;
@Column(name = "medium_path")
private String mediumPath;
@OneToOne(mappedBy = "file", cascade = CascadeType.ALL, orphanRemoval = true) @OneToOne(mappedBy = "file", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore @JsonIgnore
private ImageHashEntity imageHash; private ImageHashEntity imageHash;
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
if (this.supportId == null) {
this.supportId = supportIdCounter.incrementAndGet();
}
if (this.status == null) { if (this.status == null) {
this.status = FileStatus.ACTIVE; this.status = FileStatus.ACTIVE;
} }
@@ -101,4 +115,8 @@ public class FileEntity {
public void preUpdate() { public void preUpdate() {
this.updatedAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now();
} }
public static void initializeCounter(int maxId) {
supportIdCounter = new AtomicInteger(maxId);
}
} }
@@ -7,7 +7,7 @@ import java.util.List;
@Getter @Getter
public enum FileType { public enum FileType {
IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp")), IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "bmp")),
VIDEO("video", Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpg", "mpeg", VIDEO("video", Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpg", "mpeg",
"3gp", "3g2", "f4v", "m2ts", "mts", "vob", "ogv", "divx")), "3gp", "3g2", "f4v", "m2ts", "mts", "vob", "ogv", "divx")),
AUDIO("audio", List.of("wav")), AUDIO("audio", List.of("wav")),
@@ -75,6 +75,9 @@ public class FileUploadSession {
@Column(name = "completed_at") @Column(name = "completed_at")
private LocalDateTime completedAt; private LocalDateTime completedAt;
@Column(name = "convert_to")
String convertTo;
@ElementCollection @ElementCollection
@CollectionTable( @CollectionTable(
name = "uploaded_chunks", name = "uploaded_chunks",
@@ -0,0 +1,21 @@
package ru.soune.nocopy.entity.file;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.repository.FileEntityRepository;
@Component
public class SupportIdInitializer {
@Autowired
private FileEntityRepository fileRepository;
@PostConstruct
public void init() {
Integer maxId = fileRepository.findMaxSupportId();
if (maxId != null) {
FileEntity.initializeCounter(maxId);
}
}
}
@@ -1,57 +0,0 @@
package ru.soune.nocopy.entity.file;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
@Entity
@Table(name = "violations")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class Violation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long violationId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "violation_type", nullable = false, length = 50)
private String violationType;
@Column(name = "description", columnDefinition = "TEXT")
private String description;
@Column(name = "content_id")
private Long contentId;
@Column(name = "status", nullable = false, length = 20)
private String status = "new";
@Column(name = "severity", length = 20)
private String severity;
@Column(name = "resolved_at")
private LocalDateTime resolvedAt;
@Column(name = "resolved_by")
private Long resolvedBy;
@Column(name = "resolution_notes", columnDefinition = "TEXT")
private String resolutionNotes;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
}
@@ -0,0 +1,60 @@
package ru.soune.nocopy.entity.monitoring;
import jakarta.persistence.*;
import lombok.*;
import ru.soune.nocopy.entity.file.FileEntity;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "file_monitoring")
public class FileMonitoringEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "file_id", nullable = false)
private FileEntity file;
@Column(name = "user_id", nullable = false)
private Long userId;
@Enumerated(EnumType.STRING)
@Column(name = "monitoring_type", nullable = false)
private MonitoringType monitoringType;
@Column(name = "next_run")
private LocalDateTime nextRun;
@Column(name = "last_run")
private LocalDateTime lastRun;
@Column(name = "last_run_status")
private String lastRunStatus;
@Column(name = "is_active", nullable = false)
private boolean isActive = true;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
@@ -0,0 +1,6 @@
package ru.soune.nocopy.entity.monitoring;
public enum MonitoringType {
MONITORING_DAILY, MONITORING_WEEKLY, MONITORING_MONTHLY, NONE
}
@@ -0,0 +1,8 @@
package ru.soune.nocopy.entity.monitoring;
public enum SearchEngine {
YANDEX,
GOOGLE,
BOTH
}
@@ -0,0 +1,64 @@
package ru.soune.nocopy.entity.payment;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
@Entity
@Table(name = "payment")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EntityListeners(AuditingEntityListener.class)
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
private PaymentStatus status;
@Column(name = "payment_uuid", unique = true)
private String paymentUuid;
@Column(name = "amount")
private Double amount;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Enumerated(EnumType.STRING)
@Column(name = "operation_type", nullable = false)
private PaymentOperationType operationType;
@Column(name = "cancellation_reason")
private String cancellationReason;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
@JsonIgnore
private User user;
@ManyToOne
@JoinColumn(name = "tariff_id")
@JsonIgnore
private Tariff tariff;
}
@@ -0,0 +1,45 @@
package ru.soune.nocopy.entity.payment;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
@Entity
@Table(name = "payment_methods")
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public class PaymentMethod {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(name = "payment_method_id", nullable = false, unique = true)
private String paymentMethodId;
@Column(name = "last_four")
private String lastFour;
@Column(name = "card_type")
private String cardType;
@Column(name = "expiry_month")
private String expiryMonth;
@Column(name = "expiry_year")
private String expiryYear;
@Column(name = "active")
private boolean active = true;
@Column(name = "created_at")
private LocalDateTime createdAt;
}
@@ -0,0 +1,6 @@
package ru.soune.nocopy.entity.payment;
public enum PaymentOperationType {
TARIFF,
TOKEN
}
@@ -0,0 +1,10 @@
package ru.soune.nocopy.entity.payment;
public enum PaymentStatus {
PENDING,
WAITING,
SUCCEEDED,
CANCELED,
FAILED
}
@@ -0,0 +1,50 @@
package ru.soune.nocopy.entity.payout;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import lombok.*;
@Entity
@DiscriminatorValue("BANK_TRANSFER")
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
public class BankTransferPayoutMethod extends PayoutMethod {
@Column(name = "bank_name")
private String bankName;
@Column(name = "bic")
private String bic;
@Column(name = "correspondent_account")
private String correspondentAccount;
@Column(name = "account_number")
private String accountNumber;
@Column(name = "account_holder")
private String accountHolder;
@Column(name = "inn")
private String inn;
@Column(name = "kpp")
private String kpp;
@Override
public String getDisplayName() {
return String.format("Tax %s in %s", getMaskedAccount(), bankName);
}
@Override
public String getMaskedDetails() {
return getMaskedAccount();
}
private String getMaskedAccount() {
if (accountNumber == null || accountNumber.length() < 4) return "";
return "..." + accountNumber.substring(accountNumber.length() - 4);
}
}
@@ -0,0 +1,37 @@
package ru.soune.nocopy.entity.payout;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import lombok.*;
import ru.soune.nocopy.entity.payout.convertor.CardNumberEncryptor;
@Entity
@DiscriminatorValue("CARD")
@NoArgsConstructor
@AllArgsConstructor
@Setter @Getter
public class CardPayoutMethod extends PayoutMethod {
@Convert(converter = CardNumberEncryptor.class)
@Column(name = "card_number", nullable = false)
private String cardNumber;
@Column(name = "card_hash", unique = true)
private String cardHash;
@Override
public String getDisplayName() {
return "Card: " + getMaskedCardNumber();
}
@Override
public String getMaskedDetails() {
return getMaskedCardNumber();
}
private String getMaskedCardNumber() {
if (cardNumber == null || cardNumber.length() < 4) return "";
return "****" + cardNumber.substring(cardNumber.length() - 4);
}
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.entity.payout;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
@Entity
@DiscriminatorValue("INTERNAL")
public class InternalPayoutMethod extends PayoutMethod {
@Override
public String getDisplayName() {
return "Internal balance";
}
@Override
public String getMaskedDetails() {
return "Account balance";
}
}
@@ -0,0 +1,48 @@
package ru.soune.nocopy.entity.payout;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
@Entity
@Table(name = "payout_methods")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "method_type")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayoutMethod {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(name = "method_type", insertable = false, updatable = false)
private String methodType;
@Column(name = "is_default")
private boolean isDefault;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
public String getDisplayName() {
return null;
}
public String getMaskedDetails() {
return null;
}
}
@@ -0,0 +1,64 @@
package ru.soune.nocopy.entity.payout;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import ru.soune.nocopy.entity.user.User;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "payout_requests")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class PayoutRequest {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payout_method_id")
@JsonIgnore
private PayoutMethod payoutMethod;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
@JsonIgnore
private User user;
@Column(nullable = false)
private BigDecimal amount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PayoutRequestStatus status;
@Enumerated(EnumType.STRING)
@Column(nullable = false, name = "payout_type")
@JsonIgnore
private PayoutType payoutType;
@Column(name = "process_at")
private LocalDateTime processAt;
@Column(name = "completed_at")
private LocalDateTime completedAt;
@Column(name = "rejection_reason")
private String rejectionReason;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
@@ -0,0 +1,9 @@
package ru.soune.nocopy.entity.payout;
public enum PayoutRequestStatus {
PENDING,
PROCESSING,
COMPLETED,
REJECTED,
CANCELLED
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.entity.payout;
public enum PayoutType {
CARD("card"),
BANK_TRANSFER("bank_transfer"),
INTERNAL("internal");
private final String displayName;
PayoutType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
@@ -0,0 +1,74 @@
package ru.soune.nocopy.entity.payout.convertor;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
@Converter
public class CardNumberEncryptor implements AttributeConverter<String, String> {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
private static final int GCM_IV_LENGTH = 12;
private static final byte[] FIXED_KEY = "12345678901234567890123456789012".getBytes();
@Override
public String convertToDatabaseColumn(String cardNumber) {
if (cardNumber == null) return null;
try {
SecretKeySpec key = new SecretKeySpec(FIXED_KEY, "AES");
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
byte[] encrypted = cipher.doFinal(cardNumber.getBytes(StandardCharsets.UTF_8));
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(result);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt card number", e);
}
}
@Override
public String convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
try {
byte[] decoded = Base64.getDecoder().decode(dbData);
byte[] iv = new byte[GCM_IV_LENGTH];
byte[] encrypted = new byte[decoded.length - GCM_IV_LENGTH];
System.arraycopy(decoded, 0, iv, 0, iv.length);
System.arraycopy(decoded, iv.length, encrypted, 0, encrypted.length);
SecretKeySpec key = new SecretKeySpec(FIXED_KEY, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt card number", e);
}
}
}
@@ -25,6 +25,9 @@ public class Referral {
@Column(name = "total_income") @Column(name = "total_income")
private int totalIncome = 0; private int totalIncome = 0;
@Column(name = "available_income")
private int availableIncome = 0;
@Column(name = "is_active") @Column(name = "is_active")
private boolean isActive = false; private boolean isActive = false;
@@ -0,0 +1,27 @@
package ru.soune.nocopy.entity.search;
import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "global_search_results")
@Data
@Getter @Setter
@RequiredArgsConstructor
public class GlobalSearchResult {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String taskId;
private String fileId;
private String fileName;
private String fileStatus;
}
@@ -0,0 +1,34 @@
package ru.soune.nocopy.entity.search;
import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Table(name = "global_search_tasks")
@Data
@Getter @Setter
@RequiredArgsConstructor
public class GlobalSearchTask {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String taskId;
private Long userId;
private String searchType;
private String status;
private Integer totalFiles;
private Integer processedFiles;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,5 @@
package ru.soune.nocopy.entity.search;
public enum SearchStatus {
PROCESSING, COMPLETED, ACCEPTED, FAILED, SUCCESS, TIMEOUT
}
@@ -37,4 +37,10 @@ public class TariffInfo {
@ManyToOne @ManyToOne
@JoinColumn(name = "tariff_id") @JoinColumn(name = "tariff_id")
private Tariff tariff; private Tariff tariff;
@Column(name = "auto_renewal")
private boolean autoRenewal = false;
@Column(name = "payment_method_id")
private String paymentMethodId;
} }
@@ -2,5 +2,6 @@ package ru.soune.nocopy.entity.tarif;
public enum TariffType { public enum TariffType {
START, BASIC, PRO, ENTERPRISE, DEMO, BUISNES, CORPORAT, STUDIO, FREE; START, BASIC, PRO, ENTERPRISE, DEMO, BUISNES, CORPORAT, STUDIO, FREE, TOKEN_100, TOKEN_500, TOKEN_1000, TOKEN_5000,
MONITORING_DAILY, MONITORING_WEEKLY, MONITORING_MONTHLY;
} }
@@ -4,10 +4,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.*; import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.company.Company; import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.Violation;
import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.tarif.TariffStatus; import ru.soune.nocopy.entity.tarif.TariffStatus;
@@ -80,11 +80,6 @@ public class User {
@ToString.Exclude @ToString.Exclude
private List<UserContent> userContents = new ArrayList<>(); private List<UserContent> userContents = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private List<Violation> violations = new ArrayList<>();
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore @JsonIgnore
private ProtectedFileCheck protectedFileCheck; private ProtectedFileCheck protectedFileCheck;
@@ -102,6 +97,15 @@ public class User {
@ToString.Exclude @ToString.Exclude
private TariffInfo personalTariffInfo; private TariffInfo personalTariffInfo;
@Column(name = "failed_login_attempts")
@ColumnDefault("0")
private Integer failedLoginAttempts = 0;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
@ToString.Exclude
private UserNotActive userNotActive;
public TariffInfo getActiveTariffInfo() { public TariffInfo getActiveTariffInfo() {
if (company != null && company.getTariffInfo() != null) { if (company != null && company.getTariffInfo() != null) {
return company.getTariffInfo(); return company.getTariffInfo();
@@ -0,0 +1,32 @@
package ru.soune.nocopy.entity.user;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_not_active")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class UserNotActive {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
private User user;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@Column(name = "blocked_until")
private LocalDateTime blockedUntil;
}
@@ -0,0 +1,57 @@
package ru.soune.nocopy.entity.violation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.search.GlobalSearchResult;
import java.time.LocalDateTime;
@Entity
@Table(name = "violation")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Violation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "url", nullable = false, columnDefinition = "TEXT")
private String url;
@Column(name = "url_hash", nullable = false, unique = true, columnDefinition = "TEXT")
private String urlHash;
@Column(name = "page_url", columnDefinition = "TEXT")
private String pageUrl;
@Column(name = "title", columnDefinition = "TEXT")
private String pageTitle;
@Column(name = "host", columnDefinition = "TEXT")
private String host;
@Column(name = "status")
private String status;
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdDate = LocalDateTime.now();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "file")
@ToString.Exclude
@JsonIgnore
private FileEntity fileEntity;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "global_search_result")
@ToString.Exclude
@JsonIgnore
private GlobalSearchResult globalSearchResult;
}
@@ -0,0 +1,46 @@
package ru.soune.nocopy.entity.violation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
@Entity
@Getter @Setter
@Table(name = "violation_notions")
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class ViolationNotion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@JsonIgnore
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "violation_id", nullable = false)
@JsonIgnore
private Violation violation;
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@Column(name = "message")
private String message;
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class CompanyAlreadyExist extends RuntimeException {
public CompanyAlreadyExist(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class DuplicateMethodException extends RuntimeException {
public DuplicateMethodException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class FileFormatException extends RuntimeException {
public FileFormatException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package ru.soune.nocopy.exception;
public class InvalidPayoutMethodException extends RuntimeException {
public InvalidPayoutMethodException(String message) {
super(message);
}
}

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