Compare commits

...

97 Commits

Author SHA1 Message Date
Alex Ant 6ba0924f6c chore(release): publish
- coopback@1.7.37-alpha.2
 - coopdoc-generator-ts@1.0.11-alpha.2
 - cooptypes@1.0.22-alpha.6
 - coop-notificator@0.0.28-alpha.1
 - terminal@0.1.41-alpha.1
2024-09-16 11:44:22 +05:00
Alex Ant 00c75d1fd8 update 2024-09-16 11:43:37 +05:00
Alex Ant 93f28fa9d5 update 2024-09-16 11:38:25 +05:00
Alex Ant 0aed10eb8b chore(release): publish
- coopback@1.7.37-alpha.1
 - coopdoc-generator-ts@1.0.11-alpha.1
 - cooptypes@1.0.22-alpha.6
 - coop-notificator@0.0.28-alpha.0
 - terminal@0.1.41-alpha.0
2024-09-16 11:37:13 +05:00
Alex Ant d84d687eee update 2024-09-16 11:36:50 +05:00
Alex Ant d046f56832 update 2024-09-16 11:34:07 +05:00
Alex Ant 0af1fbf313 update 2024-09-16 11:24:50 +05:00
Alex Ant e9aaf4309e chore(release): publish
- cooparser-ts@1.0.20-alpha.0
 - coopback@1.7.37-alpha.0
 - coopdoc-generator-ts@1.0.11-alpha.0
 - cooptypes@1.0.22-alpha.6
 - coop-notificator@0.0.27
 - terminal@0.1.40
2024-09-16 11:19:45 +05:00
Alex Ant 80fa1085d2 FUCK 2024-09-16 11:13:22 +05:00
Alex Ant 88195291fd update 2024-09-16 11:09:40 +05:00
Alex Ant 39ad600d00 update 2024-09-16 10:58:59 +05:00
Alex Ant c63a3aba6b update 2024-09-16 10:41:15 +05:00
Alex Ant 782ecbe307 dfdf 2024-09-16 10:36:54 +05:00
Alex Ant f75bbfa048 fix 2024-09-16 10:15:16 +05:00
Alex Ant 3a772cc983 herna 2024-09-16 10:00:29 +05:00
Alex Ant 807113523e documents update 2024-09-16 09:52:00 +05:00
Alex Ant 7df2af8611 update 2024-09-15 17:28:33 +05:00
Alex Ant 10c1697b00 update 2024-09-15 17:23:58 +05:00
Alex Ant 742e5d38ab chore(release): publish
- coopback@1.7.35-alpha.5
 - coopdoc-generator-ts@1.0.79-alpha.5
 - cooptypes@1.0.22-alpha.5
 - coop-notificator@0.0.26-alpha.5
 - terminal@0.1.39-alpha.5
2024-09-15 17:20:44 +05:00
Alex Ant 1d3e7f8af0 update 2024-09-15 17:19:13 +05:00
Alex Ant c99bacab64 before update 2024-09-15 10:40:18 +05:00
Alex Ant 95d7555ebc ssr & covars -> vars 2024-09-15 09:38:02 +05:00
Alex Ant 5854e0a23c chore(release): publish
- coopback@1.7.35-alpha.4
 - coopdoc-generator-ts@1.0.79-alpha.4
 - cooptypes@1.0.22-alpha.4
 - coop-notificator@0.0.26-alpha.4
 - terminal@0.1.39-alpha.4
2024-09-14 17:02:55 +05:00
Alex Ant 81f7f32767 fix 2024-09-14 17:02:33 +05:00
Alex Ant c0374a7da1 chore(release): publish
- coopback@1.7.35-alpha.3
 - coopdoc-generator-ts@1.0.79-alpha.3
 - cooptypes@1.0.22-alpha.3
 - coop-notificator@0.0.26-alpha.3
 - terminal@0.1.39-alpha.3
2024-09-14 16:44:32 +05:00
Alex Ant ed65e655b9 update 2024-09-14 16:43:06 +05:00
Alex Ant d250e516b5 update document models 2024-09-14 12:43:06 +05:00
Alex Ant e4f404b2c7 documents update 2024-09-13 20:41:00 +05:00
Alex Ant 86571331f8 update registry 2024-09-12 15:05:11 +05:00
Alex Ant 163d420af9 request agreements 2024-09-11 16:12:05 +05:00
Alex Ant 4925ea4af0 chore(release): publish
- cooparser-ts@1.0.19-alpha.1
 - coopback@1.7.35-alpha.2
 - coopdoc-generator-ts@1.0.79-alpha.2
 - cooptypes@1.0.22-alpha.2
 - coop-notificator@0.0.26-alpha.2
 - terminal@0.1.39-alpha.2
2024-09-11 10:04:46 +05:00
Alex Ant 57b05ce348 registration agreements 2024-09-11 10:01:09 +05:00
Alex Ant 427b53fcbd chore(release): publish
- coopback@1.7.35-alpha.1
 - coopdoc-generator-ts@1.0.79-alpha.1
 - cooptypes@1.0.22-alpha.1
 - coop-notificator@0.0.26-alpha.1
 - terminal@0.1.39-alpha.1
2024-09-10 10:06:12 +05:00
Alex Ant 2ac7a31be6 промежуточный результат добавления кооперативных соглашений 2024-09-10 10:05:17 +05:00
Alex Ant 4baed6409f chore(release): publish
- cooparser-ts@1.0.19-alpha.0
 - coopback@1.7.35-alpha.0
 - coopdoc-generator-ts@1.0.79-alpha.0
 - cooptypes@1.0.22-alpha.0
 - coop-notificator@0.0.26-alpha.0
 - terminal@0.1.39-alpha.0
2024-09-06 16:39:34 +05:00
Alex Ant 2ea0ff0965 big update 2024-09-06 16:29:18 +05:00
Alex Ant 88b9a7edce small letters 2024-09-05 19:27:23 +05:00
Alex Ant 538066b960 ufff template or Template ??? always should be small letters 2024-09-05 19:25:37 +05:00
Alex Ant b10dd1d75a update generator with new type of document 2024-09-04 20:57:59 +05:00
Alex Ant f743580b55 fix signature collection name 2024-09-03 20:33:30 +05:00
Alex Ant 0688467302 update 2024-09-03 20:11:17 +05:00
Alex Ant 29e972dbf2 fix is_paid 2024-08-27 18:47:39 +05:00
Alex Ant bd99697c5b chore(release): publish
- coopback@1.7.34
 - coopdoc-generator-ts@1.0.78
 - cooptypes@1.0.21
 - coop-notificator@0.0.25
 - terminal@0.1.38
2024-08-25 11:39:35 +05:00
Alex Ant 1d5c431ff6 update types 2024-08-25 11:38:40 +05:00
Alex Ant 3736c510ff chore(release): publish
- cooparser-ts@1.0.18
 - coopback@1.7.33
 - coopdoc-generator-ts@1.0.77
 - terminal@0.1.37
2024-08-25 11:27:44 +05:00
Alex Ant 26c12c3a85 update 2024-08-25 11:25:28 +05:00
Alex Ant 10bb08e5e6 chore(release): publish
- coopback@1.7.32
 - coopdoc-generator-ts@1.0.76
 - terminal@0.1.36
2024-08-25 11:19:13 +05:00
Alex Ant b50d5b6121 publish 2024-08-25 11:07:57 +05:00
Alex Ant 38b28752e1 chore(release): publish
- coopback@1.7.31
 - coopdoc-generator-ts@1.0.75
 - cooptypes@1.0.20
 - coop-notificator@0.0.24
 - terminal@0.1.35
2024-08-25 11:02:44 +05:00
Alex Ant 87c51009de ProgramProvision | ЦПП Цифровой Кошелёк 2024-08-25 10:59:57 +05:00
Alex Ant a950a47b28 chore(release): publish
- coopback@1.7.31-alpha.1
 - coopdoc-generator-ts@1.0.75-alpha.1
 - cooptypes@1.0.20-alpha.1
 - coop-notificator@0.0.24-alpha.1
 - terminal@0.1.35-alpha.1
2024-08-23 10:38:04 +05:00
Alex Ant 2609d10c0b update 2024-08-23 10:35:48 +05:00
Alex Ant 7b4cb0f66c publish 2024-08-23 10:34:38 +05:00
Alex Ant 26db0e1e69 push 2024-08-21 15:57:02 +05:00
Alex Ant 62e64cbd22 upgrade 2024-08-21 14:43:43 +05:00
Alex Ant 844a47830f fix types 2024-08-21 13:11:37 +05:00
Alex Ant 2cc1e32f45 chore(release): publish
- coopback@1.7.31-alpha.0
 - coopdoc-generator-ts@1.0.75-alpha.0
 - cooptypes@1.0.20-alpha.0
 - coop-notificator@0.0.24-alpha.0
 - terminal@0.1.35-alpha.0
2024-08-21 12:53:38 +05:00
Alex Ant 9890958b1e update contracts interface 2024-08-21 12:50:44 +05:00
Alex Ant 6dbd6d6edc fix 2024-08-19 14:31:01 +05:00
Alex Ant bf7a4dfd28 путь установки 2024-08-19 14:20:38 +05:00
Alex Ant f52626bda0 Полное разделение форм ввода данных пользователя пригодное для использования в процессе инсталляции 2024-08-16 16:17:24 +05:00
Alex Ant 88cec4bf2b рефакторинг | вынос форм для ввода пользовательской информации в shared для дальнейшего использования в модуле установки 2024-08-15 20:30:01 +05:00
Alex Ant b880def108 fix 2024-08-15 12:39:19 +05:00
Alex Ant bfd5dc039a addUser в панели управления 2024-08-15 12:28:18 +05:00
Alex Ant c0d236dff3 chore(release): publish
- coopback@1.7.30
 - coopdoc-generator-ts@1.0.74
 - cooptypes@1.0.19
 - coop-notificator@0.0.23
 - terminal@0.1.34
2024-08-13 23:03:13 +05:00
Alex Ant 743d5fc306 разделение формы для ввода персональных данных с регистратором; зародыш формы добавления пайщика в реестр. 2024-08-13 23:00:09 +05:00
Alex Ant 2f51516163 step to install | works on add user by api 2024-08-13 15:21:23 +05:00
Alex Ant b606b46aa4 страница настроек с кнопками выхода для пайщика 2024-08-05 14:31:27 +05:00
Alex Ant 5b65ff99a0 chore(release): publish
- coopback@1.7.29
 - coopdoc-generator-ts@1.0.73
 - cooptypes@1.0.18
 - coop-notificator@0.0.22
 - terminal@0.1.33
2024-08-05 12:36:01 +05:00
Alex Ant 74a3f52324 update 2024-08-05 12:32:23 +05:00
Alex Ant 7c2a4fdfa8 удаление потерявшейся папки 2024-08-05 12:05:05 +05:00
Alex Ant fecee3a098 fix naming 2024-08-05 11:56:29 +05:00
Alex Ant 45f7111fb9 chore(release): publish
- coopback@1.7.29-alpha.0
 - coopdoc-generator-ts@1.0.73-alpha.0
 - cooptypes@1.0.18-alpha.0
 - coop-notificator@0.0.22-alpha.0
 - terminal@0.1.33-alpha.0
2024-08-05 11:51:37 +05:00
Alex Ant f2e23a8062 перевыпуск ключа доступа 2024-08-05 11:38:10 +05:00
Alex Ant 36a3631f5e login with signature 2024-08-02 17:11:01 +05:00
Alex Ant a758d19a9f обновление mongoose, интеграция eslint и активация typescript проверок 2024-08-01 14:06:01 +05:00
Alex Ant aac3e53a30 eslint repair 2024-08-01 13:06:28 +05:00
Alex Ant 85d87ee210 FIX !!! 2024-07-31 20:48:38 +05:00
Alex Ant 2f3ee80ed4 Merge branch 'main' into dev 2024-07-31 17:26:20 +05:00
Alex Ant 3632bea54e chore(release): publish
- terminal@0.1.32
2024-07-31 17:26:08 +05:00
Alex Ant 4a0521751a update 2024-07-31 17:24:44 +05:00
Alex Ant 7d6266c241 chore(release): publish
- terminal@0.1.32-alpha.0
2024-07-31 16:28:53 +05:00
Alex Ant 1d69b9ffea Рабочие столы v1 2024-07-31 16:24:50 +05:00
Alex Ant 72674cba10 chore(release): publish
- coopback@1.7.28
 - terminal@0.1.31
2024-07-26 18:42:47 +05:00
Alex Ant 495a8e2d1e chore(release): publish
- coopback@1.7.28-alpha.0
 - terminal@0.1.31-alpha.0
2024-07-26 18:41:33 +05:00
Alex Ant 95ae221d60 Добавление платежных реквизитов | удаление | просмотр | свайп в рамках страницы 2024-07-26 18:38:14 +05:00
Alex Ant 857f746da1 bug fix 2024-07-26 11:03:52 +05:00
Alex Ant 0e2e3052b1 works 2024-07-25 21:00:04 +05:00
Alex Ant 167c04cc3f commit 2024-07-25 20:47:42 +05:00
Alex Ant 043f51479a looking for bug #2 2024-07-25 20:30:56 +05:00
Alex Ant 921282df81 looking for bug 2024-07-25 20:20:44 +05:00
Alex Ant b3c641501c migration 2024-07-25 20:07:44 +05:00
Alex Ant 3608e7601f chore(release): publish
- cooparser-ts@1.0.17
 - coopback@1.7.27
 - coopdoc-generator-ts@1.0.72
 - cooptypes@1.0.17
 - coop-notificator@0.0.21
 - terminal@0.1.30
2024-07-25 19:41:18 +05:00
Alex Ant a75cfa1056 chore(release): publish
- coopback@1.7.27-alpha.5
 - terminal@0.1.30-alpha.4
