913 Commits
Author SHA1 Message Date
backdev 1f46043a1d Add demo logic for landing
Test Workflow / test (push) Has been cancelled
2026-07-11 09:53:57 -05:00
backdev ccb57c3805 Add demo logic for landing
Test Workflow / test (push) Has been cancelled
2026-07-07 18:14:23 +07:00
backdev 0fa30eb176 Add demo logic for landing
Test Workflow / test (push) Has been cancelled
2026-07-07 18:06:56 +07:00
backdev 7b7a366d78 Add api key for use rest api
Test Workflow / test (push) Has been cancelled
2026-07-01 11:49:54 +07:00
backdev be63fc3c10 Add api key for use rest api
Test Workflow / test (push) Has been cancelled
2026-06-30 23:32:57 +07:00
backdev 2330f279aa Add api key for use rest api
Test Workflow / test (push) Has been cancelled
2026-06-29 16:01:14 +07:00
backdev 38888140cd Add api key for use rest api
Test Workflow / test (push) Has been cancelled
2026-06-16 22:25:45 +07:00
backdev fb381518de Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 12:24:39 +07:00
backdev 313d0b454c Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 12:24:21 +07:00
backdev 348287f863 Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 12:07:37 +07:00
backdev a1080b5eff Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 11:55:45 +07:00
backdev fb82f56c0f Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 11:37:04 +07:00
backdev bfbdc78d0e Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 11:15:50 +07:00
backdev e3eb8a67e8 Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-15 10:46:00 +07:00
backdev acceaa55fc Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 18:56:47 +07:00
backdev c7fbd371c3 Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 15:34:00 +07:00
backdev 30df7fe83f Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 15:22:36 +07:00
backdev 41a95db83f Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 15:06:59 +07:00
backdev 8ad7bb8675 Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 14:34:42 +07:00
backdev 851b3d351d Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 14:02:34 +07:00
backdev ab1e0e7677 Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 13:14:24 +07:00
backdev 2a93152f24 Add monitoring with another system
Test Workflow / test (push) Has been cancelled
2026-06-11 11:08:03 +07:00
backdev 9c81dfea87 Add delete city from dockview dtol
Test Workflow / test (push) Has been cancelled
2026-06-10 09:57:54 +07:00
backdev 10703857f7 Add file public for thumbnail
Test Workflow / test (push) Has been cancelled
2026-06-09 11:29:46 +07:00
backdev eee3245fa9 Add fix check status for monitoring
Test Workflow / test (push) Has been cancelled
2026-06-06 11:37:28 +07:00
backdev 98f72b8943 Add fix check status for monitoring
Test Workflow / test (push) Has been cancelled
2026-06-05 21:21:35 +07:00
backdev aff80c4919 Add fix check status for monitoring
Test Workflow / test (push) Has been cancelled
2026-06-05 21:07:35 +07:00
backdev 6e8650c61d Add kafka templates
Test Workflow / test (push) Has been cancelled
2026-06-04 17:59:32 +07:00
backdev 8bc576da27 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-04 11:11:17 +07:00
backdev 2ee9ac8a92 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-03 17:25:19 +07:00
backdev 97b1fb7d85 Merge branch 'NCBACK-162' into dev
Test Workflow / test (push) Has been cancelled
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/FileController.java
2026-06-03 10:45:18 +07:00
backdev 22e9d6c9db Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 16:07:08 +07:00
backdev 8bad5a4619 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:53:03 +07:00
backdev 3b04cc8907 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:39:38 +07:00
backdev ebbd69b452 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:26:08 +07:00
backdev 47bed4149a Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 15:06:45 +07:00
backdev e788b062b8 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 14:09:25 +07:00
backdev db606284cb Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 13:01:41 +07:00
backdev f6d45e5dd2 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 12:27:45 +07:00
backdev 1a2eaaacab Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 12:11:35 +07:00
backdev 74dfbe2ae4 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 12:06:56 +07:00
backdev 45f7e3d0bf Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-02 12:04:01 +07:00
backdev de32f9b4d1 Add file info public
Test Workflow / test (push) Has been cancelled
2026-06-01 16:56:19 +07:00
backdev 5c438c466f NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-06-01 15:51:42 +07:00
backdev 5427603f89 NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-06-01 15:25:36 +07:00
backdev ab4c60f4b9 NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-06-01 15:07:11 +07:00
backdev 184f3a730c NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-06-01 14:49:36 +07:00
backdev b107b805fc NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-06-01 13:53:18 +07:00
backdev b406b50013 NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-06-01 13:44:03 +07:00
backdev 5e494ffc4b NCBACK-162
Test Workflow / test (push) Has been cancelled
2026-05-29 18:17:04 +07:00
backdev bc26b39d86 dev check fileName
Test Workflow / test (push) Has been cancelled
2026-05-29 12:12:26 +07:00
backdev 9423a44e17 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-28 14:26:06 +07:00
backdev 1f49c49679 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-28 13:38:35 +07:00
backdev be779ec496 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-28 13:07:09 +07:00
backdev 6f2d8320dc dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-28 12:44:51 +07:00
backdev ea33e9cfe3 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-28 12:18:46 +07:00
backdev a2e94b744c dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-28 11:23:12 +07:00
backdev de2a7f83fd dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-27 18:52:46 +07:00
backdev 1ac6d6f1b0 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-27 17:47:12 +07:00
backdev d82ee69d6f dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-27 17:35:47 +07:00
backdev 6555f4c0c2 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-27 16:44:07 +07:00
backdev c83dc10125 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-27 16:31:08 +07:00
backdev 71cd0ebf10 dev add tariff info and control
Test Workflow / test (push) Has been cancelled
2026-05-27 16:15:36 +07:00
backdev ca9a7478f4 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-27 14:31:40 +07:00
backdev ea763edbec dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-27 11:31:04 +07:00
backdev 357ca3ec62 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-27 11:21:33 +07:00
backdev cb8a564afe dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-27 11:02:35 +07:00
backdev 398bfe74bb dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-27 10:35:28 +07:00
backdev 1e8e1f48db dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 16:33:08 +07:00
backdev 8a4f299941 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 16:17:16 +07:00
backdev 9891b1d9b2 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 15:37:38 +07:00
backdev e26b5928a4 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 15:17:58 +07:00
backdev 2dc9a760bc dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 14:56:35 +07:00
backdev 646d6fa11d dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 14:47:56 +07:00
backdev 16791d35de dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 13:21:01 +07:00
backdev 4a85e415a7 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-26 11:45:43 +07:00
backdev 85f8528b0c dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-25 19:08:41 +07:00
backdev 31d257c9bb dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-25 18:31:41 +07:00
backdev b87a4aff39 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-25 17:20:40 +07:00
backdev 859a95fda7 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-25 16:39:19 +07:00
backdev 7ec130e006 dev add complaint all info with pagination
Test Workflow / test (push) Has been cancelled
2026-05-25 15:20:50 +07:00
backdev 8ee60229f0 dev add User info for internal-data
Test Workflow / test (push) Has been cancelled
2026-05-25 14:51:09 +07:00
backdev d55b1620d5 dev add statistic complaints by status
Test Workflow / test (push) Has been cancelled
2026-05-25 14:16:39 +07:00
backdev 6929060661 dev add statistic complaints by status
Test Workflow / test (push) Has been cancelled
2026-05-25 12:57:22 +07:00
backdev 670f14e955 dev add statistic complaints by status
Test Workflow / test (push) Has been cancelled
2026-05-25 12:38:20 +07:00
backdev 1536dbc845 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 16:57:03 +07:00
backdev 59c1b30669 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 16:47:32 +07:00
backdev 992cc1747c dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 16:37:49 +07:00
backdev b557b851e4 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 15:20:34 +07:00
backdev 9713f383a6 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 15:12:21 +07:00
backdev 34d5f159a4 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 15:01:43 +07:00
backdev 71e410aef5 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 14:55:51 +07:00
backdev b143b29a78 dev fix download with cyrrilic symbols
Test Workflow / test (push) Has been cancelled
2026-05-22 14:50:15 +07:00
backdev 9afe09ccc5 dev add cache for search
Test Workflow / test (push) Has been cancelled
2026-05-21 17:05:57 +07:00
backdev e20e4f6b48 dev add cache for search
Test Workflow / test (push) Has been cancelled
2026-05-21 16:55:21 +07:00
backdev 246d55da55 dev add cache for search
Test Workflow / test (push) Has been cancelled
2026-05-21 16:42:50 +07:00
backdev 81130fc238 dev add cache for search
Test Workflow / test (push) Has been cancelled
2026-05-21 16:26:07 +07:00
backdev 942e4d4926 dev add cache for search
Test Workflow / test (push) Has been cancelled
2026-05-21 16:15:50 +07:00
backdev cf2aefe6c3 dev add cache for search
Test Workflow / test (push) Has been cancelled
2026-05-21 16:06:47 +07:00
backdev ab796932e6 dev fields for notion
Test Workflow / test (push) Has been cancelled
2026-05-21 14:38:33 +07:00
backdev 55081d51a3 dev fields for notion
Test Workflow / test (push) Has been cancelled
2026-05-21 14:26:50 +07:00
backdev 0c522e2faf dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-21 13:15:11 +07:00
backdev 5c0a0c146b dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 17:10:20 +07:00
backdev 42e8648f53 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 17:00:52 +07:00
backdev e03c9ff0ff dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 16:34:16 +07:00
backdev 3a1f4ea9d9 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 16:22:22 +07:00
backdev 24bae18311 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 16:04:33 +07:00
backdev 7845304f87 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 15:54:29 +07:00
backdev 4dc479e31b dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 15:45:40 +07:00
backdev e633d43a74 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 15:36:55 +07:00
backdev 31713550ce dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 14:55:43 +07:00
backdev 2aac3bbdf4 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 14:35:23 +07:00
backdev 6415053840 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 14:30:56 +07:00
backdev 772cf9f40f dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 14:22:40 +07:00
backdev 20911982a6 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 13:08:55 +07:00
backdev 82e84514e4 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 12:59:17 +07:00
backdev 9dd1f8e6c8 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 12:22:39 +07:00
backdev 73ef7a57a3 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-19 12:11:14 +07:00
backdev 284bde0e27 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-18 15:13:16 +07:00
backdev 218582958c dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-18 14:01:02 +07:00
backdev 16620213c7 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-18 13:52:41 +07:00
backdev 0e0fac02a8 dev check violation for lawcase,complaint
Test Workflow / test (push) Has been cancelled
2026-05-18 13:25:53 +07:00
backdev 57a39d0cb0 dev add tarriff description
Test Workflow / test (push) Has been cancelled
2026-05-15 16:52:55 +07:00
backdev df38da6d5f dev add tarriff description
Test Workflow / test (push) Has been cancelled
2026-05-15 16:06:31 +07:00
backdev cbff850da6 dev add tarriff description
Test Workflow / test (push) Has been cancelled
2026-05-15 15:52:50 +07:00
backdev afe78f74f2 dev add tarriff description
Test Workflow / test (push) Has been cancelled
2026-05-15 15:41:24 +07:00
backdev c0818146bc dev add tarriff description
Test Workflow / test (push) Has been cancelled
2026-05-15 15:29:07 +07:00
backdev a4928395b9 dev add tarriff description
Test Workflow / test (push) Has been cancelled
2026-05-15 14:44:54 +07:00
backdev 7c7ad61397 dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 13:37:15 +07:00
backdev dfc24b77c4 dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 13:27:29 +07:00
backdev 8c8ae0eb3c dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 13:17:47 +07:00
backdev ae81c42b89 dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 13:07:16 +07:00
backdev cfe3520bad dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 12:56:56 +07:00
backdev b0c66720b0 dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 12:40:26 +07:00
backdev 0c373acc7f dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-15 10:49:44 +07:00
backdev 59e9de2684 dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-14 13:01:40 +07:00
backdev 5dc3bb7157 dev add field locale for get info for view doc
Test Workflow / test (push) Has been cancelled
2026-05-14 12:44:19 +07:00
backdev 48cdca56a2 dev add check name with locale
Test Workflow / test (push) Has been cancelled
2026-05-14 12:28:52 +07:00
backdev 59909c05c3 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-14 11:44:08 +07:00
backdev 675c93c0c6 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 16:31:55 +07:00
backdev cc77f481dc dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 16:21:04 +07:00
backdev 51feca36d0 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 16:00:35 +07:00
backdev 17850710b5 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 15:43:43 +07:00
backdev de8120c737 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 15:32:55 +07:00
backdev 2319dd498f dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 15:19:45 +07:00
backdev b77efcbce5 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 14:27:01 +07:00
backdev a6e73be032 dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 14:15:54 +07:00
backdev cda829829c dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 14:03:15 +07:00
backdev 48c14d475d dev add context for notifcations
Test Workflow / test (push) Has been cancelled
2026-05-13 13:39:02 +07:00
backdev 0546f6acd5 dev add user info method by id
Test Workflow / test (push) Has been cancelled
2026-05-13 07:59:28 +07:00
backdev 807a8e1d53 dev add user info method by id
Test Workflow / test (push) Has been cancelled
2026-05-13 07:44:24 +07:00
backdev 0314618db5 dev add user info method by id
Test Workflow / test (push) Has been cancelled
2026-05-12 16:15:23 +07:00
backdev 6bc6592f37 dev add user info method by id
Test Workflow / test (push) Has been cancelled
2026-05-12 14:22:56 +07:00
backdev 4d8d3d4df2 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-09 09:58:54 +07:00
backdev 153a875b16 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 21:07:32 +07:00
backdev 01ba4c3892 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 15:34:57 +07:00
backdev 6565c18958 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 15:19:17 +07:00
backdev f433b4dc01 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 15:05:17 +07:00
backdev 891a900a53 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 14:56:15 +07:00
backdev 96a329cc48 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 14:35:15 +07:00
backdev d506b1cdaf dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 14:09:26 +07:00
backdev ed8eb25ec1 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 13:58:40 +07:00
backdev ec8f3af710 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 12:10:38 +07:00
backdev 394259c2c7 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 11:57:29 +07:00
backdev ff44763f88 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-08 11:42:59 +07:00
backdev 9dd174eb60 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-07 12:49:01 +07:00
backdev 957b10c5b9 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-07 11:35:04 +07:00
backdev 89124a0a73 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-07 11:24:29 +07:00
backdev b864d7e9bd dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 14:30:01 +07:00
backdev 5c918d48ad dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 14:19:30 +07:00
backdev 0d943da120 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 14:09:15 +07:00
backdev af4e4717d1 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 13:59:40 +07:00
backdev d0df78730f dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 13:49:16 +07:00
backdev ef22693691 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 13:29:13 +07:00
backdev b09e4e3bdb dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 13:08:34 +07:00
backdev f88b6fd353 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 13:07:52 +07:00
backdev d23df37b45 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 12:59:38 +07:00
backdev 8b8db46c29 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 12:45:00 +07:00
backdev 3fd4cbeab5 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-06 12:28:07 +07:00
backdev 367af98994 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 15:41:05 +07:00
backdev ba89406417 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 15:31:49 +07:00
backdev d2b94802de dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 14:53:25 +07:00
backdev d98a5ce692 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 14:43:28 +07:00
backdev cdc45e400d dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 14:36:30 +07:00
backdev c9e95edf1d dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 14:22:17 +07:00
backdev 352fe2c5cf dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 14:04:54 +07:00
backdev 6d80498402 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 13:54:11 +07:00
backdev bb43b6ef4f dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 13:44:49 +07:00
backdev 6793bbfc5c dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 10:43:07 +07:00
backdev 851528ead8 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-05 10:40:25 +07:00
backdev dad0c9db3b dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 17:24:46 +07:00
backdev 50577b43a2 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 16:59:30 +07:00
backdev 25b4601bfb dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 15:15:42 +07:00
backdev aacd485584 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 14:51:56 +07:00
backdev bc86854596 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 14:37:06 +07:00
backdev 0aa30736d8 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 14:25:40 +07:00
backdev c82d67ab6f dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 13:25:33 +07:00
backdev 83a325a5ac dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 13:22:19 +07:00
backdev b7ef3c86e8 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 12:59:51 +07:00
backdev d3ab48d40c dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 11:34:43 +07:00
backdev b79f2a4969 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 11:23:33 +07:00
backdev b19abf32e9 dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 11:09:52 +07:00
backdev bbc2508a9e dev add method link
Test Workflow / test (push) Has been cancelled
2026-05-04 10:50:42 +07:00
backdev 377a71a28e dev add method link
Test Workflow / test (push) Has been cancelled
2026-04-30 18:49:48 +07:00
backdev e1894168b6 dev add method link
Test Workflow / test (push) Has been cancelled
2026-04-30 18:35:35 +07:00
backdev fa4cb2ed1b dev add method link
Test Workflow / test (push) Has been cancelled
2026-04-30 18:24:19 +07:00
backdev c46c3842aa dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-30 16:04:37 +07:00
backdev d885f3fc4c dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-30 15:51:16 +07:00
backdev fb48ce45a0 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 18:22:07 +07:00
backdev d9249c4ebc dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 18:14:46 +07:00
backdev 18383b1b62 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 18:14:15 +07:00
backdev 9686eb453f dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 18:01:47 +07:00
backdev 93da73e90b dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 17:41:41 +07:00
backdev 67204d1c5c dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 17:29:19 +07:00
backdev 9a27b56f05 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 17:16:34 +07:00
backdev f032caf0a3 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 16:59:27 +07:00
backdev 0f8017a5a9 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 15:20:46 +07:00
backdev c75ec6aa39 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 15:13:19 +07:00
backdev ef35c62a61 dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 15:05:56 +07:00
backdev aa14f0dfbf dev add check similar
Test Workflow / test (push) Has been cancelled
2026-04-29 14:53:45 +07:00
backdev ddaf729e18 dev add violation id like required parameters
Test Workflow / test (push) Has been cancelled
2026-04-29 13:33:48 +07:00
backdev 2ca39eca3a dev add violation id like required parameters
Test Workflow / test (push) Has been cancelled
2026-04-29 11:55:18 +07:00
backdev 136b2b2082 dev add violation id like required parameters
Test Workflow / test (push) Has been cancelled
2026-04-29 11:42:11 +07:00
backdev fb091496de dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 17:40:37 +07:00
backdev 5c8431195b dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 17:11:51 +07:00
backdev e5d4ec2f07 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 16:37:00 +07:00
backdev a87567e45d dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 16:32:30 +07:00
backdev 63e065373f dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 16:09:34 +07:00
backdev c44c251790 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 15:58:35 +07:00
backdev 61e47975ff dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 15:41:57 +07:00
backdev 7adb52d86f dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 15:13:46 +07:00
backdev e8bcca6ee0 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 15:07:12 +07:00
backdev 99f4db1d76 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 14:58:47 +07:00
backdev 097a31fd7b dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 14:52:55 +07:00
backdev cca3a58f5a dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 14:42:41 +07:00
backdev 8a1035c6e1 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 14:25:31 +07:00
backdev 284b402cdc dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 14:15:29 +07:00
backdev 610ab0977d dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 14:03:43 +07:00
backdev 66af0ea285 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 13:53:51 +07:00
backdev cb01e24d87 dev fix add count
Test Workflow / test (push) Has been cancelled
2026-04-28 13:30:00 +07:00
backdev b67fb403e6 dev fix check file mimetype
Test Workflow / test (push) Has been cancelled
2026-04-28 12:33:51 +07:00
backdev b3d99b0c02 dev add check for change status
Test Workflow / test (push) Has been cancelled
2026-04-28 09:06:38 +07:00
backdev d1b695a65b dev add check for change status
Test Workflow / test (push) Has been cancelled
2026-04-27 17:56:28 +07:00
backdev 67e547e185 dev add check for change status
Test Workflow / test (push) Has been cancelled
2026-04-27 13:57:19 +07:00
backdev ced8768332 dev add check for change status
Test Workflow / test (push) Has been cancelled
2026-04-27 13:56:52 +07:00
backdev 3e90609ae0 dev add check for change status
Test Workflow / test (push) Has been cancelled
2026-04-27 12:44:13 +07:00
backdev dc27e2ccf2 dev add check for change status
Test Workflow / test (push) Has been cancelled
2026-04-27 12:21:26 +07:00
backdev 3f0a514660 dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-24 14:14:04 +07:00
backdev 0797ce20e5 dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-24 13:36:30 +07:00
backdev ab4151b68b dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-24 13:24:50 +07:00
backdev 0047cfd4f6 dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-24 13:11:49 +07:00
backdev cdaa6c7ce2 dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-24 12:54:48 +07:00
backdev d2d78b5117 dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-23 18:16:50 +07:00
backdev 3002f4b04c dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-23 18:05:37 +07:00
backdev a34fb9ffa7 dev fix sort
Test Workflow / test (push) Has been cancelled
2026-04-23 16:45:16 +07:00
backdev d0b977291c dev add cold storage
Test Workflow / test (push) Has been cancelled
2026-04-23 14:28:06 +07:00
backdev 71aa8ee01a dev add cold storage
Test Workflow / test (push) Has been cancelled
2026-04-23 14:11:05 +07:00
backdev 04506f4b42 dev add cold storage
Test Workflow / test (push) Has been cancelled
2026-04-23 13:10:24 +07:00
backdev e0ecd91ed0 dev add cold storage
Test Workflow / test (push) Has been cancelled
2026-04-23 12:56:13 +07:00
backdev 2f167a0150 dev add cold storage
Test Workflow / test (push) Has been cancelled
2026-04-23 12:37:14 +07:00
backdev 33c0275a37 dev add cold storage
Test Workflow / test (push) Has been cancelled
2026-04-23 00:38:40 +07:00
backdev d1e36a8dd0 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 18:13:20 +07:00
backdev d5f7c73306 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 18:05:02 +07:00
backdev 0529af6526 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 17:53:36 +07:00
backdev 5de7e828c2 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 17:10:25 +07:00
backdev d29258855e dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 16:24:43 +07:00
backdev cbcd16d376 Merge branch 'NCBACK-35' into dev 2026-04-22 16:03:28 +07:00
backdev 4dd6c4a3c0 dev merge
Test Workflow / test (push) Has been cancelled
2026-04-22 16:02:52 +07:00
backdev da84490198 dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 14:23:35 +07:00
backdev b723247b9c dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 14:04:57 +07:00
backdev eb223c2ecb dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 12:38:05 +07:00
backdev bb96e3ac93 dev add new action
Test Workflow / test (push) Has been cancelled
2026-04-22 12:16:37 +07:00
backdev cc43b92f69 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 02:06:39 +07:00
backdev d92a9a7d86 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 01:50:18 +07:00
backdev 6dec7838ce dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-22 01:06:19 +07:00
backdev 83ed7c5b01 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 21:11:47 +07:00
backdev e66ae81d18 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 19:34:43 +07:00
backdev 6c3742913c dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 19:09:27 +07:00
backdev dfc9a22bc5 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 18:11:36 +07:00
backdev d0be6580e4 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 11:50:25 +07:00
backdev 1dd6a6b7e2 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 11:01:15 +07:00
backdev c33ffe516a dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:43:13 +07:00
backdev ac7cc54b5e dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:22:42 +07:00
backdev ea7dd5e5df dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 10:11:31 +07:00
backdev c2a649a252 dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 09:26:57 +07:00
backdev f680f766cd dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 09:26:03 +07:00
backdev 82827c9a2a dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 03:45:23 +07:00
backdev e6e8461b9b dev test baseurl for test
Test Workflow / test (push) Has been cancelled
2026-04-21 03:27:47 +07:00
backdev 1b2337ea2f dev fix thumbnal path
Test Workflow / test (push) Has been cancelled
2026-04-21 03:11:52 +07:00
backdev 5fc9563a4e dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:59:21 +07:00
backdev fdc17b8325 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:21:33 +07:00
backdev 3b68b3337c dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 02:10:17 +07:00
backdev 631066b217 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 01:52:48 +07:00
backdev 61dcada973 dev delete check file on disk
Test Workflow / test (push) Has been cancelled
2026-04-21 01:43:40 +07:00
backdev 7492b8aa7f dev fix file public endpoint
Test Workflow / test (push) Has been cancelled
2026-04-21 01:31:30 +07:00
backdev 9ad4830edb dev fix file public endpoint
Test Workflow / test (push) Has been cancelled
2026-04-21 01:06:50 +07:00
backdev 6366cbc96b dev change
Test Workflow / test (push) Has been cancelled
2026-04-21 00:36:22 +07:00
backdev 4e0e953253 dev change
Test Workflow / test (push) Has been cancelled
2026-04-20 19:50:32 +07:00
backdev 1476509b48 dev change
Test Workflow / test (push) Has been cancelled
2026-04-20 18:50:47 +07:00
backdev 9cde6f3bb6 dev change logic for get files for search
Test Workflow / test (push) Has been cancelled
2026-04-20 18:31:32 +07:00
backdev 9cd5965b37 dev change logic for get files for search
Test Workflow / test (push) Has been cancelled
2026-04-20 18:16:46 +07:00
backdev 572255c066 dev add monitoring statistic
Test Workflow / test (push) Has been cancelled
2026-04-20 16:15:08 +07:00
backdev d01ea6e2bd dev fix save to storage
Test Workflow / test (push) Has been cancelled
2026-04-20 15:35:24 +07:00
backdev 2ab91dee58 dev fix save to storage
Test Workflow / test (push) Has been cancelled
2026-04-20 14:26:10 +07:00
backdev d1a91fec1a dev add user_id for find all complaints
Test Workflow / test (push) Has been cancelled
2026-04-17 22:00:33 +07:00
vladp b0cc5720a6 dev add statistic files protected
Test Workflow / test (push) Successful in 5s
2026-04-16 17:32:49 +07:00
vladp ed89d315ae Merge branch 'dev' into NCBACK-35
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/repository/FileEntityRepository.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
2026-04-16 16:25:36 +07:00
vladp 74f9be8bf7 dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 01:08:40 +07:00
vladp bbf8e3ee3d dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 00:54:44 +07:00
vladp db3dcb8166 dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 00:37:24 +07:00
vladp 42a5368003 dev add statistic files protected
Test Workflow / test (push) Successful in 2s
2026-04-16 00:28:27 +07:00
vladp be62dd458a dev add statistic files protected
Test Workflow / test (push) Successful in 3s
2026-04-15 18:28:42 +07:00
vladp 2d80b40b27 dev add statistic files protected
Test Workflow / test (push) Successful in 3s
2026-04-15 18:12:32 +07:00
vladp 1b921b525a dev add statistic rest
Test Workflow / test (push) Successful in 2s
2026-04-15 17:46:53 +07:00
vladp b56fb63a37 dev add statistic rest
Test Workflow / test (push) Successful in 2s
2026-04-15 17:41:33 +07:00
vladp bb41898abf dev add statistic rest
Test Workflow / test (push) Successful in 3s
2026-04-15 17:32:07 +07:00
vladp 9974e22abe dev add
Test Workflow / test (push) Successful in 3s
2026-04-15 14:26:46 +07:00
vladp 064a888188 dev add
Test Workflow / test (push) Successful in 3s
2026-04-15 12:49:03 +07:00
vladp 484475eadc dev add nexus in docker-compose
Test Workflow / test (push) Successful in 2s
2026-04-15 10:58:43 +07:00
vladp c60aeec6f5 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 10:25:25 +07:00
vladp 0bb952c4e6 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 10:17:47 +07:00
vladp 11ee4e18ed dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 10:10:46 +07:00
vladp 684888aa2c dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 09:59:38 +07:00
vladp eb43621b74 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 09:51:18 +07:00
vladp dd73227db1 dev add geo country service
Test Workflow / test (push) Successful in 2s
2026-04-15 09:46:29 +07:00
vladp a7939845c7 dev add geo country service
Test Workflow / test (push) Successful in 3s
2026-04-15 09:34:32 +07:00
vladp 40cb53a3b0 dev update mongodb for ci/cd
Test Workflow / test (push) Successful in 2s
2026-04-14 18:35:21 +07:00
vladp bac45ec738 dev update mongodb for ci/cd
Test Workflow / test (push) Successful in 2s
2026-04-14 18:26:23 +07:00
vladp 01cfc16583 dev add mongodb container
Test Workflow / test (push) Successful in 3s
2026-04-14 17:32:43 +07:00
vladp f71252bb5e dev change mod user status enum
Test Workflow / test (push) Successful in 2s
2026-04-14 16:44:58 +07:00
vladp 934f3459fb dev change mod user status enum
Test Workflow / test (push) Successful in 3s
2026-04-14 12:57:14 +07:00
vladp 75643ae84c dev change sort user all files
Test Workflow / test (push) Successful in 3s
2026-04-14 11:06:07 +07:00
vladp 7df791f9b8 dev change sort user all files
Test Workflow / test (push) Successful in 16s
2026-04-13 19:19:34 +07:00
vladp b518504e58 dev change sort user all files
Test Workflow / test (push) Successful in 5s
2026-04-13 19:09:30 +07:00
vladp 3558588da9 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:47:28 +07:00
vladp 6e8ada2429 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:38:10 +07:00
vladp 64dce8601a dev change sort user all files
Test Workflow / test (push) Successful in 12s
2026-04-13 18:25:33 +07:00
vladp 86fa3b555a dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:20:38 +07:00
vladp e089d370cf dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 18:03:03 +07:00
vladp 28ddf49af1 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 17:54:46 +07:00
vladp 93946cc2e3 dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 17:45:48 +07:00
vladp 671625e0cf dev change sort user all files
Test Workflow / test (push) Successful in 4s
2026-04-13 17:30:32 +07:00
vladp 40c9b2a796 dev change sort user all files
Test Workflow / test (push) Successful in 5s
2026-04-13 17:16:32 +07:00
vladp b859b175f0 dev change user status after download passports
Test Workflow / test (push) Successful in 4s
2026-04-13 15:41:25 +07:00
vladp d827e2ca16 dev change sort for monitoring
Test Workflow / test (push) Successful in 4s
2026-04-13 15:35:49 +07:00
vladp 1ae2819245 dev change defolt sort
Test Workflow / test (push) Successful in 11s
2026-04-13 15:06:00 +07:00
vladp dbefc21e6d dev add token spent logic
Test Workflow / test (push) Successful in 5s
2026-04-13 15:04:22 +07:00
vladp e32e2d990b dev add token spent logic
Test Workflow / test (push) Successful in 5s
2026-04-13 14:20:13 +07:00
vladp 3896477783 dev add token spent logic
Test Workflow / test (push) Successful in 5s
2026-04-13 14:04:19 +07:00
vladp b5d4509769 dev add token spent logic
Test Workflow / test (push) Waiting to run
2026-04-13 12:15:44 +07:00
vladp aa6c0c1b94 dev add token spent logic
Test Workflow / test (push) Successful in 4s
2026-04-10 20:13:59 +07:00
vladp 31140e810b dev delete async
Test Workflow / test (push) Successful in 3s
2026-04-09 11:03:31 +07:00
vladp aef2c1dfe0 dev add verified notifications
Test Workflow / test (push) Successful in 1m29s
2026-04-08 21:58:28 +07:00
vladp c1360fce87 dev add verified notifications
Test Workflow / test (push) Successful in 6s
2026-04-08 11:31:49 +07:00
vladp 9e4035bf83 dev add violation link
Test Workflow / test (push) Successful in 3s
2026-04-07 18:09:14 +07:00
vladp b5de6c04f7 dev add violation link
Test Workflow / test (push) Successful in 2s
2026-04-07 16:59:30 +07:00
vladp a8a7a78ebf dev add violation link
Test Workflow / test (push) Successful in 2s
2026-04-07 16:43:08 +07:00
vladp 29ed74745f dev add violation link
Test Workflow / test (push) Successful in 4s
2026-04-07 15:35:32 +07:00
vladp 9e81b45922 dev change key
Test Workflow / test (push) Successful in 4s
2026-04-07 14:42:02 +07:00
vladp c7c3bbe07a Revert "dev change key"
Test Workflow / test (push) Successful in 2s
This reverts commit 4fdd14f81d.
2026-04-07 14:26:11 +07:00
vladp 0f71f5d58b Revert "dev change key"
This reverts commit 4ffd830f09.
2026-04-07 14:26:11 +07:00
vladp 4ffd830f09 dev change key
Test Workflow / test (push) Successful in 3s
2026-04-07 13:54:43 +07:00
vladp 4fdd14f81d dev change key
Test Workflow / test (push) Successful in 3s
2026-04-07 13:45:08 +07:00
vladp 8f2127bed5 dev change key
Test Workflow / test (push) Successful in 3s
2026-04-07 12:57:30 +07:00
vladp b71ba527d2 dev change key
Test Workflow / test (push) Successful in 2s
2026-04-07 12:01:25 +07:00
vladp e71ce9bc10 dev add delete method
Test Workflow / test (push) Successful in 2s
2026-04-07 11:48:20 +07:00
vladp d8f6a08a2f dev add delete method
Test Workflow / test (push) Successful in 11s
2026-04-07 10:08:53 +07:00
vladp 077902f4ab dev add lawcase logic
Test Workflow / test (push) Successful in 4s
2026-04-06 14:51:00 +07:00
vladp 5f063354e6 dev add updateTime
Test Workflow / test (push) Successful in 4s
2026-04-06 12:37:49 +07:00
vladp 48a83591da dev add load and download file for moderatuion user
Test Workflow / test (push) Has been cancelled
2026-04-04 18:00:50 +07:00
vladp 724e72c71a dev add load and download file for moderatuion user
Test Workflow / test (push) Waiting to run
2026-04-04 17:38:20 +07:00
vladp f4fc1f26a7 dev add load and download file for moderatuion user
Test Workflow / test (push) Waiting to run
2026-04-04 15:31:51 +07:00
vladp be1ad7f066 dev add notification entity
Test Workflow / test (push) Has been cancelled
2026-04-04 11:26:35 +07:00
vladp 7cd2aa7d32 Merge branch 'dev' into NCBACK-35
# Conflicts:
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
2026-04-04 11:23:05 +07:00
vladp 6bffd5b385 dev add notification entity
Test Workflow / test (push) Waiting to run
2026-04-04 00:52:01 +07:00
vladp 283f0b9e6f dev add notification entity
Test Workflow / test (push) Waiting to run
2026-04-03 15:42:45 +07:00
vladp a9eda37aad dev add notification entity
Test Workflow / test (push) Successful in 5s
2026-04-03 12:29:09 +07:00
vladp ecd42ce263 dev add notification entity
Test Workflow / test (push) Successful in 4s
2026-04-02 12:43:11 +07:00
vladp 9d33c7532a dev add notification entity
Test Workflow / test (push) Successful in 3s
2026-04-02 12:18:47 +07:00
vladp 0f2908b037 dev add notification entity
Test Workflow / test (push) Successful in 4s
2026-04-02 11:57:34 +07:00
vladp 37abdeca9a dev add notification entity
Test Workflow / test (push) Successful in 3s
2026-04-02 11:26:59 +07:00
vladp e87c14198b dev add notification entity
Test Workflow / test (push) Successful in 3s
2026-04-02 11:24:26 +07:00
vladp 8bc95efa2b dev add notification entity
Test Workflow / test (push) Successful in 4s
2026-04-02 11:01:09 +07:00
vladp 051f1ef5d4 dev add notification entity
Test Workflow / test (push) Successful in 2s
2026-04-01 15:32:36 +07:00
vladp 549d248cb7 dev add notification entity
Test Workflow / test (push) Successful in 14s
2026-04-01 11:43:06 +07:00
vladp 9a3a8bde05 dev add notification entity
Test Workflow / test (push) Waiting to run
2026-03-31 15:31:59 +07:00
vladp 69e758a710 dev email send when tokens not found
Test Workflow / test (push) Successful in 3s
2026-03-31 14:43:34 +07:00
vladp d7874aa37a Merge branch 'dev' into NCBACK-35
# Conflicts:
#	README.md
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java
#	src/main/java/ru/soune/nocopy/service/file/FileCleanupService.java
#	src/main/java/ru/soune/nocopy/service/file/FileEntityService.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
#	src/main/java/ru/soune/nocopy/service/file/impl/ProtectionFileProviderImpl.java
2026-03-31 14:24:03 +07:00
vladp ad4850414a dev email send when tokens not found
Test Workflow / test (push) Waiting to run
2026-03-31 13:31:26 +07:00
vladp 3d2c7ecb05 dev email send when tokens not found
Test Workflow / test (push) Waiting to run
2026-03-31 13:06:27 +07:00
vladp 51170891ce dev email send when tokens not found
Test Workflow / test (push) Waiting to run
2026-03-31 11:52:39 +07:00
vladp d775802a99 dev email send when tokens not found
Test Workflow / test (push) Waiting to run
2026-03-31 11:35:19 +07:00
vladp dfb1fca8cd dev email send when tokens not found
Test Workflow / test (push) Waiting to run
2026-03-31 11:34:41 +07:00
vladp 3f375d3b12 dev email send when tokens not found
Test Workflow / test (push) Waiting to run
2026-03-31 11:24:43 +07:00
vladp a7b9f22939 dev email send when tokens not found
Test Workflow / test (push) Successful in 3s
2026-03-30 14:42:51 +07:00
vladp e78f882417 dev email send when tokens not found
Test Workflow / test (push) Successful in 2s
2026-03-30 14:28:56 +07:00
vladp 2445b70c7a dev email send when tokens not found
Test Workflow / test (push) Successful in 2s
2026-03-30 13:48:10 +07:00
vladp 41ac8933e9 dev email send when tokens not found
Test Workflow / test (push) Successful in 2s
2026-03-30 13:36:57 +07:00
vladp 7af909c4f2 dev email send when tokens not found
Test Workflow / test (push) Successful in 2s
2026-03-30 13:21:11 +07:00
vladp 976d1f751e dev email send when tokens not found
Test Workflow / test (push) Successful in 6s
2026-03-30 12:48:11 +07:00
vladp dbef82d896 dev email send when tokens not found
Test Workflow / test (push) Successful in 2s
2026-03-30 12:29:53 +07:00
vladp 7e1877ffc3 dev email send when tokens not found
Test Workflow / test (push) Successful in 6s
2026-03-30 12:14:06 +07:00
vladp 8ce07c6b81 dev add reason entity
Test Workflow / test (push) Successful in 5s
2026-03-30 11:31:31 +07:00
vladp fa1892585a dev add reason entity
Test Workflow / test (push) Successful in 4s
2026-03-27 22:26:22 +07:00
vladp 6d6721fb0a dev add reason entity
Test Workflow / test (push) Waiting to run
2026-03-27 21:54:36 +07:00
vladp 535be69840 dev add reason entity
Test Workflow / test (push) Successful in 2s
2026-03-27 19:35:55 +07:00
vladp fdea57d947 dev add reason entity
Test Workflow / test (push) Successful in 1s
2026-03-27 19:29:54 +07:00
vladp 0aa7f5c865 dev add reason entity
Test Workflow / test (push) Successful in 2s
2026-03-27 19:08:15 +07:00
vladp 067b967a17 dev add reason entity
Test Workflow / test (push) Successful in 2s
2026-03-27 17:54:04 +07:00
vladp 9a8a4a72f4 dev add reason entity
Test Workflow / test (push) Successful in 3s
2026-03-27 17:31:06 +07:00
vladp 964910ec69 dev add reason entity
Test Workflow / test (push) Successful in 2s
2026-03-27 16:37:20 +07:00
vladp 547e70ecdd dev add reason entity
Test Workflow / test (push) Successful in 3s
2026-03-27 15:31:45 +07:00
vladp 78b97b48c9 dev add reason entity
Test Workflow / test (push) Successful in 2s
2026-03-27 14:31:49 +07:00
vladp 4e0579c952 dev add reason entity
Test Workflow / test (push) Successful in 2s
2026-03-27 14:20:09 +07:00
vladp 57dcc12cf2 dev add new status fo file
Test Workflow / test (push) Successful in 3s
2026-03-27 13:37:37 +07:00
vladp a01adfc848 dev add new status fo file
Test Workflow / test (push) Successful in 3s
2026-03-26 14:42:38 +07:00
vladp 8965ffdd1f dev add new status fo file
Test Workflow / test (push) Successful in 3s
2026-03-26 14:34:34 +07:00
vladp a21eb899b3 dev add new status fo file
Test Workflow / test (push) Successful in 3s
2026-03-26 11:40:32 +07:00
vladp 95ebcf0241 dev add new status fo file
Test Workflow / test (push) Successful in 3s
2026-03-25 16:31:21 +07:00
vladp a3e6e0091a dev add new status fo file
Test Workflow / test (push) Successful in 4s
2026-03-25 16:24:19 +07:00
vladp 2f38a7c455 dev add new status fo file
Test Workflow / test (push) Successful in 3s
2026-03-25 16:12:26 +07:00
vladp 17aa52d4ac dev add new status fo file
Test Workflow / test (push) Successful in 4s
2026-03-25 15:55:16 +07:00
vladp 8eb0772196 dev add additional info user
Test Workflow / test (push) Successful in 3s
2026-03-25 13:08:10 +07:00
vladp c1782cf59b dev add additional info user
Test Workflow / test (push) Successful in 4s
2026-03-25 12:46:33 +07:00
vladp 46cf252699 dev add additional info user
Test Workflow / test (push) Successful in 4s
2026-03-25 12:11:25 +07:00
vladp ebcb68135b dev add additional info user
Test Workflow / test (push) Successful in 4s
2026-03-25 12:00:04 +07:00
vladp e68ad3d3d3 dev add additional info user
Test Workflow / test (push) Waiting to run
2026-03-25 11:52:01 +07:00
vladp 12e001211b dev add additional info user
Test Workflow / test (push) Successful in 11s
2026-03-24 16:16:16 +07:00
vladp 40f8effb31 dev add additional info user
Test Workflow / test (push) Successful in 11s
2026-03-24 11:37:25 +07:00
vladp 69c3d876ae dev add complaint entity
Test Workflow / test (push) Successful in 3s
2026-03-23 15:17:42 +07:00
vladp 610415951d dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 19:27:13 +07:00
vladp 9ca116856c dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 18:44:03 +07:00
vladp 86db70caf4 dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 18:36:54 +07:00
vladp 0b2ef108ca dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 18:23:53 +07:00
vladp 0fbe725eb9 dev add violation notion
Test Workflow / test (push) Successful in 7s
2026-03-20 17:37:01 +07:00
vladp 29cdbb887f dev add violation notion 2026-03-20 17:37:00 +07:00
vladp 2bf5e99fbb dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 17:33:09 +07:00
vladp 655e8f0844 dev add violation notion
Test Workflow / test (push) Successful in 2s
2026-03-20 17:13:22 +07:00
vladp 31aa1c0545 dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 16:43:11 +07:00
vladp 97d6c8317e dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 16:35:35 +07:00
vladp a42f3e8ed8 dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 16:28:23 +07:00
vladp 46c75deacb dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 16:21:47 +07:00
vladp 4930a73762 dev add violation notion
Test Workflow / test (push) Successful in 3s
2026-03-20 16:08:32 +07:00
vladp 726da25ddf Revert "dev add violation notion"
Test Workflow / test (push) Successful in 3s
This reverts commit 4ff5c5ec13.
2026-03-20 16:02:11 +07:00
vladp a9b7b7ca6e Revert "dev add violation notion"
This reverts commit b6656961de.
2026-03-20 16:02:10 +07:00
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 daef30ec11 NCBACK-35 add worked logic for save protected files
Test Workflow / test (push) Successful in 4s
2026-02-16 16:05:15 +07:00
vladp a25b33fe13 NCBACK-35 add worked logic for save protected files
Test Workflow / test (push) Successful in 4s
2026-02-16 15:04:58 +07:00
vladp 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 2985d84b04 NCBACK-35 add cloud for save when download last chunk and after protected
Test Workflow / test (push) Successful in 3s
2026-02-13 21:11:30 +07:00
vladp 12aecff5c9 dev add inn check for company
Test Workflow / test (push) Successful in 2s
2026-02-13 16:25:04 +07:00
vladp 03a330b570 NCBACK-46 fix file name
Test Workflow / test (push) Successful in 3s
2026-02-12 16:03:39 +07:00
vladp b372180f83 NCBACK-42 add dadata handler
Test Workflow / test (push) Successful in 3s
2026-02-12 15:36:45 +07:00
vladp 920fc67962 dev only unige result
Test Workflow / test (push) Successful in 3s
2026-02-12 12:07:41 +07:00
vladp fb2ac0cf03 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-12 11:58:05 +07:00
vladp 8a2be0d310 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 19:50:07 +07:00
vladp 0743000c10 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 19:15:16 +07:00
vladp 640deef98d dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 18:47:58 +07:00
vladp 28b5a64a33 dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 18:28:57 +07:00
vladp 6818b74307 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 18:12:44 +07:00
vladp 6b73d01aeb dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 17:59:41 +07:00
vladp 1cb9edd5bf dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 17:53:02 +07:00
vladp 081ca05f88 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 17:41:48 +07:00
vladp 970e0fa615 dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 16:24:19 +07:00
vladp 52e1a7dd02 dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 20:50:06 +07:00
vladp 19330d853d dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 20:40:55 +07:00
vladp 1472c342f5 dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 19:14:16 +07:00
vladp 1ced86ff8c dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 18:55:21 +07:00
vladp 8149c450b2 dev add pdf docs protect
Test Workflow / test (push) Successful in 4s
2026-02-10 18:38:46 +07:00
vladp fea2d5ad89 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 16:23:49 +07:00
vladp 2ef1619ef9 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 16:10:05 +07:00
vladp 266bcaefcd dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 15:55:20 +07:00
vladp bf48725244 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:47:08 +07:00
vladp 86f7fc593a dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:26:44 +07:00
vladp 90be1d9d2b dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:03:19 +07:00
vladp 9692fe48b7 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:52:36 +07:00
vladp d50c565405 dev add utf encoding
Test Workflow / test (push) Successful in 4s
2026-02-10 12:46:45 +07:00
vladp 98fc145f1b dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:36:52 +07:00
vladp e7e870ee30 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:33:07 +07:00
vladp 8103960b45 dev add pagination for search in internet images
Test Workflow / test (push) Successful in 3s
2026-02-10 12:16:31 +07:00
vladp 792e0048cc dev add pagination for search in internet images
Test Workflow / test (push) Successful in 4s
2026-02-10 11:58:32 +07:00
vladp 33883faa98 dev split tokens
Test Workflow / test (push) Successful in 2s
2026-02-09 17:25:06 +07:00
vladp 7cc744829b dev split tariff by account types
Test Workflow / test (push) Successful in 3s
2026-02-09 17:16:08 +07:00
vladp 5a70c091f8 dev
Test Workflow / test (push) Successful in 5s
2026-02-09 15:29:55 +07:00
vladp 69aabe3619 dev
Test Workflow / test (push) Successful in 3s
2026-02-09 15:20:42 +07:00
vladp 27a8766171 dev fix name for parameters
Test Workflow / test (push) Successful in 2s
2026-02-09 14:15:29 +07:00
vladp 2bba70e583 dev fix name for parameters
Test Workflow / test (push) Successful in 3s
2026-02-09 13:50:42 +07:00
vladp 8e8193cda6 dev company users saw all company files
Test Workflow / test (push) Successful in 6s
2026-02-09 12:20:19 +07:00
vladp b96b50c01f add referal only for b2c
Test Workflow / test (push) Successful in 3s
2026-02-09 11:28:23 +07:00
vladp e926934473 add add sql scripts
Test Workflow / test (push) Successful in 3s
2026-02-09 11:24:34 +07:00
vladp a5003958a8 add set user limits
Test Workflow / test (push) Successful in 3s
2026-02-09 11:06:50 +07:00
vladp 5b73fd2c39 add document
Test Workflow / test (push) Has been cancelled
2026-02-07 11:54:34 +07:00
vladp daadc82bbb add document
Test Workflow / test (push) Successful in 3s
2026-02-06 22:21:51 +07:00
vladp 682c21ba73 fix value
Test Workflow / test (push) Successful in 2s
2026-02-06 21:22:47 +07:00
vladp 1c7a33be32 fix value
Test Workflow / test (push) Successful in 4s
2026-02-06 20:43:27 +07:00
vladp aceb70484f fix value
Test Workflow / test (push) Successful in 3s
2026-02-06 20:18:43 +07:00
backdev 3fc83e5458 Merge pull request 'NCBACK-36_1' (#19) from NCBACK-36_1 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #19
2026-02-06 20:27:56 +08:00
vladp a7e94639d2 NC_BACK-36_1
Test Workflow / test (push) Successful in 3s
2026-02-06 19:27:25 +07:00
vladp f52ce6db3c dev add referral
Test Workflow / test (push) Successful in 3s
2026-02-06 01:01:34 +07:00
vladp 80c53515fb dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 20:23:33 +07:00
vladp 511da8321b dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 20:21:27 +07:00
vladp df1fd89c29 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:47:32 +07:00
vladp 90e3134e98 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:33:37 +07:00
vladp 130fd184b9 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:02:17 +07:00
vladp f90b8a0bce dev add stats for search check
Test Workflow / test (push) Successful in 3s
2026-02-05 18:55:54 +07:00
vladp adc978bea8 dev write off for search
Test Workflow / test (push) Successful in 3s
2026-02-05 17:25:50 +07:00
vladp 7bb48f3944 dev add test find by searchapo
Test Workflow / test (push) Successful in 3s
2026-02-05 17:16:16 +07:00
milidev 27b812b0da NCBACK-36: [Referral] Referral Service
Test Workflow / test (push) Successful in 3s
2026-02-05 15:43:42 +07:00
vladp 6643070999 dev add test find by searchapo
Test Workflow / test (push) Successful in 8s
2026-02-05 12:44:27 +07:00
vladp 4395d276ed dev add test find by searchapo
Test Workflow / test (push) Successful in 4s
2026-02-05 10:47:40 +07:00
vladp e49a944b05 dev add test find by searchapo
Test Workflow / test (push) Successful in 3s
2026-02-05 03:30:15 +07:00
vladp 8e716f7d20 dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 03:27:16 +07:00
vladp 2e8080183c dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 00:46:17 +07:00
vladp dc9215523b dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 00:37:59 +07:00
vladp 72691511fa dev add
Test Workflow / test (push) Successful in 3s
2026-02-04 14:56:19 +07:00
vladp 135d94e0ef dev add
Test Workflow / test (push) Successful in 4s
2026-02-04 13:41:09 +07:00
vladp d1bacb4ac7 dev add
Test Workflow / test (push) Successful in 9s
2026-02-04 12:50:35 +07:00
vladp 7a809e6b8c dev add
Test Workflow / test (push) Successful in 4s
2026-02-04 11:47:43 +07:00
vladp 06a6196a26 dev add
Test Workflow / test (push) Successful in 10s
2026-02-03 16:01:32 +07:00
vladp 13324d4628 dev add tarifinfo by permission for user info
Test Workflow / test (push) Successful in 4s
2026-02-03 15:05:03 +07:00
vladp c38b86e1b9 dev add tariffinfo endpoints
Test Workflow / test (push) Successful in 4s
2026-02-02 15:47:58 +07:00
vladp 3ce8edd7e1 dev add tariff endpoints
Test Workflow / test (push) Successful in 3s
2026-02-02 15:15:43 +07:00
vladp 22b487da22 dev add tariff endpoints
Test Workflow / test (push) Successful in 4s
2026-02-02 15:00:18 +07:00
vladp 4395654b0d dev add apllication tariff fields
Test Workflow / test (push) Successful in 3s
2026-02-02 12:19:34 +07:00
vladp 7dd057603d dev
Test Workflow / test (push) Successful in 2s
2026-01-30 22:36:20 +07:00
vladp a4858cf6c5 dev
Test Workflow / test (push) Successful in 3s
2026-01-30 22:30:27 +07:00
vladp 7d52276b31 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 19:27:01 +07:00
vladp 8fe8c566c0 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 5s
2026-01-30 18:04:08 +07:00
vladp 36d40a0b8d dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 4s
2026-01-30 18:03:51 +07:00
vladp 32f0a07e32 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 18:02:34 +07:00
vladp 51ef63e8cd dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:55:14 +07:00
vladp b0237fd812 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:51:59 +07:00
vladp de8c53784b dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:39:19 +07:00
vladp 3cd6b5396c dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 17:36:57 +07:00
vladp 615924ea9e dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:29:25 +07:00
vladp 9a8aa498c0 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 15:09:39 +07:00
vladp 66633d4964 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 14:24:23 +07:00
vladp ce8c63475f dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 14:00:21 +07:00
vladp 30c05b6b24 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 13:02:30 +07:00
vladp 49dc19cd04 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 12:52:16 +07:00
vladp fb5642db9e dev check null company
Test Workflow / test (push) Successful in 2s
2026-01-29 21:46:37 +07:00
vladp c539b1fbc2 dev check null company
Test Workflow / test (push) Successful in 2s
2026-01-29 21:31:28 +07:00
vladp 61b8ea902b dev limit for yasearch result
Test Workflow / test (push) Successful in 1s
2026-01-29 20:13:10 +07:00
vladp 6ee7ab4d04 dev add rest for company, add link for users , update reg users
Test Workflow / test (push) Successful in 2s
2026-01-29 19:24:36 +07:00
vladp 96acf796b2 dev change status on Processing for prePresist
Test Workflow / test (push) Successful in 3s
2026-01-29 16:25:34 +07:00
vladp ff7836900b dev fix show image
Test Workflow / test (push) Successful in 2s
2026-01-29 13:08:43 +07:00
vladp ec20d68261 dev add check limits logic
Test Workflow / test (push) Successful in 3s
2026-01-28 23:25:16 +07:00
vladp 31fac0ab93 dev check to protect
Test Workflow / test (push) Successful in 3s
2026-01-28 21:18:42 +07:00
vladp a32f089989 dev add info for files
Test Workflow / test (push) Successful in 2s
2026-01-28 19:26:40 +07:00
vladp c019f23777 dev download only protected files
Test Workflow / test (push) Successful in 3s
2026-01-28 18:38:43 +07:00
vladp 02bf7868ee dev add split network and add new ports with apps
Test Workflow / test (push) Successful in 9s
2026-01-28 18:19:20 +07:00
vladp 2215adc37f dev
Test Workflow / test (push) Successful in 3s
2026-01-27 20:55:33 +07:00
vladp 6fcec8d67e dev
Test Workflow / test (push) Successful in 2s
2026-01-27 20:41:08 +07:00
vladp 4a185e6f9a dev
Test Workflow / test (push) Successful in 3s
2026-01-27 20:27:39 +07:00
vladp cf2f73955b dev
Test Workflow / test (push) Successful in 3s
2026-01-27 08:50:52 +07:00
vladp f67e172365 dev
Test Workflow / test (push) Successful in 3s
2026-01-27 08:25:38 +07:00
backdev d32e8dcd75 Merge pull request 'NCBACK-33' (#16) from NCBACK-33 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #16
2026-01-27 09:11:31 +08:00
vladp 5700adafb2 NCBACK-33
Test Workflow / test (push) Successful in 3s
2026-01-27 08:09:54 +07:00
vladp 816a984891 Merge branch 'dev' into NCBACK-33
# Conflicts:
#	src/main/java/ru/soune/nocopy/entity/file/ImageProtection.java
#	src/main/java/ru/soune/nocopy/entity/user/User.java
#	src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java
#	src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java
#	src/main/java/ru/soune/nocopy/service/file/impl/FileUploadServiceImpl.java
2026-01-27 05:46:50 +07:00
vladp 21e31c7d0f dev add google search by image
Test Workflow / test (push) Successful in 3s
2026-01-27 05:41:47 +07:00
vladp e7c6df12d6 dev update file ext
Test Workflow / test (push) Successful in 3s
2026-01-26 22:33:49 +07:00
vladp aa87ae32bc dev update search methods,update find endpoints
Test Workflow / test (push) Successful in 3s
2026-01-26 21:03:45 +07:00
vladp 9ba3d8b973 dev fix pipline
Test Workflow / test (push) Successful in 4s
2026-01-26 21:00:22 +07:00
vladp 300383e523 dev
Test Workflow / test (push) Successful in 2s
2026-01-26 14:48:41 +07:00
vladp 329000d284 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:43:57 +07:00
vladp 4c8c4bb5c1 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:28:47 +07:00
vladp 82eedb02d1 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:20:00 +07:00
vladp b2993e00a1 dev
Test Workflow / test (push) Successful in 2s
2026-01-26 14:14:56 +07:00
vladp 6197b26a81 dev
Test Workflow / test (push) Successful in 3s
2026-01-26 14:08:44 +07:00
vladp 0e2ef1d2e6 dev add indexes
Test Workflow / test (push) Successful in 3s
2026-01-26 14:02:10 +07:00
vladp 3e5eef3b42 dev add indexes
Test Workflow / test (push) Successful in 5s
2026-01-26 11:41:21 +07:00
vladp 89f83054ef NCBACK-25
Test Workflow / test (push) Successful in 3s
2026-01-25 22:05:19 +07:00
vladp f8234291b1 NCBACK-25 delete endpoint
Test Workflow / test (push) Successful in 3s
2026-01-25 21:38:56 +07:00
vladp 4cf36d7b17 NCBACK-25 change limits
Test Workflow / test (push) Successful in 3s
2026-01-25 21:10:17 +07:00
vladp 6dacbde522 NCBACK-25 fix check duplicate exception
Test Workflow / test (push) Successful in 3s
2026-01-25 20:45:29 +07:00
vladp 0a4ab562a4 NCBACK-25 fix check duplicate exception
Test Workflow / test (push) Successful in 3s
2026-01-25 20:08:01 +07:00
vladp 76103b9d96 NCBACK-25 fix check duplicate exception
Test Workflow / test (push) Successful in 3s
2026-01-25 19:59:16 +07:00
vladp 69a79b7ac7 NCBACK-25 merge to dev
Test Workflow / test (push) Successful in 4s
2026-01-25 18:59:55 +07:00
vladp c97f205a17 Merge branch 'NCBACK-25' into dev
# Conflicts:
#	src/main/java/ru/soune/nocopy/repository/SimilarImageProjection.java
#	src/main/java/ru/soune/nocopy/service/FileSimilarityService.java
2026-01-25 18:41:05 +07:00
vladp ab3eef1668 NCBACK-25 change path to file
Test Workflow / test (push) Successful in 3s
2026-01-25 12:10:56 +07:00
vladp 85449d73fd fix
Test Workflow / test (push) Successful in 5s
2026-01-24 12:23:05 +07:00
vladp 859239e77a fix
Test Workflow / test (push) Successful in 7s
2026-01-24 12:22:15 +07:00
vladp 63c3794aa4 fix
Test Workflow / test (push) Successful in 4s
2026-01-24 11:34:30 +07:00
vladp 349edbeb91 fix
Test Workflow / test (push) Successful in 5s
2026-01-24 11:32:02 +07:00
vladp e26e50454a Merge branch 'dev' into NCBACK-33
Test Workflow / test (push) Successful in 5s
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/dto/MessageCode.java
#	src/main/resources/application.yaml
2026-01-24 11:23:06 +07:00
vladp 8d026a485f dev
Test Workflow / test (push) Successful in 5s
2026-01-24 11:09:49 +07:00
vladp 204e13ca85 Merge branch 'NCBACK-34' into dev
Test Workflow / test (push) Successful in 4s
2026-01-24 11:07:20 +07:00
vladp 79b13731a6 NCBACK-34
Test Workflow / test (push) Successful in 4s
2026-01-24 10:59:02 +07:00
vladp 8555002415 NCBACK-33 fix check null user
Test Workflow / test (push) Successful in 1s
2026-01-24 00:45:57 +07:00
vladp 4560925183 NCBACK-25
Test Workflow / test (push) Successful in 3s
2026-01-23 16:09:51 +07:00
vladp 4314032a3b Merge remote-tracking branch 'origin/dev' into dev
Test Workflow / test (push) Successful in 3s
2026-01-23 14:58:09 +07:00
vladp d638819ee9 NCBACK-25
Test Workflow / test (push) Successful in 2s
2026-01-23 13:25:56 +07:00
vladp 51654fd060 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 2s
2026-01-23 13:22:02 +07:00
vladp e37cc06cd0 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 3s
2026-01-22 20:53:25 +07:00
vladp 5a3e26b6a3 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 4s
2026-01-22 20:52:45 +07:00
vladp 409c677ba6 NCBACK-25 add protection for audio and use hash method from library
Test Workflow / test (push) Successful in 3s
2026-01-22 19:55:09 +07:00
vladp 95deda12f3 Merge branch 'refs/heads/dev' into NCBACK-25
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/entity/file/FileEntity.java
2026-01-22 14:15:09 +07:00
milidev c2a780db2d referral module init commit
Test Workflow / test (push) Successful in 8s
2026-01-22 13:00:21 +07:00
vladp ad9cbd96f6 NCBACK-34 update dev
Test Workflow / test (push) Successful in 4s
2026-01-22 11:24:37 +07:00
vladp 211b99b986 Merge branch 'dev' into NCBACK-34
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
2026-01-22 11:23:58 +07:00
vladp d1b5b6eb5c add check user
Test Workflow / test (push) Successful in 3s
2026-01-21 12:34:40 +07:00
vladp 251bb37105 fix authService
Test Workflow / test (push) Successful in 4s
2026-01-21 12:21:14 +07:00
vladp 1fc065a46b Сохранение 2026-01-21 12:02:38 +07:00
vladp 60678ef889 Dev add support id
Test Workflow / test (push) Successful in 4s
2026-01-20 21:55:42 +07:00
backdev f0a87648cf Merge pull request 'NCBACK-27' (#12) from NCBACK-27 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #12
2026-01-20 16:20:38 +08:00
vladp c2ffcdcd6e Merge branch 'dev' into NCBACK-27
Test Workflow / test (push) Successful in 3s
# Conflicts:
#	src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java
2026-01-20 15:20:10 +07:00
vladp f9accd7af1 NCBACK-33 add verification logic
Test Workflow / test (push) Successful in 3s
2026-01-20 15:12:24 +07:00
vladp 55fa8b84e2 NCBACK-33 add email send
Test Workflow / test (push) Successful in 4s
2026-01-20 03:47:51 +07:00
vladp 4a8e561258 NCBACK-33 add email send
Test Workflow / test (push) Successful in 2s
2026-01-19 19:26:59 +07:00
vladp 0c0fadf5ec NCBACK-34 fix send exception
Test Workflow / test (push) Successful in 3s
2026-01-19 15:49:27 +07:00
vladp e979712b7c Merge branch 'dev' into NCBACK-34 2026-01-17 01:31:21 +07:00
vladp 5027920199 dev info for docker user
Test Workflow / test (push) Has been cancelled
2026-01-16 23:59:07 +07:00
vladp 4704f780e5 NCBACK-33 add start logic for verify by token
Test Workflow / test (push) Successful in 4s
2026-01-16 20:20:14 +07:00
vladp 3d679440f6 NCBACK-34 add logic for clear does not exist files,add dto for similar file search controller
Test Workflow / test (push) Successful in 4s
2026-01-16 14:30:45 +07:00
vladp 4a45f40e9f NCBACK-34 need fix delete filentity and hash
Test Workflow / test (push) Successful in 4s
2026-01-15 20:41:35 +07:00
vladp bbb0d7b17a Merge branch 'dev' into NCBACK-34 2026-01-15 16:55:47 +07:00
vladp 0ed3f958ff DEV_DB change db creds
Test Workflow / test (push) Successful in 7s
2026-01-15 16:52:33 +07:00
vladp de810e32b4 DEV_DB change db creds
Test Workflow / test (push) Successful in 4s
2026-01-15 16:42:37 +07:00
vladp 1a6823f7a9 DEV_DB change db creds
Test Workflow / test (push) Successful in 4s
2026-01-15 16:17:03 +07:00
vladp 82a57a2983 NCBACK-32 add all case for json
Test Workflow / test (push) Successful in 4s
2026-01-15 11:09:05 +07:00
vladp d3e50aa8d1 NCBACK-19 add all case for json
Test Workflow / test (push) Successful in 3s
2026-01-14 19:10:22 +07:00
vladp 5c6309e228 NCBACK-33 fix check company name
Test Workflow / test (push) Successful in 3s
2026-01-14 18:47:34 +07:00
vladp 8ac73ff045 NCBACK-34 add logic to find similar image in db
Test Workflow / test (push) Successful in 3s
2026-01-13 22:40:52 +07:00
vladp 20e1ab321d NCBACK-25 work version for protect file
Test Workflow / test (push) Successful in 3s
2026-01-13 13:28:09 +07:00
vladp 2b7a8b97e1 NCBACK-27 fix token life
Test Workflow / test (push) Successful in 3s
2026-01-10 02:09:39 +07:00
vladp 344ec075d4 Dev
Test Workflow / test (push) Successful in 2s
2026-01-10 01:19:30 +07:00
vladp 7b8546fd4d Dev
Test Workflow / test (push) Successful in 2s
2026-01-10 01:06:32 +07:00
vladp eb12e219ec Dev
Test Workflow / test (push) Successful in 2s
2026-01-10 00:54:49 +07:00
vladp 920cde3cdf Dev 2026-01-10 00:51:03 +07:00
backdev 5b4dca23fb Merge pull request 'NCBACK-3' (#14) from NCBACK-3 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #14
2026-01-10 01:43:36 +08:00
vladp 7b1ed5fe67 NCBACK-3
Test Workflow / test (push) Successful in 3s
2026-01-10 00:35:29 +07:00
backdev 8102d72982 Merge pull request 'NCBACK-3 Add found by yandex image' (#10) from NCBACK-3 into dev
Test Workflow / test (push) Successful in 10s
Reviewed-on: #10
2026-01-10 01:03:33 +08:00
vladp b3a3d57ee3 NCBACK-3
Test Workflow / test (push) Successful in 3s
2026-01-09 23:41:19 +07:00
vladp d896f23872 add user_files_info for avaibale actions
Test Workflow / test (push) Successful in 2s
2026-01-09 23:05:37 +07:00
vladp 3e06040123 NCBACK-27 fix build
Test Workflow / test (push) Successful in 3s
2026-01-09 22:55:06 +07:00
vladp 18d35cb0fb Merge branch 'dev' into NCBACK-27
Test Workflow / test (push) Successful in 2s
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java
2026-01-09 12:41:32 +07:00
vladp edd508e5d8 Merge branch 'dev' into NCBACK-3
Test Workflow / test (push) Successful in 5s
# Conflicts:
#	docker-compose.yaml
#	src/main/java/ru/soune/nocopy/dto/MessageCode.java
2026-01-09 12:34:24 +07:00
vladp 63baaf7c80 update types
Test Workflow / test (push) Successful in 4s
2026-01-08 12:59:30 +07:00
vladp f136ac1f0a add methods and lib for protection files
Test Workflow / test (push) Successful in 4s
2026-01-08 03:22:32 +07:00
vladp 3a9a5b48a2 update types
Test Workflow / test (push) Successful in 4s
2026-01-07 14:10:35 +07:00
vladp 6fff5884d2 set chunk
Test Workflow / test (push) Successful in 3s
2026-01-07 13:16:53 +07:00
vladp f8673dd581 set chunk
Test Workflow / test (push) Successful in 3s
2026-01-07 12:50:40 +07:00
vladp b9a401f113 correct file_extensions
Test Workflow / test (push) Successful in 8s
2026-01-07 11:56:03 +07:00
vladp 918b4894fc add endpoint with files user info
Test Workflow / test (push) Successful in 6s
2026-01-06 17:27:38 +07:00
vladp bf4d1ca450 add endpoint with files user info
Test Workflow / test (push) Successful in 4s
2026-01-06 12:50:39 +07:00
vladp f1222ede57 add token life by hour
Test Workflow / test (push) Successful in 3s
2026-01-05 16:31:40 +07:00
vladp 0da0404488 add token life by hour
Test Workflow / test (push) Successful in 4s
2026-01-05 16:26:46 +07:00
vladp bd9ecd4f84 change chunk size
Test Workflow / test (push) Successful in 3s
2025-12-30 12:25:22 +07:00
vladp 0f73c8319e add ram size
Test Workflow / test (push) Successful in 4s
2025-12-30 12:08:23 +07:00
vladp 28b0356b03 fix check status deleted
Test Workflow / test (push) Successful in 3s
2025-12-30 02:19:07 +07:00
vladp 72a4516b7d set correct file types
Test Workflow / test (push) Successful in 4s
2025-12-30 01:05:59 +07:00
vladp 851676b895 set chunk-size to 5
Test Workflow / test (push) Successful in 2s
2025-12-26 20:32:15 +07:00
vladp 254fdc0fbd set chunk-size to 5
Test Workflow / test (push) Successful in 8s
2025-12-26 20:30:38 +07:00
vladp a33c53fa6b set chunk-size to 5
Test Workflow / test (push) Successful in 3s
2025-12-26 20:18:18 +07:00
vladp 86e7371ffe set chunk-size to 1000000
Test Workflow / test (push) Successful in 2s
2025-12-26 19:50:32 +07:00
vladp 9541e89651 add post size
Test Workflow / test (push) Successful in 2s
2025-12-26 19:40:08 +07:00
vladp 291586dbf4 add post size
Test Workflow / test (push) Successful in 2s
2025-12-26 19:29:47 +07:00
vladp 91e9b0c785 add post size
Test Workflow / test (push) Successful in 2s
2025-12-26 19:26:39 +07:00
vladp 4fa6b83a10 add post size
Test Workflow / test (push) Successful in 3s
2025-12-26 19:15:45 +07:00
vladp 2c1a2aeb16 add post size
Test Workflow / test (push) Successful in 3s
2025-12-26 19:10:43 +07:00
vladp 2a9f33068a add logic with hard delete
Test Workflow / test (push) Successful in 2s
2025-12-26 16:23:17 +07:00
vladp 1ebe8b852f add file extension to search
Test Workflow / test (push) Successful in 2s
2025-12-26 14:51:02 +07:00
vladp 84993dec00 fix page_size and filtered values
Test Workflow / test (push) Successful in 3s
2025-12-26 13:52:53 +07:00
vladp a2c20ae645 add exceptions
Test Workflow / test (push) Successful in 2s
2025-12-26 12:22:40 +07:00
vladp 18ec9b51f3 add clean logic for uploadsessions
Test Workflow / test (push) Successful in 3s
2025-12-25 15:27:45 +07:00
vladp 45b7acc774 fix after test
Test Workflow / test (push) Successful in 2s
2025-12-25 13:04:49 +07:00
vladp b2cf06a512 update name controllers ends
Test Workflow / test (push) Successful in 3s
2025-12-24 17:26:13 +07:00
vladp d842af2c62 add file size
Test Workflow / test (push) Waiting to run
2025-12-24 16:55:07 +07:00
vladp c5ef58ddd4 fix after test
Test Workflow / test (push) Successful in 3s
2025-12-24 14:20:49 +07:00
vladp f6d4ebfe0e fix after test
Test Workflow / test (push) Successful in 3s
2025-12-24 13:58:46 +07:00
vladp a77a51f519 delete photo
Test Workflow / test (push) Successful in 1s
2025-12-23 18:33:53 +07:00
vladp 1aef1dcd96 add chunk size
Test Workflow / test (push) Successful in 1s
2025-12-23 16:31:09 +07:00
backdev 7dc32d055b Merge pull request 'NCBACK-26 add docker container with storage' (#11) from NCBACK-26 into dev
Test Workflow / test (push) Successful in 0s
Reviewed-on: #11
2025-12-23 17:10:36 +08:00
vladp 19d8f61e1d NCBACK-26 add docker container with storage
Test Workflow / test (push) Successful in 1s
2025-12-23 16:05:51 +07:00
vladp 8fe4d854a6 add create uploads filedir
Test Workflow / test (push) Successful in 1s
2025-12-23 13:10:36 +07:00
vladp 4c607f5bf9 fix check format
Test Workflow / test (push) Successful in 6s
2025-12-23 12:46:59 +07:00
vladp ce879fea18 add format for all office,os
Test Workflow / test (push) Successful in 1s
2025-12-23 12:03:49 +07:00
vladp 33479ea9af add check extensions
Test Workflow / test (push) Successful in 3s
2025-12-22 17:17:48 +07:00
vladp ffb147d716 NCBACK-3 Add found by yandex image
Test Workflow / test (push) Successful in 4s
2025-12-22 14:42:27 +07:00
vladp 647fd6e1a7 NCBACK-3 Add found by yandex image
Test Workflow / test (push) Successful in 3s
2025-12-22 14:07:44 +07:00
vladp 76e8f4ab1d add image type
Test Workflow / test (push) Successful in 3s
2025-12-22 10:59:36 +07:00
backdev c03ca2566d Merge pull request 'add bone for file system' (#9) from NCBACK-15 into dev
Test Workflow / test (push) Successful in 9s
Reviewed-on: #9
2025-12-21 17:19:34 +08:00
vladp 1932784264 NCBACK-15
Test Workflow / test (push) Successful in 4s
2025-12-21 16:19:12 +07:00
vladp bd78b88f33 Merge branch 'refs/heads/dev' into NCBACK-15
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/ApiController.java
#	src/main/java/ru/soune/nocopy/dto/MessageCode.java
2025-12-21 16:17:42 +07:00
backdev 22c2eed8a1 Merge pull request 'NCBACK-13' (#8) from NCBACK-13 into dev
Test Workflow / test (push) Waiting to run
Reviewed-on: #8
2025-12-21 17:15:16 +08:00
vladp 31b90f942c add fileentity controller
Test Workflow / test (push) Successful in 4s
2025-12-18 14:40:27 +07:00
vladp d167e92d90 add fileentity controller
Test Workflow / test (push) Successful in 4s
2025-12-18 11:23:42 +07:00
vladp f3aa6324c2 add fileentity controller
Test Workflow / test (push) Successful in 3s
2025-12-18 01:13:19 +07:00
vladp e2747fc999 Merge branch 'NCBACK-18' into NCBACK-15
# Conflicts:
#	src/main/java/ru/soune/nocopy/controller/AuthController.java
#	src/main/java/ru/soune/nocopy/controller/file/FileUploadController.java
#	src/main/java/ru/soune/nocopy/dto/AuthResponse.java
#	src/main/java/ru/soune/nocopy/dto/LoginResponse.java
#	src/main/java/ru/soune/nocopy/dto/RegisterRequest.java
#	src/main/java/ru/soune/nocopy/exception/UserAlreadyExistsException.java
#	src/main/java/ru/soune/nocopy/service/AuthService.java
#	src/test/java/ru/soune/nocopy/service/AuthServiceTest.java
2025-12-17 22:41:01 +07:00
vladp 7e2dd4a654 fix path
Test Workflow / test (push) Successful in 3s
2025-12-17 22:37:46 +07:00
vladp a030837d45 update controller chunk endpoints
Test Workflow / test (push) Successful in 3s
2025-12-17 22:31:30 +07:00
vladp 11a7ab6a73 Изменения в FileType.java
Test Workflow / test (push) Successful in 3s
2025-12-17 13:49:22 +07:00
vladp 641e983b89 change package
Test Workflow / test (push) Waiting to run
2025-12-17 13:41:05 +07:00
vladp 9e8c7f0fa1 add fileentity
Test Workflow / test (push) Successful in 4s
2025-12-17 01:19:48 +07:00
vladp 58b4a2f95f delete not use dto's
Test Workflow / test (push) Successful in 2s
2025-12-16 15:58:52 +07:00
vladp 4a6de6ad9a add extensions,true path for file
Test Workflow / test (push) Successful in 2s
2025-12-16 14:27:03 +07:00
vladp b3f29935f6 save chunks
Test Workflow / test (push) Successful in 4s
2025-12-16 04:18:09 +07:00
vladp 997daae639 save chunks
Test Workflow / test (push) Successful in 4s
2025-12-16 03:44:59 +07:00
vladp 4ddf2f5dde add bone for file system
Test Workflow / test (push) Successful in 4s
2025-12-15 19:49:09 +07:00
500 changed files with 37423 additions and 1242 deletions
+11 -2
View File
@@ -1,10 +1,19 @@
# Postgres
POSTGRES_DB=no_copy_
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_USER=ncp_db
POSTGRES_PASSWORD=ncpDbApp
POSTGRES_PORT=5432
POSTGRES_HOST=postgres
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
# Backend server
SERVER_PORT=8080
# Postfix
MAIL_HOST_PROD=postfix-production
MAIL_PORT_PROD=25
MAIL_USERNAME_PROD=noreply@nocopy.com
SMTP_PASSWORD_PROD=nocopy!nocopy!
+495
View File
@@ -0,0 +1,495 @@
# Demo API Guide для Лэндинга
## Обзор
Demo API предоставляет функционал для демонстрации основных возможностей платформы NoCopy на лэндинге без необходимости регистрации пользователя.
## Ключевые Особенности
**Анонимная загрузка файлов** - без авторизации
**Хранение в S3** - безопасное облачное хранилище
**Автоматическая очистка** - удаление через 24 часа
**Ограничения по IP** - защита от злоупотреблений
**Размер лимитов** - до 50 MB на файл, 10 файлов в день
---
## API Эндпоинты
### 1. Загрузка файла
```
POST /api/demo/upload
Content-Type: multipart/form-data
```
**Параметры:**
- `file` (required) - файл для загрузки (jpg, png, pdf, docx, mp3 и т.д.)
**Примеры ответа:**
✅ Успех (200 OK):
```json
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "document.pdf",
"fileType": "document",
"fileSize": 1048576,
"status": "COMPLETED",
"message": "File uploaded successfully",
"uploadedBytes": 1048576,
"progressPercentage": 100
}
```
❌ Файл слишком большой (400):
```json
{
"sessionId": null,
"fileName": null,
"fileType": null,
"fileSize": 0,
"status": "FAILED",
"message": "File too large. Max size: 50 MB",
"uploadedBytes": 0,
"progressPercentage": 0
}
```
❌ Лимит на день превышен (400):
```json
{
"sessionId": null,
"status": "FAILED",
"message": "Daily limit reached. Max 10 files per day"
}
```
---
### 2. Получение информации о файле
```
GET /api/demo/file/{sessionId}
```
**Параметры:**
- `sessionId` (path) - ID сессии из ответа загрузки
**Ответ (200 OK):**
```json
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "document.pdf",
"fileType": "document",
"fileSize": 1048576,
"status": "COMPLETED",
"createdAt": "2025-07-01T10:30:00Z",
"expiresAt": "2025-07-02T10:30:00Z",
"searchResultsCount": null,
"mimeType": "application/pdf"
}
```
---
### 3. Запуск поиска похожих файлов
```
POST /api/demo/search/{sessionId}
```
**Параметры:**
- `sessionId` (path) - ID сессии
**Ответ (200 OK):**
```json
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"status": "COMPLETED",
"resultsCount": 3,
"matchPercentage": 92.5,
"startedAt": "2025-07-01T10:31:00Z",
"completedAt": "2025-07-01T10:31:05Z",
"processingTimeMs": 5000,
"similarFiles": [
{
"fileId": "demo_abc123",
"fileName": "similar_image_1.jpg",
"similarityScore": 0.95,
"source": "web"
},
{
"fileId": "demo_def456",
"fileName": "similar_image_2.png",
"similarityScore": 0.90,
"source": "database"
},
{
"fileId": "demo_ghi789",
"fileName": "duplicate_document.pdf",
"similarityScore": 0.85,
"source": "web"
}
],
"message": "Search completed successfully"
}
```
---
### 4. Очистка сессии
```
DELETE /api/demo/cleanup/{sessionId}
```
**Параметры:**
- `sessionId` (path) - ID сессии
**Ответ (200 OK):**
```json
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"status": "CLEANED",
"message": "Session cleaned successfully",
"progressPercentage": 100
}
```
---
### 5. Health Check
```
GET /api/demo/health
```
**Ответ (200 OK):**
```json
{
"status": "HEALTHY",
"message": "Demo service is ready"
}
```
---
## Лимиты и Ограничения
| Параметр | Значение | Описание |
|----------|----------|----------|
| Max файл | 50 MB | Максимальный размер одного файла |
| Файлов в день | 10 | Максимум файлов с одного IP в день |
| TTL сессии | 24 часа | Время жизни демо-сессии |
| Cleanup интервал | 30 минут | Как часто очищаются истекшие файлы |
| Поддерживаемые типы | image, document, audio | Типы файлов для демо |
---
## Поддерживаемые Типы Файлов
**Изображения:**
- JPG/JPEG (.jpg, .jpeg)
- PNG (.png)
- GIF (.gif)
- WebP (.webp)
**Документы:**
- PDF (.pdf)
- Word (.docx, .doc)
- Excel (.xlsx, .xls)
- Text (.txt)
**Аудио:**
- MP3 (.mp3)
- WAV (.wav)
- M4A (.m4a)
- OGG (.ogg)
---
## Примеры использования
### cURL
**Загрузка файла:**
```bash
curl -X POST http://localhost:8080/api/demo/upload \
-F "file=@document.pdf"
```
**Получение информации:**
```bash
curl http://localhost:8080/api/demo/file/550e8400-e29b-41d4-a716-446655440000
```
**Запуск поиска:**
```bash
curl -X POST http://localhost:8080/api/demo/search/550e8400-e29b-41d4-a716-446655440000
```
**Очистка:**
```bash
curl -X DELETE http://localhost:8080/api/demo/cleanup/550e8400-e29b-41d4-a716-446655440000
```
### JavaScript
**Загрузка файла:**
```javascript
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const response = await fetch('http://localhost:8080/api/demo/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
const sessionId = result.sessionId;
```
**Получение информации:**
```javascript
const info = await fetch(`http://localhost:8080/api/demo/file/${sessionId}`)
.then(r => r.json());
```
**Запуск поиска:**
```javascript
const search = await fetch(`http://localhost:8080/api/demo/search/${sessionId}`, {
method: 'POST'
})
.then(r => r.json());
```
### Python
**Загрузка файла:**
```python
import requests
files = {'file': open('document.pdf', 'rb')}
response = requests.post('http://localhost:8080/api/demo/upload', files=files)
result = response.json()
session_id = result['sessionId']
```
---
## Обработка Ошибок
### 400 Bad Request - Файл слишком большой
```json
{
"status": "FAILED",
"message": "File too large. Max size: 50 MB"
}
```
**Решение:** Загрузите файл меньшего размера
### 400 Bad Request - Неподдерживаемый тип файла
```json
{
"status": "FAILED",
"message": "File type not supported for demo"
}
```
**Решение:** Используйте поддерживаемый тип файла
### 400 Bad Request - Лимит на день
```json
{
"status": "FAILED",
"message": "Daily limit reached. Max 10 files per day"
}
```
**Решение:** Подождите до следующего дня или зарегистрируйтесь
### 404 Not Found - Сессия не найдена
```json
{
"status": "ERROR"
}
```
**Решение:** Проверьте правильность sessionId
### 500 Internal Server Error
```json
{
"status": "FAILED",
"message": "Upload failed: ..."
}
```
**Решение:** Попробуйте позже или свяжитесь с поддержкой
---
## Cleanup-сервис (важное объяснение)
### Что делает Cleanup-сервис?
Это автоматизированный сервис, который периодически:
1. **Ищет истекшие сессии** (старше 24 часов)
2. **Удаляет файлы из S3** для экономии места
3. **Помечает сессии как CLEANED** в БД
4. **Удаляет FAILED сессии** (старше 1 часа)
### Почему это нужно?
- **Экономия дискового пространства** - S3 стоит денег
- **Соблюдение GDPR/приватности** - данные не лежат вечно
- **Производительность БД** - не растёт бесконечно
- **Безопасность** - временные данные не остаются в системе
### Как это работает?
```
Каждые 30 минут:
┌─────────────────────────────────────┐
│ 1. Найти истекшие сессии │
│ WHERE expiresAt <= NOW() │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. Для каждой сессии: │
│ - Удалить из S3 │
│ - Обновить статус = CLEANED │
└─────────────────────────────────────┘
```
### В конфиге (application.yaml):
```yaml
demo:
max-file-size: 52428800 # 50 MB
max-files-per-day: 10 # макс 10 файлов в день
s3-path: demo/ # путь в S3
session-ttl-hours: 24 # 24 часа жизни сессии
cleanup-interval: 30 # cleanup каждые 30 минут
failed-cleanup-interval: 60 # очистка FAILED каждый час
```
---
## Статистика и Мониторинг
### Метрики для отслеживания
```java
// Количество активных демо-сессий
long activeSessions = demoCleanupService.getActiveDemoSessionsCount();
// Общий размер демо-данных в S3
long dataSizeInBytes = demoCleanupService.getActiveDemoDataSize();
long dataSizeInMB = dataSizeInBytes / (1024 * 1024);
```
### Логирование
Все операции логируются:
```
[INFO] Demo upload started for IP: 192.168.1.1
[INFO] File uploaded to S3 at: demo/550e8400.../test.jpg
[INFO] Starting search for session: 550e8400-e29b-41d4...
[INFO] Demo cleanup completed. Cleaned 5 sessions
```
---
## Best Practices для Frontend
1. **Показывайте progress** - используйте `progressPercentage`
2. **Обрабатывайте ошибки** - проверяйте `status` == "FAILED"
3. **Очищайте сессии** - вызывайте `/cleanup` перед уходом
4. **Не перезагружайте** - максимум 10 файлов в день
5. **Таймауты** - сессия истекает через 24 часа
---
## Интеграция на лэндинге
### Step 1: Drag & Drop зона
```html
<div id="dropZone" class="upload-area">
Перетащите файл сюда или нажмите
<input type="file" id="fileInput" />
</div>
```
### Step 2: Загрузка
```javascript
const upload = async (file) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/demo/upload', {
method: 'POST',
body: formData
});
return res.json();
};
```
### Step 3: Демо поиска
```javascript
const startDemo = async (sessionId) => {
const res = await fetch(`/api/demo/search/${sessionId}`, {
method: 'POST'
});
return res.json();
};
```
### Step 4: Очистка
```javascript
const endDemo = async (sessionId) => {
await fetch(`/api/demo/cleanup/${sessionId}`, {
method: 'DELETE'
});
};
```
---
## FAQ
**Q: Мой файл исчезает через 24 часа?**
A: Да, это запланировано. Используйте полную версию для постоянного хранения.
**Q: Могу ли я загрузить больше 10 файлов?**
A: Только если зарегистрируетесь. Демо имеет лимиты в целях защиты.
**Q: Почему мне не дают загрузить файл больше 50 MB?**
A: Это демо, которое работает на общих ресурсах. Полная версия поддерживает файлы до 1 GB.
**Q: Где хранятся мои файлы?**
A: В AWS S3, в отдельной папке `demo/`. Никто другой не может получить к ним доступ.
**Q: Что если мой IP заблокирован?**
A: Попробуйте через день или зарегистрируйтесь для безлимитного доступа.
---
## Мониторинг Health
```bash
curl http://localhost:8080/api/demo/health
```
Если получите `"status": "HEALTHY"` - все работает.
---
**Последнее обновление:** 2026-07-01
**Версия API:** 1.0.0
**Статус:** Production Ready
+58
View File
@@ -0,0 +1,58 @@
# Demo API Configuration для application.yaml или application.properties
# ============================================
# DEMO API Settings
# ============================================
demo:
# Максимальный размер файла для загрузки (в байтах)
# Default: 52428800 (50 MB)
max-file-size: 52428800
# Максимальное количество файлов в день на один IP адрес
# Default: 10
max-files-per-day: 10
# Путь в S3 для хранения демо-файлов
# Default: demo/
s3-path: demo/
# Время жизни демо-сессии в часах
# Default: 24
session-ttl-hours: 24
# Интервал очистки истекших сессий (в миллисекундах)
# Default: 1800000 (30 минут)
cleanup-interval: 1800000
# Интервал очистки FAILED сессий (в миллисекундах)
# Default: 3600000 (1 час)
failed-cleanup-interval: 3600000
# ============================================
# S3 Configuration (если используется)
# ============================================
aws:
s3:
bucket: your-bucket-name
region: eu-west-1
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
# ============================================
# Logging для Demo Service
# ============================================
logging:
level:
ru.soune.nocopy.service.demo: DEBUG
ru.soune.nocopy.controller.demo: DEBUG
ru.soune.nocopy.service.file.cloud: DEBUG
# ============================================
# Task Scheduling для Cleanup
# ============================================
spring:
task:
scheduling:
pool:
size: 2
thread-name-prefix: demo-cleanup-
+357
View File
@@ -0,0 +1,357 @@
# Demo API Implementation Summary
## ✅ Что было создано
### 1. Entity & Repository
- **DemoSession.java** - JPA Entity для хранения информации о демо-сессиях
- **DemoSessionStatus.java** - Enum со статусами сессий
- **DemoSessionRepository.java** - Repository с поддержкой поиска и статистики
### 2. DTO (Data Transfer Objects)
- **DemoUploadRequest.java** - DTO для параметров загрузки
- **DemoUploadResponse.java** - DTO для ответа после загрузки
- **DemoSearchResponse.java** - DTO для результатов поиска
- **DemoSessionInfo.java** - DTO для информации о сессии
### 3. Services
- **DemoSessionService.java** (280+ строк)
- `uploadFile()` - валидация и загрузка файла в S3
- `getSessionInfo()` - информация о файле
- `startSearch()` - поиск похожих файлов
- `deleteSession()` - удаление сессии
- Встроенная валидация: размер, тип, лимит по IP
- **DemoCleanupService.java** (170+ строк)
- `cleanupExpiredSessions()` - удаление старых сессий (каждые 30 мин)
- `cleanupFailedSessions()` - удаление FAILED сессий (каждый час)
- `cleanupSessionManually()` - ручная очистка
- `getActiveDemoSessionsCount()` - статистика
- `getActiveDemoDataSize()` - размер демо-данных в S3
### 4. Controller
- **DemoController.java** (180+ строк)
- `POST /api/demo/upload` - загрузка файла
- `GET /api/demo/file/{sessionId}` - информация о файле
- `POST /api/demo/search/{sessionId}` - запуск поиска
- `DELETE /api/demo/cleanup/{sessionId}` - очистка сессии
- `GET /api/demo/health` - health check
- Автоматическое извлечение IP клиента с поддержкой прокси
### 5. Тесты (420+ строк кода тестов)
- **DemoSessionServiceTest.java** (360+ строк)
- 12 тестов: загрузка, валидация, поиск, удаление
- Проверка лимитов, типов файлов, авторизации по IP
- Обработка ошибок S3
- **DemoCleanupServiceTest.java** (380+ строк)
- 10 тестов: очистка, обработка ошибок, статистика
- Проверка интервалов очистки
- Подсчёт размеров и счётчиков
- **DemoControllerTest.java** (340+ строк)
- 15 тестов: все эндпоинты и сценарии ошибок
- Извлечение IP из разных заголовков
- Обработка пустых файлов
### 6. Документация
- **DEMO_API_GUIDE.md** - полный гайд для использования
- **DEMO_CONFIG_EXAMPLE.yaml** - пример конфигурации
- **DEMO_IMPLEMENTATION_SUMMARY.md** - этот файл
---
## 📊 Статистика Кода
| Компонент | Строк Кода | Назначение |
|-----------|-----------|-----------|
| Entities & Enums | 80 | Модели БД |
| DTOs | 120 | Transfer Objects |
| DemoSessionService | 280 | Основная логика |
| DemoCleanupService | 170 | Автоочистка |
| DemoController | 180 | REST API |
| Repository | 40 | БД запросы |
| **Всего сервис** | **870** | **Основной код** |
| DemoSessionServiceTest | 360 | Unit тесты |
| DemoCleanupServiceTest | 380 | Unit тесты |
| DemoControllerTest | 340 | Integration тесты |
| **Всего тесты** | **1080** | **Полное покрытие** |
---
## 🔒 Ограничения и Защита
### Размеры Файлов
```
Max размер: 50 MB
Типы: image/jpeg, image/png, application/pdf, audio/mp3, etc.
Проверка MIME type: обязательна
```
### Rate Limiting
```
По IP адресу:
- 10 файлов в день
- Проверка каждые 24 часа
- Блокировка при превышении
```
### Безопасность
```
✅ IP-based rate limiting
✅ Валидация типов файлов
✅ Проверка размера на бэкенде
✅ Авторизация по IP (пользователь может видеть только свои файлы)
✅ Автоматическое удаление через 24 часа
✅ Обработка ошибок S3 без падения сервиса
```
---
## 🚀 Deployment Checklist
### 1. Migration в БД
```sql
CREATE TABLE demo_sessions (
session_id VARCHAR(36) PRIMARY KEY,
ip_address VARCHAR(45) NOT NULL,
file_name VARCHAR(255) NOT NULL,
file_id VARCHAR(255) NOT NULL,
s3_path VARCHAR(500) NOT NULL,
file_size BIGINT NOT NULL,
file_type VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL,
mime_type VARCHAR(100),
search_results_count INT,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL,
INDEX idx_ip_created (ip_address, created_at),
INDEX idx_expires_at (expires_at),
INDEX idx_status (status)
);
```
### 2. Конфигурация (application.yaml)
```yaml
demo:
max-file-size: 52428800 # 50 MB
max-files-per-day: 10
s3-path: demo/
session-ttl-hours: 24
cleanup-interval: 1800000 # 30 минут
failed-cleanup-interval: 3600000 # 1 час
```
### 3. S3 Permissions (IAM Policy)
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::your-bucket/demo/*"
}
]
}
```
### 4. Environment Variables
```bash
AWS_ACCESS_KEY=your_access_key
AWS_SECRET_KEY=your_secret_key
DEMO_S3_BUCKET=your-bucket
DEMO_MAX_FILE_SIZE=52428800
```
### 5. Тесты
```bash
mvn test -Dtest=DemoSessionServiceTest
mvn test -Dtest=DemoCleanupServiceTest
mvn test -Dtest=DemoControllerTest
```
---
## 📈 Масштабирование
### Текущие возможности
- ~1000 активных демо-сессий одновременно
- ~50 GB демо-данных в S3
- ~1000 операций в минуту
### Если нужно больше:
1. **Увеличить cleanup интервал** - если много одновременных пользователей
2. **Добавить Redis** - для кэширования информации о сессиях
3. **Использовать S3 Lifecycle** - для автоматического удаления файлов старше 30 дней
4. **Шардирование по IP** - если очень много юзеров
---
## 🐛 Обработка Ошибок
### Валидация
```
❌ File too large → 400 Bad Request
❌ Unsupported file type → 400 Bad Request
❌ Daily limit reached → 400 Bad Request
❌ Empty file → 400 Bad Request
```
### Авторизация
```
❌ Unauthorized IP access → 403 Forbidden
❌ Session not found → 404 Not Found
```
### S3 Ошибки
```
⚠️ S3 upload failure → сессия помечена FAILED, но не бросает исключение
⚠️ S3 delete failure → cleanup продолжается, но логируется
```
---
## 📝 API Примеры
### Успешный Flow
```bash
# 1. Загрузка
curl -X POST http://localhost:8080/api/demo/upload \
-F "file=@image.jpg"
# Ответ: {"sessionId": "xxx", "status": "COMPLETED"}
# 2. Получение инфо
curl http://localhost:8080/api/demo/file/xxx
# Ответ: {"fileName": "image.jpg", "fileSize": 1024}
# 3. Поиск
curl -X POST http://localhost:8080/api/demo/search/xxx
# Ответ: {"resultsCount": 3, "similarFiles": [...]}
# 4. Очистка
curl -X DELETE http://localhost:8080/api/demo/cleanup/xxx
# Ответ: {"status": "CLEANED"}
```
---
## 🔍 Мониторинг
### Метрики для сбора
```java
// Active sessions
demoCleanupService.getActiveDemoSessionsCount()
// Storage usage
demoCleanupService.getActiveDemoDataSize()
// Logs
grep "Demo upload" application.log | wc -l // количество загрузок
grep "Demo cleanup" application.log | wc -l // количество очисток
```
### Prometheus метрики (опционально)
```
demo_sessions_active{} - количество активных сессий
demo_storage_bytes{} - размер демо-данных
demo_uploads_total{} - всего загрузок
demo_cleanups_total{} - всего очисток
```
---
## ✨ Особенности Cleanup-сервиса
### Почему это важно?
```
1. S3 стоит денег → нужно удалять старые файлы
2. GDPR требует удаления → временные данные должны удаляться
3. БД растёт → сессии должны очищаться
4. Безопасность → демо-данные не должны лежать вечно
```
### Как это работает?
```
@Scheduled(fixedDelay = 1800000) // каждые 30 минут
public void cleanupExpiredSessions() {
1. Найти WHERE expiresAt <= NOW()
2. Для каждой: удалить из S3
3. Обновить статус = CLEANED
4. Залогировать результаты
}
```
### Отказоустойчивость
```
✅ Если S3 недоступен - продолжаем работу, логируем
✅ Если БД недоступна - scheduler пересчитает позже
✅ Если исключение - перехватываем и логируем
✅ Максимальная защита от потери данных
```
---
## 📚 Дополнительные Файлы
```
src/main/java/ru/soune/nocopy/
├── entity/demo/
│ ├── DemoSession.java
│ └── DemoSessionStatus.java
├── repository/
│ └── DemoSessionRepository.java
├── dto/demo/
│ ├── DemoUploadRequest.java
│ ├── DemoUploadResponse.java
│ ├── DemoSearchResponse.java
│ └── DemoSessionInfo.java
├── service/demo/
│ ├── DemoSessionService.java
│ └── DemoCleanupService.java
└── controller/demo/
└── DemoController.java
src/test/java/ru/soune/nocopy/
├── service/demo/
│ ├── DemoSessionServiceTest.java
│ └── DemoCleanupServiceTest.java
└── controller/demo/
└── DemoControllerTest.java
Documentation/
├── DEMO_API_GUIDE.md
├── DEMO_CONFIG_EXAMPLE.yaml
└── DEMO_IMPLEMENTATION_SUMMARY.md (этот файл)
```
---
## 🚦 Next Steps
1. **Добавить миграцию Flyway** для создания таблицы `demo_sessions`
2. **Настроить S3 bucket** с правами доступа
3. **Запустить тесты** - убедиться что всё работает
4. **Сделать фронтенд** - drag & drop, upload progress, результаты
5. **Настроить мониторинг** - какой процент юзеров конвертируется
6. **Добавить analytics** - откуда приходят юзеры для демо
---
## 📞 Support
Если возникли вопросы:
- Смотри **DEMO_API_GUIDE.md** для использования API
- Смотри логи: `grep "Demo" application.log`
- Запусти тесты: `mvn test -Dtest=Demo*Test`
---
**Статус:** ✅ Production Ready
**Дата:** 2026-07-01
**Версия:** 1.0.0
**Автор:** Claude Code
+6 -1
View File
@@ -12,6 +12,11 @@ WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
RUN mkdir -p /data/uploads && chmod 755 /data/uploads
ENV BUILD_TIME_BACK="unknown"\
BUILD_TIME_FRONT="unknown"
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
CMD ["java", "-jar", "app.jar"]
+563
View File
@@ -0,0 +1,563 @@
Создаем сеть:
docker network create app-network
Войти в БД:
docker exec -it postgres psql -U ncp_db -d no_copy_
Выполнить команду:
\du
Должен быть один пользователь:
List of roles
Role name | Attributes
-----------+------------------------------------------------------------
ncp_db | Superuser, Create role, Create DB, Replication, Bypass RLS
суперпользователь БД:
postgres/postgres
пользователь бд для бэка
ncp_db / ncpDbApp
Подключение к БД для приложения,если volume не существует :
Создаться автоматически.
Если уже существует volume :
docker exec -it postgres psql -U postgres -d postgres
CREATE USER ncp_db WITH PASSWORD 'ncpDbApp';
GRANT ALL PRIVILEGES ON DATABASE no_copy_ TO ncp_db;
\du
\q
-------
psql -U postgres -d no_copy_
-- база
GRANT CONNECT ON DATABASE no_copy_ TO ncp_db;
-- схема
GRANT USAGE ON SCHEMA public TO ncp_db;
-- существующие таблицы
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db;
-- существующие sequence (очень важно для id)
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db;
-- будущие таблицы
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO ncp_db;
Зайти в контейнер:
docker exec -it {name} bash
------
Создать для сущности FileEntity sequence для икремента индетификатора
CREATE SEQUENCE IF NOT EXISTS file_support_id_seq START 1;
ALTER TABLE file_entities
ALTER COLUMN support_id
SET DEFAULT nextval('file_support_id_seq');
----------
Раздать всем лимиты,у кого их нет
INSERT INTO protect_check (user_id, check_limit, count_checked, last_check_at, version)
SELECT
id as user_id,
10 as check_limit,
0 as count_checked,
NULL as last_check_at,
0 as version
FROM users
WHERE id NOT IN (SELECT user_id FROM protect_check);
--------
Обновлять констрейнты для file_entities
ALTER TABLE file_entities DROP CONSTRAINT file_entities_status_check;
ALTER TABLE file_entities
ADD CONSTRAINT file_entities_status_check
CHECK (status IN (
'ACTIVE',
'DELETED',
'PROCESSING',
'VIOLATION',
'CHECKED',
'ERROR',
'TEMP'
));
-------
Записи для лимитов поиска
INSERT INTO protect_check (user_id, limit_check, count_checked, last_check_at, version)
SELECT
u.id,
100, -- limit_check = 100
0, -- count_checked = 0
NULL, -- last_check_at = NULL
0 -- version = 0
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM protect_check pc
WHERE pc.user_id = u.id
);
------
Скрипты рефералки
INSERT INTO referrals (user_id, referral_link, inviter_id, level_id, total_income, is_active, created_at, hold_balance)
SELECT
u.id as user_id,
CONCAT('ref-', u.id, '-', LOWER(SUBSTRING(MD5(RANDOM()::text) FROM 1 FOR 8))) as referral_link,
NULL as inviter_id,
'bronze' as level_id,
0 as total_income,
false as is_active,
NOW() as created_at,
0 as hold_balance
FROM users u
LEFT JOIN referrals r ON u.id = r.user_id
WHERE
u.company_id IS NULL
AND r.user_id IS NULL
ORDER BY u.id;
-------
INSERT INTO referral_levels (id, name, min_invitees, reward_percentage) VALUES
('bronze', 'BRONZE', 0, 15),
('silver', 'SILVER', 6, 18),
('gold', 'GOLD', 16, 22),
('platinum', 'PLATINUM', 50, 25);
-----------------
Скрипт,для нумерации для уже сохданных файлов
WITH max_id AS (
SELECT COALESCE(MAX(support_id), 0) as max_val FROM file_entities
),
-- Нумеруем только NULL записи, начиная с max_val + 1
numbered AS (
SELECT
f.id,
(SELECT max_val FROM max_id) + ROW_NUMBER() OVER (ORDER BY f.created_at, f.id) as new_support_id
FROM file_entities f
WHERE f.support_id IS NULL
)
UPDATE file_entities
SET support_id = numbered.new_support_id
FROM numbered
WHERE file_entities.id = numbered.id;
---------------------------------
НАСТРОЙКА NGINX
--------------------------------
# admin.not-copy.com
server {
listen 80;
listen [::]:80;
server_name admin.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name admin.not-copy.com;
ssl_certificate /etc/letsencrypt/live/admin.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2995;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# workspace.not-copy.com
server {
listen 80;
listen [::]:80;
server_name workspace.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name workspace.not-copy.com;
ssl_certificate /etc/letsencrypt/live/workspace.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/workspace.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2998;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# lp.not-copy.com
server {
listen 80;
listen [::]:80;
server_name lp.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name lp.not-copy.com;
ssl_certificate /etc/letsencrypt/live/lp.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lp.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3003;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2993;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-admin.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-admin.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-admin.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-admin.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-admin.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:8082;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2996;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-workspace.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-workspace.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-workspace.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-workspace.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-workspace.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:3002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# dev-lp.not-copy.com
server {
listen 80;
listen [::]:80;
server_name dev-lp.not-copy.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name dev-lp.not-copy.com;
ssl_certificate /etc/letsencrypt/live/dev-lp.not-copy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev-lp.not-copy.com/privkey.pem;
location /api/ {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:2994;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Проверяем конфиг
ls -la /etc/nginx/sites-available/
ls -la /etc/nginx/sites-enabled/
# Создание конфига
sudo nano /etc/nginx/sites-available/not-copy
# Создание симлинк
sudo ln -s /etc/nginx/sites-available/not-copy /etc/nginx/sites-enabled/
# Чек
sudo nginx -t
# Рестарт
sudo systemctl reload nginx
---------------------------------------------
# ПОЛНАЯ ИНСТРУКЦИЯ: НАСТРОЙКА ПРОКСИ ДЛЯ ОБХОДА БЛОКИРОВОК SEARCHAPI.IO
## Данные для подключения к прокси-серверу
IP: 193.46.217.94
Порт HTTP: 3128
Порт SOCKS: 1080
Логин SSH: root
Пароль SSH: u7nRtsD6
## 1. Настройка прокси-сервера
```bash
# Подключаемся к серверу
ssh root@193.46.217.94
# Вводим пароль: u7nRtsD6
# Обновляем систему и устанавливаем пакеты
yum update -y
yum install -y epel-release net-tools nano
yum install -y 3proxy
# Создаем конфигурацию
mkdir -p /etc/3proxy
cat > /etc/3proxy/3proxy.cfg << 'EOF'
nserver 8.8.8.8
nserver 8.8.4.4
timeouts 1 5 30 60 180 1800 15 60
daemon
log /var/log/3proxy/3proxy.log
logformat "- +_L%t.%. %N.%p %E %U %C:%c %R:%r %O %I %h %T"
rotate 30
auth none
allow * * * * *
proxy -p3128
socks -p1080
EOF
# Создаем папку для логов и запускаем прокси
mkdir -p /var/log/3proxy
/usr/bin/3proxy /etc/3proxy/3proxy.cfg
# Проверяем, что запустилось
ps aux | grep 3proxy
netstat -tulpn | grep -E '3128|1080'
# Настраиваем файрвол
firewall-cmd --permanent --add-port=3128/tcp
firewall-cmd --permanent --add-port=1080/tcp
firewall-cmd --reload
firewall-cmd --list-ports
# Проверяем локальную работу прокси
curl -x http://127.0.0.1:3128 https://api.ipify.org
# Должен вернуть: 193.46.217.94
# Настраиваем автозапуск
echo "/usr/bin/3proxy /etc/3proxy/3proxy.cfg" >> /etc/rc.local
chmod +x /etc/rc.local
# Проверяем доступность прокси
ping -c 4 193.46.217.94
curl -x http://193.46.217.94:3128 https://api.ipify.org
# Должен вернуть: 193.46.217.94
# Проверяем работу с SearchAPI через прокси
curl -x http://193.46.217.94:3128 "https://www.searchapi.io/api/v1/search?engine=google_lens&api_key=5jyYZC8jSaxhZTwjMUhwtAXi&url=https://dev-workspace.not-copy.com/api/files/public/76d06557-6df1-4fcd-a273-050ec6a35faf&search_type=exact_matches" -o test.json
ls -la test.json
tail -20 test.json
# Файл должен быть полным (заканчиваться на '}')
ps aux | grep 3proxy # Проверка статуса
netstat -tulpn | grep -E '3128|1080' # Проверка портов
tail -f /var/log/3proxy/3proxy.log # Просмотр логов
killall 3proxy # Остановка прокси
/usr/bin/3proxy /etc/3proxy/3proxy.cfg # Запуск прокси
firewall-cmd --permanent --remove-port=3128/tcp
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="IP_ТВОЕГО_СЕРВЕРА" port port="3128" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="IP_ТВОЕГО_СЕРВЕРА" port port="1080" protocol="tcp" accept'
firewall-cmd --reload
На прокси-сервере:
bash
ps aux | grep 3proxy # Проверка статуса
netstat -tulpn | grep -E '3128|1080' # Проверка портов
tail -f /var/log/3proxy/3proxy.log # Просмотр логов
killall 3proxy # Остановка прокси
/usr/bin/3proxy /etc/3proxy/3proxy.cfg # Запуск прокси
На основном сервере:
bash
curl -x http://193.46.217.94:3128 https://api.ipify.org # Проверка прокси
telnet 193.46.217.94 3128 # Проверка порта
------
Настройка Яндекс Клауд
1. YCAJEpWAtaVkVGX0sH6_EupEg - индетификатор ключа
2. YCMmVykfXrZ_nfU13Vo4yoCVGa70DnTlBgF1pUzO - секретный ключ
private static final String KEY_ID = "YCAJEmHZcWxzf4oGWvETG3mms";
private static final String SECRET_KEY = "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG";
Обновить сертики
systemctl stop nginx && certbot renew && systemctl start nginx
Создание API ключей для внешнего доступа
1.
POST /api/user/api-keys
Authorization: Bearer {YOUR_JWT_TOKEN}
Content-Type: application/json
{
"name": "My Integration Script"
}
{
"messageCode": "API_KEY_CREATED",
"messageDesc": "Save this key. It will not be shown again.",
"messageBody": {
"apiKey": "ncp_dGhpcyBpc2EzcGFtcGxla2V5Zm9ydGVzdGluZw"
}
}
2. Использование API-ключа
GET /api/v1/files
X-API-Key: ncp_dGhpcyBpc2EzcGFtcGxla2V5Zm9ydGVzdGluZw
curl -X GET \
-H "X-API-Key: ncp_dGhpcyBpc2EzcGFtcGxla2V5Zm9ydGVzdGluZw" \
https://api.nocopy.ru/api/v1/files
3.Просмотр списка ключей
GET /api/user/api-keys
Authorization: Bearer {YOUR_JWT_TOKEN}
{
"messageCode": "OK",
"messageBody": [
{
"id": 1,
"name": "My Integration Script",
"prefix": "dGhpcyBp",
"isActive": true,
"createdAt": "2024-01-15T10:30:00",
"lastUsedAt": "2024-01-16T14:22:00"
}
]
}
4. Отзыв API-ключа
DELETE /api/user/api-keys/{keyId}
Authorization: Bearer {YOUR_JWT_TOKEN}
{
"messageCode": "API_KEY_REVOKED"
}
File diff suppressed because it is too large Load Diff
+44
View File
@@ -21,6 +21,9 @@ configurations {
repositories {
mavenCentral()
flatDir {
dirs 'libs'
}
}
dependencies {
@@ -30,6 +33,17 @@ dependencies {
implementation 'org.springframework.security:spring-security-crypto:6.5.3'
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'commons-validator:commons-validator:1.7'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '4.0.1'
implementation group: 'com.google.cloud', name: 'google-cloud-vision', version: '3.55.0'
implementation group: 'com.google.api-client', name: 'google-api-client', version: '2.7.2'
implementation 'org.flywaydb:flyway-core:9.22.0'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.0'
implementation 'tools.jackson.core:jackson-core:3.0.3'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.17.2'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok'
@@ -45,6 +59,36 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation name: 'testlib-fat-0.3.1-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation 'com.maxmind.geoip2:geoip2:4.2.0'
implementation 'org.sejda.imageio:webp-imageio:0.1.6'
implementation project(':referral')
//cloud
implementation("software.amazon.awssdk:aws-sdk-java:2.29.33")
implementation("software.amazon.awssdk:apache-client:2.29.33")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.0'
implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0'
//cache
implementation("org.springframework.boot:spring-boot-starter-cache:4.0.6")
implementation 'com.github.ben-manes.caffeine:caffeine'
//security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation("io.jsonwebtoken:jjwt:0.13.0")
//kafka
implementation 'org.springframework.kafka:spring-kafka'
}
tasks.named('test') {
+171 -6
View File
@@ -1,17 +1,69 @@
version: '3.9'
services:
storage:
image: alpine:latest
container_name: file-storage
deploy:
resources:
limits:
memory: 128M
reservations:
memory: 64M
networks:
- app-network
volumes:
- uploads_data:/storage:rw
command: tail -f /dev/null
restart: unless-stopped
# postfix:
# image: boky/postfix:latest
# container_name: postfix-production
# hostname: ${DOMAIN:-no-copy.ru}
# environment:
# - DOMAIN=${DOMAIN:-no-copy.ru}
# - ALLOWED_SENDER_DOMAINS=*
# - RELAYHOST=
# - DISABLE_SMTP_AUTH=
# - SMTP_USERNAME=${MAIL_USERNAME:-noreply@no-copy.ru}
# - SMTP_PASSWORD=${SMTP_PASSWORD:-nocopy!nocopy!}
# - RELAY_NETWORKS=172.16.0.0/12
# - POSTFIX_mynetworks=127.0.0.0/8 172.17.0.0/16 172.16.0.0/12
# - POSTFIX_smtpd_relay_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
# - POSTFIX_smtpd_recipient_restrictions=permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
# - DISABLE_SIGNING=true
# ports:
# - "25:25"
# - "587:587"
# networks:
# - app-network
# restart: unless-stopped
db:
image: postgres:17
image: postgres:17.7
restart: always
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G
environment:
POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_SHARED_BUFFERS: 512MB
POSTGRES_EFFECTIVE_CACHE_SIZE: 1536MB
ports:
- "54320:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d:ro
container_name: postgres
networks:
app-network:
@@ -21,12 +73,40 @@ services:
app:
build: .
container_name: app-backend
deploy:
resources:
limits:
cpus: '1.5'
memory: 3G
reservations:
cpus: '0.5'
memory: 2G
environment:
JAVA_TOOL_OPTIONS: "-Xmx2g -Xms1g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:+UseContainerSupport -XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=75"
FILE_STORAGE_PATH: /data/uploads
MAX_FILE_SIZE: 10737418240
# FILE_CHUNK_SIZE: 1048576
FILE_CHUNK_SIZE: 1000000
POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: 5432
POSTGRES_HOST: db
STORAGE_SERVICE_URL: http://storage:8081
SPRING_PROFILES_ACTIVE: prod
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
# POSTGRES_USER: postgres
# POSTGRES_PASSWORD: postgres
# MAIL_HOST: postfix
# MAIL_PORT: 25
# MAIL_USERNAME: noreply@no-copy.ru
# SMTP_PASSWORD: nocopy!nocopy!
# SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: "false"
# SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH: "false"
# SPRING_MAIL_PROPERTIES_MAIL_DEBUG: "true"
depends_on:
- db
ports:
@@ -37,10 +117,23 @@ services:
- app
- backend
- api
volumes:
- uploads_data:/data/uploads:rw
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d no_copy_" ]
interval: 10s
retries: 5
grafana:
image: grafana/grafana:10.3.1
container_name: grafana
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
ports:
- "3000:3000"
depends_on:
@@ -57,6 +150,8 @@ services:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
GF_METRICS_ENABLED: "true"
GF_DATABASE_MAX_IDLE_CONN: "2"
GF_DATABASE_MAX_OPEN_CONN: "10"
volumes:
- ./infrastructure/grafana/provisioning:/etc/grafana/provisioning
- ./infrastructure/grafana/dashboards:/var/lib/grafana/dashboards
@@ -71,10 +166,23 @@ services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
reservations:
memory: 512M
ports:
- "9090:9090"
volumes:
- ./infrastructure/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=15d'
- '--web.enable-lifecycle'
healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
interval: 10s
@@ -86,10 +194,20 @@ services:
loki:
image: grafana/loki:2.9.2
container_name: loki
deploy:
resources:
limits:
cpus: '1.0'
memory: 2G
reservations:
memory: 1G
ports:
- "3100:3100"
volumes:
- ./infrastructure/loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro
- loki_data:/loki
command:
- -config.file=/etc/loki/loki-config.yaml
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ]
interval: 10s
@@ -98,9 +216,43 @@ services:
networks:
- app-network
nexus:
image: sonatype/nexus3:latest
container_name: nexus
restart: unless-stopped
ports:
- "9000:8081"
volumes:
- nexus-data:/nexus-data
environment:
- INSTALL4J_ADD_VM_PARAMS=-Xms512m -Xmx1024m -XX:MaxDirectMemorySize=512m
networks:
- app-network
mongodb:
image: mongo:latest
container_name: mongo_ncp
restart: unless-stopped
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: mongo_db
MONGO_INITDB_ROOT_PASSWORD: mongo_ncp
volumes:
- mongodb_data:/data/db
networks:
- app-network
tempo:
image: grafana/tempo:2.4.1
container_name: tempo
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
command: [ "-config.file=/etc/tempo/tempo.yaml" ]
volumes:
- ./infrastructure/tempo/tempo.yaml:/etc/tempo/tempo.yaml
@@ -113,6 +265,13 @@ services:
alloy:
image: grafana/alloy:latest
container_name: alloy
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
memory: 128M
user: root
ports:
- "9080:9080"
@@ -135,13 +294,19 @@ services:
volumes:
pgdata:
mongodb_data:
name: mongodb_data
artifacts:
tempo_data:
loki_data:
loki_chunks:
loki_index:
loki_rules:
uploads_data:
prometheus_data:
nexus-data:
driver: local
# postfix-data:
networks:
app-network:
external: true
+24
View File
@@ -0,0 +1,24 @@
plugins {
kotlin("jvm") version "2.1.10"
}
group = "ru.soune"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-core-jvm:4.1.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
+5
View File
@@ -0,0 +1,5 @@
package ru.soune
fun main() {
println("Hello World!")
}
@@ -0,0 +1,3 @@
package ru.soune.actions
abstract class NoCopyAction
@@ -0,0 +1,5 @@
package ru.soune.actions.free
import ru.soune.actions.NoCopyAction
sealed class NoCopyFreeAction : NoCopyAction()
@@ -0,0 +1,8 @@
package ru.soune.actions.paid
import ru.soune.actions.NoCopyAction
sealed class NoCopyPaidAction : NoCopyAction() {
abstract val cost: Double
}
+26
View File
@@ -0,0 +1,26 @@
## Команды для помещения в сеть
Сеть для работы бэка и фронта : Наименование == app-network-{port}
### Проверить состояние:
docker inspect {container-name} --format='{{range $net, $_ := .NetworkSettings.Networks}}{{$net}} {{end}}'
### Создать сеть ,если нет
docker network create {name-network} 2>/dev/null || echo "Сеть уже существует"
### Переместить контейнер:
# Отключить от старой сети
docker network disconnect {network-name-old} {container-name}
# Подключить к новой сети
docker network connect {network-name-new} {container-name}
# Добавить алиас (опционально)
docker network connect --alias {alias} {network-name-new} {container-name}
+156
View File
@@ -0,0 +1,156 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя админ-панели'
)
string(
name: 'PORT',
defaultValue: '2996',
description: 'Порт для запуска экземпляра'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-admin-panel-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-admin-panel-${params.PORT} 2>/dev/null || true
docker rm no-copy-admin-panel-${params.PORT} 2>/dev/null || true
mkdir -p /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}
rm -rf /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}/*
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}
docker build -t no-copy-admin-panel:${params.BRANCH}-${params.PORT} .
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker run -d \\
--name no-copy-admin-panel-${params.PORT} \\
--restart unless-stopped \\
--network app-network \\
-p ${params.PORT}:2996 \\
no-copy-admin-panel:${params.BRANCH}-${params.PORT}
sleep 5
docker ps --filter name=no-copy-admin-panel-${params.PORT}
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-admin-panel-${params.PORT}'; then
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} 2>/dev/null || echo '000')
echo 'Admin panel available on http://92.242.61.23:${params.PORT}'
else
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Admin panel ${params.BRANCH} deployed"
}
failure {
echo "Deployment failed"
}
}
}
@@ -0,0 +1,238 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'dev',
description: 'Ветка для деплоя'
)
string(
name: 'PORT',
defaultValue: '3001',
description: 'Порт для запуска экземпляра'
)
string(
name: 'PORT_DB',
defaultValue: '',
description: 'Порт для запуска БД'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
description: 'Сервер для деплоя'
)
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Имя сети (будет создана если не существует)'
)
choice(
name: 'SPRING_PROFILE',
choices: ['dev', 'prod'],
description: 'Профиль Spring для запуска'
)
}
stages {
stage('Git pull') {
steps {
cleanWs()
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/backdev/no-copy.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
rm -rf /opt/deployments/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
"
sshpass -p '${SSH_PASS}' scp -r ./* ${SSH_USER}@${params.SERVER}:/opt/deployments/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
def springProfile = params.SPRING_PROFILE
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD'),
string(credentialsId: 'MONGO_USER', variable: 'MONGO_USER'),
string(credentialsId: 'MONGO_PASSWORD', variable: 'MONGO_PASSWORD')
]) {
sh """
echo "Testing SSH connection..."
if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then
echo "SSH connection OK"
else
echo "SSH connection failed"
exit 1
fi
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
mkdir -p /opt/deployments/${params.BRANCH}-${params.PORT}
cd /opt/deployments/${params.BRANCH}-${params.PORT}
echo '1. Проверяем/создаем сеть ${params.NETWORK}...'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
echo 'Сеть ${params.NETWORK} создана'
else
echo 'Сеть ${params.NETWORK} уже существует'
fi
echo '2. Останавливаем старые контейнеры порта ${params.PORT}...'
docker stop app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
docker rm app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
docker stop postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker stop mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
docker rm mongodb-${params.PORT} 2>/dev/null || echo 'Контейнер MongoDB не найден'
echo '3. Запускаем БД...'
docker run -d \\
--name postgres-${params.PORT} \\
--network ${params.NETWORK} \\
--network-alias no_copy_${params.PORT} \\
--network-alias database-${params.PORT} \\
--network-alias db-${params.PORT} \\
-p ${params.PORT}0:5432 \\
-e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-v pgdata_${params.PORT}:/var/lib/postgresql/data \\
-e BUILD_TIME_BACK='${buildTime}' \\
postgres:17.7
echo '4. Запускаем MongoDB...'
docker run -d \\
--name mongodb-${params.PORT} \\
--network ${params.NETWORK} \\
--network-alias mongodb-${params.PORT} \\
-p ${params.PORT}1:27017 \\
-e MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} \\
-e MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} \\
-v mongodb_data_${params.PORT}:/data/db \\
mongo:7.0
echo '5. Запускаем storage...'
docker run -d \\
--name file-storage-${params.PORT} \\
--network ${params.NETWORK} \\
-v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null
echo '6. Ждем БД...'
sleep 15
echo '7. Собираем приложение...'
docker build --no-cache -t app-backend-${params.PORT}:latest .
echo '8. Запускаем приложение...'
docker run -d \\
--name app-backend-${params.PORT} \\
--network ${params.NETWORK} \\
--network-alias app \\
--network-alias app-${params.PORT} \\
-p ${params.PORT}:8080 \\
-v uploads_data_${params.PORT}:/data/uploads:rw \\
-e POSTGRES_DB=no_copy${params.PORT_DB} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\
-e SPRING_PROFILES_ACTIVE=${springProfile} \\
-e MONGO_URI=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@mongodb-${params.PORT}:27017 \\
-e MONGO_DATABASE=no_copy \\
app-backend-${params.PORT}:latest
echo '9. Проверка...'
sleep 10
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
echo 'Приложение работает на порту ${params.PORT}'
else
echo 'Проверка health не удалась'
docker logs app-backend-${params.PORT} --tail=20
fi
"
"""
}
}
}
}
stage('Verify network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
echo 'Проверяем подключение к сети ${params.NETWORK}'
docker network inspect ${params.NETWORK} --format='{{.Name}}: {{range .Containers}}{{.Name}} {{end}}'
echo 'Проверяем алиас app для контейнера app-backend-${params.PORT}'
docker inspect app-backend-${params.PORT} | grep -A 5 'Aliases'
"
"""
}
}
}
}
}
post {
success {
script {
def appUrl = params.SPRING_PROFILE == 'prod' ? 'https://workspace.not-copy.com' : 'https://dev-workspace.not-copy.com'
echo "Deployment successful"
echo "Application URL: ${appUrl}"
}
}
failure {
echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}"
}
always {
echo "Deployment process finished"
}
}
}
+156 -69
View File
@@ -7,6 +7,16 @@ pipeline {
defaultValue: 'main',
description: 'Ветка для деплоя'
)
string(
name: 'PORT',
defaultValue: '2998',
description: 'Порт для запуска экземпляра'
)
string(
name: 'NETWORK',
defaultValue: 'app-network-dev',
description: 'Docker сеть для подключения'
)
}
stages {
@@ -36,19 +46,19 @@ pipeline {
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
fi
"
"""
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend-${params.PORT} 2>/dev/null || true
docker rm no-copy-frontend-${params.PORT} 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml -p frontend-${params.PORT} down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml -p frontend-${params.PORT} down 2>/dev/null || true
fi
"
"""
}
}
}
@@ -65,13 +75,13 @@ pipeline {
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/
"""
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}-${params.PORT}/
"""
}
}
}
@@ -80,6 +90,8 @@ pipeline {
stage('Build on server') {
steps {
script {
def BUILD_TIME_FRONT = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
@@ -88,19 +100,21 @@ pipeline {
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
else
echo 'Exception: docker-compose file not found'
exit 1
fi
"
"""
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
# 1. СОЗДАЕМ JSON ФАЙЛ (ЭТО РАБОТАЕТ 100%)
mkdir -p public
echo '{\\"buildTime\\": \\"${BUILD_TIME_FRONT}\\"}' > public/build-info.json
# 2. Меняем имя контейнера и порт
sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
# 3. Собираем образ
docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
"
"""
}
}
}
@@ -117,24 +131,26 @@ pipeline {
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
# Проверяем и подключаем к сети app-network если нужно
docker network inspect app-network 2>/dev/null || echo 'Warning: app-network not found'
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml up -d
fi
sleep 10
echo 'Status container:'
docker ps --filter name=no-copy-frontend
"
"""
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
echo '=== Запускаем контейнер ==='
docker-compose -f docker-compose.yml -p frontend-${params.PORT} up -d
sleep 10
echo '=== Проверяем запуск ==='
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
echo '✓ Контейнер запущен: no-copy-frontend-${params.PORT}'
echo '✓ Порт: ${params.PORT}:2999'
else
echo '✗ Контейнер не запустился'
echo 'Логи:'
docker-compose -f docker-compose.yml -p frontend-${params.PORT} logs
exit 1
fi
"
"""
}
}
}
@@ -151,23 +167,93 @@ pipeline {
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container started'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP code: \$HTTP_CODE'
echo 'Frontend available on http://92.242.61.23:2998'
else
echo 'Exception: container did not start'
docker ps -a | grep frontend
exit 1
fi
"
"""
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
# Ищем контейнер с правильным именем (с портом)
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
echo 'Container started: no-copy-frontend-${params.PORT}'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} || echo '000')
echo 'HTTP code: \$HTTP_CODE'
echo 'Frontend available on http://92.242.61.23:${params.PORT}'
# Показываем все фронтенды
echo ''
echo 'All frontend containers:'
docker ps --filter name=no-copy-frontend --format 'table {{.Names}}\\t{{.Ports}}\\t{{.Status}}'
else
echo 'Exception: container did not start'
docker ps -a | grep frontend
exit 1
fi
"
"""
}
}
}
}
// stage('Connect to network') {
// steps {
// script {
// withCredentials([
// usernamePassword(
// credentialsId: 'server-root-password',
// usernameVariable: 'SSH_USER',
// passwordVariable: 'SSH_PASS'
// )
// ]) {
// sh """
// sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
// echo 'Отключаем от всех сетей и подключаем к ${params.NETWORK}'
//
// # Отключаем от всех сетей (кроме bridge, host, none)
// current_networks=\$(docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}' | grep -v -E '^(bridge|host|none)\$' || true)
//
// for net in \$current_networks; do
// echo \"Отключаем от сети: \$net\"
// docker network disconnect \$net no-copy-frontend-${params.PORT} 2>/dev/null || true
// done
//
// # Подключаем к нужной сети
// docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
//
// echo 'Готово! Контейнер теперь в сети:'
// docker inspect no-copy-frontend-${params.PORT} --format='{{range \$net, \$v := .NetworkSettings.Networks}}{{\$net}}{{\"\\n\"}}{{end}}'
// "
// """
// }
// }
// }
// }
stage('Connect to network') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
echo 'Подключаем контейнер к сети ${params.NETWORK}'
if ! docker network ls --format '{{.Name}}' | grep -q '^${params.NETWORK}\$'; then
docker network create ${params.NETWORK}
fi
if docker ps -a --format '{{.Names}}' | grep -q '^no-copy-frontend-${params.PORT}\$'; then
docker network disconnect ${params.NETWORK} no-copy-frontend-${params.PORT} 2>/dev/null || true
docker network connect ${params.NETWORK} no-copy-frontend-${params.PORT}
echo 'Контейнер подключен к ${params.NETWORK}'
fi
"
"""
}
}
}
@@ -177,7 +263,8 @@ pipeline {
post {
success {
echo "Front branch ${params.BRANCH} deployment completed"
echo "Frontend available on http://92.242.61.23:2998"
echo "Frontend available on http://92.242.61.23:${params.PORT}"
echo "Container: no-copy-frontend-${params.PORT}"
}
failure {
echo "Deployment failed"
+76 -119
View File
@@ -7,7 +7,6 @@ pipeline {
defaultValue: 'dev',
description: 'Ветка для деплоя'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
@@ -18,6 +17,7 @@ pipeline {
stages {
stage('Git pull') {
steps {
cleanWs()
script {
checkout([
$class: 'GitSCM',
@@ -34,130 +34,87 @@ pipeline {
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "TZ='Asia/Novosibirsk' date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD')
]) {
sh """
echo "Deploying branch: ${params.BRANCH}"
echo "Copying files to server..."
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH}
echo 'Step 1: Checking current state...'
# Смотрим что запущено
docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.Ports}}'
echo 'Step 2: Force cleanup old application...'
# Принудительно удаляем старый контейнер и образ
docker stop app-backend 2>/dev/null || echo 'App not running'
docker rm app-backend 2>/dev/null || echo 'App not found'
docker rmi app-backend:latest 2>/dev/null || echo 'Image not found'
# Удаляем все образы app-backend
docker images --filter 'reference=app-backend*' -q | xargs -r docker rmi 2>/dev/null || echo 'No images to remove'
echo 'Step 3: Verify copied files...'
echo 'Files in directory:'
ls -la
echo ''
echo 'Checking Java sources:'
find . -name '*.java' | head -2
echo 'Step 4: Check if PostgreSQL is running...'
if ! docker ps | grep -q postgres; then
echo 'Starting PostgreSQL...'
docker-compose up -d db
sleep 10
else
echo 'PostgreSQL already running'
fi
echo 'Step 5: Build application with forced rebuild...'
echo 'Checking Dockerfile exists:'
ls -la Dockerfile 2>/dev/null || echo 'Dockerfile not found, creating simple one'
# Если нет Dockerfile, создаем простой
if [ ! -f Dockerfile ]; then
echo 'Creating simple Dockerfile...'
cat > Dockerfile << 'EOF'
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY . /app/
CMD ["java", "-jar", "app.jar"]
EOF
fi
# Собираем с подробным выводом
echo 'Building Docker image...'
docker build --no-cache --progress=plain -t app-backend:latest . 2>&1 | tail -50
echo 'Step 6: Verify new image...'
echo 'Current app-backend images:'
docker images | grep app-backend
echo 'Image creation time:'
docker inspect app-backend:latest --format='{{.Created}}' 2>/dev/null || echo 'Cannot inspect image'
echo 'Step 7: Starting application with alias...'
docker run -d \\
--name app-backend \\
--network app-network \\
--network-alias app \\
-p 80:8080 \\
-e POSTGRES_DB=no_copy_ \\
-e POSTGRES_USER=postgres \\
-e POSTGRES_PASSWORD=postgres \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=db \\
--restart unless-stopped \\
app-backend:latest
echo 'Step 8: Checking deployment...'
sleep 10
echo 'Containers status:'
docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.CreatedAt}}'
echo 'Checking image inside container:'
docker exec app-backend ls -la /app/ 2>/dev/null || echo 'Cannot check container files'
echo 'Application logs (last 10 lines):'
docker logs --tail=10 app-backend 2>/dev/null || echo 'Logs not available yet'
echo 'Step 9: Health check...'
if docker ps | grep -q app-backend; then
echo 'Container is running'
echo 'Testing application health...'
for i in {1..5}; do
if curl -s -f http://localhost:80/health > /dev/null 2>&1; then
echo 'Health check passed'
echo 'Deployment successful'
echo 'Application URL: http://${params.SERVER}:80'
exit 0
fi
echo 'Waiting for application to start... attempt ' \$i
sleep 5
done
echo 'Health check failed, but container is running'
echo 'Application might be starting slowly'
echo 'Application URL: http://${params.SERVER}:80'
else
echo 'Application failed to start'
docker logs app-backend
exit 1
fi
"
echo "Deploying branch: ${params.BRANCH}"
echo "Copying files to server..."
sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}"
sshpass -p '$SSH_PASS' rsync -av --delete \\
--exclude=.git \\
--exclude=.gradle \\
--exclude=build \\
--exclude=**/build \\
--exclude=.idea \\
--exclude=out \\
. $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/
sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH}
echo '1. Остановка старого приложения...'
docker stop app-backend 2>/dev/null || echo 'Контейнер не найден'
docker rm app-backend 2>/dev/null || echo 'Контейнер не найден'
echo '2. Удаление старых образов...'
docker images --filter 'reference=*app*' -q | xargs -r docker rmi -f 2>/dev/null || echo 'Нет образов для удаления'
echo '3. Создание сети если нужно...'
docker network create app-network 2>/dev/null || echo 'Сеть уже существует'
echo '4. Запуск инфраструктуры...'
docker-compose up -d db storage
echo '5. Ожидание PostgreSQL...'
sleep 10
echo '6. Сборка нового образа приложения...'
docker build --no-cache -t app-backend:latest .
echo '7. Запуск приложения...'
docker run -d \\
--name app-backend \\
--network app-network \\
--network-alias app \\
-p 80:8080 \\
-v uploads_data:/data/uploads:rw \\
-e POSTGRES_DB=no_copy_ \\
-e POSTGRES_USER=$DB_USER \\
-e POSTGRES_PASSWORD=$DB_PASSWORD \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=db \\
-e BUILD_TIME_BACK='${buildTime}' \\
--restart unless-stopped \\
app-backend:latest
echo '8. Запуск мониторинга...'
docker-compose up -d grafana prometheus loki tempo alloy
echo '9. Проверка...'
sleep 5
echo 'Статус контейнеров:'
docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}'
echo '10. Проверка health...'
if curl -s -f http://localhost:80/health > /dev/null 2>&1; then
echo 'Приложение работает'
echo 'URL: http://${params.SERVER}:80'
else
echo 'Проверка health не удалась'
docker logs app-backend --tail=20
fi
"
"""
}
}
@@ -177,4 +134,4 @@ EOF
echo "Deployment process finished"
}
}
}
}
@@ -1,183 +0,0 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
fi
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
else
echo 'Exception:don't found docker-compose'
exit 1
fi
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml up -d
fi
sleep 10
echo 'Status container:'
docker ps --filter name=no-copy-frontend
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container start'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP код: ' \$HTTP_CODE
echo 'Frong start on http://92.242.61.23:2998'
else
echo 'Exception: container don't start'
docker ps -a | grep frontend
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Front branch ${params.BRANCH} completed"
echo "Use on http://92.242.61.23:2998"
}
failure {
echo "Failed"
}
}
}
+156
View File
@@ -0,0 +1,156 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя легальной панели'
)
string(
name: 'PORT',
defaultValue: '2997',
description: 'Порт для запуска экземпляра'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-legal-panel-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-legal-panel-${params.PORT} 2>/dev/null || true
docker rm no-copy-legal-panel-${params.PORT} 2>/dev/null || true
mkdir -p /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}
rm -rf /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}/*
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}
docker build -t no-copy-legal-panel:${params.BRANCH}-${params.PORT} .
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker run -d \\
--name no-copy-legal-panel-${params.PORT} \\
--restart unless-stopped \\
--network app-network \\
-p ${params.PORT}:2997 \\
no-copy-legal-panel:${params.BRANCH}-${params.PORT}
sleep 5
docker ps --filter name=no-copy-legal-panel-${params.PORT}
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-legal-panel-${params.PORT}'; then
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} 2>/dev/null || echo '000')
echo 'Legal panel available on http://92.242.61.23:${params.PORT}'
else
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Legal panel ${params.BRANCH} deployed on port ${params.PORT}"
}
failure {
echo "Deployment failed"
}
}
}
File diff suppressed because it is too large Load Diff
+353
View File
@@ -0,0 +1,353 @@
openapi: 3.0.3
info:
title: NoCopy API - API Keys Management
description: |
API для управления пользовательскими API ключами.
Включает контроллер: ApiKeyController
Позволяет создавать, просматривать и отзывать API ключи.
version: 1.0.0
contact:
name: NoCopy
servers:
- url: http://localhost:8080
description: Локальный
- url: https://dev-workspace.not-copy.com/
description: Дев
tags:
- name: User
description: Управление пользовательскими данными и API ключами
paths:
/api/user/api-keys:
post:
tags:
- User
summary: Создание API ключа
description: |
Создаёт новый API ключ для пользователя.
- Требуется авторизация (Bearer токен)
- Если имя не указано, будет присвоено "Unnamed key"
- Возвращает полный ключ только один раз при создании
- Префикс ключа можно использовать для идентификации без раскрытия секрета
operationId: createApiKey
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: Название ключа для идентификации (опционально)
example: "My API Key"
maxLength: 255
examples:
withName:
summary: С указанием названия
value:
name: "Production Key"
withDescription:
summary: С описательным названием
value:
name: "Key for mobile app"
withoutName:
summary: Без названия (по умолчанию "Unnamed key")
value: {}
minimalExample:
summary: Пустое тело запроса
value: {}
responses:
'200':
description: Ключ успешно создан
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
success:
summary: API ключ создан
value:
msg_id: null
message_code: 0
message_desc: "API key created successfully"
message_body:
apiKey: "sk-live-abc123def456789ghi012jkl345mno678"
successWithDescription:
summary: API ключ с описанием создан
value:
msg_id: null
message_code: 0
message_desc: "API key created successfully"
message_body:
apiKey: "sk-test-xyz987abc654def321ghi098jkl765mno"
'401':
description: Неавторизованный доступ
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
unauthorized:
summary: Токен не предоставлен или недействителен
value:
msg_id: null
message_code: 7
message_desc: "Authentication required"
message_body: null
get:
tags:
- User
summary: Получение списка API ключей
description: |
Возвращает все API ключи текущего пользователя.
**Особенности:**
- Требуется авторизация (Bearer токен)
- Полные ключи не возвращаются, только префиксы
- Включает информацию об активности и времени использования
operationId: listApiKeys
security:
- BearerAuth: []
responses:
'200':
description: Список ключей пользователя
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
success:
summary: Список с несколькими ключами
value:
msg_id: null
message_code: 0
message_desc: null
message_body:
- id: 1
name: "Production Key"
prefix: "sk-live-abc123..."
isActive: true
createdAt: "2025-01-15T10:30:00Z"
lastUsedAt: "2025-06-30T14:22:00Z"
- id: 2
name: "Key for mobile app"
prefix: "sk-live-def456..."
isActive: true
createdAt: "2025-03-20T08:00:00Z"
lastUsedAt: "2025-06-29T18:45:00Z"
- id: 3
name: "Old Development Key"
prefix: "sk-test-ghi789..."
isActive: false
createdAt: "2025-02-10T12:00:00Z"
lastUsedAt: "2025-05-15T09:30:00Z"
singleKey:
summary: Один активный ключ
value:
msg_id: null
message_code: 0
message_desc: null
message_body:
- id: 1
name: "My API Key"
prefix: "sk-live-abc123..."
isActive: true
createdAt: "2025-06-01T00:00:00Z"
lastUsedAt: null
empty:
summary: Нет созданных ключей
value:
msg_id: null
message_code: 0
message_desc: null
message_body: []
'401':
description: Неавторизованный доступ
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
unauthorized:
summary: Токен не предоставлен или недействителен
value:
msg_id: null
message_code: 7
message_desc: "Authentication required"
message_body: null
/api/user/api-keys/{keyId}:
delete:
tags:
- User
summary: Отзыв (удаление) API ключа
description: |
Деактивирует API ключ пользователя. Ключ становится неактивным и больше не может использоваться.
**Особенности:**
- Требуется авторизация (Bearer токен)
- Пользователь может отозвать только свои ключи
- Отозванный ключ нельзя восстановить (нужно создавать новый)
operationId: revokeApiKey
security:
- BearerAuth: []
parameters:
- name: keyId
in: path
required: true
schema:
type: integer
format: int64
description: ID ключа для отзыва
example: 1
responses:
'200':
description: Ключ успешно отозван
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
success:
summary: Ключ отозван
value:
msg_id: null
message_code: 0
message_desc: null
message_body: null
'401':
description: Неавторизованный доступ
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
unauthorized:
summary: Токен не предоставлен или недействителен
value:
msg_id: null
message_code: 7
message_desc: "Authentication required"
message_body: null
'403':
description: Доступ запрещён (ключ не принадлежит пользователю)
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
forbidden:
summary: Ключ принадлежит другому пользователю
value:
msg_id: null
message_code: 6
message_desc: "Access denied"
message_body: null
'404':
description: Ключ не найден
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
examples:
notFound:
summary: Ключ с указанным ID не существует
value:
msg_id: null
message_code: 4
message_desc: "API key not found"
message_body:
keyId: 999
components:
schemas:
BaseResponse:
type: object
properties:
msg_id:
type: integer
description: Идентификатор сообщения (может быть null)
nullable: true
example: null
message_code:
type: integer
description: |
Код результата:
- 0 - Успешно
- 4 - Ресурс не найден
- 6 - Доступ запрещён
- 7 - Требуется авторизация
example: 0
message_desc:
type: string
description: Описание результата операции (может быть null)
nullable: true
example: "API key created successfully"
message_body:
description: Данные ответа
nullable: true
oneOf:
- type: object
properties:
apiKey:
type: string
description: Полный API ключ (только при создании)
example: "sk-live-abc123def456..."
description: Ответ при создании ключа
- type: array
items:
$ref: '#/components/schemas/ApiKeyInfo'
description: Список ключей пользователя
- type: object
description: Пустой объект или null
nullable: true
example: null
ApiKeyInfo:
type: object
description: Информация об API ключе
properties:
id:
type: integer
format: int64
description: Уникальный идентификатор ключа
example: 1
name:
type: string
description: Название ключа
example: "Production Key"
prefix:
type: string
description: Префикс ключа для идентификации
example: "sk-live-abc123..."
isActive:
type: boolean
description: Активен ли ключ
example: true
createdAt:
type: string
format: date-time
description: Дата и время создания ключа
example: "2025-01-15T10:30:00Z"
lastUsedAt:
type: string
format: date-time
nullable: true
description: Дата и время последнего использования (null если не использовался)
example: "2025-06-30T14:22:00Z"
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: JWT токен для авторизации (получить через /api/auth)
security:
- BearerAuth: []
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'ncp_db') THEN
CREATE USER ncp_db WITH PASSWORD 'ncpDbApp';
END IF;
END
$$;
GRANT CONNECT ON DATABASE no_copy_ TO ncp_db;
GRANT USAGE ON SCHEMA public TO ncp_db;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO ncp_db;
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
plugins {
kotlin("jvm") version "2.1.10"
}
group = "ru.soune"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-core-jvm:4.1.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
+5
View File
@@ -0,0 +1,5 @@
package ru.soune
fun main() {
println("Hello World!")
}
+14
View File
@@ -0,0 +1,14 @@
package ru.soune
interface Referral {
val userId: Long
val referralLink: String
val inviter: Long?
val currentLevel: String
val totalIncome: Int
val availableIncome: Int
val holdBalance: Int
val spentBalance: Int
val active: Boolean
}
@@ -0,0 +1,7 @@
package ru.soune
data class ReferralInvitee(
val email: String,
val isActive: Boolean,
val regDate: String,
)
+10
View File
@@ -0,0 +1,10 @@
package ru.soune
interface ReferralLevel {
val id: String
val name: String
val share: Int
val minInvitee: Int
val maxInvitee: Int
val next: String?
}
@@ -0,0 +1,11 @@
package ru.soune
interface ReferralLevelProvider {
fun getAvailableReferralLevels(): List<ReferralLevel>
fun getInitialReferralLevelId(): String
fun getReferralLevelById(levelId: String): ReferralLevel
}
+33
View File
@@ -0,0 +1,33 @@
package ru.soune
interface ReferralRepo {
fun getUserReferralLink(userId: Long): String
fun getUserIdByLink(link: String): Long?
fun getInviterIdForUser(userId: Long): Long?
fun getReferralLevelForUser(userId: Long): String
fun getReferralByUserId(userId: Long): Referral
fun getReferralInvitees(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
// создаем новую запись в БД
fun createReferralEntity(entity: Referral)
// прибавить к totalIncome и availableIncome значение transferAmount
fun increaseIncome(userId: Long, transferAmount: Int)
// установить active в true
fun activateUser(userId: Long): Boolean
// посчитать записи, у которых inviter == userId и active == true
fun getActiveInviteeForUser(userId: Long): Int
// посчитать записи, у которых inviter == userId
fun getTotalInviteeForUser(userId: Long): Int
fun upgradeReferralLevelForUser(userId: Long, newLevelId: String)
}
@@ -0,0 +1,70 @@
package ru.soune
class ReferralService(
private val referralLevelProvider: ReferralLevelProvider,
private val referralRepo: ReferralRepo,
) {
fun onRegister(userId: Long, inviterReferralLink: String?) {
val initialReferralLevel = referralLevelProvider.getInitialReferralLevelId()
val referralLink = referralRepo.getUserReferralLink(userId)
val inviter = inviterReferralLink?.let(referralRepo::getUserIdByLink)
val referral = object : Referral {
override val userId = userId
override val referralLink = referralLink
override val inviter = inviter
override val currentLevel = initialReferralLevel
override val totalIncome = 0
override val availableIncome = 0
override val holdBalance: Int = 0
override val spentBalance: Int = 0
override val active: Boolean = false
}
referralRepo.createReferralEntity(referral)
}
fun onUserAccountRefill(userId: Long, transferAmount: Int) {
val wasActivated = referralRepo.activateUser(userId)
val inviterId = referralRepo.getInviterIdForUser(userId) ?: return
val currentInviterLevel = referralRepo.getReferralLevelForUser(inviterId)
.let { referralLevelProvider.getReferralLevelById(it) }
val reward = transferAmount * currentInviterLevel.share / 100
referralRepo.increaseIncome(inviterId, reward)
if (!wasActivated) return
val currentCount = referralRepo.getActiveInviteeForUser(inviterId)
val nextLevel = currentInviterLevel.next
if (currentCount + 1 > currentInviterLevel.maxInvitee && nextLevel != null) {
referralRepo.upgradeReferralLevelForUser(inviterId, nextLevel)
}
}
fun getReferralLevels(): List<ReferralLevel> =
referralLevelProvider.getAvailableReferralLevels()
fun getReferralLevelForUser(userId: Long): ReferralLevel =
referralRepo.getReferralLevelForUser(userId)
.let { referralLevelProvider.getReferralLevelById(it) }
fun getReferralStatForUser(userId: Long): ReferralStat {
var referral = referralRepo.getReferralByUserId(userId)
return ReferralStat(
totalInvitee = referralRepo.getTotalInviteeForUser(userId),
activeInvitee = referralRepo.getActiveInviteeForUser(userId),
totalIncome = referral.totalIncome,
availableIncome = referral.availableIncome,
holdBalance = referral.holdBalance,
referralLink = referral.referralLink,
spentBalance = referral.spentBalance,
)
}
fun getInviteeForUser(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
referralRepo.getReferralInvitees(userId, pageSize, pageNumber)
}
+11
View File
@@ -0,0 +1,11 @@
package ru.soune
data class ReferralStat(
val totalInvitee: Int,
val activeInvitee: Int,
val totalIncome: Int,
val availableIncome: Int,
val holdBalance: Int,
val referralLink: String,
val spentBalance: Int,
)
+6
View File
@@ -1 +1,7 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
rootProject.name = 'no-copy'
include 'referral'
include 'finance'
@@ -2,8 +2,12 @@ package ru.soune.nocopy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableCaching
public class NoCopyApplication {
public static void main(String[] args) {
@@ -0,0 +1,69 @@
package ru.soune.nocopy.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
@Slf4j
public class YooKassaClient {
@Value("${yookassa.shop-id}")
private String shopId;
@Value("${yookassa.secret-key}")
private String secretKey;
private final HttpClient httpClient = HttpClient.newHttpClient();
private final ObjectMapper mapper = new ObjectMapper();
public boolean createAutoPayment(String paymentMethodId, double amount, String description, String userId) {
try {
String url = "https://api.yookassa.ru/v3/payments";
String auth = shopId + ":" + secretKey;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
Map<String, Object> body = new HashMap<>();
body.put("amount", Map.of("value", String.format("%.2f", amount),
"currency", "RUB"));
body.put("payment_method_id", paymentMethodId);
body.put("description", description);
body.put("capture", true);
body.put("metadata", Map.of("user_id", userId, "auto_payment", "true"));
String requestBody = mapper.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Basic " + encodedAuth)
.header("Idempotence-Key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200 || response.statusCode() == 201) {
log.info("Auto payment created: {}", response.body());
return true;
} else {
log.error("Auto payment failed: {} - {}", response.statusCode(), response.body());
return false;
}
} catch (Exception e) {
log.error("Error creating auto payment", e);
return false;
}
}
}
@@ -1,17 +1,73 @@
package ru.soune.nocopy.configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.vrt.AudioFilePathProvider;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.audio.AudioLocalSearch;
import com.vrt.fileprotection.documents.DocumentLocalSearch;
import com.vrt.fileprotection.image.ImageLocalSearch;
import com.vrt.fileprotection.image.ImageUniqueCheck;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableAutoConfiguration
@AllArgsConstructor
@EnableCaching
public class ApplicationConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public com.vrt.
NoCopyFileService noCopyFileService(
FileProtector.FileProvider fileProvider,
FileProtector.ProcessingListener processingListener,
ImageUniqueCheck imageUniqueCheck,
ImageLocalSearch imageLocalSearch,
AudioLocalSearch audioLocalSearch,
AudioFilePathProvider audioFilePathProvider,
DocumentLocalSearch documentLocalSearch) {
return new com.vrt.NoCopyFileService(
Collections.emptyList(),
fileProvider,
processingListener,
imageUniqueCheck,
imageLocalSearch,
audioLocalSearch,
audioFilePathProvider,
documentLocalSearch
);
}
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager(
"reverseImageSearch", "parsedImages");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(2_000)
.expireAfterWrite(1, TimeUnit.HOURS)
.expireAfterAccess(30, TimeUnit.MINUTES)
.softValues()
.recordStats()
);
return cacheManager;
}
}
@@ -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);
}
}
@@ -0,0 +1,157 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.soune.nocopy.handler.*;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class HandlerConfig {
/*
RegRequestHandler
LoginRequestHandler
FileUploadHandler
FileEntityHandler
ImageFoundRequestHandler
VerifyRegisterUserHandler
TariffHandler
ReferralHandler
ResetPasswordHandler
MonitoringHandler
GlobalSearchHandler
ComplaintEntityHandler
ViolationNotionHandler
NotificationHandler
TokenOperationHandler
DockViewFileHandler
*/
/*
Убрать
LogoutRequestHandler logoutHandler
*/
@Bean
public Map<Integer, RequestHandler> handlers(
FileUploadHandler upload,
FileEntityHandler file,
ImageFoundRequestHandler imageFoundRequestHandler,
VerifyRegisterUserHandler verifyRegisterUser,
AuthRequestHandler authRequestHandler,
CompanyHandler companyHandler,
TariffHandler tariffHandler,
TariffInfoHandler tariffInfoHandler,
ReferralHandler referralHandler,
PaymentHandler paymentHandler,
CostHandler costHandler,
MonitoringHandler monitoringHandler,
ViolationHandler violationHandler,
ViolationStatisticsHandler violationStatisticsHandler,
GlobalSearchHandler globalSearchHandler,
ViolationNotionHandler violationNotionHandler,
ComplaintEntityHandler complaintEntityHandler,
UserInfoHandler userInfoHandler,
NotificationHandler notificationHandler,
UserVerificationHandler userVerificationHandler,
LawCaseHandler lawCaseHandler,
TokenOperationHandler tokenOperationHandler,
MonitoringStatisticHandler monitoringStatisticHandler,
DockViewFileHandler dockViewFileHandler
) {
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20004, upload);
map.put(20005, file);
map.put(20007, imageFoundRequestHandler);
map.put(20008, authRequestHandler);
map.put(20009, verifyRegisterUser);
map.put(30000, companyHandler);
map.put(30001, tariffHandler);
map.put(30002, tariffInfoHandler);
map.put(30003, referralHandler);
map.put(30005, paymentHandler);
map.put(30007, monitoringHandler);
map.put(30008, costHandler);
map.put(30009, violationHandler);
map.put(30010, violationStatisticsHandler);
map.put(30011, globalSearchHandler);
map.put(30012, violationNotionHandler);
map.put(30013, complaintEntityHandler);
map.put(30014, userInfoHandler);
map.put(30015, notificationHandler);
map.put(30016, userVerificationHandler);
map.put(30017, lawCaseHandler);
map.put(30018, tokenOperationHandler);
map.put(30027, monitoringStatisticHandler);
map.put(30028, dockViewFileHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> internalInfoHandler(
StatisticComplaintHandler statisticComplaintHandler,
StatisticUserFilesHandler statisticUserHandler,
StatisticTariffInfoFileHandler statisticTariffInfoFileHandler,
StatisticUserDynamicHandler statisticUserDynamicHandler,
StatisticProtectedFilesHandler statisticProtectedFilesHandler,
StatisticViolationHandler statisticViolationHandler,
StatisticSubscriberHandler statisticSubscriberHandler,
StatisticTokenHandler statisticTokenHandler,
StatisticIncomeHandler statisticIncomeHandler,
FileInteranlInfoHandler fileInfo,
TariffInformationHandler tariffInformationHandler,
UserInfoHandler userInfoHandler
)
{
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(30014, userInfoHandler);
map.put(30019, statisticUserHandler);
map.put(30020, statisticTariffInfoFileHandler);
map.put(30021, statisticUserDynamicHandler);
map.put(30022, statisticProtectedFilesHandler);
map.put(30023, statisticViolationHandler);
map.put(30024, statisticSubscriberHandler);
map.put(30025, statisticTokenHandler);
map.put(30026, statisticIncomeHandler);
map.put(30029, statisticComplaintHandler);
map.put(40001, fileInfo);
map.put(40004, tariffInformationHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> internalControlHandler(
ControlComplaintHandler controlComplaintHandler,
FileInternalControlHandler fileInternalControlHandler,
TariffControlHandler tariffControlHandler,
UserInfoHandler userInfoHandler
)
{
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(30014, userInfoHandler);
map.put(30030, controlComplaintHandler);
map.put(40002, fileInternalControlHandler);
map.put(40003, tariffControlHandler);
return map;
}
@Bean
public Map<Integer, RequestHandler> authHandler(
RegRequestHandler reg,
LoginRequestHandler login,
DaDataHandler daDataHandler,
ResetPasswordHandler resetPasswordHandler
)
{
Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login);
map.put(20002, reg);
map.put(30004, daDataHandler);
map.put(20010, resetPasswordHandler);
return map;
}
}
@@ -0,0 +1,44 @@
package ru.soune.nocopy.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.ImageResizeService;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Component
@Slf4j
public class ImageMigration {
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private ImageResizeService imageResizeService;
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
List<FileEntity> images = fileRepository.findByThumbnailPathIsNull();
for (FileEntity file : images) {
try {
Path path = Paths.get(file.getProtectedFilePath());
if (Files.exists(path)) {
byte[] data = Files.readAllBytes(path);
imageResizeService.generateSizes(file, data);
fileRepository.save(file);
}
} catch (Exception e) {
log.error("Error migrating {}: {}", file.getId(), e.getMessage());
}
}
}
}
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -20,7 +21,13 @@ public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.defaultViewInclusion(true)
.autoDetectFields(true)
.failOnUnknownProperties(false)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.build();
JavaTimeModule javaTimeModule = new JavaTimeModule();
@@ -35,6 +42,8 @@ public class JacksonConfig {
mapper.registerModule(javaTimeModule);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.soune.ReferralLevelProvider;
import ru.soune.ReferralRepo;
import ru.soune.ReferralService;
@Configuration
public class ReferralConfig {
@Bean
public ReferralService referralService(ReferralLevelProvider referralLevelProvider, ReferralRepo referralRepo) {
return new ReferralService(referralLevelProvider, referralRepo);
}
}
@@ -0,0 +1,22 @@
package ru.soune.nocopy.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(30000);
requestFactory.setReadTimeout(60000);
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
}
}
@@ -0,0 +1,35 @@
package ru.soune.nocopy.configuration.file;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "fileUploadTaskExecutor")
public Executor fileUploadTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("FileUpload-");
executor.initialize();
return executor;
}
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("global-search-");
executor.initialize();
return executor;
}
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.configuration.file;
import lombok.AllArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@AllArgsConstructor
public class NoCopyInitializer {
@EventListener(ApplicationReadyEvent.class)
public void initializeOnStartup() {
}
}
@@ -0,0 +1,26 @@
package ru.soune.nocopy.configuration.search;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Data
@Component
@ConfigurationProperties(prefix = "search")
public class SearchProperties {
private Map<String, EngineConfig> engines;
private ProxyConfig proxy;
@Data
public static class EngineConfig {
private boolean enabled;
}
@Data
public static class ProxyConfig {
private boolean enabled;
private String host;
private int port;
}
}
@@ -0,0 +1,44 @@
package ru.soune.nocopy.configuration.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import ru.soune.nocopy.util.ApiKeyFilter;
import ru.soune.nocopy.util.JwtAuthFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthFilter jwtAuthFilter;
private final ApiKeyFilter apiKeyFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v{version}/files/public-download/**").permitAll()
.requestMatchers("/api/files/public/**").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/file/link/**").permitAll()
.requestMatchers("/check/api/**").permitAll()
.requestMatchers("/api/internal/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@@ -1,27 +1,53 @@
package ru.soune.nocopy.controller;
import jakarta.annotation.PostConstruct;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult;
import com.vrt.fileprotection.audio.AudioCheckResult;
import com.vrt.fileprotection.documents.DocumentCheckResult;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.http.ResponseEntity;
import org.apache.commons.io.IOUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.*;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.RegAnswer;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.ValidationException;
import ru.soune.nocopy.handler.LoginRequestHandler;
import ru.soune.nocopy.handler.LogoutRequestHandler;
import ru.soune.nocopy.handler.RegRequestHandler;
import ru.soune.nocopy.handler.RequestHandler;
import ru.soune.nocopy.dto.docviewer.DocViewerRequest;
import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.entity.file.permission.PermissionType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.handler.*;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.dockview.DockViewService;
import ru.soune.nocopy.service.file.*;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@@ -29,20 +55,39 @@ import java.util.stream.Collectors;
@RequestMapping("/api")
@RequiredArgsConstructor
public class ApiController {
private final ApplicationContext applicationContext;
private final FileUploadService fileUploadService;
private Map<Integer, RequestHandler> handlers = new HashMap<>();
private final Map<Integer, RequestHandler> handlers;
@PostConstruct
public void init() {
RegRequestHandler regHandler = applicationContext.getBean(RegRequestHandler.class);
LoginRequestHandler loginHandler = applicationContext.getBean(LoginRequestHandler.class);
LogoutRequestHandler logoutHandler = applicationContext.getBean(LogoutRequestHandler.class);
private final Map<Integer, RequestHandler> internalInfoHandler;
handlers.put(20001, loginHandler);
handlers.put(20002, regHandler);
handlers.put(20006, logoutHandler);
}
private final Map<Integer, RequestHandler> internalControlHandler;
private final Map<Integer, RequestHandler> authHandler;
private final FileEntityService fileEntityService;
private final FileSimilarityService fileSimilarityService;
private final FileEntityRepository fileEntityRepository;
private final NoCopyFileService noCopyFileService;
private final HttpServletRequest httpServletRequest;
private final ProtectionsLimitService protectionsLimitService;
private final AuthTokenRepository authTokenRepository;
private final UserRepository userRepository;
private final CheckCounterService checkCounterService;
private final CloudStorageService cloudStorageService;
private final ZipService zipService;
private final DockViewService dockViewService;
@PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@@ -65,6 +110,12 @@ public class ApiController {
return ResponseEntity.ok().body(response);
} catch (ValidationException e) {
return createValidationErrorResponse(e.getBindingResult(), e.getMsgId());
} catch (UserNotFoundException e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotFoundAuthToken e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotValidFieldException e) {
throw e;
} catch (Exception e) {
@@ -79,6 +130,685 @@ public class ApiController {
}
}
@PostMapping("/auth")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request) {
Integer msgId = request.getMsgId();
BaseResponse response;
try {
RequestHandler handler = authHandler.get(msgId);
if (handler == null) {
response = new BaseResponse(msgId,
MessageCode.MSG_ID_NOT_FOUND.getCode(),
MessageCode.MSG_ID_NOT_FOUND.getDescription(),
new HashMap<>());
} else {
response = handler.handle(request);
}
return ResponseEntity.ok().body(response);
} catch (ValidationException e) {
return createValidationErrorResponse(e.getBindingResult(), e.getMsgId());
} catch (UserNotFoundException e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotFoundAuthToken e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotValidFieldException e) {
throw e;
} catch (Exception e) {
log.error("Handler execution failed for msgId: {}", msgId, e);
BaseResponse errorResponse = new BaseResponse(msgId,
MessageCode.INVALID_JSON_BODY.getCode(),
MessageCode.INVALID_JSON_BODY.getDescription(),
new HashMap<>());
return ResponseEntity.ok().body(errorResponse);
}
}
@PostMapping("/internal/data-info")
public ResponseEntity<?> handleInternalInfoPostRequest(@RequestBody BaseRequest request) {
Integer msgId = request.getMsgId();
BaseResponse response;
RequestHandler internalHandler = internalInfoHandler.get(msgId);
try {
if (internalHandler == null) {
response = new BaseResponse(msgId,
MessageCode.MSG_ID_NOT_FOUND.getCode(),
MessageCode.MSG_ID_NOT_FOUND.getDescription(),
new HashMap<>());
} else {
response = internalHandler.handle(request);
}
return ResponseEntity.ok().body(response);
} catch (Exception e) {
log.error("Handler execution failed for msgId: {}", msgId, e);
BaseResponse errorResponse = new BaseResponse(msgId,
MessageCode.INVALID_JSON_BODY.getCode(),
MessageCode.INVALID_JSON_BODY.getDescription(),
new HashMap<>());
return ResponseEntity.ok().body(errorResponse);
}
}
@GetMapping("/internal/data-download/{fileId}")
public ResponseEntity<byte[]> getPublicFile(@PathVariable String fileId) {
try {
FileEntity fileEntity = fileEntityRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found in DB: " + fileId));
MediaType mediaType = getMediaType(fileEntity);
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
byte[] fileBytes;
try (InputStream inputStream = cloudStorageService.readFileFromStorage(fileEntity.getFilePath())) {
fileBytes = IOUtils.toByteArray(inputStream);
}
return ResponseEntity.ok()
.contentType(mediaType)
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(fileBytes);
} catch (FileNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (NoSuchKeyException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/internal/data-control")
public ResponseEntity<?> handleInternalControlPostRequest(@RequestBody BaseRequest request) {
Integer msgId = request.getMsgId();
BaseResponse response;
RequestHandler internalHandler = internalControlHandler.get(msgId);
try {
if (internalHandler == null) {
response = new BaseResponse(msgId,
MessageCode.MSG_ID_NOT_FOUND.getCode(),
MessageCode.MSG_ID_NOT_FOUND.getDescription(),
new HashMap<>());
} else {
response = internalHandler.handle(request);
}
return ResponseEntity.ok().body(response);
} catch (Exception e) {
log.error("Handler execution failed for msgId: {}", msgId, e);
BaseResponse errorResponse = new BaseResponse(msgId,
MessageCode.INVALID_JSON_BODY.getCode(),
MessageCode.INVALID_JSON_BODY.getDescription(),
new HashMap<>());
return ResponseEntity.ok().body(errorResponse);
}
}
@PostMapping("/v{version}/files/chunk")
public ResponseEntity<BaseResponse> uploadChunk(
@PathVariable("version") int version,
@RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "findSimilar", required = false, defaultValue = "0") Integer findSimilar) {
try {
if (chunk == null || chunk.isEmpty()) {
return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty");
}
if (uploadId == null || uploadId.isBlank()) {
return buildErrorResponse(uploadId, chunkNumber, "Upload ID is required");
}
if (chunkNumber == null || chunkNumber < 0) {
return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required");
}
UploadProgressResponse uploadProgressResponse = fileUploadService.uploadChunk(uploadId, chunkNumber,
chunk, findSimilar);
return buildSuccessResponse(uploadId, chunkNumber, chunk,
fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath()));
} catch (DuplicateImageException e) {
Map<String, Object> duplicateData = new HashMap<>();
duplicateData.put("duplicateFileId", e.duplicateFileId());
duplicateData.put("userId", e.userId());
duplicateData.put("message", e.getMessage());
if (uploadId != null) {
duplicateData.put("uploadId", uploadId);
}
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(),
duplicateData
));
} catch (FileFormatException e){
return buildErrorResponse(uploadId, chunkNumber, e.getMessage());
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
}
}
@PostMapping("/v{version}/private/files/chunk")
public ResponseEntity<BaseResponse> uploadChunk(
@RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "last_file", required = false) Boolean last_file) {
try {
if (chunk == null || chunk.isEmpty()) {
return buildErrorResponse(uploadId, chunkNumber, "Chunk file null or empty");
}
if (uploadId == null || uploadId.isBlank()) {
return buildErrorResponse(uploadId, chunkNumber, "Upload Id is required");
}
if (chunkNumber == null || chunkNumber < 0) {
return buildErrorResponse(uploadId, chunkNumber, "Valid chunk number is required");
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
UploadProgressResponse uploadProgressResponse = fileUploadService.uploadPassportChunk(uploadId, chunkNumber,
chunk, userId);
if (last_file) {
zipService.createAndSplitZip(userId,
fileEntityService.getAllUserFiles(userId, List.of(FileStatus.PRIVATE)));
}
return buildSuccessResponse(uploadId, chunkNumber, chunk,
fileEntityService.findFileIdByPath(uploadProgressResponse.getFilePath()));
}catch (FileFormatException e){
return buildErrorResponse(uploadId, chunkNumber, e.getMessage());
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
}
}
@GetMapping("/check/{fileId}")
public ResponseEntity<BaseResponse> check(@PathVariable String fileId) {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
File file = new File(fileEntity.getFilePath());
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, FileProtector.Type.IMAGE);
log.info("checkResult: {}", checkResult);
return ResponseEntity.ok(new BaseResponse(1, 1, "1", checkResult));
}
@GetMapping("/v{version}/files/{fileId}/similar")
public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version,
@PathVariable String fileId,
@RequestParam(required = false) List<String> similarityLevels,
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
try {
FileEntity fileEntity = fileEntityRepository.findByFileId(fileId);
if (fileEntity == null) {
return ResponseEntity.ok().body(new BaseResponse(0, MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), null));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
BaseResponse response;
if ("image".equals(fileEntity.getMimeType())) {
Page<SimilarFileDTO> similarFilesPage = fileSimilarityService.findSimilarFiles(
fileId, similarityLevels, pageable, userId);
response = buildSuccessResponse(similarFilesPage, MessageCode.SIMILAR_FILES_FOUND);
} else {
List<SimilarFileDTO> results = processNonImageFile(fileEntity, true);
log.info("results: {}", results);
response = buildSuccessResponse(results, MessageCode.SIMILAR_FILES_FOUND);
checkCounterService.incrementCheckCount(fileEntity.getUserId(), fileEntity.getMimeType());
}
return ResponseEntity.ok(response);
} catch(IllegalArgumentException e) {
log.error("Error with file extensions : {}", e.getMessage(), e);
return ResponseEntity.ok().body(new BaseResponse(MessageCode.FILE_FOR_SEARCH_NOT_VALID.getCode(),
MessageCode.FILE_FOR_SEARCH_NOT_VALID.getCode(),
MessageCode.FILE_FOR_SEARCH_NOT_VALID.getDescription(), null));
} catch (Exception e) {
log.error("Error finding similar files: {}", e.getMessage(), e);
return ResponseEntity.ok().body(new BaseResponse(MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getCode(),
MessageCode.INVALID_FIELD.getDescription(), null));
}
}
private List<SimilarFileDTO> processNonImageFile(FileEntity fileEntity, boolean cloud) throws Exception {
List<SimilarFileDTO> results = new ArrayList<>();
File file = null;
FileEntity duplicateByHash = fileSimilarityService.findDuplicateByHash(fileEntity.getFilePath(),
fileEntity.getMimeType(), fileEntity.getUserId(), cloud);
log.info("duplicateByHash: {}", duplicateByHash);
if (duplicateByHash != null) {
results.add(fileSimilarityService.buildDTO(duplicateByHash));
return results;
}
try {
file = cloudStorageService.readFileFromStorageByPath(fileEntity.getFilePath());
FileProtector.Type type = "document".equals(fileEntity.getMimeType()) ?
FileProtector.Type.DOC : FileProtector.Type.AUDIO;
NoCopyCheckResult checkResult = noCopyFileService.checkFile(file, type);
log.info("checkResult: {}", checkResult);
switch (checkResult) {
case DocumentCheckResult.Success success -> {
FileProtector.FileInfo info = success.getInfo();
FileEntity entity = fileEntityRepository.findByFileId(info.getId());
if (entity != null) {
results.add(fileSimilarityService.buildDTO(entity));
}
}
case AudioCheckResult.Success success -> {
FileProtector.FileInfo info = success.getInfo();
FileEntity entity = fileEntityRepository.findByFileId(info.getId());
if (entity != null) {
results.add(fileSimilarityService.buildDTO(entity));
}
}
case DocumentCheckResult.Failed failed ->
log.warn("Document check failed: {}", failed.getMessage());
case AudioCheckResult.Failed failed ->
log.warn("Audio check failed: {}", failed.getMessage());
default -> log.warn("Unexpected result type: {}", checkResult.getClass().getSimpleName());
}
} catch (Exception e) {
throw new Exception("Files for search not valid");
} finally {
if (file != null && file.exists()) {
file.delete();
}
}
return results;
}
private BaseResponse buildSuccessResponse(Page<SimilarFileDTO> page, MessageCode messageCode) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("content", page.getContent());
responseData.put("page", page.getNumber());
responseData.put("size", page.getSize());
responseData.put("totalElements", page.getTotalElements());
responseData.put("totalPages", page.getTotalPages());
responseData.put("hasNext", page.hasNext());
responseData.put("hasPrevious", page.hasPrevious());
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}")
public ResponseEntity<BaseResponse> getUploadProgress(
@PathVariable("version") int version,
@PathVariable String uploadId) {
log.info("Getting progress for upload session: {}, version: {}", uploadId, version);
try {
var progress = fileUploadService.getUploadProgress(uploadId);
UploadProgress responseBody = UploadProgress.builder()
.uploadId(progress.getUploadId())
.fileName(progress.getFileName())
.totalChunks(progress.getTotalChunks())
.uploadedChunks(progress.getUploadedChunks())
.status(progress.getStatus().toString())
.progressPercentage(progress.getProgressPercentage())
.filePath(progress.getFilePath())
.remainingChunks(progress.getTotalChunks() - progress.getUploadedChunks())
.build();
return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody));
} catch (Exception e) {
log.error("Error getting progress for upload: {}", uploadId, e);
UploadProgress responseBody = UploadProgress.builder()
.uploadId(uploadId)
.build();
return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to get upload progress: " + e.getMessage(), responseBody));
}
}
@PostMapping("/v{version}/files/{uploadId}/complete")
public ResponseEntity<BaseResponse> completeUpload(
@PathVariable("version") int version,
@PathVariable String uploadId) {
log.info("Completing upload session: {}, version: {}", uploadId, version);
try {
var progress = fileUploadService.getUploadProgress(uploadId);
if (progress.getStatus() == UploadStatus.COMPLETED) {
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
.uploadId(progress.getUploadId())
.status(progress.getStatus().toString())
.uploadedChunks(progress.getUploadedChunks())
.totalChunks(progress.getTotalChunks())
.message("Upload already completed")
.filePath(progress.getFilePath())
.build();
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(),
"Upload already completed", responseBody));
}
if (progress.getUploadedChunks() < progress.getTotalChunks()) {
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
.uploadId(progress.getUploadId())
.status(progress.getStatus().toString())
.uploadedChunks(progress.getUploadedChunks())
.totalChunks(progress.getTotalChunks())
.message("Not all chunks uploaded")
.build();
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.INCOMPLETE_UPLOAD.getCode(),
"Not all chunks uploaded", responseBody));
}
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
.uploadId(progress.getUploadId())
.status(progress.getStatus().toString())
.uploadedChunks(progress.getUploadedChunks())
.totalChunks(progress.getTotalChunks())
.message("File assembly in progress")
.build();
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(),
"File assembly in progress", responseBody));
} catch (Exception e) {
log.error("Error completing upload: {}", uploadId, e);
CompleteUploadResponse responseBody = CompleteUploadResponse.builder()
.uploadId(uploadId)
.build();
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Failed to complete upload: " + e.getMessage(), responseBody));
}
}
@GetMapping("/v{version}/files/public-download/{fileId}")
public ResponseEntity<?> downloadFile(
@PathVariable(required = false) String fileId, @PathVariable(required = false) Integer version) {
FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
FileStatus status = entityResponse.getStatus();
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
status.equals(FileStatus.REMOVED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_IS_BLOCKED.getCode(),
MessageCode.FILE_IS_BLOCKED.getDescription(),
errorData));
}
if (!entityResponse.getPermissions().get(PermissionType.DOWNLOAD)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getCode(),
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getDescription(),
errorData));
}
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(bytes);
}
@GetMapping("/v{version}/files/download/{fileId}")
public ResponseEntity<?> downloadFile(
@PathVariable(required = false) String fileId,
@PathVariable(required = false) Integer version,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.TOKEN_IS_NULL.getCode(),
MessageCode.TOKEN_IS_NULL.getDescription(),
errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
FileStatus status = entityResponse.getStatus();
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
status.equals(FileStatus.REMOVED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_IS_BLOCKED.getCode(),
MessageCode.FILE_IS_BLOCKED.getDescription(),
errorData));
}
if (!entityResponse.getPermissions().get(PermissionType.DOWNLOAD)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getCode(),
MessageCode.NOT_HAVE_DOWNLOAD_PERMISSION.getDescription(),
errorData));
}
User fileUser = userRepository.findById(entityResponse.getUserId()).orElseThrow();
User user = userRepository.findById(userId).orElseThrow();
Company companyUser = user.getCompany();
if (companyUser != null && !fileUser.getCompany().getId().equals(companyUser.getId())) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.USER_NOT_HAD_PERMISSION.getCode(),
MessageCode.USER_NOT_HAD_PERMISSION.getDescription(),
errorData));
}
if (companyUser == null && !entityResponse.getUserId().equals(userId)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.USER_NOT_HAD_PERMISSION.getCode(),
MessageCode.USER_NOT_HAD_PERMISSION.getDescription(),
errorData));
}
if (entityResponse.getStatus().equals(FileStatus.DELETED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("file_status", entityResponse.getStatus());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DELETE.getCode(),
MessageCode.FILE_DELETE.getDescription(),
errorData));
}
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + entityResponse.getFileName() + "\"")
.body(bytes);
} catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", fileId);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_NOT_FOUND.getCode(),
MessageCode.FILE_NOT_FOUND.getDescription(),
errorData));
} 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));
}
}
@PostMapping("/file/link")
public ResponseEntity<?> downloadFile(@RequestBody DocViewerRequest viewerRequest) {
try {
Long userId = httpServletRequest.getAttribute("userId") == null ? 0L:
(Long) httpServletRequest.getAttribute("userId");
if (viewerRequest.getFileId() == null) {
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.NOT_VALID_FIELD.getCode(),
MessageCode.NOT_VALID_FIELD.getDescription(),
"file_id is required"));
}
FileEntityResponse entityResponse = fileEntityService.getById(viewerRequest.getFileId(), 0);
FileStatus status = entityResponse.getStatus();
if (status.equals(FileStatus.BLOCKED) || status.equals(FileStatus.DELETED) ||
status.equals(FileStatus.REMOVED)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", entityResponse.getId());
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.FILE_IS_BLOCKED.getCode(),
MessageCode.FILE_IS_BLOCKED.getDescription(),
errorData));
}
byte[] bytes = cloudStorageService.readFileFromStorageBytes(entityResponse.getProtectedFilePath());
dockViewService.createDockView(viewerRequest, userId);
String filename = entityResponse.getFileName();
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8)
.replace("+", "%20");
String safeFilename = filename.replaceAll("[^\\x00-\\x7F]", "_");
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + safeFilename + "\"; filename*=UTF-8''" + encodedFilename)
.body(bytes);
} catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", viewerRequest.getFileId());
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.FILE_NOT_FOUND.getCode(),
MessageCode.FILE_NOT_FOUND.getDescription(),
errorData));
} catch (NotFoundAuthToken e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", viewerRequest.getFileId());
return ResponseEntity.ok().body(new BaseResponse(0,
MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
errorData));
}
}
@GetMapping("/check/file_stats")
public ResponseEntity<?> checkFileProtectStats() {
Long userId = (Long) httpServletRequest.getAttribute("userId");
Map<String, Object> fileTypeStats = protectionsLimitService.getFileTypeStats(userId);
return ResponseEntity.ok().body(fileTypeStats);
}
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
String fileId) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.chunkSize(chunk.getSize())
.fileId(fileId)
.message("Chunk uploaded successfully")
.build();
return ResponseEntity.ok().body(new BaseResponse(
20000,
MessageCode.SUCCESS.getCode(),
"Chunk uploaded successfully",
responseBody));
}
private ResponseEntity<BaseResponse> buildErrorResponse(String uploadId, Integer chunkNumber, String errorMessage) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build();
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.FILE_UPLOAD_ERROR.getCode(),
errorMessage,
responseBody));
}
private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) {
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
.stream()
@@ -105,4 +835,23 @@ public class ApiController {
return errorDetail;
}
private MediaType getMediaType(FileEntity fileEntity) {
try {
return MediaType.parseMediaType(fileEntity.getMimeType());
} catch (InvalidMediaTypeException e) {
String extension = fileEntity.getFileExtension().toLowerCase();
switch (extension) {
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
case "png":
return MediaType.IMAGE_PNG;
case "pdf":
return MediaType.APPLICATION_PDF;
default:
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}
}
@@ -0,0 +1,70 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.entity.api.ApiKey;
import ru.soune.nocopy.service.api.ApiKeyService;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class ApiKeyController {
private final ApiKeyService apiKeyService;
@PostMapping("/api-keys")
public ResponseEntity<BaseResponse> createKey(@RequestAttribute("userId") Long userId,
@RequestBody Map<String, String> body) {
String name = body.getOrDefault("name", "Unnamed key");
String apiKey = apiKeyService.createApiKey(userId, name);
return ResponseEntity.ok(
BaseResponse.builder()
.messageCode(MessageCode.API_KEY_CREATED.getCode())
.messageDesc(MessageCode.API_KEY_CREATED.getDescription())
.messageBody(Map.of("apiKey", apiKey))
.build()
);
}
@GetMapping("/api-keys")
public ResponseEntity<BaseResponse> listKeys(@RequestAttribute("userId") Long userId) {
List<ApiKey> keys = apiKeyService.getUserKeys(userId);
List<Map<String, Object>> result = keys.stream()
.map(key -> Map.<String, Object>of(
"id", key.getId(),
"name", key.getName(),
"prefix", key.getPrefix(),
"isActive", key.isActive(),
"createdAt", key.getCreatedAt(),
"lastUsedAt", key.getLastUsedAt()
))
.toList();
return ResponseEntity.ok(
BaseResponse.builder()
.messageCode(MessageCode.SUCCESS.getCode())
.messageBody(result)
.build()
);
}
@DeleteMapping("/api-keys/{keyId}")
public ResponseEntity<BaseResponse> revokeKey(@RequestAttribute("userId") Long userId,
@PathVariable Long keyId) {
apiKeyService.revoke(keyId, userId);
return ResponseEntity.ok(
BaseResponse.builder()
.messageCode(MessageCode.SUCCESS.getCode())
.build()
);
}
}
@@ -0,0 +1,233 @@
package ru.soune.nocopy.controller;
import com.google.common.io.ByteStreams;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.dto.file.FileEntityResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.ZipService;
import ru.soune.nocopy.service.file.*;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.util.FileUtil;
import software.amazon.awssdk.services.mq.model.NotFoundException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@RestController
@RequestMapping("/api/files")
@Slf4j
@AllArgsConstructor
public class FileController {
private FileEntityRepository fileRepository;
private CheckCounterService checkCounterService;
private UserRepository userRepository;
private final FileEntityService fileEntityService;
private NoCopyFileService noCopyFileService;
private FileUtil fileUtil;
private ZipService zipService;
private CloudStorageService cloudStorageService;
@GetMapping("/public/{fileId}")
public ResponseEntity<StreamingResponseBody> getPublicFile(@PathVariable String fileId) {
try {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException("Not found in DB: " + fileId));
MediaType mediaType = getMediaType(fileEntity);
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = cloudStorageService.readFileFromStorage(fileEntity.getFilePath())) {
ByteStreams.copy(inputStream, outputStream);
} catch (NoSuchKeyException e) {
log.error("File disappeared from S3 during streaming: {}", fileId, e);
throw new IOException("File not found in storage", e);
}
};
return ResponseEntity.ok()
.contentType(mediaType)
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
.body(responseBody);
} catch (FileNotFoundException e) {
log.warn("File not found in DB: {}", fileId);
return ResponseEntity.notFound().build();
} catch (NoSuchKeyException e) {
log.warn("File not found in S3: {}", fileId);
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (Exception e) {
log.error("Error reading file: {}", fileId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/public/file-info/{fileId}")
public ResponseEntity<FileEntityResponse> getPublicFileInfo(@PathVariable String fileId) {
return handleGetFileInfo(fileId);
}
private ResponseEntity<FileEntityResponse> handleGetFileInfo(String fileId) {
try {
FileEntityResponse fileInfo = fileEntityService.getById(fileId, 1);
return ResponseEntity
.ok()
.body(fileInfo);
} catch (NotFoundException e) {
return ResponseEntity
.notFound().build();
}
}
private MediaType getMediaType(FileEntity fileEntity) {
try {
return MediaType.parseMediaType(fileEntity.getMimeType());
} catch (InvalidMediaTypeException e) {
String extension = fileEntity.getFileExtension().toLowerCase();
switch (extension) {
case "jpg":
case "jpeg":
return MediaType.IMAGE_JPEG;
case "png":
return MediaType.IMAGE_PNG;
case "pdf":
return MediaType.APPLICATION_PDF;
default:
return MediaType.APPLICATION_OCTET_STREAM;
}
}
}
@GetMapping("/protected/{fileId}/{type}")
public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId, @PathVariable String type) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath();
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected");
}
String fileName = fileEntity.getOriginalFileName().replace("." +
fileEntity.getFileExtension(), "") + "_nocopy_protected" +
"." + fileEntity.getFileExtension();
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20");
InputStreamResource resource = new InputStreamResource(cloudStorageService.readFileFromStorage(filePath));
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename*=UTF-8''" + encodedFileName)
.body(resource);
}
@GetMapping("/public/{fileId}/{type}")
public ResponseEntity<Resource> getPublicFileWithTpe(
@PathVariable String fileId, @PathVariable String type) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
String filePath = type.equals("thumbnail") ? fileEntity.getThumbnailPath(): fileEntity.getFilePath();
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected");
}
String fileName = fileEntity.getOriginalFileName().replace("." +
fileEntity.getFileExtension(), "") + "_nocopy_protected" +
"." + fileEntity.getFileExtension();
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
.replace("+", "%20");
InputStreamResource resource = new InputStreamResource(cloudStorageService.readFileFromStorage(filePath));
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename*=UTF-8''" + encodedFileName)
.body(resource);
}
@GetMapping("check-file/status/{userId}")
public ResponseEntity<CheckStatus> getStatus(@PathVariable Long userId) {
CheckStatus status = checkCounterService.getCurrentStatus(userId);
return ResponseEntity.ok(status);
}
@PostMapping("check-file/update-limit/{userId}/{limit}")
public ResponseEntity<ProtectedFileCheck> getStatus(@PathVariable Long userId, @PathVariable Integer limit) {
User user = userRepository.findById(userId).orElseThrow();
ProtectedFileCheck protectedFileCheck = checkCounterService.updateLimit(user, limit);
return ResponseEntity
.ok()
.body(protectedFileCheck);
}
@PostMapping("protect/{fileId}/{convertTo}")
public ResponseEntity<FileProtector.FileInfo> convert(@PathVariable String fileId, @PathVariable String convertTo) {
FileEntity file = fileRepository.findByFileId(fileId);
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(file, convertTo);
noCopyFileService.addFile(fileInfo);
return ResponseEntity
.ok()
.body(fileInfo);
}
@GetMapping("/download/user-passport/archive/{userId}")
public ResponseEntity<byte[]> downloadPassportZip(@PathVariable Long userId) {
try {
byte[] zipBytes = zipService.assembleZipFromSplitFiles(userId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=passport_" + userId + ".zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(zipBytes.length)
.body(zipBytes);
} catch (IOException e) {
log.error("Failed to assemble passport for user {}", userId, e);
return ResponseEntity.internalServerError().build();
}
}
}
@@ -0,0 +1,116 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.search.GlobalSearchStartRequest;
import ru.soune.nocopy.dto.search.GlobalSearchStartResponse;
import ru.soune.nocopy.dto.search.GlobalSearchStatusResponse;
import ru.soune.nocopy.dto.violation.FileViolationSummaryDTO;
import ru.soune.nocopy.dto.violation.ViolationRequest;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.search.GlobalSearchTask;
import ru.soune.nocopy.entity.search.SearchStatus;
import ru.soune.nocopy.repository.GlobalSearchTaskRepository;
import ru.soune.nocopy.service.search.GlobalSearchService;
import ru.soune.nocopy.service.violation.ViolationService;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/api/v1/global-search")
@RequiredArgsConstructor
public class GlobalSearchController {
private final GlobalSearchService globalSearchService;
private final GlobalSearchTaskRepository searchTaskRepository;
private final ViolationService violationService;
private final HttpServletRequest httpServletRequest;
@PostMapping("/start")
public ResponseEntity<?> startSearch(
@RequestBody GlobalSearchStartRequest request) {
GlobalSearchStartResponse response = new GlobalSearchStartResponse();
Long userId = (Long) httpServletRequest.getAttribute("userId");
List<FileEntity> filesToProcess = globalSearchService.getFilesToProcess(request, userId);
if (filesToProcess.isEmpty()) return ResponseEntity.ok().body("Files for search not found");
String taskId = globalSearchService.startSearch(request, userId, filesToProcess);
response.setTaskId(taskId);
response.setStatus(SearchStatus.ACCEPTED.name());
response.setTotalFiles(filesToProcess.size());
return ResponseEntity.ok(response);
}
@GetMapping("/actual-task")
public ResponseEntity<?> getStatus() {
Optional<GlobalSearchTask> taskOptional;
Long userId = (Long) httpServletRequest.getAttribute("userId");
taskOptional = searchTaskRepository.findByUserIdAndStatus(userId,
"PROCESSING");
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
return ResponseEntity.ok(taskOptional.get());
}
@GetMapping("/violation-summary")
public ResponseEntity<?> getSummary() {
List<FileViolationSummaryDTO> fileViolationsSummary;
Long userId = (Long) httpServletRequest.getAttribute("userId");
fileViolationsSummary = violationService.getFileViolationsSummary(userId);
if (fileViolationsSummary.isEmpty()) return ResponseEntity.ok().body("Violations summary not found");
return ResponseEntity.ok(fileViolationsSummary);
}
@PostMapping("/violation-summary-with-files")
public ResponseEntity<?> getViolations(@RequestBody ViolationRequest request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
Page<FileViolationSummaryDTO> result = violationService.getFileViolationsSummary(
userId, request.getFileName(), request.getSortDirection(),
request.getPage(), request.getSize(), request.getStartDate(), request.getEndDate());
return ResponseEntity.ok(result);
}
@GetMapping("/status/{taskId}")
public ResponseEntity<?> getStatus(@PathVariable String taskId) {
GlobalSearchStatusResponse response;
Long userId = (Long) httpServletRequest.getAttribute("userId");
Optional<GlobalSearchTask> taskOptional = searchTaskRepository.findByTaskIdAndUserId(taskId, userId);
if (taskOptional.isEmpty()) return ResponseEntity.ok().body("Task not found");
GlobalSearchTask task = taskOptional.orElseThrow();
int progress = task.getTotalFiles() > 0 ? task.getProcessedFiles() * 100 / task.getTotalFiles() : 0;
response = new GlobalSearchStatusResponse();
response.setTaskId(taskId);
response.setStatus(task.getStatus());
response.setProgress(progress);
return ResponseEntity.ok(response);
}
}
@@ -1,14 +1,93 @@
package ru.soune.nocopy.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.function.EntityResponse;
import ru.soune.nocopy.dto.monitoring.MonitoringDTO;
import ru.soune.nocopy.entity.monitoring.FileMonitoringEntity;
import ru.soune.nocopy.repository.FileMonitoringRepository;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("check/api")
@Slf4j
public class HealtCheckController {
private final KafkaTemplate<String, Object> kafkaTemplate;
private final FileMonitoringRepository monitoringRepository;
private final ObjectMapper objectMapper;
@Value("${server.baseurl}")
private String baseUrl;
public HealtCheckController(KafkaTemplate<String, Object> kafkaTemplate, FileMonitoringRepository monitoringRepository, ObjectMapper objectMapper) {
this.kafkaTemplate = kafkaTemplate;
this.monitoringRepository = monitoringRepository;
this.objectMapper = objectMapper;
}
@GetMapping("/healt")
public HttpStatus healtCheck() {
return HttpStatus.OK;
}
@Value("${BUILD_TIME_BACK:unknown}")
private String buildTimeBack;
@GetMapping("/build")
public BuildInfo getBuildInfo() {
return new BuildInfo(buildTimeBack);
}
@GetMapping("/api/debug/memory")
public Map<String, String> getMemoryInfo() {
Runtime runtime = Runtime.getRuntime();
long mb = 1024 * 1024;
Map<String, String> info = new HashMap<>();
info.put("maxMemory (MB)", String.valueOf(runtime.maxMemory() / mb));
info.put("totalMemory (MB)", String.valueOf(runtime.totalMemory() / mb));
info.put("freeMemory (MB)", String.valueOf(runtime.freeMemory() / mb));
info.put("usedMemory (MB)", String.valueOf((runtime.totalMemory() - runtime.freeMemory()) / mb));
info.put("availableProcessors", String.valueOf(runtime.availableProcessors()));
info.put("JAVA_TOOL_OPTIONS", System.getenv("JAVA_TOOL_OPTIONS"));
info.put("JAVA_OPTS", System.getenv("JAVA_OPTS"));
return info;
}
static class BuildInfo {
public String buildTimeBack;
public BuildInfo(String buildTimeBack) {
this.buildTimeBack = buildTimeBack;
}
}
@PostMapping("/test/monitoring/{fileId}")
public ResponseEntity<?> startMonitoringKafka(@PathVariable String fileId) throws JsonProcessingException {
MonitoringDTO monitoringYandexDTO = MonitoringDTO.builder().baseUrl(baseUrl).
fileId(fileId)
.engine("yandex_reverse_image")
.searchType("visual_matches")
.build();
kafkaTemplate.send("monitoring-commands", objectMapper.writeValueAsString(monitoringYandexDTO));
return ResponseEntity
.ok()
.body("OK");
}
}
@@ -0,0 +1,83 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.soune.nocopy.dto.docviewer.DockViewResponse;
import ru.soune.nocopy.entity.docviewer.DockView;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.migration.S3MigrationTool;
import ru.soune.nocopy.repository.DockViewRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.geo.GeoCountryService;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/admin/migration")
@RequiredArgsConstructor
@Slf4j
public class MigrationController {
private final S3MigrationTool migrationTool;
private final DockViewRepository dockViewRepository;
private final GeoCountryService geoCountryService;
private final UserRepository userRepository;
private final FileEntityRepository fileEntityRepository;
@PostMapping("/start")
public ResponseEntity<String> startMigration() {
log.info("Migration triggered via API");
CompletableFuture.runAsync(() -> {
try {
migrationTool.migrationFilesToS3();
} catch (Exception e) {
log.error("Migration failed", e);
}
});
return ResponseEntity.ok("Migration started. Check logs for progress.");
}
@PostMapping("/status")
public ResponseEntity<String> getStatus() {
return ResponseEntity.ok("Check application logs and migration_failed.txt for details");
}
@PostMapping("/fill-dock-view")
public ResponseEntity<String> fillDockView() {
List<DockView> dockViews = dockViewRepository.findAll();
for (DockView dockView: dockViews) {
String viewerIp = dockView.getViewerIp();
Long viewerId = dockView.getViewerId();
String geoEnName = geoCountryService.getCityByIp(viewerIp, "en") + "/" +
geoCountryService.getCountryByIp(viewerIp, "en");
String geoRuName = geoCountryService.getCityByIp(viewerIp, "ru") + "/" +
geoCountryService.getCountryByIp(viewerIp, "ru");
User user = userRepository.findById(viewerId).orElse(null);
FileEntity file = fileEntityRepository.findByFileId(dockView.getFileId());
dockView.setGeoNameRu(geoRuName);
dockView.setGeoNameEn(geoEnName);
dockView.setViewerName(user == null ? "guest" : user.getFullName());
dockView.setDocName(file.getOriginalFileName());
dockViewRepository.save(dockView);
}
return ResponseEntity.ok("fill old dock view");
}
}
@@ -0,0 +1,123 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.entity.payment.Payment;
import ru.soune.nocopy.exception.PaymentNotFoundException;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.service.payment.PaymentService;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/payments")
@RequiredArgsConstructor
@Slf4j
public class PaymentController {
private final PaymentService paymentService;
private final HttpServletRequest httpServletRequest;
@PostMapping("/create")
public ResponseEntity<?> createPayment(@RequestParam String email, @RequestParam Long tariffId,
@RequestParam String operationType, @RequestParam String operationUuid) {
try {
Payment payment = paymentService.createPayment(email, tariffId, operationType, operationUuid);
return ResponseEntity.ok(payment);
} catch (TariffNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "Tariff not found", "message", e.getMessage()));
} catch (UserNotFoundException e) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "User not found", "message", e.getMessage()));
} catch (Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Internal server error", "message", e.getMessage()));
}
}
@PostMapping("webhook/yookassa")
public void handleYooKassaNotification(@RequestBody Map<String, Object> notification) {
try {
paymentService.handlePaymentNotification(notification);
log.info("Notification from yookassa: " + notification);
} catch (PaymentNotFoundException e) {
log.error("Error processing payment notification", e);
}
}
@PostMapping("/auto-renewal")
public ResponseEntity<?> disableAutoRenewal(@RequestParam(value = "renewal") Boolean renewal,
@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
paymentService.changeAutoRenewal(userId, renewal);
return ResponseEntity.ok(Map.of("message", "Auto-renewal changed:" + renewal));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
}
}
@DeleteMapping("/methods/{paymentMethodId}")
public ResponseEntity<?> deletePaymentMethod(@RequestHeader(value = "Authorization", required = false) String tokenHeader,
@PathVariable String paymentMethodId) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
paymentService.deletePaymentMethod(userId, paymentMethodId);
return ResponseEntity.ok(Map.of("message", "Payment method deleted"));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
} catch (RuntimeException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/methods")
public ResponseEntity<?> getPaymentMethods(@RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try {
if (tokenHeader == null || tokenHeader.isBlank()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(Map.of("error", errorData));
}
Long userId = (Long) httpServletRequest.getAttribute("userId");
return ResponseEntity.ok(paymentService.getUserPaymentMethods(userId));
} catch (UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", e.getMessage()));
}
}
}
@@ -0,0 +1,201 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.payout.*;
import ru.soune.nocopy.entity.payout.BankTransferPayoutMethod;
import ru.soune.nocopy.entity.payout.CardPayoutMethod;
import ru.soune.nocopy.entity.payout.PayoutRequest;
import ru.soune.nocopy.entity.payout.PayoutType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.InsufficientFundsException;
import ru.soune.nocopy.exception.InvalidPayoutMethodException;
import ru.soune.nocopy.exception.PendingPayoutExistsException;
import ru.soune.nocopy.service.payout.PayoutMethodService;
import ru.soune.nocopy.service.payout.PayoutRequestService;
import ru.soune.nocopy.service.user.UserService;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/payouts")
@RequiredArgsConstructor
@Slf4j
public class PayoutController {
private final PayoutRequestService payoutRequestService;
private final PayoutMethodService payoutMethodService;
private final HttpServletRequest httpServletRequest;
private final UserService userService;
/*
Create payout
*/
@PostMapping("/create-request")
public ResponseEntity<?> createPayoutRequest(@Valid @RequestBody PayoutRequestDTO request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found with id: " + userId);
}
try {
PayoutRequest payoutRequest = payoutRequestService.createPayoutRequest(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED).body(payoutRequest);
} catch (InsufficientFundsException | PendingPayoutExistsException | InvalidPayoutMethodException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage());
}
}
/*
Completed payout
*/
@PostMapping("/requests/{requestId}/process")
public ResponseEntity<PayoutRequest> processPayoutRequest(@PathVariable Long requestId) {
PayoutRequest processed = payoutRequestService.processRequest(requestId);
return ResponseEntity.ok(processed);
}
/*
Cancel payout
*/
@PostMapping("/requests/{requestId}/cancel")
public ResponseEntity<PayoutRequest> cancelPayoutRequest(@PathVariable Long requestId) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
PayoutRequest cancelled = payoutRequestService.cancelPayoutRequest(user.getId(), requestId);
return ResponseEntity.ok(cancelled);
}
/*
Get user payouts
*/
@GetMapping("/requests")
public ResponseEntity<List<PayoutRequest>> getUserRequests() {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(payoutRequestService.getUserPayoutRequests(user.getId()));
}
/*
Get payout by id
*/
@GetMapping("/requests/{requestId}")
public ResponseEntity<PayoutRequest> getPayoutRequest(@PathVariable Long requestId) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(payoutRequestService.getPayoutRequest(user.getId(), requestId));
}
/*
Get payout by id
*/
@GetMapping("user/payout-methods")
public ResponseEntity<List<PayoutMethodDTO>> getUserMethods() {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(payoutMethodService.getUserMethods(user.getId()));
}
/*
Get payout-methods
*/
@GetMapping("/payout-methods")
public ResponseEntity<List<PayoutMethodTypeDTO>> getAvailableMethodTypes() {
List<PayoutMethodTypeDTO> types = Arrays.stream(PayoutType.values())
.map(type -> PayoutMethodTypeDTO.builder()
.code(type.name())
.name(type.getDisplayName())
.build())
.collect(Collectors.toList());
return ResponseEntity.ok(types);
}
/*
add card method by userId
*/
@PostMapping("/payout-method/card")
public ResponseEntity<PayoutMethodDTO> addCardMethod(@Valid @RequestBody AddCardRequest request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
CardPayoutMethod method = payoutMethodService.addCardMethod(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(payoutMethodService.convertToDto(method));
}
/*
add bank payout-method
*/
@PostMapping("/payout-method/bank-transfer")
public ResponseEntity<PayoutMethodDTO> addBankTransferMethod(@Valid @RequestBody AddBankTransferRequest request) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
BankTransferPayoutMethod method = payoutMethodService.addBankTransferMethod(user.getId(), request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(payoutMethodService.convertToDto(method));
}
/*
set default bank payout-method
*/
@PutMapping("/payout-method/{methodId}/default")
public ResponseEntity<Void> setDefaultMethod(@PathVariable Long methodId) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
if (user == null) {
return ResponseEntity.notFound().build();
}
payoutMethodService.setDefaultMethod(user.getId(), methodId);
return ResponseEntity.ok().build();
}
}
@@ -0,0 +1,58 @@
package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.configuration.search.SearchProperties;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.search.config.SearchSettingsDto;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/search/settings")
@RequiredArgsConstructor
public class SearchSettingsController {
private final SearchProperties searchProperties;
@GetMapping
public ResponseEntity<?> getSettings() {
SearchSettingsDto dto = new SearchSettingsDto();
dto.setEngines(Map.of(
"yandex", searchProperties.getEngines().get("yandex").isEnabled(),
"google", searchProperties.getEngines().get("google").isEnabled()));
dto.setProxyEnabled(searchProperties.getProxy().isEnabled());
return ResponseEntity.ok().body(BaseResponse.builder()
.messageDesc("Success")
.messageBody(dto)
.build());
}
@PutMapping
public ResponseEntity<?> updateSettings(@RequestBody SearchSettingsDto settings) {
if (settings.getEngines() != null) {
settings.getEngines().forEach((key, value) -> {
if (searchProperties.getEngines().containsKey(key)) {
searchProperties.getEngines().get(key).setEnabled(value);
}
});
}
if (settings.getProxyEnabled() != null) {
searchProperties.getProxy().setEnabled(settings.getProxyEnabled());
}
log.info("Settings updated: {}", settings);
return ResponseEntity.ok().body(BaseResponse.builder()
.messageDesc("Settings updated")
.build());
}
}
@@ -1,11 +0,0 @@
package ru.soune.nocopy.controller;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("v1/api/content")
@AllArgsConstructor
public class UserContentController {
}
@@ -1,22 +1,33 @@
package ru.soune.nocopy.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.ChangePasswordRequest;
import ru.soune.nocopy.dto.UserDTO;
import ru.soune.nocopy.dto.UserRequest;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.entity.User;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.register.ChangePasswordRequest;
import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.dto.register.RegRequest;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.dto.user.UserDTO;
import ru.soune.nocopy.dto.user.UserRequest;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.InvalidUserEmail;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.mapper.UserMapper;
import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.UserService;
import ru.soune.nocopy.service.file.FileStatsService;
import ru.soune.nocopy.service.tariff.TariffService;
import ru.soune.nocopy.service.user.UserService;
import ru.soune.nocopy.util.JwtUtil;
import java.util.List;
import java.util.Optional;
import java.util.Arrays;
import java.util.Map;
@RestController
@RequestMapping("v1/api/user")
@@ -27,48 +38,79 @@ public class UserController {
private final UserService userService;
private final AuthTokenRepository authTokenRepository;
private final UserMapper userMapper;
@GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.getIsActive(),
u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType()))
.toList();
private final PasswordEncoder passwordEncoder;
private final TariffService tariffService;
private final FileStatsService fileStatsService;
private final JwtUtil jwtUtil;
private final HttpServletRequest httpServletRequest;
return ResponseEntity.ok(allUsers);
}
@GetMapping
public ResponseEntity<UserDTO> getUser(@RequestParam("email") String email,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
public ResponseEntity<UserDTO> getUser(@RequestParam("email") String email) {
User user = userRepository.findByEmail(email);
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
if (user != null) {
UserDTO userDTO = userMapper.toDTO(user);
userDTO.setEmail(email);
userDTO.setFullName(user.getFullName());
userDTO.setVerifiedStatus(user.getVerificationStatus());
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
if (authToken != null) {
Optional<User> user = userRepository.findByEmail(email);
//TODO add mapper
if (user.isPresent()) {
UserDTO userDTO = userMapper.toDTO(user.get());
userDTO.setEmail(email);
userDTO.setFullName(user.get().getFullName());
userDTO.setCompany(user.get().getCompany());
userDTO.setPhone(user.get().getPhone());
userDTO.setGenderType(user.get().getGenderType());
userDTO.setBirthday(user.get().getBirthday());
userDTO.setCreatedAt(user.get().getCreatedAt());
userDTO.setSubscriptionType(user.get().getSubscriptionType());
userDTO.setActive(user.get().getIsActive());
return ResponseEntity.ok(userDTO);
if (user.getCompany() != null) {
userDTO.setCompany(user.getCompany().getCompanyName());
}
if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
userDTO.setTariffs(tariffService.getTariffByAccountType(user));
Long fileOnDisk = user.canManageCompanySettings() ?
fileStatsService.calculateCompanyFileSize(user.getId()):
fileStatsService.calculateUserFileSize(user.getId());
Integer filesCount = user.canManageCompanySettings() ?
fileStatsService.filesCompanyCount(user.getId()):
fileStatsService.filesUserCount(user.getId());
TariffInfo personalTariffInfo;
if (user.getCompany() == null) {
personalTariffInfo = user.getPersonalTariffInfo();
} else {
personalTariffInfo = user.getCompany().getTariffInfo();
}
Tariff tariff = personalTariffInfo.getTariff();
TariffInfoDTO infoDTO = TariffInfoDTO.builder().tokens(personalTariffInfo.getTokens())
.id(personalTariffInfo.getId())
.tariffName(tariff.getName())
.status(personalTariffInfo.getStatus().name())
.tariffId(tariff.getId())
.currentFileCounts(filesCount)
.maxFileCounts(tariff.getMaxFilesCount())
.currentFileOnDisk(fileOnDisk)
.maxFileOnDisk(tariff.getDiskSize())
.startTariff(personalTariffInfo.getStartTariff())
.endTariff(personalTariffInfo.getEndTariff())
.tokens(personalTariffInfo.getTokens() + personalTariffInfo.getBoughtTokens())
.build();
userDTO.setTariffInfo(infoDTO);
}
userDTO.setPhone(user.getPhone());
userDTO.setGenderType(user.getGenderType());
userDTO.setBirthday(user.getBirthday());
userDTO.setCreatedAt(user.getCreatedAt());
userDTO.setActive(user.isActive());
userDTO.setPermission(user.getUserPermissions());
return ResponseEntity.ok(userDTO);
}
return ResponseEntity.notFound().build();
@@ -76,15 +118,13 @@ public class UserController {
//TODO fix mapper,doesnot exist all fields
@PostMapping("/change-password")
public ResponseEntity<UserDTO> updateUser(@RequestBody ChangePasswordRequest changePasswordRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
public ResponseEntity<UserDTO> updateUser(@RequestBody ChangePasswordRequest changePasswordRequest) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
if (user == null) {
throw new UserNotFoundException("User not found by id: " + userId);
}
if (!changePasswordRequest.getEmail().equals(user.getEmail())) {
throw new InvalidUserEmail("Email is not valid: " + changePasswordRequest.getEmail() + "not found");
@@ -96,16 +136,56 @@ public class UserController {
}
@PatchMapping("/user-update")
public ResponseEntity<UserDTO> updateUser(@RequestBody UserRequest userRequest,
@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
public ResponseEntity<UserDTO> updateUser(@RequestBody UserRequest userRequest) {
Long userId = (Long) httpServletRequest.getAttribute("userId");
User user = userService.getUserById(userId).orElse(null);
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
User user = authToken.getUser();
if (user == null) {
throw new UserNotFoundException("User not found by id: " + userId);
}
return ResponseEntity.ok(userService.updateUser(userRequest, user));
}
@PostMapping("/create-user")
public ResponseEntity<?> addUser(@RequestBody RegRequest registerRequest) {
if (userRepository.existsByEmail(registerRequest.getEmail()) ||
userRepository.existsByPhone(registerRequest.getPhone())) {
RegAnswer regAnswer = new RegAnswer();
regAnswer.setFieldErrors(Arrays.asList(Map.of("email", registerRequest.getEmail())));
regAnswer.setFieldErrors(Arrays.asList(Map.of("phone", registerRequest.getPhone())));
throw new NotValidFieldException("User already exists with email:" + registerRequest.getEmail() + " or phone: " +
registerRequest.getPhone(), new BaseResponse(2,
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getCode(),
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
}
User user = new User();
user.setFullName(registerRequest.getFullName());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setActive(true);
user.setEmailVerified(true);
if (registerRequest.getPhone() != null) {
user.setPhone(registerRequest.getPhone());
}
User savedUser = userRepository.save(user);
String authToken = jwtUtil.generateToken(savedUser.getId(), savedUser.getEmail());
return ResponseEntity.ok().body(Map.of(
"user", savedUser,
"token", authToken));
}
@DeleteMapping("/delete-user/{userId}")
public ResponseEntity<?> addUser(@PathVariable Long userId) {
userRepository.deleteById(userId);
return ResponseEntity.ok().body(Map.of("userId", userId, "deleted", "true"));
}
}
@@ -0,0 +1,187 @@
package ru.soune.nocopy.controller.demo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.demo.*;
import ru.soune.nocopy.service.demo.DemoSessionService;
@Slf4j
@RestController
@RequestMapping("/api/demo")
@RequiredArgsConstructor
public class DemoController {
private final DemoSessionService demoSessionService;
private final HttpServletRequest httpServletRequest;
/**
* X-Forwarded-For/X-Real-IP — клиентские заголовки, их можно подделать в запросе.
* Доверять им можно, только если приложение стоит за доверенным reverse-proxy/LB,
* который сам их проставляет и не пропускает клиентские значения напрямую.
*/
@Value("${demo.trust-forwarded-header:false}")
private boolean trustForwardedHeader;
/**
* Загрузка файла для демонстрации
* Требуется файл в формате multipart/form-data
*
* @param file файл для загрузки
* @return DemoUploadResponse с информацией о сессии
*/
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) {
try {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body(new DemoUploadResponse(
null, null, null, 0L,
"FAILED", "File is empty", 0L, 0
));
}
String clientIp = getClientIp();
log.info("Demo upload request from IP: {} for file: {}", clientIp, file.getOriginalFilename());
DemoUploadResponse response = demoSessionService.uploadFile(file, clientIp);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
log.warn("Demo upload validation failed: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new DemoUploadResponse(
null, null, null, 0L,
"FAILED", e.getMessage(), 0L, 0
));
} catch (Exception e) {
log.error("Error during demo upload", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new DemoUploadResponse(
null, null, null, 0L,
"FAILED", "Upload failed: " + e.getMessage(), 0L, 0
));
}
}
/**
* Получение информации о загруженном файле в демо-сессии
*
* @param sessionId ID демо-сессии
* @return DemoSessionInfo с деталями файла
*/
@GetMapping("/file/{sessionId}")
public ResponseEntity<?> getFileInfo(@PathVariable String sessionId) {
try {
String clientIp = getClientIp();
log.info("Getting file info for session: {} from IP: {}", sessionId, clientIp);
DemoSessionInfo info = demoSessionService.getSessionInfo(sessionId, clientIp);
return ResponseEntity.ok(info);
} catch (RuntimeException e) {
log.warn("Error getting session info: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new DemoSessionInfo(
null, null, null, 0L,
"ERROR", null, null, 0, null
));
}
}
/**
* Запуск поиска похожих файлов для демо
* Асинхронная операция - результаты могут загружаться в реальном времени
*
* @param sessionId ID демо-сессии
* @return DemoSearchResponse с результатами поиска
*/
@PostMapping("/search/{sessionId}")
public ResponseEntity<?> startSearch(@PathVariable String sessionId) {
try {
String clientIp = getClientIp();
log.info("Starting demo search for session: {} from IP: {}", sessionId, clientIp);
DemoSearchResponse response = demoSessionService.startSearch(sessionId, clientIp);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
log.warn("Error starting demo search: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new DemoSearchResponse(
sessionId, "FAILED", 0, 0.0,
null, null, 0L, null, e.getMessage()
));
}
}
/**
* Очистка демо-сессии и удаление файлов
* Должна быть вызвана пользователем или автоматически при закрытии демо
*
* @param sessionId ID демо-сессии
* @return успешное подтверждение удаления
*/
@DeleteMapping("/cleanup/{sessionId}")
public ResponseEntity<?> cleanup(@PathVariable String sessionId) {
try {
String clientIp = getClientIp();
log.info("Demo cleanup request for session: {} from IP: {}", sessionId, clientIp);
demoSessionService.deleteSession(sessionId, clientIp);
return ResponseEntity.ok(new DemoUploadResponse(
sessionId, null, null, 0L,
"CLEANED", "Session cleaned successfully", 0L, 100
));
} catch (RuntimeException e) {
log.warn("Error cleaning up session: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new DemoUploadResponse(
sessionId, null, null, 0L,
"ERROR", e.getMessage(), 0L, 0
));
}
}
/**
* Health check эндпоинт для демо API
* Проверяет доступность и готовность к приёму файлов
*/
@GetMapping("/health")
public ResponseEntity<?> health() {
return ResponseEntity.ok(new DemoUploadResponse(
null, null, null, 0L,
"HEALTHY", "Demo service is ready", 0L, 100
));
}
/**
* Извлечение IP адреса клиента из запроса
* Учитывает различные прокси и заголовки
*/
private String getClientIp() {
if (trustForwardedHeader) {
String xForwardedFor = httpServletRequest.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = httpServletRequest.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
}
return httpServletRequest.getRemoteAddr();
}
}
@@ -1,9 +1,11 @@
package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseRequest {
@JsonProperty("version")
Integer version;
@@ -13,4 +15,7 @@ public class BaseRequest {
@JsonProperty("message_body")
Object messageBody;
@JsonProperty(value = "admin_id", required = false)
Long adminId;
}
@@ -1,11 +1,15 @@
package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
@Builder
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseResponse {
@JsonProperty("msg_id")
private Integer msgId;
@@ -2,16 +2,82 @@ package ru.soune.nocopy.dto;
public enum MessageCode {
SUCCESS(0, "Operation successful"),
SUCCESS_MODERATION(0, "File moderated successfully"),
REG_EMAIL_EXISTS(1, "Email already registered"),
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
REFERRAL_LINK_IS_NOT_EXIST(1, "Refferal link is not exist"),
FILE_IS_BLOCKED(1, "File is blocked,deleted or removed"),
INVALID_FIELD(2, "Invalid field"),
MAIL_VERIFIED_NULL(2, "Mail verified null"),
API_KEY_CREATED(2, "Save this key. It will not be shown again"),
INVALID_TOKEN(2, "Token not found or time expired"),
TOKEN_IS_ALIVE(2, "Token is alive"),
INVALID_ACTION(2, "Invalid action"),
FILE_UPLOAD_ERROR(2, "File upload error"),
DUPLICATE_FILE_UPLOAD(2, "Duplicate file upload"),
FILE_DOWNLOAD_ERROR(2, "File download error"),
FILE_NOT_EXIST(2, "File not exist on disk"),
FILE_DELETE(2, "File was deleted"),
USER_NOT_HAD_PERMISSION(2, "User not have permission for file"),
NOT_OWNER_FILE_STATUS_VIOLATION(2, "Violation in not owner file status"),
USER_NOT_VERIFIED(2, "User not verified"),
LAW_CASE_NOT_FOUND(4, "Law case not found"),
PERMISSION_NOT_FOUND(2, "Permission not found"),
INVALID_CREDENTIALS(2, "Invalid credentials"),
USER_NOT_FOUND(2, "User not found"),
LAW_CASE_ALREADY_EXIST(2, "Law case already exists"),
COMPLAINT_ALREADY_EXIST(2, "Complaint already exists"),
COMPLAINT_NOT_FOUND(4, "Complaint not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
TOKEN_IS_NULL(2, "Token field is null"),
IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
INCOMPLETE_UPLOAD(2, "Not load all chunks"),
USER_NOT_HAVE_TOKEN(2, "Not have tokens"),
MSG_ID_NOT_FOUND(4, "Message id not found"),
AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
FILE_ENTITY_ERROR(2, "File entity error"),
NOT_VALID_FIELD(2, "Not valid field"),
ACCESS_DENIED(2, "Access denied"),
AUTH_EMAIL_NOT_FOUND(4, "Email not COMPLAINT_OR_LAW_CASE_IN_WORK"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
AUTH_TOKEN_MISMATCH(4, "Token mismatch"),
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match");
AUTH_TOKEN_NOT_FOUND(4, "Token not found"),
FILE_NOT_FOUND(4, "File not found"),
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"),
SEND_EMAIL_EXCEPTION(2, "Send email exception"),
SIMILAR_FILES_FOUND(0, "Similar files found"),
FILE_IS_PROTECTED(0, "File is protected"),
FILE_IS_NOT_PROTECTED(0, "File is not protected"),
TARIFF_IS_ADD(0, "Tariff is added"),
TARIFF_IS_DELETED(0, "Tariff is deleted"),
TARIFF_IS_NOT_DELETED(0, "Tariff is not deleted,use some users"),
TARIFF_IS_UPDATED(0, "Tariff is updated"),
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
VALIDATION_ERROR(2, "Validation error"),
RESOURCE_NOT_FOUND(4, "Resource not found"),
OPERATION_NOT_FOUND(4, "Operation not found"),
INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"),
PAYMENT_NOT_FOUND(4, "Payment not found"),
COMPANY_ALREADY_EXISTS(2, "Company already exists"),
USER_LIMIT_IS_OVER(2, "Over user limits"),
MONITORING_TYPE_NOT_FOUND(4, "Monitoring type not found"),
ERROR_TARIFF_INFO(2, "Erorr with tariff info"),
FILE_FOR_SEARCH_NOT_VALID(2, "File for search unsupported"),
ILLEGAL_STATE(2, "File is not in moderation state"),
NOT_VALID_FILE_TYPE_OR_COUNT_FILE(2, "Cost for file type not found, count is negative or files count" +
"more than max valid"),
USER_NOT_ACTIVE(2, "User not active"),
NOTION_NOT_FOUND(4, "Notion not found"),
NOT_FOUND(4, "Notion not found"),
MESSAGE_IS_REQUIRED_FOR_NOTION(4, "Message is required for notion"),
USER_VERIFICATIONS_NOT_FOUND(4, "Verifications not found"),
COMPLAINT_OR_LAW_CASE_IN_WORK(2, "Complaint or lawcase in work"),
NOT_VALID_STATUS(2, "Not valid status"),
PERMISSION_NOT_VALID(2, "Not valid permission"),
NOT_HAVE_DOWNLOAD_PERMISSION(2, "Not haver permission for download"),
ADMIN_USER_NOT_FOUND(4, "Verifications ,user or admin not found");
private final Integer code;
@@ -0,0 +1,9 @@
package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class TokenProcessRequest {
}
@@ -1,14 +0,0 @@
package ru.soune.nocopy.dto;
import lombok.Data;
@Data
public class UserContentDTO {
String fileName;
String originalFilename;
String fileType;
String fileExtension;
Long fileSize;
String filePath;
Integer protectionLevel;
}
@@ -1,17 +0,0 @@
package ru.soune.nocopy.dto;
import ru.soune.nocopy.entity.ContentStatus;
import ru.soune.nocopy.entity.FileType;
import java.time.LocalDateTime;
public record UserContentResponse(
Long id,
String filename,
String originalFilename,
FileType fileType,
long fileSize,
String filePath,
ContentStatus status,
LocalDateTime uploadDate
) {}
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.company;
import lombok.Data;
import java.util.Map;
@Data
public class CompanyActionRequestDto {
private String action;
private String companyId;
private String companyName;
private Map<String, Object> companyData;
private Long userId;
private Map<String, Object> searchParams;
private PageableParams pageable;
@Data
public static class PageableParams {
private int page;
private int size;
private String sort;
private String direction;
}
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.company;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class CompanyResponseDto {
private String id;
private String companyName;
private String phone;
private String address;
private String email;
private LocalDateTime registerDate;
private LocalDateTime updateDate;
private long userCount;
}
@@ -0,0 +1,35 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class BaseResponse {
@JsonProperty("status")
private String status;
@JsonProperty("message")
private String message;
@JsonProperty("data")
private Object data;
@JsonProperty("error_code")
private String errorCode;
public static BaseResponse success(Object data) {
return BaseResponse.builder()
.status("SUCCESS")
.data(data)
.build();
}
public static BaseResponse error(String message, String errorCode) {
return BaseResponse.builder()
.status("ERROR")
.message(message)
.errorCode(errorCode)
.build();
}
}
@@ -0,0 +1,38 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class ComplaintRequest {
@JsonProperty("action")
private String action;
@JsonProperty("id")
private Long id;
@JsonProperty("violation_id")
private Long violationId;
@JsonProperty("text")
private String complaintText;
@JsonProperty("email")
private String email;
@JsonProperty("status")
private String status;
@JsonProperty("page")
private Integer page;
@JsonProperty("size")
private Integer size;
@JsonProperty("sort_by")
private String sortBy;
@JsonProperty("sort_direction")
private String sortDirection;
}
@@ -0,0 +1,42 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComplaintResponse {
@JsonProperty("id")
private Long id;
@JsonProperty("status")
private String status;
@JsonProperty("complaint_text")
private String complaintText;
@JsonProperty("violation_id")
private Long violationId;
@JsonProperty("email")
private String email;
@JsonProperty("created_at")
private String createdAt;
@JsonProperty("updated_at")
private String updatedAt;
@JsonProperty("not_moderated")
private Boolean notModerated;
@JsonProperty("file_id")
private String fileId;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.complaint;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class LawCasePageResponse {
private List<LawCaseResponse> content;
private int pageNumber;
private int pageSize;
private long totalElements;
private int totalPages;
private boolean last;
private boolean first;
}
@@ -0,0 +1,32 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.math.BigDecimal;
@Data
public class LawCaseRequest {
private Long id;
//required
private String name;
//required
private String description;
private BigDecimal amount;
private String priority = LawCasePriority.HIGH.toString();
private String type;
private String lawyer;
private Integer pageSize = 5;
private Integer pageNumber = 0;
private String sortBy = "updatedAt";
private String sortDir = "desc";
private String action;
private String filterType;
private String filterLawyer;
private String filterPriority;
@JsonProperty("violation_id")
private Long violationId;
}
@@ -0,0 +1,71 @@
package ru.soune.nocopy.dto.complaint;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
public class LawCaseResponse {
private Long id;
private String name;
private String description;
private BigDecimal amount;
private LawCasePriority priority;
private LawCaseType type;
private String lawyer;
@JsonProperty("created_at")
private LocalDateTime createdAt;
@JsonProperty("updated_at")
private LocalDateTime updatedAt;
@JsonProperty("page_number")
private int pageNumber;
@JsonProperty("page_size")
private int pageSize;
@JsonProperty("total_elements")
private long totalElements;
@JsonProperty("total_pages")
private int totalPages;
@JsonProperty("violation_id")
private long violationId;
@JsonProperty("file_id")
private String fileId;
private List<LawCaseResponse> content;
public static LawCaseResponse fromEntity(LawCase lawCase) {
return LawCaseResponse.builder()
.id(lawCase.getId())
.name(lawCase.getName())
.description(lawCase.getDescription())
.amount(lawCase.getAmount())
.priority(lawCase.getPriority())
.type(lawCase.getType())
.lawyer(lawCase.getLawyer())
.createdAt(lawCase.getCreatedAt())
.updatedAt(lawCase.getUpdatedAt())
.violationId(lawCase.getViolationId())
.build();
}
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.complaint;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class PaginatedResponse<T> {
private List<T> content;
private long totalElements;
private int totalPages;
private int pageNumber;
private int pageSize;
private boolean first;
private boolean last;
}
@@ -0,0 +1,15 @@
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;
}
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.cost;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class CostResponse {
private int cost;
private Boolean success;
@JsonProperty("max_files_for_check")
private Integer maxFilesForCheck;
@JsonProperty("count_file")
private Integer countFile;
@JsonProperty("tokens_count")
private int needTokensCount;
}
@@ -0,0 +1,10 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataAddress {
private String value;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataData {
private String inn;
private DaDataAddress address;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataRequest {
@JsonProperty("inn")
private String inn;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataResponse {
@JsonProperty("companyName")
private String companyName;
@JsonProperty("inn")
private String inn;
@JsonProperty("address")
private String address;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataSuggestion {
private String value;
private DaDataData data;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataWrapper {
private List<DaDataSuggestion> suggestions;
}
@@ -0,0 +1,36 @@
package ru.soune.nocopy.dto.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DemoSearchResponse {
private String sessionId;
private String status;
private Integer resultsCount;
private Double matchPercentage;
private LocalDateTime startedAt;
private LocalDateTime completedAt;
private Long processingTimeMs;
private List<DemoSimilarFile> similarFiles;
private String message;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class DemoSimilarFile {
private String fileId;
private String fileName;
private Double similarityScore;
private String source; // "web" или "database"
}
}
@@ -0,0 +1,24 @@
package ru.soune.nocopy.dto.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DemoSessionInfo {
private String sessionId;
private String fileName;
private String fileType;
private Long fileSize;
private String status;
private LocalDateTime createdAt;
private LocalDateTime expiresAt;
private Integer searchResultsCount;
private String mimeType;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DemoUploadRequest {
private String fileName;
private String fileType; // "image", "document", "audio"
private Long fileSize;
private String mimeType;
}
@@ -0,0 +1,21 @@
package ru.soune.nocopy.dto.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DemoUploadResponse {
private String sessionId;
private String fileName;
private String fileType;
private Long fileSize;
private String status;
private String message;
private Long uploadedBytes;
private Integer progressPercentage;
}
@@ -0,0 +1,36 @@
package ru.soune.nocopy.dto.docviewer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class DocViewerRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("ip")
private String ip;
@JsonProperty("user_agent")
private String userAgent;
@JsonProperty("page")
private int page = 0;
@JsonProperty("size")
private int size = 10;
@JsonProperty("sort_by")
private String sortBy = "createdAt";
@JsonProperty("sort_direction")
private String sortDirection = "desc";
@JsonProperty("action")
private String action;
@JsonProperty("locale")
private String locale;
}
@@ -0,0 +1,28 @@
package ru.soune.nocopy.dto.docviewer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DockViewResponse {
@JsonProperty("country")
private String country;
@JsonProperty("viewer")
private String viewer;
@JsonProperty("document")
private String document;
@JsonProperty("view_date")
private LocalDateTime viewDate;
}
@@ -0,0 +1,55 @@
package ru.soune.nocopy.dto.docviewer;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DockViewStatsResponse {
private String fileId;
private Long totalViews;
private Long uniqueIps;
private Long uniqueUsers;
private int documentsCount;
private int uniqueCountryCount;
private int uniqueCityCount;
private LocalDateTime firstViewDate;
private LocalDateTime lastViewDate;
private List<DockViewStatsResponse> content;
private List<DockViewResponse> history;
private Integer pageNumber;
private Integer pageSize;
private Long totalElements;
private Integer totalPages;
private Boolean isLast;
private Boolean isFirst;
private Boolean hasNext;
private Boolean hasPrevious;
}
@@ -0,0 +1,17 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ActionResponse {
private List<String> availableActions;
private String action;
}
@@ -0,0 +1,14 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class AppealInfo {
private String appealId;
private String status;
private LocalDateTime createdAt;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class AppealRequest {
private String fileId;
private String appealReason;
private String additionalInfo;
}
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class AppealResponse {
private String appealId;
private String fileId;
private String appealReason;
private String additionalInfo;
private String status;
private String adminComment;
private String fileName;
private LocalDateTime createdAt;
private LocalDateTime resolvedAt;
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class CanceledUploadResponse {
@JsonProperty("upload_id")
private String uploadId;
@JsonProperty("message")
private String message;
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CheckIncrementResult {
private boolean success;
private Integer countChecked;
private Integer remainingLimit;
private String message;
public static CheckIncrementResult success(Integer countChecked) {
return CheckIncrementResult.builder()
.success(true)
.countChecked(countChecked)
.message("Check count incremented successfully")
.build();
}
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class CheckStatus {
private Long userId;
private Integer countChecked;
private Integer remainingLimit;
private LocalDateTime lastCheckAt;
}
@@ -0,0 +1,28 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
@Data
@Builder
public class ChunkStatusResponse {
@JsonProperty("upload_id")
private String uploadId;
@JsonProperty("total_chunks")
private Integer totalChunks;
@JsonProperty("chunk_status")
private Map<String, Boolean> chunkStatus;
@JsonProperty("uploaded_chunks")
private Integer uploadedChunks;
@JsonProperty("missing_chunks")
private Integer missingChunks;
}
@@ -0,0 +1,28 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChunkUploadResponse {
@JsonProperty("upload_id")
private String uploadId;
@JsonProperty("chunk_number")
private Integer chunkNumber;
@JsonProperty("chunk_size")
private Long chunkSize;
@JsonProperty("file_id")
private String fileId;
@JsonProperty("message")
private String message;
}

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