Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 095c77b65b | |||
| c1c07bff8f | |||
| 1206bd0e98 | |||
| 67bcd1b307 | |||
| 31ef70e433 | |||
| afb12961d2 | |||
| bc5d6e38c8 | |||
| 5a39143051 | |||
| 18e9571877 | |||
| a828f4c831 | |||
| 54ac12f250 | |||
| 9a6a72f605 | |||
| 03949489e8 | |||
| 8ebd735e7e | |||
| 39246da73f | |||
| bf33f60368 | |||
| 492a613769 | |||
| 2c02c3434e | |||
| a186ddd9d2 | |||
| 4b0c7a5e4e | |||
| 54cfbe3ab6 | |||
| 02015cf0b8 | |||
| 416e4d3598 | |||
| e0d9c4b0ea | |||
| 2659e3bc09 | |||
| d296167b6a | |||
| 89b0aa7fec | |||
| 787aadf444 | |||
| d67161a29b | |||
| 7f4777ead4 | |||
| 1e2147b143 | |||
| 9f0a2fbf63 | |||
| 7804851bc9 | |||
| adc50dd89e | |||
| 5378cf5ddc | |||
| a733bb605f | |||
| aaa1b5a264 | |||
| 5855cdee65 | |||
| c0d584e644 | |||
| 955f0f473e | |||
| 9ceb888124 | |||
| e902933693 | |||
| e07b47fb10 | |||
| 5bf08d9701 | |||
| 597fa1e6fa | |||
| dda3ec0ba0 | |||
| 991f08c749 | |||
| 923865cd7c | |||
| 606aee8ccd | |||
| 4a1cb27d0a | |||
| 26cd7f3a12 | |||
| f4045dbc56 | |||
| 33c2b24550 | |||
| cd86f3c8ea | |||
| 58026ebc2d | |||
| 9183275dad | |||
| 5e3a625d95 | |||
| 3297f9d2d2 | |||
| 29b8306a28 | |||
| d84746bac6 | |||
| 4eacf2ff0b | |||
| a68e0d6f61 | |||
| ddf44304c7 | |||
| 789d98f58e | |||
| 205574620d | |||
| d9bae12b31 | |||
| c951190794 | |||
| d4e1d69c3c | |||
| f93a32c07b | |||
| 8e7c152c0d | |||
| 6a64404f20 | |||
| 1a09589add | |||
| d0f9072ab5 | |||
| 011ed9f2d8 | |||
| 12332f83d9 | |||
| 8f75829948 | |||
| a7ffedb3b2 | |||
| 378d3b9747 | |||
| 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 |
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.1.0](https://github.com/coopenomics/monocoop/compare/v2.0.10-alpha.3...v2.1.0) (2024-10-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* запрос соглашений ([9a6a72f](https://github.com/coopenomics/monocoop/commit/9a6a72f605ba52eef2ed6f18ccee6fbed287ea00))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.9](https://github.com/coopenomics/monocoop/compare/v2.0.8...v2.0.9) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.8](https://github.com/coopenomics/monocoop/compare/v2.0.7...v2.0.8) (2024-10-09)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.7](https://github.com/coopenomics/monocoop/compare/v2.0.6...v2.0.7) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.6](https://github.com/coopenomics/monocoop/compare/v2.0.5...v2.0.6) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.5](https://github.com/coopenomics/monocoop/compare/v2.0.5-alpha.0...v2.0.5) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/coopenomics/monocoop/compare/v2.0.2-alpha.1...v2.0.2) (2024-09-29)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 2.1.0 (2024-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **importers:** исправлена ошибка в импорте модуля @wharfkit/contract ([bf89032](https://github.com/coopenomics/monocoop/commit/bf89032d8f66444804a2521f4eb96ffc75b0f605))
|
||||
* **terminal:** исправлен шрифт в README ([889447a](https://github.com/coopenomics/monocoop/commit/889447a93ceadb577613ddb5b1cb2ef1cc3d54b6))
|
||||
* **terminal:** исправлено описание проекта ([989b318](https://github.com/coopenomics/monocoop/commit/989b3180ded99a871e018cf26e6c493449223c01))
|
||||
* **terminal:** fix typo in README.md ([fdf9996](https://github.com/coopenomics/monocoop/commit/fdf999619d2d69e960b062fe6815f5b057c95f48))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* добавлен docker-compose.yaml ([6a46697](https://github.com/coopenomics/monocoop/commit/6a46697c9d6cc3cde14dbce8f70997c00f9850de))
|
||||
* **package:** добавлен скрипт gpt-commit для удобного коммита ([e0b5107](https://github.com/coopenomics/monocoop/commit/e0b510799bb0ac68890d572deb652beefd0651c4))
|
||||
* **terminal:** добавлена поддержка новых команд ([73e4ca2](https://github.com/coopenomics/monocoop/commit/73e4ca226acebcbea3ae59a62def99d86efb1353))
|
||||
@@ -0,0 +1,39 @@
|
||||
k# Conventional Commits
|
||||
|
||||
### Основные типы коммитов:
|
||||
|
||||
- **`feat`**: Добавление новой функциональности (feature).
|
||||
- Пример: `feat: добавлена возможность загрузки файлов`
|
||||
|
||||
- **`fix`**: Исправление бага.
|
||||
- Пример: `fix: исправлена ошибка отображения кнопки на мобильных устройствах`
|
||||
|
||||
- **`chore`**: Изменения, не влияющие на исходный код (например, обновление зависимостей или инструментария).
|
||||
- Пример: `chore: обновление зависимостей`
|
||||
|
||||
- **`docs`**: Изменения в документации.
|
||||
- Пример: `docs: обновлено руководство пользователя`
|
||||
|
||||
- **`style`**: Изменения стиля кода, не влияющие на его функциональность (форматирование, пробелы, и т.д.).
|
||||
- Пример: `style: исправлены отступы в коде`
|
||||
|
||||
- **`refactor`**: Изменения в коде, которые не исправляют баги и не добавляют новую функциональность.
|
||||
- Пример: `refactor: улучшена структура класса`
|
||||
|
||||
- **`perf`**: Изменения, направленные на улучшение производительности.
|
||||
- Пример: `perf: оптимизирована работа с массивами`
|
||||
|
||||
- **`test`**: Добавление или изменение тестов.
|
||||
- Пример: `test: добавлен тест для проверки функции отправки сообщений`
|
||||
|
||||
### Дополнительные типы коммитов:
|
||||
|
||||
- **`build`**: Изменения, связанные с процессом сборки или зависимостями.
|
||||
- Пример: `build: обновление конфигурации Webpack`
|
||||
|
||||
- **`ci`**: Изменения, касающиеся настроек непрерывной интеграции (например, Travis, Jenkins).
|
||||
- Пример: `ci: настройка Travis для автоматических сборок`
|
||||
|
||||
- **`revert`**: Откат на предыдущие изменения.
|
||||
- Пример: `revert: откат коммита e0b5107`
|
||||
|
||||
+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
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
# MONOCOOP
|
||||
|
||||
Моно-репозиторий компонент Цифрового Кооператива.
|
||||
Моно-репозиторий компонент клиента системы электронного документооборота для потребительских и производственных кооперативов.
|
||||
|
||||
## Инициализация
|
||||
```
|
||||
curl -X POST http://127.0.0.1:2998/v1/system/init \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "server-secret: SECRET" \
|
||||
-d @init-cooperative1.json
|
||||
```
|
||||
|
||||
## Лицензия
|
||||
Продукт Потребительского Кооператива "ВОСХОД" распространяется по лицензии BY-NC-SA 4.0.
|
||||
|
||||
Разрешено делиться, копировать и распространять материал на любом носителе и форме, адаптировать, делать ремиксы, видоизменять и создавать новое, опираясь на этот материал. При использовании, Вы должны обеспечить указание авторства, предоставить ссылку, и обозначить изменения, если таковые были сделаны. Если вы перерабатываете, преобразовываете материал или берёте его за основу для производного произведения, вы должны распространять переделанные вами части материала на условиях той же лицензии , в соответствии с которой распространяется оригинал. Запрещено коммерческое использование материала. Использование в коммерческих целях – это использование, в первую очередь направленное на получение коммерческого преимущества или денежного вознаграждения.
|
||||
|
||||
Юридический текст лицензии: https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.1.0](https://github.com/copenomics/cooparser/compare/v2.0.10-alpha.3...v2.1.0) (2024-10-13)
|
||||
|
||||
**Note:** Version bump only for package cooparser-ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.9](https://github.com/copenomics/cooparser/compare/v2.0.8...v2.0.9) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package cooparser-ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.8](https://github.com/copenomics/cooparser/compare/v2.0.7...v2.0.8) (2024-10-09)
|
||||
|
||||
**Note:** Version bump only for package cooparser-ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/copenomics/cooparser/compare/v2.0.2-alpha.1...v2.0.2) (2024-09-29)
|
||||
|
||||
**Note:** Version bump only for package cooparser-ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.20](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.20-alpha.0...cooparser-ts@1.0.20) (2024-09-29)
|
||||
|
||||
**Note:** Version bump only for package cooparser-ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pkg-placeholder",
|
||||
"version": "1.0.18",
|
||||
"version": "2.1.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pkg-placeholder",
|
||||
"version": "1.0.18",
|
||||
"version": "2.1.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.16.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cooparser-ts",
|
||||
"type": "module",
|
||||
"version": "1.0.18",
|
||||
"version": "2.1.1",
|
||||
"private": false,
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"description": "",
|
||||
|
||||
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: {}
|
||||
|
||||
@@ -15,10 +15,9 @@ export async function BlockParser(db: Database, reader: EosioShipReaderResolved)
|
||||
})
|
||||
|
||||
blocks$.subscribe(async (block: IBlock) => {
|
||||
// console.log('new block: ', block)
|
||||
// process.stdout.write('\r') // Возврат каретки в начало строки
|
||||
// process.stdout.write(`Блок: ${block.block_num}`) // Ваш текст
|
||||
|
||||
db.updateCurrentBlock(block.last_irreversible_block_num)
|
||||
db.updateCurrentBlock(block.block_num)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export class Database {
|
||||
private sync: Collection | undefined
|
||||
|
||||
constructor() {
|
||||
console.log('mongo2: ', mongoUri)
|
||||
this.client = new MongoClient(mongoUri)
|
||||
}
|
||||
|
||||
@@ -52,18 +51,15 @@ export class Database {
|
||||
|
||||
const pipeline = [
|
||||
{ $match: filter },
|
||||
{ $sort: { block_num: -1, _id: -1 } },
|
||||
{ $sort: { block_num: -1 } }, // Сортировка по primary_key и block_num
|
||||
{ $group: { _id: '$primary_key', doc: { $first: '$$ROOT' } } },
|
||||
// { $match: { 'doc.present': true } },
|
||||
{ $replaceRoot: { newRoot: '$doc' } },
|
||||
{ $sort: { block_num: -1 } },
|
||||
{ $skip: (page - 1) * limit }, // Применяется внутри пайплайна
|
||||
{ $limit: limit }, // Применяется внутри пайплайна
|
||||
]
|
||||
|
||||
const [result] = await Promise.all([
|
||||
this.deltas.aggregate(pipeline)
|
||||
.skip((page - 1) * limit)
|
||||
.limit(limit)
|
||||
.toArray(),
|
||||
])
|
||||
const result = await this.deltas.aggregate(pipeline).toArray()
|
||||
|
||||
return {
|
||||
results: result as IDelta[],
|
||||
@@ -80,9 +76,10 @@ export class Database {
|
||||
|
||||
const result = await this.actions.aggregate([
|
||||
{ $match: query },
|
||||
{ $sort: { block_num: -1, _id: -1 } },
|
||||
{ $sort: { block_num: -1 } }, // Сортировка по primary_key и block_num
|
||||
{ $group: { _id: '$global_sequence', doc: { $first: '$$ROOT' } } },
|
||||
{ $replaceRoot: { newRoot: '$doc' } },
|
||||
{ $sort: { block_num: -1 } },
|
||||
{ $skip: (page - 1) * limit },
|
||||
{ $limit: limit },
|
||||
]).toArray()
|
||||
|
||||
@@ -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' : ''}`
|
||||
@@ -55,6 +54,7 @@ export const subsribedActions: IActionConfig[] = [
|
||||
{ code: 'soviet', action: 'joincoopdec' },
|
||||
|
||||
{ code: 'soviet', action: 'updateboard', notify: true },
|
||||
{ code: 'soviet', action: 'createboard', notify: true },
|
||||
]
|
||||
|
||||
// --------------------------
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -12,4 +12,7 @@ yarn-error.log
|
||||
coverage
|
||||
|
||||
.env-mixed
|
||||
.env-local
|
||||
.env-local
|
||||
|
||||
logs
|
||||
logs/*
|
||||
|
||||
@@ -3,6 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.1.0](https://github.com/hagopj13/node-express-boilerplate/compare/v2.0.10-alpha.3...v2.1.0) (2024-10-13)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.8](https://github.com/hagopj13/node-express-boilerplate/compare/v2.0.7...v2.0.8) (2024-10-09)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.7](https://github.com/hagopj13/node-express-boilerplate/compare/v2.0.6...v2.0.7) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.6](https://github.com/hagopj13/node-express-boilerplate/compare/v2.0.5...v2.0.6) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.5](https://github.com/hagopj13/node-express-boilerplate/compare/v2.0.5-alpha.0...v2.0.5) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/hagopj13/node-express-boilerplate/compare/v2.0.2-alpha.1...v2.0.2) (2024-09-29)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.38](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.38-alpha.3...coopback@1.7.38) (2024-09-29)
|
||||
|
||||
**Note:** Version bump only for package coopback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -8,6 +8,7 @@ A boilerplate/starter project for quickly building RESTful APIs using Node.js, E
|
||||
|
||||
By running a single command, you will get a production-ready Node.js app installed and fully configured on your machine. The app comes with many built-in features, such as authentication using JWT, request validation, unit and integration tests, continuous integration, docker support, API documentation, pagination, etc. For more details, check the features list below.
|
||||
|
||||
|
||||
## Quick Start
|
||||
|
||||
To create a project, simply run:
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "create-nodejs-express-app",
|
||||
"version": "1.7.34",
|
||||
"version": "2.1.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "create-nodejs-express-app",
|
||||
"version": "1.7.34",
|
||||
"version": "2.1.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@a2seven/yoo-checkout": "^1.1.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "coopback",
|
||||
"version": "1.7.34",
|
||||
"version": "2.1.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"bin": "bin/createNodejsApp.js",
|
||||
@@ -81,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",
|
||||
@@ -88,8 +89,10 @@
|
||||
"joi-to-typescript": "^4.13.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jspdf": "^2.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.24.0",
|
||||
"mongoose": "^8.5.2",
|
||||
"mongoose-sequence": "^6.0.1",
|
||||
"morgan": "^1.9.1",
|
||||
"nodemailer": "^6.3.1",
|
||||
"nunjucks": "^3.2.4",
|
||||
@@ -103,8 +106,10 @@
|
||||
"ts-jest": "^29.1.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsd": "^0.31.0",
|
||||
"tsoa": "^6.4.0",
|
||||
"validator": "^13.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"xss-clean": "^0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
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') {
|
||||
|
||||
@@ -8,8 +8,7 @@ const envVarsSchema = Joi.object()
|
||||
.keys({
|
||||
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
|
||||
BASE_URL: Joi.string().required(),
|
||||
SERVICE_USERNAME: Joi.string().required(),
|
||||
SERVICE_WIF: Joi.string().required(),
|
||||
SERVER_SECRET: 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'),
|
||||
@@ -44,8 +43,7 @@ export default {
|
||||
env: envVars.NODE_ENV,
|
||||
base_url: envVars.BASE_URL,
|
||||
port: envVars.PORT,
|
||||
service_wif: envVars.SERVICE_WIF,
|
||||
service_username: envVars.SERVICE_USERNAME,
|
||||
server_secret: envVars.SERVER_SECRET,
|
||||
mongoose: {
|
||||
url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''),
|
||||
options: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import winston from 'winston';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
import config from './config';
|
||||
|
||||
const enumerateErrorFormat = winston.format((info) => {
|
||||
@@ -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,8 +1,24 @@
|
||||
const user = [
|
||||
'getSelf',
|
||||
'initialPayment',
|
||||
'generateDocument',
|
||||
'getMyDocuments',
|
||||
'getMyOrders',
|
||||
'sendVerificationEmail',
|
||||
'manageMyMethods',
|
||||
'createDeposit',
|
||||
'joinCooperative',
|
||||
'getVars',
|
||||
];
|
||||
|
||||
const member = [...user, 'getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo', 'manageOrders'];
|
||||
|
||||
const chairman = [...user, ...member, 'addUser', 'setVars', 'manageSettings', 'setWif'];
|
||||
|
||||
const allRoles = {
|
||||
user: [''],
|
||||
service: ['addUser', 'sendNotification', 'install'],
|
||||
chairman: ['addUser', 'getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
|
||||
member: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
|
||||
user,
|
||||
member,
|
||||
chairman,
|
||||
};
|
||||
|
||||
export const roles = Object.keys(allRoles);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const loadAgenda = catchAsync(async (req: Request, res: Response) => {
|
||||
const { coopname } = req.query;
|
||||
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);
|
||||
|
||||
@@ -12,10 +12,14 @@ export const generateDocument = catchAsync(async (req: RGenerate, res) => {
|
||||
|
||||
export const getDocuments = catchAsync(async (req: RGetDocuments, res) => {
|
||||
const filter = req.query?.filter;
|
||||
console.log('req.query', req.query);
|
||||
|
||||
const limit = req.query?.limit as number;
|
||||
const page = req.query?.page as number;
|
||||
|
||||
const documents = await documentService.queryDocuments(filter, page, limit);
|
||||
const type = req.query?.type as string;
|
||||
console.log('type on request', type);
|
||||
console.log('filter on request', filter);
|
||||
const documents = await documentService.queryDocuments(type, filter, page, limit);
|
||||
|
||||
res.status(httpStatus.OK).send(documents);
|
||||
});
|
||||
|
||||
@@ -1,7 +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 monoController from './mono.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();
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import httpStatus from 'http-status';
|
||||
import { getMonoStatus } from '../services/mono.service';
|
||||
import { getBlockchainInfo } from '../services/blockchain.service';
|
||||
import { IHealthResponse, IInstall, RInstall } from '../types';
|
||||
import { Request, Response } from 'express';
|
||||
import catchAsync from '../utils/catchAsync';
|
||||
import { monoService } from '../services';
|
||||
|
||||
export const install = catchAsync(async (req: RInstall, res: Response) => {
|
||||
const { body } = req;
|
||||
await monoService.install(body as IInstall);
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
|
||||
export const getHealth = catchAsync(async (req: Request, res: Response) => {
|
||||
const status = await getMonoStatus();
|
||||
const blockchain = await getBlockchainInfo();
|
||||
|
||||
const result: IHealthResponse = {
|
||||
status,
|
||||
blockchain,
|
||||
};
|
||||
|
||||
res.status(httpStatus.OK).send(result);
|
||||
});
|
||||
@@ -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_num', '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,113 +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 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 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,57 @@
|
||||
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 { IInit, ISetVars, RInit, RSetVars, RSetWif } from '../types/auto-generated/system.validation';
|
||||
import logger from '../config/logger';
|
||||
|
||||
export const init = catchAsync(async (req: RInit, res: Response) => {
|
||||
const { body } = req;
|
||||
await systemService.init(body as IInit);
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
|
||||
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 setWif = catchAsync(async (req: RSetWif, res: Response) => {
|
||||
await systemService.setWif(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);
|
||||
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);
|
||||
});
|
||||
|
||||
export const getVars = catchAsync(async (req: RSetVars, res: Response) => {
|
||||
const vars = await systemService.getVars();
|
||||
res.status(httpStatus.OK).send(vars);
|
||||
});
|
||||
@@ -2,15 +2,29 @@ import http from 'http-status';
|
||||
import ApiError from '../utils/ApiError';
|
||||
import catchAsync from '../utils/catchAsync';
|
||||
import { userService, tokenService, emailService, blockchainService } from '../services';
|
||||
import { IAddUser, ICreateUser, RCreateUser, RJoinCooperative } from '../types';
|
||||
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 { IGetResponse } from '../types/common';
|
||||
import { IUser, userStatus } from '../types';
|
||||
import { Request, Response } from 'express';
|
||||
import { generateUsername } from '../../tests/utils/generateUsername';
|
||||
import config from '../config/config';
|
||||
import logger from '../config/logger';
|
||||
// import { Body, Controller, Get, Path, Post, Query, Route, SuccessResponse } from 'tsoa';
|
||||
|
||||
// @Route('users')
|
||||
// export default class UsersController extends Controller {
|
||||
// @SuccessResponse(201, 'Created') // Указываем статус 201 и описание
|
||||
// @Post()
|
||||
// public async createUser(@Body() req: RCreateUser): Promise<{ user: IUser; tokens: any }> {
|
||||
// const user = await userService.createUser(req.body);
|
||||
// const tokens = await tokenService.generateAuthTokens(user);
|
||||
|
||||
// this.setStatus(201); // Устанавливаем статус 201 Created
|
||||
// return { user, tokens }; // Возвращаем объект в ответ
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Порядок регистрации:
|
||||
@@ -27,6 +41,7 @@ import logger from '../config/logger';
|
||||
* 11. (trx confirmpay) Пользователь подписывает велком-письмо с подтверждением собственноручности оплаты (транзакция в блокчейне для активации)
|
||||
* 12. Регистрация продолжается ожиданием решения совета
|
||||
*/
|
||||
|
||||
export const createUser = catchAsync(async (req: RCreateUser, res) => {
|
||||
const user = await userService.createUser(req.body);
|
||||
const tokens = await tokenService.generateAuthTokens(user);
|
||||
@@ -36,7 +51,7 @@ export const createUser = catchAsync(async (req: RCreateUser, res) => {
|
||||
|
||||
export const addUser = catchAsync(async (req: Request, res: Response) => {
|
||||
const body: IAddUser = req.body;
|
||||
|
||||
console.log('body: ', body);
|
||||
const newUser: ICreateUser = {
|
||||
...body,
|
||||
public_key: '',
|
||||
@@ -45,24 +60,25 @@ export const addUser = catchAsync(async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
const user = await userService.createUser(newUser);
|
||||
user.status = 'registered';
|
||||
user.status = userStatus['4_Registered'];
|
||||
user.is_registered = true;
|
||||
user.has_account = true;
|
||||
|
||||
await user.save();
|
||||
|
||||
try {
|
||||
await blockchainService.addUser({
|
||||
...body,
|
||||
...newUser,
|
||||
registrator: config.service_username,
|
||||
registrator: config.coopname,
|
||||
referer: body.referer ? body.referer : '',
|
||||
coopname: config.coopname,
|
||||
meta: '',
|
||||
});
|
||||
console.log('user: ', user);
|
||||
|
||||
const token = await tokenService.generateInviteToken(user.email);
|
||||
await emailService.sendInviteEmail(req.body.email, token);
|
||||
} catch (e: any) {
|
||||
console.log('on e: ', e);
|
||||
logger.warn('error on add user: ', e);
|
||||
await userService.deleteUserByUsername(newUser.username);
|
||||
throw new ApiError(httpStatus.BAD_GATEWAY, e.message);
|
||||
@@ -71,49 +87,18 @@ export const addUser = catchAsync(async (req: Request, res: Response) => {
|
||||
res.status(httpStatus.CREATED).send({ user });
|
||||
});
|
||||
|
||||
export const joinCooperative = catchAsync(async (req: RJoinCooperative, res) => {
|
||||
await userService.joinCooperative(req.body);
|
||||
|
||||
res.status(httpStatus.OK).send();
|
||||
});
|
||||
|
||||
export const getUsers = catchAsync(async (req, res) => {
|
||||
const filter = pick(req.query, ['username', 'role']);
|
||||
const options = pick(req.query, ['sortBy', 'limit', 'page']);
|
||||
|
||||
const users = await userService.queryUsers(filter, options);
|
||||
const result = {} as IGetResponse<IUser>;
|
||||
|
||||
const data = [] as any;
|
||||
for await (const user of users.results) {
|
||||
const json = user.toJSON();
|
||||
if (user.type != 'service') {
|
||||
json.private_data = await user.getPrivateData();
|
||||
data.push(json);
|
||||
}
|
||||
}
|
||||
|
||||
result.results = data;
|
||||
result.limit = users.limit;
|
||||
result.page = users.page;
|
||||
result.totalPages = users.totalPages;
|
||||
result.totalResults = users.totalResults;
|
||||
|
||||
res.send(result);
|
||||
res.send(users);
|
||||
});
|
||||
|
||||
export const getUser = catchAsync(async (req, res) => {
|
||||
const user = await userService.getUserByUsername(req.params.username);
|
||||
if (!user) {
|
||||
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
|
||||
}
|
||||
const json = user.toJSON();
|
||||
|
||||
if (user.type != 'service') {
|
||||
json.private_data = await user.getPrivateData();
|
||||
}
|
||||
|
||||
res.send(json);
|
||||
res.send(user);
|
||||
});
|
||||
|
||||
export const updateUser = catchAsync(async (req, res) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -35,4 +36,8 @@ async function processEvent(event: IAction) {
|
||||
if (event.receiver === SovietContract.contractName.production && event.name === 'updateboard') {
|
||||
wsService.updateBoard(event.data);
|
||||
}
|
||||
|
||||
if (event.receiver === SovietContract.contractName.production && event.name === 'createboard') {
|
||||
wsService.updateBoard(event.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,73 +14,6 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/auth/register": {
|
||||
"post": {
|
||||
"summary": "Register as user",
|
||||
"tags": [
|
||||
"Auth"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"email",
|
||||
"password"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"description": "must be unique"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"minLength": 8,
|
||||
"description": "At least one number and one letter"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"name": "fake name",
|
||||
"email": "fake@example.com",
|
||||
"password": "password1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
},
|
||||
"tokens": {
|
||||
"$ref": "#/components/schemas/AuthTokens"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/DuplicateEmail"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"summary": "Login",
|
||||
@@ -739,6 +672,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/add": {
|
||||
"post": {
|
||||
"summary": "Добавить пользователя без заявления",
|
||||
"description": "Только администраторы могут добавлять новых пользователей без прохождения процедуры подачи заявления. Это используется для добавления действующих пайщиков.",
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/IAddUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Пользователь успешно добавлен",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/BadRequest"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/Forbidden"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -844,11 +822,11 @@
|
||||
"ICreatedPayment": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"order_id",
|
||||
"order_num",
|
||||
"confirmation_token"
|
||||
],
|
||||
"properties": {
|
||||
"order_id": {
|
||||
"order_num": {
|
||||
"type": "number"
|
||||
},
|
||||
"confirmation_token": {
|
||||
@@ -1290,7 +1268,7 @@
|
||||
"tags": [
|
||||
{
|
||||
"name": "Auth",
|
||||
"description": "Authentication"
|
||||
"description": "Управление аудентификацией"
|
||||
},
|
||||
{
|
||||
"name": "Orders",
|
||||
|
||||
@@ -3,7 +3,7 @@ import config from '../config/config';
|
||||
const swaggerDef = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: "API Цифрового Кооператива",
|
||||
title: 'API Цифрового Кооператива',
|
||||
// description: `Модуль предоставляет:
|
||||
|
||||
// - API для хранения персональных данных пользователей
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { PluginConfig } from '../models/plugin.model';
|
||||
import { IPlugin } from '../types/plugin.types';
|
||||
import { Powerup } from '../plugins/powerup';
|
||||
import type { Application } from 'express';
|
||||
|
||||
const defaultPlugins = [{ name: 'Powerup', enabled: true, config: { optionA: true } }];
|
||||
|
||||
export const initializeDefaultPlugins = async () => {
|
||||
for (const plugin of defaultPlugins) {
|
||||
const existingPlugin = await PluginConfig.findOne({ name: plugin.name });
|
||||
if (!existingPlugin) {
|
||||
await PluginConfig.create(plugin);
|
||||
console.log(`Дефолтный плагин ${plugin.name} добавлен в базу.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Доступные плагины
|
||||
const availablePlugins: { [key: string]: IPlugin } = {
|
||||
Powerup: new Powerup(),
|
||||
};
|
||||
|
||||
export const pluginFactory = async (app: Application) => {
|
||||
// Загрузка всех активных плагинов из базы данных
|
||||
const plugins = await PluginConfig.find({ enabled: true });
|
||||
|
||||
for (const pluginData of plugins) {
|
||||
const plugin = availablePlugins[pluginData.name];
|
||||
if (plugin) {
|
||||
await plugin.initialize(app, pluginData.config);
|
||||
console.log(`Плагин ${plugin.name} инициализирован.`);
|
||||
} else {
|
||||
console.log(`Плагин ${pluginData.name} не найден.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
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 { redisSubscriber } from './services/redis.service';
|
||||
|
||||
let server: any;
|
||||
|
||||
mongoose.connect(config.mongoose.url).then(async () => {
|
||||
logger.info('Connected to MongoDB with payment processor');
|
||||
|
||||
// подключаемся к хранилищу приватных данных
|
||||
await connectGenerator();
|
||||
|
||||
redisSubscriber.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.`);
|
||||
}
|
||||
});
|
||||
|
||||
redisSubscriber.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();
|
||||
}
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import config from './config/config';
|
||||
import logger from './config/logger';
|
||||
import { connectGenerator } from './services/document.service';
|
||||
import { initSocketConnection } from './controllers/ws.controller';
|
||||
import { monoService } from './services';
|
||||
import { initializeDefaultPlugins, pluginFactory } from './factories/pluginFactory';
|
||||
|
||||
const SERVER_URL: string = process.env.SOCKET_SERVER || 'http://localhost:2222';
|
||||
|
||||
@@ -15,14 +15,18 @@ let server: any;
|
||||
mongoose.connect(config.mongoose.url).then(async () => {
|
||||
logger.info('Connected to MongoDB');
|
||||
|
||||
await monoService.init();
|
||||
|
||||
// подключаемся к хранилищу приватных данных
|
||||
await connectGenerator();
|
||||
|
||||
// подключаемся к ws-серверу
|
||||
await initSocketConnection(SERVER_URL);
|
||||
|
||||
// Инициализация дефолтных плагинов
|
||||
await initializeDefaultPlugins();
|
||||
|
||||
// Запуск плагинов
|
||||
await pluginFactory(app);
|
||||
|
||||
server = app.listen(config.port, () => {
|
||||
logger.info(`Listening to port ${config.port}`);
|
||||
});
|
||||
@@ -40,7 +44,7 @@ const exitHandler = () => {
|
||||
};
|
||||
|
||||
const unexpectedErrorHandler = (error: any) => {
|
||||
logger.error(error);
|
||||
logger.error(error, { source: 'unexpectedErrorHandler' });
|
||||
exitHandler();
|
||||
};
|
||||
|
||||
|
||||
@@ -6,17 +6,35 @@ 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));
|
||||
const userRights = roleRights.get(user.role) || [];
|
||||
|
||||
if (!hasRequiredRights && req.params.username !== user.username) {
|
||||
//
|
||||
// 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;
|
||||
|
||||
@@ -2,3 +2,4 @@ export { default as Token } from './token.model';
|
||||
export { default as User } from './user.model';
|
||||
export { default as Order } from './order.model';
|
||||
export { default as Mono } from './mono.model';
|
||||
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;
|
||||
@@ -1,62 +1,74 @@
|
||||
import mongoose, { Schema, Document, model } from 'mongoose';
|
||||
import mongoose, { Schema, Document, model, type Model } from 'mongoose';
|
||||
import { toJSON, paginate } from './plugins/index';
|
||||
import AutoIncrementFactory from 'mongoose-sequence';
|
||||
import { generateOrderSecret } from '../services/order.service';
|
||||
import { type IOrder, orderStatus } from '../types/order.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);
|
||||
|
||||
interface IOrderModel extends Model<IOrder> {
|
||||
isEmailTaken(email: string, excludeUsername?: mongoose.Types.ObjectId): Promise<boolean>;
|
||||
paginate(filter, options): any;
|
||||
}
|
||||
|
||||
const orderSchema = new Schema<IOrder>(
|
||||
const orderSchema = new Schema<IOrder, IOrderModel>(
|
||||
{
|
||||
order_num: {
|
||||
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,
|
||||
},
|
||||
data: {
|
||||
provider: {
|
||||
type: String,
|
||||
required: true,
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
type: String,
|
||||
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 },
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
order_id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
payed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
delivered: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
error: {
|
||||
type: Object,
|
||||
required: false,
|
||||
required: false, // Поле может быть не обязательным
|
||||
},
|
||||
expired_at: {
|
||||
type: Date,
|
||||
@@ -66,19 +78,40 @@ const orderSchema = new Schema<IOrder>(
|
||||
return now;
|
||||
},
|
||||
},
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: 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();
|
||||
}
|
||||
|
||||
// Проверяем поле expired_at и обновляем статус
|
||||
if (doc.status != orderStatus.expired && doc.expired_at && new Date() > doc.expired_at) {
|
||||
doc.status = orderStatus.expired;
|
||||
await doc.save(); // Сохраняем изменения в документе
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// add plugin that converts mongoose to json
|
||||
orderSchema.plugin(toJSON);
|
||||
orderSchema.plugin(paginate);
|
||||
// Добавляем автоинкремент для поля order_num
|
||||
orderSchema.plugin(AutoIncrement, { inc_field: 'order_num' });
|
||||
|
||||
/**
|
||||
* @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);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Schema, model } from 'mongoose';
|
||||
import type { IPluginSchema } from '../types/plugin.types';
|
||||
|
||||
const pluginSchema = new Schema({
|
||||
name: { type: String, required: true },
|
||||
enabled: { type: Boolean, default: true },
|
||||
config: { type: Schema.Types.Mixed }, // Своя конфигурация для каждого плагина
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const PluginConfig = model<IPluginSchema<any>>('Plugins', pluginSchema);
|
||||
@@ -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,7 +36,7 @@ const paginate = (schema) => {
|
||||
});
|
||||
sort = sortingCriteria.join(' ');
|
||||
} else {
|
||||
sort = 'createdAt';
|
||||
sort = '-createdAt';
|
||||
}
|
||||
|
||||
const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10;
|
||||
|
||||
@@ -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;
|
||||
@@ -1,41 +1,12 @@
|
||||
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';
|
||||
import { userStatus, type IUser } from '../types/user.types';
|
||||
|
||||
const { isEmail } = validator;
|
||||
const { compare, hash } = bcryptjs;
|
||||
|
||||
export interface IUser {
|
||||
username: string;
|
||||
status: 'created' | 'joined' | 'payed' | 'registered' | 'active' | 'failed' | 'blocked';
|
||||
message: string;
|
||||
is_registered: boolean;
|
||||
has_account: boolean;
|
||||
type: 'individual' | 'entrepreneur' | 'organization' | 'service';
|
||||
public_key: string;
|
||||
referer: string;
|
||||
email: string;
|
||||
role: string;
|
||||
is_email_verified: boolean;
|
||||
statement: {
|
||||
hash: string;
|
||||
meta: object;
|
||||
public_key: string;
|
||||
signature: string;
|
||||
};
|
||||
private_data:
|
||||
| Cooperative.Users.IIndividualData
|
||||
| Cooperative.Users.IEntrepreneurData
|
||||
| Cooperative.Users.IOrganizationData
|
||||
| null;
|
||||
getPrivateData(): Promise<
|
||||
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
|
||||
>;
|
||||
}
|
||||
|
||||
interface IUserModel extends Model<IUser> {
|
||||
isEmailTaken(email: string, excludeUsername?: mongoose.Types.ObjectId): Promise<boolean>;
|
||||
@@ -52,8 +23,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,
|
||||
@@ -101,23 +72,9 @@ 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: '',
|
||||
},
|
||||
initial_order: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Order',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -137,16 +94,42 @@ userSchema.statics.isEmailTaken = async function (email) {
|
||||
return !!user;
|
||||
};
|
||||
|
||||
// Хук после find для автоматического добавления private_data
|
||||
userSchema.post('find', async function (docs) {
|
||||
for (const doc of docs) {
|
||||
// Наполняем пользователя с помощью populate
|
||||
doc.private_data = await doc.getPrivateData();
|
||||
}
|
||||
});
|
||||
|
||||
// Хук после findOne для автоматического добавления private_data
|
||||
userSchema.post('findOne', async function (doc) {
|
||||
if (doc) {
|
||||
doc.private_data = await doc.getPrivateData();
|
||||
}
|
||||
});
|
||||
|
||||
// Виртуальное свойство для 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;
|
||||
};
|
||||
|
||||
const User = model<IUser, IUserModel>('User', userSchema);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import mongoose, { Schema, type Model } from 'mongoose';
|
||||
import { paginate, toJSON } from './plugins';
|
||||
import { decrypt, encrypt } from '../utils/aes';
|
||||
|
||||
export interface IVault {
|
||||
username: string;
|
||||
permission: wifPermissions;
|
||||
wif: string;
|
||||
}
|
||||
|
||||
export enum wifPermissions {
|
||||
'Active' = 'active',
|
||||
}
|
||||
|
||||
interface IVaultActions extends Model<IVault> {
|
||||
getWif(username: string, permission?: wifPermissions): Promise<string | null>;
|
||||
setWif(username: string, wif: string, permission?: wifPermissions): Promise<boolean>;
|
||||
}
|
||||
|
||||
const VaultSchema = new Schema<IVault, IVaultActions>({
|
||||
username: { type: String, required: true },
|
||||
permission: { type: String, required: true, enum: Object.values(wifPermissions) },
|
||||
wif: { type: String, required: true, private: true },
|
||||
});
|
||||
|
||||
// Метод для извлечения WIF с дефолтным значением permission
|
||||
VaultSchema.statics.getWif = async function (
|
||||
username: string,
|
||||
permission: wifPermissions = wifPermissions.Active
|
||||
): Promise<string | null> {
|
||||
const vault = await this.findOne({ username, permission });
|
||||
if (vault) {
|
||||
try {
|
||||
return decrypt(vault.wif); // Расшифровываем WIF перед возвратом
|
||||
} catch (error) {
|
||||
console.error('Ошибка при расшифровке WIF:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Метод для обновления или создания WIF с дефолтным значением permission
|
||||
VaultSchema.statics.setWif = async function (
|
||||
username: string,
|
||||
wif: string,
|
||||
permission: wifPermissions = wifPermissions.Active
|
||||
): Promise<boolean> {
|
||||
const encryptedWif = encrypt(wif); // Шифруем WIF перед сохранением
|
||||
|
||||
const result = await this.findOneAndUpdate(
|
||||
{ username, permission },
|
||||
{ wif: encryptedWif },
|
||||
{ new: true, upsert: true } // upsert создает новую запись, если не существует
|
||||
);
|
||||
|
||||
return result !== null;
|
||||
};
|
||||
|
||||
VaultSchema.plugin(toJSON);
|
||||
VaultSchema.plugin(paginate);
|
||||
|
||||
const Vault = mongoose.model<IVault, IVaultActions>('Vault', VaultSchema);
|
||||
|
||||
export default Vault;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Application } from 'express';
|
||||
import { IPlugin } from '../../types/plugin.types';
|
||||
|
||||
export class Powerup implements IPlugin {
|
||||
name = 'Powerup';
|
||||
|
||||
async initialize(app: Application, config: any) {
|
||||
console.log(`Инициализация ${this.name} с конфигурацией`, config);
|
||||
|
||||
// Добавление маршрута для плагина
|
||||
app.get('/pluginA', (req, res) => {
|
||||
res.send('Маршрут плагина A');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "powerup",
|
||||
"version": "2.1.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -12,7 +12,7 @@ router.post('/logout', validate(authValidation.RLogout), authController.logout);
|
||||
router.post('/refresh-tokens', validate(authValidation.RRefreshTokens), authController.refreshTokens);
|
||||
router.post('/lost-key', validate(authValidation.RForgotKey), authController.lostKey);
|
||||
router.post('/reset-key', validate(authValidation.RResetKey), authController.resetKey);
|
||||
router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
|
||||
router.post('/send-verification-email', auth('sendVerificationEmail'), authController.sendVerificationEmail);
|
||||
router.post('/verify-email', validate(authValidation.RVerifyEmail), authController.verifyEmail);
|
||||
|
||||
export default router;
|
||||
@@ -21,55 +21,7 @@ export default router;
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Auth
|
||||
* description: Authentication
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /auth/register:
|
||||
* post:
|
||||
* summary: Register as user
|
||||
* tags: [Auth]
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - email
|
||||
* - password
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* description: must be unique
|
||||
* password:
|
||||
* type: string
|
||||
* format: password
|
||||
* minLength: 8
|
||||
* description: At least one number and one letter
|
||||
* example:
|
||||
* name: fake name
|
||||
* email: fake@example.com
|
||||
* password: password1
|
||||
* responses:
|
||||
* "201":
|
||||
* description: Created
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* tokens:
|
||||
* $ref: '#/components/schemas/AuthTokens'
|
||||
* "400":
|
||||
* $ref: '#/components/responses/DuplicateEmail'
|
||||
* description: Управление аудентификацией
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,11 +2,13 @@ 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 './mono.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';
|
||||
|
||||
@@ -14,7 +16,7 @@ const router = Router();
|
||||
|
||||
const defaultRoutes = [
|
||||
{
|
||||
path: '/mono',
|
||||
path: '/system',
|
||||
route: monoRoute,
|
||||
},
|
||||
{
|
||||
@@ -25,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;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Router } from 'express';
|
||||
import { monoController } from '../../controllers';
|
||||
import auth from '../../middlewares/auth';
|
||||
import validate from '../../middlewares/validate';
|
||||
import { monoValidation } from '../../validations';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.route('/install').post(auth('install'), validate(monoValidation.RInstall), monoController.install);
|
||||
router.route('/health').get(monoController.getHealth);
|
||||
|
||||
export default router;
|
||||
+13
-17
@@ -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();
|
||||
|
||||
@@ -21,10 +20,10 @@ const router = Router();
|
||||
* ICreatedPayment:
|
||||
* type: object
|
||||
* required:
|
||||
* - order_id
|
||||
* - order_num
|
||||
* - confirmation_token
|
||||
* properties:
|
||||
* order_id:
|
||||
* order_num:
|
||||
* type: number
|
||||
* confirmation_token:
|
||||
* type: string
|
||||
@@ -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,30 @@
|
||||
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();
|
||||
|
||||
//init - устанавливает данные организации кооператива
|
||||
router.route('/init').post(auth('init'), validate(systemValidation.RInit), systemController.init);
|
||||
|
||||
router.route('/install').post(validate(systemValidation.RInstall), systemController.install);
|
||||
|
||||
router.route('/wif').post(auth('setWif'), validate(systemValidation.RSetWif), systemController.setWif);
|
||||
|
||||
//wif
|
||||
|
||||
router.route('/vars/schema').get(auth('getVars'), systemController.getVarsSchema);
|
||||
router.route('/vars').get(auth('getVars'), systemController.getVars);
|
||||
router.route('/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;
|
||||
@@ -200,3 +198,33 @@ export default router;
|
||||
* $ref: '#/components/responses/NotFound'
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /users/add:
|
||||
* post:
|
||||
* summary: Добавить пользователя без заявления
|
||||
* description: Только администраторы могут добавлять новых пользователей без прохождения процедуры подачи заявления. Это используется для добавления действующих пайщиков.
|
||||
* tags: [Users]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* "$ref": '#/components/schemas/IAddUser'
|
||||
* responses:
|
||||
* "201":
|
||||
* description: Пользователь успешно добавлен
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* "400":
|
||||
* $ref: '#/components/responses/BadRequest'
|
||||
* "401":
|
||||
* $ref: '#/components/responses/Unauthorized'
|
||||
* "403":
|
||||
* $ref: '#/components/responses/Forbidden'
|
||||
*/
|
||||
|
||||
@@ -98,7 +98,7 @@ export const resetKey = async (resetKeyToken, publicKey) => {
|
||||
if (config.env !== 'test')
|
||||
await blockchainService.changeKey({
|
||||
coopname: config.coopname,
|
||||
changer: config.service_username,
|
||||
changer: config.coopname,
|
||||
username: user.username,
|
||||
public_key: publicKey,
|
||||
});
|
||||
|
||||
@@ -5,9 +5,15 @@ import fetch from 'isomorphic-fetch';
|
||||
import EosApi from 'eosjs-api';
|
||||
import getInternalAction from '../utils/getInternalAction';
|
||||
import { GatewayContract, RegistratorContract, SovietContract } from 'cooptypes';
|
||||
import { IUser } from '../models/user.model';
|
||||
import { IUser } from '../types/user.types';
|
||||
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 '../types/order.types';
|
||||
import type { IBCAction } from '../types';
|
||||
import Vault from '../models/vault.model';
|
||||
|
||||
const rpc = new JsonRpc(process.env.BLOCKCHAIN_RPC as string, { fetch });
|
||||
|
||||
@@ -30,6 +36,21 @@ async function getBlockchainAccount(username): Promise<GetAccountResult> {
|
||||
return await api.getAccount(username);
|
||||
}
|
||||
|
||||
export async function transact(actions: any): Promise<any> {
|
||||
const instance = await getInstance(config.coopname);
|
||||
const result = await instance.transact(
|
||||
{
|
||||
actions,
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить инстанс для осуществления транзакции в блокчейн.
|
||||
* @param {username} - авторизация от аккаунта
|
||||
@@ -37,7 +58,11 @@ async function getBlockchainAccount(username): Promise<GetAccountResult> {
|
||||
* @returns {eosjs-api}
|
||||
*/
|
||||
|
||||
async function getInstance(wif) {
|
||||
async function getInstance(username: string) {
|
||||
const wif = await Vault.getWif(username);
|
||||
|
||||
if (!wif) throw new ApiError(httpStatus.BAD_GATEWAY, 'Не найден приватный ключ для совершения операции');
|
||||
|
||||
const signatureProvider = new JsSignatureProvider([wif]);
|
||||
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });
|
||||
return api;
|
||||
@@ -45,7 +70,7 @@ async function getInstance(wif) {
|
||||
|
||||
function getApi() {
|
||||
const options = {
|
||||
httpEndpoint: process.env.BLOCKCHAIN_RPC, // default, null for cold-storage
|
||||
httpEndpoint: process.env.BLOCKCHAIN_RPC, //
|
||||
verbose: false, // API logging
|
||||
fetchConfiguration: {},
|
||||
};
|
||||
@@ -94,9 +119,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.coopname);
|
||||
const actions = [] as any;
|
||||
|
||||
// Создаем newaccount
|
||||
const newaccount: RegistratorContract.Actions.CreateAccount.ICreateAccount = {
|
||||
registrator: process.env.COOPNAME as string,
|
||||
coopname: process.env.COOPNAME as string,
|
||||
@@ -106,6 +133,20 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
|
||||
meta: '',
|
||||
};
|
||||
|
||||
actions.push({
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.CreateAccount.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: newaccount,
|
||||
});
|
||||
|
||||
// Создаем registerUserData
|
||||
|
||||
const registerUserData: RegistratorContract.Actions.RegisterUser.IRegistrerUser = {
|
||||
coopname: process.env.COOPNAME as string,
|
||||
registrator: process.env.COOPNAME as string,
|
||||
@@ -113,59 +154,172 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
|
||||
type: user.type,
|
||||
};
|
||||
|
||||
//не следует создавать аккаунт в случаях, если он уже есть у пользователя
|
||||
|
||||
actions.push({
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.RegisterUser.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.coopname,
|
||||
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.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: joinCooperativeData,
|
||||
});
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.CreateAccount.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
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,
|
||||
},
|
||||
];
|
||||
// Создаем createDeposit
|
||||
const createDeposit: GatewayContract.Actions.CreateDeposit.ICreateDeposit = {
|
||||
coopname: config.coopname,
|
||||
username: user.username,
|
||||
type: 'registration',
|
||||
quantity: order.quantity,
|
||||
deposit_id: order.order_num as number,
|
||||
};
|
||||
actions.push({
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CreateDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: createDeposit,
|
||||
});
|
||||
|
||||
// Создаем completeDeposit
|
||||
const completeDeposit: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit = {
|
||||
coopname: config.coopname,
|
||||
admin: config.coopname,
|
||||
deposit_id: order.order_num as number,
|
||||
memo: '',
|
||||
};
|
||||
actions.push({
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.coopname,
|
||||
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.coopname,
|
||||
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.coopname,
|
||||
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.coopname,
|
||||
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.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: userAgreementData,
|
||||
});
|
||||
|
||||
const result = await eos.transact(
|
||||
{
|
||||
@@ -176,12 +330,14 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
|
||||
expireSeconds: 30,
|
||||
}
|
||||
);
|
||||
|
||||
// console.dir(result, { depth: null });
|
||||
}
|
||||
|
||||
async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
|
||||
const eos = await getInstance(config.service_wif);
|
||||
const eos = await getInstance(config.coopname);
|
||||
|
||||
console.log('data: ', data);
|
||||
// console.log('data: ', data);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
@@ -189,7 +345,7 @@ async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICrea
|
||||
name: SovietContract.Actions.Boards.CreateBoard.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -209,11 +365,11 @@ async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICrea
|
||||
}
|
||||
|
||||
async function createOrder(data) {
|
||||
const eos = await getInstance(process.env.REGISTRATOR_WIF);
|
||||
const eos = await getInstance(config.coopname);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: process.env.GATEWAY_CONTRACT as string,
|
||||
account: GatewayContract.contractName.production,
|
||||
name: 'deposit',
|
||||
authorization: [
|
||||
{
|
||||
@@ -235,25 +391,51 @@ async function createOrder(data) {
|
||||
}
|
||||
);
|
||||
|
||||
const order_id = getInternalAction(result, 'newdepositid').id;
|
||||
const order_num = getInternalAction(result, 'newdepositid').id;
|
||||
|
||||
return order_id;
|
||||
return order_num;
|
||||
}
|
||||
|
||||
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.coopname);
|
||||
|
||||
const createDeposit: GatewayContract.Actions.CreateDeposit.ICreateDeposit = {
|
||||
coopname: config.coopname,
|
||||
username: order.username,
|
||||
type: 'deposit',
|
||||
quantity: order.quantity,
|
||||
deposit_id: order.order_num as number,
|
||||
};
|
||||
|
||||
const completeDeposit: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit = {
|
||||
coopname: config.coopname,
|
||||
admin: config.coopname,
|
||||
deposit_id: order.order_num as number,
|
||||
memo: '',
|
||||
};
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CreateDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: createDeposit,
|
||||
},
|
||||
{
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteDeposit.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: process.env.COOPNAME as string,
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
data: completeDeposit,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -269,11 +451,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.coopname);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
account: process.env.GATEWAY_CONTRACT as string,
|
||||
account: GatewayContract.contractName.production,
|
||||
name: 'dpfail',
|
||||
authorization: [
|
||||
{
|
||||
@@ -297,15 +479,15 @@ async function failOrder(data) {
|
||||
}
|
||||
|
||||
export async function addUser(data: RegistratorContract.Actions.AddUser.IAddUser) {
|
||||
const eos = await getInstance(config.service_wif);
|
||||
|
||||
const eos = await getInstance(config.coopname);
|
||||
console.log('data:', data);
|
||||
const actions = [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.AddUser.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -325,7 +507,7 @@ export async function addUser(data: RegistratorContract.Actions.AddUser.IAddUser
|
||||
}
|
||||
|
||||
export async function changeKey(data: RegistratorContract.Actions.ChangeKey.IChangeKey) {
|
||||
const eos = await getInstance(config.service_wif);
|
||||
const eos = await getInstance(config.coopname);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
@@ -333,7 +515,7 @@ export async function changeKey(data: RegistratorContract.Actions.ChangeKey.ICha
|
||||
name: RegistratorContract.Actions.ChangeKey.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: config.service_username,
|
||||
actor: config.coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
@@ -352,10 +534,26 @@ export async function changeKey(data: RegistratorContract.Actions.ChangeKey.ICha
|
||||
);
|
||||
}
|
||||
|
||||
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.coopname,
|
||||
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;
|
||||
}
|
||||
@@ -363,7 +561,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;
|
||||
}
|
||||
|
||||
@@ -376,7 +574,7 @@ export {
|
||||
createOrder,
|
||||
getCooperative,
|
||||
failOrder,
|
||||
completeOrder,
|
||||
completeDeposit,
|
||||
getSoviet,
|
||||
getBlockchainInfo,
|
||||
getBlockchainAccount,
|
||||
|
||||
@@ -6,23 +6,23 @@ 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),
|
||||
@@ -40,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);
|
||||
|
||||
@@ -18,16 +18,24 @@ 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 && document.meta.links && Array.isArray(document.meta.links))
|
||||
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 && user.type != 'service') {
|
||||
@@ -39,8 +47,6 @@ export async function buildComplexDocument(
|
||||
};
|
||||
|
||||
statement = { action, document };
|
||||
} else {
|
||||
// throw new ApiError(400, 'Ошибка, один из пользователей не найден. Обратитесь в поддержку.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,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),
|
||||
@@ -65,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,
|
||||
@@ -74,6 +78,12 @@ export async function buildComplexDocument(
|
||||
|
||||
const document = await generator.getDocument({ hash: decision_action?.data?.document?.hash });
|
||||
|
||||
if (document && document.meta.links && Array.isArray(document.meta.links))
|
||||
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,
|
||||
@@ -87,31 +97,46 @@ export async function buildComplexDocument(
|
||||
}
|
||||
|
||||
// Готовим акты
|
||||
const acts: Cooperative.Documents.IComplexAct[] = [];
|
||||
const acts: Cooperative.Document.IComplexAct[] = [];
|
||||
|
||||
return { statement, decision, acts };
|
||||
return { statement, decision, acts, links };
|
||||
}
|
||||
|
||||
function toDotNotation(obj, prefix = '') {
|
||||
return Object.keys(obj).reduce((acc, key) => {
|
||||
const value = obj[key];
|
||||
const newKey = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
Object.assign(acc, toDotNotation(value, newKey));
|
||||
} else {
|
||||
acc[newKey] = value;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export const queryDocuments = async (
|
||||
type: string,
|
||||
filter: any,
|
||||
page = 1,
|
||||
limit = 100
|
||||
): Promise<Cooperative.Documents.IGetComplexDocuments> => {
|
||||
): 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,
|
||||
name: SovietContract.Actions.Registry.NewResolved.actionName,
|
||||
receiver: process.env.COOPNAME,
|
||||
...filter,
|
||||
account: SovietContract.contractName.production,
|
||||
name: type, //< newresolved | newsubmitted
|
||||
...toDotNotation(filter),
|
||||
}),
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
);
|
||||
|
||||
const response: Cooperative.Documents.IGetComplexDocuments = {
|
||||
const response: Cooperative.Document.IGetComplexDocuments = {
|
||||
results: [],
|
||||
page,
|
||||
limit,
|
||||
@@ -120,7 +145,7 @@ export const queryDocuments = async (
|
||||
for (const raw_action_document of actions.results) {
|
||||
const complexDocument = await buildComplexDocument(raw_action_document);
|
||||
|
||||
if (complexDocument.decision.action) response.results.push(complexDocument);
|
||||
if (complexDocument.statement.action) response.results.push(complexDocument);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -3,8 +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 monoService from './mono.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);
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
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 '../types/order.types';
|
||||
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 '../types/user.types';
|
||||
|
||||
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);
|
||||
let db_order;
|
||||
console.log('user.initial_order', user.initial_order);
|
||||
|
||||
if (type === 'registration' && user.initial_order) {
|
||||
db_order = await Order.findOne({ _id: user.initial_order }); //add expiration
|
||||
}
|
||||
|
||||
if (!db_order) {
|
||||
db_order = new Order({
|
||||
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,
|
||||
});
|
||||
user.initial_order = db_order._id;
|
||||
|
||||
await db_order.save({ session });
|
||||
await user.save({ session });
|
||||
}
|
||||
|
||||
const secret = db_order.secret;
|
||||
|
||||
const provider = ProviderFactory.createProvider(providerName);
|
||||
|
||||
const paymentDetails = await provider.createPayment(
|
||||
amount,
|
||||
symbol,
|
||||
type === 'deposit'
|
||||
? `Паевый взнос №${db_order.order_num}`
|
||||
: `Вступительный и минимальный паевый взносы №${secret.slice(0, 10)}`,
|
||||
db_order.order_num 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.coopname,
|
||||
deposit_id: order.order_num 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 '../types/user.types';
|
||||
|
||||
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 '../types/user.types';
|
||||
import { orderStatus, type IOrder } from '../types/order.types';
|
||||
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,291 +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 { FilterQuery } from 'mongoose';
|
||||
|
||||
const { connection } = 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);
|
||||
};
|
||||
|
||||
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> {
|
||||
const 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> {
|
||||
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
|
||||
|
||||
if (!cooperative) throw new ApiError(httpStatus.BAD_REQUEST, 'Кооператив не найден');
|
||||
|
||||
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) {
|
||||
if (!connection.db) throw new Error('Нет подключения');
|
||||
|
||||
const payments = await connection.db.collection('payments');
|
||||
|
||||
const exist = await payments.findOne({ 'object.id': ipnBody.object.id });
|
||||
|
||||
if (!exist) {
|
||||
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: '',
|
||||
});
|
||||
|
||||
logger.info('Зарегистрирован новый пользователь: ', 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);
|
||||
logger.info(`Принят паевый взнос пользователя: ${user.username} на сумму ${order.data.quantity}`);
|
||||
}
|
||||
|
||||
await user.save();
|
||||
|
||||
order.delivered = true;
|
||||
await order.save();
|
||||
} catch (e: any) {
|
||||
logger.warn(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 {
|
||||
logger.warn(`Ордер не найден: `, ipnBody);
|
||||
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,229 @@
|
||||
import { YooCheckout } from '@a2seven/yoo-checkout';
|
||||
import { Order } from '../../../models';
|
||||
import { IPNProvider } from './ipnProvider';
|
||||
|
||||
import type { PaymentDetails } from '../../../types';
|
||||
import IPN from '../../../models/ipn.model';
|
||||
import { checkPaymentAmount, checkPaymentSymbol, getAmountPlusFee } from '../../order.service';
|
||||
import logger from '../../../config/logger';
|
||||
import Settings from '../../../models/settings.model';
|
||||
import config from '../../../config/config';
|
||||
import { redisPublisher } from '../../redis.service';
|
||||
|
||||
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 });
|
||||
redisPublisher.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' });
|
||||
redisPublisher.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 });
|
||||
redisPublisher.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' });
|
||||
redisPublisher.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_num: 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_num: 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 '../../../types/order.types';
|
||||
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_num: 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_num: 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,17 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const redisPort = Number(process.env.REDIS_PORT) || 6379;
|
||||
const redisHost = process.env.REDIS_HOST || 'localhost';
|
||||
const redisPassword = process.env.REDIS_PASSWORD;
|
||||
|
||||
export const redisSubscriber = new Redis({
|
||||
port: redisPort,
|
||||
host: redisHost,
|
||||
password: redisPassword,
|
||||
});
|
||||
|
||||
export const redisPublisher = new Redis({
|
||||
port: redisPort,
|
||||
host: redisHost,
|
||||
password: redisPassword,
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
+58
-21
@@ -7,14 +7,30 @@ 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 } from '../models/user.model';
|
||||
import { IUser, userStatus } from '../types/user.types';
|
||||
import axios from 'axios';
|
||||
import { getBlockchainInfo } from './blockchain.service';
|
||||
import { RegistratorContract } from 'cooptypes';
|
||||
import { getBlockchainAccount, getBlockchainInfo, hasActiveKey } from './blockchain.service';
|
||||
import { RegistratorContract, type Cooperative } from 'cooptypes';
|
||||
import type { IInit, ISetVars, ISetWif } from '../types/auto-generated/system.validation';
|
||||
import { VarsSchema } from 'coopdoc-generator-ts';
|
||||
import Vault, { type wifPermissions } from '../models/vault.model';
|
||||
import { PrivateKey } from '@wharfkit/antelope';
|
||||
import { createUser } from './user.service';
|
||||
|
||||
export const install = async (soviet: IInstall): Promise<void> => {
|
||||
console.log('IInstall: ', soviet);
|
||||
export const setWif = async (params: ISetWif): Promise<void> => {
|
||||
//check auth
|
||||
const blockchainAccount = await getBlockchainAccount(params.username);
|
||||
|
||||
const publicKey = PrivateKey.fromString(params.wif).toPublic().toLegacyString();
|
||||
|
||||
const hasKey = hasActiveKey(blockchainAccount, publicKey);
|
||||
|
||||
if (!hasKey) throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
|
||||
|
||||
await Vault.setWif(params.username, params.wif, params.permission as wifPermissions);
|
||||
};
|
||||
|
||||
export const install = async (data: IInstall): Promise<void> => {
|
||||
const mono = await Mono.findOne({ coopname: config.coopname });
|
||||
const info = await getBlockchainInfo();
|
||||
const coop = await blockchainService.getCooperative(config.coopname);
|
||||
@@ -25,19 +41,24 @@ export const install = async (soviet: IInstall): Promise<void> => {
|
||||
throw new ApiError(httpStatus.BAD_REQUEST, 'Установка уже выполнена');
|
||||
}
|
||||
|
||||
await setWif({
|
||||
permission: 'active',
|
||||
username: config.coopname,
|
||||
wif: data.wif,
|
||||
});
|
||||
|
||||
const users = [] as IUser[];
|
||||
const members = [] as any;
|
||||
const sovietExt = [] as any;
|
||||
const soviet = data.soviet;
|
||||
|
||||
try {
|
||||
for (const member of soviet) {
|
||||
console.log(member);
|
||||
|
||||
const username = generateUsername();
|
||||
sovietExt.push({ ...member, username });
|
||||
|
||||
const addUser: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
registrator: config.service_username,
|
||||
registrator: config.coopname,
|
||||
coopname: config.coopname,
|
||||
referer: '',
|
||||
username,
|
||||
@@ -61,7 +82,7 @@ export const install = async (soviet: IInstall): Promise<void> => {
|
||||
};
|
||||
|
||||
const user = await userService.createUser(createUser);
|
||||
user.status = 'registered';
|
||||
user.status = userStatus['4_Registered'];
|
||||
user.is_registered = true;
|
||||
await user.save();
|
||||
|
||||
@@ -85,7 +106,7 @@ export const install = async (soviet: IInstall): Promise<void> => {
|
||||
//TODO создаёт доску совета
|
||||
await blockchainService.createBoard({
|
||||
coopname: config.coopname,
|
||||
username: config.service_username,
|
||||
username: config.coopname,
|
||||
type: 'soviet',
|
||||
members: members,
|
||||
name: 'Совет',
|
||||
@@ -107,25 +128,41 @@ export const install = async (soviet: IInstall): Promise<void> => {
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('MONO активирован');
|
||||
logger.info('Система установлена');
|
||||
};
|
||||
|
||||
export const init = async (): Promise<void> => {
|
||||
export const init = async (data: IInit): Promise<void> => {
|
||||
const mono = await Mono.findOne({ coopname: config.coopname });
|
||||
|
||||
if (!mono)
|
||||
await Mono.create({
|
||||
coopname: config.coopname,
|
||||
status: 'install',
|
||||
});
|
||||
if (mono) throw new ApiError(httpStatus.BAD_REQUEST, 'MONO уже инициализирован');
|
||||
|
||||
logger.info('MONO инициализирован');
|
||||
await Mono.create({
|
||||
coopname: config.coopname,
|
||||
status: 'install',
|
||||
});
|
||||
|
||||
await setVars(data.vars);
|
||||
|
||||
await generator.save('organization', { username: config.coopname, ...data.organization_data });
|
||||
|
||||
logger.info('Система инициализирована');
|
||||
};
|
||||
|
||||
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;
|
||||
if (!mono) return 'maintenance';
|
||||
else return mono.status;
|
||||
};
|
||||
|
||||
export const setVars = async (vars: ISetVars): Promise<void> => {
|
||||
await generator.save('vars', vars);
|
||||
};
|
||||
|
||||
export const getVars = async (): Promise<Cooperative.Model.IVars> => {
|
||||
return (await generator.get('vars', {})) as Cooperative.Model.IVars;
|
||||
};
|
||||
|
||||
export const getVarsSchema = async (): Promise<unknown> => {
|
||||
return VarsSchema;
|
||||
};
|
||||
@@ -73,7 +73,7 @@ export const verifyToken = async (token, types) => {
|
||||
export const generateServiceAccessToken = async (user) => {
|
||||
const accessTokenExpires = moment().add(100, 'years'); // Пример с очень долгим сроком действия
|
||||
const accessToken = generateToken(user.id, accessTokenExpires, tokenTypes.ACCESS);
|
||||
console.log('on generate', accessToken);
|
||||
|
||||
return {
|
||||
access: {
|
||||
token: accessToken,
|
||||
|
||||
@@ -2,7 +2,7 @@ import http from 'http-status';
|
||||
import { User } from '../models';
|
||||
import ApiError from '../utils/ApiError';
|
||||
import { generator } from './document.service';
|
||||
import { ICreateUser, IJoinCooperative } from '../types/auto-generated/user.validation';
|
||||
import { ICreateUser } from '../types/auto-generated/user.validation';
|
||||
import { PublicKey, Signature } from '@wharfkit/antelope';
|
||||
import faker from 'faker';
|
||||
|
||||
@@ -57,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
|
||||
|
||||
@@ -8,13 +8,13 @@ export interface IGenerate {
|
||||
* Unknown Property
|
||||
*/
|
||||
[x: string]: unknown;
|
||||
action: string;
|
||||
block_num?: number;
|
||||
code: string;
|
||||
coopname: string;
|
||||
created_at?: string;
|
||||
generator?: string;
|
||||
lang?: 'ru';
|
||||
links?: string[];
|
||||
registry_id: number;
|
||||
timezone?: string;
|
||||
username: string;
|
||||
version?: string;
|
||||
@@ -25,12 +25,17 @@ export interface IGetDocuments {
|
||||
* Unknown Property
|
||||
*/
|
||||
[x: string]: unknown;
|
||||
filter?: {
|
||||
receiver?: string;
|
||||
filter: {
|
||||
/**
|
||||
* Unknown Property
|
||||
*/
|
||||
[x: string]: unknown;
|
||||
receiver: string;
|
||||
};
|
||||
limit?: number;
|
||||
page?: number;
|
||||
sortBy?: string;
|
||||
type?: 'newsubmitted' | 'newresolved';
|
||||
}
|
||||
|
||||
export interface RGenerate {
|
||||
@@ -39,13 +44,13 @@ export interface RGenerate {
|
||||
* Unknown Property
|
||||
*/
|
||||
[x: string]: unknown;
|
||||
action: string;
|
||||
block_num?: number;
|
||||
code: string;
|
||||
coopname: string;
|
||||
created_at?: string;
|
||||
generator?: string;
|
||||
lang?: 'ru';
|
||||
links?: string[];
|
||||
registry_id: number;
|
||||
timezone?: string;
|
||||
username: string;
|
||||
version?: string;
|
||||
@@ -58,12 +63,17 @@ export interface RGetDocuments {
|
||||
* Unknown Property
|
||||
*/
|
||||
[x: string]: unknown;
|
||||
filter?: {
|
||||
receiver?: string;
|
||||
filter: {
|
||||
/**
|
||||
* Unknown Property
|
||||
*/
|
||||
[x: string]: unknown;
|
||||
receiver: string;
|
||||
};
|
||||
limit?: number;
|
||||
page?: number;
|
||||
sortBy?: string;
|
||||
type?: 'newsubmitted' | 'newresolved';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
export * from './auth.validation';
|
||||
export * from './coop.validation';
|
||||
export * from './document.validation';
|
||||
export * from './mono.validation';
|
||||
export * from './method.validation';
|
||||
export * from './notify.validation';
|
||||
export * from './payment.validation';
|
||||
export * from './order.validation';
|
||||
export * from './participant.validation';
|
||||
export * from './system.validation';
|
||||
export * from './user.validation';
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* This file was automatically generated by joi-to-typescript
|
||||
* Do not modify this file manually
|
||||
*/
|
||||
|
||||
export interface IBankAccount {
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDeletePaymentMethod {
|
||||
method_id: number;
|
||||
}
|
||||
|
||||
export interface ISavePaymentMethod {
|
||||
data: {
|
||||
phone: string;
|
||||
} | ({
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
};
|
||||
});
|
||||
method_id: number;
|
||||
method_type: 'sbp' | 'bank_transfer';
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface ISbpDetails {
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface RDeletePaymentMethod {
|
||||
body: {
|
||||
method_id: number;
|
||||
};
|
||||
params?: {
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RGetListPaymentMethods {
|
||||
params?: {
|
||||
username?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RSavePaymentMethod {
|
||||
body: {
|
||||
data: {
|
||||
phone: string;
|
||||
} | ({
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
};
|
||||
});
|
||||
method_id: number;
|
||||
method_type: 'sbp' | 'bank_transfer';
|
||||
username: string;
|
||||
};
|
||||
params?: {
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Do not modify this file manually
|
||||
*/
|
||||
|
||||
export type IInstall = {
|
||||
export type IInstall = ({
|
||||
individual_data: {
|
||||
birthdate: string;
|
||||
email: string;
|
||||
@@ -14,10 +14,10 @@ export type IInstall = {
|
||||
phone: string;
|
||||
};
|
||||
role: 'chairman' | 'member';
|
||||
}[];
|
||||
})[];
|
||||
|
||||
export interface RInstall {
|
||||
body: {
|
||||
body: ({
|
||||
individual_data: {
|
||||
birthdate: string;
|
||||
email: string;
|
||||
@@ -28,5 +28,5 @@ export interface RInstall {
|
||||
phone: string;
|
||||
};
|
||||
role: 'chairman' | 'member';
|
||||
}[];
|
||||
})[];
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user