Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ba05409c4 | |||
| d86beef58f | |||
| 7315d67c9f | |||
| 59d79b8c7a | |||
| 3df97653a2 | |||
| 948d55e418 | |||
| 1d2c7281b1 | |||
| 35759b943c | |||
| 29da33e200 | |||
| 1d758f8126 | |||
| b663dac2ab | |||
| 485a3971e9 | |||
| 36b5d64ca6 | |||
| 8462a709bc | |||
| 20cabe8362 | |||
| 6967864361 | |||
| a6ee3840ae | |||
| 01de6139df | |||
| 6ba0924f6c | |||
| 00c75d1fd8 | |||
| 93f28fa9d5 | |||
| 0aed10eb8b | |||
| d84d687eee | |||
| d046f56832 | |||
| 0af1fbf313 | |||
| e9aaf4309e | |||
| 80fa1085d2 | |||
| 88195291fd | |||
| 39ad600d00 | |||
| c63a3aba6b | |||
| 782ecbe307 | |||
| f75bbfa048 | |||
| 3a772cc983 | |||
| 807113523e | |||
| 7df2af8611 | |||
| 10c1697b00 | |||
| 742e5d38ab | |||
| 1d3e7f8af0 | |||
| c99bacab64 | |||
| 95d7555ebc | |||
| 5854e0a23c | |||
| 81f7f32767 | |||
| c0374a7da1 | |||
| ed65e655b9 | |||
| d250e516b5 | |||
| e4f404b2c7 | |||
| 86571331f8 | |||
| 163d420af9 | |||
| 4925ea4af0 | |||
| 57b05ce348 | |||
| 427b53fcbd | |||
| 2ac7a31be6 | |||
| 4baed6409f | |||
| 2ea0ff0965 | |||
| 88b9a7edce | |||
| 538066b960 | |||
| b10dd1d75a | |||
| f743580b55 | |||
| 0688467302 | |||
| 29e972dbf2 | |||
| bd99697c5b | |||
| 1d5c431ff6 | |||
| 3736c510ff | |||
| 26c12c3a85 | |||
| 10bb08e5e6 | |||
| b50d5b6121 | |||
| 38b28752e1 | |||
| 87c51009de | |||
| a950a47b28 | |||
| 2609d10c0b | |||
| 7b4cb0f66c | |||
| 26db0e1e69 | |||
| 62e64cbd22 | |||
| 844a47830f | |||
| 2cc1e32f45 | |||
| 9890958b1e | |||
| 6dbd6d6edc | |||
| bf7a4dfd28 | |||
| f52626bda0 | |||
| 88cec4bf2b | |||
| b880def108 | |||
| bfd5dc039a | |||
| c0d236dff3 | |||
| 743d5fc306 | |||
| 2f51516163 | |||
| b606b46aa4 | |||
| 5b65ff99a0 | |||
| 74a3f52324 | |||
| 7c2a4fdfa8 | |||
| fecee3a098 | |||
| 45f7111fb9 | |||
| f2e23a8062 | |||
| 36a3631f5e | |||
| a758d19a9f | |||
| aac3e53a30 | |||
| 85d87ee210 | |||
| 2f3ee80ed4 | |||
| 3632bea54e | |||
| 4a0521751a | |||
| 7d6266c241 | |||
| 1d69b9ffea | |||
| 72674cba10 | |||
| 495a8e2d1e | |||
| 95ae221d60 | |||
| 857f746da1 | |||
| 0e2e3052b1 | |||
| 167c04cc3f | |||
| 043f51479a | |||
| 921282df81 | |||
| b3c641501c | |||
| 3608e7601f | |||
| a75cfa1056 | |||
| f08c4102fd | |||
| 9bfd7c7d9a | |||
| a359b4fbdb |
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
|
||||
Vendored
+14
@@ -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
@@ -0,0 +1,10 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install -g pnpm lerna
|
||||
RUN pnpm install
|
||||
RUN lerna run build
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -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
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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>",
|
||||
|
||||
Generated
+19
-225
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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' : ''}`
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,7 @@ yarn-error.log
|
||||
coverage
|
||||
|
||||
.env-mixed
|
||||
.env-local
|
||||
.env-local
|
||||
|
||||
logs
|
||||
logs/*
|
||||
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"CodeGPT.apiKey": "OpenAI",
|
||||
"cSpell.words": [
|
||||
"subcode"
|
||||
]
|
||||
],
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+4
-4
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "create-nodejs-express-app",
|
||||
"version": "1.7.27-alpha.3",
|
||||
"version": "1.7.38-alpha.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "create-nodejs-express-app",
|
||||
"version": "1.7.27-alpha.3",
|
||||
"version": "1.7.38-alpha.3",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "coopback",
|
||||
"version": "1.7.27-alpha.3",
|
||||
"version": "1.7.38-alpha.3",
|
||||
"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",
|
||||
@@ -79,6 +81,7 @@
|
||||
"helmet": "^4.1.0",
|
||||
"html-pdf-node": "^1.0.8",
|
||||
"http-status": "^1.4.0",
|
||||
"ioredis": "^5.4.1",
|
||||
"ip": "^2.0.1",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest": "^29.7.0",
|
||||
@@ -87,7 +90,8 @@
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"moment": "^2.24.0",
|
||||
"mongoose": "^5.7.7",
|
||||
"mongoose": "^8.5.2",
|
||||
"mongoose-sequence": "^6.0.1",
|
||||
"morgan": "^1.9.1",
|
||||
"nodemailer": "^6.3.1",
|
||||
"nunjucks": "^3.2.4",
|
||||
@@ -103,16 +107,20 @@
|
||||
"tsd": "^0.31.0",
|
||||
"validator": "^13.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -14,14 +14,6 @@ import routes from './routes/v1/index';
|
||||
import { errorConverter, errorHandler } from './middlewares/error';
|
||||
import ApiError from './utils/ApiError';
|
||||
|
||||
// import { connectGenerator } from './services/data.service';
|
||||
// Подключите генератор
|
||||
// connectGenerator()
|
||||
// .then(() => {})
|
||||
// .catch((error) => {
|
||||
// console.error('Failed to connect generator:', error);
|
||||
// });
|
||||
|
||||
const app = express();
|
||||
|
||||
if (config.env !== 'test') {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import winston from 'winston'
|
||||
import config from './config'
|
||||
import winston from 'winston';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
import config from './config';
|
||||
|
||||
const enumerateErrorFormat = winston.format((info) => {
|
||||
if (info instanceof Error) {
|
||||
@@ -11,14 +12,34 @@ const enumerateErrorFormat = winston.format((info) => {
|
||||
const logger = winston.createLogger({
|
||||
level: config.env === 'development' ? 'debug' : 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
enumerateErrorFormat(),
|
||||
config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
|
||||
winston.format.colorize(),
|
||||
winston.format.splat(),
|
||||
winston.format.printf(({ level, message }) => `${level}: ${message}`)
|
||||
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
||||
return `${timestamp} ${level}: ${message}${Object.keys(meta).length ? ` - ${JSON.stringify(meta)}` : ''}`;
|
||||
})
|
||||
),
|
||||
transports: [
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
level: 'info',
|
||||
}),
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
level: 'error',
|
||||
}),
|
||||
new winston.transports.Console({
|
||||
stderrLevels: ['error'],
|
||||
level: 'info',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import morgan from 'morgan'
|
||||
import config from './config'
|
||||
import logger from './logger'
|
||||
import morgan from 'morgan';
|
||||
import config from './config';
|
||||
import logger from './logger';
|
||||
|
||||
morgan.token('message', (req, res) => res.locals.errorMessage || '');
|
||||
|
||||
@@ -21,4 +21,4 @@ const errorHandler = morgan(errorResponseFormat, {
|
||||
export default {
|
||||
successHandler,
|
||||
errorHandler,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
const user = [
|
||||
'getSelf',
|
||||
'initialPayment',
|
||||
'generateDocument',
|
||||
'getMyDocuments',
|
||||
'getMyOrders',
|
||||
'sendVerificationEmail',
|
||||
'manageMyMethods',
|
||||
'createDeposit',
|
||||
'joinCooperative',
|
||||
'getVars',
|
||||
];
|
||||
|
||||
const member = ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo', 'manageOrders'];
|
||||
|
||||
const chairman = [...user, ...member, 'addUser', 'setVars', 'manageSettings'];
|
||||
|
||||
const allRoles = {
|
||||
user: [''],
|
||||
service: ['sendNotification'],
|
||||
chairman: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
|
||||
member: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
|
||||
user,
|
||||
member,
|
||||
chairman,
|
||||
};
|
||||
|
||||
export const roles = Object.keys(allRoles);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export * as authController from './auth.controller';
|
||||
export * as userController from './user.controller';
|
||||
export * as documentController from './document.controller';
|
||||
export * as paymentController from './payment.controller';
|
||||
export * as orderController from './order.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';
|
||||
export * as methodController from './method.controller';
|
||||
export * as settingController from './setting.controller';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import http from 'http-status';
|
||||
import catchAsync from '../utils/catchAsync';
|
||||
import { methodService } from '../services';
|
||||
import { IGetResponse } from '../types/common';
|
||||
import ApiError from '../utils/ApiError';
|
||||
import httpStatus from 'http-status';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { getUserByUsername } from '../services/user.service';
|
||||
import type { RDeletePaymentMethod, RGetListPaymentMethods, RSavePaymentMethod } from '../types';
|
||||
|
||||
export const listPaymentMethods = catchAsync(async (req: RGetListPaymentMethods, res) => {
|
||||
const filter = req.params?.username ? { username: req.params.username } : {};
|
||||
|
||||
const methods: IGetResponse<any> = await methodService.listPaymentMethods({ ...filter });
|
||||
|
||||
res.status(httpStatus.OK).send(methods);
|
||||
});
|
||||
|
||||
export const addPaymentMethod = catchAsync(async (req: RSavePaymentMethod, res) => {
|
||||
if (req.params?.username != req.body.username)
|
||||
throw new ApiError(httpStatus.BAD_REQUEST, 'username в params и body должны совпадать');
|
||||
|
||||
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,
|
||||
// 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,
|
||||
};
|
||||
|
||||
await methodService.savePaymentMethod(paymentData);
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
|
||||
export const deletePaymentMethod = catchAsync(async (req: RDeletePaymentMethod, res) => {
|
||||
await methodService.deletePaymentMethod({
|
||||
username: req.params?.username,
|
||||
method_id: req.body.method_id,
|
||||
});
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import http from 'http-status';
|
||||
import catchAsync from '../utils/catchAsync';
|
||||
import { orderService } from '../services';
|
||||
import { ICreateDeposit, ICreateInitialPayment } from '../types';
|
||||
import { Request, Response } from 'express';
|
||||
import httpStatus from 'http-status';
|
||||
import { IPNProviderFactory } from '../services/payment/ipn/ipnProviderFactory';
|
||||
import pick from '../utils/pick';
|
||||
import logger from '../config/logger';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export const catchIPN = catchAsync(async (req: Request, res: Response) => {
|
||||
const providerName = req.params.provider;
|
||||
logger.info(`Recieve new IPN for provider ${providerName}`, { body: req.body, source: 'catchIPN' });
|
||||
|
||||
const provider = IPNProviderFactory.createProvider(providerName);
|
||||
|
||||
// Обрабатываем IPN данные
|
||||
await provider.handleIPN(req.body);
|
||||
res.status(200).send();
|
||||
});
|
||||
|
||||
export const createDeposit = catchAsync(async (req: any, res: Response) => {
|
||||
const username = req.user.username;
|
||||
|
||||
const result = await orderService.createDeposit(username, req.body as ICreateDeposit);
|
||||
res.status(http.CREATED).send(result);
|
||||
});
|
||||
|
||||
export const createInitialPayment = catchAsync(async (req: any, res: Response) => {
|
||||
const username = req.user.username;
|
||||
const result = await orderService.createInitialOrder(username);
|
||||
res.status(http.CREATED).send(result);
|
||||
});
|
||||
|
||||
export const getOrders = catchAsync(async (req: Request, res: Response) => {
|
||||
const filter = pick(req.query, ['username', 'id', 'order_id', 'status']);
|
||||
const options = pick(req.query, ['sortBy', 'limit', 'page']);
|
||||
|
||||
const orders = await orderService.getOrders(filter, options);
|
||||
res.status(httpStatus.OK).send(orders);
|
||||
});
|
||||
|
||||
export const getMyOrders = catchAsync(async (req: Request, res: Response) => {
|
||||
console.log(req.params);
|
||||
|
||||
const filter = pick(req.query, ['status', 'id']);
|
||||
const options = pick(req.query, ['sortBy', 'limit', 'page']);
|
||||
|
||||
filter.username = req.params.username;
|
||||
|
||||
const orders = await orderService.getOrders(filter, options);
|
||||
res.status(httpStatus.OK).send(orders);
|
||||
});
|
||||
|
||||
export const setStatus = catchAsync(async (req: Request, res: Response) => {
|
||||
const { status, id } = req.body;
|
||||
|
||||
await orderService.setStatus(id, status);
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
import http from 'http-status';
|
||||
import catchAsync from '../utils/catchAsync';
|
||||
import { paymentService } from '../services';
|
||||
import {
|
||||
ICreateDeposit,
|
||||
ICreateInitialPayment,
|
||||
RDeletePaymentMethod,
|
||||
RGetListPaymentMethods,
|
||||
RSavePaymentMethod,
|
||||
} from '../types';
|
||||
import { Request, Response } from 'express';
|
||||
import * as ip from 'ip';
|
||||
import { IGetResponse } from '../types/common';
|
||||
import ApiError from '../utils/ApiError';
|
||||
import httpStatus from 'http-status';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { getUserByUsername } from '../services/user.service';
|
||||
import { User } from '../models';
|
||||
|
||||
// Список разрешенных IP-адресов
|
||||
// const allowedIPs = [
|
||||
// '185.71.76.0/27',
|
||||
// '185.71.77.0/27',
|
||||
// '77.75.153.0/25',
|
||||
// '77.75.156.11',
|
||||
// '77.75.156.35',
|
||||
// '77.75.154.128/25',
|
||||
// '2a02:5180::/32'
|
||||
// ];
|
||||
|
||||
// if (process.env.NODE_ENV === 'development')
|
||||
// allowedIPs.push('127.0.0.1')
|
||||
|
||||
export const catchIPN = catchAsync(async (req: Request, res: Response) => {
|
||||
// const ipAddr = ((req.headers['x-real-ip'] as string) || req.ip || '').replace(/^::ffff:/, '');
|
||||
// console.log("ip: ", ipAddr);
|
||||
|
||||
// const isAllowed = allowedIPs.some(allowedIP => {
|
||||
// if (allowedIP.includes('/')) { // Если это диапазон
|
||||
// return ip.cidrSubnet(allowedIP).contains(ipAddr);
|
||||
// }
|
||||
// return allowedIP === ipAddr; // Если это конкретный IP
|
||||
// });
|
||||
|
||||
// if (!isAllowed) {
|
||||
// console.log("попытка доступа с запрещенного IP: ", ipAddr)
|
||||
// return res.status(http.FORBIDDEN).send('Доступ запрещен');
|
||||
// }
|
||||
|
||||
// console.log("on catch ipn", req.body);
|
||||
|
||||
res.status(http.OK).send();
|
||||
paymentService.catchIPN(req.body);
|
||||
});
|
||||
|
||||
export const createDeposit = catchAsync(async (req: any, res: Response) => {
|
||||
const username = req.user.username;
|
||||
|
||||
const result = await paymentService.createDeposit(username, req.body as ICreateDeposit);
|
||||
res.status(http.CREATED).send(result);
|
||||
});
|
||||
|
||||
export const createInitialPayment = catchAsync(async (req: any, res: Response) => {
|
||||
const username = req.user.username;
|
||||
const result = await paymentService.createInitialOrder(username, req.body as ICreateInitialPayment);
|
||||
res.status(http.CREATED).send(result);
|
||||
});
|
||||
|
||||
export const listPaymentMethods = catchAsync(async (req: RGetListPaymentMethods, res) => {
|
||||
const filter = req.params?.username ? { username: req.params.username } : {};
|
||||
|
||||
const methods: IGetResponse<any> = await paymentService.listPaymentMethods({ ...filter });
|
||||
|
||||
res.status(httpStatus.OK).send(methods);
|
||||
});
|
||||
|
||||
export const addPaymentMethod = catchAsync(async (req: RSavePaymentMethod, res) => {
|
||||
if (req.params?.username != req.body.username)
|
||||
throw new ApiError(httpStatus.BAD_REQUEST, 'username в params и body должны совпадать');
|
||||
|
||||
const user = await getUserByUsername(req.body.username);
|
||||
|
||||
const paymentData: Cooperative.Payments.IPaymentData = {
|
||||
username: req.body.username,
|
||||
method_id: req.body.method_id,
|
||||
user_type: user.type,
|
||||
method_type: req.body.method_type,
|
||||
is_default: false,
|
||||
data: req.body.data,
|
||||
};
|
||||
|
||||
await paymentService.savePaymentMethod(paymentData);
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
|
||||
export const deletePaymentMethod = catchAsync(async (req: RDeletePaymentMethod, res) => {
|
||||
await paymentService.deletePaymentMethod({
|
||||
username: req.params?.username,
|
||||
method_id: req.body.method_id,
|
||||
});
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { settingService } from '../services';
|
||||
import catchAsync from '../utils/catchAsync';
|
||||
|
||||
import httpStatus from 'http-status';
|
||||
|
||||
export const updateSettings = catchAsync(async (req, res) => {
|
||||
const { settings } = req.body;
|
||||
|
||||
await settingService.updateSettings(settings);
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
|
||||
export const getSettings = catchAsync(async (req, res) => {
|
||||
const settings = await settingService.getSettings();
|
||||
|
||||
res.status(httpStatus.OK).send(settings);
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
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';
|
||||
import logger from '../config/logger';
|
||||
|
||||
export const install = catchAsync(async (req: RInstall, res: Response) => {
|
||||
const { body } = req;
|
||||
await systemService.install(body as IInstall);
|
||||
|
||||
logger.info('System installed', { source: 'install', vars: req.body });
|
||||
|
||||
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);
|
||||
logger.info('New vars installed', { source: 'setVars', vars: req.body });
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { io, Socket } from 'socket.io-client';
|
||||
import { IAction } from '../types/common';
|
||||
import { SovietContract } from 'cooptypes';
|
||||
import { wsService } from '../services';
|
||||
import logger from '../config/logger';
|
||||
|
||||
let clientSocket: Socket | undefined;
|
||||
|
||||
@@ -10,7 +11,7 @@ export const initSocketConnection = (serverUrl: string): void => {
|
||||
clientSocket = io(serverUrl);
|
||||
|
||||
clientSocket.on('connect', () => {
|
||||
console.log('Успешное подключение к серверу оповещений.');
|
||||
logger.info('Success connection to notification server', { source: 'initSocketConnection' });
|
||||
});
|
||||
|
||||
clientSocket.on('event', (event: IAction) => {
|
||||
@@ -18,7 +19,7 @@ export const initSocketConnection = (serverUrl: string): void => {
|
||||
});
|
||||
|
||||
clientSocket.on('connect_error', (error: Error) => {
|
||||
console.error('Ошибка подключения к серверу оповещений:');
|
||||
logger.error('Fail connect to notification server', { source: 'initSocketConnection' });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,7 +27,7 @@ export const initSocketConnection = (serverUrl: string): void => {
|
||||
export const closeSocketConnection = (): void => {
|
||||
if (clientSocket) {
|
||||
clientSocket.disconnect();
|
||||
console.log('Client disconnected from server');
|
||||
logger.info('Client disconnected from server', { source: 'closeSocketConnection' });
|
||||
clientSocket = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
export * from './types';
|
||||
|
||||
import mongoose from 'mongoose';
|
||||
import app from './app';
|
||||
import config from './config/config';
|
||||
import logger from './config/logger';
|
||||
import { connectGenerator } from './services/document.service';
|
||||
import { initPolling } from './services/polling.service';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
// Подключаемся к Redis
|
||||
const redis = new Redis();
|
||||
|
||||
let server: any;
|
||||
|
||||
mongoose.connect(config.mongoose.url).then(async () => {
|
||||
logger.info('Connected to MongoDB with payment processor');
|
||||
|
||||
// подключаемся к хранилищу приватных данных
|
||||
await connectGenerator();
|
||||
|
||||
redis.subscribe(`${config.coopname}:orderStatusUpdate`, (err, count) => {
|
||||
if (err) {
|
||||
logger.error('Failed to subscribe: ', err);
|
||||
} else {
|
||||
logger.info(`Subscribed successfully! This client is currently subscribed to ${count} channels.`);
|
||||
}
|
||||
});
|
||||
|
||||
redis.on('message', (channel, message) => {
|
||||
if (channel === `${config.coopname}:orderStatusUpdate` && message) {
|
||||
const { orderId, status } = JSON.parse(message);
|
||||
// Дальнейшая обработка
|
||||
}
|
||||
});
|
||||
|
||||
initPolling();
|
||||
});
|
||||
|
||||
const exitHandler = () => {
|
||||
if (server) {
|
||||
server.close(() => {
|
||||
logger.info('Server closed');
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const unexpectedErrorHandler = (error: any) => {
|
||||
logger.error(error);
|
||||
exitHandler();
|
||||
};
|
||||
|
||||
process.on('uncaughtException', unexpectedErrorHandler);
|
||||
process.on('unhandledRejection', unexpectedErrorHandler);
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
logger.info('SIGTERM received');
|
||||
if (server) {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
@@ -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, () => {
|
||||
@@ -36,7 +40,7 @@ const exitHandler = () => {
|
||||
};
|
||||
|
||||
const unexpectedErrorHandler = (error: any) => {
|
||||
logger.error(error);
|
||||
logger.error(error, { source: 'unexpectedErrorHandler' });
|
||||
exitHandler();
|
||||
};
|
||||
|
||||
|
||||
@@ -6,17 +6,37 @@ 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;
|
||||
|
||||
// Check for server-secret header
|
||||
if (serverSecret && req.headers['server-secret'] === serverSecret) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (err || info || !user) {
|
||||
return reject(new ApiError(UNAUTHORIZED, 'Please authenticate'));
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
|
||||
if (requiredRights.length) {
|
||||
const userRights = roleRights.get(user.role);
|
||||
const hasRequiredRights = requiredRights.every((requiredRight) => userRights?.includes(requiredRight));
|
||||
console.log('Required rights:', requiredRights);
|
||||
|
||||
if (!hasRequiredRights && req.params.username !== user.username) {
|
||||
//
|
||||
const userRights = roleRights.get(user.role) || [];
|
||||
|
||||
// Determine if user has the required rights
|
||||
const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight));
|
||||
|
||||
// Check if user is accessing their own resource
|
||||
const isOwnResource = req.params.username === user.username;
|
||||
|
||||
if (requiredRights.length) {
|
||||
// If rights are required but user doesn't have them, deny access
|
||||
if (!hasRequiredRights) {
|
||||
return reject(new ApiError(FORBIDDEN, 'Недостаточно прав доступа'));
|
||||
}
|
||||
} else {
|
||||
// If no rights are required, ensure user is accessing their own resource
|
||||
if (!isOwnResource) {
|
||||
return reject(new ApiError(FORBIDDEN, 'Недостаточно прав доступа'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { RpcError } from 'eosjs';
|
||||
|
||||
export const errorConverter = (err, req, res, next) => {
|
||||
let error = err;
|
||||
console.log(error);
|
||||
if (!(error instanceof ApiError)) {
|
||||
if (error instanceof RpcError) {
|
||||
const message = error.json.error.details[0].message.replace('assertion failure with message: ', ''); // Получаем первую строку ошибки
|
||||
@@ -26,6 +27,7 @@ export const errorConverter = (err, req, res, next) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export const errorHandler = (err, req, res, next?) => {
|
||||
let { statusCode, message } = err;
|
||||
console.error(err);
|
||||
if (config.env === 'production' && !err.isOperational) {
|
||||
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
|
||||
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
|
||||
@@ -40,9 +42,7 @@ export const errorHandler = (err, req, res, next?) => {
|
||||
...(config.env === 'development' && { stack: err.stack }),
|
||||
};
|
||||
|
||||
if (config.env === 'development') {
|
||||
logger.error(err);
|
||||
}
|
||||
logger.error(err);
|
||||
|
||||
res.status(statusCode).send(response);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import ApiError from '../utils/ApiError';
|
||||
|
||||
const validate = (schema) => (req, res, next) => {
|
||||
// Фильтрация ключей, чтобы убедиться, что они существуют и не пусты
|
||||
const validKeys = ['params', 'query', 'body'].filter(key => req[key] && Object.keys(req[key]).length > 0);
|
||||
const validKeys = ['params', 'query', 'body'].filter((key) => req[key] && Object.keys(req[key]).length > 0);
|
||||
const object = pick(req, validKeys);
|
||||
|
||||
const { value, error } = joi
|
||||
@@ -21,5 +21,4 @@ const validate = (schema) => (req, res, next) => {
|
||||
return next();
|
||||
};
|
||||
|
||||
|
||||
export default validate;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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';
|
||||
export * from './settings.model';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import mongoose, { Schema, Document, model } from 'mongoose';
|
||||
import { toJSON, paginate } from './plugins/index';
|
||||
|
||||
export interface IIpn {
|
||||
provider: string;
|
||||
data: object;
|
||||
}
|
||||
|
||||
const ipnSchema = new Schema<IIpn>(
|
||||
{
|
||||
provider: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
// add plugin that converts mongoose to json
|
||||
ipnSchema.plugin(toJSON);
|
||||
ipnSchema.plugin(paginate);
|
||||
|
||||
/**
|
||||
* @typedef Ipn
|
||||
*/
|
||||
const IPN = model<IIpn>('Ipn', ipnSchema);
|
||||
|
||||
export default IPN;
|
||||
@@ -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;
|
||||
@@ -1,34 +1,76 @@
|
||||
import mongoose, { Schema, Document, model } from 'mongoose';
|
||||
import mongoose, { Schema, Document, model, type Model, type ObjectId } from 'mongoose';
|
||||
import { toJSON, paginate } from './plugins/index';
|
||||
import AutoIncrementFactory from 'mongoose-sequence';
|
||||
import { generateOrderSecret } from '../services/order.service';
|
||||
import type { PaymentDetails } from '../types';
|
||||
|
||||
interface IOrder extends Document {
|
||||
creator: string;
|
||||
type: string;
|
||||
data: {
|
||||
username: string
|
||||
provider: string
|
||||
quantity: string
|
||||
};
|
||||
order_id?: number;
|
||||
payed?: boolean;
|
||||
delivered?: boolean;
|
||||
error?: object;
|
||||
expired_at?: Date;
|
||||
const AutoIncrement = AutoIncrementFactory(mongoose);
|
||||
|
||||
export enum orderStatus {
|
||||
'pending' = 'pending',
|
||||
'paid' = 'paid',
|
||||
'completed' = 'completed',
|
||||
'failed' = 'failed',
|
||||
'expired' = 'expired',
|
||||
'refunded' = 'refunded',
|
||||
}
|
||||
|
||||
const orderSchema = new Schema<IOrder>({
|
||||
creator: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
export interface IOrder {
|
||||
id?: string;
|
||||
order_id?: number;
|
||||
secret: string;
|
||||
creator: string;
|
||||
status: orderStatus;
|
||||
type: 'registration' | 'deposit';
|
||||
provider: string;
|
||||
message?: string;
|
||||
username: string;
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
details?: PaymentDetails;
|
||||
expired_at?: Date;
|
||||
user: ObjectId;
|
||||
}
|
||||
|
||||
interface IOrderModel extends Model<IOrder> {
|
||||
isEmailTaken(email: string, excludeUsername?: mongoose.Types.ObjectId): Promise<boolean>;
|
||||
paginate(filter, options): any;
|
||||
}
|
||||
|
||||
const orderSchema = new Schema<IOrder, IOrderModel>(
|
||||
{
|
||||
order_id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
unique: true,
|
||||
},
|
||||
creator: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
secret: {
|
||||
type: String,
|
||||
required: true,
|
||||
private: true,
|
||||
default: () => generateOrderSecret(16), // Генерация секрета по умолчанию
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: Object.values(orderStatus),
|
||||
default: orderStatus.pending,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['registration', 'deposit'],
|
||||
required: true,
|
||||
},
|
||||
provider: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
@@ -36,47 +78,60 @@ const orderSchema = new Schema<IOrder>({
|
||||
},
|
||||
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;
|
||||
required: true,
|
||||
},
|
||||
symbol: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
details: {
|
||||
type: {
|
||||
data: { type: Schema.Types.Mixed, required: true },
|
||||
amount_plus_fee: { type: String, required: true },
|
||||
amount_without_fee: { type: String, required: true },
|
||||
fee_amount: { type: String, required: true },
|
||||
fee_percent: { type: Number, required: true },
|
||||
fact_fee_percent: { type: Number, required: true },
|
||||
tolerance_percent: { type: Number, required: true },
|
||||
},
|
||||
required: false, // Поле может быть не обязательным
|
||||
},
|
||||
expired_at: {
|
||||
type: Date,
|
||||
default: () => {
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1); // Увеличиваем на одни сутки
|
||||
return now;
|
||||
},
|
||||
},
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // Связь с пользователем
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Хук после find для автоматического добавления private_data
|
||||
orderSchema.post('find', async function (docs) {
|
||||
for (const doc of docs) {
|
||||
// Наполняем пользователя с помощью populate
|
||||
await doc.populate('user');
|
||||
// Если пользователь найден, вызываем асинхронный метод getPrivateData
|
||||
if (doc.user) {
|
||||
doc.user.private_data = await doc.user.getPrivateData();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// add plugin that converts mongoose to json
|
||||
orderSchema.plugin(toJSON);
|
||||
orderSchema.plugin(paginate);
|
||||
// Добавляем автоинкремент для поля order_id
|
||||
orderSchema.plugin(AutoIncrement, { inc_field: 'order_id' });
|
||||
|
||||
/**
|
||||
* @typedef Order
|
||||
*/
|
||||
const Order = model<IOrder>('Order', orderSchema);
|
||||
const Order = model<IOrder, IOrderModel>('Order', orderSchema);
|
||||
|
||||
export default Order;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// models/PaymentState.ts
|
||||
|
||||
import { Schema, model } from 'mongoose';
|
||||
|
||||
interface IPaymentState {
|
||||
accountNumber: string;
|
||||
statementDate: string;
|
||||
lastProcessedPage: number;
|
||||
}
|
||||
|
||||
const PaymentStateSchema = new Schema<IPaymentState>({
|
||||
accountNumber: { type: String, required: true },
|
||||
statementDate: { type: String, required: true },
|
||||
lastProcessedPage: { type: Number, required: true },
|
||||
});
|
||||
|
||||
export const PaymentState = model<IPaymentState>('PaymentState', PaymentStateSchema);
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const paginate = (schema) => {
|
||||
/**
|
||||
* @typedef {Object} QueryResult
|
||||
@@ -20,6 +22,11 @@ const paginate = (schema) => {
|
||||
* @returns {Promise<QueryResult>}
|
||||
*/
|
||||
schema.statics.paginate = async function (filter, options) {
|
||||
if (filter.id) {
|
||||
filter._id = new mongoose.Types.ObjectId(String(filter.id));
|
||||
delete filter.id;
|
||||
}
|
||||
|
||||
let sort = '';
|
||||
if (options.sortBy) {
|
||||
const sortingCriteria: (string | never)[] = []; // Update the type of sortingCriteria array
|
||||
@@ -29,9 +36,9 @@ const paginate = (schema) => {
|
||||
});
|
||||
sort = sortingCriteria.join(' ');
|
||||
} else {
|
||||
sort = 'createdAt';
|
||||
sort = '-createdAt';
|
||||
}
|
||||
|
||||
console.log('sort: ', sort);
|
||||
const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10;
|
||||
const page = options.page && parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
@@ -31,8 +31,8 @@ const toJSON = (schema) => {
|
||||
ret.id = ret._id.toString();
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
delete ret.createdAt;
|
||||
delete ret.updatedAt;
|
||||
// delete ret.createdAt;
|
||||
// delete ret.updatedAt;
|
||||
if (transform) {
|
||||
return transform(doc, ret, options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import mongoose, { Schema, Document, model, type Model } from 'mongoose';
|
||||
import { toJSON, paginate } from './plugins/index';
|
||||
import _ from 'lodash';
|
||||
import type { ISettings } from '../types';
|
||||
|
||||
export interface ISettingsModel extends Model<ISettings> {
|
||||
updateSettings(newSettings: Partial<ISettings>): Promise<ISettings>;
|
||||
getSettings(): Promise<ISettings>;
|
||||
}
|
||||
|
||||
// Определение схемы
|
||||
const settingsSchema = new Schema<ISettings>(
|
||||
{
|
||||
provider: {
|
||||
name: { type: String, required: true, default: 'sberbank' },
|
||||
client: { type: String, default: '' },
|
||||
secret: { type: String, default: '' },
|
||||
},
|
||||
app: {
|
||||
title: { type: String, required: true, default: 'Цифровой Кооператив' },
|
||||
description: { type: String, required: true, default: 'Система электронного документооборота для кооперативов' },
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Подключаем плагины
|
||||
settingsSchema.plugin(toJSON);
|
||||
settingsSchema.plugin(paginate);
|
||||
|
||||
// Статические методы
|
||||
settingsSchema.statics.updateSettings = async function (newSettings: Partial<ISettings>): Promise<ISettings> {
|
||||
let settings = await this.findOne();
|
||||
if (!settings) {
|
||||
settings = new this();
|
||||
}
|
||||
|
||||
// Универсальное обновление всех полей
|
||||
_.merge(settings, newSettings);
|
||||
return settings.save();
|
||||
};
|
||||
|
||||
settingsSchema.statics.getSettings = async function (): Promise<ISettings> {
|
||||
let settings = await this.findOne();
|
||||
if (!settings) {
|
||||
settings = new this();
|
||||
await settings.save();
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
// Модель
|
||||
const Settings = model<ISettings, ISettingsModel>('Settings', settingsSchema);
|
||||
|
||||
export default Settings;
|
||||
@@ -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: {
|
||||
|
||||
@@ -1,32 +1,47 @@
|
||||
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',
|
||||
'100_Refunded' = 'refunded',
|
||||
'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
|
||||
_privateData?:
|
||||
| Cooperative.Users.IIndividualData
|
||||
| Cooperative.Users.IEntrepreneurData
|
||||
| Cooperative.Users.IOrganizationData
|
||||
| null;
|
||||
private_data:
|
||||
| Cooperative.Users.IIndividualData
|
||||
| Cooperative.Users.IEntrepreneurData
|
||||
@@ -35,7 +50,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 +67,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 +85,7 @@ const userSchema = new Schema<IUser, IUserModel>(
|
||||
},
|
||||
public_key: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
referer: {
|
||||
type: String,
|
||||
@@ -79,7 +93,7 @@ const userSchema = new Schema<IUser, IUserModel>(
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
unique: true,
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
@@ -89,18 +103,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 +116,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,
|
||||
@@ -150,31 +152,29 @@ userSchema.statics.isEmailTaken = async function (email) {
|
||||
return !!user;
|
||||
};
|
||||
|
||||
// Виртуальное свойство для private_data
|
||||
userSchema
|
||||
.virtual('private_data')
|
||||
.get(function () {
|
||||
return this._privateData; // Чтение из временного поля
|
||||
})
|
||||
.set(function (value) {
|
||||
this._privateData = value; // Установка временного поля
|
||||
});
|
||||
|
||||
// Асинхронный метод получения private_data
|
||||
userSchema.methods.getPrivateData = async function (): Promise<
|
||||
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
|
||||
> {
|
||||
const result = (await generator.get(this.type, { username: this.username })) as
|
||||
const privateData = (await generator.get(this.type, { username: this.username })) as
|
||||
| Cooperative.Users.IIndividualData
|
||||
| Cooperative.Users.IEntrepreneurData
|
||||
| Cooperative.Users.IOrganizationData
|
||||
| null;
|
||||
|
||||
return result;
|
||||
this.private_data = privateData; // Устанавливаем виртуальное свойство
|
||||
return privateData;
|
||||
};
|
||||
|
||||
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,9 +10,9 @@ 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('/send-verification-email', auth(), authController.sendVerificationEmail);
|
||||
router.post('/lost-key', validate(authValidation.RForgotKey), authController.lostKey);
|
||||
router.post('/reset-key', validate(authValidation.RResetKey), authController.resetKey);
|
||||
router.post('/send-verification-email', auth('sendVerificationEmail'), authController.sendVerificationEmail);
|
||||
router.post('/verify-email', validate(authValidation.RVerifyEmail), authController.verifyEmail);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,12 +6,16 @@ import { documentController } from '../../controllers';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/generate').post(auth(), validate(documentValidation.RGenerate), documentController.generateDocument);
|
||||
router
|
||||
.route('/generate')
|
||||
.post(auth('generateDocument'), validate(documentValidation.RGenerate), documentController.generateDocument);
|
||||
|
||||
router
|
||||
.route('/get-documents')
|
||||
.get(auth('getDocuments'), validate(documentValidation.RGetDocuments), documentController.getDocuments);
|
||||
|
||||
router.route('/get-my-documents').get(auth(), validate(documentValidation.RGetDocuments), documentController.getDocuments);
|
||||
router
|
||||
.route('/get-my-documents')
|
||||
.get(auth('getMyDocuments'), validate(documentValidation.RGetDocuments), documentController.getDocuments);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -2,16 +2,23 @@ import { Router } from 'express';
|
||||
import authRoute from './auth.route';
|
||||
import userRoute from './user.route';
|
||||
import docsRoute from './docs.route';
|
||||
import paymentRoute from './payment.route';
|
||||
import orderRoute from './order.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 methodRoute from './method.route';
|
||||
|
||||
import config from '../../config/config';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const defaultRoutes = [
|
||||
{
|
||||
path: '/system',
|
||||
route: monoRoute,
|
||||
},
|
||||
{
|
||||
path: '/auth',
|
||||
route: authRoute,
|
||||
@@ -20,13 +27,21 @@ const defaultRoutes = [
|
||||
path: '/users',
|
||||
route: userRoute,
|
||||
},
|
||||
{
|
||||
path: '/participants',
|
||||
route: participantsRoute,
|
||||
},
|
||||
{
|
||||
path: '/documents',
|
||||
route: dataRoute,
|
||||
},
|
||||
{
|
||||
path: '/payments',
|
||||
route: paymentRoute,
|
||||
path: '/methods',
|
||||
route: methodRoute,
|
||||
},
|
||||
{
|
||||
path: '/orders',
|
||||
route: orderRoute,
|
||||
},
|
||||
{
|
||||
path: '/coop',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Router } from 'express';
|
||||
import { methodController } from '../../controllers';
|
||||
import validate from '../../middlewares/validate';
|
||||
import { methodValidation } from '../../validations';
|
||||
import auth from '../../middlewares/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router
|
||||
.route('/:username?')
|
||||
.get(auth('manageMyMethods'), validate(methodValidation.RGetListPaymentMethods), methodController.listPaymentMethods);
|
||||
|
||||
router
|
||||
.route('/:username/add')
|
||||
.post(auth('manageMyMethods'), validate(methodValidation.RSavePaymentMethod), methodController.addPaymentMethod);
|
||||
|
||||
router
|
||||
.route('/:username/delete')
|
||||
.post(auth('manageMyMethods'), validate(methodValidation.RDeletePaymentMethod), methodController.deletePaymentMethod);
|
||||
|
||||
export default router;
|
||||
+11
-15
@@ -1,9 +1,8 @@
|
||||
import { Router } from 'express';
|
||||
import { paymentController } from '../../controllers';
|
||||
import { methodController, orderController } from '../../controllers';
|
||||
import validate from '../../middlewares/validate';
|
||||
import * as paymentValidation from '../../validations/payment.validation';
|
||||
import { orderValidation } from '../../validations';
|
||||
import auth from '../../middlewares/auth';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -41,7 +40,7 @@ const router = Router();
|
||||
* 200:
|
||||
* description: Успешное выполнение запроса.
|
||||
*/
|
||||
router.route('/ipn').post(validate(paymentValidation.RRecieveIPN), paymentController.catchIPN);
|
||||
router.route('/:provider/ipn').post(validate(orderValidation.RRecieveIPN), orderController.catchIPN);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -68,7 +67,7 @@ router.route('/ipn').post(validate(paymentValidation.RRecieveIPN), paymentContro
|
||||
*/
|
||||
router
|
||||
.route('/initial')
|
||||
.post(auth(), validate(paymentValidation.RCreateInitialPayment), paymentController.createInitialPayment);
|
||||
.post(auth('initialPayment'), validate(orderValidation.RCreateInitialPayment), orderController.createInitialPayment);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -93,18 +92,15 @@ router
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/ICreatedPayment'
|
||||
*/
|
||||
router.route('/deposit').post(auth(), validate(paymentValidation.RCreateDeposit), paymentController.createDeposit);
|
||||
router
|
||||
.route('/deposit')
|
||||
.post(auth('createDeposit'), validate(orderValidation.RCreateDeposit), orderController.createDeposit);
|
||||
|
||||
router
|
||||
.route('/methods/:username?')
|
||||
.get(auth('getUsers'), validate(paymentValidation.RGetListPaymentMethods), paymentController.listPaymentMethods);
|
||||
.route('/set-order-status')
|
||||
.post(auth('manageOrders'), validate(orderValidation.RSetOrderStatus), orderController.setStatus);
|
||||
|
||||
router
|
||||
.route('/methods/:username/add')
|
||||
.post(auth('manageUsers'), validate(paymentValidation.RSavePaymentMethod), paymentController.addPaymentMethod);
|
||||
|
||||
router
|
||||
.route('/methods/:username/delete')
|
||||
.post(auth('manageUsers'), validate(paymentValidation.RDeletePaymentMethod), paymentController.deletePaymentMethod);
|
||||
router.route('/all').get(auth('manageOrders'), validate(orderValidation.RGetCoopOrders), orderController.getOrders);
|
||||
router.route('/:username').get(auth('getMyOrders'), validate(orderValidation.RGetMyOrders), orderController.getMyOrders);
|
||||
|
||||
export default router;
|
||||
@@ -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('joinCooperative'), validate(participantValidation.RJoinCooperative), participantController.joinCooperative);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Router } from 'express';
|
||||
import { settingController, 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('getVars'), systemController.getVarsSchema);
|
||||
router.route('/get-vars').post(auth('getVars'), validate(systemValidation.RSetVars), systemController.setVars);
|
||||
|
||||
router.route('/set-vars').post(auth('setVars'), validate(systemValidation.RSetVars), systemController.setVars);
|
||||
|
||||
router.route('/health').get(systemController.getHealth);
|
||||
|
||||
router.route('/settings').get(auth('manageSettings'), settingController.getSettings);
|
||||
|
||||
router
|
||||
.route('/settings')
|
||||
.post(auth('manageSettings'), validate(systemValidation.RUpdateSettings), settingController.updateSettings);
|
||||
|
||||
export default router;
|
||||
@@ -11,13 +11,11 @@ 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')
|
||||
.get(auth('getUsers'), validate(userValidation.RGetUser), userController.getUser)
|
||||
.get(auth('getSelf'), validate(userValidation.RGetUser), userController.getUser)
|
||||
.patch(auth('manageUsers'), validate(userValidation.RUpdateUser), userController.updateUser);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -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,50 @@ 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';
|
||||
import type { IOrder } from '../models/order.model';
|
||||
import type { IBCAction } from '../types';
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export async function transact(actions: any): Promise<any> {
|
||||
const instance = await getInstance(config.service_wif);
|
||||
const result = await instance.transact(
|
||||
{
|
||||
actions,
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить инстанс для осуществления транзакции в блокчейн.
|
||||
* @param {username} - авторизация от аккаунта
|
||||
@@ -22,7 +63,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 +104,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
|
||||
@@ -73,9 +114,11 @@ async function getCooperative(coopname) {
|
||||
return cooperative;
|
||||
}
|
||||
|
||||
async function registerBlockchainAccount(user: IUser, orderData: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit) {
|
||||
const eos = await getInstance(process.env.REGISTRATOR_WIF);
|
||||
async function registerBlockchainAccount(user: IUser, order: IOrder) {
|
||||
const eos = await getInstance(config.service_wif);
|
||||
const actions = [] as any;
|
||||
|
||||
// Создаем newaccount
|
||||
const newaccount: RegistratorContract.Actions.CreateAccount.ICreateAccount = {
|
||||
registrator: process.env.COOPNAME as string,
|
||||
coopname: process.env.COOPNAME as string,
|
||||
@@ -85,6 +128,20 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
|
||||
meta: '',
|
||||
};
|
||||
|
||||
actions.push({
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.CreateAccount.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: newaccount,
|
||||
});
|
||||
|
||||
// Создаем registerUserData
|
||||
|
||||
const registerUserData: RegistratorContract.Actions.RegisterUser.IRegistrerUser = {
|
||||
coopname: process.env.COOPNAME as string,
|
||||
registrator: process.env.COOPNAME as string,
|
||||
@@ -92,61 +149,206 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
|
||||
type: user.type,
|
||||
};
|
||||
|
||||
//не следует создавать аккаунт в случаях, если он уже есть у пользователя
|
||||
|
||||
actions.push({
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.RegisterUser.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: registerUserData,
|
||||
});
|
||||
|
||||
// Проверяем наличие заявления на вступление и создаем joinCooperativeData
|
||||
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) },
|
||||
};
|
||||
actions.push({
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.JoinCooperative.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: joinCooperativeData,
|
||||
});
|
||||
|
||||
// Создаем createDeposit
|
||||
const createDeposit: GatewayContract.Actions.CreateDeposit.ICreateDeposit = {
|
||||
coopname: config.coopname,
|
||||
username: user.username,
|
||||
type: 'registration',
|
||||
quantity: order.quantity,
|
||||
deposit_id: order.order_id as number,
|
||||
};
|
||||
actions.push({
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CreateDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: createDeposit,
|
||||
});
|
||||
|
||||
// Создаем completeDeposit
|
||||
const completeDeposit: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit = {
|
||||
coopname: config.coopname,
|
||||
admin: config.service_username,
|
||||
deposit_id: order.order_id as number,
|
||||
memo: '',
|
||||
};
|
||||
actions.push({
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: completeDeposit,
|
||||
});
|
||||
|
||||
// Проверяем наличие соглашения на кошелек и создаем walletAgreementData
|
||||
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) },
|
||||
};
|
||||
actions.push({
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: walletAgreementData,
|
||||
});
|
||||
|
||||
// Проверяем наличие соглашения по ЭЦП и создаем signatureAgreementData
|
||||
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) },
|
||||
};
|
||||
actions.push({
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: signatureAgreementData,
|
||||
});
|
||||
|
||||
// Проверяем наличие соглашения о конфиденциальности и создаем privacyAgreementData
|
||||
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) },
|
||||
};
|
||||
actions.push({
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: privacyAgreementData,
|
||||
});
|
||||
|
||||
// Проверяем наличие пользовательского соглашения и создаем userAgreementData
|
||||
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) },
|
||||
};
|
||||
actions.push({
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: userAgreementData,
|
||||
});
|
||||
|
||||
const result = await eos.transact(
|
||||
{
|
||||
actions,
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
}
|
||||
);
|
||||
|
||||
// console.dir(result, { depth: null });
|
||||
}
|
||||
|
||||
async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
|
||||
const eos = await getInstance(config.service_wif);
|
||||
|
||||
// console.log('data: ', data);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.CreateAccount.actionName,
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Boards.CreateBoard.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: newaccount,
|
||||
},
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.RegisterUser.actionName, //reguser
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: registerUserData,
|
||||
},
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.JoinCooperative.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: joinCooperativeData,
|
||||
},
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: orderData,
|
||||
data,
|
||||
},
|
||||
];
|
||||
|
||||
const result = await eos.transact(
|
||||
await eos.transact(
|
||||
{
|
||||
actions,
|
||||
},
|
||||
@@ -158,11 +360,11 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
|
||||
}
|
||||
|
||||
async function createOrder(data) {
|
||||
const eos = await getInstance(process.env.REGISTRATOR_WIF);
|
||||
const eos = await getInstance(config.service_wif);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: process.env.GATEWAY_CONTRACT as string,
|
||||
account: GatewayContract.contractName.production,
|
||||
name: 'deposit',
|
||||
authorization: [
|
||||
{
|
||||
@@ -189,20 +391,46 @@ async function createOrder(data) {
|
||||
return order_id;
|
||||
}
|
||||
|
||||
async function completeOrder(data: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit) {
|
||||
const eos = await getInstance(process.env.REGISTRATOR_WIF);
|
||||
async function completeDeposit(order: IOrder) {
|
||||
const eos = await getInstance(config.service_wif);
|
||||
|
||||
const createDeposit: GatewayContract.Actions.CreateDeposit.ICreateDeposit = {
|
||||
coopname: config.coopname,
|
||||
username: order.username,
|
||||
type: 'deposit',
|
||||
quantity: order.quantity,
|
||||
deposit_id: order.order_id as number,
|
||||
};
|
||||
|
||||
const completeDeposit: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit = {
|
||||
coopname: config.coopname,
|
||||
admin: config.service_username,
|
||||
deposit_id: order.order_id as number,
|
||||
memo: '',
|
||||
};
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CreateDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: createDeposit,
|
||||
},
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
data: completeDeposit,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -218,11 +446,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(config.service_wif);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: process.env.GATEWAY_CONTRACT as string,
|
||||
account: GatewayContract.contractName.production,
|
||||
name: 'dpfail',
|
||||
authorization: [
|
||||
{
|
||||
@@ -245,10 +473,82 @@ 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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function cancelOrder(data: GatewayContract.Actions.RefundDeposit.IRefundDeposit) {
|
||||
const action: IBCAction<GatewayContract.Actions.RefundDeposit.IRefundDeposit> = {
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.RefundDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
};
|
||||
|
||||
return await transact([action]);
|
||||
}
|
||||
|
||||
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 +556,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;
|
||||
}
|
||||
|
||||
@@ -269,6 +569,10 @@ export {
|
||||
createOrder,
|
||||
getCooperative,
|
||||
failOrder,
|
||||
completeOrder,
|
||||
completeDeposit,
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,13 @@ export * as emailService from './email.service';
|
||||
export * as tokenService from './token.service';
|
||||
export * as userService from './user.service';
|
||||
export * as blockchainService from './blockchain.service';
|
||||
export * as paymentService from './payment.service';
|
||||
export * as orderService from './order.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';
|
||||
export * as redisService from './redis.service';
|
||||
export * as methodService from './method.service';
|
||||
export * as payEffectService from './payEffect.service';
|
||||
export * as settingService from './setting.service';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { Cooperative } from 'cooptypes';
|
||||
import { generator } from './document.service';
|
||||
import type { FilterQuery } from 'mongoose';
|
||||
|
||||
export const savePaymentMethod = async (data: Cooperative.Payments.IPaymentData) => {
|
||||
return await generator.save('paymentMethod', data);
|
||||
};
|
||||
|
||||
export const deletePaymentMethod = async (filter: FilterQuery<Cooperative.Payments.IPaymentData>) => {
|
||||
return await generator.del('paymentMethod', filter as any);
|
||||
};
|
||||
|
||||
export const listPaymentMethods = async (filter: FilterQuery<Cooperative.Payments.IPaymentData>) => {
|
||||
return await generator.list('paymentMethod', filter as any);
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
|
||||
export default {};
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import { Order, User } from '../models/index';
|
||||
import mongoose, { type ObjectId } from 'mongoose';
|
||||
import { getUserByUsername } from './user.service';
|
||||
import logger from '../config/logger';
|
||||
import { ICreateDeposit, ICreateInitialPayment, type IBCAction } from '../types';
|
||||
import { generator } from './document.service';
|
||||
import { ICreatedPayment } from '../types';
|
||||
import { ProviderFactory } from './payment/providerFactory';
|
||||
import { orderStatus, type IOrder } from '../models/order.model';
|
||||
import ApiError from '../utils/ApiError';
|
||||
import httpStatus from 'http-status';
|
||||
import { redisPublisher } from './redis.service';
|
||||
import crypto from 'crypto';
|
||||
import Settings from '../models/settings.model';
|
||||
import _ from 'lodash'; // lodash для глубокого клонирования
|
||||
import { blockchainService, userService } from '.';
|
||||
import config from '../config/config';
|
||||
import { GatewayContract } from 'cooptypes';
|
||||
import { userStatus } from '../models/user.model';
|
||||
|
||||
export async function createOrder(
|
||||
username: string,
|
||||
providerName: string,
|
||||
type: 'deposit' | 'registration',
|
||||
amount: string
|
||||
): Promise<ICreatedPayment> {
|
||||
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
|
||||
if (!cooperative) throw new Error('Кооператив не найден');
|
||||
|
||||
const session = await mongoose.startSession();
|
||||
|
||||
const [, symbol] = amount.split(' ');
|
||||
|
||||
let result;
|
||||
try {
|
||||
await session.withTransaction(async () => {
|
||||
const user = await getUserByUsername(username);
|
||||
// Создание ордера
|
||||
const order: IOrder = {
|
||||
creator: process.env.COOPNAME as string,
|
||||
secret: generateOrderSecret(),
|
||||
status: orderStatus.pending,
|
||||
type: type,
|
||||
provider: providerName,
|
||||
username,
|
||||
quantity: amount,
|
||||
symbol: symbol,
|
||||
user: user._id as unknown as ObjectId,
|
||||
};
|
||||
|
||||
const db_order = new Order(order);
|
||||
|
||||
await db_order.save({ session });
|
||||
|
||||
const secret = db_order.secret;
|
||||
|
||||
const provider = ProviderFactory.createProvider(providerName);
|
||||
|
||||
const paymentDetails = await provider.createPayment(
|
||||
amount,
|
||||
symbol,
|
||||
type === 'deposit'
|
||||
? `Добровольный паевый взнос №${db_order.order_id}`
|
||||
: `Добровольный вступительный взнос №${db_order.order_id}`,
|
||||
db_order.order_id as number,
|
||||
secret
|
||||
);
|
||||
|
||||
db_order.details = paymentDetails;
|
||||
|
||||
await db_order.save({ session });
|
||||
|
||||
logger.info('Order created', { providerName, type, username, provider, amount, source: 'createDeposit' });
|
||||
|
||||
result = { provider: providerName, details: paymentDetails };
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
logger.error(`Error happen on createOrder process: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function createDeposit(username: string, data: ICreateDeposit): Promise<ICreatedPayment> {
|
||||
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
if (!cooperative) throw new Error('Кооператив не найден');
|
||||
|
||||
const [, symbol_initial] = cooperative.initial.split(' ');
|
||||
|
||||
const [amount, symbol] = data.quantity.split(' ');
|
||||
const quantity = `${parseFloat(amount).toFixed(4)} ${symbol}`;
|
||||
if (symbol_initial != symbol) throw new ApiError(httpStatus.BAD_REQUEST, 'Неверный символ для платежа');
|
||||
|
||||
const provider = (await Settings.getSettings()).provider.name;
|
||||
|
||||
return createOrder(username, provider, 'deposit', quantity);
|
||||
}
|
||||
|
||||
export async function createInitialOrder(username: string): Promise<ICreatedPayment> {
|
||||
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
if (!cooperative) throw new Error('Кооператив не найден');
|
||||
|
||||
const user = await getUserByUsername(username);
|
||||
|
||||
let amount = '';
|
||||
|
||||
if (user.type === 'individual' || user.type === 'entrepreneur') {
|
||||
amount = cooperative.registration;
|
||||
} else {
|
||||
amount = cooperative.org_registration;
|
||||
}
|
||||
|
||||
const provider = (await Settings.getSettings()).provider.name;
|
||||
|
||||
return createOrder(username, provider, 'registration', amount);
|
||||
}
|
||||
|
||||
export function checkPaymentSymbol(incomeSymbol: string, extectedSymbol: string) {
|
||||
if (incomeSymbol != extectedSymbol) return { status: 'error', message: `${incomeSymbol} != expectedSymbol` };
|
||||
else return { status: 'success', message: '' };
|
||||
}
|
||||
|
||||
export function checkPaymentAmount(incomeAmount: string, expectedAmount: string, tolerancePercentage: number) {
|
||||
//погрешность возникает при округлении суммы платежа на стороне провайдера при расчете им процентов комиссии
|
||||
const tolerance = parseFloat(expectedAmount) * (tolerancePercentage / 100); // Абсолютное значение погрешности
|
||||
|
||||
if (parseFloat(incomeAmount) < parseFloat(expectedAmount) - tolerance) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: `Недостаточно средств, поступило: ${incomeAmount}, ожидается: ${expectedAmount}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
message: '',
|
||||
};
|
||||
}
|
||||
|
||||
export function generateOrderSecret(length = 16): string {
|
||||
return crypto.randomBytes(length).toString('hex'); // Генерирует случайный секрет в виде hex-строки
|
||||
}
|
||||
|
||||
export function getAmountPlusFee(amount: number, fee: number): number {
|
||||
if (fee < 0 || fee >= 100) {
|
||||
throw new Error('Fee must be between 0 and 100.');
|
||||
}
|
||||
return amount / ((100 - fee) / 100);
|
||||
}
|
||||
|
||||
export async function setStatus(id: string, status: string) {
|
||||
const order = await Order.findById(id);
|
||||
|
||||
if (!order) {
|
||||
throw new ApiError(httpStatus.BAD_REQUEST, 'Ордер не найден');
|
||||
}
|
||||
|
||||
if (status == 'refunded') {
|
||||
await blockchainService.cancelOrder({
|
||||
coopname: config.coopname,
|
||||
admin: config.service_username,
|
||||
deposit_id: order.order_id as number,
|
||||
memo: '',
|
||||
});
|
||||
|
||||
if (order.type === 'registration') {
|
||||
const user = await userService.getUserByUsername(order.username);
|
||||
user.status = userStatus['100_Refunded'];
|
||||
user.save();
|
||||
}
|
||||
}
|
||||
|
||||
//обновляем статус
|
||||
order.status = orderStatus[status];
|
||||
await order.save();
|
||||
|
||||
// Отправляем обновление через Redis, чтобы обработчик мог выполнить действия
|
||||
redisPublisher.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id, status }));
|
||||
|
||||
logger.info(`Статус ордера ${id} обновлен до ${status}`, { source: 'setStatus' });
|
||||
}
|
||||
|
||||
export async function getOrders(filter, options) {
|
||||
const orders = await Order.paginate(filter, options);
|
||||
return orders;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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> => {
|
||||
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();
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Order } from '../models';
|
||||
import { getUserByUsername } from './user.service';
|
||||
import { blockchainService } from '.';
|
||||
import logger from '../config/logger';
|
||||
import { userStatus } from '../models/user.model';
|
||||
import { orderStatus, type IOrder } from '../models/order.model';
|
||||
import { redisSubscriber } from './redis.service';
|
||||
import config from '../config/config';
|
||||
|
||||
export class PaymentEffectProcessor {
|
||||
static async processPaymentEffect(id: string, status: string) {
|
||||
const order = await Order.findById(id);
|
||||
|
||||
if (!order) {
|
||||
logger.error(`Order with id ${id} not found`, { source: 'processPaymentEffect' });
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'paid':
|
||||
if (order.status != 'completed') await PaymentEffectProcessor.process(order);
|
||||
break;
|
||||
|
||||
case 'failed':
|
||||
logger.warn(`Payment for order ${id} failed`, { source: 'processPaymentEffect' });
|
||||
// Здесь можно добавить логику повторных попыток или обработки неуспешного платежа
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.info(`Status ${status} for order ${id} does not require processing`, { source: 'processPaymentEffect' });
|
||||
}
|
||||
}
|
||||
|
||||
private static async process(order: IOrder) {
|
||||
logger.info(`Processing blockchain data for order ${order.id}`, { source: 'process' });
|
||||
|
||||
try {
|
||||
const user = await getUserByUsername(order.username);
|
||||
|
||||
if (order.type === 'registration') {
|
||||
await blockchainService.registerBlockchainAccount(user, order);
|
||||
|
||||
logger.info('New user registered:', { source: 'process', username: user.username });
|
||||
|
||||
user.status = userStatus['4_Registered'];
|
||||
user.is_registered = true;
|
||||
user.has_account = true;
|
||||
await user.save();
|
||||
|
||||
await Order.updateOne({ _id: order.id }, { status: orderStatus.completed });
|
||||
} else if (order.type === 'deposit') {
|
||||
await blockchainService.completeDeposit(order);
|
||||
|
||||
await Order.updateOne({ _id: order.id }, { status: orderStatus.completed });
|
||||
|
||||
logger.info(`User ${user.username} made a share contribution of ${order.quantity}`, { source: 'process' });
|
||||
}
|
||||
} catch (e: any) {
|
||||
await Order.updateOne({ _id: order.id }, { status: orderStatus.failed, message: e.message });
|
||||
logger.error(`Error processing blockchain transaction for order: ${order.id} with message: ${e.message}`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redisSubscriber.subscribe(`${config.coopname}:orderStatusUpdate`);
|
||||
|
||||
redisSubscriber.on('message', async (channel, message) => {
|
||||
if (channel === `${config.coopname}:orderStatusUpdate`) {
|
||||
try {
|
||||
const { id, status } = JSON.parse(message);
|
||||
await PaymentEffectProcessor.processPaymentEffect(id, status);
|
||||
} catch (error) {
|
||||
logger.error('Error processing Redis message:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,287 +0,0 @@
|
||||
import { Order, Token } from '../models/index';
|
||||
import { getCooperative } from './blockchain.service';
|
||||
import { YooCheckout, ICreatePayment } from '@a2seven/yoo-checkout';
|
||||
import mongoose from 'mongoose';
|
||||
import { getUserByUsername } from './user.service';
|
||||
import * as blockchainService from './blockchain.service';
|
||||
import logger from '../config/logger';
|
||||
import { ICreateDeposit, ICreateInitialPayment } from '../types';
|
||||
import { generator } from './document.service';
|
||||
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';
|
||||
|
||||
const { connection } = mongoose;
|
||||
|
||||
export const savePaymentMethod = async (data: Cooperative.Payments.IPaymentData) => {
|
||||
return await generator.save('paymentMethod', data);
|
||||
};
|
||||
|
||||
export const deletePaymentMethod = async (filter: Filter<Cooperative.Payments.IPaymentData>) => {
|
||||
return await generator.del('paymentMethod', filter as any);
|
||||
};
|
||||
|
||||
export const listPaymentMethods = async (filter: Filter<Cooperative.Payments.IPaymentData>) => {
|
||||
return await generator.list('paymentMethod', filter as any);
|
||||
};
|
||||
|
||||
function getAmountPlusFee(amount: number, provider: string): number {
|
||||
const matrix = {
|
||||
yookassa: 3.5,
|
||||
};
|
||||
|
||||
const fee = matrix[provider];
|
||||
|
||||
if (!fee) throw new Error('Неизвестный провайдер');
|
||||
|
||||
return amount / ((100 - fee) / 100);
|
||||
}
|
||||
|
||||
export async function createDeposit(username: string, data: ICreateDeposit): Promise<ICreatedPayment> {
|
||||
let cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
|
||||
if (!cooperative) throw new Error('Кооператив не найден');
|
||||
|
||||
// 1. Создаёшь ордер в базе и получаешь внутренний айди
|
||||
const db_order = await Order.create({
|
||||
creator: process.env.COOPNAME,
|
||||
type: 'deposit',
|
||||
data: { username, provider: data.provider, quantity: data.quantity },
|
||||
});
|
||||
const internal_id = db_order._id;
|
||||
|
||||
let order_id = 0;
|
||||
|
||||
const [, symbol_initial] = cooperative.initial.split(' ');
|
||||
const [amount, symbol] = data.quantity.split(' ');
|
||||
|
||||
if (symbol_initial != symbol) throw new Error('Неверный символ для платежа');
|
||||
|
||||
let paymentDetails = {} as PaymentDetails; // Инициализация переменной для деталей
|
||||
|
||||
if (data.provider === 'yookassa') {
|
||||
// 2. Используешь внутренний айди для получения ордера в системе платежей
|
||||
const checkout = new YooCheckout({
|
||||
shopId: process.env.YA_SHOP_ID as string,
|
||||
secretKey: process.env.YA_SHOP_SECRET as string,
|
||||
token: process.env.YA_ACCESS_TOKEN as string,
|
||||
});
|
||||
|
||||
const amount_plus_fee = getAmountPlusFee(parseFloat(amount), data.provider);
|
||||
|
||||
const payment = await checkout.createPayment(
|
||||
{
|
||||
description: 'Паевый взнос',
|
||||
amount: {
|
||||
value: amount_plus_fee.toFixed(2),
|
||||
currency: 'RUB',
|
||||
},
|
||||
confirmation: {
|
||||
type: 'embedded',
|
||||
},
|
||||
metadata: {
|
||||
internal_id: internal_id,
|
||||
},
|
||||
capture: true,
|
||||
},
|
||||
internal_id
|
||||
);
|
||||
|
||||
paymentDetails = {
|
||||
url: '',
|
||||
token: payment?.confirmation?.confirmation_token || '',
|
||||
};
|
||||
|
||||
if (!paymentDetails.token)
|
||||
throw new ApiError(
|
||||
httpStatus.INTERNAL_SERVER_ERROR,
|
||||
'Возникла проблема при получении платежного токена. Пожалуйста, повторите позже.'
|
||||
);
|
||||
|
||||
// 3. Используешь ссылку для создания ордера по блокчейну
|
||||
const order = {
|
||||
coopname: process.env.COOPNAME,
|
||||
username: username,
|
||||
type: 'deposit',
|
||||
quantity: data.quantity,
|
||||
};
|
||||
|
||||
order_id = await blockchainService.createOrder(order);
|
||||
|
||||
// 4. Обновляешь ордер идентификатором ордера в блокчейне (order_id)
|
||||
await Order.updateOne({ _id: internal_id }, { order_id });
|
||||
}
|
||||
|
||||
return { order_id, provider: data.provider, details: paymentDetails };
|
||||
}
|
||||
|
||||
export async function createInitialOrder(username: string, data: ICreateInitialPayment): Promise<ICreatedPayment> {
|
||||
let cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
|
||||
if (!cooperative) throw new Error('Кооператив не найден');
|
||||
|
||||
const user = await getUserByUsername(username);
|
||||
let amount = '';
|
||||
|
||||
if (user.type === 'individual' || user.type === 'entrepreneur') amount = cooperative.registration;
|
||||
else amount = cooperative.org_registration;
|
||||
|
||||
const [, symbol] = cooperative.initial.split(' ');
|
||||
let order_id = 0;
|
||||
|
||||
let paymentDetails = {} as PaymentDetails; // Инициализация переменной для деталей
|
||||
|
||||
// 1. Создаёшь ордер в базе и получаешь внутренний айди
|
||||
const db_order = await Order.create({
|
||||
creator: process.env.COOPNAME,
|
||||
type: 'registration',
|
||||
data: { provider: data.provider, username, quantity: amount },
|
||||
});
|
||||
const internal_id = db_order._id;
|
||||
|
||||
if (data.provider === 'yookassa') {
|
||||
// 2. Используешь внутренний айди для получения ордера в системе платежей
|
||||
const checkout = new YooCheckout({
|
||||
shopId: process.env.YA_SHOP_ID as string,
|
||||
secretKey: process.env.YA_SHOP_SECRET as string,
|
||||
token: process.env.YA_ACCESS_TOKEN as string,
|
||||
});
|
||||
|
||||
const amount_plus_fee = getAmountPlusFee(parseFloat(amount), data.provider);
|
||||
|
||||
const payment = await checkout.createPayment(
|
||||
{
|
||||
description: 'Регистрационный взнос',
|
||||
amount: {
|
||||
value: amount_plus_fee.toFixed(2),
|
||||
currency: 'RUB',
|
||||
},
|
||||
confirmation: {
|
||||
type: 'embedded',
|
||||
},
|
||||
metadata: {
|
||||
internal_id: internal_id,
|
||||
},
|
||||
capture: true,
|
||||
},
|
||||
internal_id
|
||||
);
|
||||
|
||||
paymentDetails = {
|
||||
url: '',
|
||||
token: payment?.confirmation?.confirmation_token || '',
|
||||
};
|
||||
|
||||
if (!paymentDetails.token)
|
||||
throw new ApiError(
|
||||
httpStatus.INTERNAL_SERVER_ERROR,
|
||||
'Возникла проблема при получении платежного токена. Пожалуйста, повторите позже.'
|
||||
);
|
||||
|
||||
// 3. Используешь ссылку для создания ордера по блокчейну
|
||||
const order = {
|
||||
coopname: process.env.COOPNAME,
|
||||
username: username,
|
||||
type: 'registration',
|
||||
quantity: amount,
|
||||
};
|
||||
|
||||
order_id = await blockchainService.createOrder(order);
|
||||
|
||||
// 4. Обновляешь ордер идентификатором ордера в блокчейне (order_id)
|
||||
await Order.updateOne({ _id: internal_id }, { order_id });
|
||||
}
|
||||
|
||||
return { order_id, provider: data.provider, details: paymentDetails };
|
||||
}
|
||||
|
||||
export async function catchIPN(ipnBody: IYandexIPN) {
|
||||
const payments = await connection.db.collection('payments');
|
||||
const exist = await payments.findOne({ 'object.id': ipnBody.object.id });
|
||||
|
||||
if (!exist) {
|
||||
await payments.insertOne(ipnBody);
|
||||
const { internal_id } = ipnBody.object.metadata;
|
||||
const order = await Order.findOne({ _id: internal_id });
|
||||
|
||||
if (order) {
|
||||
if (ipnBody.event === 'payment.succeeded') {
|
||||
order.payed = true;
|
||||
|
||||
const tolerancePercentage = 0.1; // Допустимая погрешность в процентах
|
||||
const tolerance = parseFloat(order.data.quantity) * (tolerancePercentage / 100); // Абсолютное значение погрешности
|
||||
|
||||
if (parseFloat(ipnBody.object.income_amount.value) < parseFloat(order.data.quantity) - tolerance) {
|
||||
order.error = {
|
||||
message: `Недостаточно средств, поступило: ${ipnBody.object.income_amount.value} ожидается: ${order.data.quantity}`,
|
||||
};
|
||||
order.delivered = false;
|
||||
await order.save();
|
||||
return;
|
||||
}
|
||||
|
||||
await order.save();
|
||||
|
||||
const user = await getUserByUsername(order.data.username);
|
||||
|
||||
try {
|
||||
if (!user) throw new Error(`Пользователь не найден ${order.data.username}`);
|
||||
|
||||
if (order.type === 'registration') {
|
||||
await blockchainService.registerBlockchainAccount(user, {
|
||||
coopname: process.env.COOPNAME as string,
|
||||
admin: process.env.COOPNAME as string,
|
||||
deposit_id: order.order_id as number,
|
||||
memo: '',
|
||||
});
|
||||
|
||||
console.log('Зарегистрирован новый пользователь: ', user.username);
|
||||
user.status = 'registered';
|
||||
user.is_registered = true;
|
||||
} else if (order.type === 'deposit') {
|
||||
await blockchainService.completeOrder({
|
||||
coopname: process.env.COOPNAME as string,
|
||||
admin: process.env.COOPNAME as string,
|
||||
deposit_id: order.order_id as number,
|
||||
memo: '',
|
||||
});
|
||||
const quantity = parseFloat(order.data.quantity);
|
||||
console.log(`Принят паевый взнос пользователя: ${user.username} на сумму ${order.data.quantity}`);
|
||||
}
|
||||
|
||||
await user.save();
|
||||
|
||||
order.delivered = true;
|
||||
await order.save();
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
order.error = e;
|
||||
order.delivered = false;
|
||||
await order.save();
|
||||
|
||||
await blockchainService.failOrder({
|
||||
coopname: process.env.COOPNAME,
|
||||
admin: process.env.COOPNAME,
|
||||
deposit_id: order.order_id,
|
||||
memo: '',
|
||||
});
|
||||
|
||||
user.status = 'failed';
|
||||
user.message = e.message;
|
||||
await user.save();
|
||||
}
|
||||
} else if (ipnBody.event === 'payment.canceled') {
|
||||
await blockchainService.failOrder({
|
||||
coopname: process.env.COOPNAME,
|
||||
admin: process.env.COOPNAME,
|
||||
deposit_id: order.order_id,
|
||||
memo: '',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Ордер не найден`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { PaymentProvider } from '../paymentProvider';
|
||||
|
||||
export interface IPNProvider extends PaymentProvider {
|
||||
handleIPN(request: any): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import YooKassaIPNProvider from './yooKassaIPNProvider';
|
||||
import { IPNProvider } from './ipnProvider';
|
||||
|
||||
export class IPNProviderFactory {
|
||||
static createProvider(providerName: string): IPNProvider {
|
||||
switch (providerName) {
|
||||
case 'yookassa':
|
||||
return new YooKassaIPNProvider();
|
||||
// Добавляем другие провайдеры для IPN
|
||||
default:
|
||||
throw new Error(`Провайдер IPN ${providerName} не поддерживается`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const ipnProviderNames = ['yookassa'];
|
||||
@@ -0,0 +1,226 @@
|
||||
import { YooCheckout } from '@a2seven/yoo-checkout';
|
||||
import { Order } from '../../../models';
|
||||
import { IPNProvider } from './ipnProvider';
|
||||
import Redis from 'ioredis';
|
||||
import type { PaymentDetails } from '../../../types';
|
||||
import IPN from '../../../models/ipn.model';
|
||||
import { checkPaymentAmount, checkPaymentSymbol, getAmountPlusFee } from '../../order.service';
|
||||
import mongoose from 'mongoose';
|
||||
import logger from '../../../config/logger';
|
||||
import Settings from '../../../models/settings.model';
|
||||
import type { isDeepStrictEqual } from 'util';
|
||||
import config from '../../../config/config';
|
||||
|
||||
const redis = new Redis();
|
||||
|
||||
interface IIpnRequest {
|
||||
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: {
|
||||
secret: string;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
class YooKassaIPNProvider implements IPNProvider {
|
||||
public tolerance_percent = 0; /// (0.0005%) < Допустимая погрешность приёма платежей
|
||||
public fee_percent = 3.5; ///%
|
||||
|
||||
async handleIPN(request: IIpnRequest): Promise<void> {
|
||||
const { event, object } = request;
|
||||
|
||||
const exist = (await IPN.findOne({ 'data.object.id': request.object.id }))?.data as IIpnRequest;
|
||||
|
||||
if (!exist) {
|
||||
await IPN.create({ provider: 'yookassa', data: request });
|
||||
|
||||
const { secret } = request.object.metadata;
|
||||
const order = await Order.findOne({ secret });
|
||||
|
||||
if (order) {
|
||||
logger.info('Order found', { source: 'handleIPN', id: order.id });
|
||||
|
||||
if (event === 'payment.succeeded') {
|
||||
const [, symbol] = order.quantity.split(' ');
|
||||
|
||||
const symbol_result = checkPaymentSymbol(request.object.income_amount.currency, symbol);
|
||||
|
||||
if (symbol_result.status == 'error') {
|
||||
logger.warn('Payment symbol verification failed', {
|
||||
source: 'handleIPN',
|
||||
id: order.id,
|
||||
message: symbol_result.message,
|
||||
});
|
||||
|
||||
await Order.updateOne({ _id: order.id }, { status: 'failed', message: symbol_result.message });
|
||||
redis.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id: order.id, status: 'failed' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const result = checkPaymentAmount(request.object.income_amount.value, order.quantity, this.tolerance_percent);
|
||||
|
||||
if (result.status === 'success') {
|
||||
// Обработка успешного платежа
|
||||
logger.info('Payment amount verified, updating order status to paid', {
|
||||
source: 'handleIPN',
|
||||
id: order.id,
|
||||
});
|
||||
|
||||
await Order.updateOne({ _id: order.id }, { status: 'paid' });
|
||||
redis.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id: order.id, status: 'paid' }));
|
||||
} else {
|
||||
// Обработка неудачного платежа
|
||||
logger.warn('Payment amount verification failed', {
|
||||
source: 'handleIPN',
|
||||
id: order.id,
|
||||
message: result.message,
|
||||
});
|
||||
|
||||
await Order.updateOne({ _id: order.id }, { status: 'failed', message: result.message });
|
||||
redis.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id: order.id, status: 'failed' }));
|
||||
}
|
||||
} else if (event === 'payment.failed') {
|
||||
// Обработка неудачного платежа
|
||||
logger.warn('Payment failed event received', { source: 'handleIPN', id: order.id });
|
||||
|
||||
await Order.updateOne({ _id: order.id }, { status: 'failed' });
|
||||
redis.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id: order.id, status: 'failed' }));
|
||||
}
|
||||
} else {
|
||||
//TODO платеж есть, а ордера на него нет. Что делаем?
|
||||
logger.error('Payment exists, but order not found', { source: 'handleIPN', requestId: request.object.id });
|
||||
}
|
||||
} else {
|
||||
logger.warn('IPN already processed', { source: 'handleIPN', requestId: request.object.id });
|
||||
}
|
||||
}
|
||||
|
||||
async createPayment(
|
||||
amount: string,
|
||||
symbol: string,
|
||||
description: string,
|
||||
order_id: number,
|
||||
secret: string
|
||||
): Promise<PaymentDetails> {
|
||||
const settings = await Settings.getSettings();
|
||||
|
||||
const checkout = new YooCheckout({
|
||||
shopId: settings.provider.client,
|
||||
secretKey: settings.provider.secret,
|
||||
});
|
||||
|
||||
const amount_plus_fee = getAmountPlusFee(parseFloat(amount), this.fee_percent).toFixed(2);
|
||||
const fee_amount = (parseFloat(amount_plus_fee) - parseFloat(amount)).toFixed(2);
|
||||
|
||||
// Фактический процент комиссии
|
||||
const fact_fee_percent = Math.round((parseFloat(fee_amount) / parseFloat(amount)) * 100 * 100) / 100;
|
||||
|
||||
const payment = await checkout.createPayment(
|
||||
{
|
||||
description,
|
||||
amount: {
|
||||
value: amount_plus_fee,
|
||||
currency: 'RUB',
|
||||
},
|
||||
confirmation: {
|
||||
type: 'embedded',
|
||||
},
|
||||
metadata: {
|
||||
secret,
|
||||
},
|
||||
capture: true,
|
||||
},
|
||||
secret
|
||||
);
|
||||
return {
|
||||
data: payment?.confirmation?.confirmation_token || '',
|
||||
amount_plus_fee: `${amount_plus_fee} ${symbol}`,
|
||||
amount_without_fee: amount,
|
||||
fee_amount: `${fee_amount} ${symbol}`,
|
||||
fee_percent: this.fee_percent,
|
||||
fact_fee_percent,
|
||||
tolerance_percent: this.tolerance_percent,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default YooKassaIPNProvider;
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { PaymentDetails } from '../../types';
|
||||
|
||||
export interface PaymentProvider {
|
||||
tolerance_percent: number;
|
||||
fee_percent: number;
|
||||
createPayment(
|
||||
amount: string,
|
||||
symbol: string,
|
||||
description: string,
|
||||
order_id: number,
|
||||
secret: string
|
||||
): Promise<PaymentDetails>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { PaymentProvider } from '../paymentProvider';
|
||||
|
||||
export interface PollingProvider extends PaymentProvider {
|
||||
sync(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import SberbankPollingProvider from './sberbankPollingProvider';
|
||||
import { PollingProvider } from './pollingProvider';
|
||||
|
||||
export class PollingProviderFactory {
|
||||
static createProvider(providerName: string): PollingProvider {
|
||||
switch (providerName) {
|
||||
case 'sberbank':
|
||||
return new SberbankPollingProvider();
|
||||
// Добавляем другие провайдеры для Polling
|
||||
default:
|
||||
throw new Error(`Провайдер Polling ${providerName} не поддерживается`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const pollingProviderNames = ['sberbank'];
|
||||
@@ -0,0 +1,248 @@
|
||||
import { PollingProvider } from './pollingProvider';
|
||||
import { Order } from '../../../models';
|
||||
import type { PaymentDetails } from '../../../types';
|
||||
import { generator } from '../../document.service';
|
||||
import { redisPublisher } from '../../redis.service';
|
||||
import { PaymentState } from '../../../models/paymentState.model';
|
||||
import axios from 'axios';
|
||||
import logger from '../../../config/logger';
|
||||
import { checkPaymentAmount, checkPaymentSymbol, getAmountPlusFee } from '../../order.service';
|
||||
import { orderStatus } from '../../../models/order.model';
|
||||
import config from '../../../config/config';
|
||||
|
||||
interface Link {
|
||||
href: string;
|
||||
rel: string;
|
||||
}
|
||||
|
||||
interface StatementTransactionV2 {
|
||||
id: string;
|
||||
transactionDate: string;
|
||||
paymentPurpose: string;
|
||||
amountRub: {
|
||||
amount: string;
|
||||
currencyName: string;
|
||||
};
|
||||
direction: 'DEBIT' | 'CREDIT';
|
||||
documentDate: string; //дата 2023-11-14;
|
||||
rurTransfer: {
|
||||
payerAccount: string; //'40802810706000000087';
|
||||
payerName: string; //'ИП_Автотест_Клиент_ЕКС_20231027092336';
|
||||
payerInn: string; //'213504669246';
|
||||
payerKpp: string; //'346801713';
|
||||
payerBankName: string; //'БАШКИРСКОЕ ОТДЕЛЕНИЕ N8598 ПАО СБЕРБАНК, г. Уфа';
|
||||
payerBankBic: string; //'048073601';
|
||||
payerBankCorrAccount: string; //'30101810300000000601';
|
||||
payeeAccount: string; //'40702810006000001792';
|
||||
payeeName: string; //'ТЕСТ9036';
|
||||
payeeInn: string; //'7379190522';
|
||||
payeeKpp: string; //'683801910';
|
||||
payeeBankName: string; //'БАШКИРСКОЕ ОТДЕЛЕНИЕ N8598 ПАО СБЕРБАНК, г. Уфа';
|
||||
payeeBankBic: string; //'048073601';
|
||||
payeeBankCorrAccount: string; //'30101810300000000601';
|
||||
receiptDate: string; //'2023-11-14';
|
||||
valueDate: string; //'2023-11-14';
|
||||
};
|
||||
// Добавьте другие необходимые поля
|
||||
// https://developers.sber.ru/docs/ru/sber-api/host/statements
|
||||
}
|
||||
|
||||
interface StatementTransactionsV2 {
|
||||
_links: Link[];
|
||||
transactions: StatementTransactionV2[];
|
||||
}
|
||||
|
||||
class SberbankPollingProvider implements PollingProvider {
|
||||
public tolerance_percent = 0; /// (0.0005%) < Допустимая погрешность приёма платежей
|
||||
public fee_percent = 0; ///%
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
constructor() {}
|
||||
|
||||
public async createPayment(
|
||||
amount: string,
|
||||
symbol: string,
|
||||
description: string,
|
||||
order_id: number,
|
||||
secret: string
|
||||
): Promise<PaymentDetails> {
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
const amount_plus_fee = getAmountPlusFee(parseFloat(amount), this.fee_percent).toFixed(2);
|
||||
const fee_amount = (parseFloat(amount_plus_fee) - parseFloat(amount)).toFixed(2);
|
||||
const fact_fee_percent = Math.round((parseFloat(fee_amount) / parseFloat(amount)) * 100 * 100) / 100;
|
||||
|
||||
const invoice = `ST00012|Name=${cooperative?.full_name}|PersonalAcc=${
|
||||
cooperative?.bank_account.account_number
|
||||
}|BankName=${cooperative?.bank_account.bank_name}|BIC=${cooperative?.bank_account.details.bik}|CorrespAcc=${
|
||||
cooperative?.bank_account.details.corr
|
||||
}|Sum=${parseInt(amount)}00|Purpose=${description}. Без НДС.|PayeeINN=${cooperative?.details.inn}|KPP=${
|
||||
cooperative?.details.kpp
|
||||
};`;
|
||||
|
||||
const result: PaymentDetails = {
|
||||
data: invoice,
|
||||
amount_plus_fee: `${amount_plus_fee} ${symbol}`,
|
||||
amount_without_fee: amount,
|
||||
fee_amount: `${fee_amount} ${symbol}`,
|
||||
fee_percent: this.fee_percent,
|
||||
fact_fee_percent,
|
||||
tolerance_percent: this.tolerance_percent,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getAccessToken(): string {
|
||||
// Реализуйте получение токена доступа
|
||||
return 'YOUR_ACCESS_TOKEN';
|
||||
}
|
||||
|
||||
private extractOrderNumber(paymentPurpose: string): string {
|
||||
const regex = /№(\d+)/;
|
||||
const match = paymentPurpose.match(regex);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private getApiUrl(): string {
|
||||
return `https://api.sberbank.ru/fintech/api/v2/statement/transactions`;
|
||||
}
|
||||
|
||||
private getAccountNumber(): string {
|
||||
return 'ВАШ_НОМЕР_СЧЕТА';
|
||||
}
|
||||
|
||||
private getStatementDate(): string {
|
||||
return '2023-10-01';
|
||||
}
|
||||
|
||||
public async sync(): Promise<void> {
|
||||
const accessToken = await this.getAccessToken();
|
||||
const accountNumber = this.getAccountNumber();
|
||||
const statementDate = this.getStatementDate();
|
||||
|
||||
// Загрузка состояния обработки
|
||||
let paymentState = await PaymentState.findOne({ accountNumber, statementDate });
|
||||
|
||||
// Если состояния нет, создаем новое
|
||||
if (!paymentState) {
|
||||
paymentState = new PaymentState({
|
||||
accountNumber,
|
||||
statementDate,
|
||||
lastProcessedPage: 1, // Начинаем с первой страницы
|
||||
});
|
||||
}
|
||||
|
||||
let hasNextPage = true;
|
||||
let page = paymentState.lastProcessedPage; // Начинаем с сохраненной страницы
|
||||
|
||||
// Формирование URL запроса
|
||||
const url = this.getApiUrl();
|
||||
|
||||
while (hasNextPage) {
|
||||
// Параметры запроса
|
||||
const params = {
|
||||
accountNumber,
|
||||
statementDate,
|
||||
page,
|
||||
};
|
||||
|
||||
try {
|
||||
// Выполнение запроса к API
|
||||
const response = await axios.get<StatementTransactionsV2>(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
params,
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
|
||||
// Обработка транзакций
|
||||
const transactions = data.transactions || [];
|
||||
|
||||
for (const transaction of transactions) {
|
||||
// Извлечение номера заказа
|
||||
const orderNumber = this.extractOrderNumber(transaction.paymentPurpose);
|
||||
|
||||
if (!orderNumber) {
|
||||
logger.warn(
|
||||
`Не найден номер заказа в строке назначения платежа: ${transaction.paymentPurpose} транзакции ${transaction.id}`
|
||||
);
|
||||
break; //прерываем цикл - в платеже не указан номер заказа
|
||||
}
|
||||
|
||||
logger.info(`Обработка транзакции ${transaction.id} с номером заказа ${orderNumber}`);
|
||||
|
||||
const order = await Order.findOne({ order_id: orderNumber });
|
||||
|
||||
if (!order) {
|
||||
logger.warn(`Не найден заказ с номером ${orderNumber} по транзакции ${transaction.id}`);
|
||||
break;
|
||||
}
|
||||
|
||||
const [, symbol] = order.quantity.split(' ');
|
||||
const symbol_check = checkPaymentSymbol(transaction.amountRub.currencyName, symbol);
|
||||
|
||||
if (symbol_check.status === 'error') {
|
||||
logger.warn(symbol_check.message);
|
||||
order.status = orderStatus.failed;
|
||||
order.message = symbol_check.message;
|
||||
await order.save();
|
||||
redisPublisher.publish(
|
||||
`${config.coopname}:orderStatusUpdate`,
|
||||
JSON.stringify({ id: order.id, status: 'failed' })
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
//ордер найден, проверяем сумму платежа
|
||||
const amount_check = checkPaymentAmount(transaction.amountRub.amount, order.quantity, this.tolerance_percent);
|
||||
|
||||
if (amount_check.status === 'error') {
|
||||
logger.warn(amount_check.message);
|
||||
order.status = orderStatus.failed;
|
||||
order.message = amount_check.message;
|
||||
await order.save();
|
||||
redisPublisher.publish(
|
||||
`${config.coopname}:orderStatusUpdate`,
|
||||
JSON.stringify({ id: order.id, status: 'failed' })
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
//отмечаем ордер оплаченным если все проверки пройдены
|
||||
order.status = orderStatus.paid;
|
||||
await order.save();
|
||||
|
||||
redisPublisher.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id: order.id, status: 'paid' }));
|
||||
}
|
||||
|
||||
// Обновление состояния после обработки текущей страницы
|
||||
paymentState.lastProcessedPage = page;
|
||||
await paymentState.save();
|
||||
|
||||
// Проверка наличия следующей страницы
|
||||
const nextLinkObj = data._links?.find((link) => link.rel === 'next');
|
||||
|
||||
if (nextLinkObj) {
|
||||
hasNextPage = true;
|
||||
page += 1; // Увеличиваем номер страницы на 1
|
||||
} else {
|
||||
hasNextPage = false;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Ошибка при обработке страницы ${page}:`, error);
|
||||
hasNextPage = false;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Все транзакции обработаны.');
|
||||
}
|
||||
}
|
||||
|
||||
export default SberbankPollingProvider;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PaymentProvider } from './paymentProvider';
|
||||
|
||||
import YooKassaIPNProvider from './ipn/yooKassaIPNProvider';
|
||||
import SberbankProvider from './polling/sberbankPollingProvider';
|
||||
|
||||
export class ProviderFactory {
|
||||
static createProvider(providerName: string): PaymentProvider {
|
||||
switch (providerName) {
|
||||
case 'yookassa':
|
||||
return new YooKassaIPNProvider(); // Поддерживает IPN
|
||||
case 'sberbank':
|
||||
return new SberbankProvider(); // Поддерживает Polling
|
||||
default:
|
||||
throw new Error(`Провайдер ${providerName} не поддерживается`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { PollingProviderFactory } from './payment/polling/pollingProviderFactory';
|
||||
import { Order } from '../models';
|
||||
import { pollingProviderNames } from './payment/polling/pollingProviderNames';
|
||||
import { redisPublisher } from './redis.service';
|
||||
|
||||
export async function initPolling() {
|
||||
const pollingInterval = 60000;
|
||||
|
||||
setInterval(async () => {
|
||||
//синхронизируем все платежи провайдеров
|
||||
for (const providerName of pollingProviderNames) {
|
||||
const provider = PollingProviderFactory.createProvider(providerName);
|
||||
await provider.sync();
|
||||
}
|
||||
|
||||
// // Извлекаем все заказы со статусом "pending" polling-провайдеров
|
||||
// const pendingOrders = await Order.find({ status: 'pending', provider: { $in: pollingProviderNames } });
|
||||
|
||||
// for (const order of pendingOrders) {
|
||||
// try {
|
||||
// // Создаем провайдера
|
||||
// const provider = PollingProviderFactory.createProvider(order.provider);
|
||||
|
||||
// // Проверяем, поддерживает ли провайдер Polling
|
||||
|
||||
// // Если да, запускаем проверку статуса через Polling
|
||||
// const status = await provider.checkPaymentStatus(order.id);
|
||||
|
||||
// if (status === 'paid' || status === 'failed') {
|
||||
// // Обновляем ордер и отправляем информацию через Redis
|
||||
// await Order.updateOne({ _id: order._id }, { status });
|
||||
// redisPublisher.publish(`${config.coopname}:orderStatusUpdate`, JSON.stringify({ id: order.id, status }));
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error(`Ошибка при опросе ордера ${order.id}:`, error);
|
||||
// }
|
||||
// }
|
||||
}, pollingInterval);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
export const redisSubscriber = new Redis();
|
||||
export const redisPublisher = new Redis();
|
||||
@@ -0,0 +1,10 @@
|
||||
import Settings from '../models/settings.model';
|
||||
import type { ISettings } from '../types';
|
||||
|
||||
export const getSettings = async (): Promise<ISettings> => {
|
||||
return await Settings.getSettings();
|
||||
};
|
||||
|
||||
export const updateSettings = async (settings: Partial<ISettings>) => {
|
||||
await Settings.updateSettings(settings);
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user