add a lot of queries and mutations

This commit is contained in:
Alex Ant
2025-01-01 12:06:41 +05:00
parent 04364c503c
commit d749ebf981
180 changed files with 8420 additions and 623 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"editor.formatOnSave": true,
"editor.tabCompletion": "onlySnippets",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
// "editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "typescript", "vue"],
// "typescript.tsdk": "node_modules/typescript/lib",
"i18n-ally.localesPaths": ["src/i18n"],
+1
View File
@@ -107,6 +107,7 @@
"glob": "^11.0.0",
"graphql": "^16.9.0",
"graphql-request": "^7.1.2",
"graphql-scalars": "^1.24.0",
"graphql-tools": "^9.0.2",
"graphql-type-json": "^0.3.2",
"graphql-ws": "^5.16.0",
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
export const tokenTypes = {
ACCESS: 'access',
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
RESET_KEY: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
INVITE: 'invite',
};
@@ -8,7 +8,7 @@ import pick from '../utils/pick';
// import { IGetResponse } from '../types/common';
import { IUser, userStatus } from '../types';
import { Request, Response } from 'express';
import { generateUsername } from '../../tests/utils/generateUsername';
import { generateUsername } from '../utils/generate-username';
import config from '../config/config';
import logger from '../config/logger';
// import { Body, Controller, Get, Path, Post, Query, Route, SuccessResponse } from 'tsoa';
@@ -87,6 +87,22 @@ export const addUser = catchAsync(async (req: Request, res: Response) => {
res.status(httpStatus.CREATED).send({ user });
});
export const updateUser = catchAsync(async (req, res) => {
if (req.user.role !== 'member' && req.user.role !== 'chairman')
throw new ApiError(httpStatus.FORBIDDEN, 'Только председатель или член совета может обновить данные пользователя');
const user = await userService.updateUserByUsername(req.params.username, req.body);
res.send(user);
});
export const deleteUser = catchAsync(async (req, res) => {
if (req.user.role !== 'member' && req.user.role !== 'chairman')
throw new ApiError(httpStatus.FORBIDDEN, 'Только председатель или член совета может обновить данные пользователя');
await userService.deleteUserByUsername(req.params.username);
res.status(http.NO_CONTENT).send();
});
export const getUsers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['username', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
@@ -100,16 +116,3 @@ export const getUser = catchAsync(async (req, res) => {
res.send(user);
});
export const updateUser = catchAsync(async (req, res) => {
if (req.user.role !== 'member' && req.user.role !== 'chairman')
throw new ApiError(httpStatus.FORBIDDEN, 'Только председатель или член совета может обновить данные пользователя');
const user = await userService.updateUserByUsername(req.params.username, req.body);
res.send(user);
});
export const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserByUsername(req.params.username);
res.status(http.NO_CONTENT).send();
});
@@ -6,7 +6,7 @@ export class AccountDomainEntity {
public readonly username!: string;
public blockchain_account!: BlockchainAccountInterface | null;
public user_account!: RegistratorContract.Tables.Accounts.IAccount | null;
public mono_account!: MonoAccountDomainInterface | null;
public provider_account!: MonoAccountDomainInterface | null;
public participant_account!: SovietContract.Tables.Participants.IParticipants | null;
// public cardcoop_account!: ?
@@ -1,28 +1,116 @@
import { AccountDomainEntity } from '../entities/account-domain.entity';
import config from '~/config/config';
import { AccountDomainService } from '~/domain/account/services/account-domain.service';
import { Injectable } from '@nestjs/common';
import { userService } from '~/services';
import { Inject, Injectable } from '@nestjs/common';
import { tokenService, userService } from '~/services';
import type { MonoAccountDomainInterface } from '../interfaces/mono-account-domain.interface';
import type { GetAccountsInputDomainInterface } from '../interfaces/get-accounts-input.interface';
import type {
PaginationInputDomainInterface,
PaginationResultDomainInterface,
} from '~/domain/common/interfaces/pagination.interface';
import type { QueryResultLegacy } from '~/domain/common/interfaces/query-result-legacy-domain.interface';
import type { RegisterAccountDomainInterface } from '../interfaces/register-account-input.interface';
import type { RegisteredAccountDomainInterface } from '../interfaces/registeted-account.interface';
import type { UpdateAccountDomainInterface } from '../interfaces/update-account-input.interface';
import { AccountType } from '~/modules/account/enum/account-type.enum';
import { ORGANIZATION_REPOSITORY, OrganizationRepository } from '~/domain/common/repositories/organization.repository';
import { INDIVIDUAL_REPOSITORY, IndividualRepository } from '~/domain/common/repositories/individual.repository';
import { ENTREPRENEUR_REPOSITORY, EntrepreneurRepository } from '~/domain/common/repositories/entrepreneur.repository';
import { IndividualDomainEntity } from '~/domain/branch/entities/individual-domain.entity';
import { OrganizationDomainEntity } from '~/domain/branch/entities/organization-domain.entity';
import { EntrepreneurDomainEntity } from '~/domain/branch/entities/entrepreneur-domain.entity';
@Injectable()
export class AccountDomainInteractor {
constructor(private readonly accountDomainService: AccountDomainService) {}
constructor(
private readonly accountDomainService: AccountDomainService,
@Inject(ORGANIZATION_REPOSITORY) private readonly organizationRepository: OrganizationRepository,
@Inject(INDIVIDUAL_REPOSITORY) private readonly individualRepository: IndividualRepository,
@Inject(ENTREPRENEUR_REPOSITORY) private readonly entrepreneurRepository: EntrepreneurRepository
) {}
async updateAccount(data: UpdateAccountDomainInterface): Promise<AccountDomainEntity> {
await userService.updateUserByUsername(data.username, {
email: data.email,
});
// обновляем данные в хранилище
if (data.type === AccountType.Individual && data.individual_data) {
//здесь и далее мы подставляем email и username т.к. они требуются в интерфесе генератора
const individual = new IndividualDomainEntity({ ...data.individual_data, username: data.username, email: data.email });
await this.individualRepository.create(individual);
} else if (data.type === AccountType.Organization && data.organization_data) {
const organization = new OrganizationDomainEntity({
...data.organization_data,
username: data.username,
email: data.email,
});
await this.organizationRepository.create(organization);
} else if (data.type === AccountType.Entrepreneur && data.entrepreneur_data) {
const entrepreneur = new EntrepreneurDomainEntity({
...data.entrepreneur_data,
username: data.username,
email: data.email,
});
await this.entrepreneurRepository.create(entrepreneur);
}
return await this.getAccount(data.username);
}
async deleteAccount(username: string): Promise<void> {
await userService.deleteUserByUsername(username);
}
async registerAccount(data: RegisterAccountDomainInterface): Promise<RegisteredAccountDomainInterface> {
//TODO refactor after migrate from mongo
const user = await userService.createUser(data);
const tokens = await tokenService.generateAuthTokens(user);
const account = await this.getAccount(data.username);
const result: RegisteredAccountDomainInterface = {
account,
tokens,
};
return result;
}
async getAccount(username: string): Promise<AccountDomainEntity> {
const user_account = await this.accountDomainService.getUserAccount(username);
const blockchain_account = await this.accountDomainService.getBlockchainAccount(username);
const participant_account = await this.accountDomainService.getParticipantAccount(config.coopname, username);
return await this.accountDomainService.getAccount(username);
}
//TODO refactor after migrate from mongo
const mono_account = (await userService.findUser(username)) as unknown as MonoAccountDomainInterface;
async getAccounts(
data: GetAccountsInputDomainInterface,
options: PaginationInputDomainInterface
): Promise<PaginationResultDomainInterface<AccountDomainEntity>> {
const provider_accounts = (await userService.queryUsers(data, options)) as QueryResultLegacy<MonoAccountDomainInterface>;
return new AccountDomainEntity({
username,
user_account,
blockchain_account,
mono_account: mono_account,
participant_account,
});
const result: PaginationResultDomainInterface<AccountDomainEntity> = {
items: [],
totalCount: provider_accounts.totalResults,
totalPages: provider_accounts.totalPages,
currentPage: provider_accounts.page,
};
for (const account of provider_accounts.results) {
const user_account = await this.accountDomainService.getUserAccount(account.username);
const blockchain_account = await this.accountDomainService.getBlockchainAccount(account.username);
const participant_account = await this.accountDomainService.getParticipantAccount(config.coopname, account.username);
const item = new AccountDomainEntity({
username: account.username,
user_account,
blockchain_account,
provider_account: account,
participant_account,
});
result.items.push(item);
}
return result;
}
}
@@ -9,6 +9,7 @@ export interface AccountBlockchainPort {
username: string
): Promise<SovietContract.Tables.Participants.IParticipants | null>;
getUserAccount(username: string): Promise<RegistratorContract.Tables.Accounts.IAccount | null>;
addParticipantAccount(data: RegistratorContract.Actions.AddUser.IAddUser): Promise<void>;
}
export const ACCOUNT_BLOCKCHAIN_PORT = Symbol('AccountBlockchainPort');
@@ -0,0 +1,4 @@
export interface GetAccountsInputDomainInterface {
username?: string;
role?: string;
}
@@ -0,0 +1,17 @@
import type { Cooperative } from 'cooptypes';
export interface RegisterAccountDomainInterface {
email: string;
entrepreneur_data?: Omit<Cooperative.Users.IEntrepreneurData, 'username'> & {
bank_account: Cooperative.Payments.IBankAccount;
};
individual_data?: Omit<Cooperative.Users.IIndividualData, 'username'>;
organization_data?: Omit<Cooperative.Users.IOrganizationData, 'username'> & {
bank_account: Cooperative.Payments.IBankAccount;
};
public_key?: string;
referer?: string;
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -0,0 +1,7 @@
import type { AccountDomainEntity } from '../entities/account-domain.entity';
import type { TokensDomainInterface } from './tokens-domain.interface';
export interface RegisteredAccountDomainInterface {
account: AccountDomainEntity;
tokens: TokensDomainInterface;
}
@@ -0,0 +1,4 @@
export interface TokenDomainInterface {
token: string;
expires: Date;
}
@@ -0,0 +1,6 @@
import type { TokenDomainInterface } from './token-domain.interface';
export interface TokensDomainInterface {
access: TokenDomainInterface;
refresh: TokenDomainInterface;
}
@@ -0,0 +1,13 @@
import type { Cooperative } from 'cooptypes';
export interface UpdateAccountDomainInterface {
email: string;
entrepreneur_data?: Omit<Cooperative.Users.IEntrepreneurData, 'username'>;
individual_data?: Omit<Cooperative.Users.IIndividualData, 'username'>;
organization_data?: Omit<Cooperative.Users.IOrganizationData, 'username'>;
public_key?: string;
referer?: string;
role: 'user';
type: 'individual' | 'entrepreneur' | 'organization';
username: string;
}
@@ -1,12 +1,56 @@
import { Inject, Injectable } from '@nestjs/common';
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import type { BlockchainAccountInterface } from '~/types/shared';
import { ACCOUNT_BLOCKCHAIN_PORT, type AccountBlockchainPort } from '../interfaces/account-blockchain.port';
import type { RegistratorContract, SovietContract } from 'cooptypes';
import config from '~/config/config';
import { AccountDomainEntity } from '../entities/account-domain.entity';
import type { MonoAccountDomainInterface } from '../interfaces/mono-account-domain.interface';
import { blockchainService, userService } from '~/services';
import type { RegisterAccountDomainInterface } from '../interfaces/register-account-input.interface';
import { userStatus } from '~/types';
@Injectable()
export class AccountDomainService {
constructor(@Inject(ACCOUNT_BLOCKCHAIN_PORT) private readonly accountBlockchainPort: AccountBlockchainPort) {}
async addProviderAccount(data: RegisterAccountDomainInterface): Promise<MonoAccountDomainInterface> {
//TODO refactor it after migrate from mongo
const user = await userService.createUser(data);
user.status = userStatus['4_Registered'];
user.is_registered = true;
user.has_account = true;
await user.save();
return user as unknown as MonoAccountDomainInterface;
}
async addParticipantAccount(data: RegistratorContract.Actions.AddUser.IAddUser): Promise<void> {
try {
await this.accountBlockchainPort.addParticipantAccount(data);
} catch (e: any) {
// удаляем аккаунт провайдера если транзакция в блокчейн не прошла (для возможности повтора)
await userService.deleteUserByUsername(data.username);
throw new BadRequestException(e.message);
}
}
async getAccount(username: string): Promise<AccountDomainEntity> {
const user_account = await this.getUserAccount(username);
const blockchain_account = await this.getBlockchainAccount(username);
const participant_account = await this.getParticipantAccount(config.coopname, username);
//TODO refactor after migrate from mongo
const provider_account = (await userService.findUser(username)) as unknown as MonoAccountDomainInterface;
return new AccountDomainEntity({
username,
user_account,
blockchain_account,
provider_account: provider_account,
participant_account,
});
}
async getBlockchainAccount(username: string): Promise<BlockchainAccountInterface | null> {
return await this.accountBlockchainPort.getBlockchainAccount(username);
}
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { DocumentDomainModule } from '../document/document.module';
import { AgendaDomainInteractor } from './interactors/agenda-domain.interactor';
@Module({
imports: [DocumentDomainModule],
providers: [AgendaDomainInteractor],
exports: [AgendaDomainInteractor],
})
export class AgendaDomainModule {}
@@ -0,0 +1,82 @@
import { DocumentDomainService } from '~/domain/document/services/document-domain.service';
import { Injectable } from '@nestjs/common';
import { blockchainService } from '~/services';
import config from '~/config/config';
import { SovietContract } from 'cooptypes';
import type { AgendaWithDocumentsDomainInterface } from '../interfaces/agenda-with-documents-domain.interface';
import { getActions } from '~/utils/getFetch';
import type { VotingAgendaDomainInterface } from '../interfaces/voting-agenda-domain.interface';
@Injectable()
export class AgendaDomainInteractor {
constructor(private readonly documentDomainService: DocumentDomainService) {}
async getAgenda(): Promise<AgendaWithDocumentsDomainInterface[]> {
// Шаг 1: Загрузить повестку дня
const agenda = await this.loadAgenda(config.coopname);
// Шаг 2: Построить комплексную повестку (complexAgenda)
const complexAgenda: AgendaWithDocumentsDomainInterface[] = [];
for (const { action, table } of agenda) {
// Создание пакета документов для каждого действия
const documents = await this.documentDomainService.buildDocumentPackage(action);
// Проверяем наличие заявления, прежде чем добавлять в комплексную повестку
// Делаем только потому что в локальной разработке заявлений после перезапуска блокчейна может и не быть в истории цепочки,
// а это ломает отображение на фронте. При нормальные условиях заявление всегда должно быть.
if (documents.statement?.document) {
complexAgenda.push({
table,
action,
documents,
});
}
}
// Шаг 3: Вернуть итоговую комплексную повестку
return complexAgenda;
}
/**
* Загружает повестку дня (agenda) на основании решений и связанных действий.
*/
async loadAgenda(coopname: string): Promise<VotingAgendaDomainInterface[]> {
//TODO блокчейн-адаптер здесь повесить
const api = await blockchainService.getApi();
// Загружаем таблицу решений
const decisions = (await blockchainService.lazyFetch(
api,
SovietContract.contractName.production as string,
coopname,
'decisions'
)) as SovietContract.Tables.Decisions.IDecision[];
const agenda: VotingAgendaDomainInterface[] = [];
for (const decision of decisions) {
// Ищем экшен, связанный с конкретным решением
const actionResponse = await getActions(`${process.env.SIMPLE_EXPLORER_API}/get-actions`, {
filter: JSON.stringify({
account: SovietContract.contractName.production,
name: SovietContract.Actions.Registry.NewSubmitted.actionName,
receiver: process.env.COOPNAME,
'data.decision_id': String(decision.id),
}),
page: 1,
limit: 1,
});
const action = actionResponse?.results?.[0];
if (action) {
agenda.push({
table: decision,
action,
});
}
}
return agenda;
}
}
@@ -0,0 +1,7 @@
import type { GeneratedDocumentDomainInterface } from '~/domain/document/interfaces/generated-document-domain.interface';
import type { ExtendedBlockchainActionDomainInterface } from './extended-blockchain-action-domain.interface';
export interface ActDetailDomainInterface {
action?: ExtendedBlockchainActionDomainInterface;
document?: GeneratedDocumentDomainInterface;
}
@@ -0,0 +1,6 @@
import type { DocumentPackageDomainInterface } from './document-package-domain.interface';
import type { VotingAgendaDomainInterface } from './voting-agenda-domain.interface';
export interface AgendaWithDocumentsDomainInterface extends VotingAgendaDomainInterface {
documents: DocumentPackageDomainInterface;
}
@@ -0,0 +1,3 @@
import type { Cooperative } from 'cooptypes';
export type BlockchainActionDomainInterface = Cooperative.Blockchain.IAction;
@@ -0,0 +1,9 @@
import type { GeneratedDocumentDomainInterface } from '~/domain/document/interfaces/generated-document-domain.interface';
import type { ExtendedBlockchainActionDomainInterface } from './extended-blockchain-action-domain.interface';
export interface DecisionDetailDomainInterface {
action: ExtendedBlockchainActionDomainInterface;
document: GeneratedDocumentDomainInterface;
votes_for: ExtendedBlockchainActionDomainInterface[];
votes_against: ExtendedBlockchainActionDomainInterface[];
}
@@ -0,0 +1,11 @@
import type { GeneratedDocumentDomainInterface } from '~/domain/document/interfaces/generated-document-domain.interface';
import type { ActDetailDomainInterface } from './act-detail-domain.interface';
import type { DecisionDetailDomainInterface } from './decision-detail-domain.interface';
import type { StatementDetailDomainInterface } from './statement-detail-domain.interface';
export interface DocumentPackageDomainInterface {
statement: StatementDetailDomainInterface;
decision: DecisionDetailDomainInterface;
acts: ActDetailDomainInterface[];
links: GeneratedDocumentDomainInterface[];
}
@@ -0,0 +1,7 @@
import type { DocumentPackageDomainInterface } from './document-package-domain.interface';
export interface DocumentPackagesResponseDomainInterface {
results: DocumentPackageDomainInterface[];
page: number;
limit: number;
}
@@ -0,0 +1,10 @@
import type { BlockchainActionDomainInterface } from './blockchain-action-domain.interface';
import type { Cooperative } from 'cooptypes';
export interface ExtendedBlockchainActionDomainInterface extends BlockchainActionDomainInterface {
user?:
| Cooperative.Users.IIndividualData
| Cooperative.Users.IEntrepreneurData
| Cooperative.Users.IOrganizationData
| null;
}
@@ -0,0 +1,7 @@
import type { GeneratedDocumentDomainInterface } from '~/domain/document/interfaces/generated-document-domain.interface';
import type { ExtendedBlockchainActionDomainInterface } from './extended-blockchain-action-domain.interface';
export interface StatementDetailDomainInterface {
action: ExtendedBlockchainActionDomainInterface;
document: GeneratedDocumentDomainInterface;
}
@@ -0,0 +1,7 @@
import type { SovietContract } from 'cooptypes';
import type { BlockchainActionDomainInterface } from './blockchain-action-domain.interface';
export interface VotingAgendaDomainInterface {
table: SovietContract.Tables.Decisions.IDecision;
action: BlockchainActionDomainInterface;
}
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { DocumentDomainModule } from '../document/document.module';
import { AgreementDomainInteractor } from './interactors/agreement-domain.interactor';
@Module({
imports: [DocumentDomainModule],
providers: [AgreementDomainInteractor],
exports: [AgreementDomainInteractor],
})
export class AgreementDomainModule {}
@@ -0,0 +1,41 @@
import { Cooperative } from 'cooptypes';
import { DocumentDomainService } from '~/domain/document/services/document-domain.service';
import { DocumentDomainEntity } from '~/domain/document/entity/document-domain.entity';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AgreementDomainInteractor {
constructor(private readonly documentDomainService: DocumentDomainService) {}
async generateWalletAgreement(
data: Cooperative.Registry.WalletAgreement.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.WalletAgreement.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
async generatePrivacyAgreement(
data: Cooperative.Registry.PrivacyPolicy.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.PrivacyPolicy.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
async generateSignatureAgreement(
data: Cooperative.Registry.RegulationElectronicSignature.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.RegulationElectronicSignature.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
async generateUserAgreement(
data: Cooperative.Registry.UserAgreement.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.UserAgreement.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
}
@@ -0,0 +1,12 @@
// domain/appstore/appstore-domain.module.ts
import { Module } from '@nestjs/common';
import { AuthDomainInteractor } from './interactors/auth.interactor';
import { AccountDomainModule } from '../account/account-domain.module';
@Module({
imports: [AccountDomainModule],
providers: [AuthDomainInteractor],
exports: [AuthDomainInteractor],
})
export class AuthDomainModule {}
@@ -0,0 +1,94 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import type { RegisteredAccountDomainInterface } from '~/domain/account/interfaces/registeted-account.interface';
import { AccountDomainService } from '~/domain/account/services/account-domain.service';
import { authService, blockchainService, emailService, tokenService, userService } from '~/services';
import type { LoginInputDomainInterface } from '../interfaces/login-input-domain.interface';
import type { StartResetKeyInputDomainInterface } from '../interfaces/start-reset-key-input.interface';
import type { ResetKeyInputDomainInterface } from '../interfaces/reset-key-input.interface';
import type { RefreshInputDomainInterface } from '../interfaces/refresh-input.interface';
import type { LogoutInputDomainInterface } from '../interfaces/logout-input-domain.interface';
import { tokenTypes } from '~/config/tokens';
import { Token } from '~/models';
import config from '~/config/config';
@Injectable()
export class AuthDomainInteractor {
constructor(private readonly accountDomainService: AccountDomainService) {}
async login(data: LoginInputDomainInterface): Promise<RegisteredAccountDomainInterface> {
const user = await authService.loginUserWithSignature(data.email, data.now, data.signature);
const tokens = await tokenService.generateAuthTokens(user);
const account = await this.accountDomainService.getAccount(user.username);
return {
account,
tokens,
};
}
async logout(data: LogoutInputDomainInterface): Promise<void> {
const refreshTokenDoc = await Token.findOne({ token: data.refresh_token, type: tokenTypes.REFRESH, blacklisted: false });
if (refreshTokenDoc) {
await refreshTokenDoc.deleteOne();
}
const accessTokenDoc = await Token.findOne({ token: data.access_token, type: tokenTypes.REFRESH, blacklisted: false });
if (accessTokenDoc) {
await accessTokenDoc.deleteOne();
}
}
async startResetKey(data: StartResetKeyInputDomainInterface): Promise<void> {
const resetKeyToken = await tokenService.generateResetKeyToken(data.email);
await emailService.sendResetKeyEmail(data.email, resetKeyToken);
}
async resetKey(data: ResetKeyInputDomainInterface): Promise<void> {
try {
const resetKeyTokenDoc = await tokenService.verifyToken(data.token, [tokenTypes.RESET_KEY, tokenTypes.INVITE]);
const user = await userService.getUserById(resetKeyTokenDoc.user);
if (!user) {
throw new Error();
}
await blockchainService.changeKey({
coopname: config.coopname,
changer: config.coopname,
username: user.username,
public_key: data.public_key,
});
await userService.updateUserById(user._id, { public_key: data.public_key });
await Token.deleteMany({ user: user._id, type: tokenTypes.RESET_KEY });
} catch (error) {
console.log(error);
throw new UnauthorizedException('Возникла ошибка при сбросе ключа');
}
}
async refresh(data: RefreshInputDomainInterface): Promise<RegisteredAccountDomainInterface> {
try {
const refreshTokenDoc = await tokenService.verifyToken(data.refresh_token, tokenTypes.REFRESH);
const user = await userService.getUserById(refreshTokenDoc.user);
if (!user) {
throw new Error();
}
await refreshTokenDoc.deleteOne();
const tokens = await tokenService.generateAuthTokens(user);
const account = await this.accountDomainService.getAccount(user.username);
return {
account,
tokens,
};
} catch (error) {
throw new UnauthorizedException('Возникла неизвестная ошибка при обновлении');
}
}
}
@@ -0,0 +1,5 @@
export interface LoginInputDomainInterface {
email: string;
now: string;
signature: string;
}
@@ -0,0 +1,4 @@
export interface LogoutInputDomainInterface {
refresh_token: string;
access_token: string;
}
@@ -0,0 +1,4 @@
export interface RefreshInputDomainInterface {
refresh_token: string;
access_token: string;
}
@@ -0,0 +1,4 @@
export interface ResetKeyInputDomainInterface {
public_key: string;
token: string;
}
@@ -0,0 +1,3 @@
export interface StartResetKeyInputDomainInterface {
email: string;
}
@@ -1,5 +1,3 @@
// domain/appstore/appstore-domain.module.ts
import { Module } from '@nestjs/common';
import { BranchDomainInteractor } from './interactors/branch.interactor';
import { DocumentDomainModule } from '../document/document.module';
@@ -0,0 +1,7 @@
export interface QueryResultLegacy<T> {
results: T[];
page: number;
limit: number;
totalPages: number;
totalResults: number;
}
@@ -1,12 +0,0 @@
// domain/appstore/appstore-domain.module.ts
import { Module } from '@nestjs/common';
import { DecisionDomainInteractor } from './interactors/decision.interactor';
import { DocumentDomainModule } from '../document/document.module';
@Module({
imports: [DocumentDomainModule],
providers: [DecisionDomainInteractor],
exports: [DecisionDomainInteractor],
})
export class DecisionDomainModule {}
@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common';
import { DocumentInteractor } from './interactors/document.interactor';
import { DocumentDomainInteractor } from './interactors/document.interactor';
import { InfrastructureModule } from '~/infrastructure/infrastructure.module';
import { DocumentDomainService } from './services/document-domain.service';
@Module({
imports: [InfrastructureModule],
providers: [DocumentInteractor, DocumentDomainService],
exports: [DocumentInteractor, DocumentDomainService],
providers: [DocumentDomainInteractor, DocumentDomainService],
exports: [DocumentDomainInteractor, DocumentDomainService],
})
export class DocumentDomainModule {}
@@ -1,17 +1,53 @@
import { Inject, Injectable } from '@nestjs/common';
import { DOCUMENT_REPOSITORY, DocumentRepository } from '../repository/document.repository';
import { Injectable } from '@nestjs/common';
import type { GenerateDocumentDomainInterface } from '../interfaces/generate-document-domain.interface';
import { DocumentDomainService } from '../services/document-domain.service';
import type { DocumentDomainEntity } from '../entity/document-domain.entity';
import type { PaginationResultDomainInterface } from '~/domain/common/interfaces/pagination.interface';
import type { DocumentPackageDomainInterface } from '~/domain/agenda/interfaces/document-package-domain.interface';
import type { GetDocumentsInputDomainInterface } from '../interfaces/get-documents-input-domain.interface';
import { toDotNotation } from '~/utils/toDotNotation';
import { getActions } from '~/utils/getFetch';
import { SovietContract } from 'cooptypes';
@Injectable()
export class DocumentInteractor {
constructor(
@Inject(DOCUMENT_REPOSITORY) private readonly documentRepository: DocumentRepository,
private readonly documentDomainService: DocumentDomainService
) {}
export class DocumentDomainInteractor {
constructor(private readonly documentDomainService: DocumentDomainService) {}
public async generateDocument(data: GenerateDocumentDomainInterface): Promise<DocumentDomainEntity> {
return await this.documentDomainService.generateDocument(data);
}
public async getDocuments(
data: GetDocumentsInputDomainInterface
): Promise<PaginationResultDomainInterface<DocumentPackageDomainInterface>> {
const { type = 'newsubmitted', page = 1, limit = 100, query } = data;
const actions = await getActions<SovietContract.Actions.Registry.NewResolved.INewResolved>(
`${process.env.SIMPLE_EXPLORER_API}/get-actions`,
{
filter: JSON.stringify({
account: SovietContract.contractName.production,
name: type,
...toDotNotation(query),
}),
page,
limit,
}
);
const response: PaginationResultDomainInterface<DocumentPackageDomainInterface> = {
items: [],
totalCount: actions.results.length,
totalPages: 0,
currentPage: page,
};
for (const raw_action_document of actions.results) {
const documentPackage = await this.documentDomainService.buildDocumentPackage(raw_action_document);
if (documentPackage.statement.action) response.items.push(documentPackage);
}
return response;
}
}
@@ -0,0 +1,6 @@
export interface GetDocumentsInputDomainInterface {
type?: 'newsubmitted' | 'newresolved';
query: Record<string, unknown>;
page?: number;
limit?: number;
}
@@ -3,6 +3,15 @@ import { DOCUMENT_REPOSITORY, DocumentRepository } from '../repository/document.
import { GeneratorInfrastructureService } from '~/infrastructure/generator/generator.service';
import type { GenerateDocumentDomainInterface } from '../interfaces/generate-document-domain.interface';
import { DocumentDomainEntity } from '../entity/document-domain.entity';
import { Cooperative, SovietContract } from 'cooptypes';
import type { GeneratedDocumentDomainInterface } from '~/domain/document/interfaces/generated-document-domain.interface';
import { User } from '~/models';
import { getActions } from '~/utils/getFetch';
import type { DocumentPackageDomainInterface } from '~/domain/agenda/interfaces/document-package-domain.interface';
import type { ActDetailDomainInterface } from '~/domain/agenda/interfaces/act-detail-domain.interface';
import type { DecisionDetailDomainInterface } from '~/domain/agenda/interfaces/decision-detail-domain.interface';
import type { ExtendedBlockchainActionDomainInterface } from '~/domain/agenda/interfaces/extended-blockchain-action-domain.interface';
import type { StatementDetailDomainInterface } from '~/domain/agenda/interfaces/statement-detail-domain.interface';
@Injectable()
export class DocumentDomainService {
@@ -20,4 +29,130 @@ export class DocumentDomainService {
if (!document) throw new BadRequestException('Документ не найден');
return document;
}
/**
* Получает rawAction (блокчейн-действие), выстраивает DocumentPackage,
* включающее заявление (statement), решение (decision), акты (acts) и связанные документы (links).
*/
async buildDocumentPackage(rawAction: Cooperative.Blockchain.IAction): Promise<DocumentPackageDomainInterface> {
// Инициализация частей будущего DocumentPackage
let statementDetail: StatementDetailDomainInterface | null = null;
let decisionDetail: DecisionDetailDomainInterface | null = null;
const actDetails: ActDetailDomainInterface[] = [];
const links: GeneratedDocumentDomainInterface[] = [];
// Извлекаем данные (newSubmitted) из rawAction
const rawData = rawAction.data as SovietContract.Actions.Registry.NewSubmitted.INewSubmitted;
// -----------------------------------------------------
// ШАГ 1: Подготовка ЗАЯВЛЕНИЯ (Statement)
// -----------------------------------------------------
{
// Основной документ (statement)
const mainDocument = await this.getDocumentByHash(rawData.document.hash);
// Если у документа есть ссылки, подгружаем их
if (mainDocument?.meta?.links && Array.isArray(mainDocument.meta.links)) {
for (const linkHash of mainDocument.meta.links) {
const linkedDoc = await this.getDocumentByHash(linkHash);
if (linkedDoc) links.push(linkedDoc);
}
}
// Ищем пользователя, оформившего заявление
const user = await User.findOne({ username: rawData.username });
if (user) {
const userData = await user.getPrivateData();
// Формируем расширенный экшен
const extendedAction: Cooperative.Blockchain.IExtendedAction = {
...rawAction,
user: userData,
};
statementDetail = {
action: extendedAction,
document: mainDocument,
};
}
}
// -----------------------------------------------------
// ШАГ 2: Подготовка РЕШЕНИЯ (Decision)
// -----------------------------------------------------
{
// Ищем экшен, соответствующий созданию решения (NewDecision)
const decisionActionResponse = await getActions(`${process.env.SIMPLE_EXPLORER_API}/get-actions`, {
filter: JSON.stringify({
account: SovietContract.contractName.production,
name: SovietContract.Actions.Registry.NewDecision.actionName,
receiver: process.env.COOPNAME,
'data.decision_id': String(rawData.decision_id),
}),
page: 1,
limit: 1,
});
const decisionAction = decisionActionResponse?.results?.[0];
if (decisionAction) {
const user = await User.findOne({ username: decisionAction.data?.username });
if (user) {
const userData = await user.getPrivateData();
const extendedDecisionAction: Cooperative.Blockchain.IExtendedAction = {
...decisionAction,
user: userData ?? null,
};
// Документ, прикреплённый к решению
const decisionDocument = await this.getDocumentByHash(decisionAction?.data?.document?.hash);
if (decisionDocument?.meta?.links && Array.isArray(decisionDocument.meta.links)) {
for (const linkHash of decisionDocument.meta.links) {
const linkedDoc = await this.getDocumentByHash(linkHash);
if (linkedDoc) links.push(linkedDoc);
}
}
decisionDetail = {
action: extendedDecisionAction,
document: decisionDocument,
votes_for: [],
votes_against: [],
};
}
}
}
// -----------------------------------------------------
// ШАГ 3: Подготовка АКТОВ (Acts)
// -----------------------------------------------------
// Здесь в примере просто пустой массив,
// но если нужно обрабатывать акты, добавьте логику получения
// и пушите объекты вида `ActDetailDomainInterface`.
// actDetails.push({ action: ..., document: ... });
// -----------------------------------------------------
// ШАГ 4: Возвращаем итоговый объект DocumentPackage
// -----------------------------------------------------
// Если statementDetail или decisionDetail вдруг оказались null —
// можно обработать это или бросить ошибку, в зависимости от бизнес-логики.
// Здесь для примера присваиваем пустые объекты:
const statement = statementDetail || ({ action: {}, document: {} } as StatementDetailDomainInterface);
const decision: DecisionDetailDomainInterface =
decisionDetail ||
({
action: {} as unknown as ExtendedBlockchainActionDomainInterface,
document: {} as unknown as GeneratedDocumentDomainInterface,
votes_for: [],
votes_against: [],
} as unknown as DecisionDetailDomainInterface);
return {
statement,
decision,
acts: actDetails,
links,
};
}
}
@@ -8,11 +8,18 @@ import { ProviderDomainModule } from './provider/provider.module';
import { SystemDomainModule } from './system/system-domain.module';
import { BranchDomainModule } from './branch/branch-domain.module';
import { DocumentDomainModule } from './document/document.module';
import { DecisionDomainModule } from './decision/decision.module';
import { FreeDecisionDomainModule } from './free-decision/free-decision.module';
import { AgreementDomainModule } from './agreement/agreement-domain.module';
import { ParticipantDomainModule } from './participant/participant-domain.module';
import { AuthDomainModule } from './auth/auth.module';
import { AgendaDomainModule } from './agenda/agenda-domain.module';
@Module({
imports: [
AuthDomainModule,
AgendaDomainModule,
AccountDomainModule,
AgreementDomainModule,
ExtensionDomainModule,
PaymentDomainModule,
PaymentMethodDomainModule,
@@ -20,10 +27,14 @@ import { DecisionDomainModule } from './decision/decision.module';
SystemDomainModule,
BranchDomainModule,
DocumentDomainModule,
DecisionDomainModule,
FreeDecisionDomainModule,
ParticipantDomainModule,
],
exports: [
AuthDomainModule,
AgendaDomainModule,
AccountDomainModule,
AgreementDomainModule,
ExtensionDomainModule,
PaymentDomainModule,
PaymentMethodDomainModule,
@@ -31,7 +42,8 @@ import { DecisionDomainModule } from './decision/decision.module';
SystemDomainModule,
BranchDomainModule,
DocumentDomainModule,
DecisionDomainModule,
FreeDecisionDomainModule,
ParticipantDomainModule,
],
})
export class DomainModule {}
@@ -0,0 +1,12 @@
// domain/appstore/appstore-domain.module.ts
import { Module } from '@nestjs/common';
import { FreeDecisionDomainInteractor } from './interactors/free-decision.interactor';
import { DocumentDomainModule } from '../document/document.module';
@Module({
imports: [DocumentDomainModule],
providers: [FreeDecisionDomainInteractor],
exports: [FreeDecisionDomainInteractor],
})
export class FreeDecisionDomainModule {}
@@ -4,7 +4,7 @@ import { DocumentDomainEntity } from '~/domain/document/entity/document-domain.e
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import type { PublishProjectFreeDecisionInputDomainInterface } from '../interfaces/publish-project-free-decision.interface';
import config from '~/config/config';
import { DECISION_BLOCKCHAIN_PORT, DecisionBlockchainPort } from '../interfaces/decision-blockchain.port';
import { FREE_DECISION_BLOCKCHAIN_PORT, FreeDecisionBlockchainPort } from '../interfaces/free-decision-blockchain.port';
import {
PROJECT_FREE_DECISION_REPOSITORY,
ProjectFreeDecisionRepository,
@@ -12,11 +12,11 @@ import {
import { ProjectFreeDecisionDomainEntity } from '~/domain/branch/entities/project-free-decision.entity';
@Injectable()
export class DecisionDomainInteractor {
export class FreeDecisionDomainInteractor {
constructor(
private readonly documentDomainService: DocumentDomainService,
@Inject(PROJECT_FREE_DECISION_REPOSITORY) private readonly projectDecisionRepository: ProjectFreeDecisionRepository,
@Inject(DECISION_BLOCKCHAIN_PORT) private readonly decisionBlockchainPort: DecisionBlockchainPort
@Inject(FREE_DECISION_BLOCKCHAIN_PORT) private readonly FreeDecisionBlockchainPort: FreeDecisionBlockchainPort
) {}
async createProjectOfFreeDecision(data: Cooperative.Document.IProjectData): Promise<ProjectFreeDecisionDomainEntity> {
@@ -32,6 +32,14 @@ export class DecisionDomainInteractor {
return await this.documentDomainService.generateDocument({ data, options });
}
async generateFreeDecisionDocument(
data: Cooperative.Registry.FreeDecision.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.FreeDecision.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
async publishProjectOfFreeDecision(data: PublishProjectFreeDecisionInputDomainInterface): Promise<boolean> {
const document = await this.documentDomainService.getDocumentByHash(data.document.hash);
@@ -43,7 +51,7 @@ export class DecisionDomainInteractor {
if (data.coopname != config.coopname)
throw new BadRequestException('Указанное имя аккаунта кооператива не обслуживается здесь');
await this.decisionBlockchainPort.publichProjectOfFreeDecision({
await this.FreeDecisionBlockchainPort.publichProjectOfFreeDecision({
coopname: data.coopname,
username: data.username,
meta: data.meta,
@@ -1,7 +1,7 @@
import { SovietContract } from 'cooptypes';
import type { TransactResult } from '@wharfkit/session';
export interface DecisionBlockchainPort {
export interface FreeDecisionBlockchainPort {
getProjects(coopname: string): Promise<SovietContract.Tables.Decisions.IDecision[]>;
getProject(coopname: string, decision_id: string): Promise<SovietContract.Tables.Decisions.IDecision | null>;
@@ -10,4 +10,4 @@ export interface DecisionBlockchainPort {
): Promise<TransactResult>;
}
export const DECISION_BLOCKCHAIN_PORT = Symbol('DecisionBlockchainPort');
export const FREE_DECISION_BLOCKCHAIN_PORT = Symbol('FreeDecisionBlockchainPort');
@@ -0,0 +1,69 @@
import { Cooperative } from 'cooptypes';
import { DocumentDomainService } from '~/domain/document/services/document-domain.service';
import { DocumentDomainEntity } from '~/domain/document/entity/document-domain.entity';
import { Injectable } from '@nestjs/common';
import type { AddParticipantDomainInterface } from '../interfaces/add-participant-domain.interface';
import type { RegisterAccountDomainInterface } from '~/domain/account/interfaces/register-account-input.interface';
import { AccountDomainService } from '~/domain/account/services/account-domain.service';
import type { AccountDomainEntity } from '~/domain/account/entities/account-domain.entity';
import config from '~/config/config';
import { emailService, participantService, tokenService } from '~/services';
import type { RegisterParticipantDomainInterface } from '../interfaces/register-participant-domain.interface';
@Injectable()
export class ParticipantDomainInteractor {
constructor(
private readonly documentDomainService: DocumentDomainService,
private readonly accountDomainService: AccountDomainService
) {}
async generateParticipantApplication(
data: Cooperative.Registry.ParticipantApplication.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.ParticipantApplication.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
async generateParticipantApplicationDecision(
data: Cooperative.Registry.DecisionOfParticipantApplication.Action,
options: Cooperative.Document.IGenerationOptions
): Promise<DocumentDomainEntity> {
data.registry_id = Cooperative.Registry.DecisionOfParticipantApplication.registry_id;
return await this.documentDomainService.generateDocument({ data, options });
}
async registerParticipant(data: RegisterParticipantDomainInterface): Promise<AccountDomainEntity> {
await participantService.joinCooperative(data);
return await this.accountDomainService.getAccount(data.username);
}
async addParticipant(data: AddParticipantDomainInterface): Promise<AccountDomainEntity> {
const newAccount: RegisterAccountDomainInterface = {
...data,
public_key: '',
username: data.username,
};
await this.accountDomainService.addProviderAccount(newAccount);
await this.accountDomainService.addParticipantAccount({
registrator: config.coopname,
referer: data.referer ? data.referer : '',
coopname: config.coopname,
meta: '',
username: data.username,
type: data.type,
created_at: data.created_at,
initial: data.initial,
minimum: data.minimum,
spread_initial: data.spread_initial,
});
//TODO move it to hexagon services
const token = await tokenService.generateInviteToken(data.email);
await emailService.sendInviteEmail(data.email, token);
return this.accountDomainService.getAccount(data.username);
}
}
@@ -0,0 +1,23 @@
import type { Cooperative } from 'cooptypes';
import type { RegisterRole } from '~/modules/account/enum/account-role-on-register.enum';
import type { AccountType } from '~/modules/account/enum/account-type.enum';
export interface AddParticipantDomainInterface {
email: string;
entrepreneur_data?: Omit<Cooperative.Users.IEntrepreneurData, 'username'> & {
bank_account: Cooperative.Payments.IBankAccount;
};
individual_data?: Omit<Cooperative.Users.IIndividualData, 'username'>;
organization_data?: Omit<Cooperative.Users.IOrganizationData, 'username'> & {
bank_account: Cooperative.Payments.IBankAccount;
};
public_key?: string;
referer?: string;
role: RegisterRole;
type: AccountType;
username: string;
created_at: string;
initial: string;
minimum: string;
spread_initial: boolean;
}
@@ -0,0 +1,10 @@
import type { Cooperative } from 'cooptypes';
export interface RegisterParticipantDomainInterface {
username: string;
privacy_agreement: Cooperative.Document.ISignedDocument<Cooperative.Registry.PrivacyPolicy.Action>;
signature_agreement: Cooperative.Document.ISignedDocument<Cooperative.Registry.RegulationElectronicSignature.Action>;
statement: Cooperative.Document.ISignedDocument<Cooperative.Registry.ParticipantApplication.Action>;
user_agreement: Cooperative.Document.ISignedDocument<Cooperative.Registry.UserAgreement.Action>;
wallet_agreement: Cooperative.Document.ISignedDocument<Cooperative.Registry.WalletAgreement.Action>;
}
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { DocumentDomainModule } from '../document/document.module';
import { ParticipantDomainInteractor } from './interactors/participant-domain.interactor';
import { AccountDomainModule } from '../account/account-domain.module';
@Module({
imports: [AccountDomainModule, DocumentDomainModule],
providers: [ParticipantDomainInteractor],
exports: [ParticipantDomainInteractor],
})
export class ParticipantDomainModule {}
@@ -1,13 +1,28 @@
import { Injectable } from '@nestjs/common';
import { BadGatewayException, Injectable } from '@nestjs/common';
import { BlockchainService } from '../blockchain.service';
import { RegistratorContract, SovietContract } from 'cooptypes';
import type { BlockchainAccountInterface } from '~/types/shared';
import type { AccountBlockchainPort } from '~/domain/account/interfaces/account-blockchain.port';
import Vault from '~/models/vault.model';
@Injectable()
export class AccountBlockchainAdapter implements AccountBlockchainPort {
constructor(private readonly blockchainService: BlockchainService) {}
async addParticipantAccount(data: RegistratorContract.Actions.AddUser.IAddUser): Promise<void> {
const wif = await Vault.getWif(data.coopname);
if (!wif) throw new BadGatewayException('Не найден приватный ключ для совершения операции');
await this.blockchainService.initialize(data.coopname, wif);
await this.blockchainService.transact({
account: RegistratorContract.contractName.production,
name: RegistratorContract.Actions.AddUser.actionName,
authorization: [{ actor: data.coopname, permission: 'active' }],
data,
});
}
getBlockchainAccount(username: string): Promise<BlockchainAccountInterface | null> {
return this.blockchainService.getAccount(username);
}
@@ -5,10 +5,10 @@ import { TransactResult } from '@wharfkit/session';
import Vault from '~/models/vault.model';
import httpStatus from 'http-status';
import { HttpApiError } from '~/errors/http-api-error';
import type { DecisionBlockchainPort } from '~/domain/decision/interfaces/decision-blockchain.port';
import type { FreeDecisionBlockchainPort } from '~/domain/free-decision/interfaces/free-decision-blockchain.port';
@Injectable()
export class DecisionBlockchainAdapter implements DecisionBlockchainPort {
export class DecisionBlockchainAdapter implements FreeDecisionBlockchainPort {
constructor(private readonly blockchainService: BlockchainService) {}
async getProjects(coopname: string): Promise<SovietContract.Tables.Decisions.IDecision[]> {
@@ -7,8 +7,8 @@ import { SystemBlockchainAdapter } from './adapters/system.adapter';
import { SYSTEM_BLOCKCHAIN_PORT } from '~/domain/system/interfaces/system-blockchain.port';
import { ACCOUNT_BLOCKCHAIN_PORT } from '~/domain/account/interfaces/account-blockchain.port';
import { AccountBlockchainAdapter } from './adapters/account.adapter';
import { DECISION_BLOCKCHAIN_PORT } from '~/domain/decision/interfaces/decision-blockchain.port';
import { DecisionBlockchainAdapter } from './adapters/decision-blockchain.adapter';
import { DecisionBlockchainAdapter } from './adapters/free-decision-blockchain.adapter';
import { FREE_DECISION_BLOCKCHAIN_PORT } from '~/domain/free-decision/interfaces/free-decision-blockchain.port';
@Global()
@Module({
@@ -31,7 +31,7 @@ import { DecisionBlockchainAdapter } from './adapters/decision-blockchain.adapte
useClass: AccountBlockchainAdapter,
},
{
provide: DECISION_BLOCKCHAIN_PORT,
provide: FREE_DECISION_BLOCKCHAIN_PORT,
useClass: DecisionBlockchainAdapter,
},
],
@@ -40,7 +40,7 @@ import { DecisionBlockchainAdapter } from './adapters/decision-blockchain.adapte
BRANCH_BLOCKCHAIN_PORT,
SYSTEM_BLOCKCHAIN_PORT,
ACCOUNT_BLOCKCHAIN_PORT,
DECISION_BLOCKCHAIN_PORT,
FREE_DECISION_BLOCKCHAIN_PORT,
],
})
export class BlockchainModule {}
@@ -1,4 +1,5 @@
import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils';
import { UnauthorizedException } from '@nestjs/common';
import { GraphQLSchema, defaultFieldResolver } from 'graphql';
import config from '~/config/config';
@@ -33,11 +34,10 @@ export function fieldAuthDirectiveTransformer(schema: GraphQLSchema, directiveNa
// Проверка соответствия ролей
const hasAccess = requiredRoles.includes(user.role);
if (!hasAccess) {
throw new Error(
if (!hasAccess)
throw new UnauthorizedException(
`Недостаточно прав доступа к полю "${info.fieldName}". Требуемые роли: ${requiredRoles.join(', ')}.`
);
}
return resolve(source, args, context, info);
};
@@ -25,7 +25,7 @@ const tokenSchema = new Schema<IToken>(
},
type: {
type: String,
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL, tokenTypes.INVITE],
enum: [tokenTypes.REFRESH, tokenTypes.RESET_KEY, tokenTypes.VERIFY_EMAIL, tokenTypes.INVITE],
required: true,
},
expires: {
@@ -29,7 +29,7 @@ export class AccountDTO {
@Field(() => MonoAccountDTO, { description: 'Объект аккаунта в системе учёта провайдера', nullable: true })
@ValidateNested()
@Type(() => MonoAccountDTO)
public readonly mono_account!: MonoAccountDTO | null;
public readonly provider_account!: MonoAccountDTO | null;
@Field(() => ParticipantAccountDTO, { description: 'Объект пайщика кооператива', nullable: true })
@ValidateNested()
@@ -39,7 +39,7 @@ export class AccountDTO {
constructor(entity: AccountDomainEntity) {
this.username = entity.username;
this.blockchain_account = entity.blockchain_account || null;
this.mono_account = entity.mono_account ? new MonoAccountDTO(entity.mono_account) : null;
this.provider_account = entity.provider_account ? new MonoAccountDTO(entity.provider_account) : null;
this.user_account = entity.user_account ? new UserAccountDTO(entity.user_account) : null;
this.participant_account = entity.participant_account ? new ParticipantAccountDTO(entity.participant_account) : null;
//TODO cardcoop_account
@@ -0,0 +1,51 @@
import { Field, InputType } from '@nestjs/graphql';
import { BankAccountInputDTO } from '~/modules/payment-method/dto/bank-account-input.dto';
import { Country } from '../enum/country.enum';
import { EntrepreneurDetailsInputDTO } from './entrepreneur-details-input.dto';
import { IsNotEmpty } from 'class-validator';
@InputType('CreateEntrepreneurDataInput')
export class CreateEntrepreneurDataInputDTO {
@Field(() => BankAccountInputDTO, { description: 'Банковский счет' })
@IsNotEmpty({ message: 'Поле "bank_account" обязательно для заполнения.' })
bank_account!: BankAccountInputDTO;
@Field({ description: 'Дата рождения' })
@IsNotEmpty({ message: 'Поле "birthdate" обязательно для заполнения.' })
birthdate!: string;
@Field({ description: 'Город' })
@IsNotEmpty({ message: 'Поле "city" обязательно для заполнения.' })
city!: string;
@Field(() => Country, { description: 'Страна' })
@IsNotEmpty({ message: 'Поле "country" обязательно для заполнения.' })
country!: Country;
@Field(() => EntrepreneurDetailsInputDTO, { description: 'Детали индивидуального предпринимателя' })
@IsNotEmpty({ message: 'Поле "details" обязательно для заполнения.' })
details!: EntrepreneurDetailsInputDTO;
//поле не принимаем - устанавливаем автоматически
email!: string;
@Field({ description: 'Имя' })
@IsNotEmpty({ message: 'Поле "first_name" обязательно для заполнения.' })
first_name!: string;
@Field({ description: 'Полный адрес' })
@IsNotEmpty({ message: 'Поле "full_address" обязательно для заполнения.' })
full_address!: string;
@Field({ description: 'Фамилия' })
@IsNotEmpty({ message: 'Поле "last_name" обязательно для заполнения.' })
last_name!: string;
@Field({ description: 'Отчество' })
@IsNotEmpty({ message: 'Поле "middle_name" обязательно для заполнения.' })
middle_name!: string;
@Field({ description: 'Телефон' })
@IsNotEmpty({ message: 'Поле "phone" обязательно для заполнения.' })
phone!: string;
}
@@ -0,0 +1,38 @@
import { InputType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsOptional, ValidateNested } from 'class-validator';
import { PassportInputDTO } from './passport-input.dto';
@InputType('CreateIndividualDataInput')
export class CreateIndividualDataInputDTO {
@Field({ description: 'Дата рождения' })
@IsNotEmpty({ message: 'Поле "birthdate" обязательно для заполнения.' })
birthdate!: string;
//поле не принимаем - устанавливаем автоматически
email!: string;
@Field({ description: 'Имя' })
@IsNotEmpty({ message: 'Поле "first_name" обязательно для заполнения.' })
first_name!: string;
@Field({ description: 'Полный адрес' })
@IsNotEmpty({ message: 'Поле "full_address" обязательно для заполнения.' })
full_address!: string;
@Field({ description: 'Фамилия' })
@IsNotEmpty({ message: 'Поле "last_name" обязательно для заполнения.' })
last_name!: string;
@Field({ description: 'Отчество' })
@IsNotEmpty({ message: 'Поле "middle_name" обязательно для заполнения.' })
middle_name!: string;
@Field(() => PassportInputDTO, { nullable: true, description: 'Данные паспорта' })
@IsOptional()
@ValidateNested()
passport?: PassportInputDTO;
@Field({ description: 'Телефон' })
@IsNotEmpty({ message: 'Поле "phone" обязательно для заполнения.' })
phone!: string;
}
@@ -0,0 +1,63 @@
import { InputType, Field } from '@nestjs/graphql';
import { ValidateNested, IsNotEmpty } from 'class-validator';
import { Type } from 'class-transformer';
import { BankAccountInputDTO } from '~/modules/payment-method/dto/bank-account-input.dto';
import { OrganizationType } from '../enum/organization-type.enum';
import { OrganizationDetailsInputDTO } from './organization-details-input.dto';
import { RepresentedByInputDTO } from './represented-by-input.dto';
@InputType('CreateOrganizationDataInput')
export class CreateOrganizationDataInputDTO {
@Field(() => BankAccountInputDTO, { description: 'Банковский счет организации' })
@ValidateNested()
@Type(() => BankAccountInputDTO)
@IsNotEmpty({ message: 'Поле "bank_account" обязательно для заполнения.' })
bank_account!: BankAccountInputDTO;
@Field({ description: 'Город' })
@IsNotEmpty({ message: 'Поле "city" обязательно для заполнения.' })
city!: string;
@Field({ description: 'Страна' })
@IsNotEmpty({ message: 'Поле "country" обязательно для заполнения.' })
country!: string;
@Field(() => OrganizationDetailsInputDTO, { description: 'Детали организации' })
@ValidateNested()
@Type(() => OrganizationDetailsInputDTO)
@IsNotEmpty({ message: 'Поле "details" обязательно для заполнения.' })
details!: OrganizationDetailsInputDTO;
//поле не принимаем - устанавливаем автоматически
email!: string;
@Field({ description: 'Фактический адрес' })
@IsNotEmpty({ message: 'Поле "fact_address" обязательно для заполнения.' })
fact_address!: string;
@Field({ description: 'Полный адрес' })
@IsNotEmpty({ message: 'Поле "full_address" обязательно для заполнения.' })
full_address!: string;
@Field({ description: 'Полное наименование организации' })
@IsNotEmpty({ message: 'Поле "full_name" обязательно для заполнения.' })
full_name!: string;
@Field({ description: 'Телефон' })
@IsNotEmpty({ message: 'Поле "phone" обязательно для заполнения.' })
phone!: string;
@Field(() => RepresentedByInputDTO, { description: 'Представитель организации' })
@ValidateNested()
@Type(() => RepresentedByInputDTO)
@IsNotEmpty({ message: 'Поле "represented_by" обязательно для заполнения.' })
represented_by!: RepresentedByInputDTO;
@Field({ description: 'Краткое наименование организации' })
@IsNotEmpty({ message: 'Поле "short_name" обязательно для заполнения.' })
short_name!: string;
@Field(() => OrganizationType, { description: 'Тип организации' })
@IsNotEmpty({ message: 'Поле "type" обязательно для заполнения.' })
type!: OrganizationType;
}
@@ -0,0 +1,9 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';
@InputType('DeleteAccountInput')
export class DeleteAccountInputDTO {
@Field({ description: 'Имя аккаунта пользователя' })
@IsNotEmpty({ message: 'Поле "username_for_delete" обязательно для заполнения.' })
username_for_delete!: string;
}
@@ -0,0 +1,10 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType('EntrepreneurDetailsInput')
export class EntrepreneurDetailsInputDTO {
@Field(() => String, { description: 'ИНН' })
inn!: string;
@Field(() => String, { description: 'ОГРН' })
ogrn!: string;
}
@@ -0,0 +1,10 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType('GetAccountsInput')
export class GetAccountsInputDTO {
@Field({ nullable: true })
username?: string;
@Field({ nullable: true })
role?: string;
}
@@ -0,0 +1,13 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType('OrganizationDetailsInput')
export class OrganizationDetailsInputDTO {
@Field()
inn!: string;
@Field()
kpp!: string;
@Field()
ogrn!: string;
}
@@ -0,0 +1,19 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType('PassportInput')
export class PassportInputDTO {
@Field()
code!: string;
@Field()
issued_at!: string;
@Field()
issued_by!: string;
@Field()
number!: number;
@Field()
series!: number;
}
@@ -0,0 +1,59 @@
import { InputType, Field } from '@nestjs/graphql';
import { AccountType } from '../enum/account-type.enum';
import { RegisterRole } from '../enum/account-role-on-register.enum';
import { CreateEntrepreneurDataInputDTO } from './create-entrepreneur-data-input.dto';
import { CreateIndividualDataInputDTO } from './create-individual-data-input.dto';
import { CreateOrganizationDataInputDTO } from './create-organization-data-input.dto';
import { IsNotEmpty, IsOptional, ValidateIf, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
@InputType('RegisterAccountInput')
export class RegisterAccountInputDTO {
@Field({ description: 'Электронная почта' })
@IsNotEmpty({ message: 'Поле "email" обязательно для заполнения.' })
email!: string;
@Field({ nullable: true, description: 'Имя аккаунта реферера' })
@IsOptional()
referer?: string;
@Field(() => RegisterRole, { description: 'Роль пользователя' })
@IsNotEmpty({ message: 'Поле "role" обязательно для заполнения.' })
role!: RegisterRole;
@Field(() => AccountType, { description: 'Тип аккаунта' })
@IsNotEmpty({ message: 'Поле "type" обязательно для заполнения.' })
type!: AccountType;
@Field({ description: 'Имя пользователя' })
@IsNotEmpty({ message: 'Поле "username" обязательно для заполнения.' })
username!: string;
@Field({ nullable: true, description: 'Публичный ключ' })
@IsOptional()
public_key?: string;
@Field(() => CreateEntrepreneurDataInputDTO, { nullable: true, description: 'Данные индивидуального предпринимателя' })
@ValidateNested()
@Type(() => CreateEntrepreneurDataInputDTO)
@IsOptional()
entrepreneur_data?: CreateEntrepreneurDataInputDTO;
@Field(() => CreateIndividualDataInputDTO, { nullable: true, description: 'Данные физического лица' })
@ValidateNested()
@Type(() => CreateIndividualDataInputDTO)
@IsOptional()
individual_data?: CreateIndividualDataInputDTO;
@Field(() => CreateOrganizationDataInputDTO, { nullable: true, description: 'Данные организации' })
@ValidateNested()
@Type(() => CreateOrganizationDataInputDTO)
@IsOptional()
organization_data?: CreateOrganizationDataInputDTO;
@ValidateIf((o: RegisterAccountInputDTO) => !o.entrepreneur_data && !o.individual_data && !o.organization_data)
@IsNotEmpty({
message: 'Необходимо указать хотя бы одно из полей: "entrepreneur_data", "individual_data" или "organization_data".',
})
validateOneTypePresent!: boolean;
}
@@ -0,0 +1,21 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { AccountDTO } from './account.dto';
import { TokensDTO } from './tokens.dto';
import { ValidateNested } from 'class-validator';
import type { RegisteredAccountDomainInterface } from '~/domain/account/interfaces/registeted-account.interface';
@ObjectType('RegisteredAccount')
export class RegisteredAccountDTO {
@Field(() => AccountDTO, { description: 'Информация об зарегистрированном аккаунте' })
@ValidateNested()
account!: AccountDTO;
@Field(() => TokensDTO, { description: 'Токены доступа и обновления' })
@ValidateNested()
tokens!: TokensDTO;
constructor(data: RegisteredAccountDomainInterface) {
this.account = new AccountDTO(data.account);
this.tokens = data.tokens;
}
}
@@ -0,0 +1,19 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType('RepresentedByInput')
export class RepresentedByInputDTO {
@Field()
based_on!: string;
@Field()
first_name!: string;
@Field()
last_name!: string;
@Field()
middle_name!: string;
@Field()
position!: string;
}
@@ -0,0 +1,10 @@
import { ObjectType, Field } from '@nestjs/graphql';
@ObjectType('Token')
export class TokenDTO {
@Field({ description: 'Токен доступа' })
token!: string;
@Field({ description: 'Дата истечения токена доступа' })
expires!: Date;
}
@@ -0,0 +1,11 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { TokenDTO } from './token.dto';
@ObjectType('Tokens')
export class TokensDTO {
@Field(() => TokenDTO, { description: 'Токен доступа' })
access!: TokenDTO;
@Field(() => TokenDTO, { description: 'Токен обновления' })
refresh!: TokenDTO;
}
@@ -0,0 +1,59 @@
import { InputType, Field } from '@nestjs/graphql';
import { AccountType } from '../enum/account-type.enum';
import { RegisterRole } from '../enum/account-role-on-register.enum';
import { IsNotEmpty, IsOptional, ValidateIf, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { UpdateEntrepreneurDataInputDTO } from './update-entrepreneur-data-input.dto';
import { UpdateIndividualDataInputDTO } from './update-individual-data-input.dto';
import { UpdateOrganizationDataInputDTO } from './update-organization-data-input.dto';
@InputType('UpdateAccountInput')
export class UpdateAccountInputDTO {
@Field({ description: 'Электронная почта' })
@IsNotEmpty({ message: 'Поле "email" обязательно для заполнения.' })
email!: string;
@Field({ nullable: true, description: 'Имя аккаунта реферера' })
@IsOptional()
referer?: string;
@Field(() => RegisterRole, { description: 'Роль пользователя' })
@IsNotEmpty({ message: 'Поле "role" обязательно для заполнения.' })
role!: RegisterRole;
@Field(() => AccountType, { description: 'Тип аккаунта' })
@IsNotEmpty({ message: 'Поле "type" обязательно для заполнения.' })
type!: AccountType;
@Field({ description: 'Имя пользователя' })
@IsNotEmpty({ message: 'Поле "username" обязательно для заполнения.' })
username!: string;
@Field({ nullable: true, description: 'Публичный ключ' })
@IsOptional()
public_key?: string;
@Field(() => UpdateEntrepreneurDataInputDTO, { nullable: true, description: 'Данные индивидуального предпринимателя' })
@ValidateNested()
@Type(() => UpdateEntrepreneurDataInputDTO)
@IsOptional()
entrepreneur_data?: UpdateEntrepreneurDataInputDTO;
@Field(() => UpdateIndividualDataInputDTO, { nullable: true, description: 'Данные физического лица' })
@ValidateNested()
@Type(() => UpdateIndividualDataInputDTO)
@IsOptional()
individual_data?: UpdateIndividualDataInputDTO;
@Field(() => UpdateOrganizationDataInputDTO, { nullable: true, description: 'Данные организации' })
@ValidateNested()
@Type(() => UpdateOrganizationDataInputDTO)
@IsOptional()
organization_data?: UpdateOrganizationDataInputDTO;
@ValidateIf((o: UpdateAccountInputDTO) => !o.entrepreneur_data && !o.individual_data && !o.organization_data)
@IsNotEmpty({
message: 'Необходимо указать хотя бы одно из полей: "entrepreneur_data", "individual_data" или "organization_data".',
})
validateOneTypePresent!: boolean;
}
@@ -0,0 +1,47 @@
import { Field, InputType } from '@nestjs/graphql';
import { BankAccountInputDTO } from '~/modules/payment-method/dto/bank-account-input.dto';
import { Country } from '../enum/country.enum';
import { EntrepreneurDetailsInputDTO } from './entrepreneur-details-input.dto';
import { IsNotEmpty } from 'class-validator';
@InputType('UpdateEntrepreneurDataInput')
export class UpdateEntrepreneurDataInputDTO {
@Field({ description: 'Дата рождения' })
@IsNotEmpty({ message: 'Поле "birthdate" обязательно для заполнения.' })
birthdate!: string;
@Field({ description: 'Город' })
@IsNotEmpty({ message: 'Поле "city" обязательно для заполнения.' })
city!: string;
@Field(() => Country, { description: 'Страна' })
@IsNotEmpty({ message: 'Поле "country" обязательно для заполнения.' })
country!: Country;
@Field(() => EntrepreneurDetailsInputDTO, { description: 'Детали индивидуального предпринимателя' })
@IsNotEmpty({ message: 'Поле "details" обязательно для заполнения.' })
details!: EntrepreneurDetailsInputDTO;
//поле не принимаем - устанавливаем автоматически
email!: string;
@Field({ description: 'Имя' })
@IsNotEmpty({ message: 'Поле "first_name" обязательно для заполнения.' })
first_name!: string;
@Field({ description: 'Полный адрес' })
@IsNotEmpty({ message: 'Поле "full_address" обязательно для заполнения.' })
full_address!: string;
@Field({ description: 'Фамилия' })
@IsNotEmpty({ message: 'Поле "last_name" обязательно для заполнения.' })
last_name!: string;
@Field({ description: 'Отчество' })
@IsNotEmpty({ message: 'Поле "middle_name" обязательно для заполнения.' })
middle_name!: string;
@Field({ description: 'Телефон' })
@IsNotEmpty({ message: 'Поле "phone" обязательно для заполнения.' })
phone!: string;
}
@@ -0,0 +1,38 @@
import { InputType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsOptional, ValidateNested } from 'class-validator';
import { PassportInputDTO } from './passport-input.dto';
@InputType('UpdateIndividualDataInput')
export class UpdateIndividualDataInputDTO {
@Field({ description: 'Дата рождения' })
@IsNotEmpty({ message: 'Поле "birthdate" обязательно для заполнения.' })
birthdate!: string;
//поле не принимаем - устанавливаем автоматически
email!: string;
@Field({ description: 'Имя' })
@IsNotEmpty({ message: 'Поле "first_name" обязательно для заполнения.' })
first_name!: string;
@Field({ description: 'Полный адрес' })
@IsNotEmpty({ message: 'Поле "full_address" обязательно для заполнения.' })
full_address!: string;
@Field({ description: 'Фамилия' })
@IsNotEmpty({ message: 'Поле "last_name" обязательно для заполнения.' })
last_name!: string;
@Field({ description: 'Отчество' })
@IsNotEmpty({ message: 'Поле "middle_name" обязательно для заполнения.' })
middle_name!: string;
@Field(() => PassportInputDTO, { nullable: true, description: 'Данные паспорта' })
@IsOptional()
@ValidateNested()
passport?: PassportInputDTO;
@Field({ description: 'Телефон' })
@IsNotEmpty({ message: 'Поле "phone" обязательно для заполнения.' })
phone!: string;
}
@@ -0,0 +1,56 @@
import { InputType, Field } from '@nestjs/graphql';
import { ValidateNested, IsNotEmpty } from 'class-validator';
import { Type } from 'class-transformer';
import { OrganizationType } from '../enum/organization-type.enum';
import { OrganizationDetailsInputDTO } from './organization-details-input.dto';
import { RepresentedByInputDTO } from './represented-by-input.dto';
@InputType('UpdateOrganizationDataInput')
export class UpdateOrganizationDataInputDTO {
@Field({ description: 'Город' })
@IsNotEmpty({ message: 'Поле "city" обязательно для заполнения.' })
city!: string;
@Field({ description: 'Страна' })
@IsNotEmpty({ message: 'Поле "country" обязательно для заполнения.' })
country!: string;
@Field(() => OrganizationDetailsInputDTO, { description: 'Детали организации' })
@ValidateNested()
@Type(() => OrganizationDetailsInputDTO)
@IsNotEmpty({ message: 'Поле "details" обязательно для заполнения.' })
details!: OrganizationDetailsInputDTO;
//поле не принимаем - устанавливаем автоматически
email!: string;
@Field({ description: 'Фактический адрес' })
@IsNotEmpty({ message: 'Поле "fact_address" обязательно для заполнения.' })
fact_address!: string;
@Field({ description: 'Полный адрес' })
@IsNotEmpty({ message: 'Поле "full_address" обязательно для заполнения.' })
full_address!: string;
@Field({ description: 'Полное наименование организации' })
@IsNotEmpty({ message: 'Поле "full_name" обязательно для заполнения.' })
full_name!: string;
@Field({ description: 'Телефон' })
@IsNotEmpty({ message: 'Поле "phone" обязательно для заполнения.' })
phone!: string;
@Field(() => RepresentedByInputDTO, { description: 'Представитель организации' })
@ValidateNested()
@Type(() => RepresentedByInputDTO)
@IsNotEmpty({ message: 'Поле "represented_by" обязательно для заполнения.' })
represented_by!: RepresentedByInputDTO;
@Field({ description: 'Краткое наименование организации' })
@IsNotEmpty({ message: 'Поле "short_name" обязательно для заполнения.' })
short_name!: string;
@Field(() => OrganizationType, { description: 'Тип организации' })
@IsNotEmpty({ message: 'Поле "type" обязательно для заполнения.' })
type!: OrganizationType;
}
@@ -0,0 +1,6 @@
import { registerEnumType } from '@nestjs/graphql';
export enum RegisterRole {
User = 'user',
}
registerEnumType(RegisterRole, { name: 'RegisterRole', description: 'Роль пользователя при регистрации' });
@@ -0,0 +1,8 @@
import { registerEnumType } from '@nestjs/graphql';
export enum AccountType {
Individual = 'individual',
Entrepreneur = 'entrepreneur',
Organization = 'organization',
}
registerEnumType(AccountType, { name: 'AccountType', description: 'Тип аккаунта пользователя в системе' });
@@ -0,0 +1,6 @@
import { registerEnumType } from '@nestjs/graphql';
export enum Country {
Russia = 'Russia',
}
registerEnumType(Country, { name: 'Country', description: 'Страна регистрации пользователя' });
@@ -0,0 +1,11 @@
import { registerEnumType } from '@nestjs/graphql';
export enum OrganizationType {
COOP = 'coop',
OOO = 'ooo',
OAO = 'oao',
ZAO = 'zao',
PAO = 'pao',
AO = 'ao',
}
registerEnumType(OrganizationType, { name: 'OrganizationType', description: 'Тип юридического лица' });
@@ -1,4 +1,4 @@
import { Resolver, Query, Args } from '@nestjs/graphql';
import { Resolver, Query, Args, Mutation } from '@nestjs/graphql';
import { AccountService } from '../services/account.service';
import { AccountDTO } from '../dto/account.dto';
import { GetAccountInputDTO } from '../dto/get-account-input.dto';
@@ -6,6 +6,16 @@ import { UseGuards } from '@nestjs/common';
import { AuthRoles } from '~/modules/auth/decorators/auth.decorator';
import { GqlJwtAuthGuard } from '~/modules/auth/guards/graphql-jwt-auth.guard';
import { RolesGuard } from '~/modules/auth/guards/roles.guard';
import { createPaginationResult, PaginationInputDTO } from '~/modules/common/dto/pagination.dto';
import { GetAccountsInputDTO } from '../dto/get-accounts-input.dto';
import type { PaginationResultDomainInterface } from '~/domain/common/interfaces/pagination.interface';
import type { AccountDomainEntity } from '~/domain/account/entities/account-domain.entity';
import { RegisterAccountInputDTO } from '../dto/register-account-input.dto';
import { RegisteredAccountDTO } from '../dto/registered-account.dto';
import { DeleteAccountInputDTO } from '../dto/delete-account-input.dto';
import { UpdateAccountInputDTO } from '../dto/update-account-input.dto';
export const AccountsPaginationResult = createPaginationResult(AccountDTO, 'Accounts');
@Resolver(() => AccountDTO)
export class AccountResolver {
@@ -20,4 +30,54 @@ export class AccountResolver {
async getAccount(@Args('data', { type: () => GetAccountInputDTO }) input: GetAccountInputDTO): Promise<AccountDTO> {
return this.accountService.getAccount(input.username);
}
@Query(() => AccountsPaginationResult, {
name: 'getAccounts',
description: 'Получить сводную информацию о аккаунтах системы',
})
async getAccounts(
@Args('data', { type: () => GetAccountsInputDTO, nullable: true }) data: GetAccountsInputDTO,
@Args('options', { type: () => PaginationInputDTO, nullable: true }) options: PaginationInputDTO
): Promise<PaginationResultDomainInterface<AccountDomainEntity>> {
return await this.accountService.getAccounts(data, options);
}
@Mutation(() => RegisteredAccountDTO, {
name: 'registerAccount',
description: 'Зарегистрировать аккаунт пользователя в системе',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
async registerAccount(
@Args('data', { type: () => RegisterAccountInputDTO })
data: RegisterAccountInputDTO
): Promise<RegisteredAccountDTO> {
return this.accountService.registerAccount(data);
}
@Mutation(() => Boolean, {
name: 'deleteAccount',
description: 'Удалить аккаунт из системы учёта провайдера',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async deleteAccount(
@Args('data', { type: () => DeleteAccountInputDTO })
data: DeleteAccountInputDTO
): Promise<boolean> {
await this.accountService.deleteAccount(data);
return true;
}
@Mutation(() => AccountDTO, {
name: 'updateAccount',
description: 'Обновить аккаунт в системе провайдера',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
async updateAccount(
@Args('data', { type: () => UpdateAccountInputDTO })
data: UpdateAccountInputDTO
): Promise<AccountDTO> {
return await this.accountService.updateAccount(data);
}
}
@@ -1,14 +1,43 @@
import { Injectable } from '@nestjs/common';
import { AccountDTO } from '../dto/account.dto';
import { AccountDomainInteractor } from '~/domain/account/interactors/account.interactor';
import type { GetAccountsInputDTO } from '../dto/get-accounts-input.dto';
import type { PaginationInputDTO } from '~/modules/common/dto/pagination.dto';
import type { AccountDomainEntity } from '~/domain/account/entities/account-domain.entity';
import type { PaginationResultDomainInterface } from '~/domain/common/interfaces/pagination.interface';
import type { RegisterAccountInputDTO } from '../dto/register-account-input.dto';
import { RegisteredAccountDTO } from '../dto/registered-account.dto';
import type { DeleteAccountInputDTO } from '../dto/delete-account-input.dto';
import type { UpdateAccountInputDTO } from '../dto/update-account-input.dto';
@Injectable()
export class AccountService {
constructor(private readonly accountDomainInteractor: AccountDomainInteractor) {}
public async updateAccount(data: UpdateAccountInputDTO): Promise<AccountDTO> {
const result = await this.accountDomainInteractor.updateAccount(data);
return new AccountDTO(result);
}
public async deleteAccount(data: DeleteAccountInputDTO): Promise<void> {
await this.accountDomainInteractor.deleteAccount(data.username_for_delete);
}
public async getAccount(username: string): Promise<AccountDTO> {
const account = await this.accountDomainInteractor.getAccount(username);
return new AccountDTO(account);
}
public async getAccounts(
data: GetAccountsInputDTO,
options: PaginationInputDTO
): Promise<PaginationResultDomainInterface<AccountDomainEntity>> {
return await this.accountDomainInteractor.getAccounts(data, options);
}
public async registerAccount(data: RegisterAccountInputDTO): Promise<RegisteredAccountDTO> {
const result = await this.accountDomainInteractor.registerAccount(data);
return new RegisteredAccountDTO(result);
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { DomainModule } from '~/domain/domain.module';
import { AgendaResolver } from './resolvers/agenda.resolver';
import { AgendaService } from './services/agenda.service';
@Module({
imports: [DomainModule],
controllers: [],
providers: [AgendaResolver, AgendaService],
exports: [],
})
export class AgendaModule {}
@@ -0,0 +1,13 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
import type { ActDetailDomainInterface } from '~/domain/agenda/interfaces/act-detail-domain.interface';
import { ExtendedBlockchainActionDTO } from './extended-action.dto';
@ObjectType('ActDetail')
export class ActDetailDTO implements ActDetailDomainInterface {
@Field(() => ExtendedBlockchainActionDTO, { nullable: true })
action?: ExtendedBlockchainActionDTO;
@Field(() => GeneratedDocumentDTO, { nullable: true })
document?: GeneratedDocumentDTO;
}
@@ -0,0 +1,23 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { DocumentPackageDTO } from './document-package.dto';
import type { AgendaWithDocumentsDomainInterface } from '~/domain/agenda/interfaces/agenda-with-documents-domain.interface';
import { BlockchainActionDTO } from './blockchain-action.dto';
import { BlockchainDecisionDTO } from './blockchain-decision.dto';
@ObjectType('AgendaWithDocuments')
export class AgendaWithDocumentsDTO implements AgendaWithDocumentsDomainInterface {
@Field(() => BlockchainDecisionDTO, {
description: 'Запись в таблице блокчейна о вопросе на голосовании',
})
table!: BlockchainDecisionDTO;
@Field(() => BlockchainActionDTO, {
description: 'Действие, которое привело к появлению вопроса на голосовании',
})
action!: BlockchainActionDTO;
@Field(() => DocumentPackageDTO, {
description: 'Пакет документов, включающий разные подсекции',
})
documents!: DocumentPackageDTO;
}
@@ -0,0 +1,112 @@
import { ObjectType, Field, Int } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
/**
* Составные части `BlockchainAction`
*/
@ObjectType('ActionAuthorization')
export class ActionAuthorizationDTO {
@Field()
actor!: string;
@Field()
permission!: string;
}
@ObjectType('AccountRamDelta')
export class AccountRamDeltaDTO {
@Field()
account!: string;
@Field(() => Int)
delta!: number;
}
@ObjectType('ActionReceipt')
export class ActionReceiptDTO {
@Field()
receiver!: string;
@Field()
act_digest!: string;
@Field()
global_sequence!: string;
@Field()
recv_sequence!: string;
@Field(() => [AuthSequenceDTO])
auth_sequence!: AuthSequenceDTO[];
@Field(() => Int)
code_sequence!: number;
@Field(() => Int)
abi_sequence!: number;
}
@ObjectType('AuthSequence')
export class AuthSequenceDTO {
@Field()
account!: string;
@Field()
sequence!: string;
}
@ObjectType('BlockchainAction', {
description: 'Объект действия в блокчейне',
})
export class BlockchainActionDTO {
@Field()
transaction_id!: string;
@Field()
account!: string;
@Field(() => Int)
block_num!: number;
@Field()
block_id!: string;
@Field()
chain_id!: string;
@Field()
name!: string;
@Field()
receiver!: string;
@Field(() => [ActionAuthorizationDTO])
authorization!: ActionAuthorizationDTO[];
@Field(() => GraphQLJSON, { description: 'Произвольные данные действия в формате JSON' })
data!: any; // Теперь это явно JSON
@Field(() => Int)
action_ordinal!: number;
@Field()
global_sequence!: string;
@Field(() => [AccountRamDeltaDTO])
account_ram_deltas!: AccountRamDeltaDTO[];
@Field()
console!: string;
@Field(() => ActionReceiptDTO)
receipt!: ActionReceiptDTO;
@Field(() => Int)
creator_action_ordinal!: number;
@Field()
context_free!: boolean;
@Field(() => Int)
elapsed!: number;
}
@@ -0,0 +1,56 @@
import { ObjectType, Field } from '@nestjs/graphql';
import type { SovietContract } from 'cooptypes';
import { SignedBlockchainDocumentDTO } from '~/modules/document/dto/signed-blockchain-document.dto';
@ObjectType('BlockchainDecision', {
description: 'Запись в таблице блокчейна о процессе принятия решения советом кооператива',
})
export class BlockchainDecisionDTO implements SovietContract.Tables.Decisions.IDecision {
@Field(() => Number)
id!: number | string;
@Field()
coopname!: string;
@Field()
username!: string;
@Field()
type!: string;
@Field(() => Number)
batch_id!: number | string;
@Field(() => SignedBlockchainDocumentDTO)
statement!: SignedBlockchainDocumentDTO;
@Field(() => [String])
votes_for!: string[];
@Field(() => [String])
votes_against!: string[];
@Field()
validated!: boolean;
@Field()
approved!: boolean;
@Field()
authorized!: boolean;
@Field()
authorized_by!: string;
@Field(() => SignedBlockchainDocumentDTO)
authorization!: SignedBlockchainDocumentDTO;
@Field()
created_at!: string;
@Field()
expired_at!: string;
@Field()
meta!: string;
}
@@ -0,0 +1,21 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
import type { DecisionDetailDomainInterface } from '~/domain/agenda/interfaces/decision-detail-domain.interface';
import { ExtendedBlockchainActionDTO } from './extended-action.dto';
@ObjectType('DecisionDetail', {
description: 'Комплексный объект решения совета, включающий в себя информацию о голосовавших членах совета',
})
export class DecisionDetailDTO implements DecisionDetailDomainInterface {
@Field(() => ExtendedBlockchainActionDTO)
action!: ExtendedBlockchainActionDTO;
@Field(() => GeneratedDocumentDTO)
document!: GeneratedDocumentDTO;
@Field(() => [ExtendedBlockchainActionDTO])
votes_for!: ExtendedBlockchainActionDTO[];
@Field(() => [ExtendedBlockchainActionDTO])
votes_against!: ExtendedBlockchainActionDTO[];
}
@@ -0,0 +1,32 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
import { StatementDetailDTO } from './statement-detail.dto';
import { DecisionDetailDTO } from './decision-detail.dto';
import { ActDetailDTO } from './act-detail.dto';
import type { DocumentPackageDomainInterface } from '~/domain/agenda/interfaces/document-package-domain.interface';
@ObjectType('DocumentPackage', {
description:
'Комплексный объект папки цифрового документа, который включает в себя заявление, решение, акты и связанные документы',
})
export class DocumentPackageDTO implements DocumentPackageDomainInterface {
@Field(() => StatementDetailDTO, {
description: 'Объект цифрового документа заявления',
})
statement!: StatementDetailDTO;
@Field(() => DecisionDetailDTO, {
description: 'Объект цифрового документа решения',
})
decision!: DecisionDetailDTO;
@Field(() => [ActDetailDTO], {
description: 'Массив объект(ов) актов, относящихся к заявлению',
})
acts!: ActDetailDTO[];
@Field(() => [GeneratedDocumentDTO], {
description: 'Массив связанных документов, извлечённых из мета-данных',
})
links!: GeneratedDocumentDTO[];
}
@@ -0,0 +1,17 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { IndividualDTO } from '~/modules/common/dto/individual.dto';
import { EntrepreneurDTO } from '~/modules/common/dto/entrepreneur.dto';
import { OrganizationDTO } from '~/modules/common/dto/organization.dto';
import type { ExtendedBlockchainActionDomainInterface } from '~/domain/agenda/interfaces/extended-blockchain-action-domain.interface';
import { BlockchainActionDTO } from './blockchain-action.dto';
@ObjectType('ExtendedBlockchainAction', {
description: 'Расширенное действие блокчейна с персональными данными пользователя, совершившего его.',
})
export class ExtendedBlockchainActionDTO extends BlockchainActionDTO implements ExtendedBlockchainActionDomainInterface {
@Field(() => [IndividualDTO, EntrepreneurDTO, OrganizationDTO], {
nullable: true,
description: 'Доп. данные о пользователе (физ/ИП/организация)',
})
user?: IndividualDTO | EntrepreneurDTO | OrganizationDTO | null | undefined;
}
@@ -0,0 +1,16 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
import type { StatementDetailDomainInterface } from '~/domain/agenda/interfaces/statement-detail-domain.interface';
import { ExtendedBlockchainActionDTO } from './extended-action.dto';
@ObjectType('StatementDetail', {
description:
'Комплексный объект цифрового документа заявления (или другого ведущего документа для цепочки принятия решений совета)',
})
export class StatementDetailDTO implements StatementDetailDomainInterface {
@Field(() => ExtendedBlockchainActionDTO)
action!: ExtendedBlockchainActionDTO;
@Field(() => GeneratedDocumentDTO)
document!: GeneratedDocumentDTO;
}
@@ -0,0 +1,23 @@
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { GqlJwtAuthGuard } from '~/modules/auth/guards/graphql-jwt-auth.guard';
import { RolesGuard } from '~/modules/auth/guards/roles.guard';
import { UseGuards } from '@nestjs/common';
import { AuthRoles } from '~/modules/auth/decorators/auth.decorator';
import { AgendaService } from '../services/agenda.service';
import { AgendaWithDocumentsDTO } from '../dto/agenda-with-documents.dto';
@Resolver()
export class AgendaResolver {
constructor(private readonly agendaService: AgendaService) {}
//TODO add votefor / voteagainst
@Query(() => [AgendaWithDocumentsDTO], {
name: 'getAgenda',
description: 'Получить список вопросов совета кооператива для голосования',
})
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async getAgenda(): Promise<AgendaWithDocumentsDTO[]> {
return this.agendaService.getAgenda();
}
}
@@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { type AgendaWithDocumentsDTO } from '../dto/agenda-with-documents.dto';
import { AgendaDomainInteractor } from '~/domain/agenda/interactors/agenda-domain.interactor';
@Injectable()
export class AgendaService {
constructor(private readonly agendaDomainInteractor: AgendaDomainInteractor) {}
public async getAgenda(): Promise<AgendaWithDocumentsDTO[]> {
const agenda = await this.agendaDomainInteractor.getAgenda();
return agenda;
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { DomainModule } from '~/domain/domain.module';
import { AgreementResolver } from './resolvers/agreement.resolver';
import { AgreementService } from './services/agreement.service';
@Module({
imports: [DomainModule],
controllers: [],
providers: [AgreementResolver, AgreementService],
exports: [],
})
export class AgreementModule {}
@@ -0,0 +1,79 @@
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { GqlJwtAuthGuard } from '~/modules/auth/guards/graphql-jwt-auth.guard';
import { RolesGuard } from '~/modules/auth/guards/roles.guard';
import { UseGuards } from '@nestjs/common';
import { AuthRoles } from '~/modules/auth/decorators/auth.decorator';
import { GenerateDocumentOptionsInputDTO } from '~/modules/document/dto/generate-document-options-input.dto';
import { Throttle } from '@nestjs/throttler';
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
import { GenerateDocumentInputDTO } from '~/modules/document/dto/generate-document-input.dto';
import { AgreementService } from '../services/agreement.service';
@Resolver()
export class AgreementResolver {
constructor(private readonly agreementService: AgreementService) {}
@Mutation(() => GeneratedDocumentDTO, {
name: 'generateWalletAgreement',
description: 'Сгенерировать документ соглашения о целевой потребительской программе "Цифровой Кошелёк"',
})
@Throttle({ default: { limit: 3, ttl: 60000 } })
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async generateWalletAgreement(
@Args('data', { type: () => GenerateDocumentInputDTO })
data: GenerateDocumentInputDTO,
@Args('options', { type: () => GenerateDocumentOptionsInputDTO, nullable: true })
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
return this.agreementService.generateWalletAgreement(data, options);
}
@Mutation(() => GeneratedDocumentDTO, {
name: 'generatePrivacyAgreement',
description: 'Сгенерировать документ соглашения с политикой конфиденциальности',
})
@Throttle({ default: { limit: 3, ttl: 60000 } })
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async generatePrivacyAgreement(
@Args('data', { type: () => GenerateDocumentInputDTO })
data: GenerateDocumentInputDTO,
@Args('options', { type: () => GenerateDocumentOptionsInputDTO, nullable: true })
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
return this.agreementService.generatePrivacyAgreement(data, options);
}
@Mutation(() => GeneratedDocumentDTO, {
name: 'generateSignatureAgreement',
description: 'Сгенерировать документ соглашения о целевой потребительской программе "Цифровой Кошелёк"',
})
@Throttle({ default: { limit: 3, ttl: 60000 } })
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async generateSignatureAgreement(
@Args('data', { type: () => GenerateDocumentInputDTO })
data: GenerateDocumentInputDTO,
@Args('options', { type: () => GenerateDocumentOptionsInputDTO, nullable: true })
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
return this.agreementService.generateSignatureAgreement(data, options);
}
@Mutation(() => GeneratedDocumentDTO, {
name: 'generateUserAgreement',
description: 'Сгенерировать документ пользовательского соглашения',
})
@Throttle({ default: { limit: 3, ttl: 60000 } })
@UseGuards(GqlJwtAuthGuard, RolesGuard)
@AuthRoles(['chairman', 'member'])
async generateUserAgreement(
@Args('data', { type: () => GenerateDocumentInputDTO })
data: GenerateDocumentInputDTO,
@Args('options', { type: () => GenerateDocumentOptionsInputDTO, nullable: true })
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
return this.agreementService.generateUserAgreement(data, options);
}
}
@@ -0,0 +1,42 @@
import { Injectable } from '@nestjs/common';
import { GenerateDocumentOptionsInputDTO } from '~/modules/document/dto/generate-document-options-input.dto';
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
import { GenerateDocumentInputDTO } from '~/modules/document/dto/generate-document-input.dto';
import { AgreementDomainInteractor } from '~/domain/agreement/interactors/agreement-domain.interactor';
@Injectable()
export class AgreementService {
constructor(private readonly agreementDomainInteractor: AgreementDomainInteractor) {}
public async generateWalletAgreement(
data: GenerateDocumentInputDTO,
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
const document = await this.agreementDomainInteractor.generateWalletAgreement(data, options);
return document as GeneratedDocumentDTO;
}
public async generatePrivacyAgreement(
data: GenerateDocumentInputDTO,
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
const document = await this.agreementDomainInteractor.generatePrivacyAgreement(data, options);
return document as GeneratedDocumentDTO;
}
public async generateSignatureAgreement(
data: GenerateDocumentInputDTO,
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
const document = await this.agreementDomainInteractor.generateSignatureAgreement(data, options);
return document as GeneratedDocumentDTO;
}
public async generateUserAgreement(
data: GenerateDocumentInputDTO,
options: GenerateDocumentOptionsInputDTO
): Promise<GeneratedDocumentDTO> {
const document = await this.agreementDomainInteractor.generateUserAgreement(data, options);
return document as GeneratedDocumentDTO;
}
}
@@ -10,11 +10,16 @@ import { AppStoreModule } from './appstore/appstore-app.module';
import { QueueModule } from './queue/queue-app.module';
import { DocumentModule } from './document/document.module';
import { RedisAppModule } from './redis/redis-app.module';
import { DecisionModule } from './decision/decision.module';
import { DecisionModule } from './free-decision/decision.module';
import { AgreementModule } from './agreement/agreement.module';
import { ParticipantModule } from './participant/participant.module';
import { AgendaModule } from './agenda/agenda.module';
@Module({
imports: [
AccountModule,
AgreementModule,
AgendaModule,
AppStoreModule,
AuthModule,
BranchModule,
@@ -25,9 +30,12 @@ import { DecisionModule } from './decision/decision.module';
SystemModule,
DocumentModule,
DecisionModule,
ParticipantModule,
],
exports: [
AccountModule,
AgendaModule,
AgreementModule,
AppStoreModule,
AuthModule,
BranchModule,
@@ -38,6 +46,7 @@ import { DecisionModule } from './decision/decision.module';
SystemModule,
DocumentModule,
DecisionModule,
ParticipantModule,
],
})
export class ApplicationModule {}
@@ -2,10 +2,13 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtAuthStrategy } from './strategies/jwt.strategy';
import { DomainModule } from '~/domain/domain.module';
import { AuthResolver } from './resolvers/auth.resolver';
import { AuthService } from './services/auth.service';
@Module({
imports: [PassportModule],
providers: [JwtAuthStrategy],
imports: [PassportModule, DomainModule],
providers: [JwtAuthStrategy, AuthResolver, AuthService],
exports: [PassportModule],
})
export class AuthModule {}
@@ -0,0 +1,17 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';
@InputType('LoginInput')
export class LoginInputDTO {
@Field({ description: 'Электронная почта' })
@IsNotEmpty({ message: 'Поле "email" обязательно для заполнения.' })
email!: string;
@Field({ description: 'Метка времени в строковом формате ISO' })
@IsNotEmpty({ message: 'Поле "now" обязательно для заполнения.' })
now!: string;
@Field({ description: 'Цифровая подпись метки времени' })
@IsNotEmpty({ message: 'Поле "signature" обязательно для заполнения.' })
signature!: string;
}
@@ -0,0 +1,13 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';
@InputType('LogoutInput')
export class LogoutInputDTO {
@Field({ description: 'Токен обновления' })
@IsNotEmpty({ message: 'Поле "access_token" обязательно для заполнения.' })
access_token!: string;
@Field({ description: 'Токен доступа' })
@IsNotEmpty({ message: 'Поле "refresh_token" обязательно для заполнения.' })
refresh_token!: string;
}
@@ -0,0 +1,13 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';
@InputType('RefreshInput')
export class RefreshInputDTO {
@Field({ description: 'Токен обновления' })
@IsNotEmpty({ message: 'Поле "refresh_token" обязательно для заполнения.' })
refresh_token!: string;
@Field({ description: 'Токен доступа' })
@IsNotEmpty({ message: 'Поле "access_token" обязательно для заполнения.' })
access_token!: string;
}
@@ -0,0 +1,13 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';
@InputType('ResetKeyInput')
export class ResetKeyInputDTO {
@Field({ description: 'Публичный ключ для замены' })
@IsNotEmpty({ message: 'Поле "public_key" обязательно для заполнения.' })
public_key!: string;
@Field({ description: 'Токен авторизации для замены ключа, полученный по email' })
@IsNotEmpty({ message: 'Поле "token" обязательно для заполнения.' })
token!: string;
}

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