Compare commits

...

26 Commits

Author SHA1 Message Date
Alex Ant bd99697c5b chore(release): publish
- coopback@1.7.34
 - coopdoc-generator-ts@1.0.78
 - cooptypes@1.0.21
 - coop-notificator@0.0.25
 - terminal@0.1.38
2024-08-25 11:39:35 +05:00
Alex Ant 1d5c431ff6 update types 2024-08-25 11:38:40 +05:00
Alex Ant 3736c510ff chore(release): publish
- cooparser-ts@1.0.18
 - coopback@1.7.33
 - coopdoc-generator-ts@1.0.77
 - terminal@0.1.37
2024-08-25 11:27:44 +05:00
Alex Ant 26c12c3a85 update 2024-08-25 11:25:28 +05:00
Alex Ant 10bb08e5e6 chore(release): publish
- coopback@1.7.32
 - coopdoc-generator-ts@1.0.76
 - terminal@0.1.36
2024-08-25 11:19:13 +05:00
Alex Ant b50d5b6121 publish 2024-08-25 11:07:57 +05:00
Alex Ant 38b28752e1 chore(release): publish
- coopback@1.7.31
 - coopdoc-generator-ts@1.0.75
 - cooptypes@1.0.20
 - coop-notificator@0.0.24
 - terminal@0.1.35
2024-08-25 11:02:44 +05:00
Alex Ant 87c51009de ProgramProvision | ЦПП Цифровой Кошелёк 2024-08-25 10:59:57 +05:00
Alex Ant a950a47b28 chore(release): publish
- coopback@1.7.31-alpha.1
 - coopdoc-generator-ts@1.0.75-alpha.1
 - cooptypes@1.0.20-alpha.1
 - coop-notificator@0.0.24-alpha.1
 - terminal@0.1.35-alpha.1
2024-08-23 10:38:04 +05:00
Alex Ant 2609d10c0b update 2024-08-23 10:35:48 +05:00
Alex Ant 7b4cb0f66c publish 2024-08-23 10:34:38 +05:00
Alex Ant 26db0e1e69 push 2024-08-21 15:57:02 +05:00
Alex Ant 62e64cbd22 upgrade 2024-08-21 14:43:43 +05:00
Alex Ant 844a47830f fix types 2024-08-21 13:11:37 +05:00
Alex Ant 2cc1e32f45 chore(release): publish
- coopback@1.7.31-alpha.0
 - coopdoc-generator-ts@1.0.75-alpha.0
 - cooptypes@1.0.20-alpha.0
 - coop-notificator@0.0.24-alpha.0
 - terminal@0.1.35-alpha.0
2024-08-21 12:53:38 +05:00
Alex Ant 9890958b1e update contracts interface 2024-08-21 12:50:44 +05:00
Alex Ant 6dbd6d6edc fix 2024-08-19 14:31:01 +05:00
Alex Ant bf7a4dfd28 путь установки 2024-08-19 14:20:38 +05:00
Alex Ant f52626bda0 Полное разделение форм ввода данных пользователя пригодное для использования в процессе инсталляции 2024-08-16 16:17:24 +05:00
Alex Ant 88cec4bf2b рефакторинг | вынос форм для ввода пользовательской информации в shared для дальнейшего использования в модуле установки 2024-08-15 20:30:01 +05:00
Alex Ant b880def108 fix 2024-08-15 12:39:19 +05:00
Alex Ant bfd5dc039a addUser в панели управления 2024-08-15 12:28:18 +05:00
Alex Ant c0d236dff3 chore(release): publish
- coopback@1.7.30
 - coopdoc-generator-ts@1.0.74
 - cooptypes@1.0.19
 - coop-notificator@0.0.23
 - terminal@0.1.34
