Compare commits

...

102 Commits

Author SHA1 Message Date
Alex Ant 6345c2d414 chore(release): publish 2024-10-28 10:16:08 +05:00
Alex Ant 87930cad75 update 2024-10-28 10:14:41 +05:00
Alex Ant 3ab2232121 Dockerfile 2024-10-28 10:06:40 +05:00
Alex Ant 9719e25421 move to weasyprint 2024-10-28 10:03:29 +05:00
Alex Ant 8e1f998c39 try to fix it ! 2024-10-27 20:34:08 +05:00
Alex Ant deb1c224f8 update 2024-10-27 19:33:06 +05:00
Alex Ant b2fa79898a arial embded font 2024-10-27 17:56:49 +05:00
Alex Ant da2759a3c6 update 2024-10-27 15:29:12 +05:00
Alex Ant f6bc696426 add common Arial font 2024-10-27 15:09:17 +05:00
Alex Ant da653e9b32 looking for bug #2 2024-10-27 12:46:47 +05:00
Alex Ant 227e153bde looking for bug 2024-10-27 12:03:18 +05:00
Alex Ant 12f906b9b0 update 2024-10-26 21:09:25 +05:00
Alex Ant c8236d4d10 update 2024-10-26 21:01:26 +05:00
Alex Ant e2006da50d TSK-289 Устранить избыточное сохранение документа при регенерации (1) 2024-10-26 20:20:05 +05:00
Alex Ant 433a7d3641 chore(release): publish 2024-10-26 17:45:45 +05:00
Alex Ant 6b41c74b43 regenerate document button, some bugs and fixs 2024-10-26 17:38:12 +05:00
Alex Ant cc3e7ff7c6 fix bug auto powerup on every restart 2024-10-26 13:42:11 +05:00
Alex Ant a35a6bf642 separate init payment factory 2024-10-26 13:10:52 +05:00
Alex Ant 9ecd523646 fix double subscriber bug 2024-10-26 12:52:24 +05:00
Alex Ant e7f896c38e update notification template 2024-10-26 10:49:28 +05:00
Alex Ant 624ec743b1 fix bug loading button 2024-10-25 19:26:41 +05:00
Alex Ant 553c5745ef fix update soviet bug 2024-10-25 16:46:26 +05:00
Alex Ant 3f2779ca3d fix bug voskhod everywhere 2024-10-25 16:42:18 +05:00
Alex Ant e51b962627 databazor bug fix 2024-10-25 16:20:58 +05:00
Alex Ant f709e135b6 Merge branch 'testnet' 2024-10-25 11:10:33 +05:00
Alex Ant 50b48aa247 update 2024-10-24 11:44:05 +05:00
Alex Ant 508f2abc82 update 2024-10-24 11:37:39 +05:00
Alex Ant 42ec56e578 fix 2024-10-24 11:26:07 +05:00
Alex Ant 92673dac6e chore(release): publish 2024-10-23 21:21:41 +05:00
Alex Ant daf57aba35 fix 2024-10-23 21:21:00 +05:00
Alex Ant dee629c429 chore(release): publish 2024-10-23 21:16:50 +05:00
Alex Ant 67f9092350 fix lib 2024-10-23 21:15:52 +05:00
Alex Ant 88691d47a5 chore(release): publish 2024-10-23 21:02:47 +05:00
Alex Ant 0fdd9bc803 update 2024-10-23 21:02:25 +05:00
Alex Ant 8452c607b4 chore(release): publish 2024-10-23 20:57:27 +05:00
Alex Ant 4fc57c2d57 update 2024-10-23 20:56:58 +05:00
Alex Ant 9f0b707a30 chore(release): publish 2024-10-23 20:51:30 +05:00
Alex Ant 511bc351d5 publish 2024-10-23 20:49:38 +05:00
Alex Ant 56ee3e962c chore(release): publish 2024-10-23 20:42:13 +05:00
Alex Ant 2781e8519d fix 2024-10-23 20:40:34 +05:00
Alex Ant 46575348fe chore(release): publish 2024-10-23 20:24:23 +05:00
Alex Ant bae2ca646b publish again 2024-10-23 20:24:00 +05:00
Alex Ant 0686c6ab00 chore(release): publish 2024-10-23 20:20:51 +05:00
Alex Ant db04b66ce5 update 2024-10-23 20:20:25 +05:00
Alex Ant c43c8a29af chore(release): publish 2024-10-23 20:17:40 +05:00
Alex Ant aaea18abe1 publish 2024-10-23 20:16:35 +05:00
Alex Ant 0b6076cb61 chore(release): publish 2024-10-23 20:10:05 +05:00
Alex Ant 3b6d02f02c fix 2024-10-23 20:08:45 +05:00
Alex Ant 625a0586a3 public 2024-10-23 20:06:22 +05:00
Alex Ant 10d63ca586 powerup is private plugin 2024-10-23 20:06:01 +05:00
Alex Ant d2697bf9de make library from terminal 2024-10-23 20:04:34 +05:00
Alex Ant fa8c8843e2 make powerup private plugin 2024-10-19 22:07:20 +05:00
Alex Ant 7b59ea4b96 chore(release): publish 2024-10-19 22:03:06 +05:00
Alex Ant 058215934e Merge branch 'testnet' 2024-10-19 22:02:08 +05:00
Alex Ant 1d724d841f plugin factory and powerup plugin 2024-10-19 21:58:34 +05:00
Alex Ant 32db2100de implementation plugins worker system 2024-10-18 11:27:39 +05:00
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 3dbd40e334 bug fix soviet update 2024-10-16 11:10:18 +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
268 changed files with 20055 additions and 14752 deletions
Vendored
BIN
View File
Binary file not shown.
+47
View File
@@ -3,6 +3,53 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.3](https://github.com/coopenomics/monocoop/compare/v2.1.2-alpha.10...v2.1.3) (2024-10-26)
## [2.1.2](https://github.com/coopenomics/monocoop/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package monocoop
## [2.1.2](https://github.com/coopenomics/monocoop/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package monocoop
# [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
+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`
+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.
+44
View File
@@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.3](https://github.com/copenomics/cooparser/compare/v2.1.2-alpha.10...v2.1.3) (2024-10-26)
## [2.1.2](https://github.com/copenomics/cooparser/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package cooparser-ts
## [2.1.2](https://github.com/copenomics/cooparser/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package cooparser-ts
# [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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "pkg-placeholder",
"version": "2.0.2",
"version": "2.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pkg-placeholder",
"version": "2.0.2",
"version": "2.1.3",
"license": "MIT",
"devDependencies": {
"@antfu/eslint-config": "^2.16.0",
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "cooparser-ts",
"type": "module",
"version": "2.0.2",
"version": "2.1.3",
"private": false,
"packageManager": "pnpm@9.0.6",
"description": "",
@@ -82,5 +82,6 @@
"unbuild": "^2.0.0",
"vite": "^5.2.10",
"vitest": "^1.5.2"
}
},
"gitHead": "7b59ea4b96991ed06abdc92e6d7835632845e3a5"
}
@@ -14,12 +14,10 @@ export async function BlockParser(db: Database, reader: EosioShipReaderResolved)
setTimeout(() => process.exit(1), 10000)
})
blocks$.subscribe(async (block: IBlock) => {
// console.log('new block: ', block)
// process.stdout.write('\r') // Возврат каретки в начало строки
// process.stdout.write(`Блок: ${block.block_num}`) // Ваш текст
db.updateCurrentBlock(block.last_irreversible_block_num)
db.updateCurrentBlock(block.block_num)
})
}
+7 -9
View File
@@ -51,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[],
@@ -79,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
View File
@@ -54,6 +54,7 @@ export const subsribedActions: IActionConfig[] = [
{ code: 'soviet', action: 'joincoopdec' },
{ code: 'soviet', action: 'updateboard', notify: true },
{ code: 'soviet', action: 'createboard', notify: true },
]
// --------------------------
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.3](https://github.com/hagopj13/node-express-boilerplate/compare/v2.1.2-alpha.10...v2.1.3) (2024-10-26)
## [2.1.2](https://github.com/hagopj13/node-express-boilerplate/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package @coopenomics/controller
## [2.1.2](https://github.com/hagopj13/node-express-boilerplate/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package coopback
# [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
+21 -38
View File
@@ -1,47 +1,30 @@
FROM node:alpine AS base
# Базовый образ Ubuntu для лучшей совместимости с WeasyPrint
FROM ubuntu:20.04 AS base
# ENV PNPM_HOME="/pnpm"
# ENV PATH="$PNPM_HOME:$PATH"
# Обновление и установка необходимых пакетов для Node.js и WeasyPrint
RUN apt-get update && \
apt-get install -y curl gnupg python3 python3-pip && \
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get install -y nodejs
# Включение pnpm
RUN corepack enable pnpm \
&& corepack prepare pnpm@latest --activate
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем package.json
COPY package.json ./
# Install dependencies for Puppeteer
RUN apk add --no-cache \
chromium \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont \
&& apk add --no-cache --virtual .build-deps \
curl \
&& rm -rf /var/cache/*
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# RUN mkdir -p /usr/src/node-app && chown -R node:node /usr/src/node-app
# WORKDIR /usr/src/node-app
# COPY package.json pnpm-lock.yaml ./
# USER node
# FROM base AS prod-deps
# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
# FROM base AS build
# RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# COPY --chown=node:node . .
# EXPOSE 3000
# CMD ["npx", "ts-node", "src/index.ts"]
# Установка зависимостей для WeasyPrint
RUN apt-get install -y \
libpango-1.0-0 \
libharfbuzz0b \
libpangoft2-1.0-0 \
libharfbuzz-subset0 \
libffi-dev \
libjpeg-dev \
libopenjp2-7-dev \
&& pip3 install WeasyPrint==62.3 \
&& rm -rf /var/lib/apt/lists/*
+1 -1
View File
@@ -1,4 +1,4 @@
# COOPERATIVE CONTROLLER
# COOPERATIVE CONTROLLER.
[![Build Status](https://travis-ci.org/hagopj13/node-express-boilerplate.svg?branch=master)](https://travis-ci.org/hagopj13/node-express-boilerplate)
[![Coverage Status](https://coveralls.io/repos/github/hagopj13/node-express-boilerplate/badge.svg?branch=master)](https://coveralls.io/github/hagopj13/node-express-boilerplate?branch=master)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "create-nodejs-express-app",
"version": "2.0.7",
"version": "2.1.4-alpha.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-nodejs-express-app",
"version": "2.0.7",
"version": "2.1.4-alpha.0",
"license": "MIT",
"dependencies": {
"@a2seven/yoo-checkout": "^1.1.4",
+10 -3
View File
@@ -1,8 +1,8 @@
{
"name": "coopback",
"version": "2.0.7",
"name": "@coopenomics/controller",
"version": "2.1.4-alpha.0",
"description": "",
"private": true,
"private": false,
"bin": "bin/createNodejsApp.js",
"main": "src/index.ts",
"repository": "https://github.com/hagopj13/node-express-boilerplate.git",
@@ -12,6 +12,9 @@
"engines": {
"node": ">=12.0.0"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"deploy-testnet": "git checkout testnet && git merge main && git push origin testnet && git checkout main",
"deploy-production": "git checkout production && git merge testnet && git push origin production && git checkout main",
@@ -86,6 +89,7 @@
"isomorphic-fetch": "^3.0.0",
"jest": "^29.7.0",
"joi": "^17.13.1",
"joi-to-json": "^4.3.0",
"joi-to-typescript": "^4.13.0",
"jsonwebtoken": "^8.5.1",
"jspdf": "^2.5.1",
@@ -94,6 +98,7 @@
"mongoose": "^8.5.2",
"mongoose-sequence": "^6.0.1",
"morgan": "^1.9.1",
"node-cron": "^3.0.3",
"nodemailer": "^6.3.1",
"nunjucks": "^3.2.4",
"passport": "^0.4.0",
@@ -103,9 +108,11 @@
"socket.io-client": "^4.7.5",
"swagger-jsdoc": "^6.0.8",
"swagger-ui-express": "^4.1.6",
"threads": "^1.7.0",
"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",
-4
View File
@@ -9,8 +9,6 @@ const envVarsSchema = Joi.object()
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
BASE_URL: Joi.string().required(),
SERVER_SECRET: Joi.string().required(),
SERVICE_USERNAME: Joi.string().required(),
SERVICE_WIF: Joi.string().required(),
PORT: Joi.number().default(3000),
MONGODB_URL: Joi.string().required().description('Mongo DB url'),
JWT_SECRET: Joi.string().required().description('JWT secret key'),
@@ -46,8 +44,6 @@ export default {
base_url: envVars.BASE_URL,
port: envVars.PORT,
server_secret: envVars.SERVER_SECRET,
service_wif: envVars.SERVICE_WIF,
service_username: envVars.SERVICE_USERNAME,
mongoose: {
url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''),
options: {
+12 -3
View File
@@ -1,7 +1,6 @@
const user = [
'getSelf',
'initialPayment',
'generateDocument',
'getMyDocuments',
'getMyOrders',
'sendVerificationEmail',
@@ -11,9 +10,19 @@ const user = [
'getVars',
];
const member = ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo', 'manageOrders'];
const member = [
...user,
'getUsers',
'manageUsers',
'loadAgenda',
'loadStaff',
'getDocuments',
'loadInfo',
'manageOrders',
'regenerateDocument',
];
const chairman = [...user, ...member, 'addUser', 'setVars', 'manageSettings'];
const chairman = [...user, ...member, 'addUser', 'setVars', 'manageSettings', 'setWif', 'setPlugin'];
const allRoles = {
user,
@@ -14,8 +14,8 @@ export const getDocuments = catchAsync(async (req: RGetDocuments, res) => {
const filter = req.query?.filter;
const limit = req.query?.limit as number;
const page = req.query?.page as number;
const documents = await documentService.queryDocuments(filter, page, limit);
const type = req.query?.type as string;
const documents = await documentService.queryDocuments(type, filter, page, limit);
res.status(httpStatus.OK).send(documents);
});
@@ -8,3 +8,4 @@ export * as systemController from './system.controller';
export * as participantController from './participant.controller';
export * as methodController from './method.controller';
export * as settingController from './setting.controller';
export * as pluginController from './plugin.controller';
@@ -0,0 +1,55 @@
import { Request, Response } from 'express';
import { pluginService } from '../services';
import httpStatus from 'http-status';
import type { RGetPluginConfig, RGetPluginList, RGetPluginSchema, RSetPlugin } from '../types';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { restartWorker, terminateWorker } from '../factories/pluginFactory';
import pick from '../utils/pick';
export const getPluginList = catchAsync(async (req: RGetPluginList, res: Response) => {
const filter = pick(req.query, ['name']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const plugins = await pluginService.queryPlugins(filter, options);
res.send(plugins);
});
// Контроллер для обновления параметров плагинов
export const setPluginConfig = catchAsync(async (req: RSetPlugin, res: Response) => {
const { name, config, enabled } = req.body;
try {
const updatedConfig = await pluginService.updatePluginConfig(name, enabled, config);
if (enabled == false) await terminateWorker(name);
else await restartWorker(name);
res.status(httpStatus.OK).json({
message: `Конфигурация плагина ${name} успешно обновлена`,
config: updatedConfig,
});
} catch (e: any) {
throw new ApiError(httpStatus.BAD_REQUEST, e.message);
}
});
// Контроллер для обновления параметров плагинов
export const getPluginSchema = catchAsync(async (req: RGetPluginSchema, res: Response) => {
const { name } = req.query;
try {
const schema = pluginService.getPluginSchema(name);
res.status(httpStatus.OK).json(schema);
} catch (e: any) {
throw new ApiError(httpStatus.BAD_REQUEST, e.message);
}
});
// Контроллер для обновления параметров плагинов
export const getPluginConfig = catchAsync(async (req: RGetPluginConfig, res: Response) => {
const { name } = req.query;
console.log('plugin', name);
const config = await pluginService.getPluginConfig(name);
res.status(httpStatus.OK).json(config);
});
@@ -5,18 +5,30 @@ import { IHealthResponse, IInstall, RInstall } from '../types';
import { Request, Response } from 'express';
import catchAsync from '../utils/catchAsync';
import { systemService } from '../services';
import type { ISetVars, RSetVars } from '../types/auto-generated/system.validation';
import 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 });
logger.info('System installed');
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();
@@ -31,7 +43,6 @@ export const getHealth = catchAsync(async (req: Request, res: Response) => {
export const setVars = catchAsync(async (req: RSetVars, res: Response) => {
await systemService.setVars(req.body as ISetVars);
logger.info('New vars installed', { source: 'setVars', vars: req.body });
res.status(httpStatus.OK).send();
});
@@ -39,3 +50,8 @@ 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);
});
@@ -5,12 +5,26 @@ import { userService, tokenService, emailService, blockchainService } from '../s
import { IAddUser, ICreateUser, RCreateUser } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
// 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: '',
@@ -47,13 +62,15 @@ export const addUser = catchAsync(async (req: Request, res: Response) => {
const user = await userService.createUser(newUser);
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: '',
@@ -3,6 +3,7 @@ import { IAction } from '../types/common';
import { SovietContract } from 'cooptypes';
import { wsService } from '../services';
import logger from '../config/logger';
import config from '../config/config';
let clientSocket: Socket | undefined;
@@ -33,7 +34,13 @@ export const closeSocketConnection = (): void => {
};
async function processEvent(event: IAction) {
if (event.receiver === SovietContract.contractName.production && event.name === 'updateboard') {
wsService.updateBoard(event.data);
if (event.receiver === config.coopname) {
if (event.name === 'updateboard') {
wsService.updateBoard(event.data);
}
if (event.name === 'createboard') {
wsService.updateBoard(event.data);
}
}
}
+156 -100
View File
@@ -14,78 +14,22 @@
}
],
"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",
"tags": ["Auth"],
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["email", "password"],
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
@@ -143,14 +87,18 @@
"/auth/logout": {
"post": {
"summary": "Logout",
"tags": ["Auth"],
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["refreshToken"],
"required": [
"refreshToken"
],
"properties": {
"refreshToken": {
"type": "string"
@@ -176,14 +124,18 @@
"/auth/refresh-tokens": {
"post": {
"summary": "Refresh auth tokens",
"tags": ["Auth"],
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["refreshToken"],
"required": [
"refreshToken"
],
"properties": {
"refreshToken": {
"type": "string"
@@ -217,14 +169,18 @@
"post": {
"summary": "Forgot password",
"description": "An email will be sent to reset password.",
"tags": ["Auth"],
"tags": [
"Auth"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["email"],
"required": [
"email"
],
"properties": {
"email": {
"type": "string",
@@ -251,7 +207,9 @@
"/auth/reset-password": {
"post": {
"summary": "Reset password",
"tags": ["Auth"],
"tags": [
"Auth"
],
"parameters": [
{
"in": "query",
@@ -269,7 +227,9 @@
"application/json": {
"schema": {
"type": "object",
"required": ["password"],
"required": [
"password"
],
"properties": {
"password": {
"type": "string",
@@ -310,7 +270,9 @@
"post": {
"summary": "Send verification email",
"description": "An email will be sent to verify email.",
"tags": ["Auth"],
"tags": [
"Auth"
],
"security": [
{
"bearerAuth": []
@@ -329,7 +291,9 @@
"/auth/verify-email": {
"post": {
"summary": "verify email",
"tags": ["Auth"],
"tags": [
"Auth"
],
"parameters": [
{
"in": "query",
@@ -364,7 +328,9 @@
},
"/orders/ipn": {
"post": {
"tags": ["Orders"],
"tags": [
"Orders"
],
"summary": "Обработка IPN уведомления",
"description": "Маршрут для обработки IPN уведомления.",
"responses": {
@@ -376,7 +342,9 @@
},
"/orders/initial": {
"post": {
"tags": ["Orders"],
"tags": [
"Orders"
],
"summary": "Создание регистрационного платежа",
"description": "Маршрут для создания регистрационного платежа.",
"security": [
@@ -410,7 +378,9 @@
},
"/orders/deposit": {
"post": {
"tags": ["Orders"],
"tags": [
"Orders"
],
"summary": "Создание депозита в кошелёк",
"description": "Маршрут для создания депозита.",
"security": [
@@ -446,7 +416,9 @@
"post": {
"summary": "Создать пользователя",
"description": "Только администраторы могут создавать новых пользователей.",
"tags": ["Users"],
"tags": [
"Users"
],
"security": [
{
"bearerAuth": []
@@ -487,7 +459,9 @@
"get": {
"summary": "Получить всех пользователей",
"description": "Только администратор может получить всех пользователей",
"tags": ["Users"],
"tags": [
"Users"
],
"security": [
{
"bearerAuth": []
@@ -587,7 +561,9 @@
"get": {
"summary": "Получить пользователя",
"description": "Авторизованные пользователи могут получать только свою собственную информацию. Только администраторы могут получать информацию о других пользователях.",
"tags": ["Users"],
"tags": [
"Users"
],
"security": [
{
"bearerAuth": []
@@ -629,7 +605,9 @@
"patch": {
"summary": "Обновить пользователя",
"description": "Только администраторы могут обновлять информацию о других пользователях.",
"tags": ["Users"],
"tags": [
"Users"
],
"security": [
{
"bearerAuth": []
@@ -744,14 +722,25 @@
},
"status": {
"type": "string",
"enum": ["created", "joined", "payed", "registered", "active", "blocked"]
"enum": [
"created",
"joined",
"payed",
"registered",
"active",
"blocked"
]
},
"is_registered": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": ["individual", "entrepreneur", "organization"]
"enum": [
"individual",
"entrepreneur",
"organization"
]
},
"public_key": {
"type": "string"
@@ -787,7 +776,10 @@
},
"ICreatedPayment": {
"type": "object",
"required": ["order_num", "confirmation_token"],
"required": [
"order_num",
"confirmation_token"
],
"properties": {
"order_num": {
"type": "number"
@@ -799,7 +791,10 @@
},
"ICreateDeposit": {
"type": "object",
"required": ["username", "quantity"],
"required": [
"username",
"quantity"
],
"properties": {
"username": {
"type": "string"
@@ -811,7 +806,9 @@
},
"ICreateInitialPayment": {
"type": "object",
"required": ["username"],
"required": [
"username"
],
"properties": {
"username": {
"type": "string"
@@ -844,7 +841,15 @@
"format": "email"
}
},
"required": ["first_name", "last_name", "middle_name", "birthdate", "full_address", "phone", "email"]
"required": [
"first_name",
"last_name",
"middle_name",
"birthdate",
"full_address",
"phone",
"email"
]
},
"RepresentedBySchema": {
"type": "object",
@@ -865,7 +870,13 @@
"type": "string"
}
},
"required": ["first_name", "last_name", "middle_name", "position", "based_on"]
"required": [
"first_name",
"last_name",
"middle_name",
"position",
"based_on"
]
},
"RussiaDetailsSchema": {
"type": "object",
@@ -877,7 +888,10 @@
"type": "string"
}
},
"required": ["inn", "ogrn"]
"required": [
"inn",
"ogrn"
]
},
"RussianBankDetailsSchema": {
"type": "object",
@@ -895,14 +909,22 @@
"type": "string"
}
},
"required": ["bik", "inn", "corr", "kpp"]
"required": [
"bik",
"inn",
"corr",
"kpp"
]
},
"IBankAccount": {
"type": "object",
"properties": {
"currency": {
"type": "string",
"enum": ["RUB", "Other"]
"enum": [
"RUB",
"Other"
]
},
"card_number": {
"type": "string"
@@ -917,7 +939,13 @@
"$ref": "#/components/schemas/RussianBankDetailsSchema"
}
},
"required": ["currency", "card_number", "bank_name", "account_number", "details"]
"required": [
"currency",
"card_number",
"bank_name",
"account_number",
"details"
]
},
"IOrganizationData": {
"type": "object",
@@ -937,7 +965,10 @@
},
"country": {
"type": "string",
"enum": ["Russia", "Other"]
"enum": [
"Russia",
"Other"
]
},
"city": {
"type": "string"
@@ -997,7 +1028,10 @@
},
"country": {
"type": "string",
"enum": ["Russia", "Other"]
"enum": [
"Russia",
"Other"
]
},
"city": {
"type": "string"
@@ -1038,7 +1072,10 @@
},
"role": {
"type": "string",
"enum": ["user", "admin"]
"enum": [
"user",
"admin"
]
},
"public_key": {
"type": "string"
@@ -1053,7 +1090,11 @@
},
"type": {
"type": "string",
"enum": ["individual", "entrepreneur", "organization"]
"enum": [
"individual",
"entrepreneur",
"organization"
]
},
"individual_data": {
"$ref": "#/components/schemas/IIndividualData"
@@ -1065,7 +1106,14 @@
"$ref": "#/components/schemas/IEntrepreneurData"
}
},
"required": ["email", "password", "role", "public_key", "username", "type"]
"required": [
"email",
"password",
"role",
"public_key",
"username",
"type"
]
},
"IDocument": {
"type": "object",
@@ -1083,7 +1131,12 @@
"type": "object"
}
},
"required": ["hash", "signature", "public_key", "meta"]
"required": [
"hash",
"signature",
"public_key",
"meta"
]
},
"IJoinCooperative": {
"type": "object",
@@ -1095,7 +1148,10 @@
"$ref": "#/components/schemas/IDocument"
}
},
"required": ["username", "document"]
"required": [
"username",
"document"
]
}
},
"responses": {
@@ -1167,7 +1223,7 @@
"tags": [
{
"name": "Auth",
"description": "Authentication"
"description": "Управление аудентификацией"
},
{
"name": "Orders",
@@ -1178,4 +1234,4 @@
"description": "Управление пользователями"
}
]
}
}
+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,104 @@
import { spawn, Thread, Worker } from 'threads';
import { PluginConfig } from '../models/pluginConfig.model';
// Объект для хранения запущенных воркеров
const workerMap: { [key: string]: { worker: any; thread: Worker } } = {};
export const initializeDefaultPlugins = async () => {
const defaultPlugins = [
{
name: 'powerup',
enabled: true,
config: {
dailyPackageSize: 5, // Размер ежедневного пакета в AXON
topUpAmount: 5, // Сумма пополнения при достижении порога (в AXON)
systemSymbol: 'AXON',
systemPrecision: 4,
thresholds: {
cpu: 5000, // Порог CPU в микросекундах
net: 1024, // Порог NET в байтах
ram: 10240, // Порог RAM в байтах
},
},
},
];
for (const plugin of defaultPlugins) {
const existingPlugin = await PluginConfig.findOne({ name: plugin.name });
if (!existingPlugin) {
await PluginConfig.create(plugin);
console.log(`Дефолтный плагин ${plugin.name} добавлен в базу.`);
}
}
};
export const pluginFactory = async () => {
const plugins = await PluginConfig.find({ enabled: true });
for (const pluginData of plugins) {
await initializeWorkerByName(pluginData.name);
}
};
// Функция для завершения воркера по имени плагина
export const terminateWorker = async (pluginName: string) => {
const workerData = workerMap[pluginName];
if (workerData) {
await workerData.thread.terminate();
delete workerMap[pluginName]; // Удаляем воркер из карты
console.log(`Воркер для плагина ${pluginName} завершен.`);
} else {
console.log(`Воркер для плагина ${pluginName} не найден.`);
}
};
// Функция для инициализации воркера по имени плагина
export const initializeWorkerByName = async (pluginName: string) => {
// Проверяем, запущен ли уже воркер
if (workerMap[pluginName]) {
console.log(`Воркер для плагина ${pluginName} уже запущен.`);
return;
}
// Получаем данные плагина из базы
const pluginData = await PluginConfig.findOne({ name: pluginName });
if (!pluginData || !pluginData.enabled) {
console.log(`Плагин ${pluginName} не найден или отключен.`);
return;
}
// Запускаем новый воркер
const thread = new Worker('../workers/plugin.worker.ts');
const worker = spawn(thread, { timeout: 30000 }); // Не используем await, чтобы не блокировать основной поток
//таймаут выше для локальных машин
// Сохраняем воркера и поток в объекте по имени плагина
workerMap[pluginName] = { worker, thread };
// Воркеры работают в фоне
worker.then(async (workerInstance) => {
try {
const result = await workerInstance.initializePlugin(pluginName);
console.log(result); // Плагин инициализирован успешно
} catch (err) {
console.error(err); // Обработка ошибки
}
});
console.log(`Воркер для плагина ${pluginName} успешно запущен.`);
};
// Функция для перезагрузки воркера по имени плагина
export const restartWorker = async (pluginName: string) => {
const workerData = workerMap[pluginName];
if (workerData) {
// Если воркер уже запущен, сначала завершаем его
console.log(`Перезагрузка воркера для плагина ${pluginName}...`);
await terminateWorker(pluginName);
}
// Затем запускаем воркер заново
await initializeWorkerByName(pluginName);
console.log(`Воркер для плагина ${pluginName} перезагружен.`);
};
+20 -8
View File
@@ -4,9 +4,10 @@ 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 { connectGenerator, diconnectGenerator } from './services/document.service';
import { initSocketConnection } from './controllers/ws.controller';
import { systemService } from './services';
import { initializeDefaultPlugins, pluginFactory } from './factories/pluginFactory';
import { paymentService } from './services';
const SERVER_URL: string = process.env.SOCKET_SERVER || 'http://localhost:2222';
@@ -15,20 +16,27 @@ let server: any;
mongoose.connect(config.mongoose.url).then(async () => {
logger.info('Connected to MongoDB');
await systemService.init();
// подключаемся к хранилищу приватных данных
await connectGenerator();
// подключаемся к ws-серверу
await initSocketConnection(SERVER_URL);
// Запуск фабрики платежей
await paymentService.init();
// Инициализация дефолтных плагинов
await initializeDefaultPlugins();
// // Запуск плагинов
await pluginFactory();
server = app.listen(config.port, () => {
logger.info(`Listening to port ${config.port}`);
});
});
const exitHandler = () => {
const exitHandler = async () => {
if (server) {
server.close(() => {
logger.info('Server closed');
@@ -39,16 +47,20 @@ const exitHandler = () => {
}
};
const unexpectedErrorHandler = (error: any) => {
const unexpectedErrorHandler = async (error: any) => {
console.log(error);
await diconnectGenerator();
await mongoose.disconnect();
logger.error(error, { source: 'unexpectedErrorHandler' });
exitHandler();
await exitHandler();
};
process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);
process.on('SIGTERM', () => {
process.on('SIGTERM', async () => {
logger.info('SIGTERM received');
await mongoose.disconnect();
if (server) {
server.close();
}
+5 -11
View File
@@ -25,18 +25,12 @@ const verifyCallback = (req, resolve, reject, requiredRights) => async (err, use
const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight));
// Check if user is accessing their own resource
const isOwnResource = req.params.username === user.username;
const isOwnResource =
(req.params.username && req.params.username === user.username) ||
(req.body.username && req.body.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, 'Недостаточно прав доступа'));
}
if (!isOwnResource && (!hasRequiredRights || !requiredRights.length)) {
return reject(new ApiError(FORBIDDEN, 'Недостаточно прав доступа'));
}
resolve();
@@ -7,7 +7,6 @@ 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: ', ''); // Получаем первую строку ошибки
@@ -27,7 +26,6 @@ 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];
@@ -0,0 +1,25 @@
import { Schema, model, type Model } from 'mongoose';
import type { IPluginSchema } from '../types/plugin.types';
import { paginate, toJSON } from './plugins';
interface IPaginate extends Model<IPluginSchema<any>> {
paginate(filter, options): any;
}
export const pluginSchema = new Schema<IPluginSchema<any>, IPaginate>(
{
name: { type: String, required: true },
enabled: { type: Boolean, default: true },
config: { type: Schema.Types.Mixed }, // Своя конфигурация для каждого плагина
},
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
pluginSchema.plugin(toJSON);
pluginSchema.plugin(paginate);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const PluginConfig = model<IPluginSchema<any>, IPaginate>('Plugins', pluginSchema);
@@ -0,0 +1,24 @@
import { Schema, model, type Model } from 'mongoose';
import type { IPluginLog } from '../types/plugin.types';
import { paginate, toJSON } from './plugins';
interface IPaginate extends Model<IPluginLog<any>> {
paginate(filter, options): any;
}
export const pluginLog = new Schema<IPluginLog<any>, IPaginate>(
{
name: { type: String, required: true },
log: { type: Schema.Types.Mixed }, // Своя конфигурация для каждого плагина
},
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
pluginLog.plugin(toJSON);
pluginLog.plugin(paginate);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const PluginLog = model<IPluginLog<any>, IPaginate>('PluginLogs', pluginLog);
@@ -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;
+5
View File
@@ -0,0 +1,5 @@
import * as powerup from './powerup/powerup.plugin';
export const PluginRegistry = {
powerup,
};
@@ -0,0 +1,20 @@
# 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.3](https://github.com/coopenomics/monocoop/compare/v2.1.2-alpha.10...v2.1.3) (2024-10-26)
## [2.1.2](https://github.com/coopenomics/monocoop/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package powerup
## [2.1.2](https://github.com/coopenomics/monocoop/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package powerup
@@ -0,0 +1,14 @@
{
"name": "powerup",
"version": "2.1.3",
"description": "",
"private": true,
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"gitHead": "7b59ea4b96991ed06abdc92e6d7835632845e3a5"
}
@@ -0,0 +1,196 @@
import { IPlugin, type IPluginLog, type IPluginSchema } from '../../types/plugin.types';
import Joi from 'joi';
import cron from 'node-cron';
import { blockchainService } from '../../services';
import logger from '../../config/logger';
import { default as coopConfig } from '../../config/config';
import { PluginConfig } from '../../models/pluginConfig.model';
import { PluginLog } from '../../models/pluginLog.model';
import type { IGetPluginConfig } from '../../types';
import type { Document, Types } from 'mongoose';
// Интерфейс для параметров конфигурации плагина powerup
export interface IConfig {
dailyPackageSize: number; // Размер ежедневного пакета в AXON
topUpAmount: number; // Сумма пополнения при достижении порога (в AXON)
systemSymbol: string;
systemPrecision: number;
lastDailyReplenishmentDate: Date;
thresholds: {
cpu: number; // Порог CPU в микросекундах
net: number; // Порог NET в байтах
ram: number; // Порог RAM в байтах
};
}
export interface ILog {
type: 'daily' | 'now';
amount: string;
resources: {
username: string;
ram_usage: any;
ram_quota: any;
net_limit: any;
cpu_limit: any;
};
}
export class Plugin implements IPlugin {
name = 'powerup';
data!: IPluginSchema<IConfig>;
public pluginSchema = Joi.object<IConfig>({
dailyPackageSize: Joi.number().required(),
topUpAmount: Joi.number().required(),
thresholds: Joi.object({
cpu: Joi.number().required(),
net: Joi.number().required(),
ram: Joi.number().required(),
}).required(),
});
async initialize() {
const pluginData = await PluginConfig.findOne({ name: this.name });
if (!pluginData) throw new Error('Конфиг не найден');
this.data = pluginData;
logger.info(`Инициализация ${this.name} с конфигурацией`, this.data);
// Проверяем, было ли ежедневное пополнение в последние 24 часа
const lastDate = this.data.config.lastDailyReplenishmentDate;
const now = new Date();
if (lastDate) {
const diffInHours = Math.abs(now.getTime() - lastDate.getTime()) / 36e5; // Разница во времени в часах
if (diffInHours < 24) {
logger.info('Ежедневное пополнение уже выполнялось в последние 24 часа. Повторное пополнение не требуется.');
} else {
logger.info('Ежедневное пополнение не выполнялось в последние 24 часа. Выполняем пополнение...');
await this.runDailyTask();
}
} else {
logger.info('Дата последнего пополнения отсутствует. Выполняем пополнение...');
await this.runDailyTask();
}
// Регистрация cron-задачи для ежедневного пополнения
cron.schedule('0 0 * * *', () => {
logger.info('Запуск ежедневной задачи пополнения');
this.runDailyTask();
});
// Регистрация cron-задачи для проверки ресурсов каждую минуту
cron.schedule('* * * * *', () => {
logger.info('Запуск задачи проверки ресурсов');
this.runTask();
});
// eslint-disable-next-line @typescript-eslint/no-empty-function
await new Promise(() => {});
}
private getQuantity(amount: number): string {
return `${amount.toFixed(this.data.config.systemPrecision)} ${this.data.config.systemSymbol}`;
}
// Ежедневная задача пополнения
private async runDailyTask() {
const quantity = this.getQuantity(this.data.config.dailyPackageSize);
logger.info(`Выполнение ежедневного пополнения на сумму ${quantity}`);
try {
// Получаем имя пользователя из окружения или другой конфигурации
const username = coopConfig.coopname;
const account = await blockchainService.getBlockchainAccount(username);
await blockchainService.powerUp(username, quantity);
this.data.config.lastDailyReplenishmentDate = new Date();
this.data.markModified('config'); // Помечаем config как измененное поле
await this.data.save();
await this.log({
type: 'daily',
amount: quantity,
resources: {
username: account.account_name,
ram_usage: account.ram_usage,
ram_quota: account.ram_quota,
net_limit: account.net_limit,
cpu_limit: account.cpu_limit,
},
});
logger.info('Ежедневное пополнение выполнено успешно');
} catch (error) {
console.error('Ошибка при выполнении ежедневного пополнения:', error);
}
}
private async log(action: ILog) {
await PluginLog.create({ name: this.name, log: action });
}
// Задача проверки и пополнения ресурсов
private async runTask() {
logger.info(`Задача плагина ${this.name} выполняется...`);
try {
// Получаем имя пользователя из окружения или другой конфигурации
const username = coopConfig.coopname;
const account = await blockchainService.getBlockchainAccount(username);
// Получаем текущие значения квот
const cpuLimit = account.cpu_limit;
const netLimit = account.net_limit;
const ramQuota = account.ram_quota;
const ramUsage = account.ram_usage;
const availableRam = ramQuota - ramUsage;
// Проверяем пороги и пополняем при необходимости
let needPowerUp = false;
if (cpuLimit.available <= this.data.config.thresholds.cpu) {
logger.info(`CPU квота ниже порога (${cpuLimit.available} µs).`);
needPowerUp = true;
}
if (netLimit.available <= this.data.config.thresholds.net) {
logger.info(`NET квота ниже порога (${netLimit.available} bytes).`);
needPowerUp = true;
}
if (availableRam <= this.data.config.thresholds.ram) {
logger.info(`RAM квота ниже порога (${availableRam} bytes).`);
needPowerUp = true;
}
if (needPowerUp) {
// Выполняем пополнение ресурсов
const quantity = this.getQuantity(this.data.config.topUpAmount);
await blockchainService.powerUp(username, quantity);
logger.info(`Пополнение выполнено на сумму ${quantity}.`);
await this.log({
type: 'now',
amount: quantity,
resources: {
username: account.account_name,
ram_usage: account.ram_usage,
ram_quota: account.ram_quota,
net_limit: account.net_limit,
cpu_limit: account.cpu_limit,
},
});
} else {
logger.info('Квоты ресурсов в норме, пополнение не требуется.');
}
} catch (error) {
console.error('Ошибка при проверке и пополнении ресурсов:', error);
}
}
}
@@ -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: Управление аудентификацией
*/
/**
@@ -8,14 +8,14 @@ const router = express.Router();
router
.route('/generate')
.post(auth('generateDocument'), validate(documentValidation.RGenerate), documentController.generateDocument);
.post(auth('regenerateDocument'), validate(documentValidation.RGenerate), documentController.generateDocument);
router
.route('/get-documents')
.get(auth('getDocuments'), validate(documentValidation.RGetDocuments), documentController.getDocuments);
router
.route('/get-my-documents')
.get(auth('getMyDocuments'), validate(documentValidation.RGetDocuments), documentController.getDocuments);
// router
// .route('/get-my-documents')
// .get(auth('getMyDocuments'), validate(documentValidation.RGetDocuments), documentController.getDocuments);
export default router;
@@ -9,6 +9,7 @@ import dataRoute from './document.route';
import notifyRoute from './notify.route';
import participantsRoute from './participant.route';
import methodRoute from './method.route';
import pluginRoute from './plugin.route';
import config from '../../config/config';
@@ -51,6 +52,10 @@ const defaultRoutes = [
path: '/notify',
route: notifyRoute,
},
{
path: '/plugins',
route: pluginRoute,
},
];
const devRoutes = [
@@ -0,0 +1,20 @@
import { Router } from 'express';
import { pluginController } from '../../controllers';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import { pluginValidation } from '../../validations';
const router = Router();
router.route('/').post(auth('setPlugin'), validate(pluginValidation.RSetPlugin), pluginController.setPluginConfig);
router.route('/').get(auth('setPlugin'), validate(pluginValidation.RGetPluginList), pluginController.getPluginList);
router
.route('/schema')
.get(auth('setPlugin'), validate(pluginValidation.RGetPluginSchema), pluginController.getPluginSchema);
router
.route('/config')
.get(auth('setPlugin'), validate(pluginValidation.RGetPluginConfig), pluginController.getPluginConfig);
export default router;
@@ -6,12 +6,18 @@ import { systemValidation } from '../../validations';
const router = Router();
router.route('/install').post(auth('install'), validate(systemValidation.RInstall), systemController.install);
//init - устанавливает данные организации кооператива
router.route('/init').post(auth('init'), validate(systemValidation.RInit), systemController.init);
router.route('/get-vars-schema').post(auth('getVars'), systemController.getVarsSchema);
router.route('/get-vars').post(auth('getVars'), systemController.setVars);
router.route('/install').post(validate(systemValidation.RInstall), systemController.install);
router.route('/set-vars').post(auth('setVars'), validate(systemValidation.RSetVars), systemController.setVars);
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);
@@ -198,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,
});
@@ -1,10 +1,9 @@
import { Api, JsonRpc } from 'eosjs';
import { JsSignatureProvider } from 'eosjs/dist/eosjs-jssig';
import { TextEncoder, TextDecoder } from 'util';
import fetch from 'isomorphic-fetch';
import EosApi from 'eosjs-api';
import getInternalAction from '../utils/getInternalAction';
import { GatewayContract, RegistratorContract, SovietContract } from 'cooptypes';
import { GatewayContract, RegistratorContract, SovietContract, type SystemContract } from 'cooptypes';
import { IUser } from '../types/user.types';
import { GetAccountResult, GetInfoResult } from 'eosjs/dist/eosjs-rpc-interfaces';
import config from '../config/config';
@@ -13,6 +12,7 @@ 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 });
@@ -36,7 +36,7 @@ async function getBlockchainAccount(username): Promise<GetAccountResult> {
}
export async function transact(actions: any): Promise<any> {
const instance = await getInstance(config.service_wif);
const instance = await getInstance(config.coopname);
const result = await instance.transact(
{
actions,
@@ -57,15 +57,19 @@ export async function transact(actions: any): Promise<any> {
* @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() });
const api = new Api({ rpc, signatureProvider });
return api;
}
function getApi() {
const options = {
httpEndpoint: process.env.BLOCKCHAIN_RPC, // default, null for cold-storage
httpEndpoint: process.env.BLOCKCHAIN_RPC, //
verbose: false, // API logging
fetchConfiguration: {},
};
@@ -115,7 +119,7 @@ async function getCooperative(coopname) {
}
async function registerBlockchainAccount(user: IUser, order: IOrder) {
const eos = await getInstance(config.service_wif);
const eos = await getInstance(config.coopname);
const actions = [] as any;
// Создаем newaccount
@@ -133,7 +137,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: RegistratorContract.Actions.CreateAccount.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -156,7 +160,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: RegistratorContract.Actions.RegisterUser.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -178,7 +182,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: RegistratorContract.Actions.JoinCooperative.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -198,7 +202,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: GatewayContract.Actions.CreateDeposit.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -208,7 +212,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
// Создаем completeDeposit
const completeDeposit: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit = {
coopname: config.coopname,
admin: config.service_username,
admin: config.coopname,
deposit_id: order.order_num as number,
memo: '',
};
@@ -217,7 +221,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: GatewayContract.Actions.CompleteDeposit.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -240,7 +244,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -263,7 +267,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -286,7 +290,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -309,7 +313,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
name: SovietContract.Actions.Agreements.SendAgreement.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -330,7 +334,7 @@ async function registerBlockchainAccount(user: IUser, order: IOrder) {
}
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);
@@ -340,7 +344,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',
},
],
@@ -360,7 +364,7 @@ async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICrea
}
async function createOrder(data) {
const eos = await getInstance(config.service_wif);
const eos = await getInstance(config.coopname);
const actions = [
{
@@ -392,7 +396,7 @@ async function createOrder(data) {
}
async function completeDeposit(order: IOrder) {
const eos = await getInstance(config.service_wif);
const eos = await getInstance(config.coopname);
const createDeposit: GatewayContract.Actions.CreateDeposit.ICreateDeposit = {
coopname: config.coopname,
@@ -404,7 +408,7 @@ async function completeDeposit(order: IOrder) {
const completeDeposit: GatewayContract.Actions.CompleteDeposit.ICompleteDeposit = {
coopname: config.coopname,
admin: config.service_username,
admin: config.coopname,
deposit_id: order.order_num as number,
memo: '',
};
@@ -415,7 +419,7 @@ async function completeDeposit(order: IOrder) {
name: GatewayContract.Actions.CreateDeposit.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -426,7 +430,7 @@ async function completeDeposit(order: IOrder) {
name: GatewayContract.Actions.CompleteDeposit.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -446,7 +450,7 @@ async function completeDeposit(order: IOrder) {
}
async function failOrder(data) {
const eos = await getInstance(config.service_wif);
const eos = await getInstance(config.coopname);
const actions = [
{
@@ -474,15 +478,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',
},
],
@@ -502,7 +506,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 = [
{
@@ -510,7 +514,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',
},
],
@@ -535,7 +539,7 @@ export async function cancelOrder(data: GatewayContract.Actions.RefundDeposit.IR
name: GatewayContract.Actions.RefundDeposit.actionName,
authorization: [
{
actor: config.service_username,
actor: config.coopname,
permission: 'active',
},
],
@@ -545,6 +549,38 @@ export async function cancelOrder(data: GatewayContract.Actions.RefundDeposit.IR
return await transact([action]);
}
export async function powerUp(username: string, quantity: string): Promise<void> {
const data: SystemContract.Actions.Powerup.IPowerup = {
payer: username,
receiver: username,
days: 1,
payment: quantity,
transfer: false,
};
const actions = [
{
account: 'eosio',
name: 'powerup',
authorization: [
{
actor: username,
permission: 'active',
},
],
data,
},
];
try {
const result = await transact(actions);
console.log('Транзакция powerup выполнена:', result);
} catch (error) {
console.error('Ошибка при выполнении транзакции powerup:', error);
throw error;
}
}
async function getSoviet(coopname) {
const api = await getApi();
@@ -11,6 +11,10 @@ export const connectGenerator = async () => {
await generator.connect(config.mongoose.url);
};
export const diconnectGenerator = async () => {
await generator.disconnect();
};
export const generateDocument = async (options: IGenerate) => {
return await generator.generate(options);
};
@@ -30,7 +34,7 @@ export async function buildComplexDocument(
{
const document = await generator.getDocument({ hash: raw_document.document.hash });
if (document)
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);
@@ -78,7 +82,7 @@ export async function buildComplexDocument(
const document = await generator.getDocument({ hash: decision_action?.data?.document?.hash });
if (document)
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);
@@ -102,7 +106,23 @@ export async function buildComplexDocument(
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
@@ -112,9 +132,8 @@ export const queryDocuments = async (
{
filter: JSON.stringify({
account: SovietContract.contractName.production,
name: SovietContract.Actions.Registry.NewResolved.actionName,
receiver: process.env.COOPNAME,
...filter,
name: type, //< newresolved | newsubmitted
...toDotNotation(filter),
}),
page,
limit,
@@ -130,7 +149,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;
+2 -1
View File
@@ -11,5 +11,6 @@ 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 paymentService from './payment.service';
export * as settingService from './setting.service';
export * as pluginService from './plugin.service';
@@ -69,8 +69,8 @@ export async function createOrder(
amount,
symbol,
type === 'deposit'
? `Добровольный паевый взнос №${db_order.order_num}`
: `Добровольный вступительный взнос №${db_order.order_num}`,
? `Паевый взнос №${db_order.order_num}`
: `Вступительный и минимальный паевый взносы${secret.slice(0, 10)}`,
db_order.order_num as number,
secret
);
@@ -168,7 +168,7 @@ export async function setStatus(id: string, status: string) {
if (status == 'refunded') {
await blockchainService.cancelOrder({
coopname: config.coopname,
admin: config.service_username,
admin: config.coopname,
deposit_id: order.order_num as number,
memo: '',
});
@@ -8,7 +8,7 @@ import { redisSubscriber } from './redis.service';
import config from '../config/config';
export class PaymentEffectProcessor {
static async processPaymentEffect(id: string, status: string) {
public async processPaymentEffect(id: string, status: string) {
const order = await Order.findById(id);
if (!order) {
@@ -18,7 +18,7 @@ export class PaymentEffectProcessor {
switch (status) {
case 'paid':
if (order.status != 'completed') await PaymentEffectProcessor.process(order);
if (order.status != 'completed') await this.process(order);
break;
case 'failed':
@@ -31,7 +31,7 @@ export class PaymentEffectProcessor {
}
}
private static async process(order: IOrder) {
private async process(order: IOrder) {
logger.info(`Processing blockchain data for order ${order.id}`, { source: 'process' });
try {
@@ -62,15 +62,17 @@ export class PaymentEffectProcessor {
}
}
redisSubscriber.subscribe(`${config.coopname}:orderStatusUpdate`);
export const init = () => {
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);
redisSubscriber.on('message', async (channel, message) => {
if (channel === `${config.coopname}:orderStatusUpdate`) {
try {
const { id, status } = JSON.parse(message);
await new PaymentEffectProcessor().processPaymentEffect(id, status);
} catch (error) {
logger.error('Error processing Redis message:', error);
}
}
}
});
});
};
@@ -78,7 +78,7 @@ class SberbankPollingProvider implements PollingProvider {
cooperative?.bank_account.details.corr
}|Sum=${parseInt(amount)}00|Purpose=${description}. Без НДС.|PayeeINN=${cooperative?.details.inn}|KPP=${
cooperative?.details.kpp
};`;
}`;
const result: PaymentDetails = {
data: invoice,
@@ -0,0 +1,71 @@
import { PluginConfig } from '../models/pluginConfig.model';
import Joi from 'joi';
import { PluginRegistry } from '../plugins';
import parse from 'joi-to-json';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
// Динамическое создание объектов и сбор схем
const getPluginSchemasInternal = () => {
const pluginSchemas = Object.entries(PluginRegistry).reduce((schemas, [key, PluginModule]) => {
const pluginInstance = new PluginModule.Plugin();
schemas[pluginInstance.name] = pluginInstance.pluginSchema;
return schemas;
}, {} as { [key: string]: Joi.ObjectSchema });
return pluginSchemas;
};
// Функция для обновления конфигурации плагина
export const updatePluginConfig = async (pluginName: string, enabled: boolean, config: any) => {
const schema = getPluginSchemasInternal()[pluginName];
if (!schema) {
throw new ApiError(httpStatus.BAD_REQUEST, `Схема для плагина ${pluginName} не найдена.`);
}
// Валидация переданных параметров
const { error } = schema.validate(config);
if (error) {
throw new ApiError(httpStatus.BAD_REQUEST, `Ошибка валидации конфигурации плагина ${pluginName}: ${error.message}`);
}
// Обновление конфигурации в базе данных
const pluginConfig = await PluginConfig.findOneAndUpdate(
{ name: pluginName },
{ enabled, config },
{ new: true, upsert: true }
);
return pluginConfig;
};
// Метод для извлечения Joi схемы
export const getPluginSchema = (pluginName: string) => {
const pluginInstance = getPluginSchemasInternal()[pluginName];
if (!pluginInstance) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Плагин не найден');
}
// Преобразование Joi схемы в JSON
try {
const jsonSchema = parse(pluginInstance);
return jsonSchema;
} catch (e: any) {
console.log('on error');
throw new ApiError(httpStatus.BAD_REQUEST, e.message);
}
};
// Метод для извлечения Joi схемы
export const getPluginConfig = async (pluginName: string): Promise<any> => {
const config = await PluginConfig.findOne({ name: pluginName });
return config;
};
export const queryPlugins = async (filter, options) => {
const plugins = await PluginConfig.paginate(filter, options);
return plugins;
};
@@ -9,12 +9,28 @@ import { generator } from './document.service';
import { blockchainService, emailService, tokenService, userService } from '.';
import { IUser, userStatus } from '../types/user.types';
import axios from 'axios';
import { getBlockchainInfo } from './blockchain.service';
import { getBlockchainAccount, getBlockchainInfo, hasActiveKey } from './blockchain.service';
import { RegistratorContract, type Cooperative } from 'cooptypes';
import type { ISetVars } from '../types/auto-generated/system.validation';
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> => {
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,9 +41,16 @@ 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) {
@@ -35,7 +58,7 @@ export const install = async (soviet: IInstall): Promise<void> => {
sovietExt.push({ ...member, username });
const addUser: RegistratorContract.Actions.AddUser.IAddUser = {
registrator: config.service_username,
registrator: config.coopname,
coopname: config.coopname,
referer: '',
username,
@@ -83,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: 'Совет',
@@ -105,33 +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;
};
@@ -1,9 +1,12 @@
import { SovietContract } from 'cooptypes';
import { userService } from '.';
import { User } from '../models';
import config from '../config/config';
export const updateBoard = async (action: SovietContract.Actions.Boards.UpdateBoard.IUpdateBoard): Promise<void> => {
//сброс всех прав
if (action.coopname != config.coopname) return;
const users = await User.find({ role: { $in: ['member', 'chairman'] } }).exec();
for (const user of users) {
user.role = 'user';
@@ -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 {
@@ -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';
};
}
@@ -10,5 +10,6 @@ export * from './method.validation';
export * from './notify.validation';
export * from './order.validation';
export * from './participant.validation';
export * from './plugin.validation';
export * from './system.validation';
export * from './user.validation';
@@ -0,0 +1,50 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface IGetPluginConfig {
name: string;
}
export interface IGetPluginList {
limit?: number;
name: string;
page?: number;
}
export interface IGetPluginSchema {
name: string;
}
export interface ISetPlugin {
config?: object;
enabled: boolean;
name: string;
}
export interface RGetPluginConfig {
query: {
name: string;
};
}
export interface RGetPluginList {
query: {
name: string;
};
}
export interface RGetPluginSchema {
query: {
name: string;
};
}
export interface RSetPlugin {
body: {
config?: object;
enabled: boolean;
name: string;
};
}
@@ -3,25 +3,102 @@
* Do not modify this file manually
*/
export type IInstall = ({
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
passport?: {
code: string;
issued_at: string;
issued_by: string;
number: number;
series: number;
export interface IInit {
organization_data: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
kpp: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
role: 'chairman' | 'member';
})[];
vars: {
/**
* Unknown Property
*/
[x: string]: unknown;
confidential_email: string;
confidential_link: string;
contact_email: string;
coopname: string;
full_abbr: string;
full_abbr_dative: string;
full_abbr_genitive: string;
name: string;
participant_application: {
protocol_day_month_year: string;
protocol_number: string;
};
passport_request: 'yes' | 'no';
privacy_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
short_abbr: string;
signature_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
user_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
wallet_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
website: string;
};
}
export interface IInstall {
soviet: ({
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
passport?: {
code: string;
issued_at: string;
issued_by: string;
number: number;
series: number;
};
phone: string;
};
role: 'chairman' | 'member';
})[];
wif: string;
}
export interface ISetVars {
/**
@@ -61,26 +138,111 @@ export interface ISetVars {
website: string;
}
export interface RInstall {
body: ({
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
passport?: {
code: string;
issued_at: string;
issued_by: string;
number: number;
series: number;
export interface ISetWif {
permission: 'active';
username: string;
wif: string;
}
export interface RInit {
body: {
organization_data: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
kpp: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
role: 'chairman' | 'member';
})[];
vars: {
/**
* Unknown Property
*/
[x: string]: unknown;
confidential_email: string;
confidential_link: string;
contact_email: string;
coopname: string;
full_abbr: string;
full_abbr_dative: string;
full_abbr_genitive: string;
name: string;
participant_application: {
protocol_day_month_year: string;
protocol_number: string;
};
passport_request: 'yes' | 'no';
privacy_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
short_abbr: string;
signature_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
user_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
wallet_agreement: {
protocol_day_month_year: string;
protocol_number: string;
};
website: string;
};
};
}
export interface RInstall {
body: {
soviet: ({
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
passport?: {
code: string;
issued_at: string;
issued_by: string;
number: number;
series: number;
};
phone: string;
};
role: 'chairman' | 'member';
})[];
wif: string;
};
}
export interface RSetVars {
@@ -123,6 +285,14 @@ export interface RSetVars {
};
}
export interface RSetWif {
body: {
permission: 'active';
username: string;
wif: string;
};
}
export interface RUpdateSettings {
body: {
settings: object;
@@ -0,0 +1,18 @@
import type { Document } from 'mongoose';
export interface IPlugin {
name: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialize(config: any): Promise<void>;
}
export interface IPluginSchema<T> extends Document {
name: string;
enabled: boolean;
config: T;
}
export interface IPluginLog<T> extends Document {
name: string;
log: T;
}
+27
View File
@@ -0,0 +1,27 @@
import * as crypto from 'crypto'; // Импорт crypto из Node.js
import config from '../config/config';
const ALGORITHM = 'aes-256-cbc'; // Алгоритм шифрования
const SERVER_SECRET = config.server_secret; // Получаем секретный ключ из переменной окружения
const IV_LENGTH = 16; // Длина вектора инициализации для AES
// Преобразуем ключ в 32 байта с использованием SHA-256
const getKey = (secret: string) => crypto.createHash('sha256').update(secret).digest();
// Функция для шифрования
export function encrypt(text: string): string {
const iv = crypto.randomBytes(IV_LENGTH); // Генерация случайного вектора инициализации
const cipher = crypto.createCipheriv(ALGORITHM, getKey(SERVER_SECRET), iv); // Используем ключ 32 байта
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted; // Сохраняем вектор инициализации вместе с зашифрованным текстом
}
// Функция для расшифровки
export function decrypt(text: string): string {
const [ivHex, encrypted] = text.split(':');
const iv = Buffer.from(ivHex, 'hex'); // Преобразование вектора инициализации из строки обратно в Buffer
const decipher = crypto.createDecipheriv(ALGORITHM, getKey(SERVER_SECRET), iv); // Используем тот же ключ 32 байта
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
@@ -19,8 +19,11 @@ export const RGenerate = Joi.object({
export const IGetDocuments = Joi.object({
filter: Joi.object({
receiver: Joi.string(),
}),
receiver: Joi.string().required(),
})
.required()
.unknown(true),
type: Joi.string().optional().valid('newsubmitted', 'newresolved').default('newresolved'),
sortBy: Joi.string(),
limit: Joi.number().integer(),
page: Joi.number().integer(),
@@ -8,3 +8,4 @@ export * as notifyValidation from './notify.validation';
export * as systemValidation from './system.validation';
export * as participantValidation from './participant.validation';
export * as methodValidation from './method.validation';
export * as pluginValidation from './plugin.validation';
@@ -8,7 +8,7 @@ const RussianBankDetailsSchema = Joi.object({
export const IBankAccount = Joi.object({
currency: Joi.string().required().valid('RUB', 'Other'),
card_number: Joi.string().default(''),
card_number: Joi.string().default('').optional().allow(''),
bank_name: Joi.string().required(),
account_number: Joi.string().required(),
details: RussianBankDetailsSchema.required(),
@@ -0,0 +1,37 @@
import * as Joi from 'joi';
export const ISetPlugin = Joi.object({
name: Joi.string().required(),
enabled: Joi.boolean().required(),
config: Joi.object().optional(),
});
export const RSetPlugin = Joi.object({
body: ISetPlugin.required(),
});
export const IGetPluginSchema = Joi.object({
name: Joi.string().required(),
});
export const RGetPluginSchema = Joi.object({
query: IGetPluginSchema.required(),
});
export const IGetPluginConfig = Joi.object({
name: Joi.string().required(),
});
export const RGetPluginConfig = Joi.object({
query: IGetPluginConfig.required(),
});
export const IGetPluginList = Joi.object({
name: Joi.string().required(),
limit: Joi.number().integer(),
page: Joi.number().integer(),
});
export const RGetPluginList = Joi.object({
query: IGetPluginConfig,
});
@@ -1,17 +1,32 @@
import * as Joi from 'joi';
import { IIndividualData } from './user.validation';
import { IIndividualData, IOrganizationData } from './user.validation';
export const IInstall = Joi.array().items(
Joi.object({
role: Joi.string().required().valid('chairman', 'member'),
individual_data: IIndividualData.required(),
})
);
export const IInstall = Joi.object({
wif: Joi.string().required(), // поле wif - обязательная строка
soviet: Joi.array()
.items(
Joi.object({
role: Joi.string().required().valid('chairman', 'member'), // поле role - обязательная строка с валидными значениями
individual_data: IIndividualData.required(), // поле individual_data - обязательное
})
)
.required(), // массив soviet - обязателен
});
export const RInstall = Joi.object({
body: IInstall.required(),
});
export const ISetWif = Joi.object({
username: Joi.string().required(),
permission: Joi.string().required().valid('active'),
wif: Joi.string().required(),
});
export const RSetWif = Joi.object({
body: ISetWif.required(),
});
export const ISetVars = Joi.object({
coopname: Joi.string().required(),
full_abbr: Joi.string().required(),
@@ -55,3 +70,12 @@ export const RUpdateSettings = Joi.object({
settings: Joi.object().required(),
}).required(),
});
export const IInit = Joi.object({
organization_data: IOrganizationData.required(),
vars: ISetVars.required(),
});
export const RInit = Joi.object({
body: IInit.required(),
});
@@ -0,0 +1,26 @@
import { expose } from 'threads/worker';
import mongoose from 'mongoose';
import { PluginRegistry } from '../plugins';
import rootConfig from '../config/config';
const initializePlugin = async (pluginName: string) => {
try {
// Устанавливаем соединение с базой данных
await mongoose.connect(rootConfig.mongoose.url);
const plugin = new PluginRegistry[pluginName].Plugin();
await plugin.initialize();
return `Плагин ${pluginName} инициализирован успешно.`;
} catch (error: any) {
throw new Error(`Ошибка при инициализации плагина ${pluginName}: ${error.message}`);
} finally {
// Закрываем соединение с базой данных
await mongoose.disconnect();
}
};
// Экспортируем функцию для вызова из основного потока
expose({
initializePlugin,
});
+439
View File
@@ -0,0 +1,439 @@
{
"openapi": "3.0.0",
"components": {
"examples": {},
"headers": {},
"parameters": {},
"requestBodies": {},
"responses": {},
"schemas": {
"RCreateUser": {
"properties": {
"body": {
"properties": {
"username": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"individual",
"entrepreneur",
"organization"
]
},
"role": {
"type": "string",
"enum": [
"user"
],
"nullable": false
},
"referer": {
"type": "string"
},
"public_key": {
"type": "string"
},
"organization_data": {
"properties": {
"type": {
"type": "string",
"enum": [
"coop",
"ooo",
"oao",
"zao",
"pao",
"ao"
]
},
"short_name": {
"type": "string"
},
"represented_by": {
"properties": {
"position": {
"type": "string"
},
"middle_name": {
"type": "string"
},
"last_name": {
"type": "string"
},
"first_name": {
"type": "string"
},
"based_on": {
"type": "string"
}
},
"required": [
"position",
"middle_name",
"last_name",
"first_name",
"based_on"
],
"type": "object"
},
"phone": {
"type": "string"
},
"is_cooperative": {
"type": "boolean"
},
"full_name": {
"type": "string"
},
"full_address": {
"type": "string"
},
"email": {
"type": "string"
},
"details": {
"properties": {
"ogrn": {
"type": "string"
},
"kpp": {
"type": "string"
},
"inn": {
"type": "string"
}
},
"required": [
"ogrn",
"kpp",
"inn"
],
"type": "object"
},
"country": {
"type": "string",
"enum": [
"Russia",
"Other"
]
},
"city": {
"type": "string"
},
"bank_account": {
"properties": {
"details": {
"properties": {
"kpp": {
"type": "string"
},
"corr": {
"type": "string"
},
"bik": {
"type": "string"
}
},
"required": [
"kpp",
"corr",
"bik"
],
"type": "object"
},
"currency": {
"type": "string",
"enum": [
"RUB",
"Other"
]
},
"card_number": {
"type": "string"
},
"bank_name": {
"type": "string"
},
"account_number": {
"type": "string"
}
},
"required": [
"details",
"currency",
"bank_name",
"account_number"
],
"type": "object"
}
},
"required": [
"type",
"short_name",
"represented_by",
"phone",
"is_cooperative",
"full_name",
"full_address",
"email",
"details",
"country",
"city",
"bank_account"
],
"type": "object"
},
"individual_data": {
"properties": {
"phone": {
"type": "string"
},
"passport": {
"properties": {
"series": {
"type": "number",
"format": "double"
},
"number": {
"type": "number",
"format": "double"
},
"issued_by": {
"type": "string"
},
"issued_at": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"series",
"number",
"issued_by",
"issued_at",
"code"
],
"type": "object"
},
"middle_name": {
"type": "string"
},
"last_name": {
"type": "string"
},
"full_address": {
"type": "string"
},
"first_name": {
"type": "string"
},
"email": {
"type": "string"
},
"birthdate": {
"type": "string"
}
},
"required": [
"phone",
"middle_name",
"last_name",
"full_address",
"first_name",
"email",
"birthdate"
],
"type": "object"
},
"entrepreneur_data": {
"properties": {
"phone": {
"type": "string"
},
"middle_name": {
"type": "string"
},
"last_name": {
"type": "string"
},
"full_address": {
"type": "string"
},
"first_name": {
"type": "string"
},
"email": {
"type": "string"
},
"details": {
"properties": {
"ogrn": {
"type": "string"
},
"inn": {
"type": "string"
}
},
"required": [
"ogrn",
"inn"
],
"type": "object"
},
"country": {
"type": "string",
"enum": [
"Russia",
"Other"
]
},
"city": {
"type": "string"
},
"birthdate": {
"type": "string"
},
"bank_account": {
"properties": {
"details": {
"properties": {
"kpp": {
"type": "string"
},
"corr": {
"type": "string"
},
"bik": {
"type": "string"
}
},
"required": [
"kpp",
"corr",
"bik"
],
"type": "object"
},
"currency": {
"type": "string",
"enum": [
"RUB",
"Other"
]
},
"card_number": {
"type": "string"
},
"bank_name": {
"type": "string"
},
"account_number": {
"type": "string"
}
},
"required": [
"details",
"currency",
"bank_name",
"account_number"
],
"type": "object"
}
},
"required": [
"phone",
"middle_name",
"last_name",
"full_address",
"first_name",
"email",
"details",
"country",
"city",
"birthdate",
"bank_account"
],
"type": "object"
},
"email": {
"type": "string"
}
},
"required": [
"username",
"type",
"role",
"email"
],
"type": "object"
}
},
"required": [
"body"
],
"type": "object",
"additionalProperties": false
}
},
"securitySchemes": {}
},
"info": {
"title": "coopback",
"version": "2.0.7",
"license": {
"name": "MIT"
},
"contact": {
"name": "Alex Ant ",
"email": "dacom.dark.sun@gmail.com"
}
},
"paths": {
"/users": {
"post": {
"operationId": "CreateUser",
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"properties": {
"tokens": {},
"user": {}
},
"required": [
"tokens",
"user"
],
"type": "object"
}
}
}
}
},
"security": [],
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RCreateUser"
}
}
}
}
}
}
},
"servers": [
{
"url": "/"
}
]
}
+13
View File
@@ -0,0 +1,13 @@
{
"entryFile": "src/index.ts",
"noImplicitAdditionalProperties": "throw-on-extras",
"controllerPathGlobs": ["src/controllers/*.ts"],
"spec": {
"outputDirectory": "./swagger",
"specVersion": 3
},
"exclude": ["node_modules", "dist"],
"tsNode": true,
"ignore": ["**/dist/**/*"],
"debug": true
}
@@ -11,3 +11,4 @@ node_modules
temp
documents/*
.env
src/Services/Generator/tmp
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.3](https://github.com/copenomics/coopdoc-generator-ts/compare/v2.1.2-alpha.10...v2.1.3) (2024-10-26)
## [2.1.2](https://github.com/copenomics/coopdoc-generator-ts/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package coopdoc-generator-ts
## [2.1.2](https://github.com/copenomics/coopdoc-generator-ts/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package coopdoc-generator-ts
# [2.1.0](https://github.com/copenomics/coopdoc-generator-ts/compare/v2.0.10-alpha.3...v2.1.0) (2024-10-13)
**Note:** Version bump only for package coopdoc-generator-ts
## [2.0.8](https://github.com/copenomics/coopdoc-generator-ts/compare/v2.0.7...v2.0.8) (2024-10-09)
**Note:** Version bump only for package coopdoc-generator-ts
## [2.0.7](https://github.com/copenomics/coopdoc-generator-ts/compare/v2.0.6...v2.0.7) (2024-09-30)
**Note:** Version bump only for package coopdoc-generator-ts
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "coopdoc-generator-ts",
"type": "module",
"version": "2.0.7",
"version": "2.1.4-alpha.0",
"private": false,
"packageManager": "pnpm@9.0.6",
"description": "",
@@ -50,7 +50,7 @@
"ajv-formats": "^3.0.1",
"ajv-i18n": "^4.2.0",
"axios": "^1.7.2",
"cooptypes": "workspace:*",
"cooptypes": "2.1.2",
"dotenv": "^16.4.5",
"eosjs-ecc": "^4.0.7",
"handlebars": "^4.7.8",
@@ -61,7 +61,7 @@
"nunjucks": "^3.2.4",
"pdf-lib": "^1.17.1",
"puppeteer": "^22.8.0",
"uuid": "^10.0.0"
"uuid": "^11.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "^2.17.0",
@@ -92,5 +92,5 @@
"vite@>=5.1.0 <=5.1.6": ">=5.1.7"
}
},
"gitHead": "5ba05409c4a208ba2004c3f0d1f14e993b5f63ae"
"gitHead": "7b59ea4b96991ed06abdc92e6d7835632845e3a5"
}
@@ -1,7 +1,7 @@
import { DraftContract } from 'cooptypes'
import { WalletAgreement } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { WalletAgreement as Template } from '../templates'
@@ -11,19 +11,19 @@ export class Factory extends DocFactory<WalletAgreement.Action> {
super(storage)
}
async generateDocument(options: WalletAgreement.Action): Promise<IGeneratedDocument> {
async generateDocument(data: WalletAgreement.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<WalletAgreement.Model>
if (process.env.SOURCE === 'local') {
template = WalletAgreement.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, WalletAgreement.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, WalletAgreement.registry_id, data.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...data })
const coop = await super.getCooperative(data.coopname, data.block_num)
const vars = await super.getVars(data.coopname, data.block_num)
const combinedData: WalletAgreement.Model = {
meta,
@@ -33,8 +33,8 @@ export class Factory extends DocFactory<WalletAgreement.Action> {
await super.validate(combinedData, template.model)
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta, options?.skip_save)
return document
}
}
@@ -1,6 +1,6 @@
import { DraftContract } from 'cooptypes'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
import DataService from '../Services/Databazor/DataService'
import { ParticipantApplication } from '../templates'
@@ -12,26 +12,28 @@ export class Factory extends DocFactory<ParticipantApplication.Action> {
super(storage)
}
async generateDocument(options: ParticipantApplication.Action): Promise<IGeneratedDocument> {
async generateDocument(data: ParticipantApplication.Action, _options?: IGenerationOptions): Promise<IGeneratedDocument> {
// TODO надо использовать skip_save из options вместо skip_save из data, для этого на фронте внести соответствующие правки
let template: ITemplate<ParticipantApplication.Model>
if (process.env.SOURCE === 'local') {
template = ParticipantApplication.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, ParticipantApplication.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, ParticipantApplication.registry_id, data.block_num)
}
const user = await super.getUser(options.username, options.block_num)
const user = await super.getUser(data.username, data.block_num)
const userData = {
[user.type]: user.data,
}
const coop = await super.getCooperative(options.coopname, options.block_num)
const coop = await super.getCooperative(data.coopname, data.block_num)
let { signature, ...modifiedOptions } = options
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...modifiedOptions }) // Генерируем мета-данные
let { signature, ...modifieddata } = data
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...modifieddata }) // Генерируем мета-данные
interface SignatureData {
username: string
@@ -42,12 +44,12 @@ export class Factory extends DocFactory<ParticipantApplication.Action> {
const data_service = new DataService<SignatureData>(this.storage, 'signatures')
// пропуск сохранения необходим при вступлении для того, чтобы подготовить документ только для отображения без сохранения
if (!options.skip_save) {
if (!data.skip_save) {
// если подпись не указана, то проверяем на наличие в базе данных
if (!signature) {
if (options.block_num) {
const block_filter = options.block_num ? { block_num: { $lte: options.block_num } } : {}
const db_signature = await data_service.getOne({ username: options.username, ...block_filter }) as SignatureData
if (data.block_num) {
const block_filter = data.block_num ? { block_num: { $lte: data.block_num } } : {}
const db_signature = await data_service.getOne({ username: data.username, ...block_filter }) as SignatureData
if (db_signature)
signature = db_signature.signature
@@ -58,11 +60,11 @@ export class Factory extends DocFactory<ParticipantApplication.Action> {
else {
// если подпись указана - сохраняем в хранилище
const data_service = new DataService<SignatureData>(this.storage, 'signatures')
await data_service.save({ username: options.username, block_num: meta.block_num, signature })
await data_service.save({ username: data.username, block_num: meta.block_num, signature })
}
}
const vars = await super.getVars(options.coopname, options.block_num)
const vars = await super.getVars(data.coopname, data.block_num)
const combinedData: ParticipantApplication.Model = { ...userData, meta, coop, type: user.type, vars, signature }
@@ -73,11 +75,7 @@ export class Factory extends DocFactory<ParticipantApplication.Action> {
const translation = template.translations[meta.lang]
// генерируем документ
const document: IGeneratedDocument = await super.generatePDF(user.data, template.context, combinedData, translation, meta)
// сохраняем его в бд
if (!options.skip_save)
await super.saveDraft(document)
const document: IGeneratedDocument = await super.generatePDF(user.data, template.context, combinedData, translation, meta, data.skip_save)
return document
}
@@ -1,7 +1,7 @@
import { DraftContract } from 'cooptypes'
import { RegulationElectronicSignature } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { RegulationElectronicSignature as Template } from '../templates'
@@ -11,19 +11,19 @@ export class Factory extends DocFactory<RegulationElectronicSignature.Action> {
super(storage)
}
async generateDocument(options: RegulationElectronicSignature.Action): Promise<IGeneratedDocument> {
async generateDocument(data: RegulationElectronicSignature.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<RegulationElectronicSignature.Model>
if (process.env.SOURCE === 'local') {
template = RegulationElectronicSignature.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, RegulationElectronicSignature.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, RegulationElectronicSignature.registry_id, data.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...data })
const coop = await super.getCooperative(data.coopname, data.block_num)
const vars = await super.getVars(data.coopname, data.block_num)
const combinedData: RegulationElectronicSignature.Model = {
meta,
@@ -35,9 +35,7 @@ export class Factory extends DocFactory<RegulationElectronicSignature.Action> {
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta, options?.skip_save)
return document
}
@@ -1,7 +1,7 @@
import { DraftContract } from 'cooptypes'
import { PrivacyPolicy } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { PrivacyPolicy as Template } from '../templates'
@@ -11,19 +11,19 @@ export class Factory extends DocFactory<PrivacyPolicy.Action> {
super(storage)
}
async generateDocument(options: PrivacyPolicy.Action): Promise<IGeneratedDocument> {
async generateDocument(data: PrivacyPolicy.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<PrivacyPolicy.Model>
if (process.env.SOURCE === 'local') {
template = PrivacyPolicy.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, PrivacyPolicy.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, PrivacyPolicy.registry_id, data.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...data })
const coop = await super.getCooperative(data.coopname, data.block_num)
const vars = await super.getVars(data.coopname, data.block_num)
const combinedData: PrivacyPolicy.Model = {
meta,
@@ -35,9 +35,7 @@ export class Factory extends DocFactory<PrivacyPolicy.Action> {
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta, options?.skip_save)
return document
}
@@ -1,7 +1,7 @@
import { DraftContract } from 'cooptypes'
import { UserAgreement } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { UserAgreement as Template } from '../templates'
@@ -11,20 +11,20 @@ export class Factory extends DocFactory<UserAgreement.Action> {
super(storage)
}
async generateDocument(options: UserAgreement.Action): Promise<IGeneratedDocument> {
async generateDocument(data: UserAgreement.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<UserAgreement.Model>
if (process.env.SOURCE === 'local') {
template = UserAgreement.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, UserAgreement.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, UserAgreement.registry_id, data.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const user = await super.getUser(options.username, options.block_num)
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...data })
const coop = await super.getCooperative(data.coopname, data.block_num)
const vars = await super.getVars(data.coopname, data.block_num)
const user = await super.getUser(data.username, data.block_num)
const full_name = super.getFullName(user.data)
const combinedData: UserAgreement.Model = {
@@ -40,9 +40,7 @@ export class Factory extends DocFactory<UserAgreement.Action> {
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta, options?.skip_save)
return document
}
@@ -1,6 +1,7 @@
import { DraftContract } from 'cooptypes'
import { CoopenomicsAgreement } from '../templates'
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument, ITemplate } from '../Interfaces'
import type { IGeneratedDocument, IGenerationOptions, IMetaDocument, ITemplate } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
export { CoopenomicsAgreement as Template } from '../templates'
@@ -10,20 +11,20 @@ export class Factory extends DocFactory<CoopenomicsAgreement.Action> {
super(storage)
}
async generateDocument(options: CoopenomicsAgreement.Action): Promise<IGeneratedDocument> {
async generateDocument(data: CoopenomicsAgreement.Action, options?: IGenerationOptions): Promise<IGeneratedDocument> {
let template: ITemplate<CoopenomicsAgreement.Model>
if (process.env.SOURCE === 'local') {
template = CoopenomicsAgreement.Template
}
else {
template = await this.getTemplate(options.coopname, CoopenomicsAgreement.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, CoopenomicsAgreement.registry_id, data.block_num)
}
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...data })
const meta: IMetaDocument = await super.getMeta({ title: template.title, ...options })
const coop = await super.getCooperative(options.coopname, options.block_num)
const vars = await super.getVars(options.coopname, options.block_num)
const partner = await super.getOrganization(options.coopname, options.block_num)
const coop = await super.getCooperative(data.coopname, data.block_num)
const vars = await super.getVars(data.coopname, data.block_num)
const partner = await super.getOrganization(data.username, data.block_num)
const combinedData: CoopenomicsAgreement.Model = {
meta,
@@ -36,9 +37,7 @@ export class Factory extends DocFactory<CoopenomicsAgreement.Action> {
const translation = template.translations[meta.lang]
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
await super.saveDraft(document)
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta, options?.skip_save)
return document
}
@@ -2,6 +2,7 @@ import { type Cooperative, DraftContract } from 'cooptypes'
import { DocFactory } from '../Factory'
import type {
IGeneratedDocument,
IGenerationOptions,
IMetaDocument,
ITemplate,
} from '../Interfaces'
@@ -17,7 +18,8 @@ export class Factory extends DocFactory<DecisionOfParticipantApplication.Action>
}
async generateDocument(
options: DecisionOfParticipantApplication.Action,
data: DecisionOfParticipantApplication.Action,
options?: IGenerationOptions,
): Promise<IGeneratedDocument> {
let template: ITemplate<DecisionOfParticipantApplication.Model>
@@ -25,27 +27,27 @@ export class Factory extends DocFactory<DecisionOfParticipantApplication.Action>
template = DecisionOfParticipantApplication.Template
}
else {
template = await this.getTemplate(DraftContract.contractName.production, DecisionOfParticipantApplication.registry_id, options.block_num)
template = await this.getTemplate(DraftContract.contractName.production, DecisionOfParticipantApplication.registry_id, data.block_num)
}
const user = await super.getUser(options.username, options.block_num)
const user = await super.getUser(data.username, data.block_num)
const userData = {
[user.type]: user.data,
}
const coop = await super.getCooperative(options.coopname, options.block_num)
const coop = await super.getCooperative(data.coopname, data.block_num)
// TODO необходимо строго типизировать мета-данные документов друг под друга!
const meta: IMetaDocument = await super.getMeta({
title: template.title,
...options,
...data,
}) // Генерируем мета-данные
const decision: Cooperative.Document.IDecisionData = await super.getDecision(
coop,
options.coopname,
options.decision_id,
data.coopname,
data.decision_id,
meta.created_at,
)
@@ -70,11 +72,9 @@ export class Factory extends DocFactory<DecisionOfParticipantApplication.Action>
combinedData,
translation,
meta,
options?.skip_save,
)
// сохраняем его в бд
await super.saveDraft(document)
// возвращаем
return document
}
@@ -5,7 +5,7 @@ import { DraftContract, SovietContract } from 'cooptypes'
import type { ICombinedData, IGeneratedDocument, IMetaDocument, IMetaDocumentPartial, ITemplate, ITranslations, externalDataTypes } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
import { type IVars, Individual, Organization, Vars } from '../Models'
import type { IGenerate } from '../Interfaces/Documents'
import type { IGenerate, IGenerationOptions } from '../Interfaces/Documents'
import { PDFService } from '../Services/Generator'
import packageJson from '../../package.json'
import { Validator } from '../Services/Validator'
@@ -20,7 +20,7 @@ import type { IOrganizationData } from '..'
const packageVersion = packageJson.version
export abstract class DocFactory<T extends IGenerate> {
abstract generateDocument(options: T): Promise<IGeneratedDocument>
abstract generateDocument(data: T, options?: IGenerationOptions): Promise<IGeneratedDocument>
public storage: MongoDBConnector
@@ -216,6 +216,7 @@ export abstract class DocFactory<T extends IGenerate> {
vars: ICombinedData,
translation: ITranslations,
meta: IMetaDocument,
skip_save: boolean = false,
): Promise<IGeneratedDocument> {
const pdfService = new PDFService()
const document: IGeneratedDocument = await pdfService.generateDocument(context, vars, translation, meta)
@@ -223,6 +224,9 @@ export abstract class DocFactory<T extends IGenerate> {
document.full_title = `${document.meta.title} - ${full_name} - ${document.meta.created_at}.pdf`
if (!skip_save)
await this.saveDraft(document)
return document
}
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -48,3 +48,7 @@ export interface IMetaDocumentPartial extends Partial<IMetaDocument> {
}
export type IGenerate = Cooperative.Document.IGenerate
export interface IGenerationOptions {
skip_save: boolean
}
@@ -36,9 +36,10 @@ export class Cooperative {
const cooperative_response = await getFetch(`${getEnvVar('SIMPLE_EXPLORER_API')}/get-tables`, new URLSearchParams({
filter: JSON.stringify({
code: RegistratorContract.contractName.production,
scope: RegistratorContract.contractName.production,
table: RegistratorContract.Tables.Cooperatives.tableName,
'code': RegistratorContract.contractName.production,
'scope': RegistratorContract.contractName.production,
'table': RegistratorContract.Tables.Cooperatives.tableName,
'value.username': username,
}),
}))
@@ -10,9 +10,9 @@ class DataService<T extends IDocument> {
private collection: Collection<T>
private state: Collection
constructor(private dbConnector: MongoDBConnector, collectionName: string) {
this.collection = this.dbConnector.getCollection<T>(collectionName)
this.state = this.dbConnector.getCollection('sync')
constructor(dbConnector: MongoDBConnector, collectionName: string) {
this.collection = dbConnector.getCollection<T>(collectionName)
this.state = dbConnector.getCollection('sync')
}
async getOne(filter: Filter<T>): Promise<T | null> {
@@ -52,6 +52,7 @@ export class MongoDBConnector {
if (!this.documents)
throw new Error('Database not connected')
// await this.documents.insertOne({ hash: document.hash, document })
await this.documents.updateOne({ hash: document.hash }, { $set: { ...document } }, { upsert: true })
}
}
@@ -1,9 +1,16 @@
import fs from 'node:fs'
import path from 'node:path'
import { exec } from 'node:child_process'
import puppeteer from 'puppeteer'
import { PDFDocument } from 'pdf-lib'
import moment from 'moment-timezone'
import { v4 as uuidv4 } from 'uuid'
import type { IGeneratedDocument, IMetaDocument, ITranslations } from '../../Interfaces'
import { TemplateEngine } from '../Templator'
import { calculateSha256 } from '../../Utils/calculateSHA'
import { ArialBase64 } from '../../Fonts/arial'
const weasyPrintVersion = '62.3'
export interface IPDFService {
generateDocument: (
@@ -39,35 +46,55 @@ export class PDFService implements IPDFService {
}
private static async generatePDFBuffer(htmlContent: string): Promise<Uint8Array> {
const browser = await puppeteer.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-extensions',
'--disable-software-rasterizer',
],
timeout: 120000, // увеличиваем общее время ожидания до 2 минут
protocolTimeout: 120000, // увеличиваем время ожидания протокола до 2 минут
const tempId = uuidv4() // Генерируем уникальный ID для временных файлов
const tempDir = path.join(__dirname, 'tmp')
// Создаем папку tmp, если её нет
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir)
}
const tempHtmlPath = path.join(tempDir, `${tempId}.html`)
const tempPdfPath = path.join(tempDir, `${tempId}.pdf`)
// CSS с указанием кодировки и шрифтом для кириллицы
const fontStyle = `
<style>
@font-face {
font-family: 'Arial';
src: url(data:font/ttf;base64,${ArialBase64}) format('truetype');
}
* {
font-family: 'Arial', sans-serif;
}
</style>
<meta charset="UTF-8">
`
// Объединяем CSS и HTML-контент
const htmlWithFontStyle = fontStyle + htmlContent
// Сохраняем HTML-контент во временный файл
fs.writeFileSync(tempHtmlPath, htmlWithFontStyle, { encoding: 'utf8' })
return new Promise((resolve, reject) => {
// Запускаем WeasyPrint для конвертации HTML в PDF
exec(`SOURCE_DATE_EPOCH=0 weasyprint ${tempHtmlPath} ${tempPdfPath}`, (error) => {
if (error) {
// Удаляем временные файлы при ошибке
fs.unlinkSync(tempHtmlPath)
reject(error)
}
else {
// Читаем PDF-файл и возвращаем его как Uint8Array
const pdfBuffer = fs.readFileSync(tempPdfPath)
// Удаляем временные файлы после завершения
fs.unlinkSync(tempHtmlPath)
fs.unlinkSync(tempPdfPath)
resolve(new Uint8Array(pdfBuffer))
}
})
})
const page = await browser.newPage()
// const data = await inlineCss(htmlContent, { url: 'about:blank' })
// const template = hb.compile(htmlContent, { strict: true })
// const result = template({})
await page.setContent(htmlContent, {
waitUntil: 'networkidle0',
})
// Добавьте задержку перед созданием PDF
await new Promise(resolve => setTimeout(resolve, 1000))
const pdfBuffer = await page.pdf({ format: 'A4' })
await browser.close()
return new Uint8Array(pdfBuffer)
}
private static async updateMetadata(pdfBuffer: ArrayBuffer, meta: IMetaDocument): Promise<Uint8Array> {
@@ -76,11 +103,12 @@ export class PDFService implements IPDFService {
const pdfDoc = await PDFDocument.load(pdfBuffer)
pdfDoc.setTitle(meta.title)
pdfDoc.setLanguage(meta.lang)
pdfDoc.setProducer(meta.version)
pdfDoc.setSubject(`Шаблона документа по реестру №${meta.registry_id}`)
pdfDoc.setCreator(meta.generator)
pdfDoc.setCreator(`${meta.generator}-${meta.version}`)
pdfDoc.setCreationDate(dateWithTimezone)
pdfDoc.setModificationDate(dateWithTimezone)
return pdfDoc.save()
pdfDoc.setProducer(`weasyprint-v${weasyPrintVersion}`)
return pdfDoc.save({ useObjectStreams: false })
}
}
+7 -7
View File
@@ -5,7 +5,7 @@ export * from './Schema'
import type { Filter, InsertOneResult, UpdateResult } from 'mongodb'
import type { Cooperative as CooperativeModel } from 'cooptypes'
import type { IFilterDocuments, IGeneratedDocument, Numbers, externalDataTypes, externalDataTypesArrays, internalFilterTypes } from './Interfaces'
import type { IGenerate } from './Interfaces/Documents'
import type { IGenerate, IGenerationOptions } from './Interfaces/Documents'
import * as Actions from './Actions'
import { MongoDBConnector } from './Services/Databazor'
@@ -30,7 +30,7 @@ export type { CooperativeData as ICooperativeData } from './Models'
export interface IGenerator {
connect: (mongoUri: string) => Promise<void>
disconnect: () => Promise<void>
generate: (options: IGenerate) => Promise<IGeneratedDocument>
generate: (data: IGenerate, options?: IGenerationOptions) => Promise<IGeneratedDocument>
getDocument: (filter: Filter<IFilterDocuments>) => Promise<IGeneratedDocument>
constructCooperative: (username: string, block_num?: number) => Promise<CooperativeData | null>
@@ -62,7 +62,7 @@ export class Generator implements IGenerator {
[Actions.RegulationElectronicSignature.Template.registry_id]: new Actions.RegulationElectronicSignature.Factory(this.storage), // 2
[Actions.PrivacyPolicy.Template.registry_id]: new Actions.PrivacyPolicy.Factory(this.storage), // 3
[Actions.UserAgreement.Template.registry_id]: new Actions.UserAgreement.Factory(this.storage), // 4
[Actions.CoopenomicsAgreement.Template.registry_id]: new Actions.UserAgreement.Factory(this.storage), // 50
[Actions.CoopenomicsAgreement.Template.registry_id]: new Actions.CoopenomicsAgreement.Factory(this.storage), // 50
[Actions.ParticipantApplication.Template.registry_id]: new Actions.ParticipantApplication.Factory(this.storage), // 100
[Actions.DecisionOfParticipantApplication.Template.registry_id]: new Actions.DecisionOfParticipantApplication.Factory(this.storage), // 501
}
@@ -75,14 +75,14 @@ export class Generator implements IGenerator {
}
// Метод генерации документа
async generate(options: IGenerate): Promise<IGeneratedDocument> {
const factory = this.factories[options.registry_id as Numbers] // Get the factory
async generate(data: IGenerate, options?: IGenerationOptions): Promise<IGeneratedDocument> {
const factory = this.factories[data.registry_id as Numbers] // Get the factory
if (!factory)
throw new Error(`Фабрика для документа #${options.registry_id} не найдена.`)
throw new Error(`Фабрика для документа #${data.registry_id} не найдена.`)
// синтезируем документ
return await factory.generateDocument(options)
return await factory.generateDocument(data, options)
}
async getDocument(filter: Filter<IFilterDocuments>): Promise<IGeneratedDocument> {
@@ -1,3 +1,4 @@
import path from 'node:path'
import { beforeAll, beforeEach, describe, expect, it } from 'vitest'
import type { RegistratorContract, SovietContract } from 'cooptypes'
import { Generator } from '../src'
@@ -12,14 +13,13 @@ const coopname = 'voskhod'
import type { ExternalEntrepreneurData, ExternalIndividualData, ExternalOrganizationData, IVars } from '../src/Models'
import type { PaymentData } from '../src/Models/PaymentMethod'
import { CoopenomicsAgreement, PrivacyPolicy, Registry, RegulationElectronicSignature, UserAgreement, WalletAgreement } from '../src/templates'
import type { CoopenomicsAgreement } from '../src/templates'
import { PrivacyPolicy, Registry, RegulationElectronicSignature, UserAgreement, WalletAgreement } from '../src/templates'
import { signatureExample } from './signatureExample'
// eslint-disable-next-line ts/no-require-imports
const fs = require('node:fs').promises
import path from 'node:path'
async function deleteAllFiles(folderPath: string) {
try {
const absolutePath = path.resolve(folderPath)
@@ -551,49 +551,6 @@ describe('тест генератора документов', async () => {
expect(hash1).toEqual(hash2)
})
it('генерируем согласие с пользовательским соглашением платформы "Кооперативная Экономика"', async () => {
const params: CoopenomicsAgreement.Action = {
coopname: 'voskhod',
username: 'ant',
lang: 'ru',
registry_id: CoopenomicsAgreement.registry_id,
protocol_number: '01-01-2024',
protocol_day_month_year: '1 января 2024 г.',
}
const document: IGeneratedDocument = await generator.generate(params)
const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(document.binary, filename1)
const regenerated_document: IGeneratedDocument = await generator.generate({
...document.meta,
})
const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(regenerated_document.binary, filename2)
expect(document.meta).toEqual(regenerated_document.meta)
expect(document.hash).toEqual(regenerated_document.hash)
const document_from_disk1 = await loadBufferFromDisk(filename1)
const document_from_disk2 = await loadBufferFromDisk(filename2)
const hash1 = calculateSha256(document_from_disk1)
const hash2 = calculateSha256(document_from_disk2)
const getted_document = await generator.getDocument({ hash: regenerated_document.hash })
expect(getted_document).toBeDefined()
expect(getted_document.hash).toEqual(document.hash)
console.log('hash1: ', hash1)
console.log('hash2: ', hash2)
console.log(document.meta)
expect(hash1).toEqual(hash2)
})
it('генерируем заявление на вступление физического лица', async () => {
const document: IGeneratedDocument = await generator.generate({
registry_id: 100,
@@ -823,4 +780,137 @@ describe('тест генератора документов', async () => {
const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
await saveBufferToDisk(decision_document.binary, filename3)
})
it('#1', async () => {
const voskhodData: ExternalOrganizationData = {
username: 'voskhod',
type: 'coop',
is_cooperative: true,
short_name: '"ПК Восход"',
full_name: 'Потребительский Кооператив "ВОСХОД"',
represented_by: {
first_name: 'Алексей',
last_name: 'Муравьев',
middle_name: 'Николаевич',
position: 'Председатель',
based_on: 'Решение общего собрания №1',
},
country: 'Russia',
city: 'Москва',
full_address: '117593 г. Москва, муниципальный округ Ясенево, проезд Соловьиный, дом 1, помещение 1/1',
email: 'copenomics@yandex.ru',
phone: '+71234567890',
details: {
inn: '9728130611',
ogrn: '1247700283346',
kpp: '772801001',
},
bank_account: {
account_number: '40703810038000110117',
currency: 'RUB',
card_number: '',
bank_name: 'ПАО Сбербанк',
details: {
bik: '044525225',
corr: '30101810400000000225',
kpp: '773643001',
},
},
}
await generator.save('organization', voskhodData)
const vars: IVars = {
coopname: 'voskhod',
full_abbr: 'потребительский кооператив',
full_abbr_genitive: 'потребительского кооператива',
full_abbr_dative: 'потребительскому кооперативу',
short_abbr: 'ПК',
website: 'цифровой-кооператив.рф',
name: 'Восход',
confidential_link: 'coopenomics.world/privacy',
confidential_email: 'privacy@coopenomics.world',
contact_email: 'contact@coopenomics.world',
passport_request: 'no',
wallet_agreement: {
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
signature_agreement: {
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
privacy_agreement: {
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
user_agreement: {
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
participant_application: {
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
coopenomics_agreement: {
protocol_number: '10-04-2024',
protocol_day_month_year: '10 апреля 2024 г.',
},
}
await generator.save('vars', vars)
const coopname = 'test'
const organizationData: ExternalOrganizationData = {
username: coopname,
type: 'coop',
is_cooperative: true,
short_name: 'Ромашка',
full_name: 'Ромашка',
represented_by: {
first_name: 'Иван',
last_name: 'Иванов',
middle_name: 'Иванович',
position: 'Директор',
based_on: 'решения собрания учредителей №22',
},
country: 'Russia',
city: 'Moscow',
full_address: 'г. Москва, ул. Арбат д. 22, офис 306',
email: 'contact@exampleorg.com',
phone: '+71234567890',
details: {
kpp: '123456789',
inn: '0987654321',
ogrn: '0987654321098',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '0987654321098765',
bank_name: 'ПАО СБЕРБАНК',
details: {
bik: '098765432',
corr: '30101810400000000225',
kpp: '098765432',
},
},
}
await generator.save('organization', organizationData)
const act: CoopenomicsAgreement.Action = {
registry_id: 50,
coopname: 'voskhod',
username: 'test',
lang: 'ru',
}
const document: IGeneratedDocument = await generator.generate(act)
const filename1 = `Соглашение_о_подключении_${coopname}.pdf`
await saveBufferToDisk(document.binary, filename1)
})
})
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.1.3](https://github.com/coopenomics/cooptypes/compare/v2.1.2-alpha.10...v2.1.3) (2024-10-26)
## [2.1.2](https://github.com/coopenomics/cooptypes/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package cooptypes
## [2.1.2](https://github.com/coopenomics/cooptypes/compare/v2.1.1...v2.1.2) (2024-10-19)
**Note:** Version bump only for package cooptypes
# [2.1.0](https://github.com/coopenomics/cooptypes/compare/v2.0.10-alpha.3...v2.1.0) (2024-10-13)
**Note:** Version bump only for package cooptypes
## [2.0.8](https://github.com/coopenomics/cooptypes/compare/v2.0.7...v2.0.8) (2024-10-09)
**Note:** Version bump only for package cooptypes
## [2.0.2](https://github.com/coopenomics/cooptypes/compare/v2.0.2-alpha.1...v2.0.2) (2024-09-29)
**Note:** Version bump only for package cooptypes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "cooptypes",
"type": "module",
"version": "2.0.2",
"version": "2.1.4-alpha.0",
"description": "_description_",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
@@ -65,5 +65,5 @@
"vite": "^5.2.11",
"vitest": "^1.5.3"
},
"gitHead": "5ba05409c4a208ba2004c3f0d1f14e993b5f63ae"
"gitHead": "7b59ea4b96991ed06abdc92e6d7835632845e3a5"
}
@@ -6,7 +6,6 @@ export const authorizations = [{ permissions: [Permissions.active], actor: Contr
/**
* Имя действия
*/
export const actionName = 'init'
/**
* @interface
@@ -1,18 +0,0 @@
import * as Permissions from '../../../common/permissions'
import * as ContractNames from '../../../common/names'
import type * as Registrator from '../../../interfaces/registrator'
/**
* Требуется подпись контракта {@link ContractNames._soviet | совета}, которая поставляется автоматически в момент исполнения решения совета о приёме нового пайщика.
*/
export const authorizations = [{ permissions: [Permissions.active], actor: ContractNames._soviet }] as const
/**
* Имя действия
*/
export const actionName = 'confirmreg'
/**
* @interface
*/
export type IConfirmJoin = Registrator.IConfirmreg
@@ -38,12 +38,6 @@ export * as ChangeKey from './changeKey'
*/
export * as JoinCooperative from './joinCooperative'
/**
* Действие, которое вызывается контрактом _soviet для подтверждения заявки на вступление в кооператив после принятия советом решения.
* @private
*/
export * as ConfirmJoin from './confirmJoin'
/**
* Действие, которые вызывается системным контрактом для инициализации.
* @private
@@ -121,3 +121,7 @@ export interface IDecisionData {
votes_abstained: number
voters_percent: number
}
export interface IGenerationOptions {
skip_save: boolean
}
@@ -17,7 +17,7 @@ export interface Model {
export const title = 'Согласие с условиями ЦПП «ЦИФРОВОЙ КОШЕЛЕК»'
export const description = 'Форма присоединения к целевой потребительской программе «ЦИФРОВОЙ КОШЕЛЕК»'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.wallet_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }} </p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.wallet_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PROVISION\' %}</h1>\n{% trans \'consumer_program_title\' %}\n</div>\n<p>{% trans \'consumer_program_provision_intro\' %} {{ coop.short_name }} {% trans \'cooperative_hereto\' %}</p>\n<p>{% trans \'legal_basis\' %}</p>\n<p>{% trans \'program_duration\' %}</p>\n<p>{% trans \'program_provision_status\' %}</p>\n<div style="text-align: center">\n<h3>{% trans \'terms_program\' %}</h3>\n</div>\n<p>{% trans \'consumer_program_terms\' %}</p>\n<div style="text-align: center">\n<h3>{% trans \'program_goals\' %}</h3>\n</div>\n<p>{% trans \'goal_efficiency\' %}</p>\n<p>{% trans \'goal_transparency\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_participation_procedure\' %}</h3></div>\n<p>{% trans \'program_participation_terms\' %}</p>\n<p>{% trans \'offer_acceptance\' %}</p>\n<p>{% trans \'procedure_participation\' %}</p>\n<div style="text-align: center"><h3>{% trans \'user_cabinet\' %}</h3></div>\n<p>{% trans \'site_access\' %}</p>\n<p>{% trans \'unique_code\' %}</p>\n<p>{% trans \'digital_signature\' %}</p>\n<p>{% trans \'identification\' %}</p>\n<p>{% trans \'information_security\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_interaction_mechanism\' %}</h3></div>\n<p>{% trans \'program_share_contribution\' %}</p>\n<p>{% trans \'share_contribution_procedure\' %}</p>\n<p>{% trans \'fund_management\' %}</p>\n<p>{% trans \'application_to_council\' %}</p>\n<p>{% trans \'share_contribution_return\' %}</p>\n<p>{% trans \'claim_return\' %}</p>\n<p>{% trans \'payment_confirmation\' %}</p>\n<p>{% trans \'novation_agreement\' %}</p>\n<p>{% trans \'novation_agreement_terms\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_rights_obligations\' %}</h3></div>\n<p>{% trans \'participant_rights\' %}</p>\n<p>{% trans \'participant_obligations\' %}</p>\n<p>{% trans \'cooperative_obligations\' %}</p>\n<p>{% trans \'cooperative_rights\' %}</p>\n<div style="text-align: center"><h3>{% trans \'force_majeure\' %}</h3></div>\n<p>{% trans \'legal_changes\' %}</p>\n<p>{% trans \'program_modification\' %}</p>\n<p>{% trans \'force_majeure_cases\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_communication\' %}</h3></div>\n<p>{% trans \'communication_channels\' %}</p>\n<p>{% trans \'alternative_methods\' %}</p>\n<p>{% trans \'program_additional_conditions\' %}</p>\n<p>{% trans \'governing_documents\' %}</p>\n<p>{% trans \'changes_amendments\' %}</p>\n<div class="digital-signature">\n{% trans \'acceptance_agreement\' %}</div></div>\n\n<style>\n.digital-document {\npadding: 20px;\n}\n</style>'
export const context = '<style>* { font-family: Arial, sans-serif;}</style><div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.wallet_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }} </p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.wallet_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PROVISION\' %}</h1>\n<p class="subheader"> {% trans \'consumer_program_title\' %}</p>\n</div>\n<p>{% trans \'consumer_program_provision_intro\' %} {{ coop.short_name }} {% trans \'cooperative_hereto\' %}</p>\n<p>{% trans \'legal_basis\' %}</p>\n<p>{% trans \'program_duration\' %}</p>\n<p>{% trans \'program_provision_status\' %}</p>\n<div style="text-align: center">\n<h3>{% trans \'terms_program\' %}</h3>\n</div>\n<p>{% trans \'consumer_program_terms\' %}</p>\n<div style="text-align: center">\n<h3>{% trans \'program_goals\' %}</h3>\n</div>\n<p>{% trans \'goal_efficiency\' %}</p>\n<p>{% trans \'goal_transparency\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_participation_procedure\' %}</h3></div>\n<p>{% trans \'program_participation_terms\' %}</p>\n<p>{% trans \'offer_acceptance\' %}</p>\n<p>{% trans \'procedure_participation\' %}</p>\n<div style="text-align: center"><h3>{% trans \'user_cabinet\' %}</h3></div>\n<p>{% trans \'site_access\' %}</p>\n<p>{% trans \'unique_code\' %}</p>\n<p>{% trans \'digital_signature\' %}</p>\n<p>{% trans \'identification\' %}</p>\n<p>{% trans \'information_security\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_interaction_mechanism\' %}</h3></div>\n<p>{% trans \'program_share_contribution\' %}</p>\n<p>{% trans \'share_contribution_procedure\' %}</p>\n<p>{% trans \'fund_management\' %}</p>\n<p>{% trans \'application_to_council\' %}</p>\n<p>{% trans \'share_contribution_return\' %}</p>\n<p>{% trans \'claim_return\' %}</p>\n<p>{% trans \'payment_confirmation\' %}</p>\n<p>{% trans \'novation_agreement\' %}</p>\n<p>{% trans \'novation_agreement_terms\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_rights_obligations\' %}</h3></div>\n<p>{% trans \'participant_rights\' %}</p>\n<p>{% trans \'participant_obligations\' %}</p>\n<p>{% trans \'cooperative_obligations\' %}</p>\n<p>{% trans \'cooperative_rights\' %}</p>\n<div style="text-align: center"><h3>{% trans \'force_majeure\' %}</h3></div>\n<p>{% trans \'legal_changes\' %}</p>\n<p>{% trans \'program_modification\' %}</p>\n<p>{% trans \'force_majeure_cases\' %}</p>\n<div style="text-align: center"><h3>{% trans \'program_communication\' %}</h3></div>\n<p>{% trans \'communication_channels\' %}</p>\n<p>{% trans \'alternative_methods\' %}</p>\n<p>{% trans \'program_additional_conditions\' %}</p>\n<p>{% trans \'governing_documents\' %}</p>\n<p>{% trans \'changes_amendments\' %}</p>\n<div class="digital-signature">\n{% trans \'acceptance_agreement\' %}</div></div>\n\n<style>\n.digital-document {\npadding: 20px;\nwhite-space: pre-wrap;\n}\n.subheader{\npadding-bottom: 20px;\n}\n</style>'
export const translations = {
ru: {
File diff suppressed because one or more lines are too long
@@ -18,14 +18,14 @@ export interface Model {
export const title = 'Согласие с условиями положения о простой электронной подписи'
export const description = 'Форма согласия с условиями положения о простой электронной подписи'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.signature_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }} </p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.signature_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PROVISION\' %} {{vars.full_abbr_genitive}} "{{vars.name}}"</h1>\n</div>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_1\' %}</h3>\n</div>\n<p> {% trans \'regulation_text_1_1\' %} {{vars.website}}. {% trans \'regulation_text_1_2\' %} {{vars.full_abbr_genitive}} "{{vars.name}}" {% trans \'regulation_text_1_4\' %} {{vars.short_abbr}} "{{vars.name}}" {% trans \'regulation_text_1_6\' %} {{vars.short_abbr}} "{{vars.name}}" {% trans \'regulation_text_1_8\' %} {{vars.short_abbr}} "{{vars.name}}".{% trans \'regulation_text_1_10\' %} </p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_2\' %}</h3>\n</div>\n<p>{% trans \'regulation_text_2_1\' %} {{vars.short_abbr}} "{{vars.name}}", {% trans \'regulation_text_2_3\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_3\' %} </h3>\n</div>\n<p>{% trans \'regulation_text_3_1\' %} {{vars.website}} {% trans \'regulation_text_3_2\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_4\' %}</h3>\n</div>\n<p>{% trans \'regulation_pep_text_4\' %} </p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_5\' %}</h3>\n</div>\n<p>{% trans \'regulation_pep_text_5\' %} </p>\n<div class="signature">\n{% trans \'acceptance_agreement\' %}</div>\n<div style="text-align: center">\n<h3 class="header"> {% trans \'PRIL_PROVISION\' %} </h3>\n</div>\n<p>{% trans \'PRIL_PROVISION_TEXT\' %}</p>\n</div>\n<style>.digital-document {padding: 10px;}</style>'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.signature_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }} </p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.signature_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PROVISION\' %}</h1>\n<p class="subheader">{% trans \'PROVISION2\' %} {{vars.full_abbr_genitive}} "{{vars.name}}"</p>\n</div>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_1\' %}</h3>\n</div>\n<p> {% trans \'regulation_text_1_1\' %} {{vars.website}}. {% trans \'regulation_text_1_2\' %} {{vars.full_abbr_genitive}} "{{vars.name}}" {% trans \'regulation_text_1_4\' %} {{vars.short_abbr}} "{{vars.name}}" {% trans \'regulation_text_1_6\' %} {{vars.short_abbr}} "{{vars.name}}" {% trans \'regulation_text_1_8\' %} {{vars.short_abbr}} "{{vars.name}}".{% trans \'regulation_text_1_10\' %} </p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_2\' %}</h3>\n</div>\n<p>{% trans \'regulation_text_2_1\' %} {{vars.short_abbr}} "{{vars.name}}", {% trans \'regulation_text_2_3\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_3\' %} </h3>\n</div>\n<p>{% trans \'regulation_text_3_1\' %} {{vars.website}} {% trans \'regulation_text_3_2\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_4\' %}</h3>\n</div>\n<p>{% trans \'regulation_pep_text_4\' %} </p>\n<div style="text-align: left">\n<h3>{% trans \'regulation_pep_title_5\' %}</h3>\n</div>\n<p>{% trans \'regulation_pep_text_5\' %} </p>\n<div class="signature">\n{% trans \'acceptance_agreement\' %}</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PRIL_PROVISION\' %} </h1>\n</div>\n<p>{% trans \'PRIL_PROVISION_TEXT\' %}</p>\n</div>\n<style>.digital-document {\npadding: 20px;\nwhite-space: pre-wrap;\n}</style>'
export const translations = {
ru: {
APPROVED: 'УТВЕРЖДЕНО',
protocol: 'Протоколом Совета № СС',
from: 'от',
PROVISION: 'ПОЛОЖЕНИЕ\nо порядке и правилах использования простой электронной подписи \n',
PROVISION: 'ПОЛОЖЕНИЕ',
regulation_pep_title_1: '1. ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ',
regulation_text_1_1: '1.1. «Правила» - настоящие правила об использовании простой электронной подписи.\n1.2. «Электронный документ» (ЭД) – документированная информация Общества, представленная в электронно-цифровой форме, то есть в виде, пригодном для восприятия пайщиком Общества с использованием электронных вычислительных машин, а также для передачи по информационно –телекоммуникационным сетям или обработки в информационных системах.\n1.3. «Электронная подпись» (ЭП) – информация в электронной форме, которая присоединена к другой информации в электронной форме (подписываемой информации) или иным образом связана с такой информацией и которая используется для однозначного определения лица, подписывающего информацию.\n1.4. «Простая электронная подпись» (ПЭП) – буквенно-цифровую строка, которая зашифрована стандартным алгоритмом, описанным в Приложении №1 к настоящему Положению. Электронная подпись позволяет идентифицировать пользователя, а также подписывать документы в электронном виде. \n1.5. «Электронный документооборот» (ЭД) – процесс обмена электронными документами, подписанными ПЭП, между Обществом и пайщиками Общества.\n1.6. «Интернет-Cайт» (Сайт) – сайт Общества в сети Интернет, доступ к которому осуществляется по адресу: ',
regulation_text_1_2: '\n1.7. «Личный кабинет» (ЛК) - поддерживаемая Сервисом информационная подсистема Сайта, представляющая собой личную страницу Пользователя, позволяющая Сервису и Пользователю осуществлять индивидуальное дистанционное взаимодействие в электронной форме. Доступ к информации в ЛК происходит с помощью Логина и пароля, известного только Пользователю.\n1.8. «Пользователь» - пайщик ',
@@ -46,6 +46,7 @@ export const translations = {
acceptance_agreement: 'СОГЛАСЕН',
PRIL_PROVISION: 'ПРИЛОЖЕНИЕ № 1 к Положению\nТехнические характеристики электронной подписи',
PRIL_PROVISION_TEXT: 'Электронная подпись генерируется Пользователем в приложении и является и является математическим двухкаскадным прокурсором секретного ключа, для получения которого используется одна из эллиптических кривых - secp256k1 или p256. \n\nПриватный ключ генерируется случайным образом криптографической библиотекой на стороне клиента без передачи на сервер и конвертируется в массив байтов (Uint8Array) длиной 32 байта для дальнейшего использования. \n\nДля подписания документов используется алгоритм ECDSA (Elliptic Curve Digital Signature Algorithm) с SHA-256 в качестве хэш-функции. Процесс подписания включает:\n1.\tХэширование документа с использованием алгоритма SHA-256.\n2.\tПрименение приватного ключа пользователя для создания цифровой подписи на основе алгоритма эллиптической кривой ECDSA.\nВерификация подписи проводится с использованием публичного ключа, который генерируется одновременно с приватным и передается на сервер. Алгоритм верификации цифровой подписи включает:\n1.\tПолучение публичного ключа из подписи.\n2.\tСравнение результата с ранее сохраненным публичным ключом , чтобы подтвердить подлинность подписи.\nЭлектронная подпись соответствует стандартам безопасности и криптографическим требованиям, обеспечивая высокую степень защиты данных. Процесс полностью совместим с международными стандартами и большинством публичных сетей.\n',
PROVISION2: 'о порядке и правилах использования простой электронной подписи ',
},
// ... другие переводы
@@ -18,7 +18,7 @@ export interface Model {
export const title = 'Согласие с условиями политики обработки конфиденциальных данных'
export const description = 'Форма согласия с условиями политики обработки конфиденциальных данных'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.privacy_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }} </p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.privacy_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PRIVACY_POLICY\' %}</h1>\n</div>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_1\' %}</b>\n</div>\n<p> {% trans \'privacy_policy_text_1_1\' %} {{vars.full_abbr_dative}} {% trans \'privacy_policy_text_1_2\' %} "{{ vars.name}}" {{vars.website}}.</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_2\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_2_1\' %} {{vars.website}}; {% trans \'privacy_policy_text_2_2\' %} {{vars.website}}; {% trans \'privacy_policy_text_2_3\' %} {{vars.website}}; {% trans \'privacy_policy_text_2_4\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_3_1\' %} {{vars.short_abbr}} "{{vars.name}}":</b>\n</div>\n<p>{% trans \'privacy_policy_text_3\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_4\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_4_1\' %} {{vars.short_abbr}} "{{vars.name}}"{% trans \'privacy_policy_text_4_2\' %} {{vars.confidential_email}} {% trans \'privacy_policy_text_4_3\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_5\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_5_1\' %} {{vars.website}} {% trans \'privacy_policy_text_5_2\' %} </p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_6\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_6_1\' %} {{vars.confidential_email}} {% trans \'privacy_policy_text_6_2\' %} {{vars.confidential_email}} {% trans \'privacy_policy_text_6_3\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_7\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_7\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_8\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_8_1\' %} {{vars.confidential_email}}. {% trans \'privacy_policy_text_8_2\' %} {{vars.confidential_link}}.</p>\n</div>\n<style>\n.digital-document {\npadding: 20px;\n};\n</style>\n\n'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.privacy_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }} </p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.privacy_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PRIVACY_POLICY\' %}</h1>\n</div>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_1\' %}</b>\n</div>\n<p> {% trans \'privacy_policy_text_1_1\' %} {{vars.full_abbr_dative}} {% trans \'privacy_policy_text_1_2\' %} "{{ vars.name}}" {{vars.website}}.</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_2\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_2_1\' %} {{vars.website}}; {% trans \'privacy_policy_text_2_2\' %} {{vars.website}}; {% trans \'privacy_policy_text_2_3\' %} {{vars.website}}; {% trans \'privacy_policy_text_2_4\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_3_1\' %} {{vars.short_abbr}} "{{vars.name}}":</b>\n</div>\n<p>{% trans \'privacy_policy_text_3\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_4\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_4_1\' %} {{vars.short_abbr}} "{{vars.name}}"{% trans \'privacy_policy_text_4_2\' %} {{vars.confidential_email}} {% trans \'privacy_policy_text_4_3\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_5\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_5_1\' %} {{vars.website}} {% trans \'privacy_policy_text_5_2\' %} </p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_6\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_6_1\' %} {{vars.confidential_email}} {% trans \'privacy_policy_text_6_2\' %} {{vars.confidential_email}} {% trans \'privacy_policy_text_6_3\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_7\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_7\' %}</p>\n<div style="text-align: left">\n<b>{% trans \'privacy_policy_title_8\' %}</b>\n</div>\n<p>{% trans \'privacy_policy_text_8_1\' %} {{vars.confidential_email}}. {% trans \'privacy_policy_text_8_2\' %} {{vars.confidential_link}}.</p>\n</div>\n<style>\n.digital-document {\npadding: 20px;\nwhite-space: pre-wrap;\n};\n</style>\n\n'
export const translations = {
ru: {
File diff suppressed because one or more lines are too long
@@ -20,7 +20,7 @@ export interface Model {
export const title = 'Пользовательское соглашение (оферта) о присоединении к платформе "Кооперативная Экономика"'
export const description = 'Форма пользовательского соглашения (оферта) о присоединении к платформе "Кооперативная Экономика"'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.coopenomics_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }}</p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.coopenomics_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PROVISION\' %}</h1>\n<p>{% trans \'consumer_program_title\' %}</p>\n</div>\n<p> {% trans \'coop_agreement_intro_1\' %} {{coop.chairman.last_name}} {{coop.chairman.first_name}} {{coop.chairman.middle_name}} {% trans \'coop_agreement_intro_2\' %} {{ partner.short_name}} {% trans \'coop_agreement_intro_3\' %}{{partner.represented_by.last_name}} {{partner.represented_by.first_name}} {{partner.represented_by.middle_name}}{% trans \'coop_agreement_intro_4\' %} </p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_1\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_1_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_2\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_2_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_3\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_3_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_4\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_4_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_5\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_5_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_6\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_6_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_7\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_7_text\', vars.contact_email %}</p>\n<div class="signature">\n{% trans \'acceptance_agreement\' %}</div></div>\n<style>\n.digital-document {\npadding: 20px;\n}\n</style>\n'
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'APPROVED\' %}</p>\n<p style="margin: 0px !important">{% trans \'protocol\' %} {{ vars.coopenomics_agreement.protocol_number }}</p>\n<p style="margin: 0px !important">{{ coop.full_name }}</p>\n<p style="margin: 0px !important">{% trans \'from\' %} {{ vars.coopenomics_agreement.protocol_day_month_year }}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header"> {% trans \'PROVISION\' %}</h1>\n<p class="subheader">{% trans \'consumer_program_title\' %}</p>\n</div>\n<p>{% trans \'coop_agreement_intro_1\' %} {{coop.chairman.last_name}} {{coop.chairman.first_name}} {{coop.chairman.middle_name}}{% trans \'coop_agreement_intro_2\' %} {{ partner.short_name}} {% trans \'coop_agreement_intro_3\' %}{{partner.represented_by.last_name}} {{partner.represented_by.first_name}} {{partner.represented_by.middle_name}}{% trans \'coop_agreement_intro_4\' %} </p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_1\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_1_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_2\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_2_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_3\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_3_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_4\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_4_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_5\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_5_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_6\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_6_text\' %}</p>\n<div style="text-align: left">\n<h3>{% trans \'coop_agreement_7\' %}</h3>\n</div>\n<p>{% trans \'coop_agreement_7_text\', vars.contact_email %}</p>\n<div class="signature">\n{% trans \'acceptance_agreement\' %}</div></div>\n<style>\n.digital-document {\npadding: 20px;\nwhite-space: pre-wrap;\n}\n.subheader {padding-bottom: 20px;}\n</style>\n'
export const translations = {
ru: {
@@ -39,7 +39,7 @@ export const translations = {
coop_agreement_2: '2.\tПРЕДМЕТ СОГЛАШЕНИЯ',
coop_agreement_2_text: 'Настоящее Соглашение формулируют условия и регламент присоединения и осуществления деятельности Кооператива на Платформе в режиме натурного тестирования Платформы и MONOCOOP и является по отношению к Кооперативу Офертой от Общества, где Общество является Оферентом, а Кооператив является Акцептантом.\n',
coop_agreement_3: '3.\tУСЛОВИЯ И РЕГЛАМЕНТ ПРИСОЕДИНЕНИЯ',
coop_agreement_3_text: '3.1.\tФактическое присоединение Кооператива к Платформе осуществляется Обществом посредством запуска MONOCOOР на (под)домене Кооператива в соответствии с разделом 5 Инструкции в течение Пробного Периода, которая является неотъемлемым Приложением к настоящему Соглашению.\n3.2.\tДействия, которые Кооператив совершает на Платформе, измеряются в Квотах.\n3.3.\tКооператив выплачивает Обществу Членский Взнос Денежными средствами в размере 2500 (две тысячи пятьсот) рублей за использование одной Квоты.\n3.4.\tКооператив, в соответствии с п. 3.3 настоящего Соглашения, и по истечению Пробного Периода выплачивает Членский Взнос в Общество за 24 (двадцать четыре) часа до начала использования Квоты согласно реквизитов Общества, указанных на сайте https://цифровой-кооператив.рф. \n3.5.\tФактом оплаты Членского Взноса Кооператива в Общество признается время и дата начисления Членского Взноса, вносимого Кооперативом согласно п. 3.3 настоящего Соглашения на расчетный счет Общества. \n3.6.\tПри неоплате Членского Взноса в Общество с соответствии с п. 3.3 настоящего Соглашения, за исключением услови, изложенного в п. 4.6. настоящего Соглашения, подключение Кооператива к Платформе приостанавливается до момента осуществления оплаты Членского Взноса Кооперативом.\n',
coop_agreement_3_text: '3.1.\tФактическое присоединение Кооператива к Платформе осуществляется Обществом посредством запуска MONOCOOР на (под)домене Кооператива в соответствии с разделом 5 Инструкции в течение Пробного Периода, которая является неотъемлемым Приложением к настоящему Соглашению.\n3.2.\tДействия, которые Кооператив совершает на Платформе, измеряются в Квотах.\n3.3.\tКооператив выплачивает Обществу Членский Взнос Денежными средствами в размере 1500 (полторы тысячи) рублей за использование одной Квоты.\n3.4.\tКооператив, в соответствии с п. 3.3 настоящего Соглашения, и по истечению Пробного Периода выплачивает Членский Взнос в Общество за 24 (двадцать четыре) часа до начала использования Квоты согласно реквизитов Общества, указанных на сайте https://цифровой-кооператив.рф. \n3.5.\tФактом оплаты Членского Взноса Кооператива в Общество признается время и дата начисления Членского Взноса, вносимого Кооперативом согласно п. 3.3 настоящего Соглашения на расчетный счет Общества. \n3.6.\tПри неоплате Членского Взноса в Общество с соответствии с п. 3.3 настоящего Соглашения, за исключением услови, изложенного в п. 4.6. настоящего Соглашения, подключение Кооператива к Платформе приостанавливается до момента осуществления оплаты Членского Взноса Кооперативом.\n',
coop_agreement_4: '4.\tОБЯЗАТЕЛЬСТВА И ПРАВА',
coop_agreement_4_text: '4.1.\tОбщество обязуется заблаговременно - за 3 (три) дня до предполагаемого истечения Квоты - известить Кооператив посредством направления уведомления или сообщения согласно реквизитов Кооператива, указанных в п.7.7. настоящего Соглашения. \n4.2.\tКооператив соглашается не предпринимать действий юридического характера с какими-то ни было претензиями или рекламациями в отношении Общества в связи с возможными сбоями и/или ошибками в функционировании Платформы и/или MONOCOOР в рамках срока действия настоящего Соглашения, ввиду проведения натурного тестирования Платформы с участием Кооператива.\n4.3.\tКооператив обязуется предоставлять Обществу информацию об ошибках и сбоях функционирования Платформы и MONOCOOР при наличии таковых согласно реквизитов Общества, указанных на сайте https://цифровой-кооператив.рф.\n4.4.\tКооператив обязуется сохранять в тайне передаваемую или создаваемую для него Обществом защищаемую информацию, в том числе, но не ограничиваясь: логины, пароли, коды доступа закрытые ключи электронной подписи и прочую информацию, имеющую пометку “конфиденциально”, а также обеспечить такую тайну со стороны своих пайщиков и аффилированных лиц, в соответствии с условиями, изложенными в разделе 5 настоящего Соглашения, в течение срока действия настоящего Соглашения, а в случае его расторжения в соответствии с п.7.5 или окончания срока действия настоящего Соглашения без заключения последующего договора или соглашения с Обществом по подключению к Платформе - в течение 2 (двух) лет с даты такого расторжения или окончания срока действия настоящего Соглашения.\n4.5.\tКооператив обязуется немедленно информировать Общество о факте доступа посторонних лиц к защищаемой Обществом информации, а также подозрении на него, направив сообщение согласно реквизитов Общества, указанных на сайте https://цифровой-кооператив.рф.\n4.6.\tКооператив имеет право запросить продление Пробного Периода у Общества на срок по согласованию с Обществом, направив соответствующее обращение по реквизитам Общества, указанных на сайте https://цифровой-кооператив.рф за 24 (двадцать четыре) часа до окончания срока Пробного Периода согласно п. 1.6. настоящего Соглашения.\n',
coop_agreement_5: '5.\tКОНФИДЕНЦИАЛЬНОСТЬ',
@@ -25,7 +25,8 @@ export interface Model {
export const title = 'Решение совета о приёме пайщика в кооператив'
export const description = 'Форма решения совета о приёме пайщика в потребительский кооператив'
export const context = '<style> \nh1 {\nmargin: 0px; \ntext-align:center;\n}\nh3{\nmargin: 0px;\npadding-top: 15px;\n}\n.about {\npadding: 20px;\n}\n.about p{\nmargin: 0px;\n}\n.signature {\npadding-top: 20px;\n}\n.digital-document {padding: 20px;}\n</style>\n\n<div class="digital-document"><h1 class="header">{% trans \'protocol_number\', decision.id %}</h1>\n<p style="text-align:center">{% trans \'council_meeting_name\' %}\n{{ coop.full_name }}</p>\n<p style="text-align: right"> {{ meta.created_at }}, {{ coop.city }}</p>\n\n<div class="about">\n<p>{% trans \'meeting_format\' %}</p>\n<p>{% trans \'meeting_place\', coop.full_address %}</p>\n<p>{% trans \'meeting_date\', decision.date %}</p>\n<p>{% trans \'opening_time\', decision.time %}</p>\n</div>\n\n<h3>{% trans \'council_members\' %}</h3>\n<ol>{% for member in coop.members %}\n<li>{{ member.last_name }} {{ member.first_name }} {{ member.middle_name }}{% if member.is_chairman %} {% trans \'chairman_of_the_council\' %}{% endif %}</li>\n{% endfor %}\n</ol>\n\n<h3>{% trans \'meeting_legality\' %} </h3>\n<p>{% trans \'voting_results\', decision.voters_percent %} {% trans \'quorum\' %} {% trans \'chairman_of_the_meeting\', coop.chairman.last_name, coop.chairman.first_name, coop.chairman.middle_name %}.</p>\n\n<h3>{% trans \'agenda\' %}</h3>\n<ol><li> {% trans \'decision_joincoop1\' %}{% if type == \'individual\' %} {{individual.last_name}} {{individual.first_name}} {{individual.middle.name}} ({{individual.birthdate}} {% trans \'birthdate\' %}) {% endif %}{% if type == \'entrepreneur\' %} {% trans \'entrepreneur\' %} {{entrepreneur.last_name}} {{entrepreneur.first_name}} {{entrepreneur.middle.name}} ({% trans \'ogrnip\' %} {{entrepreneur.details.ogrn}}) {% endif %}{% if type == \'organization\' %} {{organization.short_name}} ({% trans \'ogrn\' %} {{organization.details.ogrn}}) {% endif %}{% trans \'in_participants\' %} {{ coop.short_name }}.\n</li></ol>\n\n<h3>{% trans \'voting\' %}</h3>\n<p>{% trans \'vote_results\', decision.votes_for, decision.votes_against, decision.votes_abstained %} </p>\n\n<h3>{% trans \'decision_made\' %}</h3>\n<p>{% trans \'decision_joincoop2\' %}{% if type == \'individual\' %} {{individual.last_name}} {{individual.first_name}} {{individual.middle.name}} {{individual.birthdate}} {% trans \'birthdate\' %}{% endif %} {% if type == \'entrepreneur\' %}{% trans \'entrepreneur\' %} {{entrepreneur.last_name}} {{entrepreneur.first_name}} {{entrepreneur.middle.name}}, {% trans \'ogrnip\' %} {{entrepreneur.details.ogrn}}{% endif %} {% if type == \'organization\' %} {{organization.short_name}}, {% trans \'ogrn\' %} {{organization.details.ogrn}} {% endif %}</p>\n<hr>\n<p>{% trans \'closing_time\', decision.time %}</p>\n\n<div class="signature"> \n<p>{% trans \'signature\' %}<p>\n<p style="text-align: right;">{% trans \'chairman\' %} {{ coop.chairman.last_name }} {{ coop.chairman.first_name }} {{ coop.chairman.middle_name }}</p></div></div>'
export const context = '<style> \nh1 {\nmargin: 0px; \ntext-align:center;\n}\nh3{\nmargin: 0px;\npadding-top: 15px;\n}\n.about {\npadding: 20px;\n}\n.about p{\nmargin: 0px;\n}\n.signature {\npadding-top: 20px;\n}\n.digital-document {\npadding: 20px;\nwhite-space: pre-wrap;\n}\n.subheader {\npadding-bottom: 20px; \n}\n</style>\n\n<div class="digital-document"><h1 class="header">{% trans \'protocol_number\', decision.id %}</h1>\n<p style="text-align:center" class="subheader">{% trans \'council_meeting_name\' %}\n{{ coop.full_name }}</p>\n<p style="text-align: right"> {{ meta.created_at }}, {{ coop.city }}</p>\n\n<div class="about">\n<p>{% trans \'meeting_format\' %}</p>\n<p>{% trans \'meeting_place\', coop.full_address %}</p>\n<p>{% trans \'meeting_date\', decision.date %}</p>\n<p>{% trans \'opening_time\', decision.time %}</p>\n</div>\n\n<h3>{% trans \'council_members\' %}</h3>\n<ol>{% for member in coop.members %}\n<li>{{ member.last_name }} {{ member.first_name }} {{ member.middle_name }}{% if member.is_chairman %} {% trans \'chairman_of_the_council\' %}{% endif %}</li>\n{% endfor %}\n</ol>\n\n<h3>{% trans \'meeting_legality\' %} </h3>\n<p>{% trans \'voting_results\', decision.voters_percent %} {% trans \'quorum\' %} {% trans \'chairman_of_the_meeting\', coop.chairman.last_name, coop.chairman.first_name, coop.chairman.middle_name %}.</p>\n\n<h3>{% trans \'agenda\' %}</h3>\n<ol><li> {% trans \'decision_joincoop1\' %}{% if type == \'individual\' %} {{individual.last_name}} {{individual.first_name}} {{individual.middle.name}} ({{individual.birthdate}} {% trans \'birthdate\' %}) {% endif %}{% if type == \'entrepreneur\' %} {% trans \'entrepreneur\' %} {{entrepreneur.last_name}} {{entrepreneur.first_name}} {{entrepreneur.middle.name}} ({% trans \'ogrnip\' %} {{entrepreneur.details.ogrn}}) {% endif %}{% if type == \'organization\' %} {{organization.short_name}} ({% trans \'ogrn\' %} {{organization.details.ogrn}}) {% endif %}{% trans \'in_participants\' %} {{ coop.short_name }}.\n</li></ol>\n\n<h3>{% trans \'voting\' %}</h3>\n<p>{% trans \'vote_results\', decision.votes_for, decision.votes_against, decision.votes_abstained %} </p>\n\n<h3>{% trans \'decision_made\' %}</h3>\n<p>{% trans \'decision_joincoop2\' %}{% if type == \'individual\' %} {{individual.last_name}} {{individual.first_name}} {{individual.middle.name}} {{individual.birthdate}} {% trans \'birthdate\' %}{% endif %} {% if type == \'entrepreneur\' %}{% trans \'entrepreneur\' %} {{entrepreneur.last_name}} {{entrepreneur.first_name}} {{entrepreneur.middle.name}}, {% trans \'ogrnip\' %} {{entrepreneur.details.ogrn}}{% endif %} {% if type == \'organization\' %} {{organization.short_name}}, {% trans \'ogrn\' %} {{organization.details.ogrn}} {% endif %}</p>\n<hr>\n<p>{% trans \'closing_time\', decision.time %}</p>\n\n<div class="signature"> \n<p>{% trans \'signature\' %}<p>\n<p style="text-align: right;">{% trans \'chairman\' %} {{ coop.chairman.last_name }} {{ coop.chairman.first_name }} {{ coop.chairman.middle_name }}</p></div></div>'
export const translations = {
ru: {
meeting_format: 'Форма проведения собрания совета: заочная',

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