234 Commits
Author SHA1 Message Date
vladp daef30ec11 NCBACK-35 add worked logic for save protected files
Test Workflow / test (push) Successful in 4s
2026-02-16 16:05:15 +07:00
vladp a25b33fe13 NCBACK-35 add worked logic for save protected files
Test Workflow / test (push) Successful in 4s
2026-02-16 15:04:58 +07:00
vladp 2985d84b04 NCBACK-35 add cloud for save when download last chunk and after protected
Test Workflow / test (push) Successful in 3s
2026-02-13 21:11:30 +07:00
vladp 03a330b570 NCBACK-46 fix file name
Test Workflow / test (push) Successful in 3s
2026-02-12 16:03:39 +07:00
vladp b372180f83 NCBACK-42 add dadata handler
Test Workflow / test (push) Successful in 3s
2026-02-12 15:36:45 +07:00
vladp 920fc67962 dev only unige result
Test Workflow / test (push) Successful in 3s
2026-02-12 12:07:41 +07:00
vladp fb2ac0cf03 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-12 11:58:05 +07:00
vladp 8a2be0d310 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 19:50:07 +07:00
vladp 0743000c10 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 19:15:16 +07:00
vladp 640deef98d dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 18:47:58 +07:00
vladp 28b5a64a33 dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 18:28:57 +07:00
vladp 6818b74307 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 18:12:44 +07:00
vladp 6b73d01aeb dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 17:59:41 +07:00
vladp 1cb9edd5bf dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 17:53:02 +07:00
vladp 081ca05f88 dev change to visual_matches engine
Test Workflow / test (push) Successful in 3s
2026-02-11 17:41:48 +07:00
vladp 970e0fa615 dev change to visual_matches engine
Test Workflow / test (push) Successful in 4s
2026-02-11 16:24:19 +07:00
vladp 52e1a7dd02 dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 20:50:06 +07:00
vladp 19330d853d dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 20:40:55 +07:00
vladp 1472c342f5 dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 19:14:16 +07:00
vladp 1ced86ff8c dev add pdf docs protect
Test Workflow / test (push) Successful in 3s
2026-02-10 18:55:21 +07:00
vladp 8149c450b2 dev add pdf docs protect
Test Workflow / test (push) Successful in 4s
2026-02-10 18:38:46 +07:00
vladp fea2d5ad89 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 16:23:49 +07:00
vladp 2ef1619ef9 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 16:10:05 +07:00
vladp 266bcaefcd dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 15:55:20 +07:00
vladp bf48725244 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:47:08 +07:00
vladp 86f7fc593a dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:26:44 +07:00
vladp 90be1d9d2b dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 13:03:19 +07:00
vladp 9692fe48b7 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:52:36 +07:00
vladp d50c565405 dev add utf encoding
Test Workflow / test (push) Successful in 4s
2026-02-10 12:46:45 +07:00
vladp 98fc145f1b dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:36:52 +07:00
vladp e7e870ee30 dev add utf encoding
Test Workflow / test (push) Successful in 3s
2026-02-10 12:33:07 +07:00
vladp 8103960b45 dev add pagination for search in internet images
Test Workflow / test (push) Successful in 3s
2026-02-10 12:16:31 +07:00
vladp 792e0048cc dev add pagination for search in internet images
Test Workflow / test (push) Successful in 4s
2026-02-10 11:58:32 +07:00
vladp 33883faa98 dev split tokens
Test Workflow / test (push) Successful in 2s
2026-02-09 17:25:06 +07:00
vladp 7cc744829b dev split tariff by account types
Test Workflow / test (push) Successful in 3s
2026-02-09 17:16:08 +07:00
vladp 5a70c091f8 dev
Test Workflow / test (push) Successful in 5s
2026-02-09 15:29:55 +07:00
vladp 69aabe3619 dev
Test Workflow / test (push) Successful in 3s
2026-02-09 15:20:42 +07:00
vladp 27a8766171 dev fix name for parameters
Test Workflow / test (push) Successful in 2s
2026-02-09 14:15:29 +07:00
vladp 2bba70e583 dev fix name for parameters
Test Workflow / test (push) Successful in 3s
2026-02-09 13:50:42 +07:00
vladp 8e8193cda6 dev company users saw all company files
Test Workflow / test (push) Successful in 6s
2026-02-09 12:20:19 +07:00
vladp b96b50c01f add referal only for b2c
Test Workflow / test (push) Successful in 3s
2026-02-09 11:28:23 +07:00
vladp e926934473 add add sql scripts
Test Workflow / test (push) Successful in 3s
2026-02-09 11:24:34 +07:00
vladp a5003958a8 add set user limits
Test Workflow / test (push) Successful in 3s
2026-02-09 11:06:50 +07:00
vladp 5b73fd2c39 add document
Test Workflow / test (push) Has been cancelled
2026-02-07 11:54:34 +07:00
vladp daadc82bbb add document
Test Workflow / test (push) Successful in 3s
2026-02-06 22:21:51 +07:00
vladp 682c21ba73 fix value
Test Workflow / test (push) Successful in 2s
2026-02-06 21:22:47 +07:00
vladp 1c7a33be32 fix value
Test Workflow / test (push) Successful in 4s
2026-02-06 20:43:27 +07:00
vladp aceb70484f fix value
Test Workflow / test (push) Successful in 3s
2026-02-06 20:18:43 +07:00
backdev 3fc83e5458 Merge pull request 'NCBACK-36_1' (#19) from NCBACK-36_1 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #19
2026-02-06 20:27:56 +08:00
vladp a7e94639d2 NC_BACK-36_1
Test Workflow / test (push) Successful in 3s
2026-02-06 19:27:25 +07:00
vladp f52ce6db3c dev add referral
Test Workflow / test (push) Successful in 3s
2026-02-06 01:01:34 +07:00
vladp 80c53515fb dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 20:23:33 +07:00
vladp 511da8321b dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 20:21:27 +07:00
vladp df1fd89c29 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:47:32 +07:00
vladp 90e3134e98 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:33:37 +07:00
vladp 130fd184b9 dev add stats api
Test Workflow / test (push) Successful in 3s
2026-02-05 19:02:17 +07:00
vladp f90b8a0bce dev add stats for search check
Test Workflow / test (push) Successful in 3s
2026-02-05 18:55:54 +07:00
vladp adc978bea8 dev write off for search
Test Workflow / test (push) Successful in 3s
2026-02-05 17:25:50 +07:00
vladp 7bb48f3944 dev add test find by searchapo
Test Workflow / test (push) Successful in 3s
2026-02-05 17:16:16 +07:00
milidev 27b812b0da NCBACK-36: [Referral] Referral Service
Test Workflow / test (push) Successful in 3s
2026-02-05 15:43:42 +07:00
vladp 6643070999 dev add test find by searchapo
Test Workflow / test (push) Successful in 8s
2026-02-05 12:44:27 +07:00
vladp 4395d276ed dev add test find by searchapo
Test Workflow / test (push) Successful in 4s
2026-02-05 10:47:40 +07:00
vladp e49a944b05 dev add test find by searchapo
Test Workflow / test (push) Successful in 3s
2026-02-05 03:30:15 +07:00
vladp 8e716f7d20 dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 03:27:16 +07:00
vladp 2e8080183c dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 00:46:17 +07:00
vladp dc9215523b dev add test find by searchapo
Test Workflow / test (push) Waiting to run
2026-02-05 00:37:59 +07:00
vladp 72691511fa dev add
Test Workflow / test (push) Successful in 3s
2026-02-04 14:56:19 +07:00
vladp 135d94e0ef dev add
Test Workflow / test (push) Successful in 4s
2026-02-04 13:41:09 +07:00
vladp d1bacb4ac7 dev add
Test Workflow / test (push) Successful in 9s
2026-02-04 12:50:35 +07:00
vladp 7a809e6b8c dev add
Test Workflow / test (push) Successful in 4s
2026-02-04 11:47:43 +07:00
vladp 06a6196a26 dev add
Test Workflow / test (push) Successful in 10s
2026-02-03 16:01:32 +07:00
vladp 13324d4628 dev add tarifinfo by permission for user info
Test Workflow / test (push) Successful in 4s
2026-02-03 15:05:03 +07:00
vladp c38b86e1b9 dev add tariffinfo endpoints
Test Workflow / test (push) Successful in 4s
2026-02-02 15:47:58 +07:00
vladp 3ce8edd7e1 dev add tariff endpoints
Test Workflow / test (push) Successful in 3s
2026-02-02 15:15:43 +07:00
vladp 22b487da22 dev add tariff endpoints
Test Workflow / test (push) Successful in 4s
2026-02-02 15:00:18 +07:00
vladp 4395654b0d dev add apllication tariff fields
Test Workflow / test (push) Successful in 3s
2026-02-02 12:19:34 +07:00
vladp 7dd057603d dev
Test Workflow / test (push) Successful in 2s
2026-01-30 22:36:20 +07:00
vladp a4858cf6c5 dev
Test Workflow / test (push) Successful in 3s
2026-01-30 22:30:27 +07:00
vladp 7d52276b31 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 19:27:01 +07:00
vladp 8fe8c566c0 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 5s
2026-01-30 18:04:08 +07:00
vladp 36d40a0b8d dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 4s
2026-01-30 18:03:51 +07:00
vladp 32f0a07e32 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 18:02:34 +07:00
vladp 51ef63e8cd dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:55:14 +07:00
vladp b0237fd812 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:51:59 +07:00
vladp de8c53784b dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:39:19 +07:00
vladp 3cd6b5396c dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 3s
2026-01-30 17:36:57 +07:00
vladp 615924ea9e dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 17:29:25 +07:00
vladp 9a8aa498c0 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 15:09:39 +07:00
vladp 66633d4964 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 14:24:23 +07:00
vladp ce8c63475f dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 14:00:21 +07:00
vladp 30c05b6b24 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 13:02:30 +07:00
vladp 49dc19cd04 dev add tariff and temp flag for files
Test Workflow / test (push) Successful in 2s
2026-01-30 12:52:16 +07:00
vladp fb5642db9e dev check null company
Test Workflow / test (push) Successful in 2s
2026-01-29 21:46:37 +07:00
vladp c539b1fbc2 dev check null company
Test Workflow / test (push) Successful in 2s
2026-01-29 21:31:28 +07:00
vladp 61b8ea902b dev limit for yasearch result
Test Workflow / test (push) Successful in 1s
2026-01-29 20:13:10 +07:00
vladp 6ee7ab4d04 dev add rest for company, add link for users , update reg users
Test Workflow / test (push) Successful in 2s
2026-01-29 19:24:36 +07:00
vladp 96acf796b2 dev change status on Processing for prePresist
Test Workflow / test (push) Successful in 3s
2026-01-29 16:25:34 +07:00
vladp ff7836900b dev fix show image
Test Workflow / test (push) Successful in 2s
2026-01-29 13:08:43 +07:00
vladp ec20d68261 dev add check limits logic
Test Workflow / test (push) Successful in 3s
2026-01-28 23:25:16 +07:00
vladp 31fac0ab93 dev check to protect
Test Workflow / test (push) Successful in 3s
2026-01-28 21:18:42 +07:00
vladp a32f089989 dev add info for files
Test Workflow / test (push) Successful in 2s
2026-01-28 19:26:40 +07:00
vladp c019f23777 dev download only protected files
Test Workflow / test (push) Successful in 3s
2026-01-28 18:38:43 +07:00
vladp 02bf7868ee dev add split network and add new ports with apps
Test Workflow / test (push) Successful in 9s
2026-01-28 18:19:20 +07:00
vladp 2215adc37f dev
Test Workflow / test (push) Successful in 3s
2026-01-27 20:55:33 +07:00
vladp 6fcec8d67e dev
Test Workflow / test (push) Successful in 2s
2026-01-27 20:41:08 +07:00
vladp 4a185e6f9a dev
Test Workflow / test (push) Successful in 3s
2026-01-27 20:27:39 +07:00
vladp cf2f73955b dev
Test Workflow / test (push) Successful in 3s
2026-01-27 08:50:52 +07:00
vladp f67e172365 dev
Test Workflow / test (push) Successful in 3s
2026-01-27 08:25:38 +07:00
backdev d32e8dcd75 Merge pull request 'NCBACK-33' (#16) from NCBACK-33 into dev
Test Workflow / test (push) Successful in 3s
Reviewed-on: #16
2026-01-27 09:11:31 +08:00
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
201 changed files with 9140 additions and 1499 deletions
+11 -2
View File
@@ -1,10 +1,19 @@
# Postgres
POSTGRES_DB=no_copy_ POSTGRES_DB=no_copy_
POSTGRES_USER=postgres POSTGRES_USER=ncp_db
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=ncpDbApp
POSTGRES_PORT=5432 POSTGRES_PORT=5432
POSTGRES_HOST=postgres POSTGRES_HOST=postgres
# Redis
REDIS_HOST=redis REDIS_HOST=redis
REDIS_PORT=6379 REDIS_PORT=6379
# Backend server
SERVER_PORT=8080 SERVER_PORT=8080
# Postfix
MAIL_HOST_PROD=postfix-production
MAIL_PORT_PROD=25
MAIL_USERNAME_PROD=noreply@nocopy.com
SMTP_PASSWORD_PROD=nocopy!nocopy!
+6 -1
View File
@@ -12,6 +12,11 @@ WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar 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 EXPOSE 8080
CMD ["java", "-jar", "app.jar"] CMD ["java", "-jar", "app.jar"]
+163
View File
@@ -0,0 +1,163 @@
Создаем сеть:
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);
------
Настройка Яндекс Клауд
1. YCAJEpWAtaVkVGX0sH6_EupEg - индетификатор ключа
2. YCMmVykfXrZ_nfU13Vo4yoCVGa70DnTlBgF1pUzO - секретный ключ
private static final String KEY_ID = "YCAJEmHZcWxzf4oGWvETG3mms";
private static final String SECRET_KEY = "YCPAPZ32XC-LWb8xE0b_xTcBD8NZUCHpSOfu8LUG";
+26
View File
@@ -21,6 +21,9 @@ configurations {
repositories { repositories {
mavenCentral() mavenCentral()
flatDir {
dirs 'libs'
}
} }
dependencies { dependencies {
@@ -30,6 +33,16 @@ dependencies {
implementation 'org.springframework.security:spring-security-crypto:6.5.3' implementation 'org.springframework.security:spring-security-crypto:6.5.3'
implementation 'org.mapstruct:mapstruct:1.5.5.Final' implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'commons-validator:commons-validator:1.7' implementation 'commons-validator:commons-validator:1.7'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '4.0.1'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.17.2'
implementation group: 'com.google.cloud', name: 'google-cloud-vision', version: '3.55.0'
implementation group: 'com.google.api-client', name: 'google-api-client', version: '2.7.2'
implementation 'org.flywaydb:flyway-core:9.22.0'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.0'
implementation 'tools.jackson.core:jackson-core:3.0.3'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
@@ -45,6 +58,19 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:5.3.1' testImplementation 'org.mockito:mockito-core:5.3.1'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation name: 'testlib-fat-0.3.0-all'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.12.0'
implementation project(':referral')
//cloud
implementation("software.amazon.awssdk:aws-sdk-java:2.29.33")
implementation("software.amazon.awssdk:apache-client:2.29.33")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
implementation("org.springframework.boot:spring-boot-starter-web")
} }
tasks.named('test') { tasks.named('test') {
+138 -10
View File
@@ -1,17 +1,69 @@
version: '3.9' version: '3.9'
services: 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: db:
image: postgres:17 image: postgres:17.7
restart: always restart: always
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G
environment: environment:
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
POSTGRES_USER: postgres # POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres # POSTGRES_PASSWORD: postgres
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_SHARED_BUFFERS: 512MB
POSTGRES_EFFECTIVE_CACHE_SIZE: 1536MB
ports: ports:
- "54320:5432" - "54320:5432"
volumes: volumes:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d:ro
container_name: postgres container_name: postgres
networks: networks:
app-network: app-network:
@@ -21,15 +73,40 @@ services:
app: app:
build: . build: .
container_name: app-backend container_name: app-backend
deploy:
resources:
limits:
cpus: '1.5'
memory: 3G
reservations:
cpus: '0.5'
memory: 2G
environment: environment:
JAVA_TOOL_OPTIONS: "-Xmx2g -Xms1g -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:+UseContainerSupport -XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=75"
FILE_STORAGE_PATH: /data/uploads FILE_STORAGE_PATH: /data/uploads
MAX_FILE_SIZE: 1073741824 MAX_FILE_SIZE: 10737418240
FILE_CHUNK_SIZE: 5242880 # FILE_CHUNK_SIZE: 1048576
FILE_CHUNK_SIZE: 1000000
POSTGRES_DB: no_copy_ POSTGRES_DB: no_copy_
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: 5432 POSTGRES_PORT: 5432
POSTGRES_HOST: db POSTGRES_HOST: db
STORAGE_SERVICE_URL: http://storage:8081
SPRING_PROFILES_ACTIVE: prod
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
YANDEX_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: depends_on:
- db - db
ports: ports:
@@ -41,11 +118,22 @@ services:
- backend - backend
- api - api
volumes: volumes:
- ./uploads:/data/uploads:rw - uploads_data:/data/uploads:rw
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d no_copy_" ]
interval: 10s
retries: 5
grafana: grafana:
image: grafana/grafana:10.3.1 image: grafana/grafana:10.3.1
container_name: grafana container_name: grafana
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
ports: ports:
- "3000:3000" - "3000:3000"
depends_on: depends_on:
@@ -62,6 +150,8 @@ services:
GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin GF_SECURITY_ADMIN_PASSWORD: admin
GF_METRICS_ENABLED: "true" GF_METRICS_ENABLED: "true"
GF_DATABASE_MAX_IDLE_CONN: "2"
GF_DATABASE_MAX_OPEN_CONN: "10"
volumes: volumes:
- ./infrastructure/grafana/provisioning:/etc/grafana/provisioning - ./infrastructure/grafana/provisioning:/etc/grafana/provisioning
- ./infrastructure/grafana/dashboards:/var/lib/grafana/dashboards - ./infrastructure/grafana/dashboards:/var/lib/grafana/dashboards
@@ -76,10 +166,23 @@ services:
prometheus: prometheus:
image: prom/prometheus:latest image: prom/prometheus:latest
container_name: prometheus container_name: prometheus
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
reservations:
memory: 512M
ports: ports:
- "9090:9090" - "9090:9090"
volumes: volumes:
- ./infrastructure/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - ./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: healthcheck:
test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ] test: [ "CMD", "wget", "--spider", "http://localhost:9090/-/healthy" ]
interval: 10s interval: 10s
@@ -91,10 +194,20 @@ services:
loki: loki:
image: grafana/loki:2.9.2 image: grafana/loki:2.9.2
container_name: loki container_name: loki
deploy:
resources:
limits:
cpus: '1.0'
memory: 2G
reservations:
memory: 1G
ports: ports:
- "3100:3100" - "3100:3100"
volumes: volumes:
- ./infrastructure/loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro - ./infrastructure/loki/loki-config.yaml:/etc/loki/loki-config.yaml:ro
- loki_data:/loki
command:
- -config.file=/etc/loki/loki-config.yaml
healthcheck: healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ] test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ]
interval: 10s interval: 10s
@@ -106,6 +219,13 @@ services:
tempo: tempo:
image: grafana/tempo:2.4.1 image: grafana/tempo:2.4.1
container_name: tempo container_name: tempo
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
memory: 256M
command: [ "-config.file=/etc/tempo/tempo.yaml" ] command: [ "-config.file=/etc/tempo/tempo.yaml" ]
volumes: volumes:
- ./infrastructure/tempo/tempo.yaml:/etc/tempo/tempo.yaml - ./infrastructure/tempo/tempo.yaml:/etc/tempo/tempo.yaml
@@ -118,6 +238,13 @@ services:
alloy: alloy:
image: grafana/alloy:latest image: grafana/alloy:latest
container_name: alloy container_name: alloy
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
memory: 128M
user: root user: root
ports: ports:
- "9080:9080" - "9080:9080"
@@ -146,8 +273,9 @@ volumes:
loki_chunks: loki_chunks:
loki_index: loki_index:
loki_rules: loki_rules:
# uploads_volume: uploads_data:
prometheus_data:
# postfix-data:
networks: networks:
app-network: app-network:
external: true external: true
+24
View File
@@ -0,0 +1,24 @@
plugins {
kotlin("jvm") version "2.1.10"
}
group = "ru.soune"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-core-jvm:4.1.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
+5
View File
@@ -0,0 +1,5 @@
package ru.soune
fun main() {
println("Hello World!")
}
@@ -0,0 +1,3 @@
package ru.soune.actions
abstract class NoCopyAction
@@ -0,0 +1,5 @@
package ru.soune.actions.free
import ru.soune.actions.NoCopyAction
sealed class NoCopyFreeAction : NoCopyAction()
@@ -0,0 +1,8 @@
package ru.soune.actions.paid
import ru.soune.actions.NoCopyAction
sealed class NoCopyPaidAction : NoCopyAction() {
abstract val cost: Double
}
+26
View File
@@ -0,0 +1,26 @@
## Команды для помещения в сеть
Сеть для работы бэка и фронта : Наименование == app-network-{port}
### Проверить состояние:
docker inspect {container-name} --format='{{range $net, $_ := .NetworkSettings.Networks}}{{$net}} {{end}}'
### Создать сеть ,если нет
docker network create {name-network} 2>/dev/null || echo "Сеть уже существует"
### Переместить контейнер:
# Отключить от старой сети
docker network disconnect {network-name-old} {container-name}
# Подключить к новой сети
docker network connect {network-name-new} {container-name}
# Добавить алиас (опционально)
docker network connect --alias {alias} {network-name-new} {container-name}
+156
View File
@@ -0,0 +1,156 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя админ-панели'
)
string(
name: 'PORT',
defaultValue: '2996',
description: 'Порт для запуска экземпляра'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-admin-panel-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-admin-panel-${params.PORT} 2>/dev/null || true
docker rm no-copy-admin-panel-${params.PORT} 2>/dev/null || true
mkdir -p /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}
rm -rf /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}/*
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/admin-panel/${params.BRANCH}-${params.PORT}
docker build -t no-copy-admin-panel:${params.BRANCH}-${params.PORT} .
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker run -d \\
--name no-copy-admin-panel-${params.PORT} \\
--restart unless-stopped \\
--network app-network \\
-p ${params.PORT}:2996 \\
no-copy-admin-panel:${params.BRANCH}-${params.PORT}
sleep 5
docker ps --filter name=no-copy-admin-panel-${params.PORT}
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-admin-panel-${params.PORT}'; then
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} 2>/dev/null || echo '000')
echo 'Admin panel available on http://92.242.61.23:${params.PORT}'
else
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Admin panel ${params.BRANCH} deployed"
}
failure {
echo "Deployment failed"
}
}
}
@@ -0,0 +1,148 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'dev',
description: 'Ветка для деплоя'
)
string(
name: 'PORT',
defaultValue: '3000',
description: 'Порт для запуска экземпляра'
)
string(
name: 'SERVER',
defaultValue: '92.242.61.23',
description: 'Сервер для деплоя'
)
}
stages {
stage('Git pull') {
steps {
cleanWs()
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/backdev/no-copy.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Deploy with docker-compose') {
steps {
script {
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD')
]) {
sh """
# Тестируем подключение сначала
echo "Testing SSH connection..."
if sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} 'echo "SSH connection successful"'; then
echo "SSH connection OK"
else
echo "SSH connection failed"
exit 1
fi
# Основная команда
sshpass -p '${SSH_PASS}' ssh -o StrictHostKeyChecking=no ${SSH_USER}@${params.SERVER} "
cd /opt/deployments/${params.BRANCH}-${params.PORT}
echo '1. Создаем изолированную сеть для порта ${params.PORT}...'
docker network create app-network-${params.PORT} 2>/dev/null || echo 'Сеть уже существует'
echo '2. Останавливаем старые контейнеры порта ${params.PORT}...'
docker stop app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
docker rm app-backend-${params.PORT} 2>/dev/null || echo 'Контейнер не найден'
docker stop postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker rm postgres-${params.PORT} 2>/dev/null || echo 'Контейнер БД не найден'
docker stop file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
docker rm file-storage-${params.PORT} 2>/dev/null || echo 'Контейнер storage не найден'
echo '3. Запускаем БД...'
docker run -d \\
--name postgres-${params.PORT} \\
--network app-network-${params.PORT} \\
--network-alias no_copy_${params.PORT} \\
--network-alias database-${params.PORT} \\
--network-alias db-${params.PORT} \\
-p ${params.PORT}0:5432 \\
-e POSTGRES_DB=no_copy_${params.PORT} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-v pgdata_${params.PORT}:/var/lib/postgresql/data \\
-e BUILD_TIME_BACK='${buildTime}' \\
postgres:17.7
echo '4. Запускаем storage...'
docker run -d \\
--name file-storage-${params.PORT} \\
--network app-network-${params.PORT} \\
-v uploads_data_${params.PORT}:/storage:rw \\
alpine:latest tail -f /dev/null
echo '5. Ждем БД...'
sleep 15
echo '6. Собираем приложение...'
docker build --no-cache -t app-backend-${params.PORT}:latest .
echo '7. Запускаем приложение...'
docker run -d \\
--name app-backend-${params.PORT} \\
--network app-network-${params.PORT} \\
-p ${params.PORT}:8080 \\
-v uploads_data_${params.PORT}:/data/uploads:rw \\
-e POSTGRES_DB=no_copy_${params.PORT} \\
-e POSTGRES_USER=${DB_USER} \\
-e POSTGRES_PASSWORD=${DB_PASSWORD} \\
-e POSTGRES_PORT=5432 \\
-e POSTGRES_HOST=no_copy_${params.PORT} \\
-e FILE_STORAGE_PATH=/data/uploads \\
app-backend-${params.PORT}:latest
echo '8. Проверка...'
sleep 10
if curl -s -f http://localhost:${params.PORT}/health > /dev/null 2>&1; then
echo 'Приложение работает на порту ${params.PORT}'
else
echo 'Проверка health не удалась'
docker logs app-backend-${params.PORT} --tail=20
fi
"
"""
}
}
}
}
}
post {
success {
echo "Deployment successful"
echo "Application URL: http://${params.SERVER}:${params.PORT}"
}
failure {
echo "Deployment failed for branch ${params.BRANCH} on port ${params.PORT}"
}
always {
echo "Deployment process finished"
}
}
}
+87 -69
View File
@@ -7,6 +7,11 @@ pipeline {
defaultValue: 'main', defaultValue: 'main',
description: 'Ветка для деплоя' description: 'Ветка для деплоя'
) )
string(
name: 'PORT',
defaultValue: '2998',
description: 'Порт для запуска экземпляра'
)
} }
stages { stages {
@@ -36,19 +41,19 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true docker stop no-copy-frontend-${params.PORT} 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true docker rm no-copy-frontend-${params.PORT} 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH} cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
if [ -f 'docker-compose.yaml' ]; then if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true docker-compose -f docker-compose.yaml -p frontend-${params.PORT} down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true docker-compose -f docker-compose.yml -p frontend-${params.PORT} down 2>/dev/null || true
fi fi
" "
""" """
} }
} }
} }
@@ -65,13 +70,13 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/* rm -rf /opt/deployments/frontend/${params.BRANCH}-${params.PORT}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH} mkdir -p /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
" "
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/ sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}-${params.PORT}/
""" """
} }
} }
} }
@@ -80,6 +85,8 @@ pipeline {
stage('Build on server') { stage('Build on server') {
steps { steps {
script { script {
def BUILD_TIME_FRONT = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([ withCredentials([
usernamePassword( usernamePassword(
credentialsId: 'server-root-password', credentialsId: 'server-root-password',
@@ -88,19 +95,21 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH} cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
if [ -f 'docker-compose.yaml' ]; then # 1. СОЗДАЕМ JSON ФАЙЛ (ЭТО РАБОТАЕТ 100%)
docker-compose -f docker-compose.yaml build mkdir -p public
elif [ -f 'docker-compose.yml' ]; then echo '{\\"buildTime\\": \\"${BUILD_TIME_FRONT}\\"}' > public/build-info.json
docker-compose -f docker-compose.yml build
else # 2. Меняем имя контейнера и порт
echo 'Exception: docker-compose file not found' sed -i 's/container_name: no-copy-frontend\$/container_name: no-copy-frontend-${params.PORT}/' docker-compose.yml
exit 1 sed -i 's/\\\"2998:2999\\\"/\\\"${params.PORT}:2999\\\"/' docker-compose.yml
fi
" # 3. Собираем образ
""" docker-compose -f docker-compose.yml -p frontend-${params.PORT} build
"
"""
} }
} }
} }
@@ -117,24 +126,26 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH} cd /opt/deployments/frontend/${params.BRANCH}-${params.PORT}
# Проверяем и подключаем к сети app-network если нужно echo '=== Запускаем контейнер ==='
docker network inspect app-network 2>/dev/null || echo 'Warning: app-network not found' docker-compose -f docker-compose.yml -p frontend-${params.PORT} up -d
if [ -f 'docker-compose.yaml' ]; then sleep 10
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then echo '=== Проверяем запуск ==='
docker-compose -f docker-compose.yml up -d if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
fi echo '✓ Контейнер запущен: no-copy-frontend-${params.PORT}'
echo '✓ Порт: ${params.PORT}:2999'
sleep 10 else
echo '✗ Контейнер не запустился'
echo 'Status container:' echo 'Логи:'
docker ps --filter name=no-copy-frontend docker-compose -f docker-compose.yml -p frontend-${params.PORT} logs
" exit 1
""" fi
"
"""
} }
} }
} }
@@ -151,23 +162,29 @@ pipeline {
) )
]) { ]) {
sh """ sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 " sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then # Ищем контейнер с правильным именем (с портом)
echo 'Container started' if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend-${params.PORT}'; then
echo 'Container started: no-copy-frontend-${params.PORT}'
sleep 5
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP code: \$HTTP_CODE' HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} || echo '000')
echo 'HTTP code: \$HTTP_CODE'
echo 'Frontend available on http://92.242.61.23:2998'
else echo 'Frontend available on http://92.242.61.23:${params.PORT}'
echo 'Exception: container did not start'
docker ps -a | grep frontend # Показываем все фронтенды
exit 1 echo ''
fi echo 'All frontend containers:'
" docker ps --filter name=no-copy-frontend --format 'table {{.Names}}\\t{{.Ports}}\\t{{.Status}}'
""" else
echo 'Exception: container did not start'
docker ps -a | grep frontend
exit 1
fi
"
"""
} }
} }
} }
@@ -177,7 +194,8 @@ pipeline {
post { post {
success { success {
echo "Front branch ${params.BRANCH} deployment completed" echo "Front branch ${params.BRANCH} deployment completed"
echo "Frontend available on http://92.242.61.23:2998" echo "Frontend available on http://92.242.61.23:${params.PORT}"
echo "Container: no-copy-frontend-${params.PORT}"
} }
failure { failure {
echo "Deployment failed" echo "Deployment failed"
+76 -119
View File
@@ -7,7 +7,6 @@ pipeline {
defaultValue: 'dev', defaultValue: 'dev',
description: 'Ветка для деплоя' description: 'Ветка для деплоя'
) )
string( string(
name: 'SERVER', name: 'SERVER',
defaultValue: '92.242.61.23', defaultValue: '92.242.61.23',
@@ -18,6 +17,7 @@ pipeline {
stages { stages {
stage('Git pull') { stage('Git pull') {
steps { steps {
cleanWs()
script { script {
checkout([ checkout([
$class: 'GitSCM', $class: 'GitSCM',
@@ -34,130 +34,87 @@ pipeline {
stage('Deploy with docker-compose') { stage('Deploy with docker-compose') {
steps { steps {
script { script {
def buildTime = sh(script: "date '+%d-%m-%Y %H:%M:%S'", returnStdout: true).trim()
withCredentials([ withCredentials([
usernamePassword( usernamePassword(
credentialsId: 'server-root-password', credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER', usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS' passwordVariable: 'SSH_PASS'
) ),
string(credentialsId: 'DB_USER', variable: 'DB_USER'),
string(credentialsId: 'DB_PASSWORD', variable: 'DB_PASSWORD')
]) { ]) {
sh """ sh """
echo "Deploying branch: ${params.BRANCH}" echo "Deploying branch: ${params.BRANCH}"
echo "Copying files to server..." echo "Copying files to server..."
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}" sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "mkdir -p /opt/deployments/${params.BRANCH}"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/
sshpass -p '$SSH_PASS' rsync -av --delete \\
sshpass -p '$SSH_PASS' ssh $SSH_USER@$SERVER " --exclude=.git \\
cd /opt/deployments/${params.BRANCH} --exclude=.gradle \\
--exclude=build \\
echo 'Step 1: Checking current state...' --exclude=**/build \\
--exclude=.idea \\
# Смотрим что запущено --exclude=out \\
docker ps --filter 'name=postgres|app-backend' --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.Ports}}' . $SSH_USER@$SERVER:/opt/deployments/${params.BRANCH}/
echo 'Step 2: Force cleanup old application...' sshpass -p '$SSH_PASS' ssh -o StrictHostKeyChecking=no $SSH_USER@$SERVER "
cd /opt/deployments/${params.BRANCH}
# Принудительно удаляем старый контейнер и образ
docker stop app-backend 2>/dev/null || echo 'App not running' echo '1. Остановка старого приложения...'
docker rm app-backend 2>/dev/null || echo 'App not found' docker stop app-backend 2>/dev/null || echo 'Контейнер не найден'
docker rmi app-backend:latest 2>/dev/null || echo 'Image not found' docker rm app-backend 2>/dev/null || echo 'Контейнер не найден'
# Удаляем все образы app-backend echo '2. Удаление старых образов...'
docker images --filter 'reference=app-backend*' -q | xargs -r docker rmi 2>/dev/null || echo 'No images to remove' docker images --filter 'reference=*app*' -q | xargs -r docker rmi -f 2>/dev/null || echo 'Нет образов для удаления'
echo 'Step 3: Verify copied files...' echo '3. Создание сети если нужно...'
echo 'Files in directory:' docker network create app-network 2>/dev/null || echo 'Сеть уже существует'
ls -la
echo '' echo '4. Запуск инфраструктуры...'
echo 'Checking Java sources:' docker-compose up -d db storage
find . -name '*.java' | head -2
echo '5. Ожидание PostgreSQL...'
echo 'Step 4: Check if PostgreSQL is running...' sleep 10
if ! docker ps | grep -q postgres; then
echo 'Starting PostgreSQL...' echo '6. Сборка нового образа приложения...'
docker-compose up -d db docker build --no-cache -t app-backend:latest .
sleep 10
else echo '7. Запуск приложения...'
echo 'PostgreSQL already running' docker run -d \\
fi --name app-backend \\
--network app-network \\
echo 'Step 5: Build application with forced rebuild...' --network-alias app \\
echo 'Checking Dockerfile exists:' -p 80:8080 \\
ls -la Dockerfile 2>/dev/null || echo 'Dockerfile not found, creating simple one' -v uploads_data:/data/uploads:rw \\
-e POSTGRES_DB=no_copy_ \\
# Если нет Dockerfile, создаем простой -e POSTGRES_USER=$DB_USER \\
if [ ! -f Dockerfile ]; then -e POSTGRES_PASSWORD=$DB_PASSWORD \\
echo 'Creating simple Dockerfile...' -e POSTGRES_PORT=5432 \\
cat > Dockerfile << 'EOF' -e POSTGRES_HOST=db \\
FROM eclipse-temurin:21-jre -e BUILD_TIME_BACK='${buildTime}' \\
WORKDIR /app --restart unless-stopped \\
COPY . /app/ app-backend:latest
CMD ["java", "-jar", "app.jar"]
EOF echo '8. Запуск мониторинга...'
fi docker-compose up -d grafana prometheus loki tempo alloy
# Собираем с подробным выводом echo '9. Проверка...'
echo 'Building Docker image...' sleep 5
docker build --no-cache --progress=plain -t app-backend:latest . 2>&1 | tail -50
echo 'Статус контейнеров:'
echo 'Step 6: Verify new image...' docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Status}}'
echo 'Current app-backend images:'
docker images | grep app-backend echo '10. Проверка health...'
if curl -s -f http://localhost:80/health > /dev/null 2>&1; then
echo 'Image creation time:' echo 'Приложение работает'
docker inspect app-backend:latest --format='{{.Created}}' 2>/dev/null || echo 'Cannot inspect image' echo 'URL: http://${params.SERVER}:80'
else
echo 'Step 7: Starting application with alias...' echo 'Проверка health не удалась'
docker run -d \\ docker logs app-backend --tail=20
--name app-backend \\ fi
--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
"
""" """
} }
} }
@@ -177,4 +134,4 @@ EOF
echo "Deployment process finished" echo "Deployment process finished"
} }
} }
} }
@@ -1,183 +0,0 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-frontend 2>/dev/null || true
docker rm no-copy-frontend 2>/dev/null || true
cd /opt/deployments/frontend/${params.BRANCH} 2>/dev/null || mkdir -p /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml down 2>/dev/null || true
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml down 2>/dev/null || true
fi
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
rm -rf /opt/deployments/frontend/${params.BRANCH}/*
mkdir -p /opt/deployments/frontend/${params.BRANCH}
"
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/frontend/${params.BRANCH}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml build
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml build
else
echo 'Exception:don't found docker-compose'
exit 1
fi
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/frontend/${params.BRANCH}
if [ -f 'docker-compose.yaml' ]; then
docker-compose -f docker-compose.yaml up -d
elif [ -f 'docker-compose.yml' ]; then
docker-compose -f docker-compose.yml up -d
fi
sleep 10
echo 'Status container:'
docker ps --filter name=no-copy-frontend
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-frontend'; then
echo 'Container start'
sleep 5
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2998 || echo '000')
echo 'HTTP код: ' \$HTTP_CODE
echo 'Frong start on http://92.242.61.23:2998'
else
echo 'Exception: container don't start'
docker ps -a | grep frontend
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Front branch ${params.BRANCH} completed"
echo "Use on http://92.242.61.23:2998"
}
failure {
echo "Failed"
}
}
}
+156
View File
@@ -0,0 +1,156 @@
pipeline {
agent any
parameters {
string(
name: 'BRANCH',
defaultValue: 'main',
description: 'Ветка для деплоя легальной панели'
)
string(
name: 'PORT',
defaultValue: '2997',
description: 'Порт для запуска экземпляра'
)
}
stages {
stage('Git pull') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: params.BRANCH]],
userRemoteConfigs: [[
url: 'https://code.3err0.ru/frontdev/no-copy-legal-panel-frontend.git',
credentialsId: 'nx-jen'
]]
])
}
}
}
stage('Stop old') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker stop no-copy-legal-panel-${params.PORT} 2>/dev/null || true
docker rm no-copy-legal-panel-${params.PORT} 2>/dev/null || true
mkdir -p /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}
rm -rf /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}/*
"
"""
}
}
}
}
stage('Copy to server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' scp -r ./* $SSH_USER@92.242.61.23:/opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}/
"""
}
}
}
}
stage('Build on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
cd /opt/deployments/legal-panel/${params.BRANCH}-${params.PORT}
docker build -t no-copy-legal-panel:${params.BRANCH}-${params.PORT} .
"
"""
}
}
}
}
stage('Start on server') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
docker run -d \\
--name no-copy-legal-panel-${params.PORT} \\
--restart unless-stopped \\
--network app-network \\
-p ${params.PORT}:2997 \\
no-copy-legal-panel:${params.BRANCH}-${params.PORT}
sleep 5
docker ps --filter name=no-copy-legal-panel-${params.PORT}
"
"""
}
}
}
}
stage('Check status') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'server-root-password',
usernameVariable: 'SSH_USER',
passwordVariable: 'SSH_PASS'
)
]) {
sh """
sshpass -p '$SSH_PASS' ssh $SSH_USER@92.242.61.23 "
if docker ps --format '{{.Names}}' | grep -q 'no-copy-legal-panel-${params.PORT}'; then
HTTP_CODE=\$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${params.PORT} 2>/dev/null || echo '000')
echo 'Legal panel available on http://92.242.61.23:${params.PORT}'
else
exit 1
fi
"
"""
}
}
}
}
}
post {
success {
echo "Legal panel ${params.BRANCH} deployed on port ${params.PORT}"
}
failure {
echo "Deployment failed"
}
}
}
+19
View File
@@ -0,0 +1,19 @@
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'ncp_db') THEN
CREATE USER ncp_db WITH PASSWORD 'ncpDbApp';
END IF;
END
$$;
GRANT CONNECT ON DATABASE no_copy_ TO ncp_db;
GRANT USAGE ON SCHEMA public TO ncp_db;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ncp_db;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO ncp_db;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO ncp_db;
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
plugins {
kotlin("jvm") version "2.1.10"
}
group = "ru.soune"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-core-jvm:4.1.1")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
+5
View File
@@ -0,0 +1,5 @@
package ru.soune
fun main() {
println("Hello World!")
}
+13
View File
@@ -0,0 +1,13 @@
package ru.soune
interface Referral {
val userId: Long
val referralLink: String
val inviter: Long?
val currentLevel: String
val totalIncome: Int
val availableIncome: Int
val holdBalance: Int
val active: Boolean
}
@@ -0,0 +1,7 @@
package ru.soune
data class ReferralInvitee(
val email: String,
val isActive: Boolean,
val regDate: String,
)
+10
View File
@@ -0,0 +1,10 @@
package ru.soune
interface ReferralLevel {
val id: String
val name: String
val share: Int
val minInvitee: Int
val maxInvitee: Int
val next: String?
}
@@ -0,0 +1,11 @@
package ru.soune
interface ReferralLevelProvider {
fun getAvailableReferralLevels(): List<ReferralLevel>
fun getInitialReferralLevelId(): String
fun getReferralLevelById(levelId: String): ReferralLevel
}
+33
View File
@@ -0,0 +1,33 @@
package ru.soune
interface ReferralRepo {
fun getUserReferralLink(userId: Long): String
fun getUserIdByLink(link: String): Long?
fun getInviterIdForUser(userId: Long): Long?
fun getReferralLevelForUser(userId: Long): String
fun getReferralByUserId(userId: Long): Referral
fun getReferralInvitees(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee>
// создаем новую запись в БД
fun createReferralEntity(entity: Referral)
// прибавить к totalIncome и availableIncome значение transferAmount
fun increaseIncome(userId: Long, transferAmount: Int)
// установить active в true
fun activateUser(userId: Long): Boolean
// посчитать записи, у которых inviter == userId и active == true
fun getActiveInviteeForUser(userId: Long): Int
// посчитать записи, у которых inviter == userId
fun getTotalInviteeForUser(userId: Long): Int
fun upgradeReferralLevelForUser(userId: Long, newLevelId: String)
}
@@ -0,0 +1,68 @@
package ru.soune
class ReferralService(
private val referralLevelProvider: ReferralLevelProvider,
private val referralRepo: ReferralRepo,
) {
fun onRegister(userId: Long, inviterReferralLink: String?) {
val initialReferralLevel = referralLevelProvider.getInitialReferralLevelId()
val referralLink = referralRepo.getUserReferralLink(userId)
val inviter = inviterReferralLink?.let(referralRepo::getUserIdByLink)
val referral = object : Referral {
override val userId = userId
override val referralLink = referralLink
override val inviter = inviter
override val currentLevel = initialReferralLevel
override val totalIncome = 0
override val availableIncome = 0
override val holdBalance: Int = 0
override val active: Boolean = false
}
referralRepo.createReferralEntity(referral)
}
fun onUserAccountRefill(userId: Long, transferAmount: Int) {
val wasActivated = referralRepo.activateUser(userId)
val inviterId = referralRepo.getInviterIdForUser(userId) ?: return
val currentInviterLevel = referralRepo.getReferralLevelForUser(inviterId)
.let { referralLevelProvider.getReferralLevelById(it) }
val reward = transferAmount * currentInviterLevel.share / 100
referralRepo.increaseIncome(inviterId, reward)
if (!wasActivated) return
val currentCount = referralRepo.getActiveInviteeForUser(inviterId)
val nextLevel = currentInviterLevel.next
if (currentCount + 1 > currentInviterLevel.maxInvitee && nextLevel != null) {
referralRepo.upgradeReferralLevelForUser(inviterId, nextLevel)
}
}
fun getReferralLevels(): List<ReferralLevel> =
referralLevelProvider.getAvailableReferralLevels()
fun getReferralLevelForUser(userId: Long): ReferralLevel =
referralRepo.getReferralLevelForUser(userId)
.let { referralLevelProvider.getReferralLevelById(it) }
fun getReferralStatForUser(userId: Long): ReferralStat {
var referral = referralRepo.getReferralByUserId(userId)
return ReferralStat(
totalInvitee = referralRepo.getTotalInviteeForUser(userId),
activeInvitee = referralRepo.getActiveInviteeForUser(userId),
totalIncome = referral.totalIncome,
availableIncome = referral.availableIncome,
holdBalance = referral.holdBalance,
referralLink = referral.referralLink
)
}
fun getInviteeForUser(userId: Long, pageSize: Int, pageNumber: Int): List<ReferralInvitee> =
referralRepo.getReferralInvitees(userId, pageSize, pageNumber)
}
+10
View File
@@ -0,0 +1,10 @@
package ru.soune
data class ReferralStat(
val totalInvitee: Int,
val activeInvitee: Int,
val totalIncome: Int,
val availableIncome: Int,
val holdBalance: Int,
val referralLink: String,
)
+6
View File
@@ -1 +1,7 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
rootProject.name = 'no-copy' rootProject.name = 'no-copy'
include 'referral'
include 'finance'
@@ -2,8 +2,10 @@ package ru.soune.nocopy;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication @SpringBootApplication
@EnableScheduling
public class NoCopyApplication { public class NoCopyApplication {
public static void main(String[] args) { public static void main(String[] args) {
@@ -1,17 +1,51 @@
package ru.soune.nocopy.configuration; package ru.soune.nocopy.configuration;
import com.vrt.AudioFilePathProvider;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.audio.AudioLocalSearch;
import com.vrt.fileprotection.documents.DocumentLocalSearch;
import com.vrt.fileprotection.image.ImageLocalSearch;
import com.vrt.fileprotection.image.ImageUniqueCheck;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Collections;
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@AllArgsConstructor
public class ApplicationConfig { public class ApplicationConfig {
@Bean @Bean
PasswordEncoder passwordEncoder() { PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Bean
public com.vrt.
NoCopyFileService noCopyFileService(
FileProtector.FileProvider fileProvider,
FileProtector.ProcessingListener processingListener,
ImageUniqueCheck imageUniqueCheck,
ImageLocalSearch imageLocalSearch,
AudioLocalSearch audioLocalSearch,
AudioFilePathProvider audioFilePathProvider,
DocumentLocalSearch documentLocalSearch) {
return new com.vrt.NoCopyFileService(
Collections.emptyList(),
fileProvider,
processingListener,
imageUniqueCheck,
imageLocalSearch,
audioLocalSearch,
audioFilePathProvider,
documentLocalSearch
);
}
} }
@@ -16,7 +16,15 @@ public class HandlerConfig {
LoginRequestHandler login, LoginRequestHandler login,
FileUploadHandler upload, FileUploadHandler upload,
FileEntityHandler file, FileEntityHandler file,
LogoutRequestHandler logoutHandler LogoutRequestHandler logoutHandler,
ImageFoundRequestHandler imageFoundRequestHandler,
VerifyRegisterUserHandler verifyRegisterUser,
AuthRequestHandler authRequestHandler,
CompanyHandler companyHandler,
TariffHandler tariffHandler,
TariffInfoHandler tariffInfoHandler,
ReferralHandler referralHandler,
DaDataHandler daDataHandler
) { ) {
Map<Integer, RequestHandler> map = new HashMap<>(); Map<Integer, RequestHandler> map = new HashMap<>();
map.put(20001, login); map.put(20001, login);
@@ -24,6 +32,14 @@ public class HandlerConfig {
map.put(20004, upload); map.put(20004, upload);
map.put(20005, file); map.put(20005, file);
map.put(20006, logoutHandler); map.put(20006, logoutHandler);
map.put(20007, imageFoundRequestHandler);
map.put(20008, authRequestHandler);
map.put(20009, verifyRegisterUser);
map.put(30000, companyHandler);
map.put(30001, tariffHandler);
map.put(30002, tariffInfoHandler);
map.put(30003, referralHandler);
map.put(30004, daDataHandler);
return map; return map;
} }
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -20,7 +21,13 @@ public class JacksonConfig {
@Bean @Bean
@Primary @Primary
public ObjectMapper objectMapper() { public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.defaultViewInclusion(true)
.autoDetectFields(true)
.failOnUnknownProperties(false)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT)
.build();
JavaTimeModule javaTimeModule = new JavaTimeModule(); JavaTimeModule javaTimeModule = new JavaTimeModule();
@@ -35,6 +42,8 @@ public class JacksonConfig {
mapper.registerModule(javaTimeModule); mapper.registerModule(javaTimeModule);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 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; 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,25 @@
package ru.soune.nocopy.configuration.file;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@Configuration
@ConfigurationProperties(prefix = "file.storage")
@Data
public class FileStorageConfig {
private String basePath;
private long maxFileSize;
private int chunkSize;
private int authTokenLifeHours;
@PostConstruct
public void init() throws IOException {
Files.createDirectories(Paths.get(basePath));
}
}
@@ -0,0 +1,19 @@
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;
import ru.soune.nocopy.service.file.FileProcessingOrchestrator;
@Component
@AllArgsConstructor
public class NoCopyInitializer {
private final FileProcessingOrchestrator orchestrator;
@EventListener(ApplicationReadyEvent.class)
public void initializeOnStartup() {
orchestrator.initializeProcessingQueue();
}
}
@@ -1,9 +1,14 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import com.vrt.NoCopyFileService;
import com.vrt.fileprotection.FileProtector;
import com.vrt.fileprotection.NoCopyCheckResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.UrlResource; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -14,21 +19,26 @@ import org.springframework.web.multipart.MultipartFile;
import ru.soune.nocopy.dto.BaseRequest; import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse; import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode; import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.RegAnswer; import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.dto.file.ChunkUploadResponse; import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.dto.file.CompleteUploadResponse; import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.dto.file.UploadProgress; import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.AuthToken;
import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.entity.file.UploadStatus;
import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.*;
import ru.soune.nocopy.exception.ValidationException;
import ru.soune.nocopy.handler.*; import ru.soune.nocopy.handler.*;
import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.FileSimilarityService;
import ru.soune.nocopy.service.file.ProtectionsLimitService;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileUploadService; import ru.soune.nocopy.service.file.FileUploadService;
import ru.soune.nocopy.util.FileUtil;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -36,6 +46,7 @@ import java.nio.file.Paths;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -49,8 +60,22 @@ public class ApiController {
private final FileEntityService fileEntityService; private final FileEntityService fileEntityService;
private final AuthService authService;
private final FileSimilarityService fileSimilarityService;
private final FileEntityRepository fileEntityRepository;
private final NoCopyFileService noCopyFileService;
private final FileUtil fileUtil;
private final ProtectionsLimitService protectionsLimitService;
private final AuthTokenRepository authTokenRepository; private final AuthTokenRepository authTokenRepository;
private final CloudStorageService cloudStorageService;
@PostMapping("/v{version}/data") @PostMapping("/v{version}/data")
public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request, public ResponseEntity<?> handlePostRequest(@RequestBody BaseRequest request,
@PathVariable("version") int version) { @PathVariable("version") int version) {
@@ -72,6 +97,12 @@ public class ApiController {
return ResponseEntity.ok().body(response); return ResponseEntity.ok().body(response);
} catch (ValidationException e) { } catch (ValidationException e) {
return createValidationErrorResponse(e.getBindingResult(), e.getMsgId()); return createValidationErrorResponse(e.getBindingResult(), e.getMsgId());
} catch (UserNotFoundException e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.USER_NOT_FOUND.getCode(),
MessageCode.USER_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotFoundAuthToken e) {
return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>()));
} catch (NotValidFieldException e) { } catch (NotValidFieldException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
@@ -89,55 +120,84 @@ public class ApiController {
@PostMapping("/v{version}/files/chunk") @PostMapping("/v{version}/files/chunk")
public ResponseEntity<BaseResponse> uploadChunk( public ResponseEntity<BaseResponse> uploadChunk(
@PathVariable("version") int version, @PathVariable("version") int version,
@RequestParam("upload_id") String uploadId, @RequestParam(value = "upload_id", required = false) String uploadId,
@RequestParam("chunk_number") Integer chunkNumber, @RequestParam(value = "chunk_number", required = false) Integer chunkNumber,
@RequestParam("chunk") MultipartFile chunk) { @RequestParam(value = "chunk", required = false) MultipartFile chunk,
@RequestParam(value = "findSimilar", required = false, defaultValue = "0") Integer findSimilar) {
log.info("Uploading chunk {} for session {}, file size: {} bytes, version: {}",
chunkNumber, uploadId, chunk.getSize(), version);
if (chunk.isEmpty()) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Chunk file is empty", null));
}
if (uploadId == null || uploadId.isBlank()) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Upload ID is required", null));
}
if (chunkNumber == null || chunkNumber < 0) {
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.FILE_UPLOAD_ERROR.getCode(),
"Valid chunk number is required", null));
}
try { try {
fileUploadService.uploadChunk(uploadId, chunkNumber, chunk); 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");
}
ChunkUploadResponse responseBody = ChunkUploadResponse.builder() UploadProgressResponse uploadProgressResponse = fileUploadService.uploadChunk(uploadId, chunkNumber,
.uploadId(uploadId) chunk, findSimilar);
.chunkNumber(chunkNumber)
.chunkSize(chunk.getSize())
.message("Chunk uploaded successfully")
.build();
return ResponseEntity.ok().body(new BaseResponse(20004, MessageCode.SUCCESS.getCode(), return buildSuccessResponse(uploadId, chunkNumber, chunk,
"Chunk uploaded successfully", responseBody)); 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());
} catch (Exception e) { if (uploadId != null) {
log.error("Error uploading chunk", e); duplicateData.put("uploadId", uploadId);
}
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build();
return ResponseEntity.ok().body(new BaseResponse( return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.FILE_UPLOAD_ERROR.getCode(), 20004,
"Failed to upload chunk: " + e.getMessage(), responseBody)); MessageCode.DUPLICATE_FILE_UPLOAD.getCode(),
MessageCode.DUPLICATE_FILE_UPLOAD.getDescription(),
duplicateData
));
} catch (Exception e) {
log.error("Error uploading chunk", e);
return buildErrorResponse(uploadId, chunkNumber, "Failed to upload chunk: " + e.getMessage());
} }
} }
@GetMapping("/v{version}/files/{fileId}/similar")
public ResponseEntity<BaseResponse> findSimilarFiles(
@PathVariable("version") int version,
@PathVariable String fileId,
@RequestParam(value = "auth_token",required = false) String authToken,
@RequestParam(required = false) List<String> similarityLevels,
@PageableDefault(size = 20, sort = "hammingDistance") Pageable pageable) {
SimilarityFilter filter = SimilarityFilter.builder()
.similarityLevels(similarityLevels)
.build();
Page<SimilarFileDTO> similarFiles = fileSimilarityService.findSimilarFiles(fileId, filter, pageable, authToken);
String messageDesc;
MessageCode success;
if (similarFiles.isEmpty()) {
messageDesc = MessageCode.FILE_NOT_FOUND.getDescription();
success = MessageCode.SUCCESS;
} else {
messageDesc = MessageCode.SIMILAR_FILES_FOUND.getDescription();
success = MessageCode.SUCCESS;
}
Map<String, Object> responseData = new HashMap<>();
responseData.put("content", similarFiles.getContent());
responseData.put("page", similarFiles.getNumber());
responseData.put("size", similarFiles.getSize());
responseData.put("totalElements", similarFiles.getTotalElements());
responseData.put("totalPages", similarFiles.getTotalPages());
responseData.put("hasNext", similarFiles.hasNext());
responseData.put("hasPrevious", similarFiles.hasPrevious());
return ResponseEntity.ok()
.body(new BaseResponse(20004, success.getCode(), messageDesc, responseData));
}
@GetMapping("/v{version}/files/progress/{uploadId}") @GetMapping("/v{version}/files/progress/{uploadId}")
public ResponseEntity<BaseResponse> getUploadProgress( public ResponseEntity<BaseResponse> getUploadProgress(
@PathVariable("version") int version, @PathVariable("version") int version,
@@ -161,7 +221,6 @@ public class ApiController {
return ResponseEntity.ok().body(new BaseResponse( return ResponseEntity.ok().body(new BaseResponse(
20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody)); 20004, MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), responseBody));
} catch (Exception e) { } catch (Exception e) {
log.error("Error getting progress for upload: {}", uploadId, e); log.error("Error getting progress for upload: {}", uploadId, e);
@@ -235,116 +294,182 @@ public class ApiController {
} }
} }
@GetMapping("/v{version}/files/download/{fileId}") @GetMapping("/v{version}/files/download/{fileId}")
public ResponseEntity<Resource> downloadFile( public ResponseEntity<?> downloadFile(
@PathVariable String fileId, @PathVariable(required = false) String fileId,
@PathVariable Integer version, @PathVariable(required = false) Integer version,
@RequestHeader("Authorization") String tokenHeader) { @RequestHeader(value = "Authorization", required = false) String tokenHeader) {
try { try {
Long userId = getUserIdFromToken(tokenHeader); if (tokenHeader == null) {
FileEntityResponse fileInfo = fileEntityService.getById(fileId, version); Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
errorData.put("fileId", fileId);
errorData.put("version", version);
if (!fileInfo.getUserId().equals(userId)) { return ResponseEntity.ok().body(new BaseResponse(20004,
return ResponseEntity.status(403).build(); MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
errorData));
} }
if (!fileInfo.isExistsOnDisk()) { Long userId = authService.useUserAuthToken(tokenHeader);
return ResponseEntity.status(404) FileEntityResponse entityResponse = fileEntityService.getById(fileId, version);
.body(null);
if (!entityResponse.getUserId().equals(userId)) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
} }
Path filePath = Paths.get(fileInfo.getFilePath()); if (entityResponse.getStatus().equals(FileStatus.DELETED)) {
Resource resource = new UrlResource(filePath.toUri()); Map<String, Object> errorData = new HashMap<>();
errorData.put("file_status", entityResponse.getStatus());
if (!resource.exists()) { return ResponseEntity.ok().body(new BaseResponse(20004,
return ResponseEntity.status(404).build(); MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
if (!entityResponse.isExistsOnDisk()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("onDisk", entityResponse.isExistsOnDisk());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
}
Path filePath = Paths.get(entityResponse.getProtectedFilePath());
File file = cloudStorageService.readFileFromStorageByPath(
entityResponse.getProtectedFilePath());
long fileSize = Files.size(filePath);
if (!file.exists()) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("file", file.exists());
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR.getDescription(),
errorData));
} }
String contentType = determineContentType(filePath); String contentType = determineContentType(filePath);
return ResponseEntity.ok() return ResponseEntity.ok()
.contentLength(fileSize)
.contentType(MediaType.parseMediaType(contentType)) .contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileInfo.getOriginalFileName() + "\"") "attachment; filename=\"" + entityResponse.getFileName() + "\"")
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileInfo.getFileSize())) .body(file);
.body(resource); } catch (FileEntityNotFoundException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("fileId", fileId);
} catch (NotFoundAuthToken e) { return ResponseEntity.ok().body(new BaseResponse(20004,
return ResponseEntity.status(401).build(); MessageCode.FILE_NOT_FOUND.getCode(),
} catch (Exception e) { MessageCode.FILE_NOT_FOUND.getDescription(),
log.error("Error downloading file", e); errorData));
return ResponseEntity.status(500).build(); } catch (NotFoundAuthToken | IOException e) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("token", tokenHeader);
errorData.put("fileId", fileId);
errorData.put("version", version);
return ResponseEntity.ok().body(new BaseResponse(20004,
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getCode(),
MessageCode.FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD.getDescription(),
errorData));
} }
} }
@GetMapping("/v{version}/files/info/{fileId}") @GetMapping("/protect/{fileId}")
public ResponseEntity<FileEntityResponse> getFileInfo( public ResponseEntity<?> protect( @PathVariable(required = false) String fileId) {
@PathVariable String fileId, Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
@PathVariable Integer version,
@RequestHeader("Authorization") String tokenHeader) {
try { if (!optionalFileEntity.isPresent()) {
Long userId = getUserIdFromToken(tokenHeader); return ResponseEntity.notFound().build();
FileEntityResponse fileInfo = fileEntityService.getById(fileId, version);
if (!fileInfo.getUserId().equals(userId)) {
return ResponseEntity.status(403).build();
}
return ResponseEntity.ok(fileInfo);
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401).build();
} catch (Exception e) {
log.error("Error getting file info", e);
return ResponseEntity.status(500).build();
} }
FileEntity fileEntity = optionalFileEntity.get();
FileProtector.FileInfo fileInfo = fileUtil.createFileInfo(fileEntity);
noCopyFileService.addFile(fileInfo);
fileEntity.setProtectionStatus(ProtectionStatus.PROCESSING);
fileEntityRepository.save(fileEntity);
return ResponseEntity.ok().build();
} }
@GetMapping("/v{version}/files/preview/{fileId}") @GetMapping("/check/{fileId}/{type}")
public ResponseEntity<Resource> previewFile( public ResponseEntity<?> check(@PathVariable(required = false) String fileId,
@PathVariable String fileId, @PathVariable(required = false) String type) {
@PathVariable Integer version, Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);
@RequestHeader("Authorization") String tokenHeader) {
try { if (!optionalFileEntity.isPresent()) {
Long userId = getUserIdFromToken(tokenHeader); return ResponseEntity.notFound().build();
FileEntityResponse fileInfo = fileEntityService.getById(fileId, version);
if (!fileInfo.getUserId().equals(userId)) {
return ResponseEntity.status(403).build();
}
if (!isPreviewSupported(fileInfo.getMimeType())) {
return ResponseEntity.status(415)
.body(null);
}
Path filePath = Paths.get(fileInfo.getFilePath());
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.status(404).build();
}
String contentType = determineContentType(filePath);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + fileInfo.getOriginalFileName() + "\"")
.body(resource);
} catch (NotFoundAuthToken e) {
return ResponseEntity.status(401).build();
} catch (Exception e) {
log.error("Error previewing file", e);
return ResponseEntity.status(500).build();
} }
FileEntity fileEntity = optionalFileEntity.get();
Path path = Paths.get(fileEntity.getProtectedFilePath());
File file = path.toFile();
NoCopyCheckResult noCopyCheckResult = noCopyFileService.checkFile(file,
FileProtector.Type.valueOf(type.toUpperCase()));
return ResponseEntity.ok().body(noCopyCheckResult);
} }
@GetMapping("/check/file_stats")
public ResponseEntity<?> checkFileProtectStats(@RequestHeader("Authorization") String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found"));
Map<String, Object> fileTypeStats = protectionsLimitService.getFileTypeStats(authToken.getUser().getId());
return ResponseEntity.ok().body(fileTypeStats);
}
private ResponseEntity<BaseResponse> buildSuccessResponse(String uploadId, Integer chunkNumber, MultipartFile chunk,
String fileId) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.chunkSize(chunk.getSize())
.fileId(fileId)
.message("Chunk uploaded successfully")
.build();
return ResponseEntity.ok().body(new BaseResponse(
20000,
MessageCode.SUCCESS.getCode(),
"Chunk uploaded successfully",
responseBody));
}
private ResponseEntity<BaseResponse> buildErrorResponse(String uploadId, Integer chunkNumber, String errorMessage) {
ChunkUploadResponse responseBody = ChunkUploadResponse.builder()
.uploadId(uploadId)
.chunkNumber(chunkNumber)
.build();
return ResponseEntity.ok().body(new BaseResponse(
20004,
MessageCode.FILE_UPLOAD_ERROR.getCode(),
errorMessage,
responseBody));
}
private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) { private ResponseEntity<BaseResponse> createValidationErrorResponse(BindingResult bindingResult, Integer msgId) {
List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors() List<Map<String, String>> fieldErrors = bindingResult.getFieldErrors()
@@ -381,23 +506,4 @@ public class ApiController {
} }
return contentType; return contentType;
} }
private boolean isPreviewSupported(String mimeType) {
if (mimeType == null) {
return false;
}
return mimeType.startsWith("image/") ||
mimeType.startsWith("text/") ||
mimeType.equals("application/pdf") ||
mimeType.startsWith("video/") ||
mimeType.startsWith("audio/");
}
private Long getUserIdFromToken(String tokenHeader) {
String token = tokenHeader.replace("Bearer ", "");
AuthToken authToken = authTokenRepository.findByToken(token)
.orElseThrow(() -> new NotFoundAuthToken("Token not found"));
return authToken.getUser().getId();
}
} }
@@ -0,0 +1,97 @@
package ru.soune.nocopy.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.file.CheckStatus;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.entity.user.ProtectedFileCheck;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService;
import org.springframework.http.ContentDisposition;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
@RestController
@RequestMapping("/api/files")
@Slf4j
public class FileController {
@Autowired
private FileStorageService fileStorageService;
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private CheckCounterService checkCounterService;
@Autowired
private UserRepository userRepository;
@GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(
@PathVariable String fileId) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
ContentDisposition contentDisposition = ContentDisposition.inline()
.filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.body(resource);
}
@GetMapping("/protected/{fileId}")
public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getProtectedFilePath());
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
throw new RuntimeException("File is not protected");
}
return ResponseEntity.ok()
.contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
.header(HttpHeaders.CONTENT_DISPOSITION,
"inline; filename=\"" + fileEntity.getOriginalFileName() + "\"")
.body(resource);
}
@GetMapping("check-file/status/{userId}")
public ResponseEntity<CheckStatus> getStatus(@PathVariable Long userId) {
CheckStatus status = checkCounterService.getCurrentStatus(userId);
return ResponseEntity.ok(status);
}
@PostMapping("check-file/update-limit/{userId}/{limit}")
public ResponseEntity<ProtectedFileCheck> getStatus(@PathVariable Long userId, @PathVariable Integer limit) {
User user = userRepository.findById(userId).orElseThrow();
ProtectedFileCheck protectedFileCheck = checkCounterService.updateLimit(user, limit);
return ResponseEntity
.ok()
.body(protectedFileCheck);
}
}
@@ -1,8 +1,12 @@
package ru.soune.nocopy.controller; package ru.soune.nocopy.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController @RestController
@RequestMapping("check/api") @RequestMapping("check/api")
public class HealtCheckController { public class HealtCheckController {
@@ -11,4 +15,38 @@ public class HealtCheckController {
public HttpStatus healtCheck() { public HttpStatus healtCheck() {
return HttpStatus.OK; return HttpStatus.OK;
} }
@Value("${BUILD_TIME_BACK:unknown}")
private String buildTimeBack;
@GetMapping("/build")
public BuildInfo getBuildInfo() {
return new BuildInfo(buildTimeBack);
}
@GetMapping("/api/debug/memory")
public Map<String, String> getMemoryInfo() {
Runtime runtime = Runtime.getRuntime();
long mb = 1024 * 1024;
Map<String, String> info = new HashMap<>();
info.put("maxMemory (MB)", String.valueOf(runtime.maxMemory() / mb));
info.put("totalMemory (MB)", String.valueOf(runtime.totalMemory() / mb));
info.put("freeMemory (MB)", String.valueOf(runtime.freeMemory() / mb));
info.put("usedMemory (MB)", String.valueOf((runtime.totalMemory() - runtime.freeMemory()) / mb));
info.put("availableProcessors", String.valueOf(runtime.availableProcessors()));
info.put("JAVA_TOOL_OPTIONS", System.getenv("JAVA_TOOL_OPTIONS"));
info.put("JAVA_OPTS", System.getenv("JAVA_OPTS"));
return info;
}
static class BuildInfo {
public String buildTimeBack;
public BuildInfo(String buildTimeBack) {
this.buildTimeBack = buildTimeBack;
}
}
} }
@@ -1,11 +0,0 @@
package ru.soune.nocopy.controller;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("v1/api/content")
@AllArgsConstructor
public class UserContentController {
}
@@ -2,20 +2,34 @@ package ru.soune.nocopy.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import ru.soune.nocopy.dto.ChangePasswordRequest; import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.UserDTO; import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.UserRequest; import ru.soune.nocopy.dto.register.ChangePasswordRequest;
import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.dto.register.RegAnswer;
import ru.soune.nocopy.entity.User; import ru.soune.nocopy.dto.register.RegRequest;
import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.dto.user.UserDTO;
import ru.soune.nocopy.dto.user.UserRequest;
import ru.soune.nocopy.entity.tarif.Tariff;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.AuthToken;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.exception.InvalidUserEmail; import ru.soune.nocopy.exception.InvalidUserEmail;
import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.mapper.UserMapper; import ru.soune.nocopy.mapper.UserMapper;
import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.AuthTokenRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.UserService; import ru.soune.nocopy.service.file.FileStatsService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffService;
import ru.soune.nocopy.service.user.UserService;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
@RestController @RestController
@@ -31,12 +45,21 @@ public class UserController {
private final UserMapper userMapper; private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final AuthService authService;
private final TariffService tariffService;
private final FileStatsService fileStatsService;
@GetMapping("/all") @GetMapping("/all")
public ResponseEntity<List<UserDTO>> getAllUsers() { public ResponseEntity<List<UserDTO>> getAllUsers() {
List<UserDTO> allUsers = userRepository.findAll().stream() List<UserDTO> allUsers = userRepository.findAll().stream()
.map(u -> new UserDTO(u.getFullName(), u.getCompany(), u.getEmail(), u.getIsActive(), .map(u -> new UserDTO(u.getFullName(), u.getCompanyName(), u.getEmail(), u.isActive(),
u.getPhone(), u.getGenderType(), u.getPhone(), u.getGenderType(),
u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType())) u.getBirthday(), u.getCreatedAt(), u.getSubscriptionType(), tariffService.getAllTariffs(),
null, null))
.toList(); .toList();
return ResponseEntity.ok(allUsers); return ResponseEntity.ok(allUsers);
@@ -49,26 +72,69 @@ public class UserController {
Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token); Optional<AuthToken> tokenOptional = authTokenRepository.findByToken(token);
AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token: " + token + "not found")); if (tokenOptional.isPresent()) {
User user = userRepository.findByEmail(email);
if (authToken != null) { if (user != null) {
Optional<User> user = userRepository.findByEmail(email); UserDTO userDTO = userMapper.toDTO(user);
//TODO add mapper
if (user.isPresent()) {
UserDTO userDTO = userMapper.toDTO(user.get());
userDTO.setEmail(email); userDTO.setEmail(email);
userDTO.setFullName(user.get().getFullName()); userDTO.setFullName(user.getFullName());
userDTO.setCompany(user.get().getCompany());
userDTO.setPhone(user.get().getPhone()); if (user.getCompany() != null) {
userDTO.setGenderType(user.get().getGenderType()); userDTO.setCompany(user.getCompany().getCompanyName());
userDTO.setBirthday(user.get().getBirthday()); }
userDTO.setCreatedAt(user.get().getCreatedAt());
userDTO.setSubscriptionType(user.get().getSubscriptionType()); if (user.canManageCompanySettings() || user.canLogin() && user.getCompany() == null) {
userDTO.setActive(user.get().getIsActive()); userDTO.setTariffs(tariffService.getTariffByAccountType(user));
Long fileOnDisk = user.canManageCompanySettings() ?
fileStatsService.calculateCompanyFileSize(user.getId()):
fileStatsService.calculateUserFileSize(user.getId());
Integer filesCount = user.canManageCompanySettings() ?
fileStatsService.filesCompanyCount(user.getId()):
fileStatsService.filesUserCount(user.getId());
TariffInfo personalTariffInfo;
if (user.getCompany() == null) {
personalTariffInfo = user.getPersonalTariffInfo();
} else {
personalTariffInfo = user.getCompany().getTariffInfo();
}
Tariff tariff = personalTariffInfo.getTariff();
TariffInfoDTO infoDTO = TariffInfoDTO.builder().tokens(personalTariffInfo.getTokens())
.id(personalTariffInfo.getId())
.tariffName(tariff.getName())
.status(personalTariffInfo.getStatus().name())
.tariffId(tariff.getId())
.currentFileCounts(filesCount)
.maxFileCounts(tariff.getMaxFilesCount())
.currentFileOnDisk(fileOnDisk)
.maxFileOnDisk(tariff.getDiskSize())
.startTariff(personalTariffInfo.getStartTariff())
.endTariff(personalTariffInfo.getEndTariff())
.tokens(personalTariffInfo.getTokens() + personalTariffInfo.getBoughtTokens())
.build();
userDTO.setTariffInfo(infoDTO);
}
userDTO.setPhone(user.getPhone());
userDTO.setGenderType(user.getGenderType());
userDTO.setBirthday(user.getBirthday());
userDTO.setCreatedAt(user.getCreatedAt());
userDTO.setSubscriptionType(user.getSubscriptionType());
userDTO.setActive(user.isActive());
userDTO.setPermission(user.getUserPermissions());
return ResponseEntity.ok(userDTO); return ResponseEntity.ok(userDTO);
} }
} else {
return ResponseEntity.notFound().build();
} }
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
@@ -108,4 +174,46 @@ public class UserController {
return ResponseEntity.ok(userService.updateUser(userRequest, user)); return ResponseEntity.ok(userService.updateUser(userRequest, user));
} }
@PostMapping("/create-user")
public ResponseEntity<User> addUser(@RequestBody RegRequest registerRequest) {
if (userRepository.existsByEmail(registerRequest.getEmail()) ||
userRepository.existsByPhone(registerRequest.getPhone())) {
RegAnswer regAnswer = new RegAnswer();
regAnswer.setFieldErrors(Arrays.asList(Map.of("email", registerRequest.getEmail())));
regAnswer.setFieldErrors(Arrays.asList(Map.of("phone", registerRequest.getPhone())));
throw new NotValidFieldException("User already exists with email:" + registerRequest.getEmail() + " or phone: " +
registerRequest.getPhone(), new BaseResponse(2,
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getCode(),
MessageCode.REG_EMAIL_OR_PHONE_EXISTS.getDescription(), regAnswer));
}
User user = new User();
user.setFullName(registerRequest.getFullName());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setActive(true);
user.setEmailVerified(true);
if (registerRequest.getPhone() != null) {
user.setPhone(registerRequest.getPhone());
}
User savedUser = userRepository.save(user);
AuthToken authToken = authService.generateAuthToken(savedUser);
authTokenRepository.save(authToken);
return ResponseEntity.ok().body(savedUser);
}
@DeleteMapping("/delete-user/{userId}")
public ResponseEntity<?> addUser(@PathVariable Long userId) {
userRepository.deleteById(userId);
return ResponseEntity.ok().body(Map.of("userId", userId, "deleted", "true"));
}
} }
@@ -1,10 +0,0 @@
package ru.soune.nocopy.dto;
import java.time.LocalDateTime;
public record AuthResponse (
boolean success,
String message,
String token,
LocalDateTime expiresAt
) {}
@@ -1,9 +1,11 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
@Data @Data
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseRequest { public class BaseRequest {
@JsonProperty("version") @JsonProperty("version")
Integer version; Integer version;
@@ -1,11 +1,13 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class BaseResponse { public class BaseResponse {
@JsonProperty("msg_id") @JsonProperty("msg_id")
private Integer msgId; private Integer msgId;
@@ -4,10 +4,19 @@ public enum MessageCode {
SUCCESS(0, "Operation successful"), SUCCESS(0, "Operation successful"),
REG_EMAIL_EXISTS(1, "Email already registered"), REG_EMAIL_EXISTS(1, "Email already registered"),
REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"), REG_EMAIL_OR_PHONE_EXISTS(1, "Email or phone already registered"),
REFERRAL_LINK_IS_NOT_EXIST(1, "Refferal link is not exist"),
INVALID_FIELD(2, "Invalid field"), INVALID_FIELD(2, "Invalid field"),
INVALID_TOKEN(2, "Invalid token"), INVALID_TOKEN(2, "Invalid token"),
TOKEN_IS_ALIVE(2, "Token is alive"),
INVALID_ACTION(2, "Invalid action"), INVALID_ACTION(2, "Invalid action"),
FILE_UPLOAD_ERROR(2, "File upload error"), FILE_UPLOAD_ERROR(2, "File upload error"),
DUPLICATE_FILE_UPLOAD(2, "Duplicate file upload"),
FILE_DOWNLOAD_ERROR(2, "File download error"),
USER_NOT_VERIFIED(2, "User not verified"),
PERMISSION_NOT_FOUND(2, "Permission not found"),
USER_NOT_FOUND(2, "User not found"),
FILE_DOWNLOAD_ERROR_NOT_CORRECT_FIELD(2, "Not correct field"),
IMAGE_FOUND_ERROR(2, "Image found error"),
INVALID_JSON_BODY(2, "Invalid fields in JSON object"), INVALID_JSON_BODY(2, "Invalid fields in JSON object"),
INCOMPLETE_UPLOAD(2, "Not load all chunks"), INCOMPLETE_UPLOAD(2, "Not load all chunks"),
MSG_ID_NOT_FOUND(4, "Message id not found"), MSG_ID_NOT_FOUND(4, "Message id not found"),
@@ -16,8 +25,24 @@ public enum MessageCode {
AUTH_EMAIL_NOT_FOUND(4, "Email not found"), AUTH_EMAIL_NOT_FOUND(4, "Email not found"),
AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "), AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "),
AUTH_TOKEN_MISMATCH(4, "Token mismatch"), AUTH_TOKEN_MISMATCH(4, "Token mismatch"),
AUTH_TOKEN_NOT_FOUND(4, "Token not found"),
FILE_NOT_FOUND(4, "File not found"), FILE_NOT_FOUND(4, "File not found"),
AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"); AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"),
SEND_EMAIL_EXCEPTION(2, "Send email exception"),
SIMILAR_FILES_FOUND(0, "Similar files found"),
FILE_IS_PROTECTED(0, "File is protected"),
FILE_IS_NOT_PROTECTED(0, "File is not protected"),
TARIFF_IS_ADD(0, "Tariff is added"),
TARIFF_IS_DELETED(0, "Tariff is deleted"),
TARIFF_IS_UPDATED(0, "Tariff is updated"),
TARIFF_IS_NOT_FOUND(0, "Tariff is not found"),
VALIDATION_ERROR(2, "Validation error"),
RESOURCE_NOT_FOUND(4, "Resource not found"),
INTERNAL_ERROR(4, "Internal server error"),
COMPANY_NOT_FOUND(4, "Company not found"),
COMPANY_ALREADY_EXISTS(2, "Company already exists"),
USER_LIMIT_IS_OVER(2, "Over user limits"),
ERROR_TARIFF_INFO(2, "Erorr with tariff info");
private final Integer code; private final Integer code;
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class TokenProcessRequest {
@JsonProperty("token")
private String token;
}
@@ -1,14 +0,0 @@
package ru.soune.nocopy.dto;
import lombok.Data;
@Data
public class UserContentDTO {
String fileName;
String originalFilename;
String fileType;
String fileExtension;
Long fileSize;
String filePath;
Integer protectionLevel;
}
@@ -1,17 +0,0 @@
package ru.soune.nocopy.dto;
import ru.soune.nocopy.entity.ContentStatus;
import ru.soune.nocopy.entity.file.FileType;
import java.time.LocalDateTime;
public record UserContentResponse(
Long id,
String filename,
String originalFilename,
FileType fileType,
long fileSize,
String filePath,
ContentStatus status,
LocalDateTime uploadDate
) {}
@@ -0,0 +1,23 @@
package ru.soune.nocopy.dto.company;
import lombok.Data;
import java.util.Map;
@Data
public class CompanyActionRequestDto {
private String action;
private String companyId;
private String companyName;
private Map<String, Object> companyData;
private Long userId;
private Map<String, Object> searchParams;
private PageableParams pageable;
@Data
public static class PageableParams {
private int page;
private int size;
private String sort;
private String direction;
}
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.company;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class CompanyResponseDto {
private String id;
private String companyName;
private String phone;
private String address;
private String email;
private LocalDateTime registerDate;
private LocalDateTime updateDate;
private long userCount;
}
@@ -0,0 +1,10 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataAddress {
private String value;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataData {
private String inn;
private DaDataAddress address;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataRequest {
@JsonProperty("inn")
private String inn;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class DaDataResponse {
@JsonProperty("companyName")
private String companyName;
@JsonProperty("inn")
private String inn;
@JsonProperty("address")
private String address;
}
@@ -0,0 +1,11 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataSuggestion {
private String value;
private DaDataData data;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.dadata;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DaDataWrapper {
private List<DaDataSuggestion> suggestions;
}
@@ -0,0 +1,25 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CheckIncrementResult {
private boolean success;
private Integer countChecked;
private Integer remainingLimit;
private String message;
public static CheckIncrementResult success(Integer countChecked) {
return CheckIncrementResult.builder()
.success(true)
.countChecked(countChecked)
.message("Check count incremented successfully")
.build();
}
}
@@ -0,0 +1,15 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class CheckStatus {
private Long userId;
private Integer countChecked;
private Integer remainingLimit;
private LocalDateTime lastCheckAt;
}
@@ -20,6 +20,9 @@ public class ChunkUploadResponse {
@JsonProperty("chunk_size") @JsonProperty("chunk_size")
private Long chunkSize; private Long chunkSize;
@JsonProperty("file_id")
private String fileId;
@JsonProperty("message") @JsonProperty("message")
private String message; private String message;
} }
@@ -1,41 +0,0 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileApiResponse<T> {
private boolean success;
private String message;
private T data;
private String error;
public static <T> FileApiResponse<T> success(T data) {
return FileApiResponse.<T>builder()
.success(true)
.message("Operation completed successfully")
.data(data)
.build();
}
public static <T> FileApiResponse<T> success(String message, T data) {
return FileApiResponse.<T>builder()
.success(true)
.message(message)
.data(data)
.build();
}
public static <T> FileApiResponse<T> error(String error) {
return FileApiResponse.<T>builder()
.success(false)
.error(error)
.build();
}
}
@@ -17,6 +17,9 @@ public class FileEntityRequest {
@JsonProperty("file_id") @JsonProperty("file_id")
private String fileId; private String fileId;
@JsonProperty("full_delete")
private Integer fullDelete;
@JsonProperty("upload_session_id") @JsonProperty("upload_session_id")
private String uploadSessionId; private String uploadSessionId;
@@ -19,6 +19,7 @@ public class FileEntityResponse {
private String originalFileName; private String originalFileName;
private String storedFileName; private String storedFileName;
private String filePath; private String filePath;
private String protectedFilePath;
private Long fileSize; private Long fileSize;
private String mimeType; private String mimeType;
private String fileExtension; private String fileExtension;
@@ -30,4 +31,13 @@ public class FileEntityResponse {
private String formattedSize; private String formattedSize;
private String downloadUrl; private String downloadUrl;
private boolean existsOnDisk; private boolean existsOnDisk;
private Integer supportId;
private String protectStatus;
private String ownerName;
private String ownerEmail;
private String ownerCompany;
private String fileName;
private String fileFormat;
private Integer checksCount;
private LocalDateTime fileUploadDate;
} }
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class FileExtensionResponse {
@JsonProperty("file_extension")
private List<String> extension;
@JsonProperty("count")
private Integer count;
@JsonProperty("max_file_size")
private Long maxFileSize;
}
@@ -0,0 +1,88 @@
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 FileInfoUserResponse {
@JsonProperty("all_files_size")
private Long allFileSize;
@JsonProperty("all_files_quantity")
private Integer fileCount;
@JsonProperty("all_files_check")
private Integer filesCheck;
@JsonProperty("all_files_violation")
private Integer filesViolation;
@JsonProperty("images_size")
private Long imagesSize;
@JsonProperty("images_quantity")
private Integer imagesCount;
@JsonProperty("images_check")
private Integer imagesCheck;
@JsonProperty("images_violations")
private Integer imagesViolations;
@JsonProperty("videos_size")
private Long videosSize;
@JsonProperty("videos_quantity")
private Integer videosCount;
@JsonProperty("videos_check")
private Integer videosCheck;
@JsonProperty("videos_violations")
private Integer videosViolations;
@JsonProperty("audios_size")
private Long audiosSize;
@JsonProperty("audios_quantity")
private Integer audiosCount;
@JsonProperty("audios_check")
private Integer audiosCheck;
@JsonProperty("audios_violations")
private Integer audiosViolations;
@JsonProperty("document_size")
private Long documentSize;
@JsonProperty("document_quantity")
private Integer documentCount;
@JsonProperty("document_check")
private Integer documentCheck;
@JsonProperty("document_violations")
private Integer documentViolations;
@JsonProperty("protected_document_files_count")
private Long protectedDocumentFilesCount;
@JsonProperty("protected_files_count")
private Long protectedFilesCount;
@JsonProperty("protected_audio_files_count")
private Long protectedAudioFilesCount;
@JsonProperty("protected_video_files_count")
private Long protectedVideoFilesCount;
@JsonProperty("protected_image_files_count")
private Long protectedImageFilesCount;
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileTypeStatsDto {
private String fileType;
private Integer count;
}
@@ -0,0 +1,68 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class GoogleVisionSearchResponse {
@JsonProperty("bestGuessLabels")
private List<BestGuessLabel> bestGuessLabels;
@JsonProperty("fullMatchingImages")
private List<ImageResult> fullMatchingImages;
@JsonProperty("visuallySimilarImages")
private List<ImageResult> visuallySimilarImages;
@JsonProperty("pagesWithMatchingImages")
private List<PageResult> pagesWithMatchingImages;
@JsonProperty("partialMatchingImages")
private List<ImageResult> partialMatchingImages;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BestGuessLabel {
@JsonProperty("label")
private String label;
@JsonProperty("languageCode")
private String languageCode;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult {
@JsonProperty("url")
private String url;
@JsonProperty("score")
private Float score;
@JsonProperty("height")
private Integer height;
@JsonProperty("width")
private Integer width;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PageResult {
@JsonProperty("url")
private String url;
@JsonProperty("pageTitle")
private String pageTitle;
@JsonProperty("fullMatchingImages")
private List<ImageResult> fullMatchingImages;
@JsonProperty("partialMatchingImages")
private List<ImageResult> partialMatchingImages;
}
}
@@ -0,0 +1,16 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class ImageSearchRequest {
@JsonProperty("file_id")
private String fileId;
@JsonProperty("page")
private Integer page = 1;
@JsonProperty("page_size")
private Integer pageSize = 10;
}
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PeriodHistoryDto {
private LocalDate periodStart;
private LocalDate periodEnd;
private Integer totalChecks;
private Map<String, Integer> checksByType;
}
@@ -0,0 +1,29 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProtectionLimitsDto {
private Long userId;
private String userName;
private String userEmail;
private LocalDate lastResetDate;
private Integer totalChecksAllTime;
private Integer periodChecksCount;
private Map<String, Integer> checksByFileType;
private LocalDate periodStartDate;
private LocalDate periodEndDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastCheckAt;
}
@@ -0,0 +1,22 @@
package ru.soune.nocopy.dto.file;
import lombok.Builder;
import lombok.Value;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import java.time.LocalDateTime;
@Value
@Builder
public class SimilarFileDTO {
String fileId;
String originalFileName;
Long fileSize;
Integer hammingDistance;
String similarityLevel;
Long ownerId;
Long supportId;
ProtectionStatus status;
LocalDateTime uploadDate;
String url;
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.dto.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SimilarityFilter {
private List<String> similarityLevels;
private Integer page;
private Integer size;
}
@@ -0,0 +1,49 @@
package ru.soune.nocopy.dto.file;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class YandexSearchResponse {
@JsonProperty("images")
private List<ImageResult> images;
// @JsonProperty("images")
// public void setImages(List<ImageResult> images) {
// this.images = images;
// }
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ImageResult {
@JsonProperty("url")
private String url;
@JsonProperty("pageUrl")
private String pageUrl;
@JsonProperty("pageTitle")
private String pageTitle;
@JsonProperty("width")
private Integer width;
@JsonProperty("height")
private Integer height;
@JsonProperty("host")
private String host;
}
private int page;
private int pageSize;
private int totalResults;
private int totalPages;
}
@@ -0,0 +1,18 @@
package ru.soune.nocopy.dto.referral;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ReferralRequest {
private String token;
private Integer amount;
private Integer pageSize;
private Integer pageNumber;
private String action;
}
@@ -0,0 +1,5 @@
package ru.soune.nocopy.dto.register;
public enum AccountType {
B2B, B2C
}
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -13,5 +13,9 @@ import java.util.Map;
public class LoginAnswer { public class LoginAnswer {
private String Token; private String Token;
private boolean isActive;
private boolean isVerified;
private List<Map<String, String>> fieldErrors; private List<Map<String, String>> fieldErrors;
} }
@@ -1,5 +1,6 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@@ -10,6 +11,7 @@ import lombok.NoArgsConstructor;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class LoginRequest { public class LoginRequest {
@NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128) @NotBlank(message = "error.not.blank") @Email(message = "error.not.email") @Size(max = 128)
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -13,5 +13,13 @@ import java.util.Map;
public class RegAnswer { public class RegAnswer {
private String Token; private String Token;
private boolean isActive;
private boolean isVerified;
private Long userId;
private String email;
private List<Map<String, String>> fieldErrors; private List<Map<String, String>> fieldErrors;
} }
@@ -1,16 +1,21 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import lombok.Data; import lombok.Data;
@Data @Data
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class RegRequest { public class RegRequest {
@NotEmpty(message = "Full name is required") @NotEmpty(message = "Full name is required")
private String fullName; private String fullName;
private String companyName; private String companyName;
@NotEmpty(message = "Need account type")
private String accountType;
@Size(min = 11, max = 14, message = "Phone must be 11-14 digits") @Size(min = 11, max = 14, message = "Phone must be 11-14 digits")
private String phone; private String phone;
@@ -22,4 +27,8 @@ public class RegRequest {
@NotBlank(message = "Password is required") @NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters") @Size(min = 8, message = "Password must be at least 8 characters")
private String password; private String password;
private String authToken;
private String referralLink;
} }
@@ -0,0 +1,20 @@
package ru.soune.nocopy.dto.register;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VerifyUserRequest {
@JsonProperty("verify_token")
private String verifyToken;
@JsonProperty("user_id")
private Long userId;
@JsonProperty("resend")
private Integer resend;
}
@@ -0,0 +1,19 @@
package ru.soune.nocopy.dto.tarriff;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TariffDTO {
private Long id;
private String type;
private String name;
private double price;
private int tokens;
private int maxFilesCount;
private Long diskSize;
private Long maxUsers;
}
@@ -0,0 +1,26 @@
package ru.soune.nocopy.dto.tarriff;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TariffInfoDTO {
private String id;
private String status;
private LocalDateTime startTariff;
private LocalDateTime endTariff;
private Long tariffId;
private String tariffName;
private Integer tokens;
private Long maxFileOnDisk;
private Long currentFileOnDisk;
private Integer currentFileCounts;
private Integer maxFileCounts;
}
@@ -0,0 +1,35 @@
package ru.soune.nocopy.dto.tarriff;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TariffInfoRequest {
private String id;
@NotNull(message = "Action is required")
@JsonProperty("action")
private String action;
@JsonProperty("status")
private String status;
@JsonProperty("start_tariff")
private LocalDateTime startTariff;
@JsonProperty("end_tariff")
private LocalDateTime endTariff;
@JsonProperty("tariff_id")
private Long tariffId;
@JsonProperty("tariff_type")
private String tariffType;
}
@@ -0,0 +1,33 @@
package ru.soune.nocopy.dto.tarriff;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TariffInfoResponse {
@JsonProperty("id")
private String id;
@JsonProperty("status")
private String status;
@JsonProperty("start_tariff")
private LocalDateTime startTariff;
@JsonProperty("end_tariff")
private LocalDateTime endTariff;
@JsonProperty("tariff_id")
private Long tariffId;
@JsonProperty("tariff_name")
private String tariffName;
}
@@ -0,0 +1,52 @@
package ru.soune.nocopy.dto.tarriff;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TariffRequest {
private Long id;
@NotNull
@NotBlank
@JsonProperty("type")
private String type;
@NotNull
@NotBlank
@JsonProperty("tariff_name")
private String name;
@Min(value = 0, message = "Price cannot be negative")
@JsonProperty("tariff_price")
private double price;
@Min(value = 0, message = "Tokens must be at least 0")
@JsonProperty("tariff_tokens")
private int tokens;
@Min(value = 0, message = "Max files count must be at least 0")
@JsonProperty("max_files")
private int maxFilesCount;
@Min(value = 0, message = "Disk size must be at least 0")
@JsonProperty("disk_size")
private int diskSize;
@Min(value = 0, message = "Max users must be at least 0")
@JsonProperty("max_users")
private int maxUsers;
@JsonProperty("action")
private String action;
@JsonProperty("user_token")
private String userToken;
}
@@ -0,0 +1,12 @@
package ru.soune.nocopy.dto.tarriff;
import lombok.Builder;
import lombok.Data;
import ru.soune.nocopy.entity.tarif.Tariff;
@Data
@Builder
public class TariffResponse {
private String tariffName;
private String tariffType;
}
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.user;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@@ -1,4 +1,4 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.user;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@@ -1,14 +1,17 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.user;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import ru.soune.nocopy.entity.GenderType; import ru.soune.nocopy.dto.tarriff.TariffDTO;
import ru.soune.nocopy.entity.SubscriptionType; import ru.soune.nocopy.dto.tarriff.TariffInfoDTO;
import ru.soune.nocopy.entity.user.GenderType;
import ru.soune.nocopy.entity.user.SubscriptionType;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@@ -24,4 +27,7 @@ public class UserDTO {
private LocalDate birthday; private LocalDate birthday;
private LocalDateTime createdAt; private LocalDateTime createdAt;
private SubscriptionType subscriptionType; private SubscriptionType subscriptionType;
private List<TariffDTO> tariffs;
private TariffInfoDTO tariffInfo;
private Long permission;
} }
@@ -1,15 +1,16 @@
package ru.soune.nocopy.dto; package ru.soune.nocopy.dto.user;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import ru.soune.nocopy.entity.GenderType; import ru.soune.nocopy.entity.user.GenderType;
import java.time.LocalDate; import java.time.LocalDate;
@Data @Data
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public class UserRequest { public class UserRequest {
@Size(min = 2) @Size(min = 2)
private String fullName; private String fullName;
@@ -1,49 +0,0 @@
package ru.soune.nocopy.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "image_protection")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class ImageProtection {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long protectionId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "content_id", nullable = false)
@ToString.Exclude
private UserContent content;
@Column(name = "protection_method", nullable = false, length = 50)
private String protectionMethod;
@Column(name = "protection_level", nullable = false)
private Integer protectionLevel;
@Column(name = "is_active", nullable = false)
private Boolean isActive = true;
@CreatedDate
@Column(name = "applied_at", nullable = false, updatable = false)
private LocalDateTime appliedAt;
@Column(name = "metadata", columnDefinition = "JSON")
private String metadata;
}
@@ -1,44 +0,0 @@
package ru.soune.nocopy.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@Table(name = "user_actions")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
public class UserAction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long actionId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@ToString.Exclude
private User user;
@Column(name = "action", nullable = false, length = 100)
private String action;
@Column(name = "details", columnDefinition = "TEXT")
private String details;
@Column(name = "ip_address", length = 45)
private String ipAddress;
@Column(name = "user_agent", columnDefinition = "TEXT")
private String userAgent;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
}
@@ -0,0 +1,54 @@
package ru.soune.nocopy.entity.company;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import ru.soune.nocopy.entity.tarif.TariffInfo;
import ru.soune.nocopy.entity.user.User;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter @Setter
@Table(name = "company")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(name = "company_name")
private String companyName;
@Column(name = "address")
private String address;
@Column(name = "phone")
private String phone;
@CreatedDate
@Column(name = "register_date", updatable = false, nullable = false)
private LocalDateTime registerDate = LocalDateTime.now();
@Column(name = "update_date")
private LocalDateTime updateDate;
@Size(max = 1024)
@Column(name = "email", length = 1024)
private String email;
@OneToMany(mappedBy = "company")
@JsonIgnore
@ToString.Exclude
private List<User> users = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "tariff_info_id")
private TariffInfo tariffInfo;
}
@@ -1,4 +1,4 @@
package ru.soune.nocopy.entity; package ru.soune.nocopy.entity.file;
import lombok.Getter; import lombok.Getter;
@@ -1,5 +1,6 @@
package ru.soune.nocopy.entity.file; package ru.soune.nocopy.entity.file;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
@@ -16,11 +17,13 @@ import java.time.LocalDateTime;
@Table(name = "file_entities") @Table(name = "file_entities")
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
public class FileEntity { public class FileEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
private String id; private String id;
@Column(name = "support_id", unique = true, updatable = false)
private Integer supportId;
@Column(name = "user_id", nullable = false) @Column(name = "user_id", nullable = false)
private Long userId; private Long userId;
@@ -60,11 +63,34 @@ public class FileEntity {
@Column(name = "updated_at") @Column(name = "updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@Column(name = "protected_file_path")
private String protectedFilePath;
@Column(name = "protection_status")
@Enumerated(EnumType.STRING)
private ProtectionStatus protectionStatus;
@Column(name = "protected_at")
private LocalDateTime protectedAt;
@Column(name = "signature")
@JsonIgnore
private String signature;
@OneToOne(mappedBy = "file", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private ImageHashEntity imageHash;
@PrePersist @PrePersist
public void prePersist() { public void prePersist() {
if (this.status == null) { if (this.status == null) {
this.status = FileStatus.ACTIVE; this.status = FileStatus.ACTIVE;
} }
if (this.protectionStatus == null) {
this.protectionStatus = ProtectionStatus.NOT_PROTECTED;
}
if (this.createdAt == null) { if (this.createdAt == null) {
this.createdAt = LocalDateTime.now(); this.createdAt = LocalDateTime.now();
} }
@@ -4,5 +4,8 @@ public enum FileStatus {
ACTIVE, ACTIVE,
DELETED, DELETED,
PROCESSING, PROCESSING,
ERROR VIOLATION,
CHECKED,
ERROR,
TEMP
} }
@@ -2,20 +2,35 @@ package ru.soune.nocopy.entity.file;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays;
import java.util.List;
@Getter @Getter
public enum FileType { public enum FileType {
PHOTO("photo"), IMAGE("image", Arrays.asList("jpg", "jpeg", "png", "gif", "bmp")),
VIDEO("video"), VIDEO("video", Arrays.asList("mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpg", "mpeg",
AUDIO("audio"), "3gp", "3g2", "f4v", "m2ts", "mts", "vob", "ogv", "divx")),
DOCUMENT("document"); AUDIO("audio", List.of("wav")),
DOCUMENT("document", Arrays.asList("pdf"));
private final String code;
FileType(String code) { private final String displayName;
this.code = code; private final List<String> allowedExtensions;
FileType(String displayName, List<String> allowedExtensions) {
this.displayName = displayName;
this.allowedExtensions = allowedExtensions;
} }
public String getCode() { public String getDisplayName() {
return code; return displayName;
}
public List<String> getAllowedExtensions() {
return allowedExtensions;
}
public boolean supportsExtension(String extension) {
return allowedExtensions.contains(extension.toLowerCase());
} }
} }

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