2024-07-25 19:39:41 +05:00
Alex Ant f08c4102fd login with email 2024-07-25 19:37:13 +05:00
Alex Ant 9bfd7c7d9a chore(release): publish
- coopback@1.7.27-alpha.4
2024-07-25 18:24:15 +05:00
Alex Ant a359b4fbdb fix 2024-07-25 18:22:17 +05:00
506 changed files with 12649 additions and 14005 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules
dist
+14
View File
@@ -0,0 +1,14 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.tabCompletion": "onlySnippets",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib",
"i18n-ally.localesPaths": ["src/i18n"],
"typescript.format.enable": true,
"typescript.format.indentSwitchCase": false,
"typescript.validate.enable": true,
}
+10
View File
@@ -0,0 +1,10 @@
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install -g pnpm lerna
RUN pnpm install
RUN lerna run build
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.18](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.17...cooparser-ts@1.0.18) (2024-08-25)
**Note:** Version bump only for package cooparser-ts
## [1.0.17](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.17-alpha.2...cooparser-ts@1.0.17) (2024-07-25)
**Note:** Version bump only for package cooparser-ts
## [1.0.16](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.16-alpha.0...cooparser-ts@1.0.16) (2024-07-15)
**Note:** Version bump only for package cooparser-ts
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "pkg-placeholder",
"version": "1.0.17-alpha.2",
"version": "1.0.20-alpha.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pkg-placeholder",
"version": "1.0.17-alpha.2",
"version": "1.0.20-alpha.0",
"license": "MIT",
"devDependencies": {
"@antfu/eslint-config": "^2.16.0",
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "cooparser-ts",
"type": "module",
"version": "1.0.17-alpha.2",
"private": true,
"version": "1.0.20-alpha.0",
"private": false,
"packageManager": "pnpm@9.0.6",
"description": "",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
+19 -225
View File
@@ -69,9 +69,6 @@ importers:
esno:
specifier: ^4.7.0
version: 4.7.0
lint-staged:
specifier: ^15.2.2
version: 15.2.2
nodemon:
specifier: ^3.1.4
version: 3.1.4
@@ -1039,10 +1036,6 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-escapes@6.2.1:
resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==}
engines: {node: '>=14.16'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -1233,14 +1226,6 @@ packages:
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
engines: {node: '>=4'}
cli-cursor@4.0.0:
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
cli-truncate@4.0.0:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -1271,9 +1256,6 @@ packages:
colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
colorspace@1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
@@ -1281,10 +1263,6 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
commander@11.1.0:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
@@ -1506,9 +1484,6 @@ packages:
elliptic@6.5.4:
resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==}
emoji-regex@10.3.0:
resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1770,9 +1745,6 @@ packages:
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
@@ -1893,10 +1865,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.2.0:
resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==}
engines: {node: '>=18'}
get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
@@ -2083,14 +2051,6 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
is-fullwidth-code-point@5.0.0:
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
engines: {node: '>=18'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -2200,10 +2160,6 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lilconfig@3.0.0:
resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
engines: {node: '>=14'}
lilconfig@3.1.1:
resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
engines: {node: '>=14'}
@@ -2211,15 +2167,6 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lint-staged@15.2.2:
resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==}
engines: {node: '>=18.12.0'}
hasBin: true
listr2@8.0.1:
resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==}
engines: {node: '>=18.0.0'}
local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@@ -2257,10 +2204,6 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
log-update@6.0.0:
resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==}
engines: {node: '>=18'}
logform@2.6.0:
resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
engines: {node: '>= 12.0.0'}
@@ -2346,10 +2289,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
@@ -2546,10 +2485,6 @@ packages:
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
onetime@5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
onetime@6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
@@ -2682,11 +2617,6 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
pidtree@0.6.0:
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
engines: {node: '>=0.10'}
hasBin: true
pkg-types@1.1.0:
resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==}
@@ -2976,17 +2906,10 @@ packages:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
restore-cursor@4.0.0:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rfdc@1.3.1:
resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@@ -3085,9 +3008,6 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
@@ -3118,14 +3038,6 @@ packages:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
slice-ansi@7.1.0:
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
engines: {node: '>=18'}
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
@@ -3179,10 +3091,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string-width@7.1.0:
resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==}
engines: {node: '>=18'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -3558,10 +3466,6 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
wrap-ansi@9.0.0:
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
engines: {node: '>=18'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -3595,10 +3499,6 @@ packages:
resolution: {integrity: sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==}
engines: {node: ^14.17.0 || >=16.0.0}
yaml@2.3.4:
resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
engines: {node: '>= 14'}
yaml@2.4.2:
resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==}
engines: {node: '>= 14'}
@@ -3703,7 +3603,7 @@ snapshots:
'@babel/traverse': 7.24.1
'@babel/types': 7.24.0
convert-source-map: 2.0.0
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -3804,7 +3704,7 @@ snapshots:
'@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.24.4
'@babel/types': 7.24.0
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -4013,7 +3913,7 @@ snapshots:
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.1
@@ -4029,7 +3929,7 @@ snapshots:
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -4348,7 +4248,7 @@ snapshots:
'@typescript-eslint/type-utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.7.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
@@ -4366,7 +4266,7 @@ snapshots:
'@typescript-eslint/types': 7.7.1
'@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.7.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
optionalDependencies:
typescript: 5.4.5
@@ -4387,7 +4287,7 @@ snapshots:
dependencies:
'@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
'@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
@@ -4403,7 +4303,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
@@ -4418,7 +4318,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 7.7.1
'@typescript-eslint/visitor-keys': 7.7.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
@@ -4550,8 +4450,6 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-escapes@6.2.1: {}
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@@ -4762,15 +4660,6 @@ snapshots:
dependencies:
escape-string-regexp: 1.0.5
cli-cursor@4.0.0:
dependencies:
restore-cursor: 4.0.0
cli-truncate@4.0.0:
dependencies:
slice-ansi: 5.0.0
string-width: 7.1.0
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -4803,8 +4692,6 @@ snapshots:
colord@2.9.3: {}
colorette@2.0.20: {}
colorspace@1.1.4:
dependencies:
color: 3.2.1
@@ -4814,8 +4701,6 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
commander@11.1.0: {}
commander@7.2.0: {}
comment-parser@1.4.1: {}
@@ -4952,10 +4837,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.3.4:
dependencies:
ms: 2.1.2
debug@4.3.4(supports-color@5.5.0):
dependencies:
ms: 2.1.2
@@ -5040,8 +4921,6 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
emoji-regex@10.3.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -5185,7 +5064,7 @@ snapshots:
eslint-plugin-import-x@0.5.0(eslint@8.57.0)(typescript@5.4.5):
dependencies:
'@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
doctrine: 3.0.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@@ -5202,7 +5081,7 @@ snapshots:
'@es-joy/jsdoccomment': 0.42.0
are-docs-informative: 0.0.2
comment-parser: 1.4.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
escape-string-regexp: 4.0.0
eslint: 8.57.0
esquery: 1.5.0
@@ -5258,7 +5137,7 @@ snapshots:
eslint-plugin-toml@0.11.0(eslint@8.57.0):
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
eslint-compat-utils: 0.5.0(eslint@8.57.0)
lodash: 4.17.21
@@ -5322,7 +5201,7 @@ snapshots:
eslint-plugin-yml@1.14.0(eslint@8.57.0):
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
eslint-compat-utils: 0.5.0(eslint@8.57.0)
lodash: 4.17.21
@@ -5358,7 +5237,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
@@ -5420,8 +5299,6 @@ snapshots:
eventemitter3@4.0.7: {}
eventemitter3@5.0.1: {}
execa@8.0.1:
dependencies:
cross-spawn: 7.0.3
@@ -5587,8 +5464,6 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.2.0: {}
get-func-name@2.0.2: {}
get-intrinsic@1.2.4:
@@ -5760,7 +5635,7 @@ snapshots:
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -5801,12 +5676,6 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@4.0.0: {}
is-fullwidth-code-point@5.0.0:
dependencies:
get-east-asian-width: 1.2.0
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -5891,36 +5760,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lilconfig@3.0.0: {}
lilconfig@3.1.1: {}
lines-and-columns@1.2.4: {}
lint-staged@15.2.2:
dependencies:
chalk: 5.3.0
commander: 11.1.0
debug: 4.3.4
execa: 8.0.1
lilconfig: 3.0.0
listr2: 8.0.1
micromatch: 4.0.5
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.3.4
transitivePeerDependencies:
- supports-color
listr2@8.0.1:
dependencies:
cli-truncate: 4.0.0
colorette: 2.0.20
eventemitter3: 5.0.1
log-update: 6.0.0
rfdc: 1.3.1
wrap-ansi: 9.0.0
local-pkg@0.5.0:
dependencies:
mlly: 1.6.1
@@ -5952,14 +5795,6 @@ snapshots:
lodash@4.17.21: {}
log-update@6.0.0:
dependencies:
ansi-escapes: 6.2.1
cli-cursor: 4.0.0
slice-ansi: 7.1.0
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
logform@2.6.0:
dependencies:
'@colors/colors': 1.6.0
@@ -6023,7 +5858,7 @@ snapshots:
micromark@2.11.4:
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
parse-entities: 2.0.0
transitivePeerDependencies:
- supports-color
@@ -6041,8 +5876,6 @@ snapshots:
mime@1.6.0: {}
mimic-fn@2.1.0: {}
mimic-fn@4.0.0: {}
min-indent@1.0.1: {}
@@ -6210,10 +6043,6 @@ snapshots:
dependencies:
fn.name: 1.1.0
onetime@5.1.2:
dependencies:
mimic-fn: 2.1.0
onetime@6.0.0:
dependencies:
mimic-fn: 4.0.0
@@ -6329,8 +6158,6 @@ snapshots:
picomatch@4.0.2: {}
pidtree@0.6.0: {}
pkg-types@1.1.0:
dependencies:
confbox: 0.1.7
@@ -6601,15 +6428,8 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
restore-cursor@4.0.0:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
reusify@1.0.4: {}
rfdc@1.3.1: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
@@ -6742,8 +6562,6 @@ snapshots:
siginfo@2.0.0: {}
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
simple-git-hooks@2.11.1: {}
@@ -6764,16 +6582,6 @@ snapshots:
slash@5.1.0: {}
slice-ansi@5.0.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 4.0.0
slice-ansi@7.1.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 5.0.0
source-map-js@1.2.0: {}
sparse-bitfield@3.0.3:
@@ -6825,12 +6633,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
string-width@7.1.0:
dependencies:
emoji-regex: 10.3.0
get-east-asian-width: 1.2.0
strip-ansi: 7.1.0
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@@ -7087,7 +6889,7 @@ snapshots:
vite-node@1.5.2(@types/node@20.12.7):
dependencies:
cac: 6.7.14
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
pathe: 1.1.2
picocolors: 1.0.0
vite: 5.2.10(@types/node@20.12.7)
@@ -7119,7 +6921,7 @@ snapshots:
'@vitest/utils': 1.5.2
acorn-walk: 8.3.2
chai: 4.4.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
execa: 8.0.1
local-pkg: 0.5.0
magic-string: 0.30.10
@@ -7149,7 +6951,7 @@ snapshots:
vue-eslint-parser@9.4.2(eslint@8.57.0):
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
@@ -7219,12 +7021,6 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.0
wrap-ansi@9.0.0:
dependencies:
ansi-styles: 6.2.1
string-width: 7.1.0
strip-ansi: 7.1.0
wrappy@1.0.2: {}
ws@7.5.9: {}
@@ -7243,8 +7039,6 @@ snapshots:
lodash: 4.17.21
yaml: 2.4.2
yaml@2.3.4: {}
yaml@2.4.2: {}
yargs-parser@21.1.1: {}
@@ -14,6 +14,7 @@ export async function BlockParser(db: Database, reader: EosioShipReaderResolved)
setTimeout(() => process.exit(1), 10000)
})
blocks$.subscribe(async (block: IBlock) => {
// console.log('new block: ', block)
// process.stdout.write('\r') // Возврат каретки в начало строки
@@ -11,7 +11,6 @@ export class Database {
private sync: Collection | undefined
constructor() {
console.log('mongo2: ', mongoUri)
this.client = new MongoClient(mongoUri)
}
-1
View File
@@ -11,7 +11,6 @@ function getEnvVar(key: string): string {
return envVar
}
export const node_env = getEnvVar('NODE_ENV')
console.log('ENV: ', node_env)
export const eosioApi = getEnvVar('API')
export const shipApi = getEnvVar('SHIP')
export const mongoUri = `${getEnvVar('MONGO_EXPLORER_URI')}${node_env === 'test' ? '-test' : ''}`
-31
View File
@@ -1,31 +0,0 @@
{
"env": {
"node": true,
"jest": true
},
"extends": ["airbnb-base", "plugin:jest/recommended", "plugin:security/recommended", "plugin:prettier/recommended"],
"plugins": ["jest", "security", "prettier", "import"],
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"@typescript-eslint/no-explicit-any":"off",
"no-console": "error",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
"jest/expect-expect": "off",
"security/detect-object-injection": "off",
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
]
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"env": {
"node": true,
"jest": true,
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"plugins": ["@typescript-eslint", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"no-console": "warn",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
"jest/expect-expect": "off",
"security/detect-object-injection": "off",
"@typescript-eslint/no-unused-vars": "warn"
}
}
+4 -2
View File
@@ -1,6 +1,8 @@
{
"CodeGPT.apiKey": "OpenAI",
"cSpell.words": [
"subcode"
]
],
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
+64
View File
@@ -3,6 +3,70 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.34](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.33...coopback@1.7.34) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.33](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.32...coopback@1.7.33) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.32](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.31...coopback@1.7.32) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.31](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.31-alpha.1...coopback@1.7.31) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.30](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29...coopback@1.7.30) (2024-08-13)
**Note:** Version bump only for package coopback
## [1.7.29](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29-alpha.0...coopback@1.7.29) (2024-08-05)
**Note:** Version bump only for package coopback
## [1.7.28](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.28-alpha.0...coopback@1.7.28) (2024-07-26)
**Note:** Version bump only for package coopback
## [1.7.27](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.27-alpha.5...coopback@1.7.27) (2024-07-25)
**Note:** Version bump only for package coopback
## [1.7.26](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.26-alpha.0...coopback@1.7.26) (2024-07-15)
**Note:** Version bump only for package coopback
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "create-nodejs-express-app",
"version": "1.7.27-alpha.3",
"version": "1.7.37-alpha.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-nodejs-express-app",
"version": "1.7.27-alpha.3",
"version": "1.7.37-alpha.2",
"license": "MIT",
"dependencies": {
"@a2seven/yoo-checkout": "^1.1.4",
@@ -32,7 +32,7 @@
"jsonwebtoken": "^8.5.1",
"jspdf": "^2.5.1",
"moment": "^2.24.0",
"mongoose": "^5.7.7",
"mongoose": "^8.5.2",
"morgan": "^1.9.1",
"nodemailer": "^6.3.1",
"nunjucks": "^3.2.4",
@@ -54,7 +54,7 @@
"@types/express": "^4.17.21",
"@types/node": "^20.11.26",
"coveralls": "^3.0.7",
"eslint": "^7.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.29.1",
+8 -3
View File
@@ -1,6 +1,6 @@
{
"name": "coopback",
"version": "1.7.27-alpha.3",
"version": "1.7.37-alpha.2",
"description": "",
"private": true,
"bin": "bin/createNodejsApp.js",
@@ -8,6 +8,7 @@
"repository": "https://github.com/hagopj13/node-express-boilerplate.git",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
"types": "src/types/index.ts",
"engines": {
"node": ">=12.0.0"
},
@@ -28,6 +29,7 @@
"docker:dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up",
"docker:test": "docker-compose -f docker-compose.yml -f docker-compose.test.yml up",
"types": "ts-node scripts/types.ts",
"init": "ts-node scripts/init.ts",
"swagger": "ts-node scripts/swagger.ts",
"typecheck": "tsc --noEmit",
"docs-watch": "swagger-ui-watcher ./src/docs/swagger.json",
@@ -87,7 +89,7 @@
"jsonwebtoken": "^8.5.1",
"jspdf": "^2.5.1",
"moment": "^2.24.0",
"mongoose": "^5.7.7",
"mongoose": "^8.5.2",
"morgan": "^1.9.1",
"nodemailer": "^6.3.1",
"nunjucks": "^3.2.4",
@@ -106,13 +108,16 @@
"xss-clean": "^0.1.1"
},
"devDependencies": {
"@antfu/eslint-config": "^2.24.1",
"@redocly/cli": "^1.18.0",
"@types/express": "^4.17.21",
"@types/faker": "^6.6.9",
"@types/node": "^20.11.26",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^8.0.0",
"concurrently": "^8.2.2",
"coveralls": "^3.0.7",
"eslint": "^7.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.29.1",
@@ -1,13 +1,14 @@
import config from '../src/config/config';
import logger from '../src/config/logger';
import { User } from '../src/models';
import { tokenService } from '../src/services';
import userService from '../src/services/user.service';
import { userService } from '../src/services';
import mongoose from 'mongoose';
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Ошибка: Необходимо указать имя пользователя');
logger.error('Ошибка: Необходимо указать имя пользователя');
process.exit(1);
}
@@ -16,12 +17,11 @@ const [username] = args;
async function createServiceUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const user = await userService.createServiceUser(username);
const token = await tokenService.generateServiceAccessToken(user);
console.log('token:', token.access.token);
} catch (e: any) {
console.log('Ошибка: ', e.message);
logger.error('Ошибка: ', e.message);
}
process.exit(0);
+1 -1
View File
@@ -17,7 +17,7 @@ async function deleteUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
await userService.deleteUserById(username);
await userService.deleteUserByUsername(username);
console.log('Пользователь удалён: ', username);
} catch (e: any) {
+95
View File
@@ -0,0 +1,95 @@
import config from '../src/config/config';
import { User } from '../src/models';
import { tokenService } from '../src/services';
import { userService } from '../src/services';
import mongoose from 'mongoose';
import { connectGenerator } from '../src/services/document.service';
import crypto from 'crypto';
import bcryptjs from 'bcryptjs';
const { compare, hash } = bcryptjs;
async function init() {
try {
await connectGenerator();
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
await userService.createUser({
username: 'ant',
email: 'dacom.dark.sun@gmail.com',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
type: 'individual',
individual_data: {
first_name: 'Иван',
last_name: 'Иванов',
middle_name: 'Иванович',
birthdate: '2000-01-01',
phone: '+1234567890',
email: 'ivanov.ivan@example.com',
full_address: 'Переулок Иванов д. 1',
},
});
const user = await userService.getUserByUsername('ant');
user.status = 'active';
user.is_registered = true;
await user.save();
const voskhod = await userService.createUser({
username: 'voskhod',
email: 'chairman.voskhod@gmail.com',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
type: 'organization',
organization_data: {
is_cooperative: true,
type: 'coop',
short_name: '"ПК ТЕСТ-Восход"',
full_name: 'Потребительский Кооператив "ТЕСТ-ВОСХОД"',
represented_by: {
first_name: 'Иван',
last_name: 'Иванов',
middle_name: 'Иванович',
position: 'Председатель',
based_on: 'Решения общего собрания №1',
},
country: 'Russia',
city: 'Москва',
full_address: 'Переулок Правды, дом 1',
email: 'chairman.voskhod@gmail.com',
phone: '+77077770707',
details: {
inn: '1234567890',
ogrn: '1234567890123',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '1234567890123456',
bank_name: 'Sberbank',
details: {
bik: '123456789',
corr: '30101810400000000225',
kpp: '123456789',
},
},
},
});
const coop = await userService.getUserByUsername('voskhod');
coop.status = 'active';
coop.is_registered = true;
await coop.save();
console.log('ok');
} catch (e: any) {
console.log('Ошибка: ', e);
}
process.exit(0);
}
init();
@@ -0,0 +1,43 @@
import config from '../../src/config/config';
import { User } from '../../src/models';
import { tokenService } from '../../src/services';
import { userService } from '../../src/services';
import mongoose from 'mongoose';
import { connectGenerator } from '../../src/services/document.service';
import crypto from 'crypto';
import bcryptjs from 'bcryptjs';
const { compare, hash } = bcryptjs;
async function init() {
try {
await connectGenerator();
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const orgs_collection = mongoose.connection.collection('OrgData');
const bankData_collection = mongoose.connection.collection('PaymentData');
const orgs = await orgs_collection.find().toArray();
for (const org of orgs) {
await bankData_collection.insertOne({
_created_at: org._created_at,
username: org.username,
method_id: 1,
user_type: 'organization',
method_type: 'bank_transfer',
is_default: true,
data: org.bank_account,
deleted: false,
block_num: org.block_num,
});
}
} catch (e: any) {
console.log('Ошибка: ', e);
}
process.exit(0);
}
init();
+4 -4
View File
@@ -3,12 +3,12 @@ import crypto from 'crypto';
const { compare, hash } = bcryptjs;
async function generate() {
const wif = '';
const wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
const hash_from_wif = await crypto.createHash('sha256').update(wif).digest('hex');
const password = await hash(hash_from_wif, 8)
console.log(password)
console.log('hash_from_wif: ', hash_from_wif);
const password = await hash(hash_from_wif, 8);
console.log(password);
}
generate();
+1 -1
View File
@@ -7,7 +7,7 @@ async function types(): Promise<void> {
// Configure your settings here
const result = await convertFromDirectory({
schemaDirectory: './src/validations',
typeOutputDirectory: './src/types',
typeOutputDirectory: './src/types/auto-generated',
debug: true,
});
+18 -8
View File
@@ -1,17 +1,16 @@
import dotenv from 'dotenv';
import Joi from 'joi';
import path from 'path';
// import { fileURLToPath } from 'url';
// import { dirname, join } from 'path';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = dirname(__filename);
dotenv.config({ path: path.join(__dirname, '../../.env') });
const envVarsSchema = Joi.object()
.keys({
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
BASE_URL: Joi.string().required(),
SERVER_SECRET: Joi.string().required(),
SERVICE_USERNAME: Joi.string().required(),
SERVICE_WIF: Joi.string().required(),
PORT: Joi.number().default(3000),
MONGODB_URL: Joi.string().required().description('Mongo DB url'),
JWT_SECRET: Joi.string().required().description('JWT secret key'),
@@ -23,11 +22,16 @@ const envVarsSchema = Joi.object()
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which verify email token expires'),
JWT_INVITE_EXPIRATION_MINUTES: Joi.number().default(3600).description('minutes after invite token expires'),
SMTP_HOST: Joi.string().description('server that will send the emails'),
SMTP_PORT: Joi.number().description('port to connect to the email server'),
SMTP_USERNAME: Joi.string().description('username for email server'),
SMTP_PASSWORD: Joi.string().description('password for email server'),
EMAIL_FROM: Joi.string().description('the from field in the emails sent by the app'),
COOPNAME: Joi.string().description('имя аккаунта кооператива'),
})
.unknown();
@@ -39,13 +43,17 @@ if (error) {
export default {
env: envVars.NODE_ENV,
base_url: envVars.BASE_URL,
port: envVars.PORT,
server_secret: envVars.SERVER_SECRET,
service_wif: envVars.SERVICE_WIF,
service_username: envVars.SERVICE_USERNAME,
mongoose: {
url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''),
options: {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
// useCreateIndex: true,
// useNewUrlParser: true,
// useUnifiedTopology: true,
},
},
jwt: {
@@ -54,6 +62,7 @@ export default {
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
inviteExpirationMinutes: envVars.JWT_INVITE_EXPIRATION_MINUTES,
},
email: {
smtp: {
@@ -66,4 +75,5 @@ export default {
},
from: envVars.EMAIL_FROM,
},
coopname: envVars.COOPNAME,
};
+2 -2
View File
@@ -1,5 +1,5 @@
import winston from 'winston'
import config from './config'
import winston from 'winston';
import config from './config';
const enumerateErrorFormat = winston.format((info) => {
if (info instanceof Error) {
+3 -4
View File
@@ -1,9 +1,8 @@
import {Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
import config from './config'
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import config from './config';
import { tokenTypes } from './tokens';
import { User } from '../models';
const jwtOptions = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
@@ -14,7 +13,7 @@ const jwtVerify = async (payload, done) => {
if (payload.type !== tokenTypes.ACCESS) {
throw new Error('Invalid token type');
}
const user = await User.findOne({_id: payload.sub});
const user = await User.findOne({ _id: payload.sub });
if (!user) {
return done(null, false);
}
+2 -2
View File
@@ -1,7 +1,7 @@
const allRoles = {
user: [''],
service: ['sendNotification'],
chairman: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
service: ['addUser', 'sendNotification', 'install'],
chairman: ['addUser', 'getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo', 'set-vars'],
member: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
};
+1
View File
@@ -3,4 +3,5 @@ export const tokenTypes = {
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
INVITE: 'invite',
};
@@ -1,17 +1,15 @@
import httpStatus from 'http-status';
import catchAsync from '../utils/catchAsync';
import { authService, userService, tokenService, emailService, blockchainService } from '../services';
import { RForgotPassword, RRefreshTokens, RResetPassword, RSendNotification, RVerifyEmail } from '../types';
import { authService, tokenService, emailService } from '../services';
import { RForgotKey, RRefreshTokens, RResetKey, RVerifyEmail } from '../types';
const { CREATED, NO_CONTENT } = httpStatus;
const { NO_CONTENT } = httpStatus;
export const login = catchAsync(async (req, res) => {
const { email, password } = req.body;
const user = await authService.loginUserWithEmailAndPassword(email, password);
const { now, signature, email } = req.body;
if (user.is_registered === false)
throw new Error('Регистрация не была завершена. Пожалуйста, подождите решения совета или начните с начала. ');
const user = await authService.loginUserWithSignature(email, now, signature);
const tokens = await tokenService.generateAuthTokens(user);
res.send({ user, tokens });
@@ -27,14 +25,14 @@ export const refreshTokens = catchAsync(async (req: RRefreshTokens, res: any) =>
res.send(tokens);
});
export const forgotPassword = catchAsync(async (req: RForgotPassword, res) => {
const resetPasswordToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken);
export const lostKey = catchAsync(async (req: RForgotKey, res) => {
const resetKeyToken = await tokenService.generateResetKeyToken(req.body.email);
await emailService.sendResetKeyEmail(req.body.email, resetKeyToken);
res.status(NO_CONTENT).send();
});
export const resetPassword = catchAsync(async (req: RResetPassword, res) => {
await authService.resetPassword(req.query.token, req.body.password);
export const resetKey = catchAsync(async (req: RResetKey, res) => {
await authService.resetKey(req.body.token, req.body.public_key);
res.status(NO_CONTENT).send();
});
@@ -1,26 +1,24 @@
import catchAsync from '../utils/catchAsync';
import { coopService, documentService } from '../services';
import { Cooperative } from 'cooptypes';
import { generator } from '../services/document.service';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import { Request, Response } from 'express';
export const loadInfo = catchAsync(async (req, res) => {
export const loadInfo = catchAsync(async (req: Request, res: Response) => {
const cooperative = await coopService.loadInfo(String(process.env.COOPNAME));
res.send(cooperative);
});
export const loadContacts = catchAsync(async (req, res) => {
export const loadContacts = catchAsync(async (req: Request, res: Response) => {
const contacts = await coopService.loadContacts(String(process.env.COOPNAME));
res.send(contacts);
});
export const loadAgenda = catchAsync(async (req, res) => {
export const loadAgenda = catchAsync(async (req: Request, res: Response) => {
const { coopname } = req.query;
const agenda = await coopService.loadAgenda(coopname);
const agenda = await coopService.loadAgenda(coopname as string);
const complexAgenda: Cooperative.Documents.IComplexAgenda[] = [];
const complexAgenda: Cooperative.Document.IComplexAgenda[] = [];
for (const { action, table } of agenda) {
const documents = await documentService.buildComplexDocument(action);
@@ -30,7 +28,7 @@ export const loadAgenda = catchAsync(async (req, res) => {
res.send(complexAgenda);
});
export const loadStaff = catchAsync(async (req, res) => {
export const loadStaff = catchAsync(async (req, res: Response) => {
const { coopname } = req.query;
const staff = await coopService.loadStaff(coopname);
res.send(staff);
@@ -1,18 +1,8 @@
import catchAsync from '../utils/catchAsync';
import { documentService } from '../services/index';
import type {
IGetDocuments,
RDeletePaymentMethod,
RGenerate,
RGetDocuments,
RGetListPaymentMethods,
RSavePaymentMethod,
} from '../types';
import type { RGenerate, RGetDocuments } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
import { Cooperative } from 'cooptypes';
import ApiError from '../utils/ApiError';
export const generateDocument = catchAsync(async (req: RGenerate, res) => {
const document = await documentService.generateDocument(req.body);
@@ -21,9 +11,9 @@ export const generateDocument = catchAsync(async (req: RGenerate, res) => {
});
export const getDocuments = catchAsync(async (req: RGetDocuments, res) => {
let filter = req.query?.filter;
let limit = req.query?.limit as number;
let page = req.query?.page as number;
const filter = req.query?.filter;
const limit = req.query?.limit as number;
const page = req.query?.page as number;
const documents = await documentService.queryDocuments(filter, page, limit);
@@ -4,3 +4,5 @@ export * as documentController from './document.controller';
export * as paymentController from './payment.controller';
export * as coopController from './coop.controller';
export * as notifyController from './notify.controller';
export * as systemController from './system.controller';
export * as participantController from './participant.controller';
@@ -0,0 +1,10 @@
import httpStatus from 'http-status';
import { participantService } from '../services';
import type { RJoinCooperative } from '../types';
import catchAsync from '../utils/catchAsync';
export const joinCooperative = catchAsync(async (req: RJoinCooperative, res) => {
await participantService.joinCooperative(req.body);
res.status(httpStatus.OK).send();
});
@@ -80,10 +80,19 @@ export const addPaymentMethod = catchAsync(async (req: RSavePaymentMethod, res)
const user = await getUserByUsername(req.body.username);
const method = req.body as any;
if (method.method_type === 'sbp' && !method.data.phone)
throw new ApiError(httpStatus.BAD_REQUEST, 'Не указан телефон для метода СБП');
if (method.method_type === 'bank_transfer' && !method.data.account_number)
throw new ApiError(httpStatus.BAD_REQUEST, 'Не верно указаны реквизиты для банковского платежа');
const paymentData: Cooperative.Payments.IPaymentData = {
username: req.body.username,
method_id: req.body.method_id,
user_type: user.type,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
user_type: user.type as any,
method_type: req.body.method_type,
is_default: false,
data: req.body.data,
@@ -0,0 +1,36 @@
import httpStatus from 'http-status';
import { getMonoStatus } from '../services/system.service';
import { getBlockchainInfo } from '../services/blockchain.service';
import { IHealthResponse, IInstall, RInstall } from '../types';
import { Request, Response } from 'express';
import catchAsync from '../utils/catchAsync';
import { systemService } from '../services';
import type { ISetVars, RSetVars } from '../types/auto-generated/system.validation';
export const install = catchAsync(async (req: RInstall, res: Response) => {
const { body } = req;
await systemService.install(body as IInstall);
res.status(httpStatus.OK).send();
});
export const getHealth = catchAsync(async (req: Request, res: Response) => {
const status = await getMonoStatus();
const blockchain = await getBlockchainInfo();
const result: IHealthResponse = {
status,
blockchain,
};
res.status(httpStatus.OK).send(result);
});
export const setVars = catchAsync(async (req: RSetVars, res: Response) => {
await systemService.setVars(req.body as ISetVars);
res.status(httpStatus.OK).send();
});
export const getVarsSchema = catchAsync(async (req: RSetVars, res: Response) => {
const schema = await systemService.getVarsSchema();
res.status(httpStatus.OK).send(schema);
});
@@ -1,12 +1,16 @@
import http from 'http-status';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService, tokenService } from '../services';
import { RCreateUser, RJoinCooperative } from '../types';
import { userService, tokenService, emailService, blockchainService } from '../services';
import { IAddUser, ICreateUser, RCreateUser } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
import { IUser } from '../models/user.model';
import { IUser, userStatus } from '../models/user.model';
import { Request, Response } from 'express';
import { generateUsername } from '../../tests/utils/generateUsername';
import config from '../config/config';
import logger from '../config/logger';
/**
* Порядок регистрации:
@@ -19,9 +23,9 @@ import { IUser } from '../models/user.model';
* 7. (joinCooperative) Пользователь подписывает заявление на вступление и вызывает метод joinCooperative, прикладывая подписанное заявление.
* 8. Система сохраняет подписанное заявление
* 9. (createInitialPayment) Пользователь получает ордер на оплату.
* 9. Пользователь оплачивает ордер.
* 10. Система принимает оплату и регистрирует аккаунт в блокчейне.
* 11. (trx confirmpay) Пользователь подписывает велком-письмо с подтверждением собственноручности оплаты (транзакция в блокчейне для активации)
* 12. Регистрация продолжается ожиданием решения совета
*/
export const createUser = catchAsync(async (req: RCreateUser, res) => {
const user = await userService.createUser(req.body);
@@ -30,33 +34,56 @@ export const createUser = catchAsync(async (req: RCreateUser, res) => {
res.status(http.CREATED).send({ user, tokens });
});
export const joinCooperative = catchAsync(async (req: RJoinCooperative, res) => {
await userService.joinCooperative(req.body);
export const addUser = catchAsync(async (req: Request, res: Response) => {
const body: IAddUser = req.body;
res.status(httpStatus.OK).send();
const newUser: ICreateUser = {
...body,
public_key: '',
role: 'user',
username: generateUsername(),
};
const user = await userService.createUser(newUser);
user.status = userStatus['4_Registered'];
user.is_registered = true;
await user.save();
try {
await blockchainService.addUser({
...body,
...newUser,
registrator: config.service_username,
referer: body.referer ? body.referer : '',
coopname: config.coopname,
meta: '',
});
const token = await tokenService.generateInviteToken(user.email);
await emailService.sendInviteEmail(req.body.email, token);
} catch (e: any) {
logger.warn('error on add user: ', e);
await userService.deleteUserByUsername(newUser.username);
throw new ApiError(httpStatus.BAD_GATEWAY, e.message);
}
res.status(httpStatus.CREATED).send({ user });
});
export const getUsers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['username', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
Object.keys(filter).forEach((key) => {
if (filter[key] !== undefined) {
filter[key] = { $eq: filter[key], $ne: 'service' };
} else {
filter[key] = { $ne: 'service' };
}
});
const users = await userService.queryUsers(filter, options);
const result = {} as IGetResponse<IUser>;
//TODO wrong format answer
const data = [] as any;
for await (let user of users.results) {
for await (const user of users.results) {
const json = user.toJSON();
json.private_data = (await user.getPrivateData()) || {};
data.push(json);
if (user.type != 'service') {
json.private_data = await user.getPrivateData();
data.push(json);
}
}
result.results = data;
@@ -75,7 +102,9 @@ export const getUser = catchAsync(async (req, res) => {
}
const json = user.toJSON();
json.private_data = await user.getPrivateData();
if (user.type != 'service') {
json.private_data = await user.getPrivateData();
}
res.send(json);
});
@@ -89,6 +118,6 @@ export const updateUser = catchAsync(async (req, res) => {
});
export const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserById(req.params.username);
await userService.deleteUserByUsername(req.params.username);
res.status(http.NO_CONTENT).send();
});
+8 -4
View File
@@ -1,22 +1,26 @@
export * from './types';
import mongoose from 'mongoose';
import app from './app';
import config from './config/config';
import logger from './config/logger';
import { updateAuth } from './services/auth.service';
import { connectGenerator } from './services/document.service';
import { initSocketConnection } from './controllers/ws.controller';
import { systemService } from './services';
const SERVER_URL: string = process.env.SOCKET_SERVER || 'http://localhost:2222';
let server: any;
mongoose.connect(config.mongoose.url, config.mongoose.options).then(async () => {
mongoose.connect(config.mongoose.url).then(async () => {
logger.info('Connected to MongoDB');
//подключаемся к хранилищу приватных данных
await systemService.init();
// подключаемся к хранилищу приватных данных
await connectGenerator();
//подключаемся к ws-серверу
// подключаемся к ws-серверу
await initSocketConnection(SERVER_URL);
server = app.listen(config.port, () => {
@@ -6,6 +6,13 @@ import { roleRights } from '../config/roles';
const { UNAUTHORIZED, FORBIDDEN } = httpStatus;
const verifyCallback = (req, resolve, reject, requiredRights) => async (err, user, info) => {
const serverSecret = process.env.SERVER_SECRET;
// Проверка заголовка server-secret
if (serverSecret && req.headers['server-secret'] === serverSecret) {
return resolve();
}
if (err || info || !user) {
return reject(new ApiError(UNAUTHORIZED, 'Please authenticate'));
}
+1
View File
@@ -1,3 +1,4 @@
export { default as Token } from './token.model';
export { default as User } from './user.model';
export { default as Order } from './order.model';
export { default as Mono } from './mono.model';
@@ -0,0 +1,26 @@
import mongoose, { Schema } from 'mongoose';
import { paginate, toJSON } from './plugins';
import { IHealthStatus } from '../types';
export interface IMono {
coopname: string;
status: IHealthStatus;
}
const Status = {
Install: 'install',
Active: 'active',
Maintenance: 'maintenance',
};
const MonoSchema = new Schema<IMono>({
coopname: { type: String, required: true },
status: { type: String, required: true, enum: Object.values(Status) },
});
MonoSchema.plugin(toJSON);
MonoSchema.plugin(paginate);
const MonoModel = mongoose.model<IMono>('Mono', MonoSchema);
export default MonoModel;
+52 -50
View File
@@ -5,9 +5,9 @@ interface IOrder extends Document {
creator: string;
type: string;
data: {
username: string
provider: string
quantity: string
username: string;
provider: string;
quantity: string;
};
order_id?: number;
payed?: boolean;
@@ -16,59 +16,61 @@ interface IOrder extends Document {
expired_at?: Date;
}
const orderSchema = new Schema<IOrder>({
creator: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
data: {
provider: {
type: String,
required: true
},
username: {
const orderSchema = new Schema<IOrder>(
{
creator: {
type: String,
required: true,
},
quantity: {
type: {
type: String,
required: true
}
},
order_id: {
type: Number,
required: false,
},
payed: {
type: Boolean,
required: false,
default: false,
},
delivered: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
},
expired_at: {
type: Date,
default: () => {
const now = new Date();
now.setDate(now.getDate() + 1); // Увеличиваем на одни сутки
return now;
required: true,
},
data: {
provider: {
type: String,
required: true,
},
username: {
type: String,
required: true,
},
quantity: {
type: String,
required: true,
},
},
order_id: {
type: Number,
required: false,
},
payed: {
type: Boolean,
required: false,
default: false,
},
delivered: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
},
expired_at: {
type: Date,
default: () => {
const now = new Date();
now.setDate(now.getDate() + 1); // Увеличиваем на одни сутки
return now;
},
},
},
},
{
timestamps: true,
});
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
orderSchema.plugin(toJSON);
@@ -0,0 +1,51 @@
import mongoose, { Schema } from 'mongoose';
import { paginate, toJSON } from './plugins';
export enum tempdocType {
JoinStatement = 'joinStatement',
WalletAgreement = 'walletAgreement',
SignatureAgreement = 'signatureAgreement',
PrivacyAgreement = 'privacyAgreement',
UserAgreement = 'userAgreement',
}
export interface ITempDocument {
username: string;
type: tempdocType;
document: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
}
const MonoSchema = new Schema<ITempDocument>({
username: { type: String, required: true },
type: { type: String, required: true, enum: ['joinStatement', 'walletAgreement'] },
document: {
public_key: {
type: String,
default: '',
},
signature: {
type: String,
default: '',
},
meta: {
type: Object,
default: {},
},
hash: {
type: String,
default: '',
},
},
});
MonoSchema.plugin(toJSON);
MonoSchema.plugin(paginate);
const TempDocument = mongoose.model<ITempDocument>('TempDocument', MonoSchema);
export default TempDocument;
@@ -25,7 +25,7 @@ const tokenSchema = new Schema<IToken>(
},
type: {
type: String,
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL],
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL, tokenTypes.INVITE],
required: true,
},
expires: {
+42 -60
View File
@@ -1,32 +1,40 @@
import mongoose, { Document, Schema, model, Model } from 'mongoose';
import mongoose, { Schema, model, Model } from 'mongoose';
import validator from 'validator/index';
import bcryptjs from 'bcryptjs';
import { toJSON, paginate } from './plugins/index';
import { roles } from '../config/roles';
import { generator } from '../services/document.service';
import { Cooperative } from 'cooptypes';
const { isEmail } = validator;
const { compare, hash } = bcryptjs;
export enum userStatus {
'1_Created' = 'created',
'2_Joined' = 'joined',
'3_Payed' = 'payed',
'4_Registered' = 'registered',
'5_Active' = 'active',
'10_Failed' = 'failed',
'200_Blocked' = 'blocked',
}
export interface IUser {
username: string;
status: 'created' | 'joined' | 'payed' | 'registered' | 'active' | 'failed' | 'blocked';
status: userStatus;
message: string;
is_registered: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
has_account: boolean;
type: 'individual' | 'entrepreneur' | 'organization' | 'service';
public_key: string;
referer: string;
email: string;
password: string;
role: string;
is_email_verified: boolean;
statement: {
hash: string;
meta: Object;
public_key: string;
signature: string;
};
// statement: {
// hash: string;
// meta: object;
// public_key: string;
// signature: string;
// };
private_data:
| Cooperative.Users.IIndividualData
| Cooperative.Users.IEntrepreneurData
@@ -35,7 +43,6 @@ export interface IUser {
getPrivateData(): Promise<
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
>;
isPasswordMatch(password: string): Promise<boolean>;
}
interface IUserModel extends Model<IUser> {
@@ -53,8 +60,8 @@ const userSchema = new Schema<IUser, IUserModel>(
},
status: {
type: String,
enum: ['created', 'joined', 'payed', 'registered', 'active', 'failed', 'blocked'],
default: 'created',
enum: Object.values(userStatus),
default: userStatus['1_Created'],
},
message: {
type: String,
@@ -71,7 +78,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
public_key: {
type: String,
required: true,
default: '',
},
referer: {
type: String,
@@ -79,7 +86,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
email: {
type: String,
required: true,
required: false,
unique: true,
trim: true,
lowercase: true,
@@ -89,18 +96,6 @@ const userSchema = new Schema<IUser, IUserModel>(
}
},
},
password: {
type: String,
required: true,
trim: true,
minlength: 8,
validate(value) {
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
throw new Error('Password must contain at least one letter and one number');
}
},
private: true, // used by the toJSON plugin
},
role: {
type: String,
enum: roles,
@@ -114,24 +109,24 @@ const userSchema = new Schema<IUser, IUserModel>(
type: Boolean,
default: false,
},
statement: {
public_key: {
type: String,
default: '',
},
signature: {
type: String,
default: '',
},
meta: {
type: Object,
default: {},
},
hash: {
type: String,
default: '',
},
},
// statement: {
// public_key: {
// type: String,
// default: '',
// },
// signature: {
// type: String,
// default: '',
// },
// meta: {
// type: Object,
// default: {},
// },
// hash: {
// type: String,
// default: '',
// },
// },
},
{
minimize: false,
@@ -162,19 +157,6 @@ userSchema.methods.getPrivateData = async function (): Promise<
return result;
};
userSchema.methods.isPasswordMatch = async function (password) {
const user = this;
return compare(password, user.password);
};
userSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password')) {
user.password = await hash(user.password, 8);
}
next();
});
const User = model<IUser, IUserModel>('User', userSchema);
export default User;
@@ -10,8 +10,8 @@ const router = Router();
router.post('/login', validate(authValidation.RLogin), authController.login);
router.post('/logout', validate(authValidation.RLogout), authController.logout);
router.post('/refresh-tokens', validate(authValidation.RRefreshTokens), authController.refreshTokens);
router.post('/forgot-password', validate(authValidation.RForgotPassword), authController.forgotPassword);
router.post('/reset-password', validate(authValidation.RResetPassword), authController.resetPassword);
router.post('/lost-key', validate(authValidation.RForgotKey), authController.lostKey);
router.post('/reset-key', validate(authValidation.RResetKey), authController.resetKey);
router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
router.post('/verify-email', validate(authValidation.RVerifyEmail), authController.verifyEmail);
@@ -2,15 +2,14 @@ import { Router } from 'express';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import * as coopValidation from '../../validations/coop.validation';
// import { loadAgenda, loadStaff, } from '../../validations/coop.validation';
import * as coopController from '../../controllers/coop.controller';
const router = Router();
router.route('/agenda').get(auth('loadAgenda'), validate(coopValidation.loadAgenda), coopController.loadAgenda);
router.route('/staff').get(auth('loadStaff'), validate(coopValidation.loadStaff), coopController.loadStaff);
router.route('/contacts').get(coopController.loadContacts);
router.route('/info').get(auth('loadInfo'), coopController.loadInfo);
router.route('/contacts').get(coopController.loadContacts);
export default router;
@@ -4,14 +4,20 @@ import userRoute from './user.route';
import docsRoute from './docs.route';
import paymentRoute from './payment.route';
import coopRoute from './coop.route';
import monoRoute from './system.route';
import dataRoute from './document.route';
import notifyRoute from './notify.route';
import participantsRoute from './participant.route';
import config from '../../config/config';
const router = Router();
const defaultRoutes = [
{
path: '/system',
route: monoRoute,
},
{
path: '/auth',
route: authRoute,
@@ -20,6 +26,10 @@ const defaultRoutes = [
path: '/users',
route: userRoute,
},
{
path: '/participants',
route: participantsRoute,
},
{
path: '/documents',
route: dataRoute,
@@ -0,0 +1,13 @@
import { Router } from 'express';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import { participantValidation } from '../../validations';
import { participantController } from '../../controllers';
const router = Router();
router
.route('/join-cooperative')
.post(auth(), validate(participantValidation.RJoinCooperative), participantController.joinCooperative);
export default router;
@@ -0,0 +1,18 @@
import { Router } from 'express';
import { systemController } from '../../controllers';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import { systemValidation } from '../../validations';
const router = Router();
router.route('/install').post(auth('install'), validate(systemValidation.RInstall), systemController.install);
router.route('/get-vars-schema').post(auth(), systemController.getVarsSchema);
router.route('/set-vars').post(auth('set-vars'), validate(systemValidation.RSetVars), systemController.setVars);
router.route('/get-vars').post(auth(), validate(systemValidation.RSetVars), systemController.setVars);
router.route('/health').get(systemController.getHealth);
export default router;
@@ -11,9 +11,7 @@ router
.post(validate(userValidation.RCreateUser), userController.createUser)
.get(auth('getUsers'), validate(userValidation.RGetUsers), userController.getUsers);
router
.route('/join-cooperative')
.post(validate(userValidation.RJoinCooperative), userController.joinCooperative)
router.route('/add').post(auth('addUser'), validate(userValidation.RAddUser), userController.addUser);
router
.route('/:username')
@@ -1,68 +1,50 @@
import httpStatus from 'http-status';
import * as tokenService from './token.service';
import * as userService from './user.service';
import { getSoviet } from './blockchain.service';
import Token from '../models/token.model';
import ApiError from '../utils/ApiError';
import { tokenTypes } from '../config/tokens';
import { IRefreshTokens } from '../types';
import { getUserByEmail } from './user.service';
import { Bytes, Checksum256, Signature } from '@wharfkit/antelope';
import { getBlockchainAccount, getBlockchainInfo, hasActiveKey } from '../services/blockchain.service';
import config from '../config/config';
import { blockchainService } from '.';
export const updateAuth = async () => {
try {
const board = await getSoviet(process.env.COOPNAME);
// TODO снимать права с тех, кто уже не в совете
// eslint-disable-next-line no-restricted-syntax
for (const member of board.members) {
// eslint-disable-next-line no-await-in-loop
const user = await userService.getUserByUsername(member.username);
if (member.position === 'chairman' && !user) {
// eslint-disable-next-line no-await-in-loop
// await userService.createUser({
// username: member.username,
// public_key: '-',
// email: process.env.CHAIRMAN_EMAIL ,
// password: process.env.CHAIRMAN_PASSWORD as string,
// is_registered: true,
// is_organization: false,
// user_profile: {
// first_name: 'Имя',
// last_name: 'Фамилия',
// middle_name: 'Отчество',
// birthday: '23-42-3423',
// phone: '+7902294404',
// },
// signature: '-',
// signature_hash: '-',
// role: 'chairman',
// });
} else if (member.position === 'chairman' && user) {
user.role = 'chairman';
user.password = process.env.CHAIRMAN_PASSWORD || 'password';
// eslint-disable-next-line no-await-in-loop
await user.save();
} else if (user) {
user.role = 'admin';
// eslint-disable-next-line no-await-in-loop
await user.save();
}
export const loginUserWithSignature = async (email, now, signature) => {
const user = await getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Пользователь не найден');
}
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const wharf_signature = Signature.from(signature);
const publicKey = wharf_signature.recoverDigest(checksum).toLegacyString();
const info = await getBlockchainInfo();
const blockchainDate = new Date(info.head_block_time).getTime();
const userData = new Date(now).getTime();
const differenceInSeconds = (blockchainDate - userData) / 1000;
if (differenceInSeconds > 30) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Время подписи и время блокчейна превышает допустимое расхождение');
}
if (config.env !== 'test') {
try {
const blockchainAccount = await getBlockchainAccount(user.username);
const hasKey = hasActiveKey(blockchainAccount, publicKey);
if (!hasKey) throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
} catch (e) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
}
} catch (e: any) {
// eslint-disable-next-line no-console
console.log('Ошибка при автоматической проверке целевой авторизации: ', e.message);
}
};
/**
* Login with email and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
export const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};
@@ -76,7 +58,7 @@ export const logout = async (refreshToken) => {
if (!refreshTokenDoc) {
throw new ApiError(httpStatus.NOT_FOUND, 'Not found');
}
await refreshTokenDoc.remove();
await refreshTokenDoc.deleteOne();
};
/**
@@ -91,7 +73,7 @@ export const refreshAuth = async (data: IRefreshTokens) => {
if (!user) {
throw new Error();
}
await refreshTokenDoc.remove();
await refreshTokenDoc.deleteOne();
return tokenService.generateAuthTokens(user);
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
@@ -99,22 +81,34 @@ export const refreshAuth = async (data: IRefreshTokens) => {
};
/**
* Reset password
* @param {string} resetPasswordToken
* @param {string} newPassword
* Reset key
* @param {string} resetKeyToken
* @param {string} publicKey
* @returns {Promise}
*/
export const resetPassword = async (resetPasswordToken, newPassword) => {
export const resetKey = async (resetKeyToken, publicKey) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD);
const user = await userService.getUserById(resetPasswordTokenDoc.user);
const resetKeyTokenDoc = await tokenService.verifyToken(resetKeyToken, [tokenTypes.RESET_PASSWORD, tokenTypes.INVITE]);
const user = await userService.getUserById(resetKeyTokenDoc.user);
if (!user) {
throw new Error();
}
await userService.updateUserById(user._id, { password: newPassword });
if (config.env !== 'test')
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.service_username,
username: user.username,
public_key: publicKey,
});
await userService.updateUserById(user._id, { public_key: publicKey });
await Token.deleteMany({ user: user._id, type: tokenTypes.RESET_PASSWORD });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
console.log(error);
throw new ApiError(httpStatus.UNAUTHORIZED, 'Token reset failed');
}
};
@@ -6,9 +6,33 @@ import EosApi from 'eosjs-api';
import getInternalAction from '../utils/getInternalAction';
import { GatewayContract, RegistratorContract, SovietContract } from 'cooptypes';
import { IUser } from '../models/user.model';
import { GetAccountResult, GetInfoResult } from 'eosjs/dist/eosjs-rpc-interfaces';
import config from '../config/config';
import TempDocument, { tempdocType } from '../models/tempDocument.model';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
const rpc = new JsonRpc(process.env.BLOCKCHAIN_RPC as string, { fetch });
function hasActiveKey(account, publicKey) {
const activePermissions = account.permissions.find((p: any) => p.perm_name === 'active');
if (!activePermissions) return false;
return activePermissions.required_auth.keys.some((key: any) => key.key === publicKey);
}
async function getBlockchainInfo(): Promise<GetInfoResult> {
const api = getApi();
return await api.getInfo({});
}
async function getBlockchainAccount(username): Promise<GetAccountResult> {
const api = getApi();
return await api.getAccount(username);
}
/**
* Получить инстанс для осуществления транзакции в блокчейн.
* @param {username} - авторизация от аккаунта
@@ -22,7 +46,7 @@ async function getInstance(wif) {
return api;
}
async function getApi() {
function getApi() {
const options = {
httpEndpoint: process.env.BLOCKCHAIN_RPC, // default, null for cold-storage
verbose: false, // API logging
@@ -63,9 +87,9 @@ async function getCooperative(coopname) {
const [cooperative] = await lazyFetch(
api,
process.env.REGISTRATOR_CONTRACT,
process.env.REGISTRATOR_CONTRACT,
'orgs',
RegistratorContract.contractName.production,
RegistratorContract.contractName.production,
RegistratorContract.Tables.Cooperatives.tableName,
coopname,
coopname,
1
@@ -74,7 +98,7 @@ async function getCooperative(coopname) {
}
async function registerBlockchainAccount(user: IUser, orderData: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit) {
const eos = await getInstance(process.env.REGISTRATOR_WIF);
const eos = await getInstance(process.env.SERVICE_WIF);
const newaccount: RegistratorContract.Actions.CreateAccount.ICreateAccount = {
registrator: process.env.COOPNAME as string,
@@ -92,11 +116,59 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
type: user.type,
};
const statement = await TempDocument.findOne({ username: user.username, type: tempdocType.JoinStatement });
if (!statement) throw new ApiError(httpStatus.BAD_REQUEST, 'Не найдено заявление на вступление');
const joinCooperativeData: RegistratorContract.Actions.JoinCooperative.IJoinCooperative = {
coopname: process.env.COOPNAME as string,
registrator: process.env.COOPNAME as string,
username: user.username,
document: { ...user.statement, meta: JSON.stringify(user.statement.meta) },
document: { ...statement.document, meta: JSON.stringify(statement.document.meta) },
};
//TODO добавить здесь соглашений
const walletAgreement = await TempDocument.findOne({ username: user.username, type: tempdocType.WalletAgreement });
if (!walletAgreement) throw new ApiError(httpStatus.BAD_REQUEST, 'Не найдено заявление на вступление');
const walletAgreementData: SovietContract.Actions.Agreements.SendAgreement.ISendAgreement = {
coopname: process.env.COOPNAME as string,
administrator: process.env.COOPNAME as string,
username: user.username,
agreement_type: 'wallet',
document: { ...walletAgreement.document, meta: JSON.stringify(walletAgreement.document.meta) },
};
const privacyAgreement = await TempDocument.findOne({ username: user.username, type: tempdocType.PrivacyAgreement });
if (!privacyAgreement) throw new ApiError(httpStatus.BAD_REQUEST, 'Не найдено соглашение о политике конфиденциальности');
const privacyAgreementData: SovietContract.Actions.Agreements.SendAgreement.ISendAgreement = {
coopname: process.env.COOPNAME as string,
administrator: process.env.COOPNAME as string,
username: user.username,
agreement_type: 'privacy',
document: { ...privacyAgreement.document, meta: JSON.stringify(privacyAgreement.document.meta) },
};
const signatureAgreement = await TempDocument.findOne({ username: user.username, type: tempdocType.SignatureAgreement });
if (!signatureAgreement) throw new ApiError(httpStatus.BAD_REQUEST, 'Не найдено соглашение о правилах использования ЭЦП');
const signatureAgreementData: SovietContract.Actions.Agreements.SendAgreement.ISendAgreement = {
coopname: process.env.COOPNAME as string,
administrator: process.env.COOPNAME as string,
username: user.username,
agreement_type: 'signature',
document: { ...signatureAgreement.document, meta: JSON.stringify(signatureAgreement.document.meta) },
};
const userAgreement = await TempDocument.findOne({ username: user.username, type: tempdocType.UserAgreement });
if (!userAgreement) throw new ApiError(httpStatus.BAD_REQUEST, 'Не найдено подписанное пользовательское соглашение');
const userAgreementData: SovietContract.Actions.Agreements.SendAgreement.ISendAgreement = {
coopname: process.env.COOPNAME as string,
administrator: process.env.COOPNAME as string,
username: user.username,
agreement_type: 'user',
document: { ...userAgreement.document, meta: JSON.stringify(userAgreement.document.meta) },
};
const actions = [
@@ -144,6 +216,50 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
],
data: orderData,
},
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: process.env.COOPNAME as string,
permission: 'active',
},
],
data: walletAgreementData,
},
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: process.env.COOPNAME as string,
permission: 'active',
},
],
data: signatureAgreementData,
},
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: process.env.COOPNAME as string,
permission: 'active',
},
],
data: privacyAgreementData,
},
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: process.env.COOPNAME as string,
permission: 'active',
},
],
data: userAgreementData,
},
];
const result = await eos.transact(
@@ -155,14 +271,46 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
expireSeconds: 30,
}
);
console.dir(result, { depth: null });
}
async function createOrder(data) {
const eos = await getInstance(process.env.REGISTRATOR_WIF);
async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
const eos = await getInstance(config.service_wif);
console.log('data: ', data);
const actions = [
{
account: process.env.GATEWAY_CONTRACT as string,
account: SovietContract.contractName.production,
name: SovietContract.Actions.Boards.CreateBoard.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
async function createOrder(data) {
const eos = await getInstance(process.env.SERVICE_WIF);
const actions = [
{
account: GatewayContract.contractName.production,
name: 'deposit',
authorization: [
{
@@ -190,7 +338,7 @@ async function createOrder(data) {
}
async function completeOrder(data: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit) {
const eos = await getInstance(process.env.REGISTRATOR_WIF);
const eos = await getInstance(process.env.SERVICE_WIF);
const actions = [
{
@@ -218,11 +366,11 @@ async function completeOrder(data: GatewayContract.Actions.CompleteDeposit.IComp
}
async function failOrder(data) {
const eos = await getInstance(process.env.REGISTRATOR_WIF);
const eos = await getInstance(process.env.SERVICE_WIF);
const actions = [
{
account: process.env.GATEWAY_CONTRACT as string,
account: GatewayContract.contractName.production,
name: 'dpfail',
authorization: [
{
@@ -245,10 +393,66 @@ async function failOrder(data) {
);
}
export async function addUser(data: RegistratorContract.Actions.AddUser.IAddUser) {
const eos = await getInstance(config.service_wif);
const actions = [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
export async function changeKey(data: RegistratorContract.Actions.ChangeKey.IChangeKey) {
const eos = await getInstance(config.service_wif);
const actions = [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.ChangeKey.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
async function getSoviet(coopname) {
const api = await getApi();
const soviet = (await lazyFetch(api, process.env.SOVIET_CONTRACT, coopname, 'boards'))[0];
const soviet = (await lazyFetch(api, SovietContract.contractName.production, coopname, 'boards'))[0];
return soviet;
}
@@ -256,7 +460,7 @@ async function getSoviet(coopname) {
async function fetchAllParticipants() {
const api = await getApi();
const participants = await lazyFetch(api, process.env.SOVIET_CONTRACT, process.env.COOPNAME, 'participants');
const participants = await lazyFetch(api, SovietContract.contractName.production, process.env.COOPNAME, 'participants');
return participants;
}
@@ -271,4 +475,8 @@ export {
failOrder,
completeOrder,
getSoviet,
getBlockchainInfo,
getBlockchainAccount,
hasActiveKey,
createBoard,
};
@@ -4,24 +4,25 @@ import { userService, blockchainService } from './index';
import { Cooperative, RegistratorContract, SovietContract } from 'cooptypes';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import logger from '../config/logger';
export const loadAgenda = async (coopname: string): Promise<Cooperative.Documents.IAgenda[]> => {
export const loadAgenda = async (coopname: string): Promise<Cooperative.Document.IAgenda[]> => {
const api = await blockchainService.getApi();
const decisions = (await blockchainService.lazyFetch(
api,
process.env.SOVIET_CONTRACT as string,
SovietContract.contractName.production as string,
coopname,
'decisions'
)) as SovietContract.Tables.Decisions.IDecision[];
const agenda = [] as Cooperative.Documents.IAgenda[];
const agenda = [] as Cooperative.Document.IAgenda[];
for (const table of decisions) {
const action = (
await getActions(`${process.env.SIMPLE_EXPLORER_API}/get-actions`, {
filter: JSON.stringify({
account: process.env.SOVIET_CONTRACT,
account: SovietContract.contractName.production,
name: SovietContract.Actions.Registry.NewSubmitted.actionName,
receiver: process.env.COOPNAME,
'data.decision_id': String(table.id),
@@ -39,7 +40,7 @@ export const loadAgenda = async (coopname: string): Promise<Cooperative.Document
export const loadStaff = async (coopname) => {
const api = await blockchainService.getApi();
const staff = await blockchainService.lazyFetch(api, process.env.SOVIET_CONTRACT, coopname, 'staff');
const staff = await blockchainService.lazyFetch(api, SovietContract.contractName.production, coopname, 'staff');
for (const staf of staff) {
const user = await userService.getUserByUsername(staf.username);
@@ -85,7 +86,9 @@ export const loadContacts = async (coopname: string) => {
if (coopAccount.meta) {
try {
announce = JSON.parse(coopAccount.meta);
} catch (e: any) {}
} catch (e: any) {
logger.warn(`Ошибка при получении контактов: ${e.message}`);
}
}
return {
@@ -1,14 +1,9 @@
import eosjsecc from 'eosjs-ecc';
import ApiError from '../utils/ApiError';
import { getActions, getTables } from '../utils/getFetch';
import * as coopService from './coop.service';
const { verify, sha256 } = eosjsecc;
import { getActions } from '../utils/getFetch';
import config from '../config/config';
import { Generator } from 'coopdoc-generator-ts';
import type { IGenerate, IGetDocuments } from '../types';
import type { IGenerate } from '../types';
import { Cooperative, SovietContract } from 'cooptypes';
import { User } from '../models';
import { IUser } from '../models/user.model';
export const generator = new Generator();
@@ -23,19 +18,27 @@ export const generateDocument = async (options: IGenerate) => {
// Шаг 1: Создание новой функции для сборки complexDocument
export async function buildComplexDocument(
raw_action_document: Cooperative.Blockchain.IAction
): Promise<Cooperative.Documents.IComplexDocument> {
let statement = {} as Cooperative.Documents.IComplexStatement;
let decision = {} as Cooperative.Documents.IComplexDecision;
let act = {} as Cooperative.Documents.IComplexAct;
): Promise<Cooperative.Document.IComplexDocument> {
let statement = {} as Cooperative.Document.IComplexStatement;
let decision = {} as Cooperative.Document.IComplexDecision;
const act = {} as Cooperative.Document.IComplexAct;
const links: Cooperative.Document.IGeneratedDocument[] = [];
const raw_document = raw_action_document.data as SovietContract.Actions.Registry.NewSubmitted.INewSubmitted;
// Готовим заявления
{
const document = await generator.getDocument({ hash: raw_document.document.hash });
if (document)
for (const link of document.meta.links) {
const linked_document = await generator.getDocument({ hash: link });
links.push(linked_document);
}
const user = await User.findOne({ username: raw_document.username });
if (user) {
if (user && user.type != 'service') {
const user_data = await user?.getPrivateData();
const action: Cooperative.Blockchain.IExtendedAction = {
@@ -44,8 +47,6 @@ export async function buildComplexDocument(
};
statement = { action, document };
} else {
// throw new ApiError(400, 'Ошибка, один из пользователей не найден. Обратитесь в поддержку.');
}
}
@@ -56,7 +57,7 @@ export async function buildComplexDocument(
const decision_action = (
await getActions(`${process.env.SIMPLE_EXPLORER_API}/get-actions`, {
filter: JSON.stringify({
account: process.env.SOVIET_CONTRACT,
account: SovietContract.contractName.production,
name: SovietContract.Actions.Registry.NewDecision.actionName,
receiver: process.env.COOPNAME,
'data.decision_id': String(raw_document.decision_id),
@@ -70,8 +71,6 @@ export async function buildComplexDocument(
const user = await User.findOne({ username: decision_action?.data?.username });
if (user) {
const user_data = user.getPrivateData();
decision_extended_action = {
...decision_action,
user: (await user?.getPrivateData()) || null,
@@ -79,6 +78,12 @@ export async function buildComplexDocument(
const document = await generator.getDocument({ hash: decision_action?.data?.document?.hash });
if (document)
for (const link of document.meta.links) {
const linked_document = await generator.getDocument({ hash: link });
links.push(linked_document);
}
decision = {
document,
action: decision_extended_action,
@@ -92,21 +97,21 @@ export async function buildComplexDocument(
}
// Готовим акты
const acts: Cooperative.Documents.IComplexAct[] = [];
const acts: Cooperative.Document.IComplexAct[] = [];
return { statement, decision, acts };
return { statement, decision, acts, links };
}
export const queryDocuments = async (
filter: any,
page: number = 1,
limit: number = 100
): Promise<Cooperative.Documents.IGetComplexDocuments> => {
page = 1,
limit = 100
): Promise<Cooperative.Document.IGetComplexDocuments> => {
const actions = await getActions<SovietContract.Actions.Registry.NewResolved.INewResolved>(
`${process.env.SIMPLE_EXPLORER_API}/get-actions`,
{
filter: JSON.stringify({
account: process.env.SOVIET_CONTRACT,
account: SovietContract.contractName.production,
name: SovietContract.Actions.Registry.NewResolved.actionName,
receiver: process.env.COOPNAME,
...filter,
@@ -116,7 +121,7 @@ export const queryDocuments = async (
}
);
let response: Cooperative.Documents.IGetComplexDocuments = {
const response: Cooperative.Document.IGetComplexDocuments = {
results: [],
page,
limit,
@@ -2,9 +2,9 @@ import { createTransport } from 'nodemailer';
import config from '../config/config';
import logger from '../config/logger';
const { email, env } = config
const { email, env } = config;
const transport = createTransport(email.smtp);
export const transport = createTransport(email.smtp);
/* istanbul ignore next */
if (env !== 'test') {
@@ -21,7 +21,7 @@ if (env !== 'test') {
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to, subject, text) => {
export const sendEmail = async (to, subject, text) => {
const msg = { from: email.from, to, subject, text };
await transport.sendMail(msg);
};
@@ -32,13 +32,33 @@ const sendEmail = async (to, subject, text) => {
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to, token) => {
const subject = 'Reset password';
// replace this url with the link to the reset password page of your front-end app
const resetPasswordUrl = `http://link-to-app/reset-password?token=${token}`;
const text = `Dear user,
To reset your password, click on this link: ${resetPasswordUrl}
If you did not request any password resets, then ignore this email.`;
export const sendResetKeyEmail = async (to, token) => {
const subject = 'Восстановление доступа';
const resetPasswordUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
const text = `Мы получили запрос на перевыпуск приватного ключа,
Для перевыпуска нажмите на ссылку: ${resetPasswordUrl}. Время действия ссылки - 10 минут.
Если вы не запрашивали перевыпуск ключа - проигнорируйте это сообщение.`;
await sendEmail(to, subject, text);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
export const sendInviteEmail = async (to, token) => {
const subject = 'Приглашение в Цифровой Кооператив';
const inviteUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
const text = `Вам отправлено приглашение на подключение к Цифровому Кооперативу в качестве действующего пайщика.
Для того, чтобы воспользоваться приглашением и получить ключ доступа, пожалуйста, нажмите на ссылку: ${inviteUrl}
Время действия ссылки - 24 часа.`;
await sendEmail(to, subject, text);
};
@@ -48,7 +68,7 @@ If you did not request any password resets, then ignore this email.`;
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to, token) => {
export const sendVerificationEmail = async (to, token) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
@@ -57,10 +77,3 @@ To verify your email, click on this link: ${verificationEmailUrl}
If you did not create an account, then ignore this email.`;
await sendEmail(to, subject, text);
};
export {
transport,
sendEmail,
sendResetPasswordEmail,
sendVerificationEmail,
};
@@ -7,3 +7,5 @@ export * as paymentService from './payment.service';
export * as documentService from './document.service';
export * as coopService from './coop.service';
export * as wsService from './ws.service';
export * as systemService from './system.service';
export * as participantService from './participant.service';
@@ -1 +1 @@
export default {};
@@ -0,0 +1,83 @@
import { PublicKey, Signature } from '@wharfkit/antelope';
import type { IDocument, IJoinCooperative } from '../types';
import ApiError from '../utils/ApiError';
import { getUserByUsername } from './user.service';
import http from 'http-status';
import TempDocument, { tempdocType } from '../models/tempDocument.model';
import mongoose from 'mongoose';
import { userStatus, type IUser } from '../models/user.model';
const verifyDocumentSignature = (user: IUser, document: IDocument): void => {
const { hash, public_key, signature } = document;
const publicKeyObj = PublicKey.from(public_key);
const signatureObj = Signature.from(signature);
const verified: boolean = signatureObj.verifyDigest(hash, publicKeyObj);
if (!verified) {
throw new ApiError(http.INTERNAL_SERVER_ERROR, 'Invalid signature');
}
if (user.public_key !== document.public_key) throw new ApiError(http.BAD_REQUEST, 'Public keys are mismatched');
};
/**
* Join a Cooperative
*
*/
export const joinCooperative = async (data: IJoinCooperative): Promise<void> => {
console.log('data: ', data);
const user = await getUserByUsername(data.username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
}
if (user.status !== userStatus['1_Created'] && user.status !== userStatus['2_Joined']) {
throw new ApiError(http.NOT_FOUND, 'Пользователь уже вступил в кооператив');
}
verifyDocumentSignature(user, data.statement);
verifyDocumentSignature(user, data.wallet_agreement);
verifyDocumentSignature(user, data.user_agreement);
verifyDocumentSignature(user, data.privacy_agreement);
verifyDocumentSignature(user, data.signature_agreement);
const session = await mongoose.startSession();
await session.withTransaction(async () => {
await TempDocument.findOneAndUpdate(
{ username: user.username, type: tempdocType.JoinStatement },
{ $set: { document: data.statement } },
{ upsert: true, new: true, session }
);
await TempDocument.findOneAndUpdate(
{ username: user.username, type: tempdocType.WalletAgreement },
{ $set: { document: data.wallet_agreement } },
{ upsert: true, new: true, session }
);
await TempDocument.findOneAndUpdate(
{ username: user.username, type: tempdocType.PrivacyAgreement },
{ $set: { document: data.privacy_agreement } },
{ upsert: true, new: true, session }
);
await TempDocument.findOneAndUpdate(
{ username: user.username, type: tempdocType.SignatureAgreement },
{ $set: { document: data.signature_agreement } },
{ upsert: true, new: true, session }
);
await TempDocument.findOneAndUpdate(
{ username: user.username, type: tempdocType.UserAgreement },
{ $set: { document: data.user_agreement } },
{ upsert: true, new: true, session }
);
user.status = userStatus['2_Joined'];
await user.save({ session });
});
session.endSession();
};
@@ -11,7 +11,8 @@ import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import { ICreatedPayment, IYandexIPN, PaymentDetails } from '../types/common';
import { Cooperative } from 'cooptypes';
import type { Filter } from 'mongodb';
import { FilterQuery } from 'mongoose';
import { userStatus } from '../models/user.model';
const { connection } = mongoose;
@@ -19,11 +20,11 @@ export const savePaymentMethod = async (data: Cooperative.Payments.IPaymentData)
return await generator.save('paymentMethod', data);
};
export const deletePaymentMethod = async (filter: Filter<Cooperative.Payments.IPaymentData>) => {
export const deletePaymentMethod = async (filter: FilterQuery<Cooperative.Payments.IPaymentData>) => {
return await generator.del('paymentMethod', filter as any);
};
export const listPaymentMethods = async (filter: Filter<Cooperative.Payments.IPaymentData>) => {
export const listPaymentMethods = async (filter: FilterQuery<Cooperative.Payments.IPaymentData>) => {
return await generator.list('paymentMethod', filter as any);
};
@@ -40,7 +41,7 @@ function getAmountPlusFee(amount: number, provider: string): number {
}
export async function createDeposit(username: string, data: ICreateDeposit): Promise<ICreatedPayment> {
let cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
if (!cooperative) throw new Error('Кооператив не найден');
@@ -50,7 +51,7 @@ export async function createDeposit(username: string, data: ICreateDeposit): Pro
type: 'deposit',
data: { username, provider: data.provider, quantity: data.quantity },
});
const internal_id = db_order._id;
const internal_id = db_order.id;
let order_id = 0;
@@ -118,9 +119,9 @@ export async function createDeposit(username: string, data: ICreateDeposit): Pro
}
export async function createInitialOrder(username: string, data: ICreateInitialPayment): Promise<ICreatedPayment> {
let cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
if (!cooperative) throw new Error('Кооператив не найден');
if (!cooperative) throw new ApiError(httpStatus.BAD_REQUEST, 'Кооператив не найден');
const user = await getUserByUsername(username);
let amount = '';
@@ -139,7 +140,7 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
type: 'registration',
data: { provider: data.provider, username, quantity: amount },
});
const internal_id = db_order._id;
const internal_id = db_order.id;
if (data.provider === 'yookassa') {
// 2. Используешь внутренний айди для получения ордера в системе платежей
@@ -198,7 +199,10 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
}
export async function catchIPN(ipnBody: IYandexIPN) {
if (!connection.db) throw new Error('Нет подключения');
const payments = await connection.db.collection('payments');
const exist = await payments.findOne({ 'object.id': ipnBody.object.id });
if (!exist) {
@@ -237,8 +241,8 @@ export async function catchIPN(ipnBody: IYandexIPN) {
memo: '',
});
console.log('Зарегистрирован новый пользователь: ', user.username);
user.status = 'registered';
logger.info('Зарегистрирован новый пользователь: ', user.username);
user.status = userStatus['4_Registered'];
user.is_registered = true;
} else if (order.type === 'deposit') {
await blockchainService.completeOrder({
@@ -248,7 +252,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
memo: '',
});
const quantity = parseFloat(order.data.quantity);
console.log(`Принят паевый взнос пользователя: ${user.username} на сумму ${order.data.quantity}`);
logger.info(`Принят паевый взнос пользователя: ${user.username} на сумму ${order.data.quantity}`);
}
await user.save();
@@ -256,7 +260,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
order.delivered = true;
await order.save();
} catch (e: any) {
console.error(e);
logger.warn(e);
order.error = e;
order.delivered = false;
await order.save();
@@ -268,7 +272,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
memo: '',
});
user.status = 'failed';
user.status = userStatus['10_Failed'];
user.message = e.message;
await user.save();
}
@@ -281,6 +285,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
});
}
} else {
logger.warn(`Ордер не найден: `, ipnBody);
throw new Error(`Ордер не найден`);
}
}
@@ -0,0 +1,137 @@
import httpStatus from 'http-status';
import { Mono } from '../models';
import ApiError from '../utils/ApiError';
import config from '../config/config';
import logger from '../config/logger';
import { IAddUser, ICreateUser, IHealthStatus, IInstall } from '../types';
import { generateUsername } from '../../tests/utils/generateUsername';
import { generator } from './document.service';
import { blockchainService, emailService, tokenService, userService } from '.';
import { IUser, userStatus } from '../models/user.model';
import axios from 'axios';
import { getBlockchainInfo } from './blockchain.service';
import { RegistratorContract, type Cooperative } from 'cooptypes';
import type { ISetVars } from '../types/auto-generated/system.validation';
import { VarsSchema } from 'coopdoc-generator-ts';
export const install = async (soviet: IInstall): Promise<void> => {
const mono = await Mono.findOne({ coopname: config.coopname });
const info = await getBlockchainInfo();
const coop = await blockchainService.getCooperative(config.coopname);
if (!coop) throw new ApiError(httpStatus.BAD_REQUEST, 'Информация о кооперативе не обнаружена');
if (mono && mono.status != 'install') {
throw new ApiError(httpStatus.BAD_REQUEST, 'Установка уже выполнена');
}
const users = [] as IUser[];
const members = [] as any;
const sovietExt = [] as any;
try {
for (const member of soviet) {
const username = generateUsername();
sovietExt.push({ ...member, username });
const addUser: RegistratorContract.Actions.AddUser.IAddUser = {
registrator: config.service_username,
coopname: config.coopname,
referer: '',
username,
type: 'individual',
created_at: info.head_block_time,
initial: coop.initial,
minimum: coop.minimum,
spread_initial: false,
meta: '',
};
await blockchainService.addUser(addUser);
const createUser: ICreateUser = {
email: member.individual_data.email,
individual_data: member.individual_data,
referer: '',
role: 'user',
type: 'individual',
username,
};
const user = await userService.createUser(createUser);
user.status = userStatus['4_Registered'];
user.is_registered = true;
await user.save();
//Генерируем токен и отправляем приглашение
const token = await tokenService.generateInviteToken(member.individual_data.email);
await emailService.sendInviteEmail(member.individual_data.email, token);
//Добавляем в массив членов для отправки в бч
members.push({
username: username,
is_voting: true,
position_title: member.role === 'chairman' ? 'Председатель совета' : 'Член совета',
position: member.role,
});
users.push(user);
}
const chairman = sovietExt.find((el) => el.role == 'chairman');
//TODO создаёт доску совета
await blockchainService.createBoard({
coopname: config.coopname,
username: config.service_username,
type: 'soviet',
members: members,
name: 'Совет',
description: '',
});
} catch (e: any) {
console.log('on error', e);
for (const user of users) {
await userService.deleteUserByUsername(user.username);
await generator.del('individual', { username: user.username });
}
throw new ApiError(httpStatus.BAD_REQUEST, e.message);
}
await Mono.updateOne(
{ coopname: config.coopname },
{
status: 'active',
}
);
logger.info('MONO активирован');
};
export const init = async (): Promise<void> => {
const mono = await Mono.findOne({ coopname: config.coopname });
if (!mono)
await Mono.create({
coopname: config.coopname,
status: 'install',
});
logger.info('MONO инициализирован');
};
export const getMonoStatus = async (): Promise<IHealthStatus> => {
const mono = await Mono.findOne({ coopname: config.coopname });
if (!mono) throw new ApiError(httpStatus.BAD_REQUEST, 'Установщик не найден');
return mono.status;
};
export const setVars = async (vars: ISetVars): Promise<void> => {
await generator.save('vars', vars);
};
export const getVarsSchema = async (): Promise<unknown> => {
return VarsSchema;
};
@@ -48,12 +48,17 @@ export const saveToken = async (token, userId, expires, type, blacklisted = fals
/**
* Verify token and return token doc (or throw an error if it is not valid)
* @param {string} token
* @param {string} type
* @param {string[]} types
* @returns {Promise<Token>}
*/
export const verifyToken = async (token, type) => {
export const verifyToken = async (token, types) => {
const payload = jwt.verify(token, config.jwt.secret);
const tokenDoc = await Token.findOne({ token, type, user: payload.sub, blacklisted: false });
const tokenDoc = await Token.findOne({
token,
$or: types.map((type) => ({ type })),
user: payload.sub,
blacklisted: false,
});
if (!tokenDoc) {
throw new Error('Token not found');
}
@@ -68,7 +73,7 @@ export const verifyToken = async (token, type) => {
export const generateServiceAccessToken = async (user) => {
const accessTokenExpires = moment().add(100, 'years'); // Пример с очень долгим сроком действия
const accessToken = generateToken(user.id, accessTokenExpires, tokenTypes.ACCESS);
console.log('on generate', accessToken);
return {
access: {
token: accessToken,
@@ -107,15 +112,31 @@ export const generateAuthTokens = async (user) => {
* @param {string} email
* @returns {Promise<string>}
*/
export const generateResetPasswordToken = async (email) => {
export const generateResetKeyToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(user.id, expires, tokenTypes.RESET_PASSWORD);
await saveToken(resetPasswordToken, user.id, expires, tokenTypes.RESET_PASSWORD);
return resetPasswordToken;
const resetKeyToken = generateToken(user.id, expires, tokenTypes.RESET_PASSWORD);
await saveToken(resetKeyToken, user.id, expires, tokenTypes.RESET_PASSWORD);
return resetKeyToken;
};
/**
* Generate invite token
* @param {string} email
* @returns {Promise<string>}
*/
export const generateInviteToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.inviteExpirationMinutes, 'minutes');
const inviteToken = generateToken(user.id, expires, tokenTypes.INVITE);
await saveToken(inviteToken, user.id, expires, tokenTypes.INVITE);
return inviteToken;
};
/**
@@ -2,21 +2,15 @@ import http from 'http-status';
import { User } from '../models';
import ApiError from '../utils/ApiError';
import { generator } from './document.service';
import { ICreateUser, IJoinCooperative } from '../types/user.validation';
import ecc from 'eosjs-ecc';
import { ICreateUser } from '../types/auto-generated/user.validation';
import { PublicKey, Signature } from '@wharfkit/antelope';
import faker from 'faker';
import { randomBytes } from 'crypto';
export const createServiceUser = async (username: string) => {
const password = randomBytes(16).toString('hex');
return User.create({
username,
type: 'service',
role: 'service',
public_key: 'thanks-no-need',
password,
email: faker.internet.email().toLowerCase(),
});
};
@@ -28,6 +22,7 @@ export const createServiceUser = async (username: string) => {
export const createUser = async (userBody: ICreateUser) => {
//TODO проверяем на существование пользователя
//допускаем обновление личных данных, если пользователь находится в статусе 'created'
const exist = await User.findOne({ email: userBody.email });
if (userBody.type === 'individual' && !userBody.individual_data)
@@ -62,35 +57,6 @@ export const createUser = async (userBody: ICreateUser) => {
}
};
/**
* Join a Cooperative
*
*/
export const joinCooperative = async (data: IJoinCooperative): Promise<void> => {
const user = await getUserByUsername(data.username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
}
user.statement = data.statement;
user.status = 'joined';
const hash = data.statement.hash;
const public_key = PublicKey.from(data.statement.public_key);
const signature = Signature.from(data.statement.signature);
const verified: boolean = signature.verifyDigest(hash, public_key);
if (!verified) {
throw new ApiError(http.INTERNAL_SERVER_ERROR, 'Invalid signature');
}
if (user.public_key !== data.statement.public_key) throw new ApiError(http.BAD_REQUEST, 'Public keys are mismatched');
await user.save();
};
/**
* Query for users
* @param {Object} filter - Mongo filter
@@ -186,11 +152,11 @@ export const updateUserByUsername = async (username, updateBody) => {
* @param {string} username
* @returns {Promise<User>}
*/
export const deleteUserById = async (username) => {
export const deleteUserByUsername = async (username) => {
const user = await getUserByUsername(username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
}
await user.remove();
await user.deleteOne();
return user;
};
@@ -16,7 +16,9 @@ export const updateBoard = async (action: SovietContract.Actions.Boards.UpdateBo
await user.save();
}
const chairman = await userService.getUserByUsername(action.chairman);
const chairman_username = action.members.find((el) => el.position == 'chairman')?.username;
const chairman = await userService.getUserByUsername(chairman_username as string);
chairman.role = 'chairman';
await chairman.save();
};
@@ -9,7 +9,8 @@ export interface IForgotPassword {
export interface ILogin {
email: string;
password: string;
now: string;
signature: string;
}
export interface ILogout {
@@ -20,11 +21,8 @@ export interface IRefreshTokens {
refreshToken: string;
}
export interface IResetPasswordBody {
password: string;
}
export interface IResetPasswordQuery {
export interface IResetKeyBody {
public_key: string;
token: string;
}
@@ -32,7 +30,7 @@ export interface IVerifyEmail {
token: string;
}
export interface RForgotPassword {
export interface RForgotKey {
body: {
email: string;
};
@@ -41,7 +39,8 @@ export interface RForgotPassword {
export interface RLogin {
body: {
email: string;
password: string;
now: string;
signature: string;
};
}
@@ -57,11 +56,9 @@ export interface RRefreshTokens {
};
}
export interface RResetPassword {
export interface RResetKey {
body: {
password: string;
};
query: {
public_key: string;
token: string;
};
}
@@ -8,13 +8,13 @@ export interface IGenerate {
* Unknown Property
*/
[x: string]: unknown;
action: string;
block_num?: number;
code: string;
coopname: string;
created_at?: string;
generator?: string;
lang?: 'ru';
links?: string[];
registry_id: number;
timezone?: string;
username: string;
version?: string;
@@ -39,13 +39,13 @@ export interface RGenerate {
* Unknown Property
*/
[x: string]: unknown;
action: string;
block_num?: number;
code: string;
coopname: string;
created_at?: string;
generator?: string;
lang?: 'ru';
links?: string[];
registry_id: number;
timezone?: string;
username: string;
version?: string;
@@ -0,0 +1,13 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export * from './auth.validation';
export * from './coop.validation';
export * from './document.validation';
export * from './notify.validation';
export * from './participant.validation';
export * from './payment.validation';
export * from './system.validation';
export * from './user.validation';
@@ -0,0 +1,32 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type IInstall = ({
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
})[];
export interface RInstall {
body: ({
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
})[];
}
@@ -0,0 +1,81 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface IDocument {
hash: string;
meta: object;
public_key: string;
signature: string;
}
export interface IJoinCooperative {
privacy_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
signature_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
statement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
user_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
username: string;
wallet_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
}
export interface RJoinCooperative {
body: {
privacy_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
signature_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
statement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
user_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
username: string;
wallet_agreement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
};
}
@@ -0,0 +1,100 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type IInstall = {
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
}[];
export interface ISetVars {
/**
* Unknown Property
*/
[x: string]: unknown;
confidential_email: string;
confidential_link: string;
contact_email: string;
coopname: string;
full_abbr: string;
full_abbr_dative: string;
full_abbr_genitive: string;
name: string;
privacy_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
short_abbr: string;
signature_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
user_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
wallet_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
website: string;
}
export interface RInstall {
body: {
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
}[];
}
export interface RSetVars {
body: {
/**
* Unknown Property
*/
[x: string]: unknown;
confidential_email: string;
confidential_link: string;
contact_email: string;
coopname: string;
full_abbr: string;
full_abbr_dative: string;
full_abbr_genitive: string;
name: string;
privacy_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
short_abbr: string;
signature_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
user_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
wallet_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
website: string;
};
}
@@ -3,6 +3,84 @@
* Do not modify this file manually
*/
export interface IAddUser {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
}
export interface ICreateUser {
email: string;
entrepreneur_data?: {
@@ -73,21 +151,13 @@ export interface ICreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
public_key?: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
export interface IDocument {
hash: string;
meta: object;
public_key: string;
signature: string;
}
export interface IEntrepreneurData {
bank_account: {
account_number: string;
@@ -125,16 +195,6 @@ export interface IIndividualData {
phone: string;
}
export interface IJoinCooperative {
statement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
username: string;
}
export interface IOrganizationData {
bank_account: {
account_number: string;
@@ -169,6 +229,86 @@ export interface IOrganizationData {
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
}
export interface RAddUser {
body: {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
};
}
export interface RCreateUser {
body: {
email: string;
@@ -240,10 +380,9 @@ export interface RCreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
public_key?: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
};
@@ -265,22 +404,9 @@ export interface RGetUsers {
};
}
export interface RJoinCooperative {
body: {
statement: {
hash: string;
meta: object;
public_key: string;
signature: string;
};
username: string;
};
}
export interface RUpdateUser {
body?: {
email?: string;
password?: string;
};
params?: {
username: string;
+2 -2
View File
@@ -59,7 +59,7 @@ export interface ICreatedPayment {
details: PaymentDetails;
}
export type IGetResponse<T> = Cooperative.Documents.IGetResponse<T>;
export type IGetResponse<T> = Cooperative.Document.IGetResponse<T>;
export interface IGetActions<T> {
results: IAction[];
@@ -122,7 +122,7 @@ export interface IAction {
elapsed: number;
}
export interface IExtendedTable extends ITable {}
export type IExtendedTable = ITable;
export interface IExtendedAction extends IAction {
user: IIndividualData | IEntrepreneurData | IOrganizationData | null;
@@ -1,74 +0,0 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface IGenerate {
/**
* Unknown Property
*/
[x: string]: unknown;
action: string;
block_num?: number;
code: string;
coopname: string;
created_at?: string;
generator?: string;
lang?: 'ru';
timezone?: string;
username: string;
version?: string;
}
export interface IGetDocuments {
/**
* Unknown Property
*/
[x: string]: unknown;
filter?: {
receiver?: string;
};
limit?: number;
page?: number;
sortBy?: string;
}
export interface RGenerate {
body: {
/**
* Unknown Property
*/
[x: string]: unknown;
action: string;
block_num?: number;
code: string;
coopname: string;
created_at?: string;
generator?: string;
lang?: 'ru';
timezone?: string;
username: string;
version?: string;
};
}
export interface RGetDocuments {
query?: {
/**
* Unknown Property
*/
[x: string]: unknown;
filter?: {
receiver?: string;
};
limit?: number;
page?: number;
sortBy?: string;
};
}
export interface RGetMyDocuments {
query?: {
username?: string;
};
}
+3 -11
View File
@@ -1,11 +1,3 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export * from './auth.validation';
export * from './coop.validation';
export * from './document.validation';
export * from './notify.validation';
export * from './payment.validation';
export * from './user.validation';
export * from './auto-generated';
export * from './mono.types';
export * from './common';
@@ -0,0 +1,8 @@
import { GetInfoResult } from 'eosjs/dist/eosjs-rpc-interfaces';
export type IHealthStatus = 'install' | 'active' | 'maintenance';
export interface IHealthResponse {
status: IHealthStatus;
blockchain: GetInfoResult;
}
@@ -1,100 +0,0 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface ICreateDeposit {
provider: 'yookassa';
quantity: string;
}
export interface ICreateInitialPayment {
provider: 'yookassa';
}
export interface IRecieveIPN {
event: string;
object: {
/**
* Unknown Property
*/
[x: string]: unknown;
amount: {
/**
* Unknown Property
*/
[x: string]: unknown;
currency: string;
value: string;
};
authorization_details?: {
/**
* Unknown Property
*/
[x: string]: unknown;
auth_code?: string;
rrn?: string;
three_d_secure?: {
/**
* Unknown Property
*/
[x: string]: unknown;
applied?: boolean;
};
};
created_at?: string;
description?: string;
expires_at?: string;
id: string;
income_amount: {
/**
* Unknown Property
*/
[x: string]: unknown;
currency: string;
value: string;
};
metadata?: {
/**
* Unknown Property
*/
[x: string]: unknown;
};
paid: boolean;
payment_method: {
/**
* Unknown Property
*/
[x: string]: unknown;
card?: {
/**
* Unknown Property
*/
[x: string]: unknown;
card_type?: string;
expiry_month?: string;
expiry_year?: string;
first6?: string;
issuer_country?: string;
issuer_name?: string;
last4?: string;
};
id: string;
saved?: boolean;
title?: string;
type: string;
};
refundable?: boolean;
refunded_amount: {
/**
* Unknown Property
*/
[x: string]: unknown;
currency: string;
value: string;
};
status: string;
test?: boolean;
};
type: string;
}
@@ -14,28 +14,26 @@ export const IForgotPassword = Joi.object({
email: Joi.string().email().required(),
});
export const RForgotPassword = Joi.object({
export const RForgotKey = Joi.object({
body: IForgotPassword.required(),
});
// Внутренние параметры
export const ILogin = Joi.object({
signature: Joi.string().required(),
email: Joi.string().email().required(),
password: Joi.string().required(),
now: Joi.string().required(),
});
export const ILogout = Joi.object({
refreshToken: Joi.string().required(),
});
export const IResetPasswordQuery = Joi.object({
export const IResetKeyBody = Joi.object({
public_key: Joi.string().required(),
token: Joi.string().required(),
});
export const IResetPasswordBody = Joi.object({
password: Joi.string().required().custom(password),
});
export const IVerifyEmail = Joi.object({
token: Joi.string().required(),
});
@@ -49,9 +47,8 @@ export const RLogout = Joi.object({
body: ILogout.required(),
});
export const RResetPassword = Joi.object({
query: IResetPasswordQuery.required(),
body: IResetPasswordBody.required(),
export const RResetKey = Joi.object({
body: IResetKeyBody.required(),
});
export const RVerifyEmail = Joi.object({
@@ -1,8 +1,7 @@
import * as Joi from 'joi';
export const IGenerate = Joi.object({
code: Joi.string().required(),
action: Joi.string().required(),
registry_id: Joi.number().required(),
coopname: Joi.string().required(),
username: Joi.string().required(),
generator: Joi.string().optional(),
@@ -11,6 +10,7 @@ export const IGenerate = Joi.object({
created_at: Joi.string().optional(),
block_num: Joi.number().optional(),
timezone: Joi.string().optional(),
links: Joi.array().items(Joi.string()).default([]),
}).unknown(true);
export const RGenerate = Joi.object({
@@ -5,3 +5,5 @@ export * as orderValidation from './payment.validation';
export * as coopValidation from './coop.validation';
export * as customValidation from './custom.validation';
export * as notifyValidation from './notify.validation';
export * as systemValidation from './system.validation';
export * as participantValidation from './participant.validation';
@@ -0,0 +1,21 @@
import * as Joi from 'joi';
export const IDocument = Joi.object().keys({
hash: Joi.string().required(),
signature: Joi.string().required(),
public_key: Joi.string().required(),
meta: Joi.object().required(),
});
export const IJoinCooperative = Joi.object({
username: Joi.string().required(),
statement: IDocument.required(),
wallet_agreement: IDocument.required(),
signature_agreement: IDocument.required(),
privacy_agreement: IDocument.required(),
user_agreement: IDocument.required(),
});
export const RJoinCooperative = Joi.object({
body: IJoinCooperative.required(),
});
@@ -0,0 +1,46 @@
import * as Joi from 'joi';
import { IIndividualData } from './user.validation';
export const IInstall = Joi.array().items(
Joi.object({
role: Joi.string().required().valid('chairman', 'member'),
individual_data: IIndividualData.required(),
})
);
export const RInstall = Joi.object({
body: IInstall.required(),
});
export const ISetVars = Joi.object({
coopname: Joi.string().required(),
full_abbr: Joi.string().required(),
full_abbr_genitive: Joi.string().required(),
full_abbr_dative: Joi.string().required(),
short_abbr: Joi.string().required(),
website: Joi.string().required(),
name: Joi.string().required(),
confidential_link: Joi.string().required(),
confidential_email: Joi.string().required(),
contact_email: Joi.string().required(),
wallet_agreement: Joi.object({
protocol_number: Joi.string().required(),
protocol_day_month_year: Joi.string().required(),
}).required(),
signature_agreement: Joi.object({
protocol_number: Joi.string().required(),
protocol_day_month_year: Joi.string().required(),
}).required(),
privacy_agreement: Joi.object({
protocol_number: Joi.string().required(),
protocol_day_month_year: Joi.string().required(),
}).required(),
user_agreement: Joi.object({
protocol_number: Joi.string().required(),
protocol_day_month_year: Joi.string().required(),
}).required(),
}).unknown(true);
export const RSetVars = Joi.object({
body: ISetVars.required(),
});
@@ -1,5 +1,5 @@
import * as Joi from 'joi';
import { password, objectId } from './custom.validation';
import { password } from './custom.validation';
import { IBankAccount } from './payment.validation';
export const IIndividualData = Joi.object({
@@ -57,9 +57,8 @@ export const IEntrepreneurData = Joi.object({
export const ICreateUser = Joi.object({
email: Joi.string().required().email(),
password: Joi.string().required().custom(password),
role: Joi.string().required().valid('user', 'chairman', 'member'),
public_key: Joi.string().required(),
role: Joi.string().required().valid('user'),
public_key: Joi.string().optional(),
username: Joi.string().required().length(12),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
@@ -68,11 +67,23 @@ export const ICreateUser = Joi.object({
entrepreneur_data: IEntrepreneurData.optional(),
});
export const IDocument = Joi.object().keys({
hash: Joi.string().required(),
signature: Joi.string().required(),
public_key: Joi.string().required(),
meta: Joi.object().required(),
export const IAddUser = Joi.object({
email: Joi.string().required().email(),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
individual_data: IIndividualData.optional(),
organization_data: IOrganizationData.optional(),
entrepreneur_data: IEntrepreneurData.optional(),
spread_initial: Joi.boolean().required(),
created_at: Joi.string().required(),
initial: Joi.string().required(),
minimum: Joi.string().required(),
});
export const RAddUser = Joi.object({
body: IAddUser.required(),
});
// API
@@ -104,20 +115,10 @@ export const RUpdateUser = Joi.object({
body: Joi.object()
.keys({
email: Joi.string().email(),
password: Joi.string().custom(password),
})
.min(1),
});
export const IJoinCooperative = Joi.object({
username: Joi.string().required(),
statement: IDocument.required(),
});
export const RJoinCooperative = Joi.object({
body: IJoinCooperative.required(),
});
/**
* @swagger
* components:
+6 -6
View File
@@ -27,12 +27,12 @@ export const installInitialCooperativeData = async () => {
is_cooperative: true,
is_branched: false,
coop_type: 'conscoop',
registration: '2.0000 TRUB',
initial: '1.0000 TRUB',
minimum: '1.0000 TRUB',
org_registration: '20.0000 TRUB',
org_initial: '10.0000 TRUB',
org_minimum: '10.0000 TRUB',
registration: '200.0000 TRUB',
initial: '100.0000 TRUB',
minimum: '100.0000 TRUB',
org_registration: '2000.0000 TRUB',
org_initial: '1000.0000 TRUB',
org_minimum: '1000.0000 TRUB',
} as RegistratorContract.Tables.Cooperatives.ICooperative);
await insertDelta(delta1);
@@ -1,17 +1,11 @@
import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';
import faker from 'faker';
import User from '../../src/models/user.model';
import { generateUsername } from '../utils/generateUsername';
import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
const password = 'password1';
const email1 = faker.internet.email().toLowerCase();
export const participantOne: ICreateUser = {
email: email1,
password,
username: generateUsername(),
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
+6 -10
View File
@@ -7,10 +7,6 @@ import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
import { ObjectId } from 'mongodb';
const password = 'password1';
const salt = bcrypt.genSaltSync(8);
const hashedPassword = bcrypt.hashSync(password, salt);
const generateRandomId = () => new mongoose.Types.ObjectId();
type testUser = Omit<IUser, 'getPrivateData' | 'isPasswordMatch' | 'private_data'> & {
@@ -33,8 +29,8 @@ const adminUsername = generateUsername();
export const admin: testUser = {
_id: generateRandomId(),
email: email1,
password,
status: 'active',
has_account: false,
message: '',
is_registered: true,
role: 'chairman',
@@ -67,7 +63,7 @@ const usernameOne = generateUsername();
export const userOne: testUser = {
_id: generateRandomId(),
email: email2,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -101,7 +97,7 @@ const usernameTwo = generateUsername();
export const userTwo: testUser = {
_id: generateRandomId(),
email: email3,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -134,7 +130,7 @@ export const userTwo: testUser = {
export const chairman: testUser = {
_id: generateRandomId(),
email: email4,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -167,7 +163,7 @@ export const chairman: testUser = {
export const voskhod: testUser = {
_id: generateRandomId(),
email: email5,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -254,7 +250,7 @@ export const insertUsers = async (users: testUser[]) => {
for (const user of users) {
const { type, individual_data, organization_data, entrepreneur_data, block_num, ...rest } = user;
await User.insertMany([{ ...rest, type, password: hashedPassword }]);
await User.insertMany([{ ...rest, type }]);
if (type === 'individual' && individual_data) {
await insertPrivateIndividualUserData({
@@ -16,6 +16,8 @@ import { tokenTypes } from '../../src/config/tokens';
import { userOne, admin, insertUsers } from '../fixtures/user.fixture';
import { userOneAccessToken, adminAccessToken } from '../fixtures/token.fixture';
import { generateUsername } from '../utils/generateUsername';
import { getBlockchainInfo } from '../../src/services/blockchain.service';
import { Bytes, Checksum256, PrivateKey } from '@wharfkit/session';
setupTestDB();
@@ -27,7 +29,6 @@ describe('Auth routes', () => {
newUser = {
email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -48,8 +49,6 @@ describe('Auth routes', () => {
test('should return 201 and successfully register user if request data is ok', async () => {
const res = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(res.body.user).not.toHaveProperty('password');
expect(res.body.user).toEqual({
id: expect.anything(),
email: newUser.email,
@@ -74,7 +73,6 @@ describe('Auth routes', () => {
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
expect(dbUser).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
expect(res.body.tokens).toEqual({
@@ -95,30 +93,24 @@ describe('Auth routes', () => {
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
newUser.password = 'password';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
newUser.password = '11111111';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/login', () => {
test('should return 200 and login user if email and password match', async () => {
test('should return 200 and login user if email and signature match', async () => {
await insertUsers([userOne]);
const now = (await getBlockchainInfo()).head_block_time;
const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const signature = privateKey.signDigest(checksum);
const loginCredentials = {
email: userOne.email,
password: userOne.password,
now,
signature,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials);
@@ -140,27 +132,46 @@ describe('Auth routes', () => {
});
test('should return 401 error if there are no users with that email', async () => {
const now = (await getBlockchainInfo()).head_block_time;
const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const signature = privateKey.signDigest(checksum);
const loginCredentials = {
now,
email: userOne.email,
password: userOne.password,
signature,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Пользователь не найден' });
});
test('should return 401 error if password is wrong', async () => {
await insertUsers([userOne]);
const loginCredentials = {
email: userOne.email,
password: 'wrongPassword1',
};
// test('should return 401 error if signature is wrong', async () => {
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
// Восстановить проверку. Необходимо подложить аккаунт тестового юзера в коде вместо обращения к блокчейну.
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
});
// await insertUsers([userOne]);
// const now = (await getBlockchainInfo()).head_block_time;
// const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
// const bytes = Bytes.fromString(now, 'utf8');
// const checksum = Checksum256.hash(bytes);
// const signature = privateKey.signDigest(checksum);
// const now2 = '2024-08-02T11:50:40.000';
// const loginCredentials = {
// email: userOne.email,
// now: now2,
// signature,
// };
// const res = await request(app).post('/v1/auth/login').send(loginCredentials);
// console.log(res.body);
// expect(res.status).toBe(httpStatus.UNAUTHORIZED);
// expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
// });
});
describe('POST /v1/auth/logout', () => {
@@ -260,47 +271,46 @@ describe('Auth routes', () => {
});
});
describe('POST /v1/auth/forgot-password', () => {
describe('POST /v1/auth/lost-key', () => {
beforeEach(() => {
jest.spyOn(emailService.transport, 'sendMail').mockResolvedValue();
});
test('should return 204 and send reset password email to the user', async () => {
test('should return 204 and send reset key email to the user', async () => {
await insertUsers([userOne]);
const sendResetPasswordEmailSpy = jest.spyOn(emailService, 'sendResetPasswordEmail');
const sendResetKeyEmailSpy = jest.spyOn(emailService, 'sendResetKeyEmail');
await request(app).post('/v1/auth/forgot-password').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
await request(app).post('/v1/auth/lost-key').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
expect(sendResetPasswordEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetPasswordToken = sendResetPasswordEmailSpy.mock.calls[0][1];
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetPasswordToken, user: userOne._id.toString() });
expect(sendResetKeyEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetKeyToken = sendResetKeyEmailSpy.mock.calls[0][1];
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetKeyToken, user: userOne._id.toString() });
expect(dbResetPasswordTokenDoc).toBeDefined();
});
test('should return 400 if email is missing', async () => {
await insertUsers([userOne]);
const res = await request(app).post('/v1/auth/forgot-password').send();
const res = await request(app).post('/v1/auth/lost-key').send();
expect(res.status).toBe(httpStatus.BAD_REQUEST);
});
test('should return 404 if email does not belong to any user', async () => {
await request(app).post('/v1/auth/forgot-password').send({ email: userOne.email }).expect(httpStatus.NOT_FOUND);
await request(app).post('/v1/auth/lost-key').send({ email: userOne.email }).expect(httpStatus.NOT_FOUND);
});
});
describe('POST /v1/auth/reset-password', () => {
test('should return 204 and reset the password', async () => {
describe('POST /v1/auth/reset-key', () => {
test('should return 204 and reset the key', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const res = await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' });
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: userOne.public_key });
expect(res.status).toBe(httpStatus.NO_CONTENT);
@@ -308,9 +318,6 @@ describe('Auth routes', () => {
expect(dbUser).not.toBeUndefined();
if (dbUser) {
const isPasswordMatch = await bcrypt.compare('password2', dbUser.password);
expect(isPasswordMatch).toBe(true);
const dbResetPasswordTokenCount = await Token.countDocuments({
user: userOne._id.toString(),
type: tokenTypes.RESET_PASSWORD,
@@ -319,76 +326,49 @@ describe('Auth routes', () => {
}
});
test('should return 400 if reset password token is missing', async () => {
test('should return 400 if reset key token is missing', async () => {
await insertUsers([userOne]);
await request(app).post('/v1/auth/reset-password').send({ password: 'password2' }).expect(httpStatus.BAD_REQUEST);
});
test('should return 401 if reset password token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD, true);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.post('/v1/auth/reset-key')
.send({ public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.BAD_REQUEST);
});
test('should return 401 if reset key token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD, true);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 if reset password token is expired', async () => {
test('should return 401 if reset key token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 if user is not found', async () => {
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 400 if password is missing or invalid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app).post('/v1/auth/reset-password').query({ token: resetPasswordToken }).expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'short1' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: '11111111' })
.expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/send-verification-email', () => {
@@ -56,7 +56,7 @@ describe('Проверка получения документов', () => {
skip_save: false,
};
let res = await request(app)
const res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
@@ -26,7 +26,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,7 +50,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -67,7 +67,6 @@ describe('Проверка получения документов', () => {
const email = faker.internet.email().toLowerCase();
const newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -144,7 +143,7 @@ describe('Проверка получения документов', () => {
statement: signedDocument,
};
let joincoop_result = await request(app)
const joincoop_result = await request(app)
.post('/v1/users/join-cooperative')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(joinCoopData);
@@ -4,10 +4,10 @@ import app from '../../src/app';
import { setupTestDB } from '../utils/setupTestDB';
import { User } from '../../src/models';
import { userOne, userTwo, admin, insertUsers } from '../fixtures/user.fixture';
import { userOneAccessToken, adminAccessToken } from '../fixtures/token.fixture';
import { userOneAccessToken, adminAccessToken, userTwoAccessToken } from '../fixtures/token.fixture';
import request from 'supertest';
import { generateUsername } from '../utils/generateUsername';
import { IDocument, IIndividualData, IJoinCooperative } from '../../src/types';
import { IIndividualData } from '../../src/types';
setupTestDB();
@@ -19,7 +19,6 @@ describe('User routes', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,11 +50,9 @@ describe('User routes', () => {
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user).not.toHaveProperty('password');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -65,23 +62,15 @@ describe('User routes', () => {
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: newUser.role, is_email_verified: false });
});
test('should be able to create an admin as well', async () => {
test('should not be able to create an admin as well', async () => {
await insertUsers([admin]);
newUser.role = 'member';
const res = await request(app)
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user.role).toBe('member');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser?.role).toBe('member');
.expect(httpStatus.BAD_REQUEST);
});
test('успешная регистрация без токена доступа', async () => {
@@ -122,36 +111,6 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
await insertUsers([admin]);
newUser.password = 'passwo1';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
await insertUsers([admin]);
newUser.password = 'password';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
newUser.password = '1111111';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if role is neither user nor admin', async () => {
await insertUsers([admin]);
newUser.role = 'invalid';
@@ -164,6 +123,71 @@ describe('User routes', () => {
});
});
describe('POST /v1/users/add', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
referer: '',
type: 'individual',
individual_data: {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email,
full_address: 'Russia, Moscow, Tverskaya street, 10',
},
};
});
test('should return 201 and successfully add new user if data is ok', async () => {
await insertUsers([admin]);
const res = await request(app).post('/v1/users/add').set('Authorization', `Bearer ${adminAccessToken}`).send(newUser);
// if (res.status !== httpStatus.CREATED) {
// console.error(res.body); // Выводит тело ответа, если статус не CREATED
// }
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.user.username).toBeDefined();
const dbUser = await User.findOne({ username: res.body.user.username });
expect(dbUser).toBeDefined();
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
expect(privateData.first_name).toEqual(newUser.individual_data?.first_name);
expect(privateData.last_name).toEqual(newUser.individual_data?.last_name);
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
});
test('should return 403 if service user is not admin but data new user is ok', async () => {
await insertUsers([userTwo]);
const res = await request(app)
.post('/v1/users/add')
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(newUser);
expect(res.status).toBe(httpStatus.FORBIDDEN);
});
test('should return 401 if service user is not have access token', async () => {
const res = await request(app).post('/v1/users/add').send(newUser);
expect(res.status).toBe(httpStatus.UNAUTHORIZED);
});
});
describe('GET /v1/users', () => {
test('should return 200 and apply the default query options', async () => {
await insertUsers([userOne, userTwo, admin]);
@@ -513,7 +537,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -528,7 +551,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -542,7 +564,6 @@ describe('User routes', () => {
const dbUser = await User.findById(userOne._id);
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(updateBody.password);
expect(dbUser).toMatchObject({ email: updateBody.email, role: 'user' });
});
@@ -550,7 +571,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app).patch(`/v1/users/${userOne.username}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
@@ -560,7 +580,6 @@ describe('User routes', () => {
await insertUsers([userOne, userTwo]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -574,7 +593,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -588,7 +606,6 @@ describe('User routes', () => {
await insertUsers([admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -600,7 +617,7 @@ describe('User routes', () => {
test('should return 400 if email is invalid', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: 'invalidEmail', password: 'GoodPassword!23' };
const updateBody = { email: 'invalidEmail' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -611,7 +628,7 @@ describe('User routes', () => {
test('should return 400 if email is already taken', async () => {
await insertUsers([userOne, userTwo, admin]);
const updateBody = { email: userTwo.email, password: 'GoodPassword!23' };
const updateBody = { email: userTwo.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -620,41 +637,9 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password length is less than 8 characters', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'passwo1' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password does not contain both letters and numbers', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'password' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
updateBody.password = '11111111';
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
//
test('should return 400 if email is my email then taken', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: userOne.email, password: 'veryStrongPassword!2' };
const updateBody = { email: userOne.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -10,7 +10,6 @@ describe('User model', () => {
newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
type: 'individual',
@@ -27,36 +26,9 @@ describe('User model', () => {
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain numbers', async () => {
newUser.password = 'password';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain letters', async () => {
newUser.password = '11111111';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if role is unknown', async () => {
newUser.role = 'invalid';
await expect(new User(newUser).validate()).rejects.toThrow();
});
});
describe('User toJSON()', () => {
test('should not return user password when toJSON is called', () => {
const newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
};
expect(new User(newUser).toJSON()).not.toHaveProperty('password');
});
});
});
+2
View File
@@ -5,6 +5,8 @@
"lib": ["ES2015"],
"outDir": "dist",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"allowJs": false,
"noImplicitAny": false,
@@ -3,6 +3,62 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.78](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.77...coopdoc-generator-ts@1.0.78) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.77](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.76...coopdoc-generator-ts@1.0.77) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.76](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.75...coopdoc-generator-ts@1.0.76) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.75](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.75-alpha.1...coopdoc-generator-ts@1.0.75) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.74](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73...coopdoc-generator-ts@1.0.74) (2024-08-13)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.73](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73-alpha.0...coopdoc-generator-ts@1.0.73) (2024-08-05)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.72](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.72-alpha.3...coopdoc-generator-ts@1.0.72) (2024-07-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.71](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.71-alpha.0...coopdoc-generator-ts@1.0.71) (2024-07-15)
**Note:** Version bump only for package coopdoc-generator-ts
@@ -3,6 +3,11 @@ import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
'src/index',
// {
// input: 'src/Templates/index',
// outDir: 'dist',
// name: 'Templates',
// },
],
declaration: true,
clean: false,
+6 -5
View File
@@ -1,8 +1,8 @@
{
"name": "coopdoc-generator-ts",
"type": "module",
"version": "1.0.72-alpha.3",
"private": true,
"version": "1.0.11-alpha.2",
"private": false,
"packageManager": "pnpm@9.0.6",
"description": "",
"author": "Alex Ant <chairman.voskhod@gmail.com>",
@@ -17,6 +17,7 @@
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
@@ -49,8 +50,7 @@
"ajv-formats": "^3.0.1",
"ajv-i18n": "^4.2.0",
"axios": "^1.7.2",
"cooparser-ts": "workspace:*",
"cooptypes": "workspace:*",
"cooptypes": "^1.0.22-alpha.6",
"dotenv": "^16.4.5",
"eosjs-ecc": "^4.0.7",
"handlebars": "^4.7.8",
@@ -90,5 +90,6 @@
"tar@<6.2.1": ">=6.2.1",
"vite@>=5.1.0 <=5.1.6": ">=5.1.7"
}
}
},
"gitHead": "39ad600d008966ddb7f73587987905c47958e4b3"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
import { DraftContract } from 'cooptypes'
import { WalletAgreement } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { WalletAgreement as Template } from '../templates'
export class Factory extends DocFactory<WalletAgreement.Action> {
constructor(storage: MongoDBConnector) {
super(storage)
}
async generateDocument(options: WalletAgreement.Action): Promise<IGeneratedDocument> {
let template: ITemplate<WalletAgreement.Model>
if (process.env.SOURCE === 'local') {
template = WalletAgreement.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, WalletAgreement.registry_id, options.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const combinedData: WalletAgreement.Model = {
meta,
coop,
vars,
}
await super.validate(combinedData, template.model)
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
return document
}
}
@@ -1,26 +1,25 @@
import type { IJoinCoopAction } from '../Templates/100.ParticipantApplication'
import { DraftContract } from 'cooptypes'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
import { ParticipantApplicationTemplate } from '../Templates/100.ParticipantApplication'
import DataService from '../Services/Databazor/DataService'
import type { IGenerateJoinCoop } from '../Interfaces/Actions'
import { ParticipantApplication } from '../templates'
export const registry_id = 100
export { ParticipantApplication as Template } from '../templates'
export class JoinCoopTemplateFactory extends DocFactory {
export class Factory extends DocFactory<ParticipantApplication.Action> {
constructor(storage: MongoDBConnector) {
super(storage)
}
async generateDocument(options: IGenerateJoinCoop): Promise<IGeneratedDocument> {
let template: ITemplate<IJoinCoopAction>
async generateDocument(options: ParticipantApplication.Action): Promise<IGeneratedDocument> {
let template: ITemplate<ParticipantApplication.Model>
if (process.env.SOURCE === 'local') {
template = ParticipantApplicationTemplate
template = ParticipantApplication.Template
}
else {
template = await this.getTemplate(registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, ParticipantApplication.registry_id, options.block_num)
}
const user = await super.getUser(options.username, options.block_num)
@@ -32,8 +31,11 @@ export class JoinCoopTemplateFactory extends DocFactory {
const coop = await super.getCooperative(options.coopname, options.block_num)
let { signature, ...modifiedOptions } = options
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...modifiedOptions }) // Генерируем мета-данные
const meta: IMetaDocument = await super.getMeta({ registry_id, title: template.title, ...modifiedOptions }) // Генерируем мета-данные
console.log('generator options: ', options)
console.log('generator meta: ', meta)
interface SignatureData {
username: string
@@ -41,7 +43,7 @@ export class JoinCoopTemplateFactory extends DocFactory {
signature: string
}
const data_service = new DataService<SignatureData>(this.storage, 'SignatureData')
const data_service = new DataService<SignatureData>(this.storage, 'signatures')
// пропуск сохранения необходим при вступлении для того, чтобы подготовить документ только для отображения без сохранения
if (!options.skip_save) {
@@ -59,12 +61,12 @@ export class JoinCoopTemplateFactory extends DocFactory {
}
else {
// если подпись указана - сохраняем в хранилище
const data_service = new DataService<SignatureData>(this.storage, 'SignatureData')
const data_service = new DataService<SignatureData>(this.storage, 'signatures')
await data_service.save({ username: options.username, block_num: meta.block_num, signature })
}
}
const combinedData: IJoinCoopAction = { ...userData, meta, coop, type: user.type, signature }
const combinedData: ParticipantApplication.Model = { ...userData, meta, coop, type: user.type, signature }
// валидируем скомбинированные данные
await super.validate(combinedData, template.model)
@@ -0,0 +1,44 @@
import { DraftContract } from 'cooptypes'
import { RegulationElectronicSignature } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { RegulationElectronicSignature as Template } from '../templates'
export class Factory extends DocFactory<RegulationElectronicSignature.Action> {
constructor(storage: MongoDBConnector) {
super(storage)
}
async generateDocument(options: RegulationElectronicSignature.Action): Promise<IGeneratedDocument> {
let template: ITemplate<RegulationElectronicSignature.Model>
if (process.env.SOURCE === 'local') {
template = RegulationElectronicSignature.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, RegulationElectronicSignature.registry_id, options.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const combinedData: RegulationElectronicSignature.Model = {
meta,
coop,
vars,
}
await super.validate(combinedData, template.model)
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
return document
}
}

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