Compare commits

...

33 Commits

Author SHA1 Message Date
Alex Ant a950a47b28 chore(release): publish
- coopback@1.7.31-alpha.1
 - coopdoc-generator-ts@1.0.75-alpha.1
 - cooptypes@1.0.20-alpha.1
 - coop-notificator@0.0.24-alpha.1
 - terminal@0.1.35-alpha.1
2024-08-23 10:38:04 +05:00
Alex Ant 2609d10c0b update 2024-08-23 10:35:48 +05:00
Alex Ant 7b4cb0f66c publish 2024-08-23 10:34:38 +05:00
Alex Ant 26db0e1e69 push 2024-08-21 15:57:02 +05:00
Alex Ant 62e64cbd22 upgrade 2024-08-21 14:43:43 +05:00
Alex Ant 844a47830f fix types 2024-08-21 13:11:37 +05:00
Alex Ant 2cc1e32f45 chore(release): publish
- coopback@1.7.31-alpha.0
 - coopdoc-generator-ts@1.0.75-alpha.0
 - cooptypes@1.0.20-alpha.0
 - coop-notificator@0.0.24-alpha.0
 - terminal@0.1.35-alpha.0
2024-08-21 12:53:38 +05:00
Alex Ant 9890958b1e update contracts interface 2024-08-21 12:50:44 +05:00
Alex Ant 6dbd6d6edc fix 2024-08-19 14:31:01 +05:00
Alex Ant bf7a4dfd28 путь установки 2024-08-19 14:20:38 +05:00
Alex Ant f52626bda0 Полное разделение форм ввода данных пользователя пригодное для использования в процессе инсталляции 2024-08-16 16:17:24 +05:00
Alex Ant 88cec4bf2b рефакторинг | вынос форм для ввода пользовательской информации в shared для дальнейшего использования в модуле установки 2024-08-15 20:30:01 +05:00
Alex Ant b880def108 fix 2024-08-15 12:39:19 +05:00
Alex Ant bfd5dc039a addUser в панели управления 2024-08-15 12:28:18 +05:00
Alex Ant c0d236dff3 chore(release): publish
- coopback@1.7.30
 - coopdoc-generator-ts@1.0.74
 - cooptypes@1.0.19
 - coop-notificator@0.0.23
 - terminal@0.1.34
2024-08-13 23:03:13 +05:00
Alex Ant 743d5fc306 разделение формы для ввода персональных данных с регистратором; зародыш формы добавления пайщика в реестр. 2024-08-13 23:00:09 +05:00
Alex Ant 2f51516163 step to install | works on add user by api 2024-08-13 15:21:23 +05:00
Alex Ant b606b46aa4 страница настроек с кнопками выхода для пайщика 2024-08-05 14:31:27 +05:00
Alex Ant 5b65ff99a0 chore(release): publish
- coopback@1.7.29
 - coopdoc-generator-ts@1.0.73
 - cooptypes@1.0.18
 - coop-notificator@0.0.22
 - terminal@0.1.33
2024-08-05 12:36:01 +05:00
Alex Ant 74a3f52324 update 2024-08-05 12:32:23 +05:00
Alex Ant 7c2a4fdfa8 удаление потерявшейся папки 2024-08-05 12:05:05 +05:00
Alex Ant fecee3a098 fix naming 2024-08-05 11:56:29 +05:00
Alex Ant 45f7111fb9 chore(release): publish
- coopback@1.7.29-alpha.0
 - coopdoc-generator-ts@1.0.73-alpha.0
 - cooptypes@1.0.18-alpha.0
 - coop-notificator@0.0.22-alpha.0
 - terminal@0.1.33-alpha.0
