Compare commits

...

28 Commits

Author SHA1 Message Date
Alex Ant 72674cba10 chore(release): publish
- coopback@1.7.28
 - terminal@0.1.31
2024-07-26 18:42:47 +05:00
Alex Ant 495a8e2d1e chore(release): publish
- coopback@1.7.28-alpha.0
 - terminal@0.1.31-alpha.0
2024-07-26 18:41:33 +05:00
Alex Ant 95ae221d60 Добавление платежных реквизитов | удаление | просмотр | свайп в рамках страницы 2024-07-26 18:38:14 +05:00
Alex Ant 857f746da1 bug fix 2024-07-26 11:03:52 +05:00
Alex Ant 0e2e3052b1 works 2024-07-25 21:00:04 +05:00
Alex Ant 167c04cc3f commit 2024-07-25 20:47:42 +05:00
Alex Ant 043f51479a looking for bug #2 2024-07-25 20:30:56 +05:00
Alex Ant 921282df81 looking for bug 2024-07-25 20:20:44 +05:00
Alex Ant b3c641501c migration 2024-07-25 20:07:44 +05:00
Alex Ant 3608e7601f chore(release): publish
- cooparser-ts@1.0.17
 - coopback@1.7.27
 - coopdoc-generator-ts@1.0.72
 - cooptypes@1.0.17
 - coop-notificator@0.0.21
 - terminal@0.1.30
2024-07-25 19:41:18 +05:00
Alex Ant a75cfa1056 chore(release): publish
- coopback@1.7.27-alpha.5
 - terminal@0.1.30-alpha.4
2024-07-25 19:39:41 +05:00
Alex Ant f08c4102fd login with email 2024-07-25 19:37:13 +05:00
Alex Ant 9bfd7c7d9a chore(release): publish
- coopback@1.7.27-alpha.4
2024-07-25 18:24:15 +05:00
Alex Ant a359b4fbdb fix 2024-07-25 18:22:17 +05:00
Alex Ant effb6df495 chore(release): publish
- cooparser-ts@1.0.17-alpha.2
 - coopback@1.7.27-alpha.3
 - coopdoc-generator-ts@1.0.72-alpha.3
 - cooptypes@1.0.17-alpha.3
 - coop-notificator@0.0.21-alpha.3
 - terminal@0.1.30-alpha.3
2024-07-25 17:56:22 +05:00
Alex Ant 6e6c963663 update 2024-07-25 17:55:36 +05:00
Alex Ant 23f5c7b578 update 2024-07-25 17:55:14 +05:00
Alex Ant 02b223266d pre 2024-07-25 17:05:44 +05:00
Alex Ant 745a8c99e1 publish cooptypes 2024-07-25 17:03:17 +05:00
Alex Ant 5532038fd8 coopback payment methods (list | add | del) & tests 2024-07-25 16:06:06 +05:00
Alex Ant 3be31c5240 coopback tests work 2024-07-24 23:30:01 +05:00
Alex Ant 11ab59e109 coopback tests restore #1 2024-07-23 22:34:42 +05:00
Alex Ant d5bbf10041 before generator refactoring 2024-07-23 10:10:22 +05:00
Alex Ant ba4f9581a4 before generator refactoring 2024-07-23 09:46:19 +05:00
Alex Ant 52eabab633 chore(release): publish
- coopback@1.7.27-alpha.0
 - coopdoc-generator-ts@1.0.72-alpha.0
 - cooptypes@1.0.17-alpha.0
 - coop-notificator@0.0.21-alpha.0
 - terminal@0.1.30-alpha.0
