Compare commits

...

137 Commits

Author SHA1 Message Date
Alex Ant 095c77b65b chore(release): publish 2024-10-17 16:20:02 +05:00
Alex Ant c1c07bff8f plugin factory and powerup plugin sketch 2024-10-17 16:18:39 +05:00
Alex Ant 1206bd0e98 fix voskhod everywhere bug 2024-10-17 13:22:35 +05:00
Alex Ant 67bcd1b307 update text on install 2024-10-17 10:52:06 +05:00
Alex Ant 31ef70e433 health check 2024-10-16 13:56:37 +05:00
Alex Ant afb12961d2 fix launch errors 2024-10-16 13:38:08 +05:00
Alex Ant bc5d6e38c8 console.error(e 2024-10-15 23:37:27 +05:00
Alex Ant 5a39143051 openReplay connection 2024-10-14 21:24:45 +05:00
Alex Ant 18e9571877 fix css bug 2024-10-13 16:52:55 +05:00
Alex Ant a828f4c831 chore(release): publish 2024-10-13 16:26:37 +05:00
Alex Ant 54ac12f250 update migrations 2024-10-13 16:24:26 +05:00
Alex Ant 9a6a72f605 feat: запрос соглашений
fix: интерфейс
2024-10-13 16:13:52 +05:00
Alex Ant 03949489e8 docs: добавлен файл конвенции коммитов 2024-10-13 13:19:16 +05:00
Alex Ant 8ebd735e7e update 2024-10-13 13:08:59 +05:00
Alex Ant 39246da73f update amount in agreement and fix bug in terminal 2024-10-13 12:00:39 +05:00
Alex Ant bf33f60368 cooperative connection 2024-10-13 11:50:22 +05:00
Alex Ant 492a613769 fix bugs 2024-10-12 00:59:58 +05:00
Alex Ant 2c02c3434e update 2024-10-12 00:51:37 +05:00
Alex Ant a186ddd9d2 chore(release): publish 2024-10-12 00:46:06 +05:00
Alex Ant 4b0c7a5e4e update 2024-10-12 00:45:35 +05:00
Alex Ant 54cfbe3ab6 fix 2024-10-12 00:39:07 +05:00
Alex Ant 02015cf0b8 update migrator package.json 2024-10-12 00:37:39 +05:00
Alex Ant 416e4d3598 interface rework & documents style change 2024-10-12 00:31:52 +05:00
Alex Ant e0d9c4b0ea make migrator private 2024-10-10 20:55:46 +05:00
Alex Ant 2659e3bc09 migrator release 2024-10-10 20:54:27 +05:00
Alex Ant d296167b6a agreementChecker fix, payment description fix, loader on add user 2024-10-10 20:41:18 +05:00
Alex Ant 89b0aa7fec chore(release): publish 2024-10-10 18:38:19 +05:00
Alex Ant 787aadf444 fix bugs 2024-10-10 18:09:22 +05:00
Alex Ant d67161a29b slice secret 2024-10-10 15:02:12 +05:00
Alex Ant 7f4777ead4 правка текста вступительного взноса 2024-10-10 14:41:26 +05:00
Alex Ant 1e2147b143 fix aggregation 2 2024-10-10 12:09:10 +05:00
Alex Ant 9f0a2fbf63 chore(release): publish 2024-10-10 11:28:11 +05:00
Alex Ant 7804851bc9 update agregate pipeline 2024-10-10 11:15:47 +05:00
Alex Ant adc50dd89e chore(release): publish 2024-10-09 22:20:55 +05:00
Alex Ant 5378cf5ddc template fix 2024-10-09 22:14:11 +05:00
Alex Ant a733bb605f fix model 2024-10-09 14:17:19 +05:00
Alex Ant aaa1b5a264 test update 2024-10-09 14:01:21 +05:00
Alex Ant 5855cdee65 install process 2024-10-09 13:46:08 +05:00
Alex Ant c0d584e644 TSK-225 vaultModel and wif store for cooperative operations 2024-10-04 12:01:44 +05:00
Alex Ant 955f0f473e fix bug 2024-09-30 23:10:59 +05:00
Alex Ant 9ceb888124 update 2024-09-30 22:13:59 +05:00
Alex Ant e902933693 update 2024-09-30 21:43:41 +05:00
Alex Ant e07b47fb10 update 2024-09-30 21:37:54 +05:00
Alex Ant 5bf08d9701 update 2024-09-30 21:33:02 +05:00
Alex Ant 597fa1e6fa update package 2024-09-30 21:21:45 +05:00
Alex Ant dda3ec0ba0 chore(release): publish 2024-09-30 21:09:57 +05:00
Alex Ant 991f08c749 dompurify and coopback <br> fix 2024-09-30 21:08:26 +05:00
Alex Ant 923865cd7c bug fix #3 2024-09-30 20:00:25 +05:00
Alex Ant 606aee8ccd chore(release): publish 2024-09-30 17:24:04 +05:00
Alex Ant 4a1cb27d0a big bug #2 2024-09-30 17:21:38 +05:00
Alex Ant 26cd7f3a12 chore(release): publish 2024-09-30 17:09:39 +05:00
Alex Ant f4045dbc56 chore(release): publish 2024-09-30 17:07:54 +05:00
Alex Ant 33c2b24550 fix bug of parallel generation 2024-09-30 17:06:17 +05:00
Alex Ant cd86f3c8ea redis auth 2024-09-30 15:15:57 +05:00
Alex Ant 58026ebc2d fix redis auth 2024-09-30 15:07:33 +05:00
Alex Ant 9183275dad redis authentification 2024-09-30 15:00:38 +05:00
Alex Ant 5e3a625d95 chore(release): publish 2024-09-30 14:52:28 +05:00
Alex Ant 3297f9d2d2 update package.json 2024-09-30 14:51:18 +05:00
Alex Ant 29b8306a28 chore(release): publish 2024-09-29 19:17:36 +05:00
Alex Ant d84746bac6 add compression package 2024-09-29 19:14:12 +05:00
Alex Ant 4eacf2ff0b chore(release): publish 2024-09-29 19:01:29 +05:00
Alex Ant a68e0d6f61 update 2024-09-29 18:57:09 +05:00
Alex Ant ddf44304c7 order_id -> order_num, stable initial_order 2024-09-29 15:28:16 +05:00
Alex Ant 789d98f58e chore(release): publish 2024-09-29 09:39:57 +05:00
Alex Ant 205574620d Merge branch 'testnet' 2024-09-29 09:39:19 +05:00
Alex Ant d9bae12b31 chore(release): publish 2024-09-29 09:36:26 +05:00
Alex Ant c951190794 update 2024-09-29 09:34:32 +05:00
Alex Ant d4e1d69c3c update 2024-09-29 09:29:53 +05:00
Alex Ant f93a32c07b update 2024-09-29 09:25:10 +05:00
Alex Ant 8e7c152c0d update 2024-09-29 09:23:20 +05:00
Alex Ant 6a64404f20 update 2024-09-29 09:19:43 +05:00
Alex Ant 1a09589add from-packages 2024-09-29 09:18:03 +05:00
Alex Ant d0f9072ab5 fixed packet version 2024-09-29 09:15:58 +05:00
Alex Ant 011ed9f2d8 update 2024-09-29 09:11:57 +05:00
Alex Ant 12332f83d9 Merge branch 'dev' into testnet 2024-09-29 09:05:09 +05:00
Alex Ant 8f75829948 update 2024-09-29 09:04:53 +05:00
Alex Ant a7ffedb3b2 update 2024-09-29 09:03:14 +05:00
Alex Ant 378d3b9747 update 2024-09-29 09:02:06 +05:00
Alex Ant 5ba05409c4 chore(release): publish
- coopback@1.7.38-alpha.3
 - coopdoc-generator-ts@1.0.21-alpha.3
 - cooptypes@1.0.26-alpha.3
 - coop-notificator@0.0.29-alpha.3
 - terminal@0.1.51-alpha.3
2024-09-29 08:58:35 +05:00
Alex Ant d86beef58f document updates 2024-09-29 08:57:37 +05:00
Alex Ant 7315d67c9f chore(release): publish
- coopback@1.7.38-alpha.2
 - coopdoc-generator-ts@1.0.21-alpha.2
 - cooptypes@1.0.26-alpha.2
 - coop-notificator@0.0.29-alpha.2
 - terminal@0.1.51-alpha.2
2024-09-28 19:43:01 +05:00
Alex Ant 59d79b8c7a update order statuses, list order statuses, add passport data, update registry documents 2024-09-28 19:41:12 +05:00
Alex Ant 3df97653a2 TSK-181 StatusPage and setStatus for orders [pre] 2024-09-26 21:30:08 +05:00
Alex Ant 948d55e418 download qr and show order data 2024-09-23 21:18:10 +05:00
Alex Ant 1d2c7281b1 sberbankPollingProvider prototype 2024-09-23 12:46:12 +05:00
Alex Ant 35759b943c chore(release): publish
- coopback@1.7.38-alpha.1
 - coopdoc-generator-ts@1.0.21-alpha.1
 - cooptypes@1.0.26-alpha.1
 - coop-notificator@0.0.29-alpha.1
 - terminal@0.1.51-alpha.1
2024-09-20 12:13:47 +05:00
Alex Ant 29da33e200 update 2024-09-20 12:12:06 +05:00
Alex Ant 1d758f8126 gitignore 2024-09-20 10:09:54 +05:00
Alex Ant b663dac2ab delete logs 2024-09-20 10:09:32 +05:00
Alex Ant 485a3971e9 update 2024-09-20 10:09:13 +05:00
Alex Ant 36b5d64ca6 chore(release): publish
- coopback@1.7.38-alpha.0
 - coopdoc-generator-ts@1.0.21-alpha.0
 - cooptypes@1.0.26-alpha.0
 - coop-notificator@0.0.29-alpha.0
 - terminal@0.1.51-alpha.0
2024-09-16 12:14:29 +05:00
Alex Ant 8462a709bc hz 2024-09-16 12:13:44 +05:00
Alex Ant 20cabe8362 update 2024-09-16 12:00:54 +05:00
Alex Ant 6967864361 chore(release): publish
- coopback@1.7.37-alpha.3
 - coopdoc-generator-ts@1.0.11-alpha.2
 - terminal@0.1.41-alpha.2
2024-09-16 11:58:50 +05:00
Alex Ant a6ee3840ae update 2024-09-16 11:57:43 +05:00
Alex Ant 01de6139df update 2024-09-16 11:46:48 +05:00
Alex Ant 6ba0924f6c chore(release): publish
- coopback@1.7.37-alpha.2
 - coopdoc-generator-ts@1.0.11-alpha.2
 - cooptypes@1.0.22-alpha.6
 - coop-notificator@0.0.28-alpha.1
 - terminal@0.1.41-alpha.1
2024-09-16 11:44:22 +05:00
Alex Ant 00c75d1fd8 update 2024-09-16 11:43:37 +05:00
Alex Ant 93f28fa9d5 update 2024-09-16 11:38:25 +05:00
Alex Ant 0aed10eb8b chore(release): publish
- coopback@1.7.37-alpha.1
 - coopdoc-generator-ts@1.0.11-alpha.1
 - cooptypes@1.0.22-alpha.6
 - coop-notificator@0.0.28-alpha.0
 - terminal@0.1.41-alpha.0
2024-09-16 11:37:13 +05:00
Alex Ant d84d687eee update 2024-09-16 11:36:50 +05:00
Alex Ant d046f56832 update 2024-09-16 11:34:07 +05:00
Alex Ant 0af1fbf313 update 2024-09-16 11:24:50 +05:00
Alex Ant e9aaf4309e chore(release): publish
- cooparser-ts@1.0.20-alpha.0
 - coopback@1.7.37-alpha.0
 - coopdoc-generator-ts@1.0.11-alpha.0
 - cooptypes@1.0.22-alpha.6
 - coop-notificator@0.0.27
 - terminal@0.1.40
2024-09-16 11:19:45 +05:00
Alex Ant 80fa1085d2 FUCK 2024-09-16 11:13:22 +05:00
Alex Ant 88195291fd update 2024-09-16 11:09:40 +05:00
Alex Ant 39ad600d00 update 2024-09-16 10:58:59 +05:00
Alex Ant c63a3aba6b update 2024-09-16 10:41:15 +05:00
Alex Ant 782ecbe307 dfdf 2024-09-16 10:36:54 +05:00
Alex Ant f75bbfa048 fix 2024-09-16 10:15:16 +05:00
Alex Ant 3a772cc983 herna 2024-09-16 10:00:29 +05:00
Alex Ant 807113523e documents update 2024-09-16 09:52:00 +05:00
Alex Ant 7df2af8611 update 2024-09-15 17:28:33 +05:00
Alex Ant 10c1697b00 update 2024-09-15 17:23:58 +05:00
Alex Ant 742e5d38ab chore(release): publish
- coopback@1.7.35-alpha.5
 - coopdoc-generator-ts@1.0.79-alpha.5
 - cooptypes@1.0.22-alpha.5
 - coop-notificator@0.0.26-alpha.5
 - terminal@0.1.39-alpha.5
2024-09-15 17:20:44 +05:00
Alex Ant 1d3e7f8af0 update 2024-09-15 17:19:13 +05:00
Alex Ant c99bacab64 before update 2024-09-15 10:40:18 +05:00
Alex Ant 95d7555ebc ssr & covars -> vars 2024-09-15 09:38:02 +05:00
Alex Ant 5854e0a23c chore(release): publish
- coopback@1.7.35-alpha.4
 - coopdoc-generator-ts@1.0.79-alpha.4
 - cooptypes@1.0.22-alpha.4
 - coop-notificator@0.0.26-alpha.4
 - terminal@0.1.39-alpha.4
2024-09-14 17:02:55 +05:00
Alex Ant 81f7f32767 fix 2024-09-14 17:02:33 +05:00
Alex Ant c0374a7da1 chore(release): publish
- coopback@1.7.35-alpha.3
 - coopdoc-generator-ts@1.0.79-alpha.3
 - cooptypes@1.0.22-alpha.3
 - coop-notificator@0.0.26-alpha.3
 - terminal@0.1.39-alpha.3
2024-09-14 16:44:32 +05:00
Alex Ant ed65e655b9 update 2024-09-14 16:43:06 +05:00
Alex Ant d250e516b5 update document models 2024-09-14 12:43:06 +05:00
Alex Ant e4f404b2c7 documents update 2024-09-13 20:41:00 +05:00
Alex Ant 86571331f8 update registry 2024-09-12 15:05:11 +05:00
Alex Ant 163d420af9 request agreements 2024-09-11 16:12:05 +05:00
Alex Ant 4925ea4af0 chore(release): publish
- cooparser-ts@1.0.19-alpha.1
 - coopback@1.7.35-alpha.2
 - coopdoc-generator-ts@1.0.79-alpha.2
 - cooptypes@1.0.22-alpha.2
 - coop-notificator@0.0.26-alpha.2
 - terminal@0.1.39-alpha.2
2024-09-11 10:04:46 +05:00
Alex Ant 57b05ce348 registration agreements 2024-09-11 10:01:09 +05:00
Alex Ant 427b53fcbd chore(release): publish
- coopback@1.7.35-alpha.1
 - coopdoc-generator-ts@1.0.79-alpha.1
 - cooptypes@1.0.22-alpha.1
 - coop-notificator@0.0.26-alpha.1
 - terminal@0.1.39-alpha.1
2024-09-10 10:06:12 +05:00
Alex Ant 2ac7a31be6 промежуточный результат добавления кооперативных соглашений 2024-09-10 10:05:17 +05:00
Alex Ant 4baed6409f chore(release): publish
- cooparser-ts@1.0.19-alpha.0
 - coopback@1.7.35-alpha.0
 - coopdoc-generator-ts@1.0.79-alpha.0
 - cooptypes@1.0.22-alpha.0
 - coop-notificator@0.0.26-alpha.0
 - terminal@0.1.39-alpha.0
2024-09-06 16:39:34 +05:00
Alex Ant 2ea0ff0965 big update 2024-09-06 16:29:18 +05:00
Alex Ant 88b9a7edce small letters 2024-09-05 19:27:23 +05:00
Alex Ant 538066b960 ufff template or Template ??? always should be small letters 2024-09-05 19:25:37 +05:00
Alex Ant b10dd1d75a update generator with new type of document 2024-09-04 20:57:59 +05:00
Alex Ant f743580b55 fix signature collection name 2024-09-03 20:33:30 +05:00
Alex Ant 0688467302 update 2024-09-03 20:11:17 +05:00
466 changed files with 28631 additions and 29981 deletions
Vendored
BIN
View File
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
node_modules
dist
+14
View File
@@ -0,0 +1,14 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.tabCompletion": "onlySnippets",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib",
"i18n-ally.localesPaths": ["src/i18n"],
"typescript.format.enable": true,
"typescript.format.indentSwitchCase": false,
"typescript.validate.enable": true,
}
+80
View File
@@ -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))
+39
View File
@@ -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
View File
@@ -0,0 +1,10 @@
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install -g pnpm lerna
RUN pnpm install
RUN lerna run build
+15 -1
View File
@@ -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
BIN
View File
Binary file not shown.
+40
View File
@@ -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
+2 -2
View File
@@ -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 -1
View File
@@ -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": "",
+19 -225
View File
@@ -69,9 +69,6 @@ importers:
esno:
specifier: ^4.7.0
version: 4.7.0
lint-staged:
specifier: ^15.2.2
version: 15.2.2
nodemon:
specifier: ^3.1.4
version: 3.1.4
@@ -1039,10 +1036,6 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-escapes@6.2.1:
resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==}
engines: {node: '>=14.16'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -1233,14 +1226,6 @@ packages:
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
engines: {node: '>=4'}
cli-cursor@4.0.0:
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
cli-truncate@4.0.0:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -1271,9 +1256,6 @@ packages:
colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
colorspace@1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
@@ -1281,10 +1263,6 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
commander@11.1.0:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
@@ -1506,9 +1484,6 @@ packages:
elliptic@6.5.4:
resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==}
emoji-regex@10.3.0:
resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1770,9 +1745,6 @@ packages:
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
@@ -1893,10 +1865,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.2.0:
resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==}
engines: {node: '>=18'}
get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
@@ -2083,14 +2051,6 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
is-fullwidth-code-point@5.0.0:
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
engines: {node: '>=18'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -2200,10 +2160,6 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lilconfig@3.0.0:
resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
engines: {node: '>=14'}
lilconfig@3.1.1:
resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
engines: {node: '>=14'}
@@ -2211,15 +2167,6 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lint-staged@15.2.2:
resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==}
engines: {node: '>=18.12.0'}
hasBin: true
listr2@8.0.1:
resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==}
engines: {node: '>=18.0.0'}
local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@@ -2257,10 +2204,6 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
log-update@6.0.0:
resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==}
engines: {node: '>=18'}
logform@2.6.0:
resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
engines: {node: '>= 12.0.0'}
@@ -2346,10 +2289,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
@@ -2546,10 +2485,6 @@ packages:
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
onetime@5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
onetime@6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
@@ -2682,11 +2617,6 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
pidtree@0.6.0:
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
engines: {node: '>=0.10'}
hasBin: true
pkg-types@1.1.0:
resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==}
@@ -2976,17 +2906,10 @@ packages:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
restore-cursor@4.0.0:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rfdc@1.3.1:
resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@@ -3085,9 +3008,6 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
@@ -3118,14 +3038,6 @@ packages:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
slice-ansi@7.1.0:
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
engines: {node: '>=18'}
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
@@ -3179,10 +3091,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string-width@7.1.0:
resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==}
engines: {node: '>=18'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -3558,10 +3466,6 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
wrap-ansi@9.0.0:
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
engines: {node: '>=18'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -3595,10 +3499,6 @@ packages:
resolution: {integrity: sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==}
engines: {node: ^14.17.0 || >=16.0.0}
yaml@2.3.4:
resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
engines: {node: '>= 14'}
yaml@2.4.2:
resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==}
engines: {node: '>= 14'}
@@ -3703,7 +3603,7 @@ snapshots:
'@babel/traverse': 7.24.1
'@babel/types': 7.24.0
convert-source-map: 2.0.0
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -3804,7 +3704,7 @@ snapshots:
'@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.24.4
'@babel/types': 7.24.0
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -4013,7 +3913,7 @@ snapshots:
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.1
@@ -4029,7 +3929,7 @@ snapshots:
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -4348,7 +4248,7 @@ snapshots:
'@typescript-eslint/type-utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.7.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
@@ -4366,7 +4266,7 @@ snapshots:
'@typescript-eslint/types': 7.7.1
'@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
'@typescript-eslint/visitor-keys': 7.7.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
optionalDependencies:
typescript: 5.4.5
@@ -4387,7 +4287,7 @@ snapshots:
dependencies:
'@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5)
'@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
@@ -4403,7 +4303,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
@@ -4418,7 +4318,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 7.7.1
'@typescript-eslint/visitor-keys': 7.7.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
@@ -4550,8 +4450,6 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-escapes@6.2.1: {}
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@@ -4762,15 +4660,6 @@ snapshots:
dependencies:
escape-string-regexp: 1.0.5
cli-cursor@4.0.0:
dependencies:
restore-cursor: 4.0.0
cli-truncate@4.0.0:
dependencies:
slice-ansi: 5.0.0
string-width: 7.1.0
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -4803,8 +4692,6 @@ snapshots:
colord@2.9.3: {}
colorette@2.0.20: {}
colorspace@1.1.4:
dependencies:
color: 3.2.1
@@ -4814,8 +4701,6 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
commander@11.1.0: {}
commander@7.2.0: {}
comment-parser@1.4.1: {}
@@ -4952,10 +4837,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.3.4:
dependencies:
ms: 2.1.2
debug@4.3.4(supports-color@5.5.0):
dependencies:
ms: 2.1.2
@@ -5040,8 +4921,6 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
emoji-regex@10.3.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -5185,7 +5064,7 @@ snapshots:
eslint-plugin-import-x@0.5.0(eslint@8.57.0)(typescript@5.4.5):
dependencies:
'@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5)
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
doctrine: 3.0.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@@ -5202,7 +5081,7 @@ snapshots:
'@es-joy/jsdoccomment': 0.42.0
are-docs-informative: 0.0.2
comment-parser: 1.4.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
escape-string-regexp: 4.0.0
eslint: 8.57.0
esquery: 1.5.0
@@ -5258,7 +5137,7 @@ snapshots:
eslint-plugin-toml@0.11.0(eslint@8.57.0):
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
eslint-compat-utils: 0.5.0(eslint@8.57.0)
lodash: 4.17.21
@@ -5322,7 +5201,7 @@ snapshots:
eslint-plugin-yml@1.14.0(eslint@8.57.0):
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
eslint-compat-utils: 0.5.0(eslint@8.57.0)
lodash: 4.17.21
@@ -5358,7 +5237,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
@@ -5420,8 +5299,6 @@ snapshots:
eventemitter3@4.0.7: {}
eventemitter3@5.0.1: {}
execa@8.0.1:
dependencies:
cross-spawn: 7.0.3
@@ -5587,8 +5464,6 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.2.0: {}
get-func-name@2.0.2: {}
get-intrinsic@1.2.4:
@@ -5760,7 +5635,7 @@ snapshots:
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -5801,12 +5676,6 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@4.0.0: {}
is-fullwidth-code-point@5.0.0:
dependencies:
get-east-asian-width: 1.2.0
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -5891,36 +5760,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lilconfig@3.0.0: {}
lilconfig@3.1.1: {}
lines-and-columns@1.2.4: {}
lint-staged@15.2.2:
dependencies:
chalk: 5.3.0
commander: 11.1.0
debug: 4.3.4
execa: 8.0.1
lilconfig: 3.0.0
listr2: 8.0.1
micromatch: 4.0.5
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.3.4
transitivePeerDependencies:
- supports-color
listr2@8.0.1:
dependencies:
cli-truncate: 4.0.0
colorette: 2.0.20
eventemitter3: 5.0.1
log-update: 6.0.0
rfdc: 1.3.1
wrap-ansi: 9.0.0
local-pkg@0.5.0:
dependencies:
mlly: 1.6.1
@@ -5952,14 +5795,6 @@ snapshots:
lodash@4.17.21: {}
log-update@6.0.0:
dependencies:
ansi-escapes: 6.2.1
cli-cursor: 4.0.0
slice-ansi: 7.1.0
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
logform@2.6.0:
dependencies:
'@colors/colors': 1.6.0
@@ -6023,7 +5858,7 @@ snapshots:
micromark@2.11.4:
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
parse-entities: 2.0.0
transitivePeerDependencies:
- supports-color
@@ -6041,8 +5876,6 @@ snapshots:
mime@1.6.0: {}
mimic-fn@2.1.0: {}
mimic-fn@4.0.0: {}
min-indent@1.0.1: {}
@@ -6210,10 +6043,6 @@ snapshots:
dependencies:
fn.name: 1.1.0
onetime@5.1.2:
dependencies:
mimic-fn: 2.1.0
onetime@6.0.0:
dependencies:
mimic-fn: 4.0.0
@@ -6329,8 +6158,6 @@ snapshots:
picomatch@4.0.2: {}
pidtree@0.6.0: {}
pkg-types@1.1.0:
dependencies:
confbox: 0.1.7
@@ -6601,15 +6428,8 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
restore-cursor@4.0.0:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
reusify@1.0.4: {}
rfdc@1.3.1: {}
rimraf@3.0.2:
dependencies:
glob: 7.2.3
@@ -6742,8 +6562,6 @@ snapshots:
siginfo@2.0.0: {}
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
simple-git-hooks@2.11.1: {}
@@ -6764,16 +6582,6 @@ snapshots:
slash@5.1.0: {}
slice-ansi@5.0.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 4.0.0
slice-ansi@7.1.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 5.0.0
source-map-js@1.2.0: {}
sparse-bitfield@3.0.3:
@@ -6825,12 +6633,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
string-width@7.1.0:
dependencies:
emoji-regex: 10.3.0
get-east-asian-width: 1.2.0
strip-ansi: 7.1.0
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
@@ -7087,7 +6889,7 @@ snapshots:
vite-node@1.5.2(@types/node@20.12.7):
dependencies:
cac: 6.7.14
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
pathe: 1.1.2
picocolors: 1.0.0
vite: 5.2.10(@types/node@20.12.7)
@@ -7119,7 +6921,7 @@ snapshots:
'@vitest/utils': 1.5.2
acorn-walk: 8.3.2
chai: 4.4.1
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
execa: 8.0.1
local-pkg: 0.5.0
magic-string: 0.30.10
@@ -7149,7 +6951,7 @@ snapshots:
vue-eslint-parser@9.4.2(eslint@8.57.0):
dependencies:
debug: 4.3.4
debug: 4.3.4(supports-color@5.5.0)
eslint: 8.57.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
@@ -7219,12 +7021,6 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.0
wrap-ansi@9.0.0:
dependencies:
ansi-styles: 6.2.1
string-width: 7.1.0
strip-ansi: 7.1.0
wrappy@1.0.2: {}
ws@7.5.9: {}
@@ -7243,8 +7039,6 @@ snapshots:
lodash: 4.17.21
yaml: 2.4.2
yaml@2.3.4: {}
yaml@2.4.2: {}
yargs-parser@21.1.1: {}
@@ -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)
})
}
+7 -10
View File
@@ -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()
+1 -1
View File
@@ -11,7 +11,6 @@ function getEnvVar(key: string): string {
return envVar
}
export const node_env = getEnvVar('NODE_ENV')
console.log('ENV: ', node_env)
export const eosioApi = getEnvVar('API')
export const shipApi = getEnvVar('SHIP')
export const mongoUri = `${getEnvVar('MONGO_EXPLORER_URI')}${node_env === 'test' ? '-test' : ''}`
@@ -55,6 +54,7 @@ export const subsribedActions: IActionConfig[] = [
{ code: 'soviet', action: 'joincoopdec' },
{ code: 'soviet', action: 'updateboard', notify: true },
{ code: 'soviet', action: 'createboard', notify: true },
]
// --------------------------
BIN
View File
Binary file not shown.
+4 -1
View File
@@ -12,4 +12,7 @@ yarn-error.log
coverage
.env-mixed
.env-local
.env-local
logs
logs/*
+56
View File
@@ -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
+1
View File
@@ -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:
+2 -2
View File
@@ -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",
+6 -1
View File
@@ -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": {
Binary file not shown.
-8
View File
@@ -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') {
+2 -4
View File
@@ -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: {
+23 -2
View File
@@ -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',
}),
],
});
+4 -4
View File
@@ -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,
}
};
+20 -4
View File
@@ -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);
});
+5 -2
View File
@@ -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);
}
}
+48 -70
View File
@@ -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",
+1 -1
View File
@@ -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} не найден.`);
}
}
};
+61
View File
@@ -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();
}
});
+8 -4
View File
@@ -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();
};
+23 -5
View File
@@ -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, 'Недостаточно прав доступа'));
}
}
+3 -3
View File
@@ -7,6 +7,7 @@ import { RpcError } from 'eosjs';
export const errorConverter = (err, req, res, next) => {
let error = err;
console.log(error);
if (!(error instanceof ApiError)) {
if (error instanceof RpcError) {
const message = error.json.error.details[0].message.replace('assertion failure with message: ', ''); // Получаем первую строку ошибки
@@ -26,6 +27,7 @@ export const errorConverter = (err, req, res, next) => {
// eslint-disable-next-line no-unused-vars
export const errorHandler = (err, req, res, next?) => {
let { statusCode, message } = err;
console.error(err);
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
@@ -40,9 +42,7 @@ export const errorHandler = (err, req, res, next?) => {
...(config.env === 'development' && { stack: err.stack }),
};
if (config.env === 'development') {
logger.error(err);
}
logger.error(err);
res.status(statusCode).send(response);
};
@@ -5,7 +5,7 @@ import ApiError from '../utils/ApiError';
const validate = (schema) => (req, res, next) => {
// Фильтрация ключей, чтобы убедиться, что они существуют и не пусты
const validKeys = ['params', 'query', 'body'].filter(key => req[key] && Object.keys(req[key]).length > 0);
const validKeys = ['params', 'query', 'body'].filter((key) => req[key] && Object.keys(req[key]).length > 0);
const object = pick(req, validKeys);
const { value, error } = joi
@@ -21,5 +21,4 @@ const validate = (schema) => (req, res, next) => {
return next();
};
export default validate;
+1
View File
@@ -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;
+79 -46
View File
@@ -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;
+35 -52
View File
@@ -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;
+15 -5
View File
@@ -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;
@@ -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;
+7 -2
View File
@@ -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);
};
@@ -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