2024-08-13 23:03:13 +05:00
Alex Ant 743d5fc306 разделение формы для ввода персональных данных с регистратором; зародыш формы добавления пайщика в реестр. 2024-08-13 23:00:09 +05:00
Alex Ant 2f51516163 step to install | works on add user by api 2024-08-13 15:21:23 +05:00
Alex Ant b606b46aa4 страница настроек с кнопками выхода для пайщика 2024-08-05 14:31:27 +05:00
291 changed files with 5170 additions and 3105 deletions
+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.18](https://github.com/copenomics/cooparser/compare/cooparser-ts@1.0.17...cooparser-ts@1.0.18) (2024-08-25)
**Note:** Version bump only for package cooparser-ts
## [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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "pkg-placeholder",
"version": "1.0.17",
"version": "1.0.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pkg-placeholder",
"version": "1.0.17",
"version": "1.0.18",
"license": "MIT",
"devDependencies": {
"@antfu/eslint-config": "^2.16.0",
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "cooparser-ts",
"type": "module",
"version": "1.0.17",
"private": true,
"version": "1.0.18",
"private": false,
"packageManager": "pnpm@9.0.6",
"description": "",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
+1 -1
View File
@@ -18,7 +18,7 @@
"project": "./tsconfig.json"
},
"rules": {
"no-console": "error",
"no-console": "warn",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
+4 -1
View File
@@ -1,5 +1,8 @@
{
"cSpell.words": [
"subcode"
]
],
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.34](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.33...coopback@1.7.34) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.33](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.32...coopback@1.7.33) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.32](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.31...coopback@1.7.32) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.31](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.31-alpha.1...coopback@1.7.31) (2024-08-25)
**Note:** Version bump only for package coopback
## [1.7.30](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29...coopback@1.7.30) (2024-08-13)
**Note:** Version bump only for package coopback
## [1.7.29](https://github.com/hagopj13/node-express-boilerplate/compare/coopback@1.7.29-alpha.0...coopback@1.7.29) (2024-08-05)
**Note:** Version bump only for package coopback
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "create-nodejs-express-app",
"version": "1.7.29",
"version": "1.7.34",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "create-nodejs-express-app",
"version": "1.7.29",
"version": "1.7.34",
"license": "MIT",
"dependencies": {
"@a2seven/yoo-checkout": "^1.1.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "coopback",
"version": "1.7.29",
"version": "1.7.34",
"description": "",
"private": true,
"bin": "bin/createNodejsApp.js",
@@ -17,12 +17,11 @@ const [username] = args;
async function createServiceUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const user = await userService.createServiceUser(username);
const token = await tokenService.generateServiceAccessToken(user);
logger.log('token:', token.access.token);
console.log('token:', token.access.token);
} catch (e: any) {
logger.log('Ошибка: ', e.message);
logger.error('Ошибка: ', e.message);
}
process.exit(0);
+1 -1
View File
@@ -17,7 +17,7 @@ async function deleteUser(username: string) {
try {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
await userService.deleteUserById(username);
await userService.deleteUserByUsername(username);
console.log('Пользователь удалён: ', username);
} catch (e: any) {
+1 -7
View File
@@ -15,17 +15,12 @@ async function init() {
await mongoose.connect(config.mongoose.url, config.mongoose.options);
const wif = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3';
const password = await crypto.createHash('sha256').update(wif).digest('hex');
const hashed_password = await hash(password, 8);
console.log('password: ', hashed_password);
await userService.createUser({
username: 'ant',
email: 'dacom.dark.sun@gmail.com',
password,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'chairman',
role: 'user',
type: 'individual',
individual_data: {
first_name: 'Иван',
@@ -46,7 +41,6 @@ async function init() {
const voskhod = await userService.createUser({
username: 'voskhod',
email: 'chairman.voskhod@gmail.com',
password,
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
type: 'organization',
+4
View File
@@ -21,6 +21,9 @@ const envVarsSchema = Joi.object()
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which verify email token expires'),
JWT_INVITE_EXPIRATION_MINUTES: Joi.number().default(3600).description('minutes after invite token expires'),
SMTP_HOST: Joi.string().description('server that will send the emails'),
SMTP_PORT: Joi.number().description('port to connect to the email server'),
SMTP_USERNAME: Joi.string().description('username for email server'),
@@ -57,6 +60,7 @@ export default {
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
inviteExpirationMinutes: envVars.JWT_INVITE_EXPIRATION_MINUTES,
},
email: {
smtp: {
+3 -4
View File
@@ -1,9 +1,8 @@
import {Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
import config from './config'
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import config from './config';
import { tokenTypes } from './tokens';
import { User } from '../models';
const jwtOptions = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
@@ -14,7 +13,7 @@ const jwtVerify = async (payload, done) => {
if (payload.type !== tokenTypes.ACCESS) {
throw new Error('Invalid token type');
}
const user = await User.findOne({_id: payload.sub});
const user = await User.findOne({ _id: payload.sub });
if (!user) {
return done(null, false);
}
+2 -2
View File
@@ -1,7 +1,7 @@
const allRoles = {
user: [''],
service: ['sendNotification'],
chairman: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
service: ['addUser', 'sendNotification', 'install'],
chairman: ['addUser', 'getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
member: ['getUsers', 'manageUsers', 'loadAgenda', 'loadStaff', 'getDocuments', 'loadInfo'],
};
+1
View File
@@ -3,4 +3,5 @@ export const tokenTypes = {
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
INVITE: 'invite',
};
@@ -26,8 +26,8 @@ export const refreshTokens = catchAsync(async (req: RRefreshTokens, res: any) =>
});
export const lostKey = catchAsync(async (req: RForgotKey, res) => {
const resetKeyToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetKeyToken);
const resetKeyToken = await tokenService.generateResetKeyToken(req.body.email);
await emailService.sendResetKeyEmail(req.body.email, resetKeyToken);
res.status(NO_CONTENT).send();
});
@@ -1,9 +1,16 @@
import httpStatus from 'http-status';
import { getMonoStatus } from '../services/mono.service';
import { getBlockchainInfo } from '../services/blockchain.service';
import { IHealthResponse } from '../types';
import { IHealthResponse, IInstall, RInstall } from '../types';
import { Request, Response } from 'express';
import catchAsync from '../utils/catchAsync';
import { monoService } from '../services';
export const install = catchAsync(async (req: RInstall, res: Response) => {
const { body } = req;
await monoService.install(body as IInstall);
res.status(httpStatus.OK).send();
});
export const getHealth = catchAsync(async (req: Request, res: Response) => {
const status = await getMonoStatus();
@@ -91,7 +91,8 @@ export const addPaymentMethod = catchAsync(async (req: RSavePaymentMethod, res)
const paymentData: Cooperative.Payments.IPaymentData = {
username: req.body.username,
method_id: req.body.method_id,
user_type: user.type,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
user_type: user.type as any,
method_type: req.body.method_type,
is_default: false,
data: req.body.data,
@@ -1,12 +1,16 @@
import http from 'http-status';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService, tokenService } from '../services';
import { RCreateUser, RJoinCooperative } from '../types';
import { userService, tokenService, emailService, blockchainService } from '../services';
import { IAddUser, ICreateUser, RCreateUser, RJoinCooperative } from '../types';
import httpStatus from 'http-status';
import pick from '../utils/pick';
import { IGetResponse } from '../types/common';
import { IUser } from '../models/user.model';
import { Request, Response } from 'express';
import { generateUsername } from '../../tests/utils/generateUsername';
import config from '../config/config';
import logger from '../config/logger';
/**
* Порядок регистрации:
@@ -19,9 +23,9 @@ import { IUser } from '../models/user.model';
* 7. (joinCooperative) Пользователь подписывает заявление на вступление и вызывает метод joinCooperative, прикладывая подписанное заявление.
* 8. Система сохраняет подписанное заявление
* 9. (createInitialPayment) Пользователь получает ордер на оплату.
* 9. Пользователь оплачивает ордер.
* 10. Система принимает оплату и регистрирует аккаунт в блокчейне.
* 11. (trx confirmpay) Пользователь подписывает велком-письмо с подтверждением собственноручности оплаты (транзакция в блокчейне для активации)
* 12. Регистрация продолжается ожиданием решения совета
*/
export const createUser = catchAsync(async (req: RCreateUser, res) => {
const user = await userService.createUser(req.body);
@@ -30,6 +34,43 @@ export const createUser = catchAsync(async (req: RCreateUser, res) => {
res.status(http.CREATED).send({ user, tokens });
});
export const addUser = catchAsync(async (req: Request, res: Response) => {
const body: IAddUser = req.body;
const newUser: ICreateUser = {
...body,
public_key: '',
role: 'user',
username: generateUsername(),
};
const user = await userService.createUser(newUser);
user.status = 'registered';
user.is_registered = true;
await user.save();
try {
await blockchainService.addUser({
...body,
...newUser,
registrator: config.service_username,
referer: body.referer ? body.referer : '',
coopname: config.coopname,
meta: '',
});
console.log('user: ', user);
const token = await tokenService.generateInviteToken(user.email);
await emailService.sendInviteEmail(req.body.email, token);
} catch (e: any) {
console.log('on e: ', e);
logger.warn('error on add user: ', e);
await userService.deleteUserByUsername(newUser.username);
throw new ApiError(httpStatus.BAD_GATEWAY, e.message);
}
res.status(httpStatus.CREATED).send({ user });
});
export const joinCooperative = catchAsync(async (req: RJoinCooperative, res) => {
await userService.joinCooperative(req.body);
@@ -47,7 +88,7 @@ export const getUsers = catchAsync(async (req, res) => {
for await (const user of users.results) {
const json = user.toJSON();
if (user.type != 'service') {
json.private_data = (await user.getPrivateData()) || {};
json.private_data = await user.getPrivateData();
data.push(json);
}
}
@@ -68,7 +109,9 @@ export const getUser = catchAsync(async (req, res) => {
}
const json = user.toJSON();
json.private_data = await user.getPrivateData();
if (user.type != 'service') {
json.private_data = await user.getPrivateData();
}
res.send(json);
});
@@ -82,6 +125,6 @@ export const updateUser = catchAsync(async (req, res) => {
});
export const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserById(req.params.username);
await userService.deleteUserByUsername(req.params.username);
res.status(http.NO_CONTENT).send();
});
@@ -25,7 +25,7 @@ const tokenSchema = new Schema<IToken>(
},
type: {
type: String,
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL],
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL, tokenTypes.INVITE],
required: true,
},
expires: {
+3 -32
View File
@@ -15,11 +15,10 @@ export interface IUser {
message: string;
is_registered: boolean;
has_account: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
type: 'individual' | 'entrepreneur' | 'organization' | 'service';
public_key: string;
referer: string;
email: string;
password: string;
role: string;
is_email_verified: boolean;
statement: {
@@ -36,7 +35,6 @@ export interface IUser {
getPrivateData(): Promise<
Cooperative.Users.IIndividualData | Cooperative.Users.IEntrepreneurData | Cooperative.Users.IOrganizationData | null
>;
isPasswordMatch(password: string): Promise<boolean>;
}
interface IUserModel extends Model<IUser> {
@@ -72,7 +70,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
public_key: {
type: String,
required: true,
default: '',
},
referer: {
type: String,
@@ -80,7 +78,7 @@ const userSchema = new Schema<IUser, IUserModel>(
},
email: {
type: String,
required: true,
required: false,
unique: true,
trim: true,
lowercase: true,
@@ -90,18 +88,6 @@ const userSchema = new Schema<IUser, IUserModel>(
}
},
},
password: {
type: String,
required: true,
trim: true,
minlength: 8,
validate(value) {
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
throw new Error('Password must contain at least one letter and one number');
}
},
private: true, // used by the toJSON plugin
},
role: {
type: String,
enum: roles,
@@ -163,21 +149,6 @@ userSchema.methods.getPrivateData = async function (): Promise<
return result;
};
userSchema.methods.isPasswordMatch = async function (password) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const user = this;
return compare(password, user.password);
};
userSchema.pre('save', async function (next) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const user = this;
if (user.isModified('password')) {
user.password = await hash(user.password, 8);
}
next();
});
const User = model<IUser, IUserModel>('User', userSchema);
export default User;
@@ -1,8 +1,12 @@
import { Router } from 'express';
import { monoController } from '../../controllers';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import { monoValidation } from '../../validations';
const router = Router();
router.route('/install').post(auth('install'), validate(monoValidation.RInstall), monoController.install);
router.route('/health').get(monoController.getHealth);
export default router;
@@ -11,9 +11,9 @@ router
.post(validate(userValidation.RCreateUser), userController.createUser)
.get(auth('getUsers'), validate(userValidation.RGetUsers), userController.getUsers);
router
.route('/join-cooperative')
.post(validate(userValidation.RJoinCooperative), userController.joinCooperative)
router.route('/join-cooperative').post(validate(userValidation.RJoinCooperative), userController.joinCooperative);
router.route('/add').post(auth('addUser'), validate(userValidation.RAddUser), userController.addUser);
router
.route('/:username')
@@ -41,27 +41,13 @@ export const loginUserWithSignature = async (email, now, signature) => {
if (!hasKey) throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
} catch (e) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Аккаунт в блокчейне не найден');
throw new ApiError(httpStatus.UNAUTHORIZED, 'Неверный приватный ключ');
}
}
return user;
};
/**
* Login with email and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
export const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};
/**
* Logout
* @param {string} refreshToken
@@ -102,26 +88,27 @@ export const refreshAuth = async (data: IRefreshTokens) => {
*/
export const resetKey = async (resetKeyToken, publicKey) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetKeyToken, tokenTypes.RESET_PASSWORD);
const resetKeyTokenDoc = await tokenService.verifyToken(resetKeyToken, [tokenTypes.RESET_PASSWORD, tokenTypes.INVITE]);
const user = await userService.getUserById(resetPasswordTokenDoc.user);
const user = await userService.getUserById(resetKeyTokenDoc.user);
if (!user) {
throw new Error();
}
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.service_username,
username: user.username,
public_key: publicKey,
});
if (config.env !== 'test')
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.service_username,
username: user.username,
public_key: publicKey,
});
await userService.updateUserById(user._id, { public_key: publicKey });
await Token.deleteMany({ user: user._id, type: tokenTypes.RESET_PASSWORD });
} catch (error) {
console.log(error);
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
throw new ApiError(httpStatus.UNAUTHORIZED, 'Token reset failed');
}
};
@@ -84,9 +84,9 @@ async function getCooperative(coopname) {
const [cooperative] = await lazyFetch(
api,
process.env.REGISTRATOR_CONTRACT,
process.env.REGISTRATOR_CONTRACT,
'orgs',
RegistratorContract.contractName.production,
RegistratorContract.contractName.production,
RegistratorContract.Tables.Cooperatives.tableName,
coopname,
coopname,
1
@@ -178,6 +178,36 @@ async function registerBlockchainAccount(user: IUser, orderData: GatewayContract
);
}
async function createBoard(data: SovietContract.Actions.Boards.CreateBoard.ICreateboard) {
const eos = await getInstance(config.service_wif);
console.log('data: ', data);
const actions = [
{
account: SovietContract.contractName.production,
name: SovietContract.Actions.Boards.CreateBoard.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
async function createOrder(data) {
const eos = await getInstance(process.env.REGISTRATOR_WIF);
@@ -266,6 +296,34 @@ async function failOrder(data) {
);
}
export async function addUser(data: RegistratorContract.Actions.AddUser.IAddUser) {
const eos = await getInstance(config.service_wif);
const actions = [
{
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [
{
actor: config.service_username,
permission: 'active',
},
],
data,
},
];
await eos.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
}
);
}
export async function changeKey(data: RegistratorContract.Actions.ChangeKey.IChangeKey) {
const eos = await getInstance(config.service_wif);
@@ -323,4 +381,5 @@ export {
getBlockchainInfo,
getBlockchainAccount,
hasActiveKey,
createBoard,
};
@@ -30,7 +30,7 @@ export async function buildComplexDocument(
const document = await generator.getDocument({ hash: raw_document.document.hash });
const user = await User.findOne({ username: raw_document.username });
if (user) {
if (user && user.type != 'service') {
const user_data = await user?.getPrivateData();
const action: Cooperative.Blockchain.IExtendedAction = {
@@ -4,7 +4,7 @@ import logger from '../config/logger';
const { email, env } = config;
const transport = createTransport(email.smtp);
export const transport = createTransport(email.smtp);
/* istanbul ignore next */
if (env !== 'test') {
@@ -21,7 +21,7 @@ if (env !== 'test') {
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to, subject, text) => {
export const sendEmail = async (to, subject, text) => {
const msg = { from: email.from, to, subject, text };
await transport.sendMail(msg);
};
@@ -32,7 +32,7 @@ const sendEmail = async (to, subject, text) => {
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to, token) => {
export const sendResetKeyEmail = async (to, token) => {
const subject = 'Восстановление доступа';
const resetPasswordUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
@@ -44,13 +44,31 @@ const sendResetPasswordEmail = async (to, token) => {
await sendEmail(to, subject, text);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
export const sendInviteEmail = async (to, token) => {
const subject = 'Приглашение в Цифровой Кооператив';
const inviteUrl = `${config.base_url}/#/${config.coopname}/auth/reset-key?token=${token}`;
const text = `Вам отправлено приглашение на подключение к Цифровому Кооперативу в качестве действующего пайщика.
Для того, чтобы воспользоваться приглашением и получить ключ доступа, пожалуйста, нажмите на ссылку: ${inviteUrl}
Время действия ссылки - 24 часа.`;
await sendEmail(to, subject, text);
};
/**
* Send verification email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to, token) => {
export const sendVerificationEmail = async (to, token) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
@@ -59,5 +77,3 @@ To verify your email, click on this link: ${verificationEmailUrl}
If you did not create an account, then ignore this email.`;
await sendEmail(to, subject, text);
};
export { transport, sendEmail, sendResetPasswordEmail, sendVerificationEmail };
@@ -3,7 +3,112 @@ import { Mono } from '../models';
import ApiError from '../utils/ApiError';
import config from '../config/config';
import logger from '../config/logger';
import { IHealthStatus } from '../types';
import { IAddUser, ICreateUser, IHealthStatus, IInstall } from '../types';
import { generateUsername } from '../../tests/utils/generateUsername';
import { generator } from './document.service';
import { blockchainService, emailService, tokenService, userService } from '.';
import { IUser } from '../models/user.model';
import axios from 'axios';
import { getBlockchainInfo } from './blockchain.service';
import { RegistratorContract } from 'cooptypes';
export const install = async (soviet: IInstall): Promise<void> => {
console.log('IInstall: ', soviet);
const mono = await Mono.findOne({ coopname: config.coopname });
const info = await getBlockchainInfo();
const coop = await blockchainService.getCooperative(config.coopname);
if (!coop) throw new ApiError(httpStatus.BAD_REQUEST, 'Информация о кооперативе не обнаружена');
if (mono && mono.status != 'install') {
throw new ApiError(httpStatus.BAD_REQUEST, 'Установка уже выполнена');
}
const users = [] as IUser[];
const members = [] as any;
const sovietExt = [] as any;
try {
for (const member of soviet) {
console.log(member);
const username = generateUsername();
sovietExt.push({ ...member, username });
const addUser: RegistratorContract.Actions.AddUser.IAddUser = {
registrator: config.service_username,
coopname: config.coopname,
referer: '',
username,
type: 'individual',
created_at: info.head_block_time,
initial: coop.initial,
minimum: coop.minimum,
spread_initial: false,
meta: '',
};
await blockchainService.addUser(addUser);
const createUser: ICreateUser = {
email: member.individual_data.email,
individual_data: member.individual_data,
referer: '',
role: 'user',
type: 'individual',
username,
};
const user = await userService.createUser(createUser);
user.status = 'registered';
user.is_registered = true;
await user.save();
//Генерируем токен и отправляем приглашение
const token = await tokenService.generateInviteToken(member.individual_data.email);
await emailService.sendInviteEmail(member.individual_data.email, token);
//Добавляем в массив членов для отправки в бч
members.push({
username: username,
is_voting: true,
position_title: member.role === 'chairman' ? 'Председатель совета' : 'Член совета',
position: member.role,
});
users.push(user);
}
const chairman = sovietExt.find((el) => el.role == 'chairman');
//TODO создаёт доску совета
await blockchainService.createBoard({
coopname: config.coopname,
username: config.service_username,
type: 'soviet',
members: members,
name: 'Совет',
description: '',
});
} catch (e: any) {
console.log('on error', e);
for (const user of users) {
await userService.deleteUserByUsername(user.username);
await generator.del('individual', { username: user.username });
}
throw new ApiError(httpStatus.BAD_REQUEST, e.message);
}
await Mono.updateOne(
{ coopname: config.coopname },
{
status: 'active',
}
);
logger.info('MONO активирован');
};
export const init = async (): Promise<void> => {
const mono = await Mono.findOne({ coopname: config.coopname });
@@ -198,7 +198,10 @@ export async function createInitialOrder(username: string, data: ICreateInitialP
}
export async function catchIPN(ipnBody: IYandexIPN) {
if (!connection.db) throw new Error('Нет подключения');
const payments = await connection.db.collection('payments');
const exist = await payments.findOne({ 'object.id': ipnBody.object.id });
if (!exist) {
@@ -48,12 +48,17 @@ export const saveToken = async (token, userId, expires, type, blacklisted = fals
/**
* Verify token and return token doc (or throw an error if it is not valid)
* @param {string} token
* @param {string} type
* @param {string[]} types
* @returns {Promise<Token>}
*/
export const verifyToken = async (token, type) => {
export const verifyToken = async (token, types) => {
const payload = jwt.verify(token, config.jwt.secret);
const tokenDoc = await Token.findOne({ token, type, user: payload.sub, blacklisted: false });
const tokenDoc = await Token.findOne({
token,
$or: types.map((type) => ({ type })),
user: payload.sub,
blacklisted: false,
});
if (!tokenDoc) {
throw new Error('Token not found');
}
@@ -68,7 +73,7 @@ export const verifyToken = async (token, type) => {
export const generateServiceAccessToken = async (user) => {
const accessTokenExpires = moment().add(100, 'years'); // Пример с очень долгим сроком действия
const accessToken = generateToken(user.id, accessTokenExpires, tokenTypes.ACCESS);
console.log('on generate', accessToken);
return {
access: {
token: accessToken,
@@ -107,7 +112,7 @@ export const generateAuthTokens = async (user) => {
* @param {string} email
* @returns {Promise<string>}
*/
export const generateResetPasswordToken = async (email) => {
export const generateResetKeyToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
@@ -118,6 +123,22 @@ export const generateResetPasswordToken = async (email) => {
return resetKeyToken;
};
/**
* Generate invite token
* @param {string} email
* @returns {Promise<string>}
*/
export const generateInviteToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.inviteExpirationMinutes, 'minutes');
const inviteToken = generateToken(user.id, expires, tokenTypes.INVITE);
await saveToken(inviteToken, user.id, expires, tokenTypes.INVITE);
return inviteToken;
};
/**
* Generate verify email token
* @param {User} user
@@ -3,20 +3,14 @@ import { User } from '../models';
import ApiError from '../utils/ApiError';
import { generator } from './document.service';
import { ICreateUser, IJoinCooperative } from '../types/auto-generated/user.validation';
import ecc from 'eosjs-ecc';
import { PublicKey, Signature } from '@wharfkit/antelope';
import faker from 'faker';
import { randomBytes } from 'crypto';
export const createServiceUser = async (username: string) => {
const password = randomBytes(16).toString('hex');
return User.create({
username,
type: 'service',
role: 'service',
public_key: 'thanks-no-need',
password,
email: faker.internet.email().toLowerCase(),
});
};
@@ -28,6 +22,7 @@ export const createServiceUser = async (username: string) => {
export const createUser = async (userBody: ICreateUser) => {
//TODO проверяем на существование пользователя
//допускаем обновление личных данных, если пользователь находится в статусе 'created'
const exist = await User.findOne({ email: userBody.email });
if (userBody.type === 'individual' && !userBody.individual_data)
@@ -186,7 +181,7 @@ export const updateUserByUsername = async (username, updateBody) => {
* @param {string} username
* @returns {Promise<User>}
*/
export const deleteUserById = async (username) => {
export const deleteUserByUsername = async (username) => {
const user = await getUserByUsername(username);
if (!user) {
throw new ApiError(http.NOT_FOUND, 'Пользователь не найден');
@@ -16,7 +16,9 @@ export const updateBoard = async (action: SovietContract.Actions.Boards.UpdateBo
await user.save();
}
const chairman = await userService.getUserByUsername(action.chairman);
const chairman_username = action.members.find((el) => el.position == 'chairman')?.username;
const chairman = await userService.getUserByUsername(chairman_username as string);
chairman.role = 'chairman';
await chairman.save();
};
@@ -6,6 +6,7 @@
export * from './auth.validation';
export * from './coop.validation';
export * from './document.validation';
export * from './mono.validation';
export * from './notify.validation';
export * from './payment.validation';
export * from './user.validation';
@@ -0,0 +1,32 @@
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type IInstall = {
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
}[];
export interface RInstall {
body: {
individual_data: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
role: 'chairman' | 'member';
}[];
}
@@ -3,6 +3,84 @@
* Do not modify this file manually
*/
export interface IAddUser {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
}
export interface ICreateUser {
email: string;
entrepreneur_data?: {
@@ -73,10 +151,9 @@ export interface ICreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
public_key?: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -169,6 +246,86 @@ export interface IOrganizationData {
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
}
export interface RAddUser {
body: {
created_at: string;
email: string;
entrepreneur_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
birthdate: string;
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
individual_data?: {
birthdate: string;
email: string;
first_name: string;
full_address: string;
last_name: string;
middle_name: string;
phone: string;
};
initial: string;
minimum: string;
organization_data?: {
bank_account: {
account_number: string;
bank_name: string;
card_number?: string;
currency: 'RUB' | 'Other';
details: {
bik: string;
corr: string;
kpp: string;
};
};
city: string;
country: 'Russia' | 'Other';
details: {
inn: string;
ogrn: string;
};
email: string;
full_address: string;
full_name: string;
is_cooperative: boolean;
phone: string;
represented_by: {
based_on: string;
first_name: string;
last_name: string;
middle_name: string;
position: string;
};
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
referer?: string;
spread_initial: boolean;
type: 'individual' | 'entrepreneur' | 'organization';
};
}
export interface RCreateUser {
body: {
email: string;
@@ -240,10 +397,9 @@ export interface RCreateUser {
short_name: string;
type: 'coop' | 'ooo' | 'oao' | 'zao' | 'pao' | 'ao';
};
password: string;
public_key: string;
public_key?: string;
referer?: string;
role: 'user' | 'chairman' | 'member';
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
};
@@ -280,7 +436,6 @@ export interface RJoinCooperative {
export interface RUpdateUser {
body?: {
email?: string;
password?: string;
};
params?: {
username: string;
@@ -5,3 +5,4 @@ export * as orderValidation from './payment.validation';
export * as coopValidation from './coop.validation';
export * as customValidation from './custom.validation';
export * as notifyValidation from './notify.validation';
export * as monoValidation from './mono.validation';
@@ -0,0 +1,13 @@
import * as Joi from 'joi';
import { IIndividualData } from './user.validation';
export const IInstall = Joi.array().items(
Joi.object({
role: Joi.string().required().valid('chairman', 'member'),
individual_data: IIndividualData.required(),
})
);
export const RInstall = Joi.object({
body: IInstall.required(),
});
@@ -1,5 +1,5 @@
import * as Joi from 'joi';
import { password, objectId } from './custom.validation';
import { password } from './custom.validation';
import { IBankAccount } from './payment.validation';
export const IIndividualData = Joi.object({
@@ -57,9 +57,8 @@ export const IEntrepreneurData = Joi.object({
export const ICreateUser = Joi.object({
email: Joi.string().required().email(),
password: Joi.string().required().custom(password),
role: Joi.string().required().valid('user', 'chairman', 'member'),
public_key: Joi.string().required(),
role: Joi.string().required().valid('user'),
public_key: Joi.string().optional(),
username: Joi.string().required().length(12),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
@@ -68,6 +67,25 @@ export const ICreateUser = Joi.object({
entrepreneur_data: IEntrepreneurData.optional(),
});
export const IAddUser = Joi.object({
email: Joi.string().required().email(),
referer: Joi.string().length(12).allow('').optional(),
type: Joi.string().required().valid('individual', 'entrepreneur', 'organization'),
individual_data: IIndividualData.optional(),
organization_data: IOrganizationData.optional(),
entrepreneur_data: IEntrepreneurData.optional(),
spread_initial: Joi.boolean().required(),
created_at: Joi.string().required(),
initial: Joi.string().required(),
minimum: Joi.string().required(),
});
export const RAddUser = Joi.object({
body: IAddUser.required(),
});
export const IDocument = Joi.object().keys({
hash: Joi.string().required(),
signature: Joi.string().required(),
@@ -104,7 +122,6 @@ export const RUpdateUser = Joi.object({
body: Joi.object()
.keys({
email: Joi.string().email(),
password: Joi.string().custom(password),
})
.min(1),
});
@@ -1,17 +1,11 @@
import mongoose from 'mongoose';
import bcrypt from 'bcryptjs';
import faker from 'faker';
import User from '../../src/models/user.model';
import { generateUsername } from '../utils/generateUsername';
import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
const password = 'password1';
const email1 = faker.internet.email().toLowerCase();
export const participantOne: ICreateUser = {
email: email1,
password,
username: generateUsername(),
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
role: 'user',
+1 -10
View File
@@ -7,10 +7,6 @@ import { Cooperative } from 'cooptypes';
import { ICreateUser } from '../../src/types';
import { ObjectId } from 'mongodb';
const password = 'password1';
const salt = bcrypt.genSaltSync(8);
const hashedPassword = bcrypt.hashSync(password, salt);
const generateRandomId = () => new mongoose.Types.ObjectId();
type testUser = Omit<IUser, 'getPrivateData' | 'isPasswordMatch' | 'private_data'> & {
@@ -33,7 +29,6 @@ const adminUsername = generateUsername();
export const admin: testUser = {
_id: generateRandomId(),
email: email1,
password,
status: 'active',
has_account: false,
message: '',
@@ -68,7 +63,6 @@ const usernameOne = generateUsername();
export const userOne: testUser = {
_id: generateRandomId(),
email: email2,
password,
has_account: false,
status: 'active',
message: '',
@@ -104,7 +98,6 @@ export const userTwo: testUser = {
_id: generateRandomId(),
email: email3,
has_account: false,
password,
status: 'active',
message: '',
is_registered: true,
@@ -138,7 +131,6 @@ export const chairman: testUser = {
_id: generateRandomId(),
email: email4,
has_account: false,
password,
status: 'active',
message: '',
is_registered: true,
@@ -171,7 +163,6 @@ export const chairman: testUser = {
export const voskhod: testUser = {
_id: generateRandomId(),
email: email5,
password,
has_account: false,
status: 'active',
message: '',
@@ -259,7 +250,7 @@ export const insertUsers = async (users: testUser[]) => {
for (const user of users) {
const { type, individual_data, organization_data, entrepreneur_data, block_num, ...rest } = user;
await User.insertMany([{ ...rest, type, password: hashedPassword }]);
await User.insertMany([{ ...rest, type }]);
if (type === 'individual' && individual_data) {
await insertPrivateIndividualUserData({
@@ -29,7 +29,6 @@ describe('Auth routes', () => {
newUser = {
email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -50,8 +49,6 @@ describe('Auth routes', () => {
test('should return 201 and successfully register user if request data is ok', async () => {
const res = await request(app).post('/v1/users').send(newUser).expect(httpStatus.CREATED);
expect(res.body.user).not.toHaveProperty('password');
expect(res.body.user).toEqual({
id: expect.anything(),
email: newUser.email,
@@ -76,7 +73,6 @@ describe('Auth routes', () => {
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
expect(dbUser).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
expect(res.body.tokens).toEqual({
@@ -97,26 +93,10 @@ describe('Auth routes', () => {
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
newUser.password = 'password';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
newUser.password = '11111111';
await request(app).post('/v1/users').send(newUser).expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/login', () => {
test('should return 200 and login user if email and password match', async () => {
test('should return 200 and login user if email and signature match', async () => {
await insertUsers([userOne]);
const now = (await getBlockchainInfo()).head_block_time;
@@ -154,10 +134,16 @@ describe('Auth routes', () => {
test('should return 401 error if there are no users with that email', async () => {
const now = (await getBlockchainInfo()).head_block_time;
const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
const bytes = Bytes.fromString(now, 'utf8');
const checksum = Checksum256.hash(bytes);
const signature = privateKey.signDigest(checksum);
const loginCredentials = {
now,
email: userOne.email,
signature: userOne.password,
signature,
};
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
@@ -166,7 +152,9 @@ describe('Auth routes', () => {
});
// test('should return 401 error if signature is wrong', async () => {
// Восстановить проверку. Необходимо подложить аккаунт тестового юзера в коде вместо обращения к блокчейну.
// await insertUsers([userOne]);
// const now = (await getBlockchainInfo()).head_block_time;
// const privateKey = PrivateKey.fromString('5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3');
@@ -288,14 +276,14 @@ describe('Auth routes', () => {
jest.spyOn(emailService.transport, 'sendMail').mockResolvedValue();
});
test('should return 204 and send reset password email to the user', async () => {
test('should return 204 and send reset key email to the user', async () => {
await insertUsers([userOne]);
const sendResetPasswordEmailSpy = jest.spyOn(emailService, 'sendResetPasswordEmail');
const sendResetKeyEmailSpy = jest.spyOn(emailService, 'sendResetKeyEmail');
await request(app).post('/v1/auth/lost-key').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
expect(sendResetPasswordEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetKeyToken = sendResetPasswordEmailSpy.mock.calls[0][1];
expect(sendResetKeyEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
const resetKeyToken = sendResetKeyEmailSpy.mock.calls[0][1];
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetKeyToken, user: userOne._id.toString() });
expect(dbResetPasswordTokenDoc).toBeDefined();
});
@@ -314,13 +302,15 @@ describe('Auth routes', () => {
});
describe('POST /v1/auth/reset-key', () => {
test('should return 204 and reset the password', async () => {
test('should return 204 and reset the key', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
const res = await request(app).post('/v1/auth/reset-key').send({ token: resetKeyToken, password: 'password2' });
const res = await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: userOne.public_key });
expect(res.status).toBe(httpStatus.NO_CONTENT);
@@ -328,9 +318,6 @@ describe('Auth routes', () => {
expect(dbUser).not.toBeUndefined();
if (dbUser) {
const isPasswordMatch = await bcrypt.compare('password2', dbUser.password);
expect(isPasswordMatch).toBe(true);
const dbResetPasswordTokenCount = await Token.countDocuments({
user: userOne._id.toString(),
type: tokenTypes.RESET_PASSWORD,
@@ -339,13 +326,16 @@ describe('Auth routes', () => {
}
});
test('should return 400 if reset password token is missing', async () => {
test('should return 400 if reset key token is missing', async () => {
await insertUsers([userOne]);
await request(app).post('/v1/auth/reset-key').send({ password: 'password2' }).expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.BAD_REQUEST);
});
test('should return 401 if reset password token is blacklisted', async () => {
test('should return 401 if reset key token is blacklisted', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
@@ -357,7 +347,7 @@ describe('Auth routes', () => {
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 401 if reset password token is expired', async () => {
test('should return 401 if reset key token is expired', async () => {
await insertUsers([userOne]);
const expires = moment().subtract(1, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
@@ -379,30 +369,6 @@ describe('Auth routes', () => {
.send({ token: resetKeyToken, public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV' })
.expect(httpStatus.UNAUTHORIZED);
});
test('should return 400 if password is missing or invalid', async () => {
await insertUsers([userOne]);
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetKeyToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
await tokenService.saveToken(resetKeyToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
await request(app).post('/v1/auth/reset-key').send({ token: resetKeyToken }).expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'stort' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: 'password' })
.expect(httpStatus.BAD_REQUEST);
await request(app)
.post('/v1/auth/reset-key')
.send({ token: resetKeyToken, public_key: '11111111' })
.expect(httpStatus.BAD_REQUEST);
});
});
describe('POST /v1/auth/send-verification-email', () => {
@@ -56,7 +56,7 @@ describe('Проверка получения документов', () => {
skip_save: false,
};
let res = await request(app)
const res = await request(app)
.post('/v1/documents/generate')
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
.send(options);
@@ -26,7 +26,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,7 +50,6 @@ describe('Проверка данных', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -67,7 +67,6 @@ describe('Проверка получения документов', () => {
const email = faker.internet.email().toLowerCase();
const newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -4,10 +4,10 @@ import app from '../../src/app';
import { setupTestDB } from '../utils/setupTestDB';
import { User } from '../../src/models';
import { userOne, userTwo, admin, insertUsers } from '../fixtures/user.fixture';
import { userOneAccessToken, adminAccessToken } from '../fixtures/token.fixture';
import { userOneAccessToken, adminAccessToken, userTwoAccessToken } from '../fixtures/token.fixture';
import request from 'supertest';
import { generateUsername } from '../utils/generateUsername';
import { IDocument, IIndividualData, IJoinCooperative } from '../../src/types';
import { IIndividualData } from '../../src/types';
setupTestDB();
@@ -19,7 +19,6 @@ describe('User routes', () => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
username: generateUsername(),
@@ -51,11 +50,9 @@ describe('User routes', () => {
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user).not.toHaveProperty('password');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(newUser.password);
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
@@ -65,23 +62,15 @@ describe('User routes', () => {
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: newUser.role, is_email_verified: false });
});
test('should be able to create an admin as well', async () => {
test('should not be able to create an admin as well', async () => {
await insertUsers([admin]);
newUser.role = 'member';
const res = await request(app)
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.tokens).toBeDefined();
expect(res.body.user.role).toBe('member');
const dbUser = await User.findOne({ username: newUser.username });
expect(dbUser?.role).toBe('member');
.expect(httpStatus.BAD_REQUEST);
});
test('успешная регистрация без токена доступа', async () => {
@@ -122,36 +111,6 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password length is less than 8 characters', async () => {
await insertUsers([admin]);
newUser.password = 'passwo1';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if password does not contain both letters and numbers', async () => {
await insertUsers([admin]);
newUser.password = 'password';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
newUser.password = '1111111';
await request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(newUser)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 error if role is neither user nor admin', async () => {
await insertUsers([admin]);
newUser.role = 'invalid';
@@ -164,6 +123,71 @@ describe('User routes', () => {
});
});
describe('POST /v1/users/add', () => {
let newUser;
beforeEach(() => {
const email = faker.internet.email().toLowerCase();
newUser = {
email: email,
referer: '',
type: 'individual',
individual_data: {
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
middle_name: '',
birthdate: '2023-04-01',
phone: '+1234567890',
email: email,
full_address: 'Russia, Moscow, Tverskaya street, 10',
},
};
});
test('should return 201 and successfully add new user if data is ok', async () => {
await insertUsers([admin]);
const res = await request(app).post('/v1/users/add').set('Authorization', `Bearer ${adminAccessToken}`).send(newUser);
// if (res.status !== httpStatus.CREATED) {
// console.error(res.body); // Выводит тело ответа, если статус не CREATED
// }
expect(res.status).toBe(httpStatus.CREATED);
expect(res.body.user).toBeDefined();
expect(res.body.user.username).toBeDefined();
const dbUser = await User.findOne({ username: res.body.user.username });
expect(dbUser).toBeDefined();
const privateData = (await dbUser?.getPrivateData()) as IIndividualData;
expect(privateData).toBeDefined();
expect(privateData.first_name).toEqual(newUser.individual_data?.first_name);
expect(privateData.last_name).toEqual(newUser.individual_data?.last_name);
expect(dbUser?.toObject()).toMatchObject({ email: newUser.email, role: 'user', is_email_verified: false });
});
test('should return 403 if service user is not admin but data new user is ok', async () => {
await insertUsers([userTwo]);
const res = await request(app)
.post('/v1/users/add')
.set('Authorization', `Bearer ${userTwoAccessToken}`)
.send(newUser);
expect(res.status).toBe(httpStatus.FORBIDDEN);
});
test('should return 401 if service user is not have access token', async () => {
const res = await request(app).post('/v1/users/add').send(newUser);
expect(res.status).toBe(httpStatus.UNAUTHORIZED);
});
});
describe('GET /v1/users', () => {
test('should return 200 and apply the default query options', async () => {
await insertUsers([userOne, userTwo, admin]);
@@ -513,7 +537,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -528,7 +551,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
const res = await request(app)
@@ -542,7 +564,6 @@ describe('User routes', () => {
const dbUser = await User.findById(userOne._id);
expect(dbUser).toBeDefined();
expect(dbUser?.password).not.toBe(updateBody.password);
expect(dbUser).toMatchObject({ email: updateBody.email, role: 'user' });
});
@@ -550,7 +571,6 @@ describe('User routes', () => {
await insertUsers([userOne]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app).patch(`/v1/users/${userOne.username}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
@@ -560,7 +580,6 @@ describe('User routes', () => {
await insertUsers([userOne, userTwo]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -574,7 +593,6 @@ describe('User routes', () => {
await insertUsers([userOne, admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -588,7 +606,6 @@ describe('User routes', () => {
await insertUsers([admin]);
const updateBody = {
email: faker.internet.email().toLowerCase(),
password: 'newPassword1',
};
await request(app)
@@ -600,7 +617,7 @@ describe('User routes', () => {
test('should return 400 if email is invalid', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: 'invalidEmail', password: 'GoodPassword!23' };
const updateBody = { email: 'invalidEmail' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -611,7 +628,7 @@ describe('User routes', () => {
test('should return 400 if email is already taken', async () => {
await insertUsers([userOne, userTwo, admin]);
const updateBody = { email: userTwo.email, password: 'GoodPassword!23' };
const updateBody = { email: userTwo.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -620,41 +637,9 @@ describe('User routes', () => {
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password length is less than 8 characters', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'passwo1' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
test('should return 400 if password does not contain both letters and numbers', async () => {
await insertUsers([userOne, admin]);
const updateBody = { password: 'password' };
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
updateBody.password = '11111111';
await request(app)
.patch(`/v1/users/${userOne.username}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(updateBody)
.expect(httpStatus.BAD_REQUEST);
});
//
test('should return 400 if email is my email then taken', async () => {
await insertUsers([userOne, admin]);
const updateBody = { email: userOne.email, password: 'veryStrongPassword!2' };
const updateBody = { email: userOne.email };
await request(app)
.patch(`/v1/users/${userOne.username}`)
@@ -10,7 +10,6 @@ describe('User model', () => {
newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
public_key: 'EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV',
type: 'individual',
@@ -27,36 +26,9 @@ describe('User model', () => {
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password length is less than 8 characters', async () => {
newUser.password = 'passwo1';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain numbers', async () => {
newUser.password = 'password';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if password does not contain letters', async () => {
newUser.password = '11111111';
await expect(new User(newUser).validate()).rejects.toThrow();
});
test('should throw a validation error if role is unknown', async () => {
newUser.role = 'invalid';
await expect(new User(newUser).validate()).rejects.toThrow();
});
});
describe('User toJSON()', () => {
test('should not return user password when toJSON is called', () => {
const newUser = {
name: faker.name.findName(),
email: faker.internet.email().toLowerCase(),
password: 'password1',
role: 'user',
};
expect(new User(newUser).toJSON()).not.toHaveProperty('password');
});
});
});
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.78](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.77...coopdoc-generator-ts@1.0.78) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.77](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.76...coopdoc-generator-ts@1.0.77) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.76](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.75...coopdoc-generator-ts@1.0.76) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.75](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.75-alpha.1...coopdoc-generator-ts@1.0.75) (2024-08-25)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.74](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73...coopdoc-generator-ts@1.0.74) (2024-08-13)
**Note:** Version bump only for package coopdoc-generator-ts
## [1.0.73](https://github.com/copenomics/coopdoc-generator-ts/compare/coopdoc-generator-ts@1.0.73-alpha.0...coopdoc-generator-ts@1.0.73) (2024-08-05)
**Note:** Version bump only for package coopdoc-generator-ts
+2 -3
View File
@@ -1,8 +1,8 @@
{
"name": "coopdoc-generator-ts",
"type": "module",
"version": "1.0.73",
"private": true,
"version": "1.0.78",
"private": false,
"packageManager": "pnpm@9.0.6",
"description": "",
"author": "Alex Ant <chairman.voskhod@gmail.com>",
@@ -49,7 +49,6 @@
"ajv-formats": "^3.0.1",
"ajv-i18n": "^4.2.0",
"axios": "^1.7.2",
"cooparser-ts": "workspace:*",
"cooptypes": "workspace:*",
"dotenv": "^16.4.5",
"eosjs-ecc": "^4.0.7",
@@ -1,2 +1,3 @@
export * as JoinCoop from './registrator.joincoop'
export * as JoinCoopDecision from './registrator.joincoopdec'
export * as JoinProgram from './soviet.joinprog'
@@ -0,0 +1,57 @@
import { DocFactory } from '../Factory'
import type { IGeneratedDocument, IMetaDocument } from '../Interfaces'
import type { MongoDBConnector } from '../Services/Databazor'
import { DocumentsRegistry } from '../Templates'
import type { IGenerateJoinProgram } from '../Interfaces/Actions'
import type { IJoinProgram } from '../Templates/1000.ProgramProvision'
export class JoinProgramTemplateFactory extends DocFactory {
constructor(storage: MongoDBConnector) {
super(storage)
}
async generateDocument(options: IGenerateJoinProgram): Promise<IGeneratedDocument> {
// TODO
/**
* Получаем шаблон программы по registry_id из options (registry_id)
* Получаем из бд парсера
*/
let template
if (process.env.SOURCE === 'local') {
template = DocumentsRegistry[options.registry_id as keyof typeof DocumentsRegistry]
}
else {
template = await this.getTemplate(options.registry_id, options.block_num)
}
const coop = await super.getCooperative(options.coopname, options.block_num)
const meta: IMetaDocument = await super.getMeta({
title: template.title,
...options,
}) // Генерируем мета-данные
const combinedData: IJoinProgram = {
meta,
coop,
protocol_number: options.protocol_number,
protocol_day_month_year: options.protocol_day_month_year,
}
// валидируем скомбинированные данные
await super.validate(combinedData, template.model)
// получаем комплекс перевода
const translation = template.translations[meta.lang]
// генерируем документ
const document: IGeneratedDocument = await super.generatePDF(null, template.context, combinedData, translation, meta)
// сохраняем его в бд
await super.saveDraft(document)
return document
}
}
@@ -170,7 +170,7 @@ export abstract class DocFactory {
}
async generatePDF(
data: externalDataTypes,
data: externalDataTypes | null,
context: string,
vars: ICombinedData,
translation: ITranslations,
@@ -185,13 +185,14 @@ export abstract class DocFactory {
return document
}
getFullName(data: externalDataTypes): string {
if ('first_name' in data)
return `${data.last_name} ${data.first_name} ${data.middle_name}`
if ('represented_by' in data)
return `${data.represented_by.last_name} ${data.represented_by.first_name} ${data.represented_by.middle_name}`
getFullName(data: externalDataTypes | null): string {
if (data) {
if ('first_name' in data)
return `${data.last_name} ${data.first_name} ${data.middle_name}`
if ('represented_by' in data)
return `${data.represented_by.last_name} ${data.represented_by.first_name} ${data.represented_by.middle_name}`
}
return ''
}
@@ -2,3 +2,4 @@ import type { Cooperative } from 'cooptypes'
export type IGenerateJoinCoopDecision = Cooperative.Documents.IGenerateJoinCoopDecision
export type IGenerateJoinCoop = Cooperative.Documents.IGenerateJoinCoop
export type IGenerateJoinProgram = Cooperative.Documents.IGenerateJoinProgram
@@ -25,7 +25,9 @@ class TransExtension {
args.forEach((value, index) => {
translation = translation.replace(new RegExp(`\\{${index}\\}`, 'g'), value)
})
return translation
translation = translation.replace(/\n/g, '<br>')
return new nunjucks.runtime.SafeString(translation as unknown as string)
}
}
File diff suppressed because one or more lines are too long
@@ -1,2 +1 @@
export * from './100.ParticipantApplication'
export * from './registry'
@@ -1,11 +1,15 @@
import * as ParticipantApplication from './100.ParticipantApplication'
import * as DecisionOfParticipantApplication from './501.DecisionOfParticipantApplication'
import * as ProgramProvision from './1000.ProgramProvision'
export const DocumentsRegistry = {
100: ParticipantApplication.ParticipantApplicationTemplate,
501: DecisionOfParticipantApplication.DecisionOfParticipantApplicationTemplate,
1000: ProgramProvision.JoinProgramTemplate,
}
export interface DocumentsMappingByActionAndCode {
'registrator::joincoop': ParticipantApplication.IJoinCoopAction // Тип данных для документа 'registrator::joincoop'
'registrator::joincoopdec': DecisionOfParticipantApplication.IJoinCoopDecisionAction // Тип данных для документа 'registrator::joincoopdec'
'soviet::joinprog': ProgramProvision.IJoinProgram
}
+2 -1
View File
@@ -5,7 +5,7 @@ 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 { JoinCoop, JoinCoopDecision, JoinProgram } from './Actions'
import { MongoDBConnector } from './Services/Databazor'
import type { ExternalIndividualData } from './Models/Individual'
import { Individual } from './Models/Individual'
@@ -57,6 +57,7 @@ export class Generator implements IGenerator {
this.factories = {
'registrator::joincoop': new JoinCoop.JoinCoopTemplateFactory(this.storage),
'registrator::joincoopdec': new JoinCoopDecision.DecisionOfJoinCoopTemplateFactory(this.storage),
'soviet::joinprog': new JoinProgram.JoinProgramTemplateFactory(this.storage),
}
await this.storage.connect()
}
+219 -174
View File
@@ -1,6 +1,6 @@
import { beforeAll, beforeEach, describe, expect, it } from 'vitest'
import type { RegistratorContract, SovietContract } from 'cooptypes'
import { Generator } from '../src'
import { Generator, type IGenerateJoinProgram } from '../src'
import type { IGeneratedDocument } from '../src/Interfaces/Documents'
import { saveBufferToDisk } from '../src/Utils/saveBufferToDisk'
import { loadBufferFromDisk } from '../src/Utils/loadBufferFromDisk'
@@ -298,15 +298,19 @@ describe('тест генератора документов', async () => {
}
})
it('генерируем заявление на вступление физического лица', async () => {
const document: IGeneratedDocument = await generator.generate({
code: 'registrator',
action: 'joincoop',
it('генерируем заявление на присоединение к ЦПП', async () => {
const params: IGenerateJoinProgram = {
code: 'soviet',
action: 'joinprog',
coopname: 'voskhod',
username: 'ant',
lang: 'ru',
signature: signatureExample,
})
registry_id: 1000,
protocol_number: '01-01-2024',
protocol_day_month_year: '1 января 2024 г.',
}
const document: IGeneratedDocument = await generator.generate(params)
const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(document.binary, filename1)
@@ -314,6 +318,7 @@ describe('тест генератора документов', async () => {
const regenerated_document: IGeneratedDocument = await generator.generate({
...document.meta,
})
const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(regenerated_document.binary, filename2)
@@ -331,205 +336,245 @@ describe('тест генератора документов', async () => {
expect(getted_document).toBeDefined()
expect(getted_document.hash).toEqual(document.hash)
// console.log('hash1: ', hash1)
// console.log('hash2: ', hash2)
console.log('hash1: ', hash1)
console.log('hash2: ', hash2)
// console.log(document)
expect(hash1).toEqual(hash2)
const decision_document: IGeneratedDocument = await generator.generate({
code: 'registrator',
action: 'joincoopdec',
coopname: 'voskhod',
username: 'ant',
lang: 'ru',
decision_id: 1,
})
const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
await saveBufferToDisk(decision_document.binary, filename3)
})
it('сохранение данных организации', async () => {
const organizationData: ExternalOrganizationData = {
username: 'exampleorg',
type: 'ooo',
is_cooperative: false,
short_name: 'ExampleOrg',
full_name: 'Примерная организация',
represented_by: {
first_name: 'Иван',
last_name: 'Иванов',
middle_name: 'Иванович',
position: 'Директор',
based_on: 'Устава организации',
},
country: 'Russia',
city: 'Moscow',
full_address: '456 Main St, Moscow, Russia',
email: 'contact@exampleorg.com',
phone: '+71234567890',
details: {
inn: '0987654321',
ogrn: '0987654321098',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '0987654321098765',
bank_name: 'Example Bank',
details: {
bik: '098765432',
corr: '30101810400000000225',
kpp: '098765432',
},
},
}
// it('генерируем заявление на вступление физического лица', async () => {
// const document: IGeneratedDocument = await generator.generate({
// code: 'registrator',
// action: 'joincoop',
// coopname: 'voskhod',
// username: 'ant',
// lang: 'ru',
// signature: signatureExample,
// })
const saved = await generator.save('organization', organizationData)
// const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
// await saveBufferToDisk(document.binary, filename1)
const organization = await generator.get('organization', { username: organizationData.username }) as any
// const regenerated_document: IGeneratedDocument = await generator.generate({
// ...document.meta,
// })
// const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
// await saveBufferToDisk(regenerated_document.binary, filename2)
expect(organization._id).toEqual(saved.insertedId)
// expect(document.meta).toEqual(regenerated_document.meta)
// expect(document.hash).toEqual(regenerated_document.hash)
Object.keys(organizationData).forEach((field) => {
expect(organization[field]).toBeDefined()
})
})
// const document_from_disk1 = await loadBufferFromDisk(filename1)
// const document_from_disk2 = await loadBufferFromDisk(filename2)
it('генерируем заявление на вступление юридического лица', async () => {
const document: IGeneratedDocument = await generator.generate({
code: 'registrator',
action: 'joincoop',
coopname: 'voskhod',
username: 'exampleorg',
lang: 'ru',
signature: signatureExample,
})
// const hash1 = calculateSha256(document_from_disk1)
// const hash2 = calculateSha256(document_from_disk2)
const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(document.binary, filename1)
// const getted_document = await generator.getDocument({ hash: regenerated_document.hash })
const regenerated_document: IGeneratedDocument = await generator.generate({
...document.meta,
})
// expect(getted_document).toBeDefined()
// expect(getted_document.hash).toEqual(document.hash)
const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(regenerated_document.binary, filename2)
// // console.log('hash1: ', hash1)
// // console.log('hash2: ', hash2)
// // console.log(document)
expect(document.meta).toEqual(regenerated_document.meta)
expect(document.hash).toEqual(regenerated_document.hash)
// expect(hash1).toEqual(hash2)
const document_from_disk1 = await loadBufferFromDisk(filename1)
const document_from_disk2 = await loadBufferFromDisk(filename2)
// const decision_document: IGeneratedDocument = await generator.generate({
// code: 'registrator',
// action: 'joincoopdec',
// coopname: 'voskhod',
// username: 'ant',
// lang: 'ru',
// decision_id: 1,
// })
const hash1 = calculateSha256(document_from_disk1)
const hash2 = calculateSha256(document_from_disk2)
// const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
// await saveBufferToDisk(decision_document.binary, filename3)
// })
// console.log('hash1: ', hash1)
// console.log('hash2: ', hash2)
// console.log(document)
// it('сохранение данных организации', async () => {
// const organizationData: ExternalOrganizationData = {
// username: 'exampleorg',
// type: 'ooo',
// is_cooperative: false,
// short_name: 'ExampleOrg',
// full_name: 'Примерная организация',
// represented_by: {
// first_name: 'Иван',
// last_name: 'Иванов',
// middle_name: 'Иванович',
// position: 'Директор',
// based_on: 'Устава организации',
// },
// country: 'Russia',
// city: 'Moscow',
// full_address: '456 Main St, Moscow, Russia',
// email: 'contact@exampleorg.com',
// phone: '+71234567890',
// details: {
// inn: '0987654321',
// ogrn: '0987654321098',
// },
// bank_account: {
// account_number: '40817810099910004312',
// currency: 'RUB',
// card_number: '0987654321098765',
// bank_name: 'Example Bank',
// details: {
// bik: '098765432',
// corr: '30101810400000000225',
// kpp: '098765432',
// },
// },
// }
expect(hash1).toEqual(hash2)
// const saved = await generator.save('organization', organizationData)
const decision_document: IGeneratedDocument = await generator.generate({
code: 'registrator',
action: 'joincoopdec',
coopname: 'voskhod',
username: 'exampleorg',
lang: 'ru',
decision_id: 2,
})
// const organization = await generator.get('organization', { username: organizationData.username }) as any
const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
await saveBufferToDisk(decision_document.binary, filename3)
})
// expect(organization._id).toEqual(saved.insertedId)
it('сохранение данных индивидуального предпринимателя', async () => {
const entrepreneurData: ExternalEntrepreneurData = {
username: 'entrepreneur',
first_name: 'John',
last_name: 'Doe',
middle_name: 'Middle',
birthdate: '2023-04-01',
phone: '+1234567890',
email: 'john.doe@example.com',
full_address: 'переулок правды д. 1',
country: 'Russia',
city: 'Moscow',
details: {
inn: '0987654321',
ogrn: '0987654321098',
},
bank_account: {
account_number: '40817810099910004312',
currency: 'RUB',
card_number: '0987654321098765',
bank_name: 'Example Bank',
details: {
bik: '098765432',
corr: '30101810400000000225',
kpp: '098765432',
},
},
}
// Object.keys(organizationData).forEach((field) => {
// expect(organization[field]).toBeDefined()
// })
// })
const saved = await generator.save('entrepreneur', entrepreneurData)
console.log(saved)
const entrepreneur = await generator.get('entrepreneur', { username: entrepreneurData.username }) as any
console.log(entrepreneur)
expect(entrepreneur._id).toEqual(saved.insertedId)
// it('генерируем заявление на вступление юридического лица', async () => {
// const document: IGeneratedDocument = await generator.generate({
// code: 'registrator',
// action: 'joincoop',
// coopname: 'voskhod',
// username: 'exampleorg',
// lang: 'ru',
// signature: signatureExample,
// })
Object.keys(entrepreneurData).forEach((field) => {
expect(entrepreneur[field]).toBeDefined()
})
})
// const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
// await saveBufferToDisk(document.binary, filename1)
it('генерируем заявление на вступление индивидуального предпринимателя', async () => {
const document: IGeneratedDocument = await generator.generate({
code: 'registrator',
action: 'joincoop',
coopname: 'voskhod',
username: 'entrepreneur',
lang: 'ru',
signature: signatureExample,
})
// const regenerated_document: IGeneratedDocument = await generator.generate({
// ...document.meta,
// })
const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(document.binary, filename1)
// const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
// await saveBufferToDisk(regenerated_document.binary, filename2)
const regenerated_document: IGeneratedDocument = await generator.generate({
...document.meta,
})
// expect(document.meta).toEqual(regenerated_document.meta)
// expect(document.hash).toEqual(regenerated_document.hash)
const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
await saveBufferToDisk(regenerated_document.binary, filename2)
// const document_from_disk1 = await loadBufferFromDisk(filename1)
// const document_from_disk2 = await loadBufferFromDisk(filename2)
expect(document.meta).toEqual(regenerated_document.meta)
expect(document.hash).toEqual(regenerated_document.hash)
// const hash1 = calculateSha256(document_from_disk1)
// const hash2 = calculateSha256(document_from_disk2)
const document_from_disk1 = await loadBufferFromDisk(filename1)
const document_from_disk2 = await loadBufferFromDisk(filename2)
// // console.log('hash1: ', hash1)
// // console.log('hash2: ', hash2)
// // console.log(document)
const hash1 = calculateSha256(document_from_disk1)
const hash2 = calculateSha256(document_from_disk2)
// expect(hash1).toEqual(hash2)
// console.log('hash1: ', hash1)
// console.log('hash2: ', hash2)
// console.log(document)
// const decision_document: IGeneratedDocument = await generator.generate({
// code: 'registrator',
// action: 'joincoopdec',
// coopname: 'voskhod',
// username: 'exampleorg',
// lang: 'ru',
// decision_id: 2,
// })
expect(hash1).toEqual(hash2)
// const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
// await saveBufferToDisk(decision_document.binary, filename3)
// })
const decision_document: IGeneratedDocument = await generator.generate({
code: 'registrator',
action: 'joincoopdec',
coopname: 'voskhod',
username: 'entrepreneur',
lang: 'ru',
decision_id: 3,
})
// it('сохранение данных индивидуального предпринимателя', async () => {
// const entrepreneurData: ExternalEntrepreneurData = {
// username: 'entrepreneur',
// first_name: 'John',
// last_name: 'Doe',
// middle_name: 'Middle',
// birthdate: '2023-04-01',
// phone: '+1234567890',
// email: 'john.doe@example.com',
// full_address: 'переулок правды д. 1',
// country: 'Russia',
// city: 'Moscow',
// details: {
// inn: '0987654321',
// ogrn: '0987654321098',
// },
// bank_account: {
// account_number: '40817810099910004312',
// currency: 'RUB',
// card_number: '0987654321098765',
// bank_name: 'Example Bank',
// details: {
// bik: '098765432',
// corr: '30101810400000000225',
// kpp: '098765432',
// },
// },
// }
const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
await saveBufferToDisk(decision_document.binary, filename3)
})
// const saved = await generator.save('entrepreneur', entrepreneurData)
// 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(entrepreneur[field]).toBeDefined()
// })
// })
// it('генерируем заявление на вступление индивидуального предпринимателя', async () => {
// const document: IGeneratedDocument = await generator.generate({
// code: 'registrator',
// action: 'joincoop',
// coopname: 'voskhod',
// username: 'entrepreneur',
// lang: 'ru',
// signature: signatureExample,
// })
// const filename1 = `${document.meta.title}-${document.meta.username}.pdf`
// await saveBufferToDisk(document.binary, filename1)
// const regenerated_document: IGeneratedDocument = await generator.generate({
// ...document.meta,
// })
// const filename2 = `regenerated-${document.meta.title}-${document.meta.username}.pdf`
// await saveBufferToDisk(regenerated_document.binary, filename2)
// expect(document.meta).toEqual(regenerated_document.meta)
// expect(document.hash).toEqual(regenerated_document.hash)
// const document_from_disk1 = await loadBufferFromDisk(filename1)
// const document_from_disk2 = await loadBufferFromDisk(filename2)
// const hash1 = calculateSha256(document_from_disk1)
// const hash2 = calculateSha256(document_from_disk2)
// // console.log('hash1: ', hash1)
// // console.log('hash2: ', hash2)
// // console.log(document)
// expect(hash1).toEqual(hash2)
// const decision_document: IGeneratedDocument = await generator.generate({
// code: 'registrator',
// action: 'joincoopdec',
// coopname: 'voskhod',
// username: 'entrepreneur',
// lang: 'ru',
// decision_id: 3,
// })
// const filename3 = `${decision_document.meta.title}-${decision_document.meta.username}.pdf`
// await saveBufferToDisk(decision_document.binary, filename3)
// })
})
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.21](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.20...cooptypes@1.0.21) (2024-08-25)
**Note:** Version bump only for package cooptypes
## [1.0.20](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.20-alpha.1...cooptypes@1.0.20) (2024-08-25)
**Note:** Version bump only for package cooptypes
## [1.0.19](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.18...cooptypes@1.0.19) (2024-08-13)
**Note:** Version bump only for package cooptypes
## [1.0.18](https://github.com/coopenomics/cooptypes/compare/cooptypes@1.0.18-alpha.0...cooptypes@1.0.18) (2024-08-05)
**Note:** Version bump only for package cooptypes
+4 -4
View File
@@ -68,7 +68,7 @@ api.getTableRows(
})
```
Те же параметры scope, table, code могут быть использованы для получения информации из модуля [COOPARSER](https://github.com/copenomics/cooparser). Последнее используется, когда необходимо получить исторические данные и нет необходимости проверять их актуальность по наличию таблиц в блокчейне. Однако, таблицы в блокчейне необходимо всегда проверять перед отправкой любой транзакции действия. Нельзя полагаться на данные из парсера при подготовке транзакции действия.
Те же параметры scope, table, code могут быть использованы для получения информации из модуля [COOPARSER](https://github.com/coopenomics/cooparser). Последнее используется, когда необходимо получить исторические данные и нет необходимости проверять их актуальность по наличию таблиц в блокчейне. Однако, таблицы в блокчейне необходимо всегда проверять перед отправкой любой транзакции действия. Нельзя полагаться на данные из парсера при подготовке транзакции действия.
### Транзакция действий
Состояние любого смарт-контракта может изменяться только с помощью действий. Для того, чтобы совершить действия, необходимо сформировать и отправить транзакцию с помощью библиотеки eosjs или альтернатив.
@@ -113,7 +113,7 @@ const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), te
## Лицензия
[MIT](./LICENSE) License © 2024-PRESENT [CBS VOSKHOD](https://github.com/copenomics)
[MIT](./LICENSE) License © 2024-PRESENT [CBS VOSKHOD](https://github.com/coopenomics)
<!-- Badges -->
@@ -123,7 +123,7 @@ const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), te
[npm-downloads-href]: https://npmjs.com/package/cooptypes
[bundle-src]: https://img.shields.io/bundlephobia/minzip/cooptypes?style=flat&colorA=080f12&colorB=1fa669&label=minzip
[bundle-href]: https://bundlephobia.com/result?p=cooptypes
[license-src]: https://img.shields.io/github/license/copenomics/cooptypes.svg?style=flat&colorA=080f12&colorB=1fa669
[license-href]: https://github.com/copenomics/cooptypes/blob/main/LICENSE
[license-src]: https://img.shields.io/github/license/coopenomics/cooptypes.svg?style=flat&colorA=080f12&colorB=1fa669
[license-href]: https://github.com/coopenomics/cooptypes/blob/main/LICENSE
[jsdocs-src]: https://img.shields.io/badge/jsdocs-reference-080f12?style=flat&colorA=080f12&colorB=1fa669
[jsdocs-href]: https://www.jsdocs.io/package/cooptypes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "cooptypes",
"type": "module",
"version": "1.0.18",
"version": "1.0.21",
"description": "_description_",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
"license": "MIT",
@@ -4,7 +4,7 @@ import * as ContractNames from '../../../common/names'
/**
* Имя таблицы
*/
export const tableName = 'fundwallet'
export const tableName = 'coopwallet'
/**
* Таблица хранится в {@link ContractNames._fund | области памяти контракта}.
@@ -14,4 +14,4 @@ export const scope = ContractNames._fund
/**
* @interface
*/
export type IFundWallet = Fund.IFundwallet
export type ICoopWallet = Fund.ICoopwallet
@@ -12,7 +12,7 @@ export * as ExpensedFunds from './expenseFunds'
/**
* Таблица содержит сводную информацию о балансе кошелька всех фондов кооператива.
*/
export * as FundWallet from './fundWallet'
export * as CoopWallet from './coopWallet'
/**
* Таблица содержит информацию о запросах на списание по статьям и выводах из фондов.
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'adduser'
/**
* @interface
*/
export type IAddUser = Registrator.IAdduser
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'delcoop'
/**
* @interface
*/
export type IDeleteCooperative = Registrator.IDelcoop
@@ -49,3 +49,21 @@ export * as ConfirmJoin from './confirmJoin'
* @private
*/
export * as Init from './init'
/**
* Действие добавления пайщика в обход процедуры регистрации.
* @private
*/
export * as AddUser from './addUser'
/**
* Действие изменения статуса подключенного кооператива.
* @private
*/
export * as SetCoopStatus from './setCoopStatus'
/**
* Удаление кооператива из реестра подключений
* @private
*/
export * as DeleteCooperative from './deleteCooperative'
@@ -0,0 +1,15 @@
import * as Permissions from '../../../common/permissions'
import type * as Registrator from '../../../interfaces/registrator'
import { Actors } from '../../../common'
export const authorizations = [{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin }] as const
/**
* Имя действия
*/
export const actionName = 'stcoopstatus'
/**
* @interface
*/
export type ISetCoopStatus = Registrator.IStcoopstatus
@@ -0,0 +1,21 @@
import * as Permissions from '../../../../common/permissions'
import type * as Soviet from '../../../../interfaces/soviet'
import { Actors } from '../../../../common'
/**
* Действие выполняется за подписью {@link Actors._admin | администратора}.
*/
export const authorizations = [
{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin },
] as const
/**
* Имя действия
*/
export const actionName = 'cnfrmindoc'
/**
* @interface
* Действие подтверждает приём заявления администратором
*/
export type IConfirmIncomeDocument = Soviet.ICnfrmindoc
@@ -0,0 +1,21 @@
import * as Permissions from '../../../../common/permissions'
import type * as Soviet from '../../../../interfaces/soviet'
import { Actors } from '../../../../common'
/**
* Действие выполняется за подписью {@link Actors._admin | администратора}.
*/
export const authorizations = [
{ permissions: [Permissions.active, Permissions.special], actor: Actors._admin },
] as const
/**
* Имя действия
*/
export const actionName = 'declnindoc'
/**
* @interface
* Действие отклоняет заявление пайщика администратором
*/
export type IDeclineIncomeDocument = Soviet.IDeclnindoc
@@ -23,3 +23,13 @@ export * as SetRights from './setRights'
* @private
*/
export * as Init from './init'
/**
* Действие подтверждения приёма заявления пайщика
*/
export * as ConfirmIncomeDocument from './confirmIncomeDocument'
/**
* Действие отклонения приёма заявления пайщика
*/
export * as DeclineIncomeDocument from './declineIncomeDocument'
@@ -16,6 +16,6 @@ export const actionName = 'createprog'
/**
* @interface
* Действие редактирует целевую потребительскую программу маркетплейса.
* Действие редактирует целевую потребительскую программу.
*/
export type ICreateMarketProgram = Soviet.ICreateprog
export type ICreateProgram = Soviet.ICreateprog
@@ -1,14 +1,19 @@
/**
* Действие создаёт целевую потребительскую программу маркетплейса.
* Действие создаёт целевую потребительскую маркетплейса.
*/
export * as CreateMarketProgram from './createMarketProgram'
export * as CreateProgram from './createProgram'
/**
* Действие редактирует целевую потребительскую программу маркетплейса.
*/
export * as EditMarketProgram from './editMarketProgram'
export * as EditProgram from './editProgram'
/**
* Действие отключает целевую потребительскую программу маркетплейса.
*/
export * as DisableMarketProgram from './disableMarketProgram'
export * as DisableProgram from './disableProgram'
/**
* Действие отправки заявления на присоединение к ЦПП Кооператива
*/
export * as JoinProgram from './joinProgram'
@@ -0,0 +1,20 @@
import * as Permissions from '../../../../common/permissions'
import type * as Soviet from '../../../../interfaces/soviet'
import { Actors } from '../../../../common'
/**
* Действие выполняется за подписью любого {@link Actors._username | пользователя}.
*/
export const authorizations = [
{ permissions: [Permissions.active], actor: Actors._username },
] as const
/**
* Имя действия
*/
export const actionName = 'joinprog'
/**
* Действие отправки заявления на присоединение к ЦПП кооператива
*/
export type IJoinProgram = Soviet.IJoinprog
@@ -0,0 +1,17 @@
import type * as Soviet from '../../../interfaces/soviet'
import { Actors } from '../../../common'
/**
* Имя таблицы
*/
export const tableName = 'indocuments'
/**
* Таблица хранится в {@link Actors._coopname | области памяти кооператива}.
*/
export const scope = Actors._coopname
/**
* @interface
*/
export type IIncomeDocument = Soviet.IIndocument
@@ -39,9 +39,9 @@ export * as Decisions from './decisions'
export * as JoinCoops from './joinCoops'
/**
* Таблица хранит целевые потребительские программы контракта маркетплейса.
* Таблица хранит целевые потребительские программы.
*/
export * as MarketPrograms from './marketPrograms'
export * as Programs from './programs'
/**
* Таблица хранит информацию о пайщиках.
@@ -57,3 +57,8 @@ export * as ProgramWallets from './programWallets'
* Таблица содержит сводный баланс пайщиков кооператива.
*/
export * as Wallets from './wallets'
/**
* Таблица содержит заявления пайщиков кооператива, которые ожидают приёма, но не требуют решения совета.
*/
export * as IncomeDocuments from './incomeDocuments'
@@ -15,4 +15,4 @@ export const scope = Actors._coopname
* @interface
* Таблица хранит публичную часть реестра пайщиков кооператива.
*/
export type IParticipants = Soviet.IParticipants
export type IParticipants = Soviet.IParticipant
@@ -4,7 +4,7 @@ import { Actors } from '../../../common'
/**
* Имя таблицы
*/
export const tableName = 'progcomarket'
export const tableName = 'programs'
/**
* Таблица хранится в {@link Actors._coopname | области памяти кооператива}.
@@ -15,4 +15,4 @@ export const scope = Actors._coopname
* @interface
* Таблица хранит целевые потребительские программы контракта маркетплейса.
*/
export type IMarketPrograms = Soviet.IProgcomarket
export type IProgram = Soviet.IProgram
@@ -72,6 +72,14 @@ export interface IComplexAgenda extends IAgenda {
documents: IComplexDocument
}
export interface IGetResponse<T> {
results: T[]
page: number
limit: number
totalResults: number
totalPages: number
}
/**
* Общий интерфейс для генерации/регенерации документа
*/
@@ -92,10 +100,9 @@ export interface IGenerateJoinCoopDecision extends IGenerate {
decision_id: number
}
export interface IGetResponse<T> {
results: T[]
page: number
limit: number
totalResults: number
totalPages: number
/** Интерфейс генерации документа для присоединения к программе ЦПП */
export interface IGenerateJoinProgram extends IGenerate {
registry_id: number
protocol_number: string
protocol_day_month_year: string
}
@@ -13,6 +13,8 @@ export interface ICountsBase {
}
export interface ICreatedraft {
scope: IName
username: IName
registry_id: IUint64
lang: IName
title: string
@@ -23,25 +25,37 @@ export interface ICreatedraft {
}
export interface ICreatetrans {
scope: IName
username: IName
draft_id: IUint64
lang: IName
data: string
}
export interface IDeldraft {
scope: IName
username: IName
draft_id: IUint64
}
export interface IDeltrans {
scope: IName
username: IName
translate_id: IUint64
}
export interface IEdittrans {
scope: IName
username: IName
translate_id: IUint64
data: string
}
export interface IMigrate {
}
export interface INewid {
scope: IName
id: IUint64
}
@@ -36,6 +36,11 @@ export interface IAddexpense {
quantity: IAsset
}
export interface IAddinitial {
coopname: IName
quantity: IAsset
}
export interface IAuthorize {
coopname: IName
type: IName
@@ -48,6 +53,15 @@ export interface IComplete {
withdraw_id: IUint64
}
export interface ICoopwallet {
id: IUint64
coopname: IName
circulating_account: ISimpleWallet
initial_account: ISimpleWallet
accumulative_account: ISimpleWallet
accumulative_expense_account: ISimpleWallet
}
export interface ICounts extends ICountsBase {
}
@@ -140,6 +154,9 @@ export interface IInit {
initial: IAsset
}
export interface IMigrate {
}
export interface INewfund {
coopname: IName
type: IName
@@ -152,6 +169,11 @@ export interface INewwithdraw {
id: IUint64
}
export interface ISimpleWallet {
available: IAsset
withdrawed: IAsset
}
export interface ISpreadamount {
coopname: IName
quantity: IAsset
@@ -20,6 +20,19 @@ export interface IAccount {
registered_at: ITimePointSec
}
export interface IAdduser {
registrator: IName
coopname: IName
referer: IName
username: IName
type: IName
created_at: ITimePointSec
initial: IAsset
minimum: IAsset
spread_initial: boolean
meta: string
}
export interface IBalances extends IBalancesBase {
}
@@ -36,12 +49,6 @@ export interface IChangekey {
public_key: IPublicKey
}
export interface ICheck {
hash: IChecksum256
public_key: IPublicKey
signature: ISignature
}
export interface IConfirmreg {
coopname: IName
username: IName
@@ -62,6 +69,13 @@ export interface ICooperative {
org_registration: IAsset
org_initial: IAsset
org_minimum: IAsset
status: IName
created_at: ITimePointSec
}
export interface IDelcoop {
registrator: IName
coopname: IName
}
export interface IDocument {
@@ -71,9 +85,6 @@ export interface IDocument {
meta: string
}
export interface IFix {
}
export interface IInit {
}
@@ -84,6 +95,9 @@ export interface IJoincoop {
document: IDocument
}
export interface IMigrate {
}
export interface INewaccount {
registrator: IName
coopname: IName
@@ -107,7 +121,6 @@ export interface IOrgData {
export interface IRegcoop {
registrator: IName
coopname: IName
username: IName
params: IOrgData
}
@@ -118,6 +131,12 @@ export interface IReguser {
type: IName
}
export interface IStcoopstatus {
coopname: IName
username: IName
status: IName
}
export interface IUpdateaccnt {
username: IName
account_to_change: IName
+94 -10
View File
@@ -24,6 +24,7 @@ export interface IAddprogbal {
export interface IAddress {
id: IUint64
coopname: IName
braname: IName
data: IAddressData
}
@@ -54,6 +55,16 @@ export interface IAddstaff {
position_title: string
}
export interface IAdduser {
coopname: IName
username: IName
type: IName
created_at: ITimePointSec
initial: IAsset
minimum: IAsset
spread_initial: boolean
}
export interface IAuthorize {
coopname: IName
chairman: IName
@@ -129,6 +140,13 @@ export interface IChanges {
return_product_decision_id: IUint64
}
export interface ICnfrmindoc {
coopname: IName
registrator: IName
username: IName
indoc_id: IUint64
}
export interface ICounts extends ICountsBase {
}
@@ -141,21 +159,35 @@ export interface ICountsBase {
export interface ICreaddress {
coopname: IName
chairman: IName
braname: IName
data: IAddressData
}
export interface ICreateboard {
coopname: IName
chairman: IName
username: IName
type: IName
members: IBoardMember[]
name: string
description: string
}
export interface ICreateprog {
export interface ICreatebranch {
coopname: IName
chairman: IName
braname: IName
name: string
description: string
authorizer: IName
trusted: IName[]
}
export interface ICreateprog {
coopname: IName
username: IName
program_type: IName
draft_registry_id: IUint64
hash_of_conditions: IChecksum256
title: string
announce: string
description: string
@@ -164,6 +196,7 @@ export interface ICreateprog {
calculation_type: IName
fixed_membership_contribution: IAsset
membership_percent_fee: IUint64
meta: string
}
export interface IDecision {
@@ -183,15 +216,29 @@ export interface IDecision {
created_at: ITimePointSec
}
export interface IDeclnindoc {
coopname: IName
registrator: IName
username: IName
indoc_id: IUint64
comment: string
}
export interface IDeladdress {
coopname: IName
chairman: IName
address_id: IUint64
}
export interface IDeletebranch {
coopname: IName
chairman: IName
braname: IName
}
export interface IDisableprog {
coopname: IName
id: IUint64
program_id: IUint64
}
export interface IDisautomate {
@@ -211,18 +258,33 @@ export interface IDocument {
export interface IEditaddress {
coopname: IName
chairman: IName
braname: IName
address_id: IUint64
data: IAddressData
}
export interface IEditbranch {
coopname: IName
chairman: IName
braname: IName
name: string
description: string
authorizer: IName
trusted: IName[]
}
export interface IEditprog {
coopname: IName
id: IUint64
username: IName
program_id: IUint64
draft_registry_id: IUint64
hash_of_conditions: IChecksum256
title: string
announce: string
description: string
preview: string
images: string
meta: string
}
export interface IExec {
@@ -231,9 +293,6 @@ export interface IExec {
decision_id: IUint64
}
export interface IFix {
}
export interface IFundwithdraw {
coopname: IName
username: IName
@@ -242,6 +301,15 @@ export interface IFundwithdraw {
document: IDocument
}
export interface IIndocument {
id: IUint64
coopname: IName
username: IName
type: IName
secondary_id: IUint64
document: IDocument
}
export interface IInit {
}
@@ -258,6 +326,16 @@ export interface IJoincoops {
notice: string
}
export interface IJoinprog {
coopname: IName
username: IName
document: IDocument
program_id: IUint64
}
export interface IMigrate {
}
export interface INewact {
coopname: IName
username: IName
@@ -309,7 +387,7 @@ export interface IOnewallet {
minimum: IAsset
}
export interface IParticipants {
export interface IParticipant {
username: IName
created_at: ITimePointSec
last_update: ITimePointSec
@@ -321,9 +399,12 @@ export interface IParticipants {
type: IName
}
export interface IProgcomarket {
export interface IProgram {
id: IUint64
coopname: IName
program_type: IName
draft_registry_id: IUint64
hash_of_conditions: IChecksum256
is_active: boolean
title: string
announce: string
@@ -334,6 +415,8 @@ export interface IProgcomarket {
calculation_type: IName
membership_percent_fee: IUint64
fixed_membership_contribution: IAsset
start_at: ITimePointSec
expired_at: ITimePointSec
}
export interface IProgwallet {
@@ -342,6 +425,7 @@ export interface IProgwallet {
program_id: IUint64
username: IName
available: IAsset
document: IDocument
}
export interface IRecieved {
@@ -402,7 +486,7 @@ export interface IUnblockbal {
export interface IUpdateboard {
coopname: IName
chairman: IName
username: IName
board_id: IUint64
members: IBoardMember[]
name: string
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.0.25](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.24...coop-notificator@0.0.25) (2024-08-25)
**Note:** Version bump only for package coop-notificator
## [0.0.24](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.24-alpha.1...coop-notificator@0.0.24) (2024-08-25)
**Note:** Version bump only for package coop-notificator
## [0.0.23](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.22...coop-notificator@0.0.23) (2024-08-13)
**Note:** Version bump only for package coop-notificator
## [0.0.22](https://github.com/copenomics/coop-notificator/compare/coop-notificator@0.0.22-alpha.0...coop-notificator@0.0.22) (2024-08-05)
**Note:** Version bump only for package coop-notificator
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "coop-notificator",
"type": "module",
"version": "0.0.22",
"version": "0.0.25",
"private": true,
"packageManager": "pnpm@9.1.1",
"description": "_description_",
+3 -2
View File
@@ -2,12 +2,13 @@
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.tabCompletion": "onlySnippets",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"eslint.validate": ["javascript", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib",
"i18n-ally.localesPaths": ["src/i18n"],
"typescript.format.enable": true,
"typescript.format.indentSwitchCase": false,
"typescript.validate.enable": true
"typescript.validate.enable": true,
}
+40
View File
@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.1.38](https://github.com/coopenomics/monocoop/compare/terminal@0.1.37...terminal@0.1.38) (2024-08-25)
**Note:** Version bump only for package terminal
## [0.1.37](https://github.com/coopenomics/monocoop/compare/terminal@0.1.36...terminal@0.1.37) (2024-08-25)
**Note:** Version bump only for package terminal
## [0.1.36](https://github.com/coopenomics/monocoop/compare/terminal@0.1.35...terminal@0.1.36) (2024-08-25)
**Note:** Version bump only for package terminal
## [0.1.35](https://github.com/coopenomics/monocoop/compare/terminal@0.1.35-alpha.1...terminal@0.1.35) (2024-08-25)
**Note:** Version bump only for package terminal
## [0.1.34](https://github.com/coopenomics/monocoop/compare/terminal@0.1.33...terminal@0.1.34) (2024-08-13)
**Note:** Version bump only for package terminal
## [0.1.33](https://github.com/coopenomics/monocoop/compare/terminal@0.1.33-alpha.0...terminal@0.1.33) (2024-08-05)
**Note:** Version bump only for package terminal
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "terminal",
"version": "0.1.33",
"version": "0.1.38",
"description": "A Terminal Project",
"productName": "Terminal App",
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
+5 -1
View File
@@ -1,5 +1,7 @@
<template lang='pug'>
router-view(v-if="isLoaded")
div
router-view(v-if="isLoaded")
RootPopup
</template>
<script setup lang="ts">
@@ -13,6 +15,8 @@ import { Cookies, LocalStorage, QSpinner, useQuasar } from 'quasar';
import { COOPNAME } from 'src/shared/config';
import { useCardStore } from './providers/card/store';
import { RootPopup } from 'src/widgets/Desktop/RootPopup';
const $q = useQuasar()
const router = useRouter()
@@ -33,6 +33,10 @@ export default route(async function (/* { store, ssrContext } */) {
const { initWallet } = useCardStore()
await initWallet()
const currentUser = useCurrentUserStore();
const session = useSessionStore();
console.log('on load desktops', currentUser)
await desktops.loadDesktops()
await desktops.setActiveDesktop(desktops.defaultDesktopHash)
@@ -63,13 +67,8 @@ export default route(async function (/* { store, ssrContext } */) {
from: RouteLocationNormalizedGeneric,
next: any
) => {
console.log('on router before')
desktops.healthCheck()
const { initWallet } = useCardStore()
await initWallet()
const currentUser = useCurrentUserStore();
const session = useSessionStore();
/** проверяем установлено ли приложение и не переход ли это на страницу установки */
if (desktops.health?.status === 'install' && to.name !== 'install') {
@@ -2,12 +2,12 @@ import layout from 'src/pages/_layouts/default.vue';
import index from 'src/pages/index.vue';
import blank from 'src/pages/blank/blank.vue';
import permissionDenied from 'src/pages/_layouts/permissionDenied.vue';
import { SignUpPage } from 'src/pages/SignUp';
import { SignInPage } from 'src/pages/SignIn';
import { SignUpPage } from 'src/pages/Registrator/SignUp';
import { SignInPage } from 'src/pages/Registrator/SignIn';
import { RouteRecordRaw } from 'vue-router';
import { InstallCooperativePage } from 'src/pages/Install';
import { LostKeyPage } from 'src/pages/Auth/LostKey/ui';
import { ResetKeyPage } from 'src/pages/Auth/ResetKey';
import { InstallCooperativePage } from 'src/pages/Installer';
import { LostKeyPage } from 'src/pages/Registrator/LostKey/ui';
import { ResetKeyPage } from 'src/pages/Registrator/ResetKey';
const baseRoutes = [
@@ -18,7 +18,7 @@ div.menu-container
q-icon(:name="isDark ? 'brightness_7' : 'brightness_3'").q-pt-xs.btn-icon
span.btn-font {{ isDark ? 'светлая' : 'тёмная' }}
q-btn(v-ripple flat class="cursor-pointer btn-menu" @click="logout")
q-btn(flat class="cursor-pointer btn-menu" @click="logout")
q-icon( color="red" name="logout").q-pt-xs.btn-icon
div.btn-font Выход
@@ -29,7 +29,7 @@ import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useQuasar } from 'quasar'
import { COOPNAME } from 'src/shared/config'
import { useLogoutUser } from 'src/features/User/Logout'
import { useLogoutUser } from 'src/features/Registrator/Logout'
import { FailAlert } from 'src/shared/api'
import { useCurrentUserStore } from 'src/entities/User'
import { type IRoute } from 'src/entities/Desktop/model/types'
@@ -1,47 +0,0 @@
<template lang="pug">
div(v-if="coop" flat)
div.row
div.col-md-4.q-pa-md
q-input(v-model="coop.username" flat readonly label="Имя аккаунта")
// q-input(flat readonly label="Верификация" )
// span(v-for="verification of coop.verifications") {{verification.verificator}}
// q-badge {{verification.is_verified}}
// q-input(flat readonly label="Хранилища данных" )
// q-btn(v-for="storage of coop.storages" flat) {{storage.storage_username}}
q-input(v-model="coop.initial" flat readonly label="Вступительный взнос")
q-input(v-model="coop.minimum" flat readonly label="Минимальный паевый взнос")
q-input(v-model="coop.coop_type" flat readonly label="Тип кооператива")
div.col-md-8.q-pa-md
q-input(v-model="coop.announce" flat label="Краткое описание").q-mb-lg
// q-input(flat label="Подробное описание" v-model="coop.description" type="textarea")
span(style="color: '#666666'; font-size: 12px;").text-grey Подробное описание
//- TiptapEditor(v-if="description" :v-model="description" @update:model-value="updateDescription")
q-btn(color="green" @click="save") сохранить
</template>
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue'
import { useCooperativeStore } from 'src/entities/Cooperative';
import { COOPNAME } from 'src/shared/config';
const cooperativeStore = useCooperativeStore()
const description = ref<string | undefined>('')
onMounted(async () => {
await cooperativeStore.loadPublicCooperativeData(COOPNAME)
description.value = coop.value?.description
})
let coop = computed(() => cooperativeStore.publicCooperativeData)
const save = () => {
console.log('on save')
}
</script>
@@ -28,11 +28,8 @@ div
// p Дашборд
q-tab-panel(name="Кооператив")
cooperative
// boards.col-md-12.col-xs-12.q-pa-md
q-tab-panel(name="Члены совета")
members
//- cooperative
// boards.col-md-12.col-xs-12.q-pa-md
q-tab-panel(name="Участки")
p Действующие участки
@@ -43,13 +40,13 @@ div
staff
q-tab-panel(name="Повестка")
decisions
//- decisions
q-tab-panel(name="Пайщики")
participants
//- participants
q-tab-panel(name="Документы")
documents(:receiver="coopname")
//- documents(:receiver="coopname")
q-tab-panel(name="Программы")
div.text-h6 Программы
@@ -67,15 +64,8 @@ div
</template>
<script setup lang="ts">//
import { computed, ref } from 'vue'
import cooperative from './cooperative.vue'
import members from './members.vue'
import { ref } from 'vue'
import staff from './staff.vue'
import decisions from './decisions.vue'
import participants from './participants.vue'
import documents from './documents.vue'
import { COOPNAME } from 'src/shared/config'
const coopname = computed(() => COOPNAME)
const tab = ref('Повестка')
</script>
@@ -1,21 +0,0 @@
import index from './index.vue'
export default {
'menu': [{
'pageName': 'Совет',
'name': 'soviet',
'icon': 'fa-regular fa-circle',
'isMobile': true,
'protected': true,
'params': {
coopname: ''
}
}],
'routes': [
{
path: ':coopname/soviet',
name: 'soviet',
component: index,
}
]
}
@@ -1,211 +0,0 @@
<template lang="pug">
div.row
div(v-if="members").col-md-12
q-card(v-if="members.length > 0" flat)
q-table(
ref="tableRef" flat
bordered
:rows="members"
:columns="columns"
:table-colspan="9"
row-key="username"
:pagination="pagination"
virtual-scroll
:virtual-scroll-item-size="48"
:rows-per-page-options="[10]"
).full-height
template(#bottom)
q-btn(icon="add" flat @click="showAdd = true") добавить участника
template(#header="props")
q-tr(:props="props")
q-th(
v-for="col in props.cols"
:key="col.name"
:props="props"
) {{ col.label }}
q-th(auto-width)
template(#body="props")
q-tr(:key="`m_${props.row.username}`" :props="props")
//- q-td {{ props.row.position_title }}
q-td {{ props.row.username }}
q-td {{ props.row.data?.user_profile?.last_name }}
q-td {{ props.row.data?.user_profile?.first_name }}
q-td {{ props.row.data?.user_profile?.middle_name }}
q-td {{ props.row.data?.user_profile?.phone }}
q-td {{ props.row.data?.email }}
q-td {{ moment(props.row.data?.user_profile?.birthday).format('DD.MM.YY') }}
// q-td
// q-badge(v-for="(right, index) of props.row.rights" v-bind:key="index" ).q-pa-xs.q-ma-xs {{right.contract}}::{{right.action_name}}
q-td(auto-width)
q-btn(size="sm" color="primary" round dense icon="clear" @click="removeMember(props.row.username)")
q-dialog(v-model="showAdd" persistent :maximized="false" )
q-card
div()
q-bar.bg-primary.text-white
span Добавить сотрудника
q-space
q-btn(v-close-popup dense flat icon="close")
q-tooltip Close
div.q-pa-sm.row.justify-center
div.q-pa-md
span Внимание! Вы собиретесь добавить участика в совет кооператива.
div
q-input(v-model="persona.username" label="Имя аккаунта")
q-input(v-model="persona.position_title" label="Позиция")
div.q-mt-lg
q-btn(flat @click="showAdd = false") Назад
q-btn(color="primary" @click="addMember") Добавить
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { Notify } from 'quasar';
import moment from 'moment-with-locales-es6'
import { readBlockchain, sendGET } from 'src/shared/api'
import { useSessionStore } from 'src/entities/Session';
import { useUpdateBoard } from 'src/features/Cooperative/UpdateBoard';
import { COOPNAME } from 'src/shared/config';
const route = useRoute();
const showAdd = ref(false)
const members = ref<any>([]);
let members_reserve: any = []
const persona = ref({
username: '',
position_title: '',
})
const loadMembers = async () => {
try {
members.value = await sendGET('/v1/coop/members', {
coopname: route.params.coopname
})
members_reserve = JSON.parse(JSON.stringify(members.value))
} catch (e: any) {
Notify.create({
message: e.message,
type: 'negative',
})
}
}
loadMembers()
const addMember = async () => {
const verified = await verify(persona.value.username)
if (!verified) {
Notify.create({
message: 'Имя аккаунта на найдено',
type: 'negative',
})
return
}
members.value.push({ username: persona.value.username, position_title: persona.value.position_title, position: 'member', is_voting: true });
updateBoard()
showAdd.value = false
};
const removeMember = (username: string) => {
members.value = members.value.filter((el: { username: string; }) => el.username != username)
updateBoard()
};
const verify = async (username: string) => {
try {
await readBlockchain.v1.chain.get_account(username);
return true;
} catch (e) {
return false;
}
};
const updateBoard = async () => {
try {
let members_for_send = members.value.map((el: { username: any; position_title: any; position: any; is_voting: any; }) => {
return {
username: el.username,
position_title: el.position_title,
position: el.position,
is_voting: el.is_voting
}
})
await useUpdateBoard().updateBoard({
coopname: COOPNAME,
chairman: useSessionStore().username,
board_id: 0,
members: members_for_send,
name: 'Совет',
description: 'Совет кооператива'
})
Notify.create({
message: 'Совет обновлен',
type: 'positive',
})
loadMembers()
} catch (e: any) {
members.value = members_reserve
Notify.create({
message: e.message,
type: 'negative',
})
}
}
const columns = [
// { name: 'position_title', align: 'left', label: 'Позиция', field: 'position_title', sortable: true },
{ name: 'username', align: 'left', label: 'Аккаунт', field: 'username', sortable: true },
{ name: 'last_name', align: 'left', label: 'Фамилия', field: 'last_name', sortable: true },
{ name: 'first_name', align: 'left', label: 'Имя', field: 'first_name', sortable: true },
{ name: 'middle_name', align: 'left', label: 'Отчество', field: 'middle_name', sortable: true },
{ name: 'phone', align: 'left', label: 'Телефон', field: 'phone', sortable: false },
{ name: 'email', align: 'left', label: 'Е-почта', field: 'email', sortable: false },
{ name: 'birthday', align: 'left', label: 'Дата рождения', field: 'birthday', sortable: true },
] as any
const tableRef = ref(null)
const pagination = ref({ rowsPerPage: 10 })
</script>
@@ -1,107 +0,0 @@
<template lang="pug">
div.row.justify-center.full-height
div.col-12.full-height
q-table(
ref="tableRef" v-model:expanded="expanded"
flat
bordered
:rows="participants.results"
:columns="columns"
:table-colspan="9"
row-key="username"
:pagination="pagination"
virtual-scroll
:virtual-scroll-item-size="48"
:rows-per-page-options="[10]"
:loading="onLoading"
:no-data-label="'У кооператива нет пайщиков'"
).full-height
template(#header="props")
q-tr(:props="props")
q-th(auto-width)
q-th(
v-for="col in props.cols"
:key="col.name"
:props="props"
) {{ col.label }}
template(#body="props")
q-tr(:key="`m_${props.row.username}`" :props="props")
q-td(auto-width)
// q-toggle(v-model="props.expand" checked-icon="fas fa-chevron-circle-left" unchecked-icon="fas fa-chevron-circle-right" )
q-btn(size="sm" color="primary" round dense :icon="props.expand ? 'remove' : 'add'" @click="props.expand = !props.expand")
q-td {{ props.row.username }}
q-td {{ props.row.private_data?.last_name }}
q-td {{ props.row.private_data?.first_name }}
q-td {{ props.row.private_data?.middle_name }}
q-td {{ props.row.private_data?.phone }}
q-td {{ props.row.private_data?.email }}
q-td {{ moment(props.row.private_data?.birthdate).format('DD.MM.YY') }}
q-td {{ moment(props.row.private_data._created_at).format('DD.MM.YY HH:mm:ss') }}
q-tr(v-show="props.expand" :key="`e_${props.row.username}`" :props="props" class="q-virtual-scroll--with-prev")
q-td(colspan="100%")
documents(v-if="props.expand" :receiver="props.row.username")
// div.text-left Здесь будет история подписанных документов: {{ props.row.username }}
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Notify } from 'quasar'
import documents from './documents.vue'
import { sendGET } from 'src/shared/api';
const participants = ref({ results: [] })
const onLoading = ref(false)
import moment from 'moment-with-locales-es6'
const loadParticipants = async () => {
try {
onLoading.value = true
participants.value = await sendGET('/v1/users', {
limit: 100
})
onLoading.value = false
} catch (e: any) {
onLoading.value = false
Notify.create({
message: e.message,
type: 'negative',
})
}
}
loadParticipants()
const columns = [
{ name: 'username', align: 'left', label: 'Аккаунт', field: 'username', sortable: true },
{ name: 'last_name', align: 'left', label: 'Фамилия', field: 'last_name', sortable: true },
{ name: 'first_name', align: 'left', label: 'Имя', field: 'first_name', sortable: true },
{ name: 'middle_name', align: 'left', label: 'Отчество', field: 'middle_name', sortable: true },
{ name: 'phone', align: 'left', label: 'Телефон', field: 'phone', sortable: false },
{ name: 'email', align: 'left', label: 'Е-почта', field: 'email', sortable: false },
{ name: 'birthday', align: 'left', label: 'Дата рождения', field: 'birthday', sortable: true },
{
name: 'created_at',
align: 'left',
label: 'Зарегистрирован',
field: 'created_at',
sortable: true,
},
] as any
const expanded = ref([])
const tableRef = ref(null)
const pagination = ref({ rowsPerPage: 10 })
</script>
@@ -1,9 +1,10 @@
import { UserIdentityPage } from 'src/pages/User/IdentityPage';
import { UserPaymentMethodsPage } from 'src/pages/User/PaymentMethodsPage';
import { UserWalletPage } from 'src/pages/User/WalletPage';
import decisions from 'src/components/soviet/decisions.vue';
import participants from 'src/components/soviet/participants.vue';
import documents from 'src/components/soviet/documents.vue';
import { ListOfQuestionsWidget } from 'src/widgets/Cooperative/Agenda/ListOfQuestions';
import { ListOfDocumentsWidget } from 'src/widgets/Cooperative/Documents/ListOfDocuments';
import { markRaw } from 'vue';
// import { Commutator } from 'src/widgets/Commutator';
@@ -19,6 +20,9 @@ import { CooperativeDetails } from 'src/widgets/Cooperative/Details';
import { CooperativeMembers } from 'src/widgets/Cooperative/Members';
import { ChangeCooperativeContributions } from 'src/widgets/Cooperative/Contributions';
import { ChangeCooperativeContacts } from 'src/widgets/Cooperative/Contacts';
import { UserSettingsPage } from 'src/pages/User/SettingsPage';
import { ListOfParticipantsPage } from 'src/pages/Cooperative/ListOfParticipants';
import { UnionPageListOfCooperatives } from 'src/pages/Union/ListOfCooperatives';
export const manifest = {
'name': 'UserDesktop',
@@ -39,6 +43,7 @@ export const manifest = {
title: 'Удостоверение',
icon: '',
roles: [],
programs: ['wallet']
},
path: 'identity',
name: 'user-identity',
@@ -65,6 +70,17 @@ export const manifest = {
name: 'user-payment-methods',
component: markRaw(UserPaymentMethodsPage),
children: [],
},
{
meta: {
title: 'Настройки',
icon: '',
roles: [],
},
path: 'settings',
name: 'user-settings',
component: markRaw(UserSettingsPage),
children: [],
}
],
},
@@ -81,7 +97,7 @@ export const manifest = {
{
path: 'agenda',
name: 'agenda',
component: markRaw(decisions),
component: markRaw(ListOfQuestionsWidget),
meta: {
title: 'Повестка',
icon: 'fa-solid fa-check-to-slot',
@@ -91,7 +107,7 @@ export const manifest = {
{
path: 'participants',
name: 'participants',
component: markRaw(participants),
component: markRaw(ListOfParticipantsPage),
meta: {
title: 'Пайщики',
icon: 'fa-solid fa-users',
@@ -101,7 +117,7 @@ export const manifest = {
{
path: 'documents',
name: 'documents',
component: markRaw(documents),
component: markRaw(ListOfDocumentsWidget),
meta: {
title: 'Документы',
icon: 'fa-solid fa-file-invoice',
@@ -166,7 +182,17 @@ export const manifest = {
]
},
{
meta: {
title: 'Подключения',
icon: 'fas fa-link',
roles: ['chairman', 'member'],
},
path: '/:coopname/connections',
name: 'connections',
component: markRaw(UnionPageListOfCooperatives),
},
// {
@@ -3,20 +3,20 @@ import { ContractsList, TablesList } from 'src/shared/config';
import {
IAddressesData,
IAdministratorData,
ICoopMarketProgramData,
ILoadCoopMarketPrograms,
ICoopProgramData,
ILoadCoopPrograms,
ILoadCooperativeAddresses,
} from '../model';
import {Cooperative, FundContract, RegistratorContract } from 'cooptypes';
import {Cooperative, FundContract, RegistratorContract, SovietContract } from 'cooptypes';
async function loadMarketPrograms(
params: ILoadCoopMarketPrograms
): Promise<ICoopMarketProgramData[]> {
async function loadPrograms(
params: ILoadCoopPrograms
): Promise<ICoopProgramData[]> {
return (await fetchTable(
ContractsList.Soviet,
SovietContract.contractName.production,
params.coopname,
TablesList.CoopMarketPrograms
)) as ICoopMarketProgramData[];
SovietContract.Tables.Programs.tableName
)) as ICoopProgramData[];
}
async function loadAdmins(coopname: string): Promise<IAdministratorData[]> {
@@ -33,17 +33,17 @@ async function loadContacts(): Promise<Cooperative.Model.IContacts> {
return (await sendGET('/v1/coop/contacts', {}, true)) as Cooperative.Model.IContacts;
}
async function loadFundWallet(coopname: string): Promise<FundContract.Tables.FundWallet.IFundWallet>{
async function loadCoopWallet(coopname: string): Promise<FundContract.Tables.CoopWallet.ICoopWallet>{
return (
await fetchTable(
FundContract.contractName.production,
coopname,
FundContract.Tables.FundWallet.tableName,
FundContract.Tables.CoopWallet.tableName,
0,
0,
1
)
)[0] as FundContract.Tables.FundWallet.IFundWallet;
)[0] as FundContract.Tables.CoopWallet.ICoopWallet;
}
async function loadAccumulationFunds(coopname: string): Promise<FundContract.Tables.AccumulatedFunds.IAccumulatedFund[]>{
@@ -94,13 +94,13 @@ async function loadCooperativeAddresses(
}
export const api = {
loadMarketPrograms,
loadPrograms,
loadCooperativeAddresses,
loadPublicCooperativeData,
loadAdmins,
loadPrivateCooperativeData,
loadContacts,
loadFundWallet,
loadCoopWallet,
loadAccumulationFunds,
loadExpenseFunds
};

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