2024-07-18 20:15:52 +05:00
Alex Ant 600949b784 изменяемые публичные контакты кооператива 2024-07-18 20:09:46 +05:00
Alex Ant dcca48097d chore(members): убран лишний код 2024-07-16 21:21:06 +05:00
Alex Ant 6e20c32a14 update 2024-07-16 21:13:51 +05:00
188 changed files with 15533 additions and 13872 deletions
Vendored
BIN
View File
Binary file not shown.
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.17](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.17-alpha.2...cooparser-ts@1.0.17) (2024-07-25)
**Note:** Version bump only for package cooparser-ts
## [1.0.16](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.16-alpha.0...cooparser-ts@1.0.16) (2024-07-15)
**Note:** Version bump only for package cooparser-ts
+2 -3
View File
@@ -1,12 +1,12 @@
{
"name": "pkg-placeholder",
"version": "1.0.16",
"version": "1.0.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pkg-placeholder",
"version": "1.0.16",
"version": "1.0.17",
"license": "MIT",
"devDependencies": {
"@antfu/eslint-config": "^2.16.0",
@@ -16,7 +16,6 @@
"bumpp": "^9.4.0",
"eslint": "^8.57.0",
"esno": "^4.7.0",
"lint-staged": "^15.2.2",
"pnpm": "^8.15.7",
"rimraf": "^5.0.5",
"simple-git-hooks": "^2.11.1",
+2 -8
View File
@@ -1,7 +1,7 @@
{
"name": "cooparser-ts",
"type": "module",
"version": "1.0.16",
"version": "1.0.17",
"private": true,
"packageManager": "pnpm@9.0.6",
"description": "",
@@ -41,6 +41,7 @@
"deploy-production": "git checkout production && git merge testnet && git push origin production && git checkout main",
"build": "unbuild",
"dev": "concurrently -n 'PARSER' -c 'bgBlue.white' \"nodemon --watch src --ext ts,js,env --exec 'esno' src/index.ts\"",
"dev:test": "NODE_ENV=test concurrently -n 'PARSER' -c 'bgBlue.white' \"nodemon --watch src --ext ts,js,env --exec 'esno' src/index.ts\"",
"lint": "eslint .",
"prepublishOnly": "nr build",
"release": "bumpp && npm publish",
@@ -72,7 +73,6 @@
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"esno": "^4.7.0",
"lint-staged": "^15.2.2",
"nodemon": "^3.1.4",
"pnpm": "^8.15.7",
"rimraf": "^5.0.5",
@@ -82,11 +82,5 @@
"unbuild": "^2.0.0",
"vite": "^5.2.10",
"vitest": "^1.5.2"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
}
}
+2 -1
View File
@@ -11,12 +11,13 @@ export class Database {
private sync: Collection | undefined
constructor() {
console.log('mongo2: ', mongoUri)
this.client = new MongoClient(mongoUri)
}
async connect() {
await this.client.connect()
this.db = this.client.db('cooperative')
this.db = this.client.db()
this.actions = this.db.collection('actions')
this.deltas = this.db.collection('deltas')
this.sync = this.db.collection('sync')
+3 -2
View File
@@ -10,10 +10,11 @@ function getEnvVar(key: string): string {
return envVar
}
export const node_env = getEnvVar('NODE_ENV')
console.log('ENV: ', node_env)
export const eosioApi = getEnvVar('API')
export const shipApi = getEnvVar('SHIP')
export const mongoUri = getEnvVar('MONGO_EXPLORER_URI')
export const mongoUri = `${getEnvVar('MONGO_EXPLORER_URI')}${node_env === 'test' ? '-test' : ''}`
export const startBlock = getEnvVar('START_BLOCK')
export const finishBlock = getEnvVar('FINISH_BLOCK')
export const redisPort = getEnvVar('REDIS_PORT')
+2 -2
View File
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# . "$(dirname "$0")/_/husky.sh"
yarn install
# yarn install
+2 -2
View File
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# . "$(dirname "$0")/_/husky.sh"
git status
# git status
+2 -2
View File
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# . "$(dirname "$0")/_/husky.sh"
yarn lint-staged
# yarn lint-staged
+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.28](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.28-alpha.0...coopback@1.7.28) (2024-07-26)
**Note:** Version bump only for package coopback
## [1.7.27](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.27-alpha.5...coopback@1.7.27) (2024-07-25)
**Note:** Version bump only for package coopback
## [1.7.26](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.26-alpha.0...coopback@1.7.26) (2024-07-15)
**Note:** Version bump only for package coopback
+2 -3
View File
@@ -1,12 +1,12 @@
{
"name": "create-nodejs-express-app",
"version": "1.7.26",
"version": "1.7.28",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-nodejs-express-app",
"version": "1.7.26",
"version": "1.7.28",
"license": "MIT",
"dependencies": {
"@a2seven/yoo-checkout": "^1.1.4",
@@ -61,7 +61,6 @@
"eslint-plugin-jest": "^24.0.1",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-security": "^1.4.0",
"lint-staged": "^11.0.0",
"node-mocks-http": "^1.8.0",
"nodemon": "^2.0.0",
"prettier": "^2.0.5",
+7 -4
View File
@@ -1,6 +1,6 @@
{
"name": "coopback",
"version": "1.7.26",
"version": "1.7.28",
"description": "",
"private": true,
"bin": "bin/createNodejsApp.js",
@@ -16,7 +16,7 @@
"deploy-production": "git checkout production && git merge testnet && git push origin production && git checkout main",
"start": "ts-node src/index.ts",
"dev": "concurrently -n 'CONTROLLER' -c 'bgCyan.black' \"nodemon --watch src --ext ts,js,env --exec 'ts-node' src/index.ts\"",
"test": "jest -i --colors --verbose --detectOpenHandles",
"test": "jest -i --colors --testTimeout=120000",
"test:watch": "jest -i --watchAll",
"coverage": "jest -i --coverage",
"coverage:coveralls": "jest -i --coverage --coverageReporters=text-lcov | coveralls",
@@ -28,9 +28,11 @@
"docker:dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up",
"docker:test": "docker-compose -f docker-compose.yml -f docker-compose.test.yml up",
"types": "ts-node scripts/types.ts",
"init": "ts-node scripts/init.ts",
"swagger": "ts-node scripts/swagger.ts",
"typecheck": "tsc --noEmit",
"swagger-generate": "swagger-ui-watcher ./src/docs/swagger.json"
"docs-watch": "swagger-ui-watcher ./src/docs/swagger.json",
"docs": "ts-node scripts/swagger.ts && redoc-cli bundle ./src/docs/swagger.json -o ./docs/index.html"
},
"keywords": [
"node",
@@ -105,7 +107,9 @@
"xss-clean": "^0.1.1"
},
"devDependencies": {
"@redocly/cli": "^1.18.0",
"@types/express": "^4.17.21",
"@types/faker": "^6.6.9",
"@types/node": "^20.11.26",
"concurrently": "^8.2.2",
"coveralls": "^3.0.7",
@@ -116,7 +120,6 @@
"eslint-plugin-jest": "^24.0.1",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-security": "^1.4.0",
"lint-staged": "^11.0.0",
"node-mocks-http": "^1.8.0",
"nodemon": "^2.0.0",
"prettier": "^2.0.5",
@@ -1,7 +1,7 @@
import config from '../src/config/config';
import { User } from '../src/models';
import { tokenService } from '../src/services';
import userService from '../src/services/user.service';
import { userService } from '../src/services';
import mongoose from 'mongoose';
const args = process.argv.slice(2);
+101
View File
@@ -0,0 +1,101 @@
import config from '../src/config/config';
import { User } from '../src/models';
import { tokenService } from '../src/services';
import { userService } from '../src/services';
import mongoose from 'mongoose';
import { connectGenerator } from '../src/services/document.service';
import crypto from 'crypto';
import bcryptjs from 'bcryptjs';
const { compare, hash } = bcryptjs;
async function init() {
try {
await connectGenerator();
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
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',
type: 'individual',
individual_data: {
first_name: 'Иван',
last_name: 'Иванов',
middle_name: 'Иванович',
birthdate: '2000-01-01',
phone: '+1234567890',
email: 'ivanov.ivan@example.com',
full_address: 'Переулок Иванов д. 1',
},
});
const user = await userService.getUserByUsername('ant');
user.status = 'active';
user.is_registered = true;
await user.save();
const voskhod = await userService.createUser({
username: 'voskhod',
email: 'chairman.voskhod@gmail.com',
password,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
type: 'organization',
organization_data: {
is_cooperative: true,
type: 'coop',
short_name: '"ПК ТЕСТ-Восход"',
full_name: 'Потребительский Кооператив "ТЕСТ-ВОСХОД"',
represented_by: {
first_name: 'Иван',
last_name: 'Иванов',
middle_name: 'Иванович',
position: 'Председатель',
based_on: 'Решения общего собрания №1',
},
country: 'Russia',
city: 'Москва',
full_address: 'Переулок Правды, дом 1',
email: 'chairman.voskhod@gmail.com',
phone: '+77077770707',
details: {
inn: '1234567890',
ogrn: '1234567890123',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '1234567890123456',
bank_name: 'Sberbank',
details: {
bik: '123456789',
corr: '30101810400000000225',
kpp: '123456789',
},
},
},
});
const coop = await userService.getUserByUsername('voskhod');
coop.status = 'active';
coop.is_registered = true;
await coop.save();
console.log('ok');
} catch (e: any) {
console.log('Ошибка: ', e);
}
process.exit(0);
}
init();
@@ -0,0 +1,43 @@
import config from '../../src/config/config';
import { User } from '../../src/models';
import { tokenService } from '../../src/services';
import { userService } from '../../src/services';
import mongoose from 'mongoose';
import { connectGenerator } from '../../src/services/document.service';
import crypto from 'crypto';
import bcryptjs from 'bcryptjs';
const { compare, hash } = bcryptjs;
async function init() {
try {
await connectGenerator();
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const orgs_collection = mongoose.connection.collection('OrgData');
const bankData_collection = mongoose.connection.collection('PaymentData');
const orgs = await orgs_collection.find().toArray();
for (const org of orgs) {
await bankData_collection.insertOne({
_created_at: org._created_at,
username: org.username,
method_id: 1,
user_type: 'organization',
method_type: 'bank_transfer',
is_default: true,
data: org.bank_account,
deleted: false,
block_num: org.block_num,
});
}
} catch (e: any) {
console.log('Ошибка: ', e);
}
process.exit(0);
}
init();
+4 -4
View File
@@ -3,12 +3,12 @@ import crypto from 'crypto';
const { compare, hash } = bcryptjs;
async function generate() {
const wif = '';
const wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
const hash_from_wif = await crypto.createHash('sha256').update(wif).digest('hex');
const password = await hash(hash_from_wif, 8)
console.log(password)
console.log('hash_from_wif: ', hash_from_wif);
const password = await hash(hash_from_wif, 8);
console.log(password);
}
generate();
+6 -8
View File
@@ -14,15 +14,13 @@ import routes from './routes/v1/index';
import { errorConverter, errorHandler } from './middlewares/error';
import ApiError from './utils/ApiError';
import { connectGenerator } from './services/data.service';
// import { connectGenerator } from './services/data.service';
// Подключите генератор
connectGenerator()
.then(() => {
console.log('Generator connected');
})
.catch((error) => {
console.error('Failed to connect generator:', error);
});
// connectGenerator()
// .then(() => {})
// .catch((error) => {
// console.error('Failed to connect generator:', error);
// });
const app = express();
@@ -2,15 +2,16 @@ 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';
const { CREATED, NO_CONTENT } = httpStatus;
export const login = catchAsync(async (req, res) => {
const { username, password } = req.body;
const user = await authService.loginUserWithUsernameAndPassword(username, password);
const { email, password } = req.body;
const user = await authService.loginUserWithEmailAndPassword(email, password);
if (user.is_registered === false)
throw new Error('Регистрация не была завершена. Пожалуйста, подождите решения совета или начните с начала. ')
// if (user.is_registered === false)
// throw new Error('Регистрация не была завершена. Пожалуйста, подождите решения совета или начните с начала. ');
const tokens = await tokenService.generateAuthTokens(user);
res.send({ user, tokens });
@@ -21,18 +22,18 @@ export const logout = catchAsync(async (req, res) => {
res.status(NO_CONTENT).send();
});
export const refreshTokens = catchAsync(async (req, res) => {
const tokens = await authService.refreshAuth(req.body.refreshToken);
res.send({ ...tokens });
export const refreshTokens = catchAsync(async (req: RRefreshTokens, res: any) => {
const tokens = await authService.refreshAuth(req.body);
res.send(tokens);
});
export const forgotPassword = catchAsync(async (req, res) => {
export const forgotPassword = catchAsync(async (req: RForgotPassword, res) => {
const resetPasswordToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken);
res.status(NO_CONTENT).send();
});
export const resetPassword = catchAsync(async (req, res) => {
export const resetPassword = catchAsync(async (req: RResetPassword, res) => {
await authService.resetPassword(req.query.token, req.body.password);
res.status(NO_CONTENT).send();
});
@@ -43,7 +44,7 @@ export const sendVerificationEmail = catchAsync(async (req, res) => {
res.status(NO_CONTENT).send();
});
export const verifyEmail = catchAsync(async (req, res) => {
export const verifyEmail = catchAsync(async (req: RVerifyEmail, res) => {
await authService.verifyEmail(req.query.token);
res.status(NO_CONTENT).send();
});
@@ -1,7 +1,7 @@
import catchAsync from '../utils/catchAsync';
import { coopService, dataService } from '../services';
import { coopService, documentService } from '../services';
import { Cooperative } from 'cooptypes';
import { generator } from '../services/data.service';
import { generator } from '../services/document.service';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
@@ -23,7 +23,7 @@ export const loadAgenda = catchAsync(async (req, res) => {
const complexAgenda: Cooperative.Documents.IComplexAgenda[] = [];
for (const { action, table } of agenda) {
const documents = await dataService.buildComplexDocument(action);
const documents = await documentService.buildComplexDocument(action);
if (documents.statement.document) complexAgenda.push({ documents, action, table });
}
@@ -1,22 +0,0 @@
import catchAsync from '../utils/catchAsync';
import { dataService } from '../services/index';
import type { IGetDocuments, RGenerate, RGetDocuments } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
export const generateDocument = catchAsync(async (req: RGenerate, res) => {
const document = await dataService.generateDocument(req.body);
res.status(httpStatus.CREATED).send(document);
});
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 documents: IGetResponse = await dataService.queryDocuments(filter, page, limit);
res.status(httpStatus.OK).send(documents);
});
@@ -0,0 +1,31 @@
import catchAsync from '../utils/catchAsync';
import { documentService } from '../services/index';
import type {
IGetDocuments,
RDeletePaymentMethod,
RGenerate,
RGetDocuments,
RGetListPaymentMethods,
RSavePaymentMethod,
} 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);
res.status(httpStatus.CREATED).send(document);
});
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 documents = await documentService.queryDocuments(filter, page, limit);
res.status(httpStatus.OK).send(documents);
});
+2 -2
View File
@@ -1,6 +1,6 @@
export * as authController from './auth.controller';
export * as userController from './user.controller';
export * as dataController from './data.controller';
export * as orderController from './order.controller';
export * as documentController from './document.controller';
export * as paymentController from './payment.controller';
export * as coopController from './coop.controller';
export * as notifyController from './notify.controller';
@@ -1,58 +0,0 @@
import http from 'http-status';
import catchAsync from '../utils/catchAsync';
import { orderService } from '../services';
import { ICreateDeposit, ICreateInitialPayment } from '../types';
import { Request, Response } from 'express';
import * as ip from 'ip';
// Список разрешенных IP-адресов
// const allowedIPs = [
// '185.71.76.0/27',
// '185.71.77.0/27',
// '77.75.153.0/25',
// '77.75.156.11',
// '77.75.156.35',
// '77.75.154.128/25',
// '2a02:5180::/32'
// ];
// if (process.env.NODE_ENV === 'development')
// allowedIPs.push('127.0.0.1')
export const catchIPN = catchAsync(async (req: Request, res: Response) => {
// const ipAddr = ((req.headers['x-real-ip'] as string) || req.ip || '').replace(/^::ffff:/, '');
// console.log("ip: ", ipAddr);
// const isAllowed = allowedIPs.some(allowedIP => {
// if (allowedIP.includes('/')) { // Если это диапазон
// return ip.cidrSubnet(allowedIP).contains(ipAddr);
// }
// return allowedIP === ipAddr; // Если это конкретный IP
// });
// if (!isAllowed) {
// console.log("попытка доступа с запрещенного IP: ", ipAddr)
// return res.status(http.FORBIDDEN).send('Доступ запрещен');
// }
// console.log("on catch ipn", req.body);
res.status(http.OK).send();
orderService.catchIPN(req.body);
});
export const createDeposit = catchAsync(async (req: any, res: Response) => {
const username = req.user.username
const result = await orderService.createDeposit(username, req.body as ICreateDeposit)
res.status(http.CREATED).send(result);
})
export const createInitialPayment = catchAsync(async (req: any, res: Response) => {
const username = req.user.username
const result = await orderService.createInitialOrder(username, req.body as ICreateInitialPayment)
res.status(http.CREATED).send(result);
})
@@ -0,0 +1,112 @@
import http from 'http-status';
import catchAsync from '../utils/catchAsync';
import { paymentService } from '../services';
import {
ICreateDeposit,
ICreateInitialPayment,
RDeletePaymentMethod,
RGetListPaymentMethods,
RSavePaymentMethod,
} from '../types';
import { Request, Response } from 'express';
import * as ip from 'ip';
import { IGetResponse } from '../types/common';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import { Cooperative } from 'cooptypes';
import { getUserByUsername } from '../services/user.service';
import { User } from '../models';
// Список разрешенных IP-адресов
// const allowedIPs = [
// '185.71.76.0/27',
// '185.71.77.0/27',
// '77.75.153.0/25',
// '77.75.156.11',
// '77.75.156.35',
// '77.75.154.128/25',
// '2a02:5180::/32'
// ];
// if (process.env.NODE_ENV === 'development')
// allowedIPs.push('127.0.0.1')
export const catchIPN = catchAsync(async (req: Request, res: Response) => {
// const ipAddr = ((req.headers['x-real-ip'] as string) || req.ip || '').replace(/^::ffff:/, '');
// console.log("ip: ", ipAddr);
// const isAllowed = allowedIPs.some(allowedIP => {
// if (allowedIP.includes('/')) { // Если это диапазон
// return ip.cidrSubnet(allowedIP).contains(ipAddr);
// }
// return allowedIP === ipAddr; // Если это конкретный IP
// });
// if (!isAllowed) {
// console.log("попытка доступа с запрещенного IP: ", ipAddr)
// return res.status(http.FORBIDDEN).send('Доступ запрещен');
// }
// console.log("on catch ipn", req.body);
res.status(http.OK).send();
paymentService.catchIPN(req.body);
});
export const createDeposit = catchAsync(async (req: any, res: Response) => {
const username = req.user.username;
const result = await paymentService.createDeposit(username, req.body as ICreateDeposit);
res.status(http.CREATED).send(result);
});
export const createInitialPayment = catchAsync(async (req: any, res: Response) => {
const username = req.user.username;
const result = await paymentService.createInitialOrder(username, req.body as ICreateInitialPayment);
res.status(http.CREATED).send(result);
});
export const listPaymentMethods = catchAsync(async (req: RGetListPaymentMethods, res) => {
const filter = req.params?.username ? { username: req.params.username } : {};
const methods: IGetResponse<any> = await paymentService.listPaymentMethods({ ...filter });
res.status(httpStatus.OK).send(methods);
});
export const addPaymentMethod = catchAsync(async (req: RSavePaymentMethod, res) => {
if (req.params?.username != req.body.username)
throw new ApiError(httpStatus.BAD_REQUEST, 'username в params и body должны совпадать');
const user = await getUserByUsername(req.body.username);
const method = req.body as any;
if (method.method_type === 'sbp' && !method.data.phone)
throw new ApiError(httpStatus.BAD_REQUEST, 'Не указан телефон для метода СБП');
if (method.method_type === 'bank_transfer' && !method.data.account_number)
throw new ApiError(httpStatus.BAD_REQUEST, 'Не верно указаны реквизиты для банковского платежа');
const paymentData: Cooperative.Payments.IPaymentData = {
username: req.body.username,
method_id: req.body.method_id,
user_type: user.type,
method_type: req.body.method_type,
is_default: false,
data: req.body.data,
};
await paymentService.savePaymentMethod(paymentData);
res.status(httpStatus.OK).send();
});
export const deletePaymentMethod = catchAsync(async (req: RDeletePaymentMethod, res) => {
await paymentService.deletePaymentMethod({
username: req.params?.username,
method_id: req.body.method_id,
});
res.status(httpStatus.OK).send();
});
@@ -1,11 +1,12 @@
import http, { CREATED } from 'http-status';
import http from 'http-status';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService, blockchainService, tokenService, orderService } from '../services';
import { IJoinCooperative, RCreateUser, RJoinCooperative } from '../types';
import { userService, tokenService } from '../services';
import { 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';
/**
* Порядок регистрации:
@@ -39,23 +40,23 @@ export const getUsers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['username', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
filter.role = 'user';
const users = await userService.queryUsers(filter, options);
const result = {} as IGetResponse<IUser>;
const result = {} as IGetResponse; //
//TODO wrong format answer
const data = [] as any;
for await (let user of users.results) {
const json = user.toJSON();
json.private_data = (await user.getPrivateData()) || {};
data.push(json);
if (user.type != 'service') {
json.private_data = (await user.getPrivateData()) || {};
data.push(json);
}
}
result.results = data;
result.limit = users.limit;
result.page = users.page;
result.totalPages = users.totalPages;
result.totalResults = users.totalResults;
res.send(result);
});
@@ -73,7 +74,10 @@ export const getUser = catchAsync(async (req, res) => {
});
export const updateUser = catchAsync(async (req, res) => {
const user = await userService.updateUserById(req.params.username, req.body);
if (req.user.role !== 'member' && req.user.role !== 'chairman')
throw new ApiError(httpStatus.FORBIDDEN, 'Только председатель или член совета может обновить данные пользователя');
const user = await userService.updateUserByUsername(req.params.username, req.body);
res.send(user);
});
+2 -2
View File
@@ -3,7 +3,7 @@ import app from './app';
import config from './config/config';
import logger from './config/logger';
import { updateAuth } from './services/auth.service';
import { connectGenerator } from './services/data.service';
import { connectGenerator } from './services/document.service';
import { initSocketConnection } from './controllers/ws.controller';
const SERVER_URL: string = process.env.SOCKET_SERVER || 'http://localhost:2222';
@@ -17,7 +17,7 @@ mongoose.connect(config.mongoose.url, config.mongoose.options).then(async () =>
await connectGenerator();
//подключаемся к ws-серверу
initSocketConnection(SERVER_URL);
await initSocketConnection(SERVER_URL);
server = app.listen(config.port, () => {
logger.info(`Listening to port ${config.port}`);
+1 -2
View File
@@ -15,11 +15,10 @@ const verifyCallback = (req, resolve, reject, requiredRights) => async (err, use
const userRights = roleRights.get(user.role);
const hasRequiredRights = requiredRights.every((requiredRight) => userRights?.includes(requiredRight));
// if (process.env.IS_PRODUCTION) {
if (!hasRequiredRights && req.params.username !== user.username) {
//
return reject(new ApiError(FORBIDDEN, 'Недостаточно прав доступа'));
}
// }
}
resolve();
+5 -6
View File
@@ -10,12 +10,12 @@ export const errorConverter = (err, req, res, next) => {
if (!(error instanceof ApiError)) {
if (error instanceof RpcError) {
const message = error.json.error.details[0].message.replace('assertion failure with message: ', ''); // Получаем первую строку ошибки
const statusCode = error.json.code
const subCode = error.json.error.code
const statusCode = error.json.code;
const subCode = error.json.error.code;
error = new ApiError(statusCode, message, true, undefined, subCode);
} else {
const statusCode = error.statusCode || (error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR);
const statusCode =
error.statusCode || (error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR);
const message = error.message || httpStatus[statusCode];
error = new ApiError(statusCode, message, false, err.stack);
}
@@ -24,8 +24,7 @@ export const errorConverter = (err, req, res, next) => {
};
// eslint-disable-next-line no-unused-vars
export const errorHandler = (err, req, res, next) => {
export const errorHandler = (err, req, res, next?) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
@@ -28,7 +28,7 @@ const toJSON = (schema) => {
}
});
// ret.id = ret._id.toString();
ret.id = ret._id.toString();
delete ret._id;
delete ret.__v;
delete ret.createdAt;
@@ -39,6 +39,7 @@ const tokenSchema = new Schema<IToken>(
},
{
timestamps: true,
toJSON: { virtuals: true },
}
);
+11 -4
View File
@@ -3,13 +3,13 @@ import validator from 'validator/index';
import bcryptjs from 'bcryptjs';
import { toJSON, paginate } from './plugins/index';
import { roles } from '../config/roles';
import { generator } from '../services/data.service';
import { generator } from '../services/document.service';
import { Cooperative } from 'cooptypes';
const { isEmail } = validator;
const { compare, hash } = bcryptjs;
export interface IUser extends Document {
export interface IUser {
username: string;
status: 'created' | 'joined' | 'payed' | 'registered' | 'active' | 'failed' | 'blocked';
message: string;
@@ -23,7 +23,7 @@ export interface IUser extends Document {
is_email_verified: boolean;
statement: {
hash: string;
meta: any;
meta: Object;
public_key: string;
signature: string;
};
@@ -134,6 +134,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
},
{
minimize: false,
timestamps: true,
toJSON: { virtuals: true },
}
@@ -152,7 +153,13 @@ userSchema.statics.isEmailTaken = async function (email) {
userSchema.methods.getPrivateData = async function (): Promise<
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
> {
return await generator.get(this.type, { username: this.username });
const result = (await generator.get(this.type, { username: this.username })) as
| Cooperative.Users.IIndividualData
| Cooperative.Users.IEntrepreneurData
| Cooperative.Users.IOrganizationData
| null;
return result;
};
userSchema.methods.isPasswordMatch = async function (password) {
@@ -7,13 +7,13 @@ import auth from '../../middlewares/auth';
const router = Router();
router.post('/login', validate(authValidation.login), authController.login);
router.post('/logout', validate(authValidation.logout), authController.logout);
router.post('/refresh-tokens', validate(authValidation.refreshTokens), authController.refreshTokens);
router.post('/forgot-password', validate(authValidation.forgotPassword), authController.forgotPassword);
router.post('/reset-password', validate(authValidation.resetPassword), authController.resetPassword);
router.post('/login', validate(authValidation.RLogin), authController.login);
router.post('/logout', validate(authValidation.RLogout), authController.logout);
router.post('/refresh-tokens', validate(authValidation.RRefreshTokens), authController.refreshTokens);
router.post('/forgot-password', validate(authValidation.RForgotPassword), authController.forgotPassword);
router.post('/reset-password', validate(authValidation.RResetPassword), authController.resetPassword);
router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
router.post('/verify-email', validate(authValidation.verifyEmail), authController.verifyEmail);
router.post('/verify-email', validate(authValidation.RVerifyEmail), authController.verifyEmail);
export default router;
@@ -1,32 +0,0 @@
import express from "express"
import auth from "../../middlewares/auth"
import validate from "../../middlewares/validate"
import * as dataValidation from "../../validations/data.validation"
import { dataController } from "../../controllers"
const router = express.Router()
router
.route("/generate")
.post(
auth(),
validate(dataValidation.RGenerate),
dataController.generateDocument
)
router
.route("/get-documents")
.get(
auth("getDocuments"),
validate(dataValidation.RGetDocuments),
dataController.getDocuments
)
router
.route("/get-my-documents")
.get(
auth(),
validate(dataValidation.RGetDocuments),
dataController.getDocuments
)
export default router
@@ -0,0 +1,17 @@
import express from 'express';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import * as documentValidation from '../../validations/document.validation';
import { documentController } from '../../controllers';
const router = express.Router();
router.route('/generate').post(auth(), validate(documentValidation.RGenerate), documentController.generateDocument);
router
.route('/get-documents')
.get(auth('getDocuments'), validate(documentValidation.RGetDocuments), documentController.getDocuments);
router.route('/get-my-documents').get(auth(), validate(documentValidation.RGetDocuments), documentController.getDocuments);
export default router;
+5 -5
View File
@@ -2,9 +2,9 @@ import { Router } from 'express';
import authRoute from './auth.route';
import userRoute from './user.route';
import docsRoute from './docs.route';
import orderRoute from './order.route';
import paymentRoute from './payment.route';
import coopRoute from './coop.route';
import dataRoute from './data.route';
import dataRoute from './document.route';
import notifyRoute from './notify.route';
import config from '../../config/config';
@@ -21,12 +21,12 @@ const defaultRoutes = [
route: userRoute,
},
{
path: '/data',
path: '/documents',
route: dataRoute,
},
{
path: '/orders',
route: orderRoute,
path: '/payments',
route: paymentRoute,
},
{
path: '/coop',
@@ -1,7 +1,7 @@
import { Router } from 'express';
import { orderController } from '../../controllers';
import { paymentController } from '../../controllers';
import validate from '../../middlewares/validate';
import * as orderValidation from '../../validations/order.validation';
import * as paymentValidation from '../../validations/payment.validation';
import auth from '../../middlewares/auth';
import { Request, Response } from 'express';
@@ -41,7 +41,7 @@ const router = Router();
* 200:
* description: Успешное выполнение запроса.
*/
router.route('/ipn').post(validate(orderValidation.RRecieveIPN), orderController.catchIPN);
router.route('/ipn').post(validate(paymentValidation.RRecieveIPN), paymentController.catchIPN);
/**
* @swagger
@@ -66,7 +66,9 @@ router.route('/ipn').post(validate(orderValidation.RRecieveIPN), orderController
* schema:
* $ref: '#/components/schemas/ICreatedPayment'
*/
router.route('/initial').post(auth(), validate(orderValidation.RCreateInitialPayment), orderController.createInitialPayment);
router
.route('/initial')
.post(auth(), validate(paymentValidation.RCreateInitialPayment), paymentController.createInitialPayment);
/**
* @swagger
@@ -91,6 +93,18 @@ router.route('/initial').post(auth(), validate(orderValidation.RCreateInitialPay
* schema:
* $ref: '#/components/schemas/ICreatedPayment'
*/
router.route('/deposit').post(auth(), validate(orderValidation.RCreateDeposit), orderController.createDeposit);
router.route('/deposit').post(auth(), validate(paymentValidation.RCreateDeposit), paymentController.createDeposit);
router
.route('/methods/:username?')
.get(auth('getUsers'), validate(paymentValidation.RGetListPaymentMethods), paymentController.listPaymentMethods);
router
.route('/methods/:username/add')
.post(auth('manageUsers'), validate(paymentValidation.RSavePaymentMethod), paymentController.addPaymentMethod);
router
.route('/methods/:username/delete')
.post(auth('manageUsers'), validate(paymentValidation.RDeletePaymentMethod), paymentController.deletePaymentMethod);
export default router;
@@ -1,10 +1,11 @@
import httpStatus from 'http-status';
import * as tokenService from './token.service';
import userService from './user.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';
export const updateAuth = async () => {
try {
@@ -52,15 +53,15 @@ export const updateAuth = async () => {
};
/**
* Login with username and password
* @param {string} username
* Login with email and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
export const loginUserWithUsernameAndPassword = async (username, password) => {
const user = await userService.getUserByUsername(username);
export const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect username or password');
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};
@@ -83,16 +84,14 @@ export const logout = async (refreshToken) => {
* @param {string} refreshToken
* @returns {Promise<Object>}
*/
export const refreshAuth = async (refreshToken) => {
export const refreshAuth = async (data: IRefreshTokens) => {
try {
const refreshTokenDoc = await tokenService.verifyToken(refreshToken, tokenTypes.REFRESH);
const user = await userService.getUserByUsername(refreshTokenDoc.user);
const refreshTokenDoc = await tokenService.verifyToken(data.refreshToken, tokenTypes.REFRESH);
const user = await userService.getUserById(refreshTokenDoc.user);
if (!user) {
throw new Error();
}
await refreshTokenDoc.remove();
return tokenService.generateAuthTokens(user);
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
@@ -108,12 +107,12 @@ export const refreshAuth = async (refreshToken) => {
export const resetPassword = async (resetPasswordToken, newPassword) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD);
const user = await userService.getUserByUsername(resetPasswordTokenDoc.user);
const user = await userService.getUserById(resetPasswordTokenDoc.user);
if (!user) {
throw new Error();
}
await userService.updateUserById(user.username, { password: newPassword });
await Token.deleteMany({ user: user.username, type: tokenTypes.RESET_PASSWORD });
await userService.updateUserById(user._id, { password: newPassword });
await Token.deleteMany({ user: user._id, type: tokenTypes.RESET_PASSWORD });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
@@ -127,12 +126,12 @@ export const resetPassword = async (resetPasswordToken, newPassword) => {
export const verifyEmail = async (verifyEmailToken) => {
try {
const verifyEmailTokenDoc = await tokenService.verifyToken(verifyEmailToken, tokenTypes.VERIFY_EMAIL);
const user = await userService.getUserByUsername(verifyEmailTokenDoc.user);
const user = await userService.getUserById(verifyEmailTokenDoc.user);
if (!user) {
throw new Error();
}
await Token.deleteMany({ user: user.username, type: tokenTypes.VERIFY_EMAIL });
await userService.updateUserById(user.username, { is_email_verified: true });
await Token.deleteMany({ user: user._id, type: tokenTypes.VERIFY_EMAIL });
await userService.updateUserById(user._id, { is_email_verified: true });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Email verification failed');
}
@@ -1,7 +1,7 @@
import { getActions } from '../utils/getFetch';
import { generator } from './data.service';
import { generator } from './document.service';
import { userService, blockchainService } from './index';
import { Cooperative, SovietContract } from 'cooptypes';
import { Cooperative, RegistratorContract, SovietContract } from 'cooptypes';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
@@ -63,11 +63,30 @@ export const loadInfo = async (coopname: string) => {
export const loadContacts = async (coopname: string) => {
const cooperative: Cooperative.Model.ICooperativeData | null = await generator.constructCooperative(coopname);
if (!cooperative) throw new Error('Кооператив не найден');
if (!cooperative) throw new ApiError(httpStatus.NOT_FOUND, 'Кооператив не найден');
const announce = cooperative?.announce
? JSON.parse(cooperative.announce)
: { phone: cooperative?.phone, email: cooperative?.email };
const api = await blockchainService.getApi();
const coopAccount = (
await blockchainService.lazyFetch(
api,
RegistratorContract.contractName.production,
RegistratorContract.contractName.production,
RegistratorContract.Tables.Accounts.tableName,
coopname,
coopname,
1
)
)[0];
if (!coopAccount) throw new ApiError(httpStatus.NOT_FOUND, 'Аккаунт не найден');
let announce: Cooperative.Users.IAccountMeta = { phone: cooperative?.phone, email: cooperative?.email };
if (coopAccount.meta) {
try {
announce = JSON.parse(coopAccount.meta);
} catch (e: any) {}
}
return {
full_name: cooperative?.full_name,
@@ -1,7 +1,6 @@
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 config from '../config/config';
@@ -131,27 +130,3 @@ export const queryDocuments = async (
return response;
};
export const getDocuments = async (account) => {
// let data = await getActions('/v2/history/get_actions', {
// "filter": "soviettest1:statement",
// "account": account,
// })
// for (const action of data.actions) {
// action.decision = (await getActions('/v2/history/get_actions', {
// "account": account,
// "filter": "soviettest1:decision",
// "@decision.decision_id": action.act.data.decision_id,
// }))?.actions?.[0]
// if (action.act.data.action == 'joincoop') {
// try {
// action.verified = await verify(action.act.data.statement.sign, action.act.data.statement.hash, action.act.data.statement.pkey)
// } catch(e: any){
// console.error(e)
// action.verified = false
// action.verify_message = e.message
// }
// }
// }
// return data.actions
};
+2 -2
View File
@@ -3,7 +3,7 @@ export * as emailService from './email.service';
export * as tokenService from './token.service';
export * as userService from './user.service';
export * as blockchainService from './blockchain.service';
export * as orderService from './order.service';
export * as dataService from './data.service';
export * as paymentService from './payment.service';
export * as documentService from './document.service';
export * as coopService from './coop.service';
export * as wsService from './ws.service';
@@ -6,13 +6,27 @@ import { getUserByUsername } from './user.service';
import * as blockchainService from './blockchain.service';
import logger from '../config/logger';
import { ICreateDeposit, ICreateInitialPayment } from '../types';
import { generator } from './data.service';
import { generator } from './document.service';
import ApiError from '../utils/ApiError';
import httpStatus from 'http-status';
import { ICreatedPayment, IYandexIPN, PaymentDetails } from '../types/common';
import { Cooperative } from 'cooptypes';
import { FilterQuery } from 'mongoose';
const { connection } = mongoose;
export const savePaymentMethod = async (data: Cooperative.Payments.IPaymentData) => {
return await generator.save('paymentMethod', data);
};
export const deletePaymentMethod = async (filter: FilterQuery<Cooperative.Payments.IPaymentData>) => {
return await generator.del('paymentMethod', filter as any);
};
export const listPaymentMethods = async (filter: FilterQuery<Cooperative.Payments.IPaymentData>) => {
return await generator.list('paymentMethod', filter as any);
};
function getAmountPlusFee(amount: number, provider: string): number {
const matrix = {
yookassa: 3.5,
@@ -114,8 +128,6 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
if (user.type === 'individual' || user.type === 'entrepreneur') amount = cooperative.registration;
else amount = cooperative.org_registration;
console.log('amount: ', cooperative);
const [, symbol] = cooperative.initial.split(' ');
let order_id = 0;
@@ -175,7 +187,7 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
type: 'registration',
quantity: amount,
};
console.log('amount: ', amount);
order_id = await blockchainService.createOrder(order);
// 4. Обновляешь ордер идентификатором ордера в блокчейне (order_id)
@@ -273,8 +285,3 @@ export async function catchIPN(ipnBody: IYandexIPN) {
}
}
}
export default {
createInitialOrder,
catchIPN,
};
@@ -2,7 +2,7 @@ import jwt from 'jsonwebtoken';
import moment from 'moment';
import httpStatus from 'http-status';
import config from '../config/config';
import userService from './user.service';
import * as userService from './user.service';
import { Token } from '../models';
import ApiError from '../utils/ApiError';
import { tokenTypes } from '../config/tokens';
@@ -1,7 +1,7 @@
import http from 'http-status';
import { User } from '../models';
import ApiError from '../utils/ApiError';
import { generator } from './data.service';
import { generator } from './document.service';
import { ICreateUser, IJoinCooperative } from '../types/user.validation';
import ecc from 'eosjs-ecc';
import { PublicKey, Signature } from '@wharfkit/antelope';
@@ -119,6 +119,19 @@ export const getUserByUsername = async (username: string) => {
return user;
};
/**
* Get user by username
* @param {string} id
* @returns {Promise<User>}
*/
export const getUserById = async (_id: string) => {
const user = await User.findById(_id);
if (!user) throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
return user;
};
/**
* Get user by email
* @param {string} email
@@ -134,8 +147,8 @@ export const getUserByEmail = async (email: string) => {
* @param {Object} updateBody
* @returns {Promise<User>}
*/
export const updateUserById = async (username, updateBody) => {
const user = await getUserByUsername(username);
export const updateUserById = async (id, updateBody) => {
const user = await getUserById(id);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
}
@@ -147,6 +160,27 @@ export const updateUserById = async (username, updateBody) => {
return user;
};
/**
* Update user by username
* @param {string} username
* @param {Object} updateBody
* @returns {Promise<User>}
*/
export const updateUserByUsername = async (username, updateBody) => {
const user = await getUserByUsername(username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
}
if (updateBody.email && (await User.isEmailTaken(updateBody.email))) {
throw new ApiError(http.BAD_REQUEST, 'Email already taken');
}
Object.assign(user, updateBody);
await user.save();
return user;
};
/**
* Delete user by id
* @param {string} username
@@ -160,13 +194,3 @@ export const deleteUserById = async (username) => {
await user.remove();
return user;
};
export default {
createUser,
queryUsers,
getUserByUsername,
getUserByEmail,
updateUserById,
deleteUserById,
createServiceUser,
};
@@ -3,42 +3,71 @@
* Do not modify this file manually
*/
export interface forgotPassword {
body?: {
export interface IForgotPassword {
email: string;
}
export interface ILogin {
email: string;
password: string;
}
export interface ILogout {
refreshToken: string;
}
export interface IRefreshTokens {
refreshToken: string;
}
export interface IResetPasswordBody {
password: string;
}
export interface IResetPasswordQuery {
token: string;
}
export interface IVerifyEmail {
token: string;
}
export interface RForgotPassword {
body: {
email: string;
};
}
export interface login {
body?: {
export interface RLogin {
body: {
email: string;
password: string;
username: string;
};
}
export interface logout {
body?: {
export interface RLogout {
body: {
refreshToken: string;
};
}
export interface refreshTokens {
body?: {
export interface RRefreshTokens {
body: {
refreshToken: string;
};
}
export interface resetPassword {
body?: {
export interface RResetPassword {
body: {
password: string;
};
query?: {
query: {
token: string;
};
}
export interface verifyEmail {
query?: {
export interface RVerifyEmail {
query: {
token: string;
};
}
+81 -91
View File
@@ -1,5 +1,5 @@
import { IEntrepreneurData, IGeneratedDocument, IIndividualData, IOrganizationData } from "coopdoc-generator-ts"
import { SovietContract } from "cooptypes";
import { IEntrepreneurData, IGeneratedDocument, IIndividualData, IOrganizationData } from 'coopdoc-generator-ts';
import { Cooperative, SovietContract } from 'cooptypes';
// Определение унифицированного типа для деталей платежа
export interface PaymentDetails {
@@ -8,73 +8,69 @@ export interface PaymentDetails {
}
export interface IYandexIPN {
type: string;
event: string;
object: {
id: string;
status: string;
paid: boolean;
amount: {
value: string;
currency: string;
};
income_amount: {
value: string;
currency: string;
};
authorization_details: {
rrn: string;
auth_code: string;
three_d_secure: {
applied: boolean;
};
};
created_at: string;
description: string;
expires_at: string;
metadata: Record<string, unknown>;
payment_method: {
type: string;
id: string;
saved: boolean;
card: {
first6: string;
last4: string;
expiry_month: string;
expiry_year: string;
card_type: string;
issuer_country: string;
issuer_name: string;
};
title: string;
};
refundable: boolean;
test: boolean;
type: string;
event: string;
object: {
id: string;
status: string;
paid: boolean;
amount: {
value: string;
currency: string;
};
income_amount: {
value: string;
currency: string;
};
authorization_details: {
rrn: string;
auth_code: string;
three_d_secure: {
applied: boolean;
};
};
created_at: string;
description: string;
expires_at: string;
metadata: Record<string, unknown>;
payment_method: {
type: string;
id: string;
saved: boolean;
card: {
first6: string;
last4: string;
expiry_month: string;
expiry_year: string;
card_type: string;
issuer_country: string;
issuer_name: string;
};
title: string;
};
refundable: boolean;
test: boolean;
};
}
export interface ICreatedPayment {
provider: string
provider: string;
order_id: string | number;
details: PaymentDetails
};
export interface IGetResponse {
results: any[]
page: number
limit: number
details: PaymentDetails;
}
export type IGetResponse<T> = Cooperative.Documents.IGetResponse<T>;
export interface IGetActions<T> {
results: IAction[]
page: number
limit: number
results: IAction[];
page: number;
limit: number;
}
export interface IGetTables<T> {
results: ITable[]
page: number
limit: number
results: ITable[];
page: number;
limit: number;
}
export interface ITable {
@@ -89,25 +85,24 @@ export interface ITable {
value?: any;
}
export interface IAction {
transaction_id: string;
account: string;
block_num: number,
block_id: string,
chain_id: string,
block_num: number;
block_id: string;
chain_id: string;
name: string;
receiver: string;
authorization: Array<{
actor: string;
permission: string;
actor: string;
permission: string;
}>;
data: any;
action_ordinal: number;
global_sequence: string;
account_ram_deltas: Array<{
account: string;
delta: number;
account: string;
delta: number;
}>;
console: string;
receipt: {
@@ -116,8 +111,8 @@ export interface IAction {
global_sequence: string;
recv_sequence: string;
auth_sequence: Array<{
account: string;
sequence: string;
account: string;
sequence: string;
}>;
code_sequence: number;
abi_sequence: number;
@@ -127,51 +122,46 @@ export interface IAction {
elapsed: number;
}
export interface IExtendedTable extends ITable {
}
export interface IExtendedTable extends ITable {}
export interface IExtendedAction extends IAction {
user: IIndividualData | IEntrepreneurData | IOrganizationData | null;
}
export interface IComplexStatement {
action: IExtendedAction
document: IGeneratedDocument
action: IExtendedAction;
document: IGeneratedDocument;
}
export interface IComplexDecision {
action: IExtendedAction
document: IGeneratedDocument
votes_for: IExtendedAction[],
votes_against: IExtendedAction[],
action: IExtendedAction;
document: IGeneratedDocument;
votes_for: IExtendedAction[];
votes_against: IExtendedAction[];
}
export interface IComplexAct {
action?: IExtendedAction
document?: IGeneratedDocument
action?: IExtendedAction;
document?: IGeneratedDocument;
}
export interface IComplexDocument {
statement: IComplexStatement
decision: IComplexDecision
acts: IComplexAct[]
statement: IComplexStatement;
decision: IComplexDecision;
acts: IComplexAct[];
}
export interface IGetComplexDocuments {
results: IComplexDocument[]
page: number
limit: number
results: IComplexDocument[];
page: number;
limit: number;
}
export interface IAgenda {
row: SovietContract.Tables.Decisions.IDecision
action: IAction
row: SovietContract.Tables.Decisions.IDecision;
action: IAction;
}
export interface IComplexAgenda extends IAgenda {
document: IComplexDocument
document: IComplexDocument;
}
@@ -21,6 +21,10 @@ export interface IGenerate {
}
export interface IGetDocuments {
/**
* Unknown Property
*/
[x: string]: unknown;
filter?: {
receiver?: string;
};
@@ -50,6 +54,10 @@ export interface RGenerate {
export interface RGetDocuments {
query?: {
/**
* Unknown Property
*/
[x: string]: unknown;
filter?: {
receiver?: string;
};
@@ -0,0 +1,74 @@
/**
* 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;
};
}
+2 -2
View File
@@ -5,7 +5,7 @@
export * from './auth.validation';
export * from './coop.validation';
export * from './data.validation';
export * from './document.validation';
export * from './notify.validation';
export * from './order.validation';
export * from './payment.validation';
export * from './user.validation';
@@ -0,0 +1,178 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface IBankAccount {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
}
export interface ICreateDeposit {
provider: 'yookassa';
quantity: string;
}
export interface ICreateInitialPayment {
provider: 'yookassa';
}
export interface IDeletePaymentMethod {
method_id: number;
}
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;
}
export interface ISavePaymentMethod {
data: {
phone: string;
} | ({
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
});
method_id: number;
method_type: 'sbp' | 'bank_transfer';
username: string;
}
export interface ISbpDetails {
phone: string;
}
export interface RDeletePaymentMethod {
body: {
method_id: number;
};
params?: {
username: string;
};
}
export interface RGetListPaymentMethods {
params?: {
username?: string;
};
}
export interface RSavePaymentMethod {
body: {
data: {
phone: string;
} | ({
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
});
method_id: number;
method_type: 'sbp' | 'bank_transfer';
username: string;
};
params?: {
username: string;
};
}
@@ -76,7 +76,7 @@ export interface ICreateUser {
password: string;
public_key: string;
referer?: string;
role: 'user' | 'admin';
role: 'user' | 'chairman' | 'member';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -243,7 +243,7 @@ export interface RCreateUser {
password: string;
public_key: string;
referer?: string;
role: 'user' | 'admin';
role: 'user' | 'chairman' | 'member';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
};
@@ -283,6 +283,6 @@ export interface RUpdateUser {
password?: string;
};
params?: {
username: any;
username: string;
};
}
+28
View File
@@ -0,0 +1,28 @@
import 'mongoose';
declare module 'mongoose' {
interface PaginateOptions {
sortBy?: string;
populate?: string;
limit?: number;
page?: number;
}
interface QueryResult<T> {
results: T[];
page: number;
limit: number;
totalPages: number;
totalResults: number;
}
interface PaginateModel<T extends Document> extends Model<T> {
paginate(filter: FilterQuery<T>, options: PaginateOptions): Promise<QueryResult<T>>;
}
}
declare global {
interface Error {
statusCode?: number;
subcode?: number;
}
}
@@ -1,43 +1,59 @@
import * as Joi from 'joi';
import { password } from './custom.validation'
import { password } from './custom.validation';
export const login = Joi.object({
body: Joi.object().keys({
username: Joi.string().required(),
password: Joi.string().required(),
}),
})
export const IRefreshTokens = Joi.object({
refreshToken: Joi.string().required(),
});
export const logout = Joi.object({
body: Joi.object().keys({
refreshToken: Joi.string().required(),
}),
})
export const RRefreshTokens = Joi.object({
body: IRefreshTokens.required(),
});
export const refreshTokens = Joi.object({
body: Joi.object().keys({
refreshToken: Joi.string().required(),
}),
})
export const IForgotPassword = Joi.object({
email: Joi.string().email().required(),
});
export const forgotPassword = Joi.object({
body: Joi.object().keys({
email: Joi.string().email().required(),
}),
})
export const RForgotPassword = Joi.object({
body: IForgotPassword.required(),
});
export const resetPassword = Joi.object({
query: Joi.object().keys({
token: Joi.string().required(),
}),
body: Joi.object().keys({
password: Joi.string().required().custom(password),
}),
})
// Внутренние параметры
export const ILogin = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required(),
});
export const verifyEmail = Joi.object({
query: Joi.object().keys({
token: Joi.string().required(),
}),
})
export const ILogout = Joi.object({
refreshToken: Joi.string().required(),
});
export const IResetPasswordQuery = Joi.object({
token: Joi.string().required(),
});
export const IResetPasswordBody = Joi.object({
password: Joi.string().required().custom(password),
});
export const IVerifyEmail = Joi.object({
token: Joi.string().required(),
});
// Внешние запросы
export const RLogin = Joi.object({
body: ILogin.required(),
});
export const RLogout = Joi.object({
body: ILogout.required(),
});
export const RResetPassword = Joi.object({
query: IResetPasswordQuery.required(),
body: IResetPasswordBody.required(),
});
export const RVerifyEmail = Joi.object({
query: IVerifyEmail.required(),
});
@@ -14,7 +14,7 @@ export const IGenerate = Joi.object({
}).unknown(true);
export const RGenerate = Joi.object({
body: IGenerate.required()
body: IGenerate.required(),
}).required();
export const IGetDocuments = Joi.object({
@@ -24,14 +24,14 @@ export const IGetDocuments = Joi.object({
sortBy: Joi.string(),
limit: Joi.number().integer(),
page: Joi.number().integer(),
})
}).unknown(true);
export const RGetDocuments = Joi.object({
query: IGetDocuments
})
query: IGetDocuments,
});
export const RGetMyDocuments = Joi.object({
query: Joi.object().keys({
username: Joi.string()
username: Joi.string(),
}),
})
});
+2 -2
View File
@@ -1,7 +1,7 @@
export * as authValidation from './auth.validation';
export * as userValidation from './user.validation';
export * as documentValidation from './data.validation';
export * as orderValidation from './order.validation';
export * as documentValidation from './document.validation';
export * as orderValidation from './payment.validation';
export * as coopValidation from './coop.validation';
export * as customValidation from './custom.validation';
export * as notifyValidation from './notify.validation';
@@ -2,21 +2,20 @@ import * as Joi from 'joi';
export const ICreateDeposit = Joi.object().keys({
quantity: Joi.string().required(),
provider: Joi.string().valid('yookassa').required()
})
provider: Joi.string().valid('yookassa').required(),
});
export const ICreateInitialPayment = Joi.object().keys({
provider: Joi.string().valid('yookassa').required()
})
provider: Joi.string().valid('yookassa').required(),
});
export const RCreateDeposit = {
body: ICreateDeposit.required(),
}
};
export const RCreateInitialPayment = {
body: ICreateInitialPayment.required(),
}
};
export const IRecieveIPN = Joi.object({
type: Joi.string().required(),
@@ -28,15 +27,21 @@ export const IRecieveIPN = Joi.object({
amount: Joi.object({
value: Joi.string().required(),
currency: Joi.string().required(),
}).required().unknown(true),
})
.required()
.unknown(true),
income_amount: Joi.object({
value: Joi.string().required(),
currency: Joi.string().required(),
}).required().unknown(true),
})
.required()
.unknown(true),
refunded_amount: Joi.object({
value: Joi.string().required(),
currency: Joi.string().required(),
}).required().unknown(true),
})
.required()
.unknown(true),
authorization_details: Joi.object({
rrn: Joi.string(),
auth_code: Joi.string(),
@@ -62,15 +67,70 @@ export const IRecieveIPN = Joi.object({
issuer_name: Joi.string(),
}).unknown(true),
title: Joi.string(),
}).required().unknown(true),
})
.required()
.unknown(true),
refundable: Joi.boolean(),
test: Joi.boolean(),
}).required().unknown(true),
})
.required()
.unknown(true),
});
export const RRecieveIPN = {
body: IRecieveIPN.required()
}
body: IRecieveIPN.required(),
};
const RussianBankDetailsSchema = Joi.object({
bik: Joi.string().required(),
corr: Joi.string().required(),
kpp: Joi.string().required(),
});
export const IBankAccount = Joi.object({
currency: Joi.string().required().valid('RUB', 'Other'),
card_number: Joi.string().default(''),
bank_name: Joi.string().required(),
account_number: Joi.string().required(),
details: RussianBankDetailsSchema.required(),
});
export const ISbpDetails = Joi.object({
phone: Joi.string().required(),
});
export const RGetListPaymentMethods = Joi.object({
params: {
username: Joi.string().optional(),
},
});
const MethodTypes = Joi.string().valid('sbp', 'bank_transfer');
export const ISavePaymentMethod = Joi.object({
username: Joi.string().required(),
method_id: Joi.number().required(),
method_type: MethodTypes.required(),
data: Joi.alternatives().try(ISbpDetails, IBankAccount).required(),
});
export const RSavePaymentMethod = Joi.object({
params: {
username: Joi.string().required(),
},
body: ISavePaymentMethod.required(),
});
export const IDeletePaymentMethod = Joi.object({
method_id: Joi.number().required(),
});
export const RDeletePaymentMethod = Joi.object({
params: {
username: Joi.string().required(),
},
body: IDeletePaymentMethod.required(),
});
/**
* @swagger
@@ -1,5 +1,6 @@
import * as Joi from 'joi';
import { password, objectId } from './custom.validation';
import { IBankAccount } from './payment.validation';
export const IIndividualData = Joi.object({
first_name: Joi.string().required(),
@@ -24,20 +25,6 @@ const RussiaDetailsSchema = Joi.object({
ogrn: Joi.string().required(),
});
const RussianBankDetailsSchema = Joi.object({
bik: Joi.string().required(),
corr: Joi.string().required(),
kpp: Joi.string().required(),
});
const IBankAccount = Joi.object({
currency: Joi.string().required().valid('RUB', 'Other'),
card_number: Joi.string().default(''),
bank_name: Joi.string().required(),
account_number: Joi.string().required(),
details: RussianBankDetailsSchema.required(),
});
export const IOrganizationData = Joi.object({
type: Joi.string().valid('coop', 'ooo', 'oao', 'zao', 'pao', 'ao').required(),
is_cooperative: Joi.boolean().required().default(false),
@@ -71,7 +58,7 @@ 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', 'admin'),
role: Joi.string().required().valid('user', 'chairman', 'member'),
public_key: Joi.string().required(),
username: Joi.string().required().length(12),
referer: Joi.string().length(12).allow('').optional(),
@@ -112,7 +99,7 @@ export const RGetUser = Joi.object({
export const RUpdateUser = Joi.object({
params: Joi.object().keys({
username: Joi.required().custom(objectId),
username: Joi.string().required(),
}),
body: Joi.object()
.keys({
+36 -42
View File
@@ -1,7 +1,7 @@
import { SovietContract, RegistratorContract } from 'cooptypes';
import { SovietContract, RegistratorContract, DraftContract } from 'cooptypes';
import { IAction, ITable } from '../../src/types/common';
import mongoose from 'mongoose';
import { Cooperative } from 'cooptypes';
import { DocumentsRegistry } from 'coopdoc-generator-ts';
export const insertAction = async (action: IAction) => {
const collection = mongoose.connection.db.collection('actions'); // Замените на имя вашей коллекции
@@ -18,43 +18,6 @@ export const insertDelta = async (delta: ITable) => {
await collection.insertOne(delta);
};
export const fixtureVoskhod = (): Cooperative.Users.IOrganizationData => {
return {
username: 'voskhod',
type: 'coop',
is_cooperative: true,
short_name: 'Voskhod',
full_name: 'ПК ВОСХОД',
represented_by: {
first_name: 'Алексей',
last_name: 'Муравьев',
middle_name: 'Николаевич',
position: 'Председатель',
based_on: 'Решения общего собрания №1',
},
country: 'Russia',
city: 'Moscow',
full_address: '123 Main St, Moscow, Russia',
email: 'contact@orgco.com',
phone: '+71234567890',
details: {
inn: '1234567890',
ogrn: '1234567890123',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '1234567890123456',
bank_name: 'Sberbank',
details: {
bik: '123456789',
corr: '30101810400000000225',
kpp: '123456789',
},
},
};
};
export const installInitialCooperativeData = async () => {
const delta1 = fixtureDelta(0, 'registrator', 'registrator', 'orgs', '1', {
username: 'voskhod',
@@ -64,9 +27,12 @@ export const installInitialCooperativeData = async () => {
is_cooperative: true,
is_branched: false,
coop_type: 'conscoop',
registration: '2.0000 RUB',
initial: '1.0000 RUB',
minimum: '1.0000 RUB',
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',
} as RegistratorContract.Tables.Cooperatives.ICooperative);
await insertDelta(delta1);
@@ -89,6 +55,34 @@ export const installInitialCooperativeData = async () => {
} as SovietContract.Tables.Boards.IBoards);
await insertDelta(delta2);
const k = 1;
for (const id in DocumentsRegistry) {
const template = DocumentsRegistry[id as unknown as keyof typeof DocumentsRegistry];
await insertDelta(
fixtureDelta(0, 'draft', 'draft', 'drafts', String(k), {
id: String(k),
creator: 'eosio',
version: String(1),
default_translation_id: String(k),
registry_id: String(id),
title: template.title,
description: template.description,
context: template.context,
model: JSON.stringify(template.model),
} as DraftContract.Tables.Drafts.IDraft)
);
await insertDelta(
fixtureDelta(0, 'draft', 'draft', 'translations', String(k), {
id: String(k),
draft_id: String(k),
lang: 'ru',
data: JSON.stringify(template.translations.ru),
} as DraftContract.Tables.Translations.ITranslation)
);
}
};
export const fixtureDelta = (
@@ -0,0 +1,29 @@
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',
referer: '',
type: 'individual',
individual_data: {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email1,
full_address: 'Russia, Moscow, Tverskaya street, 1',
},
};
+3 -1
View File
@@ -2,8 +2,10 @@ import moment from 'moment';
import config from '../../src/config/config';
import { tokenTypes } from '../../src/config/tokens';
import * as tokenService from '../../src/services/token.service';
import { userOne, admin } from './user.fixture';
import { userOne, userTwo, admin } from './user.fixture';
export const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
export const userOneAccessToken = tokenService.generateToken(userOne._id, accessTokenExpires, tokenTypes.ACCESS);
export const userTwoAccessToken = tokenService.generateToken(userTwo._id, accessTokenExpires, tokenTypes.ACCESS);
export const adminAccessToken = tokenService.generateToken(admin._id, accessTokenExpires, tokenTypes.ACCESS);
+242 -28
View File
@@ -1,85 +1,299 @@
import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';
import faker from 'faker';
import User from '../../src/models/user.model';
import User, { IUser } from '../../src/models/user.model';
import { generateUsername } from '../utils/generateUsername';
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 email1 = faker.internet.email().toLowerCase();
export const userOne = {
_id: mongoose.Types.ObjectId(),
const generateRandomId = () => new mongoose.Types.ObjectId();
type testUser = Omit<IUser, 'getPrivateData' | 'isPasswordMatch' | 'private_data'> & {
_id: mongoose.Types.ObjectId;
individual_data?: Cooperative.Users.IIndividualData;
organization_data?: Cooperative.Users.IOrganizationData;
entrepreneur_data?: Cooperative.Users.IEntrepreneurData;
block_num: number;
deleted: boolean;
};
const email1 = faker.internet.email().toLowerCase();
const email2 = faker.internet.email().toLowerCase();
const email3 = faker.internet.email().toLowerCase();
const email4 = faker.internet.email().toLowerCase();
const email5 = faker.internet.email().toLowerCase();
const adminUsername = generateUsername();
export const admin: testUser = {
_id: generateRandomId(),
email: email1,
password,
username: generateUsername(),
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
status: 'active',
message: '',
is_registered: true,
role: 'chairman',
is_email_verified: false,
username: adminUsername,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
statement: {
hash: '',
meta: {},
public_key: '',
signature: '',
},
referer: '',
type: 'individual',
individual_data: {
username: adminUsername,
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email1,
full_address: 'Russia, Moscow, Tverskaya street, 1',
full_address: 'Russia, Moscow, Tverskaya street, 3',
},
block_num: 0,
deleted: false,
};
const email2 = faker.internet.email().toLowerCase();
export const userTwo = {
_id: mongoose.Types.ObjectId(),
name: faker.name.findName(),
const usernameOne = generateUsername();
export const userOne: testUser = {
_id: generateRandomId(),
email: email2,
signature: '-',
signature_hash: '-',
username: generateUsername(),
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
password,
status: 'active',
message: '',
is_registered: true,
role: 'user',
is_email_verified: false,
username: usernameOne,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
statement: {
hash: '',
meta: {},
public_key: '',
signature: '',
},
referer: '',
type: 'individual',
individual_data: {
username: usernameOne,
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email2,
full_address: 'Russia, Moscow, Tverskaya street, 2',
full_address: 'Russia, Moscow, Tverskaya street, 1',
},
block_num: 0,
deleted: false,
};
const email3 = faker.internet.email().toLowerCase();
export const admin = {
_id: mongoose.Types.ObjectId(),
name: faker.name.findName(),
const usernameTwo = generateUsername();
export const userTwo: testUser = {
_id: generateRandomId(),
email: email3,
password,
role: 'admin',
status: 'active',
message: '',
is_registered: true,
role: 'user',
is_email_verified: false,
signature: '',
signature_hash: '',
username: generateUsername(),
username: usernameTwo,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
statement: {
hash: '',
meta: {},
public_key: '',
signature: '',
},
referer: '',
type: 'individual',
individual_data: {
username: usernameTwo,
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email3,
full_address: 'Russia, Moscow, Tverskaya street, 3',
full_address: 'Russia, Moscow, Tverskaya street, 2',
},
block_num: 0,
deleted: false,
};
export const insertUsers = async (users: any[]) => {
await User.insertMany(users.map((user: any) => ({ ...user, password: hashedPassword })));
export const chairman: testUser = {
_id: generateRandomId(),
email: email4,
password,
status: 'active',
message: '',
is_registered: true,
role: 'user',
is_email_verified: false,
username: 'ant',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
statement: {
hash: '',
meta: {},
public_key: '',
signature: '',
},
referer: '',
type: 'individual',
individual_data: {
username: 'ant',
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email4,
full_address: 'Russia, Moscow, Tverskaya street, 1',
},
block_num: 0,
deleted: false,
};
export const voskhod: testUser = {
_id: generateRandomId(),
email: email5,
password,
status: 'active',
message: '',
is_registered: true,
role: 'user',
is_email_verified: false,
username: 'voskhod',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
statement: {
hash: '',
meta: {},
public_key: '',
signature: '',
},
referer: '',
type: 'organization',
organization_data: {
username: 'voskhod',
type: 'coop',
is_cooperative: true,
short_name: '"ПК Восход"',
full_name: 'Потребительский Кооператив "ВОСХОД"',
represented_by: {
first_name: 'Имя',
last_name: 'Фамилия',
middle_name: 'Отчество',
position: 'Председатель',
based_on: 'Решения общего собрания №1',
},
country: 'Russia',
city: 'Москва',
full_address: 'Переулок Правды, дом 1',
email: email5,
phone: '+71234567890',
details: {
inn: '1234567890',
ogrn: '1234567890123',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '1234567890123456',
bank_name: 'Sberbank',
details: {
bik: '123456789',
corr: '30101810400000000225',
kpp: '123456789',
},
},
},
block_num: 0,
deleted: false,
};
export const insertPrivateEntrepreneurUserData = async (data: any) => {
const collection = mongoose.connection.db.collection('EntrepreneurData'); // Замените на имя вашей коллекции
await collection.insertOne({ ...data });
};
export const insertPrivateIndividualUserData = async (data: any) => {
const collection = mongoose.connection.db.collection('IndividualData'); // Замените на имя вашей коллекции
await collection.insertOne({ ...data });
};
export const insertPrivateOrganizationUserData = async (data: any) => {
const collection = mongoose.connection.db.collection('OrgData'); // Замените на имя вашей коллекции
await collection.insertOne({ ...data });
};
export interface IPaymentData {
username: string;
method_id: number;
user_type: 'individual' | 'entrepreneur' | 'organization';
method_type: 'sbp' | 'bank_transfer';
is_default: boolean;
data: any;
}
export const insertPaymentMethod = async (data: IPaymentData, block_num: number) => {
const collection = mongoose.connection.db.collection('PaymentData'); // Замените на имя вашей коллекции
await collection.insertOne({ ...data, block_num, _created_at: new Date(), deleted: false });
};
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 }]);
if (type === 'individual' && individual_data) {
await insertPrivateIndividualUserData({
...individual_data,
username: rest.username,
block_num,
_created_at: new Date(),
});
} else if (type === 'organization' && organization_data) {
const { bank_account, ...org_data } = organization_data;
await insertPrivateOrganizationUserData({ ...org_data, username: rest.username, block_num, _created_at: new Date() });
await insertPaymentMethod(
{
username: rest.username,
method_id: 1,
user_type: 'organization',
method_type: 'bank_transfer',
is_default: true,
data: bank_account,
},
block_num
);
} else if (type === 'entrepreneur' && entrepreneur_data) {
const { bank_account, ...entr_data } = entrepreneur_data;
await insertPrivateEntrepreneurUserData({ ...entr_data, username: rest.username, block_num, _created_at: new Date() });
await insertPaymentMethod(
{
username: rest.username,
method_id: 1,
user_type: 'organization',
method_type: 'bank_transfer',
is_default: true,
data: bank_account,
},
block_num
);
}
}
};
+211 -170
View File
@@ -15,30 +15,59 @@ import { roleRights } from '../../src/config/roles';
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';
setupTestDB();
describe('Auth routes', () => {
describe('POST /v1/auth/register', () => {
describe('POST /v1/users', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase();
newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
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 register user if request data is ok', async () => {
const res = await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.CREATED);
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(),
name: newUser.name,
email: newUser.email,
role: 'user',
is_email_verified: false,
has_account: false,
is_registered: false,
public_key: newUser.public_key,
referer: newUser.referer,
status: 'created',
type: 'individual',
username: newUser.username,
statement: {
hash: '',
public_key: '',
signature: '',
meta: {},
},
});
const dbUser = await User.findById(res.body.user.id);
@@ -46,7 +75,7 @@ describe('Auth routes', () => {
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
expect(dbUser).toMatchObject({ name: newUser.name, email: newUser.email, role: 'user', is_email_verified: false });
expect(dbUser).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
expect(res.body.tokens).toEqual({
access: { token: expect.anything(), expires: expect.anything() },
@@ -57,30 +86,30 @@ describe('Auth routes', () => {
test('should return 400 error if email is invalid', async () => {
newUser.email = 'invalidEmail';
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if email is already used', async () => {
await insertUsers([userOne]);
newUser.email = userOne.email;
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
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/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
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/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
newUser.password = '11111111';
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
});
@@ -92,11 +121,13 @@ describe('Auth routes', () => {
password: userOne.password,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.OK);
const res = await request(app).post('/v1/auth/login').send(loginCredentials);
expect(res.body.user).toEqual({
expect(res.status).toEqual(httpStatus.OK);
expect(res.body.user).toMatchObject({
id: expect.anything(),
name: userOne.name,
username: userOne.username,
email: userOne.email,
role: userOne.role,
is_email_verified: userOne.is_email_verified,
@@ -146,7 +177,8 @@ describe('Auth routes', () => {
});
test('should return 400 error if refresh token is missing from request body', async () => {
await request(app).post('/v1/auth/logout').send().expect(httpStatus.BAD_REQUEST);
const res = await request(app).post('/v1/auth/logout').send();
expect(res.status).toBe(httpStatus.BAD_REQUEST);
});
test('should return 404 error if refresh token is not found in the database', async () => {
@@ -173,65 +205,57 @@ describe('Auth routes', () => {
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
const res = await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.OK);
const res = await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken });
expect(res.status).toBe(httpStatus.OK);
expect(res.body).toEqual({
access: { token: expect.anything(), expires: expect.anything() },
refresh: { token: expect.anything(), expires: expect.anything() },
});
const dbRefreshTokenDoc = await Token.findOne({ token: res.body.refresh.token });
expect(dbRefreshTokenDoc).toMatchObject({ type: tokenTypes.REFRESH, user: userOne._id, blacklisted: false });
expect(dbRefreshTokenDoc).toHaveProperty('blacklisted');
expect(dbRefreshTokenDoc).toHaveProperty('token');
expect(dbRefreshTokenDoc).toHaveProperty('user');
expect(dbRefreshTokenDoc).toHaveProperty('expires');
expect(dbRefreshTokenDoc).toHaveProperty('type');
expect(dbRefreshTokenDoc).toHaveProperty('createdAt');
expect(dbRefreshTokenDoc).toHaveProperty('updatedAt');
const dbRefreshTokenCount = await Token.countDocuments();
expect(dbRefreshTokenCount).toBe(1);
});
test('should return 400 error if refresh token is missing from request body', async () => {
await request(app).post('/v1/auth/refresh-tokens').send().expect(httpStatus.BAD_REQUEST);
});
test('should return 401 error if refresh token is signed using an invalid secret', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH, 'invalidSecret');
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 error if refresh token is not found in the database', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 error if refresh token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH, true);
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 error if refresh token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const refreshToken = tokenService.generateToken(userOne._id, expires);
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 error if user is not found', async () => {
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
});
});
@@ -249,14 +273,16 @@ describe('Auth routes', () => {
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 });
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetPasswordToken, user: userOne._id.toString() });
expect(dbResetPasswordTokenDoc).toBeDefined();
});
test('should return 400 if email is missing', async () => {
await insertUsers([userOne]);
await request(app).post('/v1/auth/forgot-password').send().expect(httpStatus.BAD_REQUEST);
const res = await request(app).post('/v1/auth/forgot-password').send();
expect(res.status).toBe(httpStatus.BAD_REQUEST);
});
test('should return 404 if email does not belong to any user', async () => {
@@ -271,18 +297,26 @@ describe('Auth routes', () => {
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app)
const res = await request(app)
.post('/v1/auth/reset-password')
.query({ token: resetPasswordToken })
.send({ password: 'password2' })
.expect(httpStatus.NO_CONTENT);
.send({ password: 'password2' });
expect(res.status).toBe(httpStatus.NO_CONTENT);
const dbUser = await User.findById(userOne._id);
const isPasswordMatch = await bcrypt.compare('password2', dbUser.password);
expect(isPasswordMatch).toBe(true);
expect(dbUser).not.toBeUndefined();
const dbResetPasswordTokenCount = await Token.countDocuments({ user: userOne._id, type: tokenTypes.RESET_PASSWORD });
expect(dbResetPasswordTokenCount).toBe(0);
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,
});
expect(dbResetPasswordTokenCount).toBe(0);
}
});
test('should return 400 if reset password token is missing', async () => {
@@ -373,7 +407,7 @@ describe('Auth routes', () => {
expect(sendVerificationEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const verifyEmailToken = sendVerificationEmailSpy.mock.calls[0][1];
const dbVerifyEmailToken = await Token.findOne({ token: verifyEmailToken, user: userOne._id });
const dbVerifyEmailToken = await Token.findOne({ token: verifyEmailToken, user: userOne._id.toString() });
expect(dbVerifyEmailToken).toBeDefined();
});
@@ -389,7 +423,7 @@ describe('Auth routes', () => {
test('should return 204 and verify the email', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
const verifyEmailToken = tokenService.generateToken(userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await request(app)
@@ -400,10 +434,14 @@ describe('Auth routes', () => {
const dbUser = await User.findById(userOne._id);
expect(dbUser).not.toBeUndefined();
if (!dbUser) return;
expect(dbUser.is_email_verified).toBe(true);
const dbVerifyEmailToken = await Token.countDocuments({
user: userOne._id,
user: userOne._id.toString(),
type: tokenTypes.VERIFY_EMAIL,
});
expect(dbVerifyEmailToken).toBe(0);
@@ -418,7 +456,7 @@ describe('Auth routes', () => {
test('should return 401 if verify email token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
const verifyEmailToken = tokenService.generateToken(userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL, true);
await request(app)
@@ -431,7 +469,7 @@ describe('Auth routes', () => {
test('should return 401 if verify email token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
const verifyEmailToken = tokenService.generateToken(userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await request(app)
@@ -443,7 +481,7 @@ describe('Auth routes', () => {
test('should return 401 if user is not found', async () => {
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
const verifyEmailToken = tokenService.generateToken(userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL);
await request(app)
@@ -453,137 +491,140 @@ describe('Auth routes', () => {
.expect(httpStatus.UNAUTHORIZED);
});
});
});
describe('Auth middleware', () => {
test('should call next with no errors if access token is valid', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
const next = jest.fn();
describe('Auth middleware', () => {
test('should call next with no errors if access token is valid', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith();
expect(req.user._id).toEqual(userOne._id);
});
test('should call next with unauthorized error if access token is not found in header', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest();
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if access token is not a valid jwt token', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({ headers: { Authorization: 'Bearer randomToken' } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if the token is not an access token', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${refreshToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if access token is generated with an invalid secret', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS, 'invalidSecret');
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if access token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if user is not found', async () => {
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with forbidden error if user does not have required rights and userId is not in params', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
const next = jest.fn();
await auth('anyRight')(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(expect.objectContaining({ statusCode: httpStatus.FORBIDDEN, message: 'Forbidden' }));
});
test('should call next with no errors if user does not have required rights but userId is in params', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({
headers: { Authorization: `Bearer ${userOneAccessToken}` },
params: { userId: userOne._id.toHexString() },
expect(next).toHaveBeenCalledWith();
expect(req.user._id).toEqual(userOne._id);
});
const next = jest.fn();
await auth('anyRight')(req, httpMocks.createResponse(), next);
test('should call next with unauthorized error if access token is not found in header', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest();
const next = jest.fn();
expect(next).toHaveBeenCalledWith();
});
await auth()(req, httpMocks.createResponse(), next);
test('should call next with no errors if user has required rights', async () => {
await insertUsers([admin]);
const req = httpMocks.createRequest({
headers: { Authorization: `Bearer ${adminAccessToken}` },
params: { userId: userOne._id.toHexString() },
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
const next = jest.fn();
await auth(...roleRights.get('admin'))(req, httpMocks.createResponse(), next);
test('should call next with unauthorized error if access token is not a valid jwt token', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({ headers: { Authorization: 'Bearer randomToken' } });
const next = jest.fn();
expect(next).toHaveBeenCalledWith();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if the token is not an access token', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${refreshToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if access token is generated with an invalid secret', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS, 'invalidSecret');
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if access token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with unauthorized error if user is not found', async () => {
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
const next = jest.fn();
await auth()(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
);
});
test('should call next with forbidden error if user does not have required rights and userId is not in params', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
const next = jest.fn();
await auth('anyRight')(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
expect(next).toHaveBeenCalledWith(
expect.objectContaining({ statusCode: httpStatus.FORBIDDEN, message: 'Недостаточно прав доступа' })
);
});
test('should call next with no errors if user does not have required rights but username is in params', async () => {
await insertUsers([userOne]);
const req = httpMocks.createRequest({
headers: { Authorization: `Bearer ${userOneAccessToken}` },
params: { username: userOne.username },
});
const next = jest.fn();
await auth('anyRight')(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith();
});
test('should call next with no errors if user has required rights', async () => {
await insertUsers([admin]);
const req = httpMocks.createRequest({
headers: { Authorization: `Bearer ${adminAccessToken}` },
params: { userId: userOne._id.toHexString() },
});
const next = jest.fn();
const chairmanRights = roleRights.get('chairman') || [];
await auth(...chairmanRights)(req, httpMocks.createResponse(), next);
expect(next).toHaveBeenCalledWith();
});
});
});
@@ -7,42 +7,59 @@ import { generateUsername } from '../utils/generateUsername';
import { User } from '../../src/models';
import { IGenerateJoinCoop, IGeneratedDocument, IIndividualData } from 'coopdoc-generator-ts';
import { IDocument, IJoinCooperative } from '../../src/types';
import ecc from 'eosjs-ecc'
import { admin, insertUsers, userOne, userTwo } from '../fixtures/user.fixture';
import { adminAccessToken } from '../fixtures/token.fixture';
import ecc from 'eosjs-ecc';
import { admin, chairman, insertUsers, userOne, userTwo, voskhod } from '../fixtures/user.fixture';
import { adminAccessToken, userOneAccessToken } from '../fixtures/token.fixture';
import { fixtureAction, insertAction, insertActions, installInitialCooperativeData } from '../fixtures/document.fixture';
import { SovietContract } from 'cooptypes';
import { participantOne } from '../fixtures/participant.fixture';
const public_key = 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV';
setupTestDB();
describe('Проверка получения документов', () => {
describe('GET /v1/data/get-documents', () => {
test('Нельзя без авторизации админа получить все документы', async () => {
await insertUsers([admin]);
const documents = await request(app).get('/v1/data/get-documents').send()
describe('GET /v1/documents/get-documents', () => {
test('Нельзя без авторизации получить все документы', async () => {
const documents = await request(app).get('/v1/documents/get-documents').send();
expect(documents.status).toBe(httpStatus.UNAUTHORIZED);
});
test('Успешное получение списка документов', async () => {
await insertUsers([admin]);
installInitialCooperativeData()
test('Нельзя без авторизации админа получить все документы', async () => {
await insertUsers([userOne]);
const registeredUser = await request(app).post('/v1/users').send(userOne)
const documents = await request(app)
.get('/v1/documents/get-documents')
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(documents.status).toBe(httpStatus.FORBIDDEN);
});
test('Успешное генерация и извлечение списка документов', async () => {
const coopData = voskhod;
await insertUsers([coopData, chairman, admin]);
await installInitialCooperativeData();
const registeredUser = await request(app).post('/v1/users').send(participantOne);
expect(registeredUser.status).toBe(httpStatus.CREATED);
const options: IGenerateJoinCoop = {
action: 'joincoop',
code: 'registrator',
coopname: 'voskhod',
username: userOne.username,
signature: 'this is imaged signature'
coopname: coopData.username,
username: participantOne.username,
signature: 'this is imaged signature',
skip_save: false,
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.meta).toBeDefined();
@@ -50,109 +67,157 @@ describe('Проверка получения документов', () => {
expect(res.body.html).toBeDefined();
expect(res.body.hash).toBeDefined();
const signature = await ecc.sign(res.body.hash, '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')
const signature = await ecc.sign(res.body.hash, '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
//имитируем событие парсинга заявления из блокчейна
const newsubmitted_document = fixtureAction(1, SovietContract.Actions.Registry.NewSubmitted.actionName, userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
username: userOne.username,
action: SovietContract.Actions.Registry.NewSubmitted.actionName,
decision_id: 1,
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: userOne.public_key
}
} as SovietContract.Actions.Registry.NewSubmitted.INewSubmitted)
await insertActions(newsubmitted_document)
// //имитируем событие парсинга заявления из блокчейна
const newsubmitted_document = fixtureAction(
1,
SovietContract.Actions.Registry.NewSubmitted.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
username: participantOne.username,
action: SovietContract.Actions.Registry.NewSubmitted.actionName,
decision_id: 1,
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: public_key,
},
} as SovietContract.Actions.Registry.NewSubmitted.INewSubmitted
);
await insertActions(newsubmitted_document);
//добавляем голоса за решение
//имитируем событие парсинга решения из блокчейна
const vorfor_action1 = fixtureAction(1, SovietContract.Actions.Decisions.VoteFor.actionName, userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
member: 'ant',
decision_id: "1",
} as SovietContract.Actions.Decisions.VoteFor.IVoteForDecision)
const vorfor_action1 = fixtureAction(
1,
SovietContract.Actions.Decisions.VoteFor.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
member: 'ant',
decision_id: '1',
} as SovietContract.Actions.Decisions.VoteFor.IVoteForDecision
);
await insertActions(vorfor_action1)
await insertActions(vorfor_action1);
const vorfor_action2 = fixtureAction(1, SovietContract.Actions.Decisions.VoteFor.actionName, userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
member: 'ant',
decision_id: "2",
} as SovietContract.Actions.Decisions.VoteFor.IVoteForDecision)
const vorfor_action2 = fixtureAction(
1,
SovietContract.Actions.Decisions.VoteFor.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
member: 'ant',
decision_id: '2',
} as SovietContract.Actions.Decisions.VoteFor.IVoteForDecision
);
await insertActions(vorfor_action2)
await insertActions(vorfor_action2);
const vorfor_action3 = fixtureAction(1, SovietContract.Actions.Decisions.VoteFor.actionName,userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
member: 'ant',
decision_id: "3",
} as SovietContract.Actions.Decisions.VoteFor.IVoteForDecision)
const vorfor_action3 = fixtureAction(
1,
SovietContract.Actions.Decisions.VoteFor.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
member: 'ant',
decision_id: '3',
} as SovietContract.Actions.Decisions.VoteFor.IVoteForDecision
);
await insertActions(vorfor_action3)
await insertActions(vorfor_action3);
//TODO генерируем решение совета и добавляем его в блокчейн
//generate
//имитируем событие парсинга решения из блокчейна
const newdecision_document = fixtureAction(1, SovietContract.Actions.Registry.NewDecision.actionName, userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
username: userOne.username,
action: SovietContract.Actions.Registry.NewDecision.actionName,
decision_id: "1",
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: userOne.public_key
}
} as SovietContract.Actions.Registry.NewDecision.INewDecision)
const newdecision_document = fixtureAction(
1,
SovietContract.Actions.Registry.NewDecision.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
username: participantOne.username,
action: SovietContract.Actions.Registry.NewDecision.actionName,
decision_id: '1',
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: public_key,
},
} as SovietContract.Actions.Registry.NewDecision.INewDecision
);
await insertActions(newdecision_document)
await insertActions(newdecision_document);
const newdecision_document2 = fixtureAction(1, SovietContract.Actions.Registry.NewDecision.actionName, userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
username: userOne.username,
action: SovietContract.Actions.Registry.NewDecision.actionName,
decision_id: "2",
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: userOne.public_key
}
} as SovietContract.Actions.Registry.NewDecision.INewDecision)
const newdecision_document2 = fixtureAction(
1,
SovietContract.Actions.Registry.NewDecision.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
username: participantOne.username,
action: SovietContract.Actions.Registry.NewDecision.actionName,
decision_id: '2',
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: public_key,
},
} as SovietContract.Actions.Registry.NewDecision.INewDecision
);
await insertActions(newdecision_document2)
await insertActions(newdecision_document2);
const newdecision_document3 = fixtureAction(1, SovietContract.Actions.Registry.NewDecision.actionName, userOne.username, 'voskhod', 'soviet', {
coopname: 'voskhod',
username: userOne.username,
action: SovietContract.Actions.Registry.NewDecision.actionName,
decision_id: "3",
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: userOne.public_key
}
} as SovietContract.Actions.Registry.NewDecision.INewDecision)
await insertActions(newdecision_document3)
const newdecision_document3 = fixtureAction(
1,
SovietContract.Actions.Registry.NewDecision.actionName,
participantOne.username,
coopData.username,
'soviet',
{
coopname: coopData.username,
username: participantOne.username,
action: SovietContract.Actions.Registry.NewDecision.actionName,
decision_id: '3',
document: {
meta: res.body.meta,
hash: res.body.hash,
signature,
public_key: public_key,
},
} as SovietContract.Actions.Registry.NewDecision.INewDecision
);
await insertActions(newdecision_document3);
//получаем документ
const documents = await request(app).get('/v1/data/get-documents').set('Authorization', `Bearer ${adminAccessToken}`).query({ limit: 1 }).send()
const documents = await request(app)
.get('/v1/documents/get-documents')
.set('Authorization', `Bearer ${adminAccessToken}`)
.query({ limit: 1 })
.send();
console.log("documentss: ", documents.body)
expect(documents.body.results.length).toBeDefined()
expect(documents.body.results.length).toBeDefined();
expect(documents.status).toBe(httpStatus.OK);
});
})
})
});
});
@@ -7,15 +7,23 @@ import { generateUsername } from '../utils/generateUsername';
import { User } from '../../src/models';
import { IGenerateJoinCoop, IGeneratedDocument, IIndividualData } from 'coopdoc-generator-ts';
import { IDocument, IJoinCooperative } from '../../src/types';
import ecc from 'eosjs-ecc'
import ecc from 'eosjs-ecc';
import { admin, chairman, insertUsers, voskhod } from '../fixtures/user.fixture';
import { installInitialCooperativeData } from '../fixtures/document.fixture';
import { PrivateKey } from '@wharfkit/antelope';
setupTestDB();
describe('Проверка данных', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase()
beforeEach(async () => {
const coopData = voskhod;
await insertUsers([coopData, chairman, admin]);
await installInitialCooperativeData();
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
@@ -39,8 +47,8 @@ describe('Проверка данных', () => {
describe('POST /v1/users/join-cooperative', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase()
beforeEach(async () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
@@ -66,7 +74,7 @@ describe('Проверка данных', () => {
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
});
@@ -76,11 +84,11 @@ describe('Проверка данных', () => {
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = await dbUser?.getPrivateData() as IIndividualData;
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -89,11 +97,15 @@ describe('Проверка данных', () => {
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'this is imaged signature'
signature: 'this is imaged signature',
skip_save: false,
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
console.log("res.body: ", res.body)
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
// console.log('res.body: ', res.body);
expect(res.status).toBe(httpStatus.CREATED);
@@ -108,11 +120,11 @@ describe('Проверка данных', () => {
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = await dbUser?.getPrivateData() as IIndividualData;
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -121,10 +133,14 @@ describe('Проверка данных', () => {
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'this is imaged signature'
signature: 'this is imaged signature',
skip_save: false,
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
expect(res.status).toBe(httpStatus.CREATED);
@@ -133,30 +149,38 @@ describe('Проверка данных', () => {
expect(res.body.html).toBeDefined();
expect(res.body.hash).toBeDefined();
let res_regenerated = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(res.body.meta);
let res_regenerated = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(res.body.meta);
// console.log('res.body', res_regenerated.body);
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.hash).toEqual(res_regenerated.body.hash);
});
test('should return 500 error if signature is invalid', async () => {
const registeredUser = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = await dbUser?.getPrivateData() as IIndividualData;
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
const options: IGenerateJoinCoop = {
action: 'joincoop',
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'any imaged signature'
signature: 'any imaged signature',
skip_save: false,
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
expect(res.status).toBe(httpStatus.CREATED);
const generatedDocument: IGeneratedDocument = res.body;
@@ -170,36 +194,47 @@ describe('Проверка данных', () => {
const joinCoopData: IJoinCooperative = {
username: newUser.username,
document: signedDocument
statement: signedDocument,
};
let joincoop_result = await request(app).post('/v1/users/join-cooperative').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(joinCoopData);
console.log(joincoop_result.body);
let joincoop_result = await request(app)
.post('/v1/users/join-cooperative')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(joinCoopData);
// console.log(joincoop_result.body);
expect(joincoop_result.status).toBe(httpStatus.INTERNAL_SERVER_ERROR);
});
test('should return 400 error if public_key is mismatch', async () => {
const registeredUser = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = await dbUser?.getPrivateData() as IIndividualData;
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
const options: IGenerateJoinCoop = {
action: 'joincoop',
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'any imaged signature'
signature: 'any imaged signature',
skip_save: false,
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
expect(res.status).toBe(httpStatus.CREATED);
const generatedDocument: IGeneratedDocument = res.body;
const sign = await ecc.sign(generatedDocument.hash, '5JA8KCpXbCfWA9fS4zHKgcBPbnNaRha8iKHhZLV7ks9Gs3LenpU')
// const sign = await ecc.sign(generatedDocument.hash, '5JA8KCpXbCfWA9fS4zHKgcBPbnNaRha8iKHhZLV7ks9Gs3LenpU');
const key = '5JA8KCpXbCfWA9fS4zHKgcBPbnNaRha8iKHhZLV7ks9Gs3LenpU';
const wif = await PrivateKey.fromString(key);
const sign = await wif.signDigest(generatedDocument.hash).toString();
const signedDocument: IDocument = {
hash: generatedDocument.hash,
@@ -210,25 +245,27 @@ describe('Проверка данных', () => {
const joinCoopData: IJoinCooperative = {
username: newUser.username,
document: signedDocument
statement: signedDocument,
};
let joincoop_result = await request(app).post('/v1/users/join-cooperative').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(joinCoopData);
console.log(joincoop_result.body);
let joincoop_result = await request(app)
.post('/v1/users/join-cooperative')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(joinCoopData);
// console.log(joincoop_result.body);
expect(joincoop_result.status).toBe(httpStatus.BAD_REQUEST);
});
test('should join the cooperative', async () => {
const registeredUser = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = await dbUser?.getPrivateData() as IIndividualData;
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -237,10 +274,14 @@ describe('Проверка данных', () => {
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'this is imaged signature'
signature: 'this is imaged signature',
skip_save: false,
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
expect(res.status).toBe(httpStatus.CREATED);
@@ -249,7 +290,10 @@ describe('Проверка данных', () => {
expect(res.body.html).toBeDefined();
expect(res.body.hash).toBeDefined();
let res_regenerated = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(res.body.meta);
let res_regenerated = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(res.body.meta);
expect(res.status).toBe(httpStatus.CREATED);
@@ -257,7 +301,9 @@ describe('Проверка данных', () => {
const generatedDocument: IGeneratedDocument = res.body;
const sign = await ecc.sign(generatedDocument.hash, '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')
const key = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
const wif = await PrivateKey.fromString(key);
const sign = await wif.signDigest(generatedDocument.hash).toString();
const signedDocument: IDocument = {
hash: generatedDocument.hash,
@@ -268,93 +314,25 @@ describe('Проверка данных', () => {
const joinCoopData: IJoinCooperative = {
username: newUser.username,
document: signedDocument
statement: signedDocument,
};
let joincoop_result = await request(app).post('/v1/users/join-cooperative').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(joinCoopData);
console.log(joincoop_result.body);
let joincoop_result = await request(app)
.post('/v1/users/join-cooperative')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(joinCoopData);
// console.log(joincoop_result.body);
expect(joincoop_result.status).toBe(httpStatus.OK);
const dbUser2 = await User.findOne({username: newUser.username});
const dbUser2 = await User.findOne({ username: newUser.username });
expect(dbUser2).toBeDefined();
expect(dbUser2?.status).toBe('joined');
expect(dbUser2?.statement).toBeDefined()
console.log(dbUser2);
expect(dbUser2?.statement).toBeDefined();
// console.log(dbUser2);
});
test('получаем платежный ордер на вступление', async () => {
const registeredUser = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({username: newUser.username});
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = await dbUser?.getPrivateData() as IIndividualData;
expect(privateData).toBeDefined();
const options: IGenerateJoinCoop = {
action: 'joincoop',
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'this is imaged signature'
};
let res = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(options);
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.meta).toBeDefined();
expect(res.body.binary).toBeDefined();
expect(res.body.html).toBeDefined();
expect(res.body.hash).toBeDefined();
let res_regenerated = await request(app).post('/v1/data/generate').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(res.body.meta);
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.hash).toEqual(res_regenerated.body.hash);
const generatedDocument: IGeneratedDocument = res.body;
const sign = await ecc.sign(generatedDocument.hash, '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3')
const signedDocument: IDocument = {
hash: generatedDocument.hash,
meta: generatedDocument.meta,
signature: sign,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
};
const joinCoopData: IJoinCooperative = {
username: newUser.username,
document: signedDocument
};
let joincoop_result = await request(app).post('/v1/users/join-cooperative').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send(joinCoopData);
console.log(joincoop_result.body);
expect(joincoop_result.status).toBe(httpStatus.OK);
const dbUser2 = await User.findOne({username: newUser.username});
expect(dbUser2).toBeDefined();
expect(dbUser2?.status).toBe('joined');
expect(dbUser2?.statement).toBeDefined()
console.log(dbUser2);
const initialPayment = await request(app).post('/v1/orders/initial').set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`).send({username: newUser.username})
console.log('result: ', initialPayment.body)
expect(initialPayment.status).toBe(httpStatus.CREATED)
expect(initialPayment.body?.confirmation_token).toBeDefined()
expect(initialPayment.body?.order_id).toBeDefined()
});
});
// ---
// test('регистрируем пользователя в блокчейне', async () => {
// await request(app)
// .post('/v1/auth/register')
@@ -365,9 +343,9 @@ describe('Проверка данных', () => {
// test('should return 201 and successfully register user if request data is ok', async () => {
// await request(app)
// .post('/v1/data/generate')
// .post('/v1/documents/generate')
// // .set('Authorization', `Bearer ${adminAccessToken}`)
// .send(newUser)
// .expect(httpStatus.BAD_REQUEST);
// })
})
});
@@ -0,0 +1,540 @@
import app from '../../src/app';
import request from 'supertest';
import httpStatus from 'http-status';
import { setupTestDB } from '../utils/setupTestDB';
import faker from 'faker';
import { generateUsername } from '../utils/generateUsername';
import { User } from '../../src/models';
import { IGenerateJoinCoop, IGeneratedDocument, IIndividualData } from 'coopdoc-generator-ts';
import {
IBankAccount,
IDeletePaymentMethod,
IDocument,
IJoinCooperative,
ISavePaymentMethod,
ISbpDetails,
} from '../../src/types';
import ecc from 'eosjs-ecc';
import { admin, chairman, insertUsers, userOne, userTwo, voskhod } from '../fixtures/user.fixture';
import { adminAccessToken, userOneAccessToken, userTwoAccessToken } from '../fixtures/token.fixture';
import { fixtureAction, insertAction, insertActions, installInitialCooperativeData } from '../fixtures/document.fixture';
import { Cooperative, SovietContract } from 'cooptypes';
import { participantOne } from '../fixtures/participant.fixture';
import { PrivateKey } from '@wharfkit/antelope';
import exp from 'constants';
const public_key = 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV';
setupTestDB();
describe('Проверка получения документов', () => {
let sbpPaymentData: ISavePaymentMethod;
let bankPaymentData: ISavePaymentMethod;
beforeEach(() => {
sbpPaymentData = {
username: '',
method_id: 1,
method_type: 'sbp',
data: { phone: '+7900-888-988-00' } as ISbpDetails,
};
bankPaymentData = {
username: '',
method_id: 2,
method_type: 'bank_transfer',
data: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '1234567890123456',
bank_name: 'Sberbank',
details: {
bik: '123456789',
corr: '30101810400000000225',
kpp: '123456789',
},
} as IBankAccount,
};
});
describe('GET /v1/payments/initial', () => {
test('получаем платежный ордер на вступление', async () => {
const coopData = voskhod;
await insertUsers([coopData, chairman, admin]);
await installInitialCooperativeData();
const email = faker.internet.email().toLowerCase();
const newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
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',
},
};
const registeredUser = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(registeredUser.body.tokens.access.token).toBeDefined();
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.status).toBe('created');
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
const options: IGenerateJoinCoop = {
action: 'joincoop',
code: 'registrator',
coopname: 'voskhod',
username: newUser.username,
signature: 'this is imaged signature',
skip_save: false,
};
let res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.meta).toBeDefined();
expect(res.body.binary).toBeDefined();
expect(res.body.html).toBeDefined();
expect(res.body.hash).toBeDefined();
let res_regenerated = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(res.body.meta);
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.hash).toEqual(res_regenerated.body.hash);
const generatedDocument: IGeneratedDocument = res.body;
const key = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
const wif = await PrivateKey.fromString(key);
const sign = await wif.signDigest(generatedDocument.hash).toString();
const signedDocument: IDocument = {
hash: generatedDocument.hash,
meta: generatedDocument.meta,
signature: sign,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
};
const joinCoopData: IJoinCooperative = {
username: newUser.username,
statement: signedDocument,
};
let joincoop_result = await request(app)
.post('/v1/users/join-cooperative')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(joinCoopData);
// console.log(joincoop_result.body);
expect(joincoop_result.status).toBe(httpStatus.OK);
const dbUser2 = await User.findOne({ username: newUser.username });
expect(dbUser2).toBeDefined();
expect(dbUser2?.status).toBe('joined');
expect(dbUser2?.statement).toBeDefined();
// console.log(dbUser2);
const initialPayment = await request(app)
.post('/v1/payments/initial')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send({ provider: 'yookassa' });
expect(initialPayment.status).toBe(httpStatus.CREATED);
expect(initialPayment.body?.details?.token).toBeDefined();
expect(initialPayment.body?.order_id).toBeDefined();
expect(initialPayment.body?.provider).toBe('yookassa');
});
});
describe('POST /v1/payments/methods/:username + add', () => {
test('401 при добавлении реквизитов самому себе без авторизации', async () => {
await insertUsers([userOne]);
sbpPaymentData.username = userOne.username;
const documents = await request(app).post(`/v1/payments/methods/${userOne.username}/add`).send(sbpPaymentData);
expect(documents.status).toBe(httpStatus.UNAUTHORIZED);
});
test('403 при добавлении реквизитов кому-то со своей авторизацией', async () => {
await insertUsers([userOne, userTwo]);
sbpPaymentData.username = userOne.username;
const documents = await request(app)
.post(`/v1/payments/methods/${userTwo.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData);
expect(documents.status).toBe(httpStatus.FORBIDDEN);
});
test('200 при добавлении реквизитов СБП самому себе и получение их', async () => {
await insertUsers([userOne]);
sbpPaymentData.username = userOne.username;
const documents = await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData);
expect(documents.status).toBe(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
});
test('200 при добавлении реквизитов СБП админом кому-то и получение их', async () => {
await insertUsers([userOne, admin]);
sbpPaymentData.username = userOne.username;
const documents = await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(sbpPaymentData);
expect(documents.status).toBe(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
});
test('200 при извлечении списка всех реквизитов админом', async () => {
await insertUsers([voskhod, userOne, admin]);
sbpPaymentData.username = userOne.username;
const documents = await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData);
expect(documents.status).toBe(httpStatus.OK);
const list = await request(app).get(`/v1/payments/methods`).set('Authorization', `Bearer ${adminAccessToken}`).send();
expect(list.status).toBe(httpStatus.OK);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 1,
totalResults: 2,
});
});
test('200 при добавлении СБП и банковских реквизитов самому себе и получение их', async () => {
await insertUsers([userOne]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
bankPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(bankPaymentData)
.expect(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 1,
totalResults: 2,
});
});
test('403 при получении чужого списка платежных методов не админом', async () => {
await insertUsers([userOne, userTwo]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
bankPaymentData.username = userTwo.username;
let res = await request(app)
.post(`/v1/payments/methods/${userTwo.username}/add`)
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(bankPaymentData);
expect(res.status).toBe(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userTwo.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.FORBIDDEN);
});
test('200 при получение двух методов двух пользователей админом', async () => {
await insertUsers([userOne, userTwo, admin]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
bankPaymentData.username = userTwo.username;
let res = await request(app)
.post(`/v1/payments/methods/${userTwo.username}/add`)
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(bankPaymentData);
expect(res.status).toBe(httpStatus.OK);
const list = await request(app).get(`/v1/payments/methods/`).set('Authorization', `Bearer ${adminAccessToken}`).send();
expect(list.status).toBe(httpStatus.OK);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 1,
totalResults: 2,
});
});
test('200 при обновлении собственных реквизитов после добавления пользователем', async () => {
await insertUsers([userOne, userTwo, admin]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
const sbpPaymentData2 = sbpPaymentData;
sbpPaymentData2.data = { phone: '111111111' };
let res = await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData2);
expect(res.status).toBe(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
// console.log(list.body.results[0]);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 1,
totalResults: 1,
});
expect(list.body.results[0].data.phone).toBe(sbpPaymentData2.data.phone);
});
});
describe('POST /v1/payments/methods/:username/delete', () => {
test('200 при удалении собственных реквизитов после добавления', async () => {
await insertUsers([userOne, userTwo, admin]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
const dataForDelete: IDeletePaymentMethod = {
method_id: sbpPaymentData.method_id,
};
await request(app)
.post(`/v1/payments/methods/${userOne.username}/delete`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(dataForDelete)
.expect(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
// console.log('list.body: ', list.body);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 0,
totalResults: 0,
});
});
test('403 при удалении чужих реквизитов', async () => {
await insertUsers([userOne, userTwo, admin]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
const dataForDelete: IDeletePaymentMethod = {
method_id: sbpPaymentData.method_id,
};
await request(app)
.post(`/v1/payments/methods/${userOne.username}/delete`)
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(dataForDelete)
.expect(httpStatus.FORBIDDEN);
});
test('200 при удалении нескольких версий собственных реквизитов под одним method_id', async () => {
await insertUsers([userOne, userTwo, admin]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
const dataForDelete: IDeletePaymentMethod = {
method_id: sbpPaymentData.method_id,
};
await request(app)
.post(`/v1/payments/methods/${userOne.username}/delete`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(dataForDelete)
.expect(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
// console.log('list.body: ', list.body);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 0,
totalResults: 0,
});
});
test('200 при удалении чужих реквизитов админом', async () => {
await insertUsers([userOne, userTwo, admin]);
sbpPaymentData.username = userOne.username;
await request(app)
.post(`/v1/payments/methods/${userOne.username}/add`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(sbpPaymentData)
.expect(httpStatus.OK);
const dataForDelete: IDeletePaymentMethod = {
method_id: sbpPaymentData.method_id,
};
await request(app)
.post(`/v1/payments/methods/${userOne.username}/delete`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(dataForDelete)
.expect(httpStatus.OK);
const list = await request(app)
.get(`/v1/payments/methods/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send();
expect(list.status).toBe(httpStatus.OK);
// console.log('list.body: ', list.body);
expect(list.body).toEqual({
results: expect.any(Array),
page: 1,
limit: 100,
totalPages: 0,
totalResults: 0,
});
});
});
});
@@ -53,17 +53,6 @@ describe('User routes', () => {
expect(res.body.user).not.toHaveProperty('password');
// expect(res.body.user).toEqual({
// email: newUser.email,
// role: newUser.role,
// is_email_verified: false,
// username: newUser.username,
// public_key: newUser.public_key,
// referer: newUser.referer,
// is_registered: false,
// type: 'individual',
// });
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
@@ -73,16 +62,12 @@ describe('User routes', () => {
expect(privateData.first_name).toEqual(newUser.individual_data.first_name);
expect(privateData.last_name).toEqual(newUser.individual_data.last_name);
// const validated = IndividualDataSchema.validate(privateData)
// console.log(validated)
// expect(validated).toBeTruthy()
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 () => {
await insertUsers([admin]);
newUser.role = 'admin';
newUser.role = 'member';
const res = await request(app)
.post('/v1/users')
@@ -93,10 +78,10 @@ describe('User routes', () => {
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user.role).toBe('admin');
expect(res.body.user.role).toBe('member');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser?.role).toBe('admin');
expect(dbUser?.role).toBe('member');
});
test('успешная регистрация без токена доступа', async () => {
@@ -185,7 +170,7 @@ describe('User routes', () => {
const res = await request(app).get('/v1/users').set('Authorization', `Bearer ${adminAccessToken}`).send();
console.log(res.body);
// console.log(res.body);
expect(res.status).toBe(httpStatus.OK);
expect(res.body).toEqual({
@@ -198,23 +183,25 @@ describe('User routes', () => {
expect(res.body.results).toHaveLength(3);
expect(res.body.results[0]).toEqual({
username: userOne.username,
id: userOne._id.toString(),
email: userOne.email,
role: userOne.role,
is_email_verified: userOne.is_email_verified,
has_account: false,
is_registered: false,
private_data: {},
is_registered: userOne.is_registered,
private_data: {
...userOne.individual_data,
_id: expect.any(String),
block_num: 0,
_created_at: expect.any(String),
},
public_key: userOne.public_key,
referer: '',
statement: {
hash: '',
public_key: '',
signature: '',
},
status: 'created',
statement: userOne.statement,
status: userOne.status,
message: userOne.message,
type: userOne.type,
username: userOne.username,
});
});
@@ -232,7 +219,7 @@ describe('User routes', () => {
expect(res.status).toBe(httpStatus.FORBIDDEN);
});
test('should correctly apply filter on name field', async () => {
test('should correctly apply filter on username field', async () => {
await insertUsers([userOne, userTwo, admin]);
const res = await request(app)
@@ -242,7 +229,7 @@ describe('User routes', () => {
.send()
.expect(httpStatus.OK);
console.log('res.body', res.body);
// console.log('res.body', res.body);
expect(res.body).toEqual({
results: expect.any(Array),
@@ -262,8 +249,9 @@ describe('User routes', () => {
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.query({ role: 'user' })
.send()
.expect(httpStatus.OK);
.send();
expect(res.status).toBe(httpStatus.OK);
expect(res.body).toEqual({
results: expect.any(Array),
@@ -272,6 +260,7 @@ describe('User routes', () => {
totalPages: 1,
totalResults: 2,
});
expect(res.body.results).toHaveLength(2);
expect(res.body.results[0].id).toBe(userOne._id.toString());
expect(res.body.results[1].id).toBe(userTwo._id.toString());
@@ -329,7 +318,7 @@ describe('User routes', () => {
const res = await request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.query({ sortBy: 'role:desc,name:asc' })
.query({ sortBy: 'role:desc,username:asc' })
.send()
.expect(httpStatus.OK);
@@ -520,7 +509,7 @@ describe('User routes', () => {
// });
describe('PATCH /v1/users/:userId', () => {
test('should return 200 and successfully update user if data is ok', async () => {
test('should return 403 and fail update user if not admin', async () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
@@ -530,18 +519,27 @@ describe('User routes', () => {
const res = await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.send(updateBody)
.expect(httpStatus.OK);
.send(updateBody);
expect(res.status).toBe(httpStatus.FORBIDDEN);
});
test('should return 200 and successfully update user if data is ok and is admin', async () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody);
expect(res.status).toBe(httpStatus.OK);
expect(res.body).not.toHaveProperty('password');
// expect(res.body).toEqual({
// id: userOne._id.toString(),
// email: updateBody.email,
// role: 'user',
// is_email_verified: false,
// });
const dbUser = await User.findById(userOne._id);
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(updateBody.password);
@@ -550,14 +548,20 @@ describe('User routes', () => {
test('should return 401 error if access token is missing', async () => {
await insertUsers([userOne]);
const updateBody = { name: faker.name.findName() };
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app).patch(`/v1/users/${userOne.username}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
});
test('should return 403 if user is updating another user', async () => {
await insertUsers([userOne, userTwo]);
const updateBody = { username: faker.name.findName() };
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
.patch(`/v1/users/${userTwo.username}`)
@@ -568,7 +572,10 @@ describe('User routes', () => {
test('should return 200 and successfully update user if admin is updating another user', async () => {
await insertUsers([userOne, admin]);
const updateBody = { name: faker.name.findName() };
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -579,7 +586,10 @@ describe('User routes', () => {
test('should return 404 if admin is updating another user that is not found', async () => {
await insertUsers([admin]);
const updateBody = { name: faker.name.findName() };
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -589,45 +599,45 @@ describe('User routes', () => {
});
test('should return 400 if email is invalid', async () => {
await insertUsers([userOne]);
const updateBody = { email: 'invalidEmail' };
await insertUsers([userOne, admin]);
const updateBody = { email: 'invalidEmail', password: 'GoodPassword!23' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if email is already taken', async () => {
await insertUsers([userOne, userTwo]);
const updateBody = { email: userTwo.email };
await insertUsers([userOne, userTwo, admin]);
const updateBody = { email: userTwo.email, password: 'GoodPassword!23' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password length is less than 8 characters', async () => {
await insertUsers([userOne]);
await insertUsers([userOne, admin]);
const updateBody = { password: 'passwo1' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.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]);
await insertUsers([userOne, admin]);
const updateBody = { password: 'password' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
@@ -635,43 +645,32 @@ describe('User routes', () => {
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${userOneAccessToken}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
//
// test('should not return 400 if email is my email', async () => {
// await insertUsers([userOne]);
// const updateBody = { email: userOne.email };
test('should return 400 if email is my email then taken', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: userOne.email, password: 'veryStrongPassword!2' };
// await request(app)
// .patch(`/v1/users/${userOne.username}`)
// .set('Authorization', `Bearer ${userOneAccessToken}`)
// .send(updateBody)
// .expect(httpStatus.OK);
// });
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
// test('should return 400 error if userId is not a valid mongo id', async () => {
// await insertUsers([admin]);
test('should return 404 error if username is not found', async () => {
await insertUsers([admin]);
// await request(app)
// .get('/v1/users/invalidId')
// .set('Authorization', `Bearer ${adminAccessToken}`)
// .send()
// .expect(httpStatus.BAD_REQUEST);
// });
// test('should return 400 error if userId is not a valid mongo id', async () => {
// await insertUsers([admin]);
// const updateBody = { name: faker.name.findName() };
// await request(app)
// .patch(`/v1/users/invalidId`)
// .set('Authorization', `Bearer ${adminAccessToken}`)
// .send(updateBody)
// .expect(httpStatus.BAD_REQUEST);
// });
await request(app)
.get('/v1/users/invalidId')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send()
.expect(httpStatus.NOT_FOUND);
});
});
});
@@ -102,7 +102,7 @@ describe('Error middlewares', () => {
describe('Error handler', () => {
beforeEach(() => {
jest.spyOn(logger, 'error').mockImplementation(() => {});
jest.spyOn(logger, 'error').mockImplementation();
});
test('should send proper error response and put the error message in res.locals', () => {
@@ -1,8 +1,17 @@
import mongoose from 'mongoose';
import setupTestDB from '../../../utils/setupTestDB';
import paginate from '../../../../src/models/plugins/paginate.plugin';
import mongoose, { PaginateModel } from 'mongoose';
import { setupTestDB } from '../../../utils/setupTestDB';
import { paginate } from '../../../../src/models/plugins';
const projectSchema = new mongoose.Schema({
interface IProject extends mongoose.Document {
name: string;
}
interface ITask extends mongoose.Document {
name: string;
project: mongoose.Schema.Types.ObjectId;
}
const projectSchema = new mongoose.Schema<IProject>({
name: {
type: String,
required: true,
@@ -16,22 +25,24 @@ projectSchema.virtual('tasks', {
});
projectSchema.plugin(paginate);
const Project = mongoose.model('Project', projectSchema);
const taskSchema = new mongoose.Schema({
const Project = mongoose.model<IProject, PaginateModel<IProject>>('Project', projectSchema);
const taskSchema = new mongoose.Schema<ITask>({
name: {
type: String,
required: true,
},
project: {
type: mongoose.SchemaTypes.ObjectId,
type: mongoose.Schema.Types.ObjectId,
ref: 'Project',
required: true,
},
});
taskSchema.plugin(paginate);
const Task = mongoose.model('Task', taskSchema);
const Task = mongoose.model<ITask, PaginateModel<ITask>>('Task', taskSchema);
setupTestDB();
@@ -51,7 +62,7 @@ describe('paginate plugin', () => {
const task = await Task.create({ name: 'Task One', project: project._id });
const projectPages = await Project.paginate({ _id: project._id }, { populate: 'tasks.project' });
const { tasks } = projectPages.results[0];
const { tasks } = projectPages.results[0] as any;
expect(tasks).toHaveLength(1);
expect(tasks[0]).toHaveProperty('_id', task._id);
@@ -1,3 +1,5 @@
import { generateUsername } from '../../utils/generateUsername';
const faker = require('faker');
const { User } = require('../../../src/models');
@@ -10,6 +12,9 @@ describe('User model', () => {
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
type: 'individual',
username: generateUsername(),
};
});
+13 -4
View File
@@ -1,20 +1,29 @@
import mongoose from 'mongoose';
import config from '../../src/config/config';
import { connectGenerator, generator } from '../../src/services/data.service';
import { connectGenerator, generator } from '../../src/services/document.service';
export const setupTestDB = () => {
beforeAll(async () => {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
await connectGenerator()
await connectGenerator();
});
beforeEach(async () => {
const db = mongoose.connection.db;
const collections = ['IndividualData', 'OrgData', 'EntrepreneurData', 'PaymentData'];
await Promise.all(
collections.map(async (name) => {
const collection = db.collection(name);
return collection.deleteMany({});
})
);
await Promise.all(Object.values(mongoose.connection.collections).map(async (collection) => collection.deleteMany({})));
});
afterAll(async () => {
await mongoose.disconnect();
await generator.disconnect();
await mongoose.disconnect();
});
};
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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.0.71](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.71-alpha.0...coopdoc-generator-ts@1.0.71) (2024-07-15)
**Note:** Version bump only for package coopdoc-generator-ts
+2 -10
View File
@@ -1,7 +1,7 @@
{
"name": "coopdoc-generator-ts",
"type": "module",
"version": "1.0.71",
"version": "1.0.72",
"private": true,
"packageManager": "pnpm@9.0.6",
"description": "",
@@ -42,8 +42,7 @@
"prepublishOnly": "nr build",
"release": "bumpp && npm publish",
"test": "vitest --dir test --testTimeout=120000 --exclude documents --watch=false",
"typecheck": "tsc --noEmit",
"prepare": "simple-git-hooks"
"typecheck": "tsc --noEmit"
},
"dependencies": {
"ajv": "^8.13.0",
@@ -74,7 +73,6 @@
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"esno": "^4.7.0",
"lint-staged": "^15.2.2",
"pnpm": "^8.15.8",
"rimraf": "^5.0.7",
"simple-git-hooks": "^2.11.1",
@@ -92,11 +90,5 @@
"tar@<6.2.1": ">=6.2.1",
"vite@>=5.1.0 <=5.1.6": ">=5.1.7"
}
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
}
}
@@ -1,9 +1,8 @@
import type { JSONSchemaType } from 'ajv'
import moment from 'moment-timezone'
import { DraftContract, SovietContract } from 'cooptypes'
import type { ICombinedData, IGeneratedDocument, IMetaDocument, IMetaDocumentPartial, ITemplate, ITranslations } from '../Interfaces'
import type { ICombinedData, IGeneratedDocument, IMetaDocument, IMetaDocumentPartial, ITemplate, ITranslations, externalDataTypes } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
import type { IndividualData, OrganizationData } from '../Models'
import { Individual, Organization } from '../Models'
import type { IDecisionData, IGenerate } from '../Interfaces/Documents'
import { PDFService } from '../Services/Generator'
@@ -11,7 +10,6 @@ import packageJson from '../../package.json'
import { Validator } from '../Services/Validator'
import type { CooperativeData } from '../Models/Cooperative'
import { Cooperative } from '../Models/Cooperative'
import type { EntrepreneurData } from '../Models/Entrepreneur'
import { Entrepreneur } from '../Models/Entrepreneur'
import { getFetch } from '../Utils/getFetch'
import { getEnvVar } from '../config'
@@ -32,16 +30,16 @@ export abstract class DocFactory {
return new Validator(schema, combinedData).validate()
}
async getUser(username: string, block_num?: number): Promise<{ type: string, data: IndividualData | OrganizationData | EntrepreneurData }> {
async getUser(username: string, block_num?: number): Promise<{ type: string, data: externalDataTypes }> {
const block_filter = block_num ? { block_num: { $lte: block_num } } : {}
const individual = await new Individual(this.storage).getOne({ username, ...block_filter })
const organization = await new Organization(this.storage).getOne({ username, ...block_filter })
const entrepreneur = await new Entrepreneur(this.storage).getOne({ username, ...block_filter })
const userData: { type: string, data: IndividualData | OrganizationData | EntrepreneurData } = {
const userData: { type: string, data: externalDataTypes } = {
type: '',
data: {} as IndividualData | OrganizationData | EntrepreneurData,
data: {} as externalDataTypes,
}
if (individual) {
@@ -80,8 +78,6 @@ export abstract class DocFactory {
}
async getDecision(coop: CooperativeData, coopname: string, decision_id: number, created_at: string): Promise<IDecisionData> {
// TODO запретить снимать голоса
const votes_for_actions = (await getFetch(`${getEnvVar('SIMPLE_EXPLORER_API')}/get-actions`, new URLSearchParams({
filter: JSON.stringify({
'account': process.env.SOVIET_CONTRACT,
@@ -174,7 +170,7 @@ export abstract class DocFactory {
}
async generatePDF(
data: IndividualData | OrganizationData | EntrepreneurData,
data: externalDataTypes,
context: string,
vars: ICombinedData,
translation: ITranslations,
@@ -189,7 +185,7 @@ export abstract class DocFactory {
return document
}
getFullName(data: IndividualData | OrganizationData | EntrepreneurData): string {
getFullName(data: externalDataTypes): string {
if ('first_name' in data)
return `${data.last_name} ${data.first_name} ${data.middle_name}`
@@ -201,10 +197,6 @@ export abstract class DocFactory {
async saveDraft(document: IGeneratedDocument): Promise<void> {
await this.storage.saveDraft(document)
// TODO вынести сохранение на диск в отдельный сервис
// const filename = `!${document.meta.title}-${document.meta.username}-${document.meta.created_at}.pdf`
// await saveBufferToDisk(document.binary, filename)
}
async getMeta<T extends IMetaDocumentPartial>({
@@ -1,13 +1,7 @@
import type { Cooperative } from 'cooptypes'
export type Currency = 'RUB' | 'Other'
export interface IBankAccount {
currency: Currency
card_number?: string // < номер карты
bank_name: string // < наименование банка
account_number: string // < номер счёта
details: {
bik: string // < бик
corr: string // < корр счёт
kpp: string // < кпп
}
}
export type IBankAccount = Cooperative.Payments.IBankAccount
export type IPaymentData = Cooperative.Payments.IPaymentData
@@ -1,9 +1,8 @@
import type { JSONSchemaType } from 'ajv'
import type { Cooperative } from 'cooptypes'
import type { IndividualData, OrganizationData } from '../Models'
import type { DocumentsMappingByActionAndCode } from '../Templates/registry'
import type { EntrepreneurData } from '../Models/Entrepreneur'
import type { CooperativeData } from '../Models/Cooperative'
import type { ExternalEntrepreneurData, ExternalIndividualData, ExternalOrganizationData } from '../Models'
// Определение базового интерфейса для мета-информации
export type IMetaDocument = Cooperative.Documents.IMetaDocument
@@ -41,9 +40,9 @@ export interface ITemplate<T> {
}
export interface ICombinedData {
individual?: IndividualData
organization?: OrganizationData
entrepreneur?: EntrepreneurData
individual?: ExternalIndividualData
organization?: ExternalOrganizationData
entrepreneur?: ExternalEntrepreneurData
coop: CooperativeData
meta: IMetaDocument
}
@@ -1,5 +1,8 @@
import type { Document } from 'mongodb'
import type { ExternalEntrepreneurData, ExternalIndividualData, ExternalOrganizationData, InternalEntrepreneurData, InternalIndividualData, InternalOrganizationData } from '../Models'
import type { PaymentData } from '../Models/PaymentMethod'
import type { IGeneratedDocument } from './Documents'
import type { IPaymentData } from './BankAccounts'
export interface IFilterDeltas {
present: boolean
@@ -30,3 +33,7 @@ export interface IBCState {
export interface IFilterDocuments extends Document, IGeneratedDocument {
}
export type internalFilterTypes = InternalIndividualData | InternalEntrepreneurData | InternalOrganizationData | IPaymentData
export type externalDataTypes = ExternalIndividualData | ExternalEntrepreneurData | ExternalOrganizationData | PaymentData
export type externalDataTypesArrays = ExternalIndividualData[] | ExternalEntrepreneurData[] | ExternalOrganizationData[] | PaymentData[]
@@ -7,8 +7,10 @@ import type { MongoDBConnector } from '../Services/Databazor'
import { individualSchema } from '../Schema/IndividualSchema'
import { getFetch } from '../Utils/getFetch'
import { getEnvVar } from '../config'
import { Organization, organizationSchema } from './Organization'
import type { IndividualData } from './Individual'
import { organizationSchema } from '../Schema'
import { Organization } from './Organization'
import type { ExternalIndividualData } from './Individual'
import { Individual } from './Individual'
export type CooperativeData = CooperativeApi.Model.ICooperativeData
@@ -21,6 +23,9 @@ export const CooperativeSchema: JSONSchemaType<CooperativeData> = {
registration: { type: 'string' },
initial: { type: 'string' },
minimum: { type: 'string' },
org_registration: { type: 'string' },
org_initial: { type: 'string' },
org_minimum: { type: 'string' },
totalMembers: { type: 'number' },
members: {
type: 'array',
@@ -28,26 +33,26 @@ export const CooperativeSchema: JSONSchemaType<CooperativeData> = {
},
...organizationSchema.properties,
},
required: [...organizationSchema.required, 'is_branched', 'registration', 'initial', 'minimum', 'members', 'totalMembers'],
required: [...organizationSchema.required, 'is_branched', 'registration', 'initial', 'minimum', 'org_registration', 'org_initial', 'org_minimum', 'members', 'totalMembers'],
additionalProperties: true,
}
export class Cooperative {
cooperative: CooperativeData | null
storage: MongoDBConnector
db: MongoDBConnector
private data_service: DataService<CooperativeData>
constructor(storage: MongoDBConnector) {
this.storage = storage
this.db = storage
this.cooperative = null
this.data_service = new DataService<CooperativeData>(storage, 'IndividualData')
this.data_service = new DataService<CooperativeData>(storage, 'CooperativeData')
}
async getOne(username: string, block_num?: number): Promise<CooperativeData> {
const block_filter = block_num ? { block_num: { $lte: block_num } } : {}
const organizationPrivateData = await new Organization(this.storage).getOne({ username, ...block_filter })
const organizationPrivateData = await new Organization(this.db).getOne({ username, ...block_filter })
if (!organizationPrivateData)
throw new Error('Информация о организации не обнаружена в базе данных.')
@@ -77,15 +82,15 @@ export class Cooperative {
const soviet = soviet_response.results[0]?.value as SovietContract.Tables.Boards.IBoards
if (!soviet)
throw new Error('Совет кооператива не обранужен в базе данных.')
throw new Error('Совет кооператива не обнаружен в базе данных.')
const userModel = new Individual(this.storage)
const userModel = new Individual(this.db)
const members = [] as MembersData[]
let chairman = {} as IndividualData
let chairman = {} as ExternalIndividualData
for (const member of soviet.members) {
const userData = await userModel.getOne({ username: member.username, ...block_filter }) as IndividualData
const userData = await userModel.getOne({ username: member.username, ...block_filter }) as ExternalIndividualData
if (!userData)
throw new Error(`Пользователь ${member.username} не найден в базе данных.`)
@@ -1,40 +1,111 @@
import type { Filter, InsertOneResult } from 'mongodb'
import type { Filter, InsertOneResult, UpdateResult } from 'mongodb'
import type { Cooperative } from 'cooptypes'
import type { ValidateResult } from '../Services/Validator'
import { Validator } from '../Services/Validator'
import type { MongoDBConnector } from '../Services/Databazor'
import DataService from '../Services/Databazor/DataService'
import { entrepreneurSchema } from '../Schema/EntrepreneurSchema'
import type { IBankAccount } from '../Interfaces'
import { getCurrentBlock } from '../Utils/getCurrentBlock'
import type { PaymentData } from './PaymentMethod'
import { PaymentMethod } from './PaymentMethod'
export type EntrepreneurData = Cooperative.Users.IEntrepreneurData
export type ExternalEntrepreneurData = Cooperative.Users.IEntrepreneurData
export type InternalEntrepreneurData = Omit<ExternalEntrepreneurData, 'bank_account'> & {
bank_account?: Cooperative.Payments.IBankAccount
}
export class Entrepreneur {
entrepreneur?: EntrepreneurData
private data_service: DataService<EntrepreneurData>
db: MongoDBConnector
entrepreneur?: ExternalEntrepreneurData
private data_service: DataService<InternalEntrepreneurData>
constructor(storage: MongoDBConnector, data?: EntrepreneurData) {
constructor(storage: MongoDBConnector, data?: ExternalEntrepreneurData) {
this.db = storage
this.entrepreneur = data
this.data_service = new DataService<EntrepreneurData>(storage, 'EntrepreneurData')
this.data_service = new DataService<InternalEntrepreneurData>(storage, 'EntrepreneurData')
}
validate(): ValidateResult {
return new Validator(entrepreneurSchema, this.entrepreneur as EntrepreneurData).validate()
return new Validator(entrepreneurSchema, this.entrepreneur).validate()
}
async save(): Promise<InsertOneResult<EntrepreneurData>> {
async save(): Promise<InsertOneResult<InternalEntrepreneurData>> {
await this.validate()
return await this.data_service.save(this.entrepreneur as EntrepreneurData)
if (!this.entrepreneur)
throw new Error('Данные ИП не предоставлены для сохранения')
const { bank_account, ...entr_for_save } = this.entrepreneur
const currentBlock = await getCurrentBlock()
const bankData: PaymentData = {
username: this.entrepreneur.username,
method_id: 1,
user_type: 'entrepreneur',
method_type: 'bank_transfer',
is_default: true,
data: bank_account as Cooperative.Payments.IBankAccount,
deleted: false,
block_num: currentBlock,
}
const paymentMethod = await new PaymentMethod(this.db, bankData)
await paymentMethod.validate()
await paymentMethod.save()
const entrForSave: InternalEntrepreneurData = {
...entr_for_save,
deleted: false,
block_num: currentBlock,
}
return await this.data_service.save(entrForSave)
}
async getOne(filter: Filter<EntrepreneurData>): Promise<EntrepreneurData | null> {
return this.data_service.getOne(filter)
async getOne(filter: Filter<InternalEntrepreneurData>): Promise<ExternalEntrepreneurData | null> {
const entr = await this.data_service.getOne(filter)
const blockFilter = filter.block_num ? { block_num: filter.block_num } : {}
if (entr)
entr.bank_account = (await (new PaymentMethod(this.db).getOne({ ...blockFilter, username: entr.username, method_type: 'bank_transfer', is_default: true })))?.data as IBankAccount
return entr as ExternalEntrepreneurData
}
async getMany(filter: Filter<EntrepreneurData>): Promise<EntrepreneurData[]> {
return this.data_service.getMany(filter)
async getMany(filter: Filter<InternalEntrepreneurData>): Promise<Cooperative.Documents.IGetResponse<ExternalEntrepreneurData>> {
const response = await this.data_service.getMany({ deleted: false, ...filter }, 'username')
const entrepreneurs = response.results
const blockFilter = filter.block_num ? { block_num: filter.block_num } : {}
for (const entr of entrepreneurs) {
entr.bank_account = (await (new PaymentMethod(this.db).getOne({ ...blockFilter, username: entr.username, method_type: 'bank_transfer', is_default: true })))?.data as IBankAccount
}
const result: Cooperative.Documents.IGetResponse<ExternalEntrepreneurData> = {
results: entrepreneurs as ExternalEntrepreneurData[],
page: response.page,
limit: response.limit,
totalResults: response.totalResults,
totalPages: response.totalPages,
}
return result
}
async getHistory(filter: Filter<EntrepreneurData>): Promise<EntrepreneurData[]> {
return this.data_service.getHistory(filter)
async getHistory(filter: Filter<InternalEntrepreneurData>): Promise<ExternalEntrepreneurData[]> {
const history = await this.data_service.getHistory(filter)
const blockFilter = filter.block_num ? { block_num: filter.block_num } : {}
for (const entr of history) {
entr.bank_account = (await (new PaymentMethod(this.db).getOne({ ...blockFilter, username: entr.username, method_type: 'bank_transfer', is_default: true })))?.data as IBankAccount
}
return history as ExternalEntrepreneurData[]
}
async del(filter: Filter<InternalEntrepreneurData>): Promise<UpdateResult> {
return this.data_service.updateMany({ ...filter, deleted: false }, { deleted: true })
}
}
@@ -1,40 +1,58 @@
import type { Filter, InsertOneResult } from 'mongodb'
import type { Filter, InsertOneResult, UpdateResult } from 'mongodb'
import type { Cooperative } from 'cooptypes'
import type { ValidateResult } from '../Services/Validator'
import { Validator } from '../Services/Validator'
import type { MongoDBConnector } from '../Services/Databazor'
import DataService from '../Services/Databazor/DataService'
import { individualSchema } from '../Schema/IndividualSchema'
import { getCurrentBlock } from '../Utils/getCurrentBlock'
export type IndividualData = Cooperative.Users.IIndividualData
export type ExternalIndividualData = Cooperative.Users.IIndividualData
export type InternalIndividualData = ExternalIndividualData
export class Individual {
individual?: IndividualData
private data_service: DataService<IndividualData>
individual?: ExternalIndividualData
private data_service: DataService<InternalIndividualData>
constructor(storage: MongoDBConnector, data?: IndividualData) {
constructor(storage: MongoDBConnector, data?: ExternalIndividualData) {
this.individual = data
this.data_service = new DataService<IndividualData>(storage, 'IndividualData')
this.data_service = new DataService<InternalIndividualData>(storage, 'IndividualData')
}
validate(): ValidateResult {
return new Validator(individualSchema, this.individual as IndividualData).validate()
return new Validator(individualSchema, this.individual).validate()
}
async save(): Promise<InsertOneResult<IndividualData>> {
async save(): Promise<InsertOneResult<InternalIndividualData>> {
await this.validate()
return await this.data_service.save(this.individual as IndividualData)
if (!this.individual)
throw new Error('Данные физлица не предоставлены для сохранения')
const currentBlock = await getCurrentBlock()
const individual: InternalIndividualData = {
...this.individual,
deleted: false,
block_num: currentBlock,
}
return await this.data_service.save(individual)
}
async getOne(filter: Filter<IndividualData>): Promise<IndividualData | null> {
async getOne(filter: Filter<InternalIndividualData>): Promise<ExternalIndividualData | null> {
return this.data_service.getOne(filter)
}
async getMany(filter: Filter<IndividualData>): Promise<IndividualData[]> {
return this.data_service.getMany(filter)
async getMany(filter: Filter<InternalIndividualData>): Promise<Cooperative.Documents.IGetResponse<ExternalIndividualData>> {
return this.data_service.getMany(filter, 'username')
}
async getHistory(filter: Filter<IndividualData>): Promise<IndividualData[]> {
async getHistory(filter: Filter<InternalIndividualData>): Promise<ExternalIndividualData[]> {
return this.data_service.getHistory(filter)
}
async del(filter: Filter<InternalIndividualData>): Promise<UpdateResult> {
return this.data_service.updateMany(filter, { deleted: true })
}
}
@@ -1,87 +1,111 @@
import type { JSONSchemaType } from 'ajv'
import type { Filter, InsertOneResult } from 'mongodb'
import type { Filter, InsertOneResult, UpdateResult } from 'mongodb'
import type { Cooperative } from 'cooptypes'
import type { ValidateResult } from '../Services/Validator'
import { Validator } from '../Services/Validator'
import DataService from '../Services/Databazor/DataService'
import type { MongoDBConnector } from '../Services/Databazor'
import { BankAccountSchema } from '../Schema/BankAccount'
import { organizationSchema } from '../Schema'
import type { IBankAccount } from '../Interfaces'
import { getCurrentBlock } from '../Utils/getCurrentBlock'
import type { PaymentData } from './PaymentMethod'
import { PaymentMethod } from './PaymentMethod'
export type OrganizationData = Cooperative.Users.IOrganizationData
export type ExternalOrganizationData = Cooperative.Users.IOrganizationData
export const organizationSchema: JSONSchemaType<OrganizationData> = {
type: 'object',
properties: {
username: { type: 'string' },
type: { type: 'string', enum: ['coop', 'ooo', 'oao', 'zao', 'pao', 'ao'] },
is_cooperative: { type: 'boolean' },
short_name: { type: 'string' },
full_name: { type: 'string' },
represented_by: {
type: 'object',
required: ['first_name', 'last_name', 'position', 'based_on'],
properties: {
first_name: { type: 'string' },
last_name: { type: 'string' },
middle_name: { type: 'string' },
position: { type: 'string' },
based_on: { type: 'string' },
// signature: { type: 'string' },
},
additionalProperties: true,
},
country: { type: 'string', enum: ['Russia', 'Other'] },
city: { type: 'string' },
email: { type: 'string' },
phone: { type: 'string' },
full_address: { type: 'string' },
details: {
type: 'object',
required: ['inn', 'ogrn'],
properties: {
inn: { type: 'string' },
ogrn: { type: 'string' },
},
additionalProperties: true,
},
bank_account: {
type: 'object',
required: BankAccountSchema.required,
properties: BankAccountSchema.properties,
},
},
required: ['username', 'type', 'is_cooperative', 'short_name', 'full_name', 'represented_by', 'country', 'city', 'full_address', 'email', 'phone', 'details', 'bank_account'],
additionalProperties: true,
export type InternalOrganizationData = Omit<ExternalOrganizationData, 'bank_account'> & {
bank_account?: Cooperative.Payments.IBankAccount
}
export class Organization {
organization?: OrganizationData
private data_service: DataService<OrganizationData>
db: MongoDBConnector
organization?: ExternalOrganizationData
private data_service: DataService<InternalOrganizationData>
constructor(storage: MongoDBConnector, data?: OrganizationData) {
constructor(storage: MongoDBConnector, data?: ExternalOrganizationData) {
this.db = storage
this.organization = data
this.data_service = new DataService<OrganizationData>(storage, 'OrgData')
this.data_service = new DataService<InternalOrganizationData>(storage, 'OrgData')
}
validate(): ValidateResult {
return new Validator(organizationSchema, this.organization as OrganizationData).validate()
return new Validator(organizationSchema, this.organization as ExternalOrganizationData).validate()
}
async save(): Promise<InsertOneResult<OrganizationData>> {
async save(): Promise<InsertOneResult<InternalOrganizationData>> {
await this.validate()
return await this.data_service.save(this.organization as OrganizationData)
if (!this.organization)
throw new Error('Данные организации не предоставлены для сохранения')
const { bank_account, ...organization_for_save } = this.organization
const currentBlock = await getCurrentBlock()
const bankData: PaymentData = {
username: this.organization.username,
method_id: 1,
user_type: 'organization',
method_type: 'bank_transfer',
is_default: true,
data: bank_account as Cooperative.Payments.IBankAccount,
deleted: false,
block_num: currentBlock,
}
const paymentMethod = await new PaymentMethod(this.db, bankData)
await paymentMethod.validate()
await paymentMethod.save()
const orgForSave: InternalOrganizationData = {
...organization_for_save,
deleted: false,
block_num: currentBlock,
}
return await this.data_service.save(orgForSave)
}
async getOne(filter: Filter<OrganizationData>): Promise<OrganizationData | null> {
return this.data_service.getOne(filter)
async getOne(filter: Filter<InternalOrganizationData>): Promise<ExternalOrganizationData | null> {
const org = await this.data_service.getOne(filter)
const blockFilter = filter.block_num ? { block_num: filter.block_num } : {}
if (org) {
org.bank_account = (await (new PaymentMethod(this.db).getOne({ ...blockFilter, username: org.username, method_type: 'bank_transfer', is_default: true })))?.data as IBankAccount
}
return org as ExternalOrganizationData
}
async getMany(filter: Filter<OrganizationData>): Promise<OrganizationData[]> {
return this.data_service.getMany(filter)
async getMany(filter: Filter<InternalOrganizationData>): Promise<Cooperative.Documents.IGetResponse<ExternalOrganizationData>> {
const response = (await this.data_service.getMany({ deleted: false, ...filter }, 'username'))
const orgs = response.results
const blockFilter = filter.block_num ? { block_num: filter.block_num } : {}
for (const org of orgs) {
org.bank_account = (await (new PaymentMethod(this.db).getOne({ ...blockFilter, username: org.username, method_type: 'bank_transfer', is_default: true })))?.data as IBankAccount
}
const result: Cooperative.Documents.IGetResponse<ExternalOrganizationData> = {
results: orgs as ExternalOrganizationData[],
page: response.page,
limit: response.limit,
totalResults: response.totalResults,
totalPages: response.totalPages,
}
return result
}
async getHistory(filter: Filter<OrganizationData>): Promise<OrganizationData[]> {
return this.data_service.getHistory(filter)
async getHistory(filter: Filter<InternalOrganizationData>): Promise<ExternalOrganizationData[]> {
const orgs = await this.data_service.getHistory(filter)
const blockFilter = filter.block_num ? { block_num: filter.block_num } : {}
for (const org of orgs) {
org.bank_account = (await (new PaymentMethod(this.db).getOne({ ...blockFilter, username: org.username, method_type: 'bank_transfer', is_default: true })))?.data as IBankAccount
}
return orgs as ExternalOrganizationData[]
}
async del(filter: Filter<InternalOrganizationData>): Promise<UpdateResult> {
return this.data_service.updateMany({ ...filter }, { deleted: true })
}
}
@@ -0,0 +1,55 @@
import type { Filter, InsertOneResult, UpdateResult } from 'mongodb'
import type { Cooperative } from 'cooptypes'
import type { ValidateResult } from '../Services/Validator'
import { Validator } from '../Services/Validator'
import DataService from '../Services/Databazor/DataService'
import type { MongoDBConnector } from '../Services/Databazor'
import type { IPaymentData } from '../Interfaces'
import { paymentMethodSchema } from '../Schema'
import { getCurrentBlock } from '../Utils/getCurrentBlock'
export type PaymentData = IPaymentData
export class PaymentMethod {
db: MongoDBConnector
paymentMethod?: PaymentData
private data_service: DataService<PaymentData>
constructor(storage: MongoDBConnector, data?: PaymentData) {
this.db = storage
this.paymentMethod = data
this.data_service = new DataService(storage, 'PaymentData')
}
validate(): ValidateResult {
return new Validator(paymentMethodSchema, this.paymentMethod).validate()
}
async save(): Promise<InsertOneResult<PaymentData>> {
await this.validate()
if (!this.paymentMethod)
throw new Error('Данные платежного метода не предоставлены для сохранения')
this.paymentMethod.deleted = false
this.paymentMethod.block_num = await getCurrentBlock()
return await this.data_service.save(this.paymentMethod as PaymentData)
}
async getOne(filter: Filter<PaymentData>): Promise<PaymentData | null> {
return this.data_service.getOne(filter)
}
async getMany(filter: Filter<PaymentData>): Promise<Cooperative.Documents.IGetResponse<PaymentData>> {
return this.data_service.getMany({ deleted: false, ...filter }, ['username', 'method_id'])
}
async getHistory(filter: Filter<PaymentData>): Promise<PaymentData[]> {
return this.data_service.getHistory(filter)
}
async del(filter: Filter<PaymentData>): Promise<UpdateResult> {
return this.data_service.updateMany({ ...filter, deleted: false }, { deleted: true })
}
}
@@ -1,5 +1,4 @@
export * from './Individual'
export * from './Organization'
export * from '../Schema/BankAccount'
export * from './Entrepreneur'
export * from './Cooperative'
@@ -1,8 +1,8 @@
import type { JSONSchemaType } from 'ajv'
import type { EntrepreneurData } from '../Models/Entrepreneur'
import { BankAccountSchema } from './BankAccount'
import type { ExternalEntrepreneurData } from '../Models/Entrepreneur'
import { BankAccountSchema } from './BankAccountSchema'
export const entrepreneurSchema: JSONSchemaType<EntrepreneurData> = {
export const entrepreneurSchema: JSONSchemaType<ExternalEntrepreneurData> = {
type: 'object',
properties: {
username: { type: 'string' },
@@ -29,7 +29,8 @@ export const entrepreneurSchema: JSONSchemaType<EntrepreneurData> = {
required: BankAccountSchema.required,
properties: BankAccountSchema.properties,
},
// signature: { type: 'string' },
deleted: { type: 'boolean', nullable: true },
block_num: { type: 'number', nullable: true },
},
required: ['username', 'email', 'first_name', 'last_name', 'birthdate', 'phone', 'country', 'city', 'full_address', 'details', 'bank_account'],
additionalProperties: true,
@@ -1,7 +1,7 @@
import type { JSONSchemaType } from 'ajv'
import type { IndividualData } from '../Models/Individual'
import type { ExternalIndividualData } from '../Models/Individual'
export const individualSchema: JSONSchemaType<IndividualData> = {
export const individualSchema: JSONSchemaType<ExternalIndividualData> = {
type: 'object',
properties: {
username: { type: 'string' },
@@ -12,7 +12,8 @@ export const individualSchema: JSONSchemaType<IndividualData> = {
full_address: { type: 'string' },
phone: { type: 'string' },
email: { type: 'string', format: 'email' },
// signature: { type: 'string' },
deleted: { type: 'boolean', nullable: true },
block_num: { type: 'number', nullable: true },
},
required: ['username', 'first_name', 'last_name', 'birthdate', 'full_address', 'phone'],
additionalProperties: true,
@@ -0,0 +1,49 @@
import type { JSONSchemaType } from 'ajv'
import type { ExternalOrganizationData } from '../Models/Organization'
import { BankAccountSchema } from './BankAccountSchema'
export const organizationSchema: JSONSchemaType<ExternalOrganizationData> = {
type: 'object',
properties: {
username: { type: 'string' },
type: { type: 'string', enum: ['coop', 'ooo', 'oao', 'zao', 'pao', 'ao'] },
is_cooperative: { type: 'boolean' },
short_name: { type: 'string' },
full_name: { type: 'string' },
represented_by: {
type: 'object',
required: ['first_name', 'last_name', 'position', 'based_on'],
properties: {
first_name: { type: 'string' },
last_name: { type: 'string' },
middle_name: { type: 'string' },
position: { type: 'string' },
based_on: { type: 'string' },
},
additionalProperties: true,
},
country: { type: 'string', enum: ['Russia', 'Other'] },
city: { type: 'string' },
email: { type: 'string' },
phone: { type: 'string' },
full_address: { type: 'string' },
details: {
type: 'object',
required: ['inn', 'ogrn'],
properties: {
inn: { type: 'string' },
ogrn: { type: 'string' },
},
additionalProperties: true,
},
bank_account: {
type: 'object',
required: BankAccountSchema.required,
properties: BankAccountSchema.properties,
},
deleted: { type: 'boolean', nullable: true },
block_num: { type: 'number', nullable: true },
},
required: ['username', 'type', 'is_cooperative', 'short_name', 'full_name', 'represented_by', 'country', 'city', 'full_address', 'email', 'phone', 'details', 'bank_account'],
additionalProperties: true,
}
@@ -0,0 +1,36 @@
import type { JSONSchemaType } from 'ajv'
import type { IPaymentData } from '../Interfaces'
import { BankAccountSchema } from './BankAccountSchema'
export const paymentMethodSchema: JSONSchemaType<IPaymentData> = {
type: 'object',
properties: {
username: { type: 'string' },
method_id: { type: 'number' },
user_type: { type: 'string', enum: ['individual', 'entrepreneur', 'organization'] },
method_type: { type: 'string', enum: ['sbp', 'bank_transfer'] },
is_default: { type: 'boolean' },
data: {
type: 'object',
oneOf: [
{
type: 'object',
properties: {
phone: { type: 'string' },
},
required: ['phone'],
},
{
type: 'object',
properties: BankAccountSchema.properties,
required: BankAccountSchema.required,
additionalProperties: false,
},
],
},
deleted: { type: 'boolean', nullable: true },
block_num: { type: 'number', nullable: true },
},
required: ['username', 'method_id', 'user_type', 'method_type', 'is_default', 'data'],
additionalProperties: true,
}
@@ -0,0 +1,6 @@
export * from './BankAccountSchema'
export * from './DecisionSchema'
export * from './EntrepreneurSchema'
export * from './OrganizationSchema'
export * from './IndividualSchema'
export * from './PaymentMethodSchema'
@@ -1,5 +1,5 @@
import type { Collection, Filter } from 'mongodb'
import { getCurrentBlock } from '../../Utils/getCurrentBlock'
import type { Collection, Filter, UpdateResult } from 'mongodb'
import type { Cooperative } from 'cooptypes'
import type { MongoDBConnector } from './MongoDBConnector'
export interface IDocument {
@@ -16,25 +16,54 @@ class DataService<T extends IDocument> {
}
async getOne(filter: Filter<T>): Promise<T | null> {
const document = await this.collection.findOne(filter, { sort: { _id: -1 } })
const document = await this.collection.findOne({ ...filter }, { sort: { _id: -1 } })
return document as T | null
}
async getMany(filter: Filter<T>): Promise<T[]> {
async getMany(
filter: Filter<T>,
groupByFields: string | string[],
page: number = 1,
limit: number = 100,
): Promise<Cooperative.Documents.IGetResponse<T>> {
const groupFields = Array.isArray(groupByFields) ? groupByFields : [groupByFields]
const groupId = groupFields.reduce((acc, field) => ({ ...acc, [field]: `$${field}` }), {})
const aggregateOptions = [
{ $match: filter },
{ $sort: { _created_at: -1 } },
{
$group: {
_id: '$_id',
_id: groupId,
doc: { $first: '$$ROOT' },
},
},
{ $replaceRoot: { newRoot: '$doc' } },
]
const result = await this.collection.aggregate(aggregateOptions).toArray()
return result as T[]
const totalResult = await this.collection.aggregate([
...aggregateOptions,
{ $count: 'total' },
]).toArray()
const totalResults = totalResult[0]?.total || 0
const totalPages = Math.ceil(totalResults / limit)
const results = await this.collection.aggregate([
...aggregateOptions,
{ $skip: (page - 1) * limit },
{ $limit: limit },
]).toArray()
const result: Cooperative.Documents.IGetResponse<T> = {
results: results as T[],
page,
limit,
totalPages,
totalResults,
}
return result
}
async getHistory(filter: Filter<T>): Promise<T[]> {
@@ -43,11 +72,15 @@ class DataService<T extends IDocument> {
}
async save(data: T) {
const currentBlock = await getCurrentBlock()
const document: any = { _created_at: new Date(), block_num: currentBlock, ...data }
const document: any = { _created_at: new Date(), ...data }
return await this.collection.insertOne(document)
}
async updateMany(filter: Filter<T>, dataForUpdate: Partial<T>): Promise<UpdateResult> {
const deleted = await this.collection.updateMany(filter, { $set: dataForUpdate })
return deleted
}
}
export default DataService
@@ -3,18 +3,18 @@ import type { JSONSchemaType } from 'ajv'
import type { IMetaDocument, ITemplate } from '../Interfaces'
import { IMetaJSONSchema } from '../Services/Validator'
import { individualSchema } from '../Schema/IndividualSchema'
import type { IndividualData, OrganizationData } from '../Models'
import { organizationSchema } from '../Models'
import type { ExternalIndividualData, ExternalOrganizationData } from '../Models'
import { organizationSchema } from '../Schema'
import { type CooperativeData, CooperativeSchema } from '../Models/Cooperative'
import type { EntrepreneurData } from '../Models/Entrepreneur'
import type { ExternalEntrepreneurData } from '../Models/Entrepreneur'
import { entrepreneurSchema } from '../Schema/EntrepreneurSchema'
// Модель данных
export interface IJoinCoopAction {
type: string
individual?: IndividualData
organization?: OrganizationData
entrepreneur?: EntrepreneurData
individual?: ExternalIndividualData
organization?: ExternalOrganizationData
entrepreneur?: ExternalEntrepreneurData
coop: CooperativeData
meta: IMetaDocument
signature: string
@@ -3,19 +3,18 @@ import type { JSONSchemaType } from 'ajv'
import type { IDecisionData, IMetaDocument, ITemplate } from '../Interfaces'
import { IMetaJSONSchema } from '../Services/Validator'
import { individualSchema } from '../Schema/IndividualSchema'
import type { IndividualData, OrganizationData } from '../Models'
import { organizationSchema } from '../Models'
import { organizationSchema } from '../Schema'
import { type CooperativeData, CooperativeSchema } from '../Models/Cooperative'
import type { EntrepreneurData } from '../Models/Entrepreneur'
import { entrepreneurSchema } from '../Schema/EntrepreneurSchema'
import { decisionSchema } from '../Schema/DecisionSchema'
import type { ExternalEntrepreneurData, ExternalIndividualData, ExternalOrganizationData } from '../Models'
// Модель данных
export interface IJoinCoopDecisionAction {
type: string
individual?: IndividualData
organization?: OrganizationData
entrepreneur?: EntrepreneurData
individual?: ExternalIndividualData
organization?: ExternalOrganizationData
entrepreneur?: ExternalEntrepreneurData
coop: CooperativeData
decision: IDecisionData
meta: IMetaDocument
+43 -27
View File
@@ -1,21 +1,27 @@
export * from './Interfaces'
export * from './Templates'
import type { Filter, InsertOneResult } from 'mongodb'
import type { Actions, IFilterDocuments, IGeneratedDocument } from './Interfaces'
import type { Filter, InsertOneResult, UpdateResult } from 'mongodb'
import type { Cooperative as CooperativeModel } from 'cooptypes'
import type { Actions, IFilterDocuments, IGeneratedDocument, externalDataTypes, externalDataTypesArrays, internalFilterTypes } from './Interfaces'
import type { IGenerate } from './Interfaces/Documents'
import { JoinCoop, JoinCoopDecision } from './Actions'
import { MongoDBConnector } from './Services/Databazor'
import { Individual, type IndividualData } from './Models/Individual'
import type { EntrepreneurData, OrganizationData } from './Models'
import type { ExternalIndividualData } from './Models/Individual'
import { Individual } from './Models/Individual'
import type { ExternalEntrepreneurData, ExternalOrganizationData } from './Models'
import { Entrepreneur, Organization } from './Models'
import { Cooperative, type CooperativeData } from './Models/Cooperative'
import type { DocFactory } from './Factory'
import type { PaymentData } from './Models/PaymentMethod'
import { PaymentMethod } from './Models/PaymentMethod'
export type dataTypes = 'individual' | 'entrepreneur' | 'organization'
export type { OrganizationData as IOrganizationData } from './Models'
export type { IndividualData as IIndividualData } from './Models'
export type { EntrepreneurData as IEntrepreneurData } from './Models'
export type dataTypes = 'individual' | 'entrepreneur' | 'organization' | 'paymentMethod'
export type { ExternalOrganizationData as IOrganizationData } from './Models'
export type { ExternalIndividualData as IIndividualData } from './Models'
export type { ExternalEntrepreneurData as IEntrepreneurData } from './Models'
export type { CooperativeData as ICooperativeData } from './Models'
export interface IGenerator {
@@ -25,11 +31,13 @@ export interface IGenerator {
getDocument: (filter: Filter<IFilterDocuments>) => Promise<IGeneratedDocument>
constructCooperative: (username: string, block_num?: number) => Promise<CooperativeData | null>
save: ((type: 'individual', data: IndividualData) => Promise<InsertOneResult>) & ((type: 'entrepreneur', data: EntrepreneurData) => Promise<InsertOneResult>) & ((type: 'organization', data: OrganizationData) => Promise<InsertOneResult>)
get: (type: dataTypes, filter: Filter<IndividualData | EntrepreneurData | OrganizationData>) => Promise<IndividualData | EntrepreneurData | OrganizationData | null>
list: (type: dataTypes) => Promise<IndividualData[] | EntrepreneurData[] | OrganizationData[]>
getHistory: (type: dataTypes, filter: Filter<IndividualData | EntrepreneurData | OrganizationData>) => Promise<IndividualData[] | EntrepreneurData[] | OrganizationData[]>
save: ((type: 'individual', data: ExternalIndividualData) => Promise<InsertOneResult>) & ((type: 'entrepreneur', data: ExternalEntrepreneurData) => Promise<InsertOneResult>) & ((type: 'organization', data: ExternalOrganizationData) => Promise<InsertOneResult>) & ((type: 'paymentMethod', data: PaymentData) => Promise<InsertOneResult>)
get: (type: dataTypes, filter: Filter<internalFilterTypes>) => Promise<externalDataTypes | null>
del: (type: dataTypes, filter: Filter<internalFilterTypes>) => Promise<UpdateResult>
list: (type: dataTypes, filter: Filter<internalFilterTypes>) => Promise<CooperativeModel.Documents.IGetResponse<internalFilterTypes>>
getHistory: (type: dataTypes, filter: Filter<internalFilterTypes>) => Promise<externalDataTypesArrays>
}
export class Generator implements IGenerator {
@@ -49,14 +57,13 @@ export class Generator implements IGenerator {
this.factories = {
'registrator::joincoop': new JoinCoop.JoinCoopTemplateFactory(this.storage),
'registrator::joincoopdec': new JoinCoopDecision.DecisionOfJoinCoopTemplateFactory(this.storage),
// joincoopdec: new JoinCoopDecTemplateFactory(),
}
await this.storage.connect()
}
// Метод отключения от хранилища
async disconnect(): Promise<void> {
this.storage.disconnect()
await this.storage.disconnect()
}
// Метод генерации документа
@@ -74,42 +81,51 @@ export class Generator implements IGenerator {
return await this.storage.getDocument(filter)
}
async save(type: 'individual', data: IndividualData): Promise<InsertOneResult>
async save(type: 'entrepreneur', data: EntrepreneurData): Promise<InsertOneResult>
async save(type: 'organization', data: OrganizationData): Promise<InsertOneResult>
async save(type: 'individual', data: ExternalIndividualData): Promise<InsertOneResult>
async save(type: 'entrepreneur', data: ExternalEntrepreneurData): Promise<InsertOneResult>
async save(type: 'organization', data: ExternalOrganizationData): Promise<InsertOneResult>
async save(type: 'paymentMethod', data: PaymentData): Promise<InsertOneResult>
async save(type: dataTypes, data: IndividualData | EntrepreneurData | OrganizationData): Promise<InsertOneResult> {
async save(type: dataTypes, data: externalDataTypes): Promise<InsertOneResult> {
const model = this.getModel(type, data)
return model.save()
}
async del(type: dataTypes, filter: Filter<internalFilterTypes>): Promise<UpdateResult> {
const model = this.getModel(type)
return model.del(filter)
}
// Универсальные методы получения одного объекта
async get(type: dataTypes, filter: Filter<IndividualData | EntrepreneurData | OrganizationData>): Promise<IndividualData | EntrepreneurData | OrganizationData | null> {
async get(type: dataTypes, filter: Filter<internalFilterTypes>): Promise<externalDataTypes | null> {
const model = this.getModel(type)
return model.getOne(filter)
}
// Универсальные методы получения списка объектов
async list(type: dataTypes): Promise<IndividualData[] | EntrepreneurData[] | OrganizationData[]> {
async list(type: dataTypes, filter: Filter<internalFilterTypes>): Promise<CooperativeModel.Documents.IGetResponse<externalDataTypes>> {
const model = this.getModel(type)
return model.getMany({})
return model.getMany(filter)
}
// Универсальные методы получения истории
async getHistory(type: 'individual' | 'entrepreneur' | 'organization', filter: Filter<IndividualData | EntrepreneurData | OrganizationData>): Promise<IndividualData[] | EntrepreneurData[] | OrganizationData[]> {
// // Универсальные методы получения истории
async getHistory(type: 'individual' | 'entrepreneur' | 'organization' | 'paymentMethod', filter: Filter<internalFilterTypes>): Promise<externalDataTypesArrays> {
const model = this.getModel(type)
return model.getHistory(filter)
}
// Вспомогательный метод для получения модели
getModel(type: dataTypes, data?: IndividualData | EntrepreneurData | OrganizationData) {
getModel(type: dataTypes, data?: externalDataTypes) {
switch (type) {
case 'individual':
return new Individual(this.storage, data as IndividualData)
return new Individual(this.storage, data as ExternalIndividualData)
case 'entrepreneur':
return new Entrepreneur(this.storage, data as EntrepreneurData)
return new Entrepreneur(this.storage, data as ExternalEntrepreneurData)
case 'organization':
return new Organization(this.storage, data as OrganizationData)
return new Organization(this.storage, data as ExternalOrganizationData)
case 'paymentMethod':
return new PaymentMethod(this.storage, data as PaymentData)
default:
throw new Error(`Unknown type: ${type}`)
}
@@ -1,7 +1,6 @@
import { beforeAll, beforeEach, describe, expect, it } from 'vitest'
import type { RegistratorContract, SovietContract } from 'cooptypes'
import { Generator } from '../src'
import type { IndividualData, OrganizationData } from '../src/Models'
import type { IGeneratedDocument } from '../src/Interfaces/Documents'
import { saveBufferToDisk } from '../src/Utils/saveBufferToDisk'
import { loadBufferFromDisk } from '../src/Utils/loadBufferFromDisk'
@@ -12,7 +11,8 @@ import { MongoDBConnector } from '../src/Services/Databazor'
const mongoUri = 'mongodb://127.0.0.1:27017/cooperative'
const coopname = 'voskhod'
import type { EntrepreneurData } from '../src/Models/Entrepreneur'
import type { ExternalEntrepreneurData, ExternalIndividualData, ExternalOrganizationData } from '../src/Models'
import type { PaymentData } from '../src/Models/PaymentMethod'
import { signatureExample } from './signatureExample'
const generator = new Generator()
@@ -27,8 +27,6 @@ beforeEach(async () => {
})
describe('тест генератора документов', async () => {
// const signature = loadSignatureFile()
it('устанавливаем шаблоны документов', async () => {
const storage = new MongoDBConnector(mongoUri)
await storage.connect()
@@ -141,6 +139,9 @@ describe('тест генератора документов', async () => {
registration: '2.0000 RUB',
initial: '1.0000 RUB',
minimum: '1.0000 RUB',
org_initial: '100.0000 RUB',
org_minimum: '1000.0000 RUB',
org_registration: '1100.0000 RUB',
} as RegistratorContract.Tables.Cooperatives.ICooperative,
})
@@ -176,7 +177,7 @@ describe('тест генератора документов', async () => {
})
it('сохранение и извлечение данных пользователя', async () => {
const userData: IndividualData = {
const userData: ExternalIndividualData = {
username: 'ant',
first_name: 'Имя',
last_name: 'Фамилия',
@@ -199,8 +200,47 @@ describe('тест генератора документов', async () => {
expect(individual._id).toEqual(saved.insertedId)
})
it('сохранение, извлечение и удаление банковских реквизитов пользователя', async () => {
const paymentData: PaymentData = {
username: 'ant',
method_id: 1,
user_type: 'individual',
method_type: 'sbp',
is_default: true,
data: {
phone: '+7-111-111-111-11',
},
deleted: false,
}
const saved = await generator.save('paymentMethod', paymentData)
const paymentMethod = await generator.get('paymentMethod', { username: 'ant', method_id: paymentData.method_id }) as any
expect(paymentMethod.deleted).toEqual(false)
Object.keys(paymentData).forEach((field) => {
expect(paymentMethod[field]).toBeDefined()
})
expect(paymentMethod._id).toEqual(saved.insertedId)
const paymentList = await generator.list('paymentMethod', { username: 'ant', method_id: paymentData.method_id })
expect(paymentList.limit).toBeDefined()
expect(paymentList.results).toBeDefined()
expect(paymentList.totalPages).toBeDefined()
expect(paymentList.totalResults).toBeDefined()
expect(paymentList.results.length).toEqual(1)
await generator.del('paymentMethod', { username: 'ant', method_id: paymentData.method_id })
const updatedPaymentMethod = await generator.get('paymentMethod', { username: 'ant', method_id: paymentData.method_id }) as any
expect(updatedPaymentMethod.deleted).toEqual(true)
})
it('сохранение данных кооператива', async () => {
const organizationData: OrganizationData = {
const organizationData: ExternalOrganizationData = {
username: 'voskhod',
type: 'coop',
is_cooperative: true,
@@ -311,7 +351,7 @@ describe('тест генератора документов', async () => {
})
it('сохранение данных организации', async () => {
const organizationData: OrganizationData = {
const organizationData: ExternalOrganizationData = {
username: 'exampleorg',
type: 'ooo',
is_cooperative: false,
@@ -406,7 +446,7 @@ describe('тест генератора документов', async () => {
})
it('сохранение данных индивидуального предпринимателя', async () => {
const entrepreneurData: EntrepreneurData = {
const entrepreneurData: ExternalEntrepreneurData = {
username: 'entrepreneur',
first_name: 'John',
last_name: 'Doe',
@@ -435,13 +475,13 @@ describe('тест генератора документов', async () => {
}
const saved = await generator.save('entrepreneur', entrepreneurData)
const organization = await generator.get('entrepreneur', { username: entrepreneurData.username }) as any
expect(organization._id).toEqual(saved.insertedId)
console.log(saved)
const entrepreneur = await generator.get('entrepreneur', { username: entrepreneurData.username }) as any
console.log(entrepreneur)
expect(entrepreneur._id).toEqual(saved.insertedId)
Object.keys(entrepreneurData).forEach((field) => {
expect(organization[field]).toBeDefined()
expect(entrepreneur[field]).toBeDefined()
})
})
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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
## [1.0.16](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.16-alpha.0...cooptypes@1.0.16) (2024-07-15)
**Note:** Version bump only for package cooptypes
+2 -10
View File
@@ -1,7 +1,7 @@
{
"name": "cooptypes",
"type": "module",
"version": "1.0.16",
"version": "1.0.17",
"description": "_description_",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
@@ -36,8 +36,7 @@
"start": "esno src/index.ts",
"test": "vitest",
"typecheck": "tsc --noEmit",
"prepare": "simple-git-hooks",
"doc": "typedoc",
"docs": "typedoc",
"dev": "concurrently -n 'TYPES' -c 'bgMagenta.white' \"nodemon --watch src --ext ts --exec 'unbuild'\""
},
"dependencies": {
@@ -56,7 +55,6 @@
"esno": "^4.7.0",
"gh-pages": "^6.1.1",
"jsdoc": "^4.0.3",
"lint-staged": "^15.2.2",
"pnpm": "^9.0.6",
"rimraf": "^5.0.5",
"simple-git-hooks": "^2.11.1",
@@ -67,11 +65,5 @@
"vite": "^5.2.11",
"vitest": "^1.5.3"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
},
"gitHead": "9f1bb73326ac0ddd16c2956d18ce0bd8ba0c3ad3"
}
@@ -8,6 +8,11 @@ export * as VerificateAccount from './verificateAccount'
*/
export * as UpdateAccount from './updateAccount'
/**
* Действие обновления публичных регистрационных данных кооператива
*/
export * as UpdateCoop from './updateCoop'
/**
* Действие создания нового аккаунта
*/
@@ -21,7 +26,7 @@ export * as RegisterUser from './registerUser'
/**
* Действие регистрации карточки организации в кооперативе
*/
export * as RegisterOrganization from './registerOrganization'
export * as RegisterCooperative from './registerCooperative'
/**
* Действие замены активного ключа пользователя за подписью системного аккаунта делегатов.
@@ -7,9 +7,9 @@ export const authorizations = [{ permissions: [Permissions.active, Permissions.s
/**
* Имя действия
*/
export const actionName = 'regorg'
export const actionName = 'regcoop'
/**
* @interface
*/
export type IRegisterOrganization = Registrator.IRegorg
export type IRegisterCooperative = Registrator.IRegcoop
@@ -7,9 +7,9 @@ export const authorizations = [{ permissions: [Permissions.active], actor: Actor
/**
* Имя действия
*/
export const actionName = 'update'
export const actionName = 'updateaccnt'
/**
* @interface
*/
export type IUpdateAccount = Registrator.IUpdate
export type IUpdateAccount = Registrator.IUpdateaccnt
@@ -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], actor: Actors._username }] as const
/**
* Имя действия
*/
export const actionName = 'updatecoop'
/**
* @interface
*/
export type IUpdateCoop = Registrator.IUpdatecoop
@@ -91,3 +91,11 @@ export interface IGenerateJoinCoop extends IGenerate {
export interface IGenerateJoinCoopDecision extends IGenerate {
decision_id: number
}
export interface IGetResponse<T> {
results: T[]
page: number
limit: number
totalResults: number
totalPages: number
}
@@ -2,3 +2,4 @@ export * as Documents from './documents'
export * as Users from './users'
export * as Blockchain from './blockchain'
export * as Model from './model'
export * as Payments from './payments'

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