2024-08-05 11:51:37 +05:00
Alex Ant f2e23a8062 перевыпуск ключа доступа 2024-08-05 11:38:10 +05:00
Alex Ant 36a3631f5e login with signature 2024-08-02 17:11:01 +05:00
Alex Ant a758d19a9f обновление mongoose, интеграция eslint и активация typescript проверок 2024-08-01 14:06:01 +05:00
Alex Ant aac3e53a30 eslint repair 2024-08-01 13:06:28 +05:00
Alex Ant 85d87ee210 FIX !!! 2024-07-31 20:48:38 +05:00
Alex Ant 2f3ee80ed4 Merge branch 'main' into dev 2024-07-31 17:26:20 +05:00
Alex Ant 3632bea54e chore(release): publish
- terminal@0.1.32
2024-07-31 17:26:08 +05:00
Alex Ant 4a0521751a update 2024-07-31 17:24:44 +05:00
Alex Ant 7d6266c241 chore(release): publish
- terminal@0.1.32-alpha.0
2024-07-31 16:28:53 +05:00
Alex Ant 1d69b9ffea Рабочие столы v1 2024-07-31 16:24:50 +05:00
373 changed files with 7783 additions and 4423 deletions
-31
View File
@@ -1,31 +0,0 @@
{
"env": {
"node": true,
"jest": true
},
"extends": ["airbnb-base", "plugin:jest/recommended", "plugin:security/recommended", "plugin:prettier/recommended"],
"plugins": ["jest", "security", "prettier", "import"],
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"@typescript-eslint/no-explicit-any":"off",
"no-console": "error",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
"jest/expect-expect": "off",
"security/detect-object-injection": "off",
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
]
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"env": {
"node": true,
"jest": true,
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"plugins": ["@typescript-eslint", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"no-console": "warn",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
"jest/expect-expect": "off",
"security/detect-object-injection": "off",
"@typescript-eslint/no-unused-vars": "warn"
}
}
+4 -2
View File
@@ -1,6 +1,8 @@
{
"CodeGPT.apiKey": "OpenAI",
"cSpell.words": [
"subcode"
]
],
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.30](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29...coopback@1.7.30) (2024-08-13)
**Note:** Version bump only for package coopback
## [1.7.29](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29-alpha.0...coopback@1.7.29) (2024-08-05)
**Note:** Version bump only for package coopback
## [1.7.28](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.28-alpha.0...coopback@1.7.28) (2024-07-26)
**Note:** Version bump only for package coopback
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "create-nodejs-express-app",
"version": "1.7.28",
"version": "1.7.31-alpha.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-nodejs-express-app",
"version": "1.7.28",
"version": "1.7.31-alpha.1",
"license": "MIT",
"dependencies": {
"@a2seven/yoo-checkout": "^1.1.4",
@@ -32,7 +32,7 @@
"jsonwebtoken": "^8.5.1",
"jspdf": "^2.5.1",
"moment": "^2.24.0",
"mongoose": "^5.7.7",
"mongoose": "^8.5.2",
"morgan": "^1.9.1",
"nodemailer": "^6.3.1",
"nunjucks": "^3.2.4",
@@ -54,7 +54,7 @@
"@types/express": "^4.17.21",
"@types/node": "^20.11.26",
"coveralls": "^3.0.7",
"eslint": "^7.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.29.1",
+7 -3
View File
@@ -1,6 +1,6 @@
{
"name": "coopback",
"version": "1.7.28",
"version": "1.7.31-alpha.1",
"description": "",
"private": true,
"bin": "bin/createNodejsApp.js",
@@ -8,6 +8,7 @@
"repository": "https://github.com/hagopj13/node-express-boilerplate.git",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
"types": "src/types/index.ts",
"engines": {
"node": ">=12.0.0"
},
@@ -88,7 +89,7 @@
"jsonwebtoken": "^8.5.1",
"jspdf": "^2.5.1",
"moment": "^2.24.0",
"mongoose": "^5.7.7",
"mongoose": "^8.5.2",
"morgan": "^1.9.1",
"nodemailer": "^6.3.1",
"nunjucks": "^3.2.4",
@@ -107,13 +108,16 @@
"xss-clean": "^0.1.1"
},
"devDependencies": {
"@antfu/eslint-config": "^2.24.1",
"@redocly/cli": "^1.18.0",
"@types/express": "^4.17.21",
"@types/faker": "^6.6.9",
"@types/node": "^20.11.26",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^8.0.0",
"concurrently": "^8.2.2",
"coveralls": "^3.0.7",
"eslint": "^7.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.29.1",
@@ -1,4 +1,5 @@
import config from '../src/config/config';
import logger from '../src/config/logger';
import { User } from '../src/models';
import { tokenService } from '../src/services';
import { userService } from '../src/services';
@@ -7,7 +8,7 @@ import mongoose from 'mongoose';
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Ошибка: Необходимо указать имя пользователя');
logger.error('Ошибка: Необходимо указать имя пользователя');
process.exit(1);
}
@@ -16,12 +17,11 @@ const [username] = args;
async function createServiceUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const user = await userService.createServiceUser(username);
const token = await tokenService.generateServiceAccessToken(user);
console.log('token:', token.access.token);
} catch (e: any) {
console.log('Ошибка: ', e.message);
logger.error('Ошибка: ', e.message);
}
process.exit(0);
+1 -1
View File
@@ -17,7 +17,7 @@ async function deleteUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
await userService.deleteUserById(username);
await userService.deleteUserByUsername(username);
console.log('Пользователь удалён: ', username);
} catch (e: any) {
+1 -7
View File
@@ -15,17 +15,12 @@ async function init() {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
const password = await crypto.createHash('sha256').update(wif).digest('hex');
const hashed_password = await hash(password, 8);
console.log('password: ', hashed_password);
await userService.createUser({
username: 'ant',
email: 'dacom.dark.sun@gmail.com',
password,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'chairman',
role: 'user',
type: 'individual',
individual_data: {
first_name: 'Иван',
@@ -46,7 +41,6 @@ async function init() {
const voskhod = await userService.createUser({
username: 'voskhod',
email: 'chairman.voskhod@gmail.com',
password,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
type: 'organization',
+1 -1
View File
@@ -7,7 +7,7 @@ async function types(): Promise<void> {
// Configure your settings here
const result = await convertFromDirectory({
schemaDirectory: './src/validations',
typeOutputDirectory: './src/types',
typeOutputDirectory: './src/types/auto-generated',
debug: true,
});
+16 -8
View File
@@ -1,17 +1,15 @@
import dotenv from 'dotenv';
import Joi from 'joi';
import path from 'path';
// import { fileURLToPath } from 'url';
// import { dirname, join } from 'path';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = dirname(__filename);
dotenv.config({ path: path.join(__dirname, '../../.env') });
const envVarsSchema = Joi.object()
.keys({
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
BASE_URL: Joi.string().required(),
SERVICE_USERNAME: Joi.string().required(),
SERVICE_WIF: Joi.string().required(),
PORT: Joi.number().default(3000),
MONGODB_URL: Joi.string().required().description('Mongo DB url'),
JWT_SECRET: Joi.string().required().description('JWT secret key'),
@@ -23,11 +21,16 @@ const envVarsSchema = Joi.object()
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which verify email token expires'),
JWT_INVITE_EXPIRATION_MINUTES: Joi.number().default(3600).description('minutes after invite token expires'),
SMTP_HOST: Joi.string().description('server that will send the emails'),
SMTP_PORT: Joi.number().description('port to connect to the email server'),
SMTP_USERNAME: Joi.string().description('username for email server'),
SMTP_PASSWORD: Joi.string().description('password for email server'),
EMAIL_FROM: Joi.string().description('the from field in the emails sent by the app'),
COOPNAME: Joi.string().description('имя аккаунта кооператива'),
})
.unknown();
@@ -39,13 +42,16 @@ if (error) {
export default {
env: envVars.NODE_ENV,
base_url: envVars.BASE_URL,
port: envVars.PORT,
service_wif: envVars.SERVICE_WIF,
service_username: envVars.SERVICE_USERNAME,
mongoose: {
url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''),
options: {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
// useCreateIndex: true,
// useNewUrlParser: true,
// useUnifiedTopology: true,
},
},
jwt: {
@@ -54,6 +60,7 @@ export default {
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
inviteExpirationMinutes: envVars.JWT_INVITE_EXPIRATION_MINUTES,
},
email: {
smtp: {
@@ -66,4 +73,5 @@ export default {
},
from: envVars.EMAIL_FROM,
},
coopname: envVars.COOPNAME,
};
+2 -2
View File
@@ -1,5 +1,5 @@
import winston from 'winston'
import config from './config'
import winston from 'winston';
import config from './config';
const enumerateErrorFormat = winston.format((info) => {
if (info instanceof Error) {
+3 -4
View File
@@ -1,9 +1,8 @@
import {Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
import config from './config'
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import config from './config';
import { tokenTypes } from './tokens';
import { User } from '../models';
const jwtOptions = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
@@ -14,7 +13,7 @@ const jwtVerify = async (payload, done) => {
if (payload.type !== tokenTypes.ACCESS) {
throw new Error('Invalid token type');
}
const user = await User.findOne({_id: payload.sub});
const user = await User.findOne({ _id: payload.sub });
if (!user) {
return done(null, false);
}
+2 -2
View File
@@ -1,7 +1,7 @@
const allRoles = {
user: [''],
service: ['sendNotification'],
chairman: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
service: ['addUser', 'sendNotification', 'install'],
chairman: ['addUser', 'getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
member: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
};
+1
View File
@@ -3,4 +3,5 @@ export const tokenTypes = {
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
INVITE: 'invite',
};
@@ -1,17 +1,15 @@
import httpStatus from 'http-status';
import catchAsync from '../utils/catchAsync';
import { authService, userService, tokenService, emailService, blockchainService } from '../services';
import { RForgotPassword, RRefreshTokens, RResetPassword, RSendNotification, RVerifyEmail } from '../types';
import { authService, tokenService, emailService } from '../services';
import { RForgotKey, RRefreshTokens, RResetKey, RVerifyEmail } from '../types';
const { CREATED, NO_CONTENT } = httpStatus;
const { NO_CONTENT } = httpStatus;
export const login = catchAsync(async (req, res) => {
const { email, password } = req.body;
const user = await authService.loginUserWithEmailAndPassword(email, password);
const { now, signature, email } = req.body;
// if (user.is_registered === false)
// throw new Error('Регистрация не была завершена. Пожалуйста, подождите решения совета или начните с начала. ');
const user = await authService.loginUserWithSignature(email, now, signature);
const tokens = await tokenService.generateAuthTokens(user);
res.send({ user, tokens });
@@ -27,14 +25,14 @@ export const refreshTokens = catchAsync(async (req: RRefreshTokens, res: any) =>
res.send(tokens);
});
export const forgotPassword = catchAsync(async (req: RForgotPassword, res) => {
const resetPasswordToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken);
export const lostKey = catchAsync(async (req: RForgotKey, res) => {
const resetKeyToken = await tokenService.generateResetKeyToken(req.body.email);
await emailService.sendResetKeyEmail(req.body.email, resetKeyToken);
res.status(NO_CONTENT).send();
});
export const resetPassword = catchAsync(async (req: RResetPassword, res) => {
await authService.resetPassword(req.query.token, req.body.password);
export const resetKey = catchAsync(async (req: RResetKey, res) => {
await authService.resetKey(req.body.token, req.body.public_key);
res.status(NO_CONTENT).send();
});
@@ -1,24 +1,22 @@
import catchAsync from '../utils/catchAsync';
import { coopService, documentService } from '../services';
import { Cooperative } from 'cooptypes';
import { generator } from '../services/document.service';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import { Request, Response } from 'express';
export const loadInfo = catchAsync(async (req, res) => {
export const loadInfo = catchAsync(async (req: Request, res: Response) => {
const cooperative = await coopService.loadInfo(String(process.env.COOPNAME));
res.send(cooperative);
});
export const loadContacts = catchAsync(async (req, res) => {
export const loadContacts = catchAsync(async (req: Request, res: Response) => {
const contacts = await coopService.loadContacts(String(process.env.COOPNAME));
res.send(contacts);
});
export const loadAgenda = catchAsync(async (req, res) => {
export const loadAgenda = catchAsync(async (req: Request, res: Response) => {
const { coopname } = req.query;
const agenda = await coopService.loadAgenda(coopname);
const agenda = await coopService.loadAgenda(coopname as string);
const complexAgenda: Cooperative.Documents.IComplexAgenda[] = [];
@@ -30,7 +28,7 @@ export const loadAgenda = catchAsync(async (req, res) => {
res.send(complexAgenda);
});
export const loadStaff = catchAsync(async (req, res) => {
export const loadStaff = catchAsync(async (req, res: Response) => {
const { coopname } = req.query;
const staff = await coopService.loadStaff(coopname);
res.send(staff);
@@ -1,18 +1,8 @@
import catchAsync from '../utils/catchAsync';
import { documentService } from '../services/index';
import type {
IGetDocuments,
RDeletePaymentMethod,
RGenerate,
RGetDocuments,
RGetListPaymentMethods,
RSavePaymentMethod,
} from '../types';
import type { RGenerate, RGetDocuments } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
import { Cooperative } from 'cooptypes';
import ApiError from '../utils/ApiError';
export const generateDocument = catchAsync(async (req: RGenerate, res) => {
const document = await documentService.generateDocument(req.body);
@@ -21,9 +11,9 @@ export const generateDocument = catchAsync(async (req: RGenerate, res) => {
});
export const getDocuments = catchAsync(async (req: RGetDocuments, res) => {
let filter = req.query?.filter;
let limit = req.query?.limit as number;
let page = req.query?.page as number;
const filter = req.query?.filter;
const limit = req.query?.limit as number;
const page = req.query?.page as number;
const documents = await documentService.queryDocuments(filter, page, limit);
@@ -4,3 +4,4 @@ export * as documentController from './document.controller';
export * as paymentController from './payment.controller';
export * as coopController from './coop.controller';
export * as notifyController from './notify.controller';
export * as monoController from './mono.controller';
@@ -0,0 +1,25 @@
import httpStatus from 'http-status';
import { getMonoStatus } from '../services/mono.service';
import { getBlockchainInfo } from '../services/blockchain.service';
import { IHealthResponse, IInstall, RInstall } from '../types';
import { Request, Response } from 'express';
import catchAsync from '../utils/catchAsync';
import { monoService } from '../services';
export const install = catchAsync(async (req: RInstall, res: Response) => {
const { body } = req;
await monoService.install(body as IInstall);
res.status(httpStatus.OK).send();
});
export const getHealth = catchAsync(async (req: Request, res: Response) => {
const status = await getMonoStatus();
const blockchain = await getBlockchainInfo();
const result: IHealthResponse = {
status,
blockchain,
};
res.status(httpStatus.OK).send(result);
});
@@ -91,7 +91,8 @@ export const addPaymentMethod = catchAsync(async (req: RSavePaymentMethod, res)
const paymentData: Cooperative.Payments.IPaymentData = {
username: req.body.username,
method_id: req.body.method_id,
user_type: user.type,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
user_type: user.type as any,
method_type: req.body.method_type,
is_default: false,
data: req.body.data,
@@ -1,12 +1,16 @@
import http from 'http-status';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService, tokenService } from '../services';
import { RCreateUser, RJoinCooperative } from '../types';
import { userService, tokenService, emailService, blockchainService } from '../services';
import { IAddUser, ICreateUser, RCreateUser, RJoinCooperative } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
import { IUser } from '../models/user.model';
import { Request, Response } from 'express';
import { generateUsername } from '../../tests/utils/generateUsername';
import config from '../config/config';
import logger from '../config/logger';
/**
* Порядок регистрации:
@@ -19,9 +23,9 @@ import { IUser } from '../models/user.model';
* 7. (joinCooperative) Пользователь подписывает заявление на вступление и вызывает метод joinCooperative, прикладывая подписанное заявление.
* 8. Система сохраняет подписанное заявление
* 9. (createInitialPayment) Пользователь получает ордер на оплату.
* 9. Пользователь оплачивает ордер.
* 10. Система принимает оплату и регистрирует аккаунт в блокчейне.
* 11. (trx confirmpay) Пользователь подписывает велком-письмо с подтверждением собственноручности оплаты (транзакция в блокчейне для активации)
* 12. Регистрация продолжается ожиданием решения совета
*/
export const createUser = catchAsync(async (req: RCreateUser, res) => {
const user = await userService.createUser(req.body);
@@ -30,6 +34,43 @@ export const createUser = catchAsync(async (req: RCreateUser, res) => {
res.status(http.CREATED).send({ user, tokens });
});
export const addUser = catchAsync(async (req: Request, res: Response) => {
const body: IAddUser = req.body;
const newUser: ICreateUser = {
...body,
public_key: '',
role: 'user',
username: generateUsername(),
};
const user = await userService.createUser(newUser);
user.status = 'registered';
user.is_registered = true;
await user.save();
try {
await blockchainService.addUser({
...body,
...newUser,
registrator: config.service_username,
referer: body.referer ? body.referer : '',
coopname: config.coopname,
meta: '',
});
console.log('user: ', user);
const token = await tokenService.generateInviteToken(user.email);
await emailService.sendInviteEmail(req.body.email, token);
} catch (e: any) {
console.log('on e: ', e);
logger.warn('error on add user: ', e);
await userService.deleteUserByUsername(newUser.username);
throw new ApiError(httpStatus.BAD_GATEWAY, e.message);
}
res.status(httpStatus.CREATED).send({ user });
});
export const joinCooperative = catchAsync(async (req: RJoinCooperative, res) => {
await userService.joinCooperative(req.body);
@@ -44,10 +85,10 @@ export const getUsers = catchAsync(async (req, res) => {
const result = {} as IGetResponse<IUser>;
const data = [] as any;
for await (let user of users.results) {
for await (const user of users.results) {
const json = user.toJSON();
if (user.type != 'service') {
json.private_data = (await user.getPrivateData()) || {};
json.private_data = await user.getPrivateData();
data.push(json);
}
}
@@ -68,7 +109,9 @@ export const getUser = catchAsync(async (req, res) => {
}
const json = user.toJSON();
json.private_data = await user.getPrivateData();
if (user.type != 'service') {
json.private_data = await user.getPrivateData();
}
res.send(json);
});
@@ -82,6 +125,6 @@ export const updateUser = catchAsync(async (req, res) => {
});
export const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserById(req.params.username);
await userService.deleteUserByUsername(req.params.username);
res.status(http.NO_CONTENT).send();
});
+8 -4
View File
@@ -1,22 +1,26 @@
export * from './types';
import mongoose from 'mongoose';
import app from './app';
import config from './config/config';
import logger from './config/logger';
import { updateAuth } from './services/auth.service';
import { connectGenerator } from './services/document.service';
import { initSocketConnection } from './controllers/ws.controller';
import { monoService } from './services';
const SERVER_URL: string = process.env.SOCKET_SERVER || 'http://localhost:2222';
let server: any;
mongoose.connect(config.mongoose.url, config.mongoose.options).then(async () => {
mongoose.connect(config.mongoose.url).then(async () => {
logger.info('Connected to MongoDB');
//подключаемся к хранилищу приватных данных
await monoService.init();
// подключаемся к хранилищу приватных данных
await connectGenerator();
//подключаемся к ws-серверу
// подключаемся к ws-серверу
await initSocketConnection(SERVER_URL);
server = app.listen(config.port, () => {
+1
View File
@@ -1,3 +1,4 @@
export { default as Token } from './token.model';
export { default as User } from './user.model';
export { default as Order } from './order.model';
export { default as Mono } from './mono.model';
@@ -0,0 +1,26 @@
import mongoose, { Schema } from 'mongoose';
import { paginate, toJSON } from './plugins';
import { IHealthStatus } from '../types';
export interface IMono {
coopname: string;
status: IHealthStatus;
}
const Status = {
Install: 'install',
Active: 'active',
Maintenance: 'maintenance',
};
const MonoSchema = new Schema<IMono>({
coopname: { type: String, required: true },
status: { type: String, required: true, enum: Object.values(Status) },
});
MonoSchema.plugin(toJSON);
MonoSchema.plugin(paginate);
const MonoModel = mongoose.model<IMono>('Mono', MonoSchema);
export default MonoModel;
+52 -50
View File
@@ -5,9 +5,9 @@ interface IOrder extends Document {
creator: string;
type: string;
data: {
username: string
provider: string
quantity: string
username: string;
provider: string;
quantity: string;
};
order_id?: number;
payed?: boolean;
@@ -16,59 +16,61 @@ interface IOrder extends Document {
expired_at?: Date;
}
const orderSchema = new Schema<IOrder>({
creator: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
data: {
provider: {
type: String,
required: true
},
username: {
const orderSchema = new Schema<IOrder>(
{
creator: {
type: String,
required: true,
},
quantity: {
type: {
type: String,
required: true
}
},
order_id: {
type: Number,
required: false,
},
payed: {
type: Boolean,
required: false,
default: false,
},
delivered: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
},
expired_at: {
type: Date,
default: () => {
const now = new Date();
now.setDate(now.getDate() + 1); // Увеличиваем на одни сутки
return now;
required: true,
},
data: {
provider: {
type: String,
required: true,
},
username: {
type: String,
required: true,
},
quantity: {
type: String,
required: true,
},
},
order_id: {
type: Number,
required: false,
},
payed: {
type: Boolean,
required: false,
default: false,
},
delivered: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
},
expired_at: {
type: Date,
default: () => {
const now = new Date();
now.setDate(now.getDate() + 1); // Увеличиваем на одни сутки
return now;
},
},
},
},
{
timestamps: true,
});
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
orderSchema.plugin(toJSON);
@@ -25,7 +25,7 @@ const tokenSchema = new Schema<IToken>(
},
type: {
type: String,
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL],
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL, tokenTypes.INVITE],
required: true,
},
expires: {
+6 -32
View File
@@ -1,4 +1,4 @@
import mongoose, { Document, Schema, model, Model } from 'mongoose';
import mongoose, { Schema, model, Model } from 'mongoose';
import validator from 'validator/index';
import bcryptjs from 'bcryptjs';
import { toJSON, paginate } from './plugins/index';
@@ -14,16 +14,16 @@ export interface IUser {
status: 'created' | 'joined' | 'payed' | 'registered' | 'active' | 'failed' | 'blocked';
message: string;
is_registered: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
has_account: boolean;
type: 'individual' | 'entrepreneur' | 'organization' | 'service';
public_key: string;
referer: string;
email: string;
password: string;
role: string;
is_email_verified: boolean;
statement: {
hash: string;
meta: Object;
meta: object;
public_key: string;
signature: string;
};
@@ -35,7 +35,6 @@ export interface IUser {
getPrivateData(): Promise<
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
>;
isPasswordMatch(password: string): Promise<boolean>;
}
interface IUserModel extends Model<IUser> {
@@ -71,7 +70,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
public_key: {
type: String,
required: true,
default: '',
},
referer: {
type: String,
@@ -79,7 +78,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
email: {
type: String,
required: true,
required: false,
unique: true,
trim: true,
lowercase: true,
@@ -89,18 +88,6 @@ const userSchema = new Schema<IUser, IUserModel>(
}
},
},
password: {
type: String,
required: true,
trim: true,
minlength: 8,
validate(value) {
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
throw new Error('Password must contain at least one letter and one number');
}
},
private: true, // used by the toJSON plugin
},
role: {
type: String,
enum: roles,
@@ -162,19 +149,6 @@ userSchema.methods.getPrivateData = async function (): Promise<
return result;
};
userSchema.methods.isPasswordMatch = async function (password) {
const user = this;
return compare(password, user.password);
};
userSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password')) {
user.password = await hash(user.password, 8);
}
next();
});
const User = model<IUser, IUserModel>('User', userSchema);
export default User;
@@ -10,8 +10,8 @@ const router = Router();
router.post('/login', validate(authValidation.RLogin), authController.login);
router.post('/logout', validate(authValidation.RLogout), authController.logout);
router.post('/refresh-tokens', validate(authValidation.RRefreshTokens), authController.refreshTokens);
router.post('/forgot-password', validate(authValidation.RForgotPassword), authController.forgotPassword);
router.post('/reset-password', validate(authValidation.RResetPassword), authController.resetPassword);
router.post('/lost-key', validate(authValidation.RForgotKey), authController.lostKey);
router.post('/reset-key', validate(authValidation.RResetKey), authController.resetKey);
router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
router.post('/verify-email', validate(authValidation.RVerifyEmail), authController.verifyEmail);
@@ -2,15 +2,14 @@ import { Router } from 'express';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import * as coopValidation from '../../validations/coop.validation';
// import { loadAgenda, loadStaff, } from '../../validations/coop.validation';
import * as coopController from '../../controllers/coop.controller';
const router = Router();
router.route('/agenda').get(auth('loadAgenda'), validate(coopValidation.loadAgenda), coopController.loadAgenda);
router.route('/staff').get(auth('loadStaff'), validate(coopValidation.loadStaff), coopController.loadStaff);
router.route('/contacts').get(coopController.loadContacts);
router.route('/info').get(auth('loadInfo'), coopController.loadInfo);
router.route('/contacts').get(coopController.loadContacts);
export default router;
@@ -4,6 +4,7 @@ import userRoute from './user.route';
import docsRoute from './docs.route';
import paymentRoute from './payment.route';
import coopRoute from './coop.route';
import monoRoute from './mono.route';
import dataRoute from './document.route';
import notifyRoute from './notify.route';
@@ -12,6 +13,10 @@ import config from '../../config/config';
const router = Router();
const defaultRoutes = [
{
path: '/mono',
route: monoRoute,
},
{
path: '/auth',
route: authRoute,
@@ -0,0 +1,12 @@
import { Router } from 'express';
import { monoController } from '../../controllers';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import { monoValidation } from '../../validations';
const router = Router();
router.route('/install').post(auth('install'), validate(monoValidation.RInstall), monoController.install);
router.route('/health').get(monoController.getHealth);
export default router;
@@ -11,9 +11,9 @@ router
.post(validate(userValidation.RCreateUser), userController.createUser)
.get(auth('getUsers'), validate(userValidation.RGetUsers), userController.getUsers);
router
.route('/join-cooperative')
.post(validate(userValidation.RJoinCooperative), userController.joinCooperative)
router.route('/join-cooperative').post(validate(userValidation.RJoinCooperative), userController.joinCooperative);
router.route('/add').post(auth('addUser'), validate(userValidation.RAddUser), userController.addUser);
router
.route('/:username')
@@ -1,68 +1,50 @@
import httpStatus from 'http-status';
import * as tokenService from './token.service';
import * as userService from './user.service';
import { getSoviet } from './blockchain.service';
import Token from '../models/token.model';
import ApiError from '../utils/ApiError';
import { tokenTypes } from '../config/tokens';
import { IRefreshTokens } from '../types';
import { getUserByEmail } from './user.service';
import { Bytes, Checksum256, Signature } from '@wharfkit/antelope';
import { getBlockchainAccount, getBlockchainInfo, hasActiveKey } from '../services/blockchain.service';
import config from '../config/config';
import { blockchainService } from '.';
export const updateAuth = async () => {
try {
const board = await getSoviet(process.env.COOPNAME);
// TODO снимать права с тех, кто уже не в совете
// eslint-disable-next-line no-restricted-syntax
for (const member of board.members) {
// eslint-disable-next-line no-await-in-loop
const user = await userService.getUserByUsername(member.username);
if (member.position === 'chairman' && !user) {
// eslint-disable-next-line no-await-in-loop
// await userService.createUser({
// username: member.username,
// public_key: '-',
// email: process.env.CHAIRMAN_EMAIL ,
// password: process.env.CHAIRMAN_PASSWORD as string,
// is_registered: true,
// is_organization: false,
// user_profile: {
// first_name: 'Имя',
// last_name: 'Фамилия',
// middle_name: 'Отчество',
// birthday: '23-42-3423',
// phone: '+7902294404',
// },
// signature: '-',
// signature_hash: '-',
// role: 'chairman',
// });
} else if (member.position === 'chairman' && user) {
user.role = 'chairman';
user.password = process.env.CHAIRMAN_PASSWORD || 'password';
// eslint-disable-next-line no-await-in-loop
await user.save();
} else if (user) {
user.role = 'admin';
// eslint-disable-next-line no-await-in-loop
await user.save();
}
export const loginUserWithSignature = async (email, now, signature) => {
const user = await getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Пользователь не найден');
}
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const wharf_signature = Signature.from(signature);
const publicKey = wharf_signature.recoverDigest(checksum).toLegacyString();
const info = await getBlockchainInfo();
const blockchainDate = new Date(info.head_block_time).getTime();
const userData = new Date(now).getTime();
const differenceInSeconds = (blockchainDate - userData) / 1000;
if (differenceInSeconds > 30) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Время подписи и время блокчейна превышает допустимое расхождение');
}
if (config.env !== 'test') {
try {
const blockchainAccount = await getBlockchainAccount(user.username);
const hasKey = hasActiveKey(blockchainAccount, publicKey);
if (!hasKey) throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
} catch (e) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
}
} catch (e: any) {
// eslint-disable-next-line no-console
console.log('Ошибка при автоматической проверке целевой авторизации: ', e.message);
}
};
/**
* Login with email and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
export const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};
@@ -76,7 +58,7 @@ export const logout = async (refreshToken) => {
if (!refreshTokenDoc) {
throw new ApiError(httpStatus.NOT_FOUND, 'Not found');
}
await refreshTokenDoc.remove();
await refreshTokenDoc.deleteOne();
};
/**
@@ -91,7 +73,7 @@ export const refreshAuth = async (data: IRefreshTokens) => {
if (!user) {
throw new Error();
}
await refreshTokenDoc.remove();
await refreshTokenDoc.deleteOne();
return tokenService.generateAuthTokens(user);
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
@@ -99,22 +81,34 @@ export const refreshAuth = async (data: IRefreshTokens) => {
};
/**
* Reset password
* @param {string} resetPasswordToken
* @param {string} newPassword
* Reset key
* @param {string} resetKeyToken
* @param {string} publicKey
* @returns {Promise}
*/
export const resetPassword = async (resetPasswordToken, newPassword) => {
export const resetKey = async (resetKeyToken, publicKey) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD);
const user = await userService.getUserById(resetPasswordTokenDoc.user);
const resetKeyTokenDoc = await tokenService.verifyToken(resetKeyToken, [tokenTypes.RESET_PASSWORD, tokenTypes.INVITE]);
const user = await userService.getUserById(resetKeyTokenDoc.user);
if (!user) {
throw new Error();
}
await userService.updateUserById(user._id, { password: newPassword });
if (config.env !== 'test')
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.service_username,
username: user.username,
public_key: publicKey,
});
await userService.updateUserById(user._id, { public_key: publicKey });
await Token.deleteMany({ user: user._id, type: tokenTypes.RESET_PASSWORD });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
console.log(error);
throw new ApiError(httpStatus.UNAUTHORIZED, 'Token reset failed');
}
};
@@ -6,9 +6,30 @@ import EosApi from 'eosjs-api';
import getInternalAction from '../utils/getInternalAction';
import { GatewayContract, RegistratorContract, SovietContract } from 'cooptypes';
import { IUser } from '../models/user.model';
import { GetAccountResult, GetInfoResult } from 'eosjs/dist/eosjs-rpc-interfaces';
import config from '../config/config';
const rpc = new JsonRpc(process.env.BLOCKCHAIN_RPC as string, { fetch });
function hasActiveKey(account, publicKey) {
const activePermissions = account.permissions.find((p: any) => p.perm_name === 'active');
if (!activePermissions) return false;
return activePermissions.required_auth.keys.some((key: any) => key.key === publicKey);
}
async function getBlockchainInfo(): Promise<GetInfoResult> {
const api = getApi();
return await api.getInfo({});
}
async function getBlockchainAccount(username): Promise<GetAccountResult> {
const api = getApi();
return await api.getAccount(username);
}
/**
* Получить инстанс для осуществления транзакции в блокчейн.
* @param {username} - авторизация от аккаунта
@@ -22,7 +43,7 @@ async function getInstance(wif) {
return api;
}
async function getApi() {
function getApi() {
const options = {
httpEndpoint: process.env.BLOCKCHAIN_RPC, // default, null for cold-storage
verbose: false, // API logging
@@ -63,9 +84,9 @@ async function getCooperative(coopname) {
const [cooperative] = await lazyFetch(
api,
process.env.REGISTRATOR_CONTRACT,
process.env.REGISTRATOR_CONTRACT,
'orgs',
RegistratorContract.contractName.production,
RegistratorContract.contractName.production,
RegistratorContract.Tables.Cooperatives.tableName,
coopname,
coopname,
1
@@ -157,6 +178,36 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
);
}
async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
const eos = await getInstance(config.service_wif);
console.log('data: ', data);
const actions = [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Boards.CreateBoard.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
async function createOrder(data) {
const eos = await getInstance(process.env.REGISTRATOR_WIF);
@@ -245,6 +296,62 @@ async function failOrder(data) {
);
}
export async function addUser(data: RegistratorContract.Actions.AddUser.IAddUser) {
const eos = await getInstance(config.service_wif);
const actions = [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
export async function changeKey(data: RegistratorContract.Actions.ChangeKey.IChangeKey) {
const eos = await getInstance(config.service_wif);
const actions = [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.ChangeKey.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
async function getSoviet(coopname) {
const api = await getApi();
@@ -271,4 +378,8 @@ export {
failOrder,
completeOrder,
getSoviet,
getBlockchainInfo,
getBlockchainAccount,
hasActiveKey,
createBoard,
};
@@ -4,6 +4,7 @@ import { userService, blockchainService } from './index';
import { Cooperative, RegistratorContract, SovietContract } from 'cooptypes';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import logger from '../config/logger';
export const loadAgenda = async (coopname: string): Promise<Cooperative.Documents.IAgenda[]> => {
const api = await blockchainService.getApi();
@@ -85,7 +86,9 @@ export const loadContacts = async (coopname: string) => {
if (coopAccount.meta) {
try {
announce = JSON.parse(coopAccount.meta);
} catch (e: any) {}
} catch (e: any) {
logger.warn(`Ошибка при получении контактов: ${e.message}`);
}
}
return {
@@ -1,14 +1,9 @@
import eosjsecc from 'eosjs-ecc';
import ApiError from '../utils/ApiError';
import { getActions, getTables } from '../utils/getFetch';
import * as coopService from './coop.service';
const { verify, sha256 } = eosjsecc;
import { getActions } from '../utils/getFetch';
import config from '../config/config';
import { Generator } from 'coopdoc-generator-ts';
import type { IGenerate, IGetDocuments } from '../types';
import type { IGenerate } from '../types';
import { Cooperative, SovietContract } from 'cooptypes';
import { User } from '../models';
import { IUser } from '../models/user.model';
export const generator = new Generator();
@@ -35,7 +30,7 @@ export async function buildComplexDocument(
const document = await generator.getDocument({ hash: raw_document.document.hash });
const user = await User.findOne({ username: raw_document.username });
if (user) {
if (user && user.type != 'service') {
const user_data = await user?.getPrivateData();
const action: Cooperative.Blockchain.IExtendedAction = {
@@ -99,8 +94,8 @@ export async function buildComplexDocument(
export const queryDocuments = async (
filter: any,
page: number = 1,
limit: number = 100
page = 1,
limit = 100
): Promise<Cooperative.Documents.IGetComplexDocuments> => {
const actions = await getActions<SovietContract.Actions.Registry.NewResolved.INewResolved>(
`${process.env.SIMPLE_EXPLORER_API}/get-actions`,
@@ -116,7 +111,7 @@ export const queryDocuments = async (
}
);
let response: Cooperative.Documents.IGetComplexDocuments = {
const response: Cooperative.Documents.IGetComplexDocuments = {
results: [],
page,
limit,
@@ -2,9 +2,9 @@ import { createTransport } from 'nodemailer';
import config from '../config/config';
import logger from '../config/logger';
const { email, env } = config
const { email, env } = config;
const transport = createTransport(email.smtp);
export const transport = createTransport(email.smtp);
/* istanbul ignore next */
if (env !== 'test') {
@@ -21,7 +21,7 @@ if (env !== 'test') {
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to, subject, text) => {
export const sendEmail = async (to, subject, text) => {
const msg = { from: email.from, to, subject, text };
await transport.sendMail(msg);
};
@@ -32,13 +32,33 @@ const sendEmail = async (to, subject, text) => {
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to, token) => {
const subject = 'Reset password';
// replace this url with the link to the reset password page of your front-end app
const resetPasswordUrl = `http://link-to-app/reset-password?token=${token}`;
const text = `Dear user,
To reset your password, click on this link: ${resetPasswordUrl}
If you did not request any password resets, then ignore this email.`;
export const sendResetKeyEmail = async (to, token) => {
const subject = 'Восстановление доступа';
const resetPasswordUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
const text = `Мы получили запрос на перевыпуск приватного ключа,
Для перевыпуска нажмите на ссылку: ${resetPasswordUrl}. Время действия ссылки - 10 минут.
Если вы не запрашивали перевыпуск ключа - проигнорируйте это сообщение.`;
await sendEmail(to, subject, text);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
export const sendInviteEmail = async (to, token) => {
const subject = 'Приглашение в Цифровой Кооператив';
const inviteUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
const text = `Вам отправлено приглашение на подключение к Цифровому Кооперативу в качестве действующего пайщика.
Для того, чтобы воспользоваться приглашением и получить ключ доступа, пожалуйста, нажмите на ссылку: ${inviteUrl}
Время действия ссылки - 24 часа.`;
await sendEmail(to, subject, text);
};
@@ -48,7 +68,7 @@ If you did not request any password resets, then ignore this email.`;
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to, token) => {
export const sendVerificationEmail = async (to, token) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
@@ -57,10 +77,3 @@ To verify your email, click on this link: ${verificationEmailUrl}
If you did not create an account, then ignore this email.`;
await sendEmail(to, subject, text);
};
export {
transport,
sendEmail,
sendResetPasswordEmail,
sendVerificationEmail,
};
@@ -7,3 +7,4 @@ export * as paymentService from './payment.service';
export * as documentService from './document.service';
export * as coopService from './coop.service';
export * as wsService from './ws.service';
export * as monoService from './mono.service';
@@ -0,0 +1,131 @@
import httpStatus from 'http-status';
import { Mono } from '../models';
import ApiError from '../utils/ApiError';
import config from '../config/config';
import logger from '../config/logger';
import { IAddUser, ICreateUser, IHealthStatus, IInstall } from '../types';
import { generateUsername } from '../../tests/utils/generateUsername';
import { generator } from './document.service';
import { blockchainService, emailService, tokenService, userService } from '.';
import { IUser } from '../models/user.model';
import axios from 'axios';
import { getBlockchainInfo } from './blockchain.service';
import { RegistratorContract } from 'cooptypes';
export const install = async (soviet: IInstall): Promise<void> => {
console.log('IInstall: ', soviet);
const mono = await Mono.findOne({ coopname: config.coopname });
const info = await getBlockchainInfo();
const coop = await blockchainService.getCooperative(config.coopname);
if (!coop) throw new ApiError(httpStatus.BAD_REQUEST, 'Информация о кооперативе не обнаружена');
if (mono && mono.status != 'install') {
throw new ApiError(httpStatus.BAD_REQUEST, 'Установка уже выполнена');
}
const users = [] as IUser[];
const members = [] as any;
const sovietExt = [] as any;
try {
for (const member of soviet) {
console.log(member);
const username = generateUsername();
sovietExt.push({ ...member, username });
const addUser: RegistratorContract.Actions.AddUser.IAddUser = {
registrator: config.service_username,
coopname: config.coopname,
referer: '',
username,
type: 'individual',
created_at: info.head_block_time,
initial: coop.initial,
minimum: coop.minimum,
spread_initial: false,
meta: '',
};
await blockchainService.addUser(addUser);
const createUser: ICreateUser = {
email: member.individual_data.email,
individual_data: member.individual_data,
referer: '',
role: 'user',
type: 'individual',
username,
};
const user = await userService.createUser(createUser);
user.status = 'registered';
user.is_registered = true;
await user.save();
//Генерируем токен и отправляем приглашение
const token = await tokenService.generateInviteToken(member.individual_data.email);
await emailService.sendInviteEmail(member.individual_data.email, token);
//Добавляем в массив членов для отправки в бч
members.push({
username: username,
is_voting: true,
position_title: member.role === 'chairman' ? 'Председатель совета' : 'Член совета',
position: member.role,
});
users.push(user);
}
const chairman = sovietExt.find((el) => el.role == 'chairman');
//TODO создаёт доску совета
await blockchainService.createBoard({
coopname: config.coopname,
username: config.service_username,
type: 'soviet',
members: members,
name: 'Совет',
description: '',
});
} catch (e: any) {
console.log('on error', e);
for (const user of users) {
await userService.deleteUserByUsername(user.username);
await generator.del('individual', { username: user.username });
}
throw new ApiError(httpStatus.BAD_REQUEST, e.message);
}
await Mono.updateOne(
{ coopname: config.coopname },
{
status: 'active',
}
);
logger.info('MONO активирован');
};
export const init = async (): Promise<void> => {
const mono = await Mono.findOne({ coopname: config.coopname });
if (!mono)
await Mono.create({
coopname: config.coopname,
status: 'install',
});
logger.info('MONO инициализирован');
};
export const getMonoStatus = async (): Promise<IHealthStatus> => {
const mono = await Mono.findOne({ coopname: config.coopname });
if (!mono) throw new ApiError(httpStatus.BAD_REQUEST, 'Установщик не найден');
return mono.status;
};
@@ -1 +1 @@
export default {};
@@ -40,7 +40,7 @@ function getAmountPlusFee(amount: number, provider: string): number {
}
export async function createDeposit(username: string, data: ICreateDeposit): Promise<ICreatedPayment> {
let cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
if (!cooperative) throw new Error('Кооператив не найден');
@@ -50,7 +50,7 @@ export async function createDeposit(username: string, data: ICreateDeposit): Pro
type: 'deposit',
data: { username, provider: data.provider, quantity: data.quantity },
});
const internal_id = db_order._id;
const internal_id = db_order.id;
let order_id = 0;
@@ -118,9 +118,9 @@ export async function createDeposit(username: string, data: ICreateDeposit): Pro
}
export async function createInitialOrder(username: string, data: ICreateInitialPayment): Promise<ICreatedPayment> {
let cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
const cooperative = await generator.constructCooperative(process.env.COOPNAME as string);
if (!cooperative) throw new Error('Кооператив не найден');
if (!cooperative) throw new ApiError(httpStatus.BAD_REQUEST, 'Кооператив не найден');
const user = await getUserByUsername(username);
let amount = '';
@@ -139,7 +139,7 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
type: 'registration',
data: { provider: data.provider, username, quantity: amount },
});
const internal_id = db_order._id;
const internal_id = db_order.id;
if (data.provider === 'yookassa') {
// 2. Используешь внутренний айди для получения ордера в системе платежей
@@ -198,7 +198,10 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
}
export async function catchIPN(ipnBody: IYandexIPN) {
if (!connection.db) throw new Error('Нет подключения');
const payments = await connection.db.collection('payments');
const exist = await payments.findOne({ 'object.id': ipnBody.object.id });
if (!exist) {
@@ -237,7 +240,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
memo: '',
});
console.log('Зарегистрирован новый пользователь: ', user.username);
logger.info('Зарегистрирован новый пользователь: ', user.username);
user.status = 'registered';
user.is_registered = true;
} else if (order.type === 'deposit') {
@@ -248,7 +251,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
memo: '',
});
const quantity = parseFloat(order.data.quantity);
console.log(`Принят паевый взнос пользователя: ${user.username} на сумму ${order.data.quantity}`);
logger.info(`Принят паевый взнос пользователя: ${user.username} на сумму ${order.data.quantity}`);
}
await user.save();
@@ -256,7 +259,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
order.delivered = true;
await order.save();
} catch (e: any) {
console.error(e);
logger.warn(e);
order.error = e;
order.delivered = false;
await order.save();
@@ -281,6 +284,7 @@ export async function catchIPN(ipnBody: IYandexIPN) {
});
}
} else {
logger.warn(`Ордер не найден: `, ipnBody);
throw new Error(`Ордер не найден`);
}
}
@@ -48,12 +48,17 @@ export const saveToken = async (token, userId, expires, type, blacklisted = fals
/**
* Verify token and return token doc (or throw an error if it is not valid)
* @param {string} token
* @param {string} type
* @param {string[]} types
* @returns {Promise<Token>}
*/
export const verifyToken = async (token, type) => {
export const verifyToken = async (token, types) => {
const payload = jwt.verify(token, config.jwt.secret);
const tokenDoc = await Token.findOne({ token, type, user: payload.sub, blacklisted: false });
const tokenDoc = await Token.findOne({
token,
$or: types.map((type) => ({ type })),
user: payload.sub,
blacklisted: false,
});
if (!tokenDoc) {
throw new Error('Token not found');
}
@@ -68,7 +73,7 @@ export const verifyToken = async (token, type) => {
export const generateServiceAccessToken = async (user) => {
const accessTokenExpires = moment().add(100, 'years'); // Пример с очень долгим сроком действия
const accessToken = generateToken(user.id, accessTokenExpires, tokenTypes.ACCESS);
console.log('on generate', accessToken);
return {
access: {
token: accessToken,
@@ -107,15 +112,31 @@ export const generateAuthTokens = async (user) => {
* @param {string} email
* @returns {Promise<string>}
*/
export const generateResetPasswordToken = async (email) => {
export const generateResetKeyToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(user.id, expires, tokenTypes.RESET_PASSWORD);
await saveToken(resetPasswordToken, user.id, expires, tokenTypes.RESET_PASSWORD);
return resetPasswordToken;
const resetKeyToken = generateToken(user.id, expires, tokenTypes.RESET_PASSWORD);
await saveToken(resetKeyToken, user.id, expires, tokenTypes.RESET_PASSWORD);
return resetKeyToken;
};
/**
* Generate invite token
* @param {string} email
* @returns {Promise<string>}
*/
export const generateInviteToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.inviteExpirationMinutes, 'minutes');
const inviteToken = generateToken(user.id, expires, tokenTypes.INVITE);
await saveToken(inviteToken, user.id, expires, tokenTypes.INVITE);
return inviteToken;
};
/**
@@ -2,21 +2,15 @@ import http from 'http-status';
import { User } from '../models';
import ApiError from '../utils/ApiError';
import { generator } from './document.service';
import { ICreateUser, IJoinCooperative } from '../types/user.validation';
import ecc from 'eosjs-ecc';
import { ICreateUser, IJoinCooperative } from '../types/auto-generated/user.validation';
import { PublicKey, Signature } from '@wharfkit/antelope';
import faker from 'faker';
import { randomBytes } from 'crypto';
export const createServiceUser = async (username: string) => {
const password = randomBytes(16).toString('hex');
return User.create({
username,
type: 'service',
role: 'service',
public_key: 'thanks-no-need',
password,
email: faker.internet.email().toLowerCase(),
});
};
@@ -28,6 +22,7 @@ export const createServiceUser = async (username: string) => {
export const createUser = async (userBody: ICreateUser) => {
//TODO проверяем на существование пользователя
//допускаем обновление личных данных, если пользователь находится в статусе 'created'
const exist = await User.findOne({ email: userBody.email });
if (userBody.type === 'individual' && !userBody.individual_data)
@@ -186,11 +181,11 @@ export const updateUserByUsername = async (username, updateBody) => {
* @param {string} username
* @returns {Promise<User>}
*/
export const deleteUserById = async (username) => {
export const deleteUserByUsername = async (username) => {
const user = await getUserByUsername(username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
}
await user.remove();
await user.deleteOne();
return user;
};
@@ -16,7 +16,9 @@ export const updateBoard = async (action: SovietContract.Actions.Boards.UpdateBo
await user.save();
}
const chairman = await userService.getUserByUsername(action.chairman);
const chairman_username = action.members.find((el) => el.position == 'chairman')?.username;
const chairman = await userService.getUserByUsername(chairman_username as string);
chairman.role = 'chairman';
await chairman.save();
};
@@ -9,7 +9,8 @@ export interface IForgotPassword {
export interface ILogin {
email: string;
password: string;
now: string;
signature: string;
}
export interface ILogout {
@@ -20,11 +21,8 @@ export interface IRefreshTokens {
refreshToken: string;
}
export interface IResetPasswordBody {
password: string;
}
export interface IResetPasswordQuery {
export interface IResetKeyBody {
public_key: string;
token: string;
}
@@ -32,7 +30,7 @@ export interface IVerifyEmail {
token: string;
}
export interface RForgotPassword {
export interface RForgotKey {
body: {
email: string;
};
@@ -41,7 +39,8 @@ export interface RForgotPassword {
export interface RLogin {
body: {
email: string;
password: string;
now: string;
signature: string;
};
}
@@ -57,11 +56,9 @@ export interface RRefreshTokens {
};
}
export interface RResetPassword {
export interface RResetKey {
body: {
password: string;
};
query: {
public_key: string;
token: string;
};
}
@@ -0,0 +1,12 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export * from './auth.validation';
export * from './coop.validation';
export * from './document.validation';
export * from './mono.validation';
export * from './notify.validation';
export * from './payment.validation';
export * from './user.validation';
@@ -0,0 +1,32 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type IInstall = {
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
}[];
export interface RInstall {
body: {
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
}[];
}
@@ -3,6 +3,84 @@
* Do not modify this file manually
*/
export interface IAddUser {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
}
export interface ICreateUser {
email: string;
entrepreneur_data?: {
@@ -73,10 +151,9 @@ export interface ICreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
public_key?: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -169,6 +246,86 @@ export interface IOrganizationData {
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
}
export interface RAddUser {
body: {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
};
}
export interface RCreateUser {
body: {
email: string;
@@ -240,10 +397,9 @@ export interface RCreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
public_key?: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
};
@@ -280,7 +436,6 @@ export interface RJoinCooperative {
export interface RUpdateUser {
body?: {
email?: string;
password?: string;
};
params?: {
username: string;
+1 -1
View File
@@ -122,7 +122,7 @@ export interface IAction {
elapsed: number;
}
export interface IExtendedTable extends ITable {}
export type IExtendedTable = ITable;
export interface IExtendedAction extends IAction {
user: IIndividualData | IEntrepreneurData | IOrganizationData | null;
@@ -1,74 +0,0 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface IGenerate {
/**
* Unknown Property
*/
[x: string]: unknown;
action: string;
block_num?: number;
code: string;
coopname: string;
created_at?: string;
generator?: string;
lang?: 'ru';
timezone?: string;
username: string;
version?: string;
}
export interface IGetDocuments {
/**
* Unknown Property
*/
[x: string]: unknown;
filter?: {
receiver?: string;
};
limit?: number;
page?: number;
sortBy?: string;
}
export interface RGenerate {
body: {
/**
* Unknown Property
*/
[x: string]: unknown;
action: string;
block_num?: number;
code: string;
coopname: string;
created_at?: string;
generator?: string;
lang?: 'ru';
timezone?: string;
username: string;
version?: string;
};
}
export interface RGetDocuments {
query?: {
/**
* Unknown Property
*/
[x: string]: unknown;
filter?: {
receiver?: string;
};
limit?: number;
page?: number;
sortBy?: string;
};
}
export interface RGetMyDocuments {
query?: {
username?: string;
};
}
+3 -11
View File
@@ -1,11 +1,3 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export * from './auth.validation';
export * from './coop.validation';
export * from './document.validation';
export * from './notify.validation';
export * from './payment.validation';
export * from './user.validation';
export * from './auto-generated';
export * from './mono.types';
export * from './common';
@@ -0,0 +1,8 @@
import { GetInfoResult } from 'eosjs/dist/eosjs-rpc-interfaces';
export type IHealthStatus = 'install' | 'active' | 'maintenance';
export interface IHealthResponse {
status: IHealthStatus;
blockchain: GetInfoResult;
}
@@ -1,100 +0,0 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface ICreateDeposit {
provider: 'yookassa';
quantity: string;
}
export interface ICreateInitialPayment {
provider: 'yookassa';
}
export interface IRecieveIPN {
event: string;
object: {
/**
* Unknown Property
*/
[x: string]: unknown;
amount: {
/**
* Unknown Property
*/
[x: string]: unknown;
currency: string;
value: string;
};
authorization_details?: {
/**
* Unknown Property
*/
[x: string]: unknown;
auth_code?: string;
rrn?: string;
three_d_secure?: {
/**
* Unknown Property
*/
[x: string]: unknown;
applied?: boolean;
};
};
created_at?: string;
description?: string;
expires_at?: string;
id: string;
income_amount: {
/**
* Unknown Property
*/
[x: string]: unknown;
currency: string;
value: string;
};
metadata?: {
/**
* Unknown Property
*/
[x: string]: unknown;
};
paid: boolean;
payment_method: {
/**
* Unknown Property
*/
[x: string]: unknown;
card?: {
/**
* Unknown Property
*/
[x: string]: unknown;
card_type?: string;
expiry_month?: string;
expiry_year?: string;
first6?: string;
issuer_country?: string;
issuer_name?: string;
last4?: string;
};
id: string;
saved?: boolean;
title?: string;
type: string;
};
refundable?: boolean;
refunded_amount: {
/**
* Unknown Property
*/
[x: string]: unknown;
currency: string;
value: string;
};
status: string;
test?: boolean;
};
type: string;
}
@@ -14,28 +14,26 @@ export const IForgotPassword = Joi.object({
email: Joi.string().email().required(),
});
export const RForgotPassword = Joi.object({
export const RForgotKey = Joi.object({
body: IForgotPassword.required(),
});
// Внутренние параметры
export const ILogin = Joi.object({
signature: Joi.string().required(),
email: Joi.string().email().required(),
password: Joi.string().required(),
now: Joi.string().required(),
});
export const ILogout = Joi.object({
refreshToken: Joi.string().required(),
});
export const IResetPasswordQuery = Joi.object({
export const IResetKeyBody = Joi.object({
public_key: Joi.string().required(),
token: Joi.string().required(),
});
export const IResetPasswordBody = Joi.object({
password: Joi.string().required().custom(password),
});
export const IVerifyEmail = Joi.object({
token: Joi.string().required(),
});
@@ -49,9 +47,8 @@ export const RLogout = Joi.object({
body: ILogout.required(),
});
export const RResetPassword = Joi.object({
query: IResetPasswordQuery.required(),
body: IResetPasswordBody.required(),
export const RResetKey = Joi.object({
body: IResetKeyBody.required(),
});
export const RVerifyEmail = Joi.object({
@@ -5,3 +5,4 @@ export * as orderValidation from './payment.validation';
export * as coopValidation from './coop.validation';
export * as customValidation from './custom.validation';
export * as notifyValidation from './notify.validation';
export * as monoValidation from './mono.validation';
@@ -0,0 +1,13 @@
import * as Joi from 'joi';
import { IIndividualData } from './user.validation';
export const IInstall = Joi.array().items(
Joi.object({
role: Joi.string().required().valid('chairman', 'member'),
individual_data: IIndividualData.required(),
})
);
export const RInstall = Joi.object({
body: IInstall.required(),
});
@@ -1,5 +1,5 @@
import * as Joi from 'joi';
import { password, objectId } from './custom.validation';
import { password } from './custom.validation';
import { IBankAccount } from './payment.validation';
export const IIndividualData = Joi.object({
@@ -57,9 +57,8 @@ export const IEntrepreneurData = Joi.object({
export const ICreateUser = Joi.object({
email: Joi.string().required().email(),
password: Joi.string().required().custom(password),
role: Joi.string().required().valid('user', 'chairman', 'member'),
public_key: Joi.string().required(),
role: Joi.string().required().valid('user'),
public_key: Joi.string().optional(),
username: Joi.string().required().length(12),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
@@ -68,6 +67,25 @@ export const ICreateUser = Joi.object({
entrepreneur_data: IEntrepreneurData.optional(),
});
export const IAddUser = Joi.object({
email: Joi.string().required().email(),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
individual_data: IIndividualData.optional(),
organization_data: IOrganizationData.optional(),
entrepreneur_data: IEntrepreneurData.optional(),
spread_initial: Joi.boolean().required(),
created_at: Joi.string().required(),
initial: Joi.string().required(),
minimum: Joi.string().required(),
});
export const RAddUser = Joi.object({
body: IAddUser.required(),
});
export const IDocument = Joi.object().keys({
hash: Joi.string().required(),
signature: Joi.string().required(),
@@ -104,7 +122,6 @@ export const RUpdateUser = Joi.object({
body: Joi.object()
.keys({
email: Joi.string().email(),
password: Joi.string().custom(password),
})
.min(1),
});
+6 -6
View File
@@ -27,12 +27,12 @@ export const installInitialCooperativeData = async () => {
is_cooperative: true,
is_branched: false,
coop_type: 'conscoop',
registration: '2.0000 TRUB',
initial: '1.0000 TRUB',
minimum: '1.0000 TRUB',
org_registration: '20.0000 TRUB',
org_initial: '10.0000 TRUB',
org_minimum: '10.0000 TRUB',
registration: '200.0000 TRUB',
initial: '100.0000 TRUB',
minimum: '100.0000 TRUB',
org_registration: '2000.0000 TRUB',
org_initial: '1000.0000 TRUB',
org_minimum: '1000.0000 TRUB',
} as RegistratorContract.Tables.Cooperatives.ICooperative);
await insertDelta(delta1);
@@ -1,17 +1,11 @@
import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';
import faker from 'faker';
import User from '../../src/models/user.model';
import { generateUsername } from '../utils/generateUsername';
import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
const password = 'password1';
const email1 = faker.internet.email().toLowerCase();
export const participantOne: ICreateUser = {
email: email1,
password,
username: generateUsername(),
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
+6 -10
View File
@@ -7,10 +7,6 @@ import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
import { ObjectId } from 'mongodb';
const password = 'password1';
const salt = bcrypt.genSaltSync(8);
const hashedPassword = bcrypt.hashSync(password, salt);
const generateRandomId = () => new mongoose.Types.ObjectId();
type testUser = Omit<IUser, 'getPrivateData' | 'isPasswordMatch' | 'private_data'> & {
@@ -33,8 +29,8 @@ const adminUsername = generateUsername();
export const admin: testUser = {
_id: generateRandomId(),
email: email1,
password,
status: 'active',
has_account: false,
message: '',
is_registered: true,
role: 'chairman',
@@ -67,7 +63,7 @@ const usernameOne = generateUsername();
export const userOne: testUser = {
_id: generateRandomId(),
email: email2,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -101,7 +97,7 @@ const usernameTwo = generateUsername();
export const userTwo: testUser = {
_id: generateRandomId(),
email: email3,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -134,7 +130,7 @@ export const userTwo: testUser = {
export const chairman: testUser = {
_id: generateRandomId(),
email: email4,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -167,7 +163,7 @@ export const chairman: testUser = {
export const voskhod: testUser = {
_id: generateRandomId(),
email: email5,
password,
has_account: false,
status: 'active',
message: '',
is_registered: true,
@@ -254,7 +250,7 @@ export const insertUsers = async (users: testUser[]) => {
for (const user of users) {
const { type, individual_data, organization_data, entrepreneur_data, block_num, ...rest } = user;
await User.insertMany([{ ...rest, type, password: hashedPassword }]);
await User.insertMany([{ ...rest, type }]);
if (type === 'individual' && individual_data) {
await insertPrivateIndividualUserData({
@@ -16,6 +16,8 @@ import { tokenTypes } from '../../src/config/tokens';
import { userOne, admin, insertUsers } from '../fixtures/user.fixture';
import { userOneAccessToken, adminAccessToken } from '../fixtures/token.fixture';
import { generateUsername } from '../utils/generateUsername';
import { getBlockchainInfo } from '../../src/services/blockchain.service';
import { Bytes, Checksum256, PrivateKey } from '@wharfkit/session';
setupTestDB();
@@ -27,7 +29,6 @@ describe('Auth routes', () => {
newUser = {
email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -48,8 +49,6 @@ describe('Auth routes', () => {
test('should return 201 and successfully register user if request data is ok', async () => {
const res = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(res.body.user).not.toHaveProperty('password');
expect(res.body.user).toEqual({
id: expect.anything(),
email: newUser.email,
@@ -74,7 +73,6 @@ describe('Auth routes', () => {
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
expect(dbUser).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
expect(res.body.tokens).toEqual({
@@ -95,30 +93,24 @@ describe('Auth routes', () => {
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
newUser.password = 'password';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
newUser.password = '11111111';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/login', () => {
test('should return 200 and login user if email and password match', async () => {
test('should return 200 and login user if email and signature match', async () => {
await insertUsers([userOne]);
const now = (await getBlockchainInfo()).head_block_time;
const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const signature = privateKey.signDigest(checksum);
const loginCredentials = {
email: userOne.email,
password: userOne.password,
now,
signature,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials);
@@ -140,27 +132,46 @@ describe('Auth routes', () => {
});
test('should return 401 error if there are no users with that email', async () => {
const now = (await getBlockchainInfo()).head_block_time;
const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const signature = privateKey.signDigest(checksum);
const loginCredentials = {
now,
email: userOne.email,
password: userOne.password,
signature,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Пользователь не найден' });
});
test('should return 401 error if password is wrong', async () => {
await insertUsers([userOne]);
const loginCredentials = {
email: userOne.email,
password: 'wrongPassword1',
};
// test('should return 401 error if signature is wrong', async () => {
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
// Восстановить проверку. Необходимо подложить аккаунт тестового юзера в коде вместо обращения к блокчейну.
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
});
// await insertUsers([userOne]);
// const now = (await getBlockchainInfo()).head_block_time;
// const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
// const bytes = Bytes.fromString(now, 'utf8');
// const checksum = Checksum256.hash(bytes);
// const signature = privateKey.signDigest(checksum);
// const now2 = '2024-08-02T11:50:40.000';
// const loginCredentials = {
// email: userOne.email,
// now: now2,
// signature,
// };
// const res = await request(app).post('/v1/auth/login').send(loginCredentials);
// console.log(res.body);
// expect(res.status).toBe(httpStatus.UNAUTHORIZED);
// expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
// });
});
describe('POST /v1/auth/logout', () => {
@@ -260,47 +271,46 @@ describe('Auth routes', () => {
});
});
describe('POST /v1/auth/forgot-password', () => {
describe('POST /v1/auth/lost-key', () => {
beforeEach(() => {
jest.spyOn(emailService.transport, 'sendMail').mockResolvedValue();
});
test('should return 204 and send reset password email to the user', async () => {
test('should return 204 and send reset key email to the user', async () => {
await insertUsers([userOne]);
const sendResetPasswordEmailSpy = jest.spyOn(emailService, 'sendResetPasswordEmail');
const sendResetKeyEmailSpy = jest.spyOn(emailService, 'sendResetKeyEmail');
await request(app).post('/v1/auth/forgot-password').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
await request(app).post('/v1/auth/lost-key').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
expect(sendResetPasswordEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetPasswordToken = sendResetPasswordEmailSpy.mock.calls[0][1];
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetPasswordToken, user: userOne._id.toString() });
expect(sendResetKeyEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetKeyToken = sendResetKeyEmailSpy.mock.calls[0][1];
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetKeyToken, user: userOne._id.toString() });
expect(dbResetPasswordTokenDoc).toBeDefined();
});
test('should return 400 if email is missing', async () => {
await insertUsers([userOne]);
const res = await request(app).post('/v1/auth/forgot-password').send();
const res = await request(app).post('/v1/auth/lost-key').send();
expect(res.status).toBe(httpStatus.BAD_REQUEST);
});
test('should return 404 if email does not belong to any user', async () => {
await request(app).post('/v1/auth/forgot-password').send({ email: userOne.email }).expect(httpStatus.NOT_FOUND);
await request(app).post('/v1/auth/lost-key').send({ email: userOne.email }).expect(httpStatus.NOT_FOUND);
});
});
describe('POST /v1/auth/reset-password', () => {
test('should return 204 and reset the password', async () => {
describe('POST /v1/auth/reset-key', () => {
test('should return 204 and reset the key', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const res = await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' });
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: userOne.public_key });
expect(res.status).toBe(httpStatus.NO_CONTENT);
@@ -308,9 +318,6 @@ describe('Auth routes', () => {
expect(dbUser).not.toBeUndefined();
if (dbUser) {
const isPasswordMatch = await bcrypt.compare('password2', dbUser.password);
expect(isPasswordMatch).toBe(true);
const dbResetPasswordTokenCount = await Token.countDocuments({
user: userOne._id.toString(),
type: tokenTypes.RESET_PASSWORD,
@@ -319,76 +326,49 @@ describe('Auth routes', () => {
}
});
test('should return 400 if reset password token is missing', async () => {
test('should return 400 if reset key token is missing', async () => {
await insertUsers([userOne]);
await request(app).post('/v1/auth/reset-password').send({ password: 'password2' }).expect(httpStatus.BAD_REQUEST);
});
test('should return 401 if reset password token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD, true);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.post('/v1/auth/reset-key')
.send({ public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.BAD_REQUEST);
});
test('should return 401 if reset key token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD, true);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 if reset password token is expired', async () => {
test('should return 401 if reset key token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 if user is not found', async () => {
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 400 if password is missing or invalid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app).post('/v1/auth/reset-password').query({ token: resetPasswordToken }).expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'short1' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: '11111111' })
.expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/send-verification-email', () => {
@@ -56,7 +56,7 @@ describe('Проверка получения документов', () => {
skip_save: false,
};
let res = await request(app)
const res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
@@ -26,7 +26,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,7 +50,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -67,7 +67,6 @@ describe('Проверка получения документов', () => {
const email = faker.internet.email().toLowerCase();
const newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -144,7 +143,7 @@ describe('Проверка получения документов', () => {
statement: signedDocument,
};
let joincoop_result = await request(app)
const joincoop_result = await request(app)
.post('/v1/users/join-cooperative')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(joinCoopData);
@@ -4,10 +4,10 @@ import app from '../../src/app';
import { setupTestDB } from '../utils/setupTestDB';
import { User } from '../../src/models';
import { userOne, userTwo, admin, insertUsers } from '../fixtures/user.fixture';
import { userOneAccessToken, adminAccessToken } from '../fixtures/token.fixture';
import { userOneAccessToken, adminAccessToken, userTwoAccessToken } from '../fixtures/token.fixture';
import request from 'supertest';
import { generateUsername } from '../utils/generateUsername';
import { IDocument, IIndividualData, IJoinCooperative } from '../../src/types';
import { IIndividualData } from '../../src/types';
setupTestDB();
@@ -19,7 +19,6 @@ describe('User routes', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,11 +50,9 @@ describe('User routes', () => {
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user).not.toHaveProperty('password');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -65,23 +62,15 @@ describe('User routes', () => {
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: newUser.role, is_email_verified: false });
});
test('should be able to create an admin as well', async () => {
test('should not be able to create an admin as well', async () => {
await insertUsers([admin]);
newUser.role = 'member';
const res = await request(app)
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user.role).toBe('member');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser?.role).toBe('member');
.expect(httpStatus.BAD_REQUEST);
});
test('успешная регистрация без токена доступа', async () => {
@@ -122,36 +111,6 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
await insertUsers([admin]);
newUser.password = 'passwo1';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
await insertUsers([admin]);
newUser.password = 'password';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
newUser.password = '1111111';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if role is neither user nor admin', async () => {
await insertUsers([admin]);
newUser.role = 'invalid';
@@ -164,6 +123,71 @@ describe('User routes', () => {
});
});
describe('POST /v1/users/add', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
referer: '',
type: 'individual',
individual_data: {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email,
full_address: 'Russia, Moscow, Tverskaya street, 10',
},
};
});
test('should return 201 and successfully add new user if data is ok', async () => {
await insertUsers([admin]);
const res = await request(app).post('/v1/users/add').set('Authorization', `Bearer ${adminAccessToken}`).send(newUser);
// if (res.status !== httpStatus.CREATED) {
// console.error(res.body); // Выводит тело ответа, если статус не CREATED
// }
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.user.username).toBeDefined();
const dbUser = await User.findOne({ username: res.body.user.username });
expect(dbUser).toBeDefined();
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
expect(privateData.first_name).toEqual(newUser.individual_data?.first_name);
expect(privateData.last_name).toEqual(newUser.individual_data?.last_name);
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
});
test('should return 403 if service user is not admin but data new user is ok', async () => {
await insertUsers([userTwo]);
const res = await request(app)
.post('/v1/users/add')
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(newUser);
expect(res.status).toBe(httpStatus.FORBIDDEN);
});
test('should return 401 if service user is not have access token', async () => {
const res = await request(app).post('/v1/users/add').send(newUser);
expect(res.status).toBe(httpStatus.UNAUTHORIZED);
});
});
describe('GET /v1/users', () => {
test('should return 200 and apply the default query options', async () => {
await insertUsers([userOne, userTwo, admin]);
@@ -513,7 +537,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -528,7 +551,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -542,7 +564,6 @@ describe('User routes', () => {
const dbUser = await User.findById(userOne._id);
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(updateBody.password);
expect(dbUser).toMatchObject({ email: updateBody.email, role: 'user' });
});
@@ -550,7 +571,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app).patch(`/v1/users/${userOne.username}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
@@ -560,7 +580,6 @@ describe('User routes', () => {
await insertUsers([userOne, userTwo]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -574,7 +593,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -588,7 +606,6 @@ describe('User routes', () => {
await insertUsers([admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -600,7 +617,7 @@ describe('User routes', () => {
test('should return 400 if email is invalid', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: 'invalidEmail', password: 'GoodPassword!23' };
const updateBody = { email: 'invalidEmail' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -611,7 +628,7 @@ describe('User routes', () => {
test('should return 400 if email is already taken', async () => {
await insertUsers([userOne, userTwo, admin]);
const updateBody = { email: userTwo.email, password: 'GoodPassword!23' };
const updateBody = { email: userTwo.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -620,41 +637,9 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password length is less than 8 characters', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'passwo1' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password does not contain both letters and numbers', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'password' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
updateBody.password = '11111111';
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
//
test('should return 400 if email is my email then taken', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: userOne.email, password: 'veryStrongPassword!2' };
const updateBody = { email: userOne.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -10,7 +10,6 @@ describe('User model', () => {
newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
type: 'individual',
@@ -27,36 +26,9 @@ describe('User model', () => {
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain numbers', async () => {
newUser.password = 'password';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain letters', async () => {
newUser.password = '11111111';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if role is unknown', async () => {
newUser.role = 'invalid';
await expect(new User(newUser).validate()).rejects.toThrow();
});
});
describe('User toJSON()', () => {
test('should not return user password when toJSON is called', () => {
const newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
};
expect(new User(newUser).toJSON()).not.toHaveProperty('password');
});
});
});
+2
View File
@@ -5,6 +5,8 @@
"lib": ["ES2015"],
"outDir": "dist",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"allowJs": false,
"noImplicitAny": false,
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.74](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73...coopdoc-generator-ts@1.0.74) (2024-08-13)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.73](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73-alpha.0...coopdoc-generator-ts@1.0.73) (2024-08-05)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.72](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.72-alpha.3...coopdoc-generator-ts@1.0.72) (2024-07-25)
**Note:** Version bump only for package coopdoc-generator-ts
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "coopdoc-generator-ts",
"type": "module",
"version": "1.0.72",
"version": "1.0.75-alpha.1",
"private": true,
"packageManager": "pnpm@9.0.6",
"description": "",
@@ -40,8 +40,18 @@ export class PDFService implements IPDFService {
private static async generatePDFBuffer(htmlContent: string): Promise<Uint8Array> {
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-extensions',
'--disable-software-rasterizer',
],
timeout: 120000, // увеличиваем общее время ожидания до 2 минут
protocolTimeout: 120000, // увеличиваем время ожидания протокола до 2 минут
})
const page = await browser.newPage()
// const data = await inlineCss(htmlContent, { url: 'about:blank' })
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.19](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.18...cooptypes@1.0.19) (2024-08-13)
**Note:** Version bump only for package cooptypes
## [1.0.18](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.18-alpha.0...cooptypes@1.0.18) (2024-08-05)
**Note:** Version bump only for package cooptypes
## [1.0.17](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.17-alpha.3...cooptypes@1.0.17) (2024-07-25)
**Note:** Version bump only for package cooptypes
+4 -4
View File
@@ -68,7 +68,7 @@ api.getTableRows(
})
```
Те же параметры scope, table, code могут быть использованы для получения информации из модуля [COOPARSER](https://github.com/copenomics/cooparser). Последнее используется, когда необходимо получить исторические данные и нет необходимости проверять их актуальность по наличию таблиц в блокчейне. Однако, таблицы в блокчейне необходимо всегда проверять перед отправкой любой транзакции действия. Нельзя полагаться на данные из парсера при подготовке транзакции действия.
Те же параметры scope, table, code могут быть использованы для получения информации из модуля [COOPARSER](https://github.com/coopenomics/cooparser). Последнее используется, когда необходимо получить исторические данные и нет необходимости проверять их актуальность по наличию таблиц в блокчейне. Однако, таблицы в блокчейне необходимо всегда проверять перед отправкой любой транзакции действия. Нельзя полагаться на данные из парсера при подготовке транзакции действия.
### Транзакция действий
Состояние любого смарт-контракта может изменяться только с помощью действий. Для того, чтобы совершить действия, необходимо сформировать и отправить транзакцию с помощью библиотеки eosjs или альтернатив.
@@ -113,7 +113,7 @@ const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), te
## Лицензия
[MIT](./LICENSE) License © 2024-PRESENT [CBS VOSKHOD](https://github.com/copenomics)
[MIT](./LICENSE) License © 2024-PRESENT [CBS VOSKHOD](https://github.com/coopenomics)
<!-- Badges -->
@@ -123,7 +123,7 @@ const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), te
[npm-downloads-href]: https://npmjs.com/package/cooptypes
[bundle-src]: https://img.shields.io/bundlephobia/minzip/cooptypes?style=flat&colorA=080f12&colorB=1fa669&label=minzip
[bundle-href]: https://bundlephobia.com/result?p=cooptypes
[license-src]: https://img.shields.io/github/license/copenomics/cooptypes.svg?style=flat&colorA=080f12&colorB=1fa669
[license-href]: https://github.com/copenomics/cooptypes/blob/main/LICENSE
[license-src]: https://img.shields.io/github/license/coopenomics/cooptypes.svg?style=flat&colorA=080f12&colorB=1fa669
[license-href]: https://github.com/coopenomics/cooptypes/blob/main/LICENSE
[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
[jsdocs-href]: https://www.jsdocs.io/package/cooptypes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "cooptypes",
"type": "module",
"version": "1.0.17",
"version": "1.0.20-alpha.1",
"description": "_description_",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
@@ -4,7 +4,7 @@ import * as ContractNames from '../../../common/names'
/**
* Имя таблицы
*/
export const tableName = 'fundwallet'
export const tableName = 'coopwallet'
/**
* Таблица хранится в {@link ContractNames._fund | области памяти контракта}.
@@ -14,4 +14,4 @@ export const scope = ContractNames._fund
/**
* @interface
*/
export type IFundWallet = Fund.IFundwallet
export type ICoopWallet = Fund.ICoopwallet
@@ -12,7 +12,7 @@ export * as ExpensedFunds from './expenseFunds'
/**
* Таблица содержит сводную информацию о балансе кошелька всех фондов кооператива.
*/
export * as FundWallet from './fundWallet'
export * as CoopWallet from './coopWallet'
/**
* Таблица содержит информацию о запросах на списание по статьям и выводах из фондов.
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'adduser'
/**
* @interface
*/
export type IAddUser = Registrator.IAdduser
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'delcoop'
/**
* @interface
*/
export type IDeleteCooperative = Registrator.IDelcoop
@@ -49,3 +49,21 @@ export * as ConfirmJoin from './confirmJoin'
* @private
*/
export * as Init from './init'
/**
* Действие добавления пайщика в обход процедуры регистрации.
* @private
*/
export * as AddUser from './addUser'
/**
* Действие изменения статуса подключенного кооператива.
* @private
*/
export * as SetCoopStatus from './setCoopStatus'
/**
* Удаление кооператива из реестра подключений
* @private
*/
export * as DeleteCooperative from './deleteCooperative'
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'stcoopstatus'
/**
* @interface
*/
export type ISetCoopStatus = Registrator.IStcoopstatus
@@ -0,0 +1,21 @@
import * as Permissions from '../../../../common/permissions'
import type * as Soviet from '../../../../interfaces/soviet'
import { Actors } from '../../../../common'
/**
* Действие выполняется за подписью {@link Actors._admin | администратора}.
*/
export const authorizations = [
{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin },
] as const
/**
* Имя действия
*/
export const actionName = 'cnfrmindoc'
/**
* @interface
* Действие подтверждает приём заявления администратором
*/
export type IConfirmIncomeDocument = Soviet.ICnfrmindoc
@@ -0,0 +1,21 @@
import * as Permissions from '../../../../common/permissions'
import type * as Soviet from '../../../../interfaces/soviet'
import { Actors } from '../../../../common'
/**
* Действие выполняется за подписью {@link Actors._admin | администратора}.
*/
export const authorizations = [
{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin },
] as const
/**
* Имя действия
*/
export const actionName = 'declnindoc'
/**
* @interface
* Действие отклоняет заявление пайщика администратором
*/
export type IDeclineIncomeDocument = Soviet.IDeclnindoc
@@ -23,3 +23,13 @@ export * as SetRights from './setRights'
* @private
*/
export * as Init from './init'
/**
* Действие подтверждения приёма заявления пайщика
*/
export * as ConfirmIncomeDocument from './confirmIncomeDocument'
/**
* Действие отклонения приёма заявления пайщика
*/
export * as DeclineIncomeDocument from './declineIncomeDocument'
@@ -16,6 +16,6 @@ export const actionName = 'createprog'
/**
* @interface
* Действие редактирует целевую потребительскую программу маркетплейса.
* Действие редактирует целевую потребительскую программу.
*/
export type ICreateMarketProgram = Soviet.ICreateprog
export type ICreateProgram = Soviet.ICreateprog
@@ -12,3 +12,8 @@ export * as EditMarketProgram from './editMarketProgram'
* Действие отключает целевую потребительскую программу маркетплейса.
*/
export * as DisableMarketProgram from './disableMarketProgram'
/**
* Действие отправки заявления на присоединение к ЦПП Кооператива
*/
export * as JoinProgram from './joinProgram'
@@ -0,0 +1,20 @@
import * as Permissions from '../../../../common/permissions'
import type * as Soviet from '../../../../interfaces/soviet'
import { Actors } from '../../../../common'
/**
* Действие выполняется за подписью любого {@link Actors._username | пользователя}.
*/
export const authorizations = [
{ permissions: [Permissions.active], actor: Actors._username },
] as const
/**
* Имя действия
*/
export const actionName = 'joinprog'
/**
* Действие отправки заявления на присоединение к ЦПП кооператива
*/
export type IJoinProgram = Soviet.IJoinprog
@@ -0,0 +1,17 @@
import type * as Soviet from '../../../interfaces/soviet'
import { Actors } from '../../../common'
/**
* Имя таблицы
*/
export const tableName = 'indocuments'
/**
* Таблица хранится в {@link Actors._coopname | области памяти кооператива}.
*/
export const scope = Actors._coopname
/**
* @interface
*/
export type IIncomeDocument = Soviet.IIndocument
@@ -39,9 +39,9 @@ export * as Decisions from './decisions'
export * as JoinCoops from './joinCoops'
/**
* Таблица хранит целевые потребительские программы контракта маркетплейса.
* Таблица хранит целевые потребительские программы.
*/
export * as MarketPrograms from './marketPrograms'
export * as Programs from './programs'
/**
* Таблица хранит информацию о пайщиках.
@@ -57,3 +57,8 @@ export * as ProgramWallets from './programWallets'
* Таблица содержит сводный баланс пайщиков кооператива.
*/
export * as Wallets from './wallets'
/**
* Таблица содержит заявления пайщиков кооператива, которые ожидают приёма, но не требуют решения совета.
*/
export * as IncomeDocuments from './incomeDocuments'
@@ -15,4 +15,4 @@ export const scope = Actors._coopname
* @interface
* Таблица хранит публичную часть реестра пайщиков кооператива.
*/
export type IParticipants = Soviet.IParticipants
export type IParticipants = Soviet.IParticipant
@@ -4,7 +4,7 @@ import { Actors } from '../../../common'
/**
* Имя таблицы
*/
export const tableName = 'progcomarket'
export const tableName = 'programs'
/**
* Таблица хранится в {@link Actors._coopname | области памяти кооператива}.
@@ -15,4 +15,4 @@ export const scope = Actors._coopname
* @interface
* Таблица хранит целевые потребительские программы контракта маркетплейса.
*/
export type IMarketPrograms = Soviet.IProgcomarket
export type IProgram = Soviet.IProgram
@@ -13,6 +13,8 @@ export interface ICountsBase {
}
export interface ICreatedraft {
scope: IName
username: IName
registry_id: IUint64
lang: IName
title: string
@@ -23,25 +25,37 @@ export interface ICreatedraft {
}
export interface ICreatetrans {
scope: IName
username: IName
draft_id: IUint64
lang: IName
data: string
}
export interface IDeldraft {
scope: IName
username: IName
draft_id: IUint64
}
export interface IDeltrans {
scope: IName
username: IName
translate_id: IUint64
}
export interface IEdittrans {
scope: IName
username: IName
translate_id: IUint64
data: string
}
export interface IMigrate {
}
export interface INewid {
scope: IName
id: IUint64
}
@@ -36,6 +36,11 @@ export interface IAddexpense {
quantity: IAsset
}
export interface IAddinitial {
coopname: IName
quantity: IAsset
}
export interface IAuthorize {
coopname: IName
type: IName
@@ -48,6 +53,15 @@ export interface IComplete {
withdraw_id: IUint64
}
export interface ICoopwallet {
id: IUint64
coopname: IName
circulating_account: ISimpleWallet
initial_account: ISimpleWallet
accumulative_account: ISimpleWallet
accumulative_expense_account: ISimpleWallet
}
export interface ICounts extends ICountsBase {
}
@@ -140,6 +154,9 @@ export interface IInit {
initial: IAsset
}
export interface IMigrate {
}
export interface INewfund {
coopname: IName
type: IName
@@ -152,6 +169,11 @@ export interface INewwithdraw {
id: IUint64
}
export interface ISimpleWallet {
available: IAsset
withdrawed: IAsset
}
export interface ISpreadamount {
coopname: IName
quantity: IAsset
@@ -20,6 +20,19 @@ export interface IAccount {
registered_at: ITimePointSec
}
export interface IAdduser {
registrator: IName
coopname: IName
referer: IName
username: IName
type: IName
created_at: ITimePointSec
initial: IAsset
minimum: IAsset
spread_initial: boolean
meta: string
}
export interface IBalances extends IBalancesBase {
}
@@ -30,16 +43,12 @@ export interface IBalancesBase {
}
export interface IChangekey {
coopname: IName
changer: IName
username: IName
public_key: IPublicKey
}
export interface ICheck {
hash: IChecksum256
public_key: IPublicKey
signature: ISignature
}
export interface IConfirmreg {
coopname: IName
username: IName
@@ -60,6 +69,13 @@ export interface ICooperative {
org_registration: IAsset
org_initial: IAsset
org_minimum: IAsset
status: IName
created_at: ITimePointSec
}
export interface IDelcoop {
registrator: IName
coopname: IName
}
export interface IDocument {
@@ -69,9 +85,6 @@ export interface IDocument {
meta: string
}
export interface IFix {
}
export interface IInit {
}
@@ -82,6 +95,9 @@ export interface IJoincoop {
document: IDocument
}
export interface IMigrate {
}
export interface INewaccount {
registrator: IName
coopname: IName
@@ -105,7 +121,6 @@ export interface IOrgData {
export interface IRegcoop {
registrator: IName
coopname: IName
username: IName
params: IOrgData
}
@@ -116,6 +131,12 @@ export interface IReguser {
type: IName
}
export interface IStcoopstatus {
coopname: IName
username: IName
status: IName
}
export interface IUpdateaccnt {
username: IName
account_to_change: IName
+94 -10
View File
@@ -24,6 +24,7 @@ export interface IAddprogbal {
export interface IAddress {
id: IUint64
coopname: IName
braname: IName
data: IAddressData
}
@@ -54,6 +55,16 @@ export interface IAddstaff {
position_title: string
}
export interface IAdduser {
coopname: IName
username: IName
type: IName
created_at: ITimePointSec
initial: IAsset
minimum: IAsset
spread_initial: boolean
}
export interface IAuthorize {
coopname: IName
chairman: IName
@@ -129,6 +140,13 @@ export interface IChanges {
return_product_decision_id: IUint64
}
export interface ICnfrmindoc {
coopname: IName
registrator: IName
username: IName
indoc_id: IUint64
}
export interface ICounts extends ICountsBase {
}
@@ -141,21 +159,35 @@ export interface ICountsBase {
export interface ICreaddress {
coopname: IName
chairman: IName
braname: IName
data: IAddressData
}
export interface ICreateboard {
coopname: IName
chairman: IName
username: IName
type: IName
members: IBoardMember[]
name: string
description: string
}
export interface ICreateprog {
export interface ICreatebranch {
coopname: IName
chairman: IName
braname: IName
name: string
description: string
authorizer: IName
trusted: IName[]
}
export interface ICreateprog {
coopname: IName
username: IName
program_type: IName
draft_registry_id: IUint64
hash_of_conditions: IChecksum256
title: string
announce: string
description: string
@@ -164,6 +196,7 @@ export interface ICreateprog {
calculation_type: IName
fixed_membership_contribution: IAsset
membership_percent_fee: IUint64
meta: string
}
export interface IDecision {
@@ -183,15 +216,29 @@ export interface IDecision {
created_at: ITimePointSec
}
export interface IDeclnindoc {
coopname: IName
registrator: IName
username: IName
indoc_id: IUint64
comment: string
}
export interface IDeladdress {
coopname: IName
chairman: IName
address_id: IUint64
}
export interface IDeletebranch {
coopname: IName
chairman: IName
braname: IName
}
export interface IDisableprog {
coopname: IName
id: IUint64
program_id: IUint64
}
export interface IDisautomate {
@@ -211,18 +258,33 @@ export interface IDocument {
export interface IEditaddress {
coopname: IName
chairman: IName
braname: IName
address_id: IUint64
data: IAddressData
}
export interface IEditbranch {
coopname: IName
chairman: IName
braname: IName
name: string
description: string
authorizer: IName
trusted: IName[]
}
export interface IEditprog {
coopname: IName
id: IUint64
username: IName
program_id: IUint64
draft_registry_id: IUint64
hash_of_conditions: IChecksum256
title: string
announce: string
description: string
preview: string
images: string
meta: string
}
export interface IExec {
@@ -231,9 +293,6 @@ export interface IExec {
decision_id: IUint64
}
export interface IFix {
}
export interface IFundwithdraw {
coopname: IName
username: IName
@@ -242,6 +301,15 @@ export interface IFundwithdraw {
document: IDocument
}
export interface IIndocument {
id: IUint64
coopname: IName
username: IName
type: IName
secondary_id: IUint64
document: IDocument
}
export interface IInit {
}
@@ -258,6 +326,16 @@ export interface IJoincoops {
notice: string
}
export interface IJoinprog {
coopname: IName
username: IName
document: IDocument
program_id: IUint64
}
export interface IMigrate {
}
export interface INewact {
coopname: IName
username: IName
@@ -309,7 +387,7 @@ export interface IOnewallet {
minimum: IAsset
}
export interface IParticipants {
export interface IParticipant {
username: IName
created_at: ITimePointSec
last_update: ITimePointSec
@@ -321,9 +399,12 @@ export interface IParticipants {
type: IName
}
export interface IProgcomarket {
export interface IProgram {
id: IUint64
coopname: IName
program_type: IName
draft_registry_id: IUint64
hash_of_conditions: IChecksum256
is_active: boolean
title: string
announce: string
@@ -334,6 +415,8 @@ export interface IProgcomarket {
calculation_type: IName
membership_percent_fee: IUint64
fixed_membership_contribution: IAsset
start_at: ITimePointSec
expired_at: ITimePointSec
}
export interface IProgwallet {
@@ -342,6 +425,7 @@ export interface IProgwallet {
program_id: IUint64
username: IName
available: IAsset
document: IDocument
}
export interface IRecieved {
@@ -402,7 +486,7 @@ export interface IUnblockbal {
export interface IUpdateboard {
coopname: IName
chairman: IName
username: IName
board_id: IUint64
members: IBoardMember[]
name: string
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.23](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.22...coop-notificator@0.0.23) (2024-08-13)
**Note:** Version bump only for package coop-notificator
## [0.0.22](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.22-alpha.0...coop-notificator@0.0.22) (2024-08-05)
**Note:** Version bump only for package coop-notificator
## [0.0.21](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.21-alpha.3...coop-notificator@0.0.21) (2024-07-25)
**Note:** Version bump only for package coop-notificator
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "coop-notificator",
"type": "module",
"version": "0.0.21",
"version": "0.0.24-alpha.1",
"private": true,
"packageManager": "pnpm@9.1.1",
"description": "_description_",

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