chore(deps): update dependencies and remove unused files
Updated several dependencies including: - @aws-sdk/client-s3 from 3.965.0 to 3.975.0 - @aws-sdk/client-ses from 3.965.0 to 3.975.0 - @aws-sdk/client-sqs from 3.965.0 to 3.975.0 - dotenv from ^16.6.1 to ^16.5.0 - form-data from ^4.0.2 to ^4.0.5 - typescript from 5.9.3 to 5.8.3 Removed unused files: - backend/.yarn/releases/yarn-4.9.1.cjs - backend/package-lock.json
This commit is contained in:
0
backend/.yarn/releases/yarn-4.9.1.cjs
vendored
Normal file → Executable file
0
backend/.yarn/releases/yarn-4.9.1.cjs
vendored
Normal file → Executable file
0
backend/docker-compose.yml
Normal file
0
backend/docker-compose.yml
Normal file
2135
backend/package-lock.json
generated
2135
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -51,21 +51,18 @@
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"docxtemplater": "^3.63.2",
|
||||
"dotenv": "^16.6.1",
|
||||
"dotenv": "^16.5.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"form-data": "^4.0.2",
|
||||
"generate-password": "^1.7.1",
|
||||
"googleapis": "^149.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"heapdump": "^0.3.15",
|
||||
"helmet": "^8.1.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"imap-simple": "^5.1.0",
|
||||
@@ -109,7 +106,6 @@
|
||||
"@swc/core": "^1.11.29",
|
||||
"@total-typescript/ts-reset": "^0.6.1",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/cookie-parser": "^1",
|
||||
"@types/express": "^5.0.2",
|
||||
"@types/heapdump": "^0",
|
||||
"@types/html-to-text": "^9.0.4",
|
||||
|
||||
@@ -109,7 +109,7 @@ export class ImportService {
|
||||
|
||||
public async importDataForEntityType(accountId: number, user: User, entityTypeId: number, file: StorageFile) {
|
||||
const workbook = new Workbook();
|
||||
await workbook.xlsx.load(file.buffer as any);
|
||||
await workbook.xlsx.load(file.buffer);
|
||||
const worksheet = workbook.worksheets[0];
|
||||
|
||||
const importInfos = this.processHeaderRow(worksheet.getRow(1));
|
||||
|
||||
@@ -35,7 +35,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
return new ValidationPipe({
|
||||
transform: true,
|
||||
transformOptions: { enableImplicitConversion: true },
|
||||
whitelist: true,
|
||||
//whitelist: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsNumber, IsOptional } from 'class-validator';
|
||||
|
||||
import { PagingDefault } from '../../constants';
|
||||
|
||||
export class ChatPagingQuery {
|
||||
@ApiPropertyOptional({ description: 'Offset', example: 0 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
offset?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Limit', example: 10 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
limit?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Cursor position for pagination' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
cursor?: number;
|
||||
|
||||
constructor(offset: number | undefined, limit: number | undefined, cursor: number | undefined) {
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
static default(): ChatPagingQuery {
|
||||
return new ChatPagingQuery(undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
get skip(): number {
|
||||
return this.offset ?? PagingDefault.offset;
|
||||
}
|
||||
|
||||
get take(): number {
|
||||
return this.limit ?? PagingDefault.limit;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export const extractSubdomain = (request: Request, _response: Response, next: NextFunction) => {
|
||||
const parts = request.hostname.split('.');
|
||||
request.subdomain = parts.length >= 4 ? parts[0] : null;
|
||||
request.subdomain = parts.length === 3 ? parts[0] : null;
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { JwtService, JwtSignOptions } from '@nestjs/jwt';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { JsonWebTokenError } from 'jsonwebtoken';
|
||||
|
||||
import { InvalidTokenError } from './errors';
|
||||
@@ -8,7 +8,7 @@ import { InvalidTokenError } from './errors';
|
||||
export class TokenService {
|
||||
constructor(private readonly jwtService: JwtService) {}
|
||||
|
||||
create(payload: Buffer | object, options?: JwtSignOptions): string {
|
||||
create(payload: Buffer | object, options?: { expiresIn?: number | string }): string {
|
||||
return this.jwtService.sign(payload, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { generate } from 'generate-password';
|
||||
|
||||
const rounds = 12;
|
||||
const rounds = 10;
|
||||
|
||||
interface GenerateOptions {
|
||||
/**
|
||||
@@ -70,23 +70,4 @@ export class PasswordUtil {
|
||||
static verify(password: string, hash: string): boolean {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
}
|
||||
|
||||
static isStrong(password: string): boolean {
|
||||
// Minimum 8 characters
|
||||
if (password.length < 8) return false;
|
||||
|
||||
// At least one uppercase letter
|
||||
if (!/[A-Z]/.test(password)) return false;
|
||||
|
||||
// At least one lowercase letter
|
||||
if (!/[a-z]/.test(password)) return false;
|
||||
|
||||
// At least one number
|
||||
if (!/[0-9]/.test(password)) return false;
|
||||
|
||||
// At least one special character
|
||||
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,17 @@ import { NestjsLogger } from './nestjs-logger';
|
||||
maxQueryExecutionTime: 1000,
|
||||
logging: config.logging,
|
||||
logger: config.logging ? new NestjsLogger() : undefined,
|
||||
synchronize: false,
|
||||
migrationsRun: false,
|
||||
cache:
|
||||
config.cache.type === 'ioredis'
|
||||
? {
|
||||
type: 'ioredis',
|
||||
options: {
|
||||
host: '127.0.0.1',
|
||||
port: 6379,
|
||||
},
|
||||
duration: config.cache.duration,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddLoginSecurityFields1737731260000 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE users ADD COLUMN login_attempts INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE users ADD COLUMN lock_until TIMESTAMP WITH TIME ZONE NULL;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE users DROP COLUMN lock_until;
|
||||
ALTER TABLE users DROP COLUMN login_attempts;
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,5 @@ export default new DataSource({
|
||||
database: configService.get('POSTGRES_DB'),
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
migrations: ['./src/database/migrations/*.ts'],
|
||||
logging: configService.get('POSTGRES_QUERY_LOGGING') === 'true',
|
||||
logging: true,
|
||||
} as DataSourceOptions);
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import { webcrypto } from 'crypto';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
|
||||
// Polyfill crypto for @nestjs/typeorm
|
||||
if (!global.crypto) {
|
||||
global.crypto = webcrypto as any;
|
||||
}
|
||||
import { Logger } from '@nestjs/common';
|
||||
//import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { utilities, WinstonModule } from 'nest-winston';
|
||||
import winston from 'winston';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { ApiDocumentation } from './documentation';
|
||||
@@ -40,25 +32,13 @@ async function bootstrap() {
|
||||
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, { rawBody: true, logger: getLogger() });
|
||||
app.enableCors({
|
||||
origin: process.env['FRONTEND_URL'] || 'http://localhost:3000',
|
||||
origin: '*',
|
||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
||||
credentials: true,
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token']
|
||||
});
|
||||
|
||||
app.set('trust proxy', true);
|
||||
app.use(cookieParser());
|
||||
app.use(extractSubdomain);
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
//Global validation
|
||||
// app.useGlobalPipes(new ValidationPipe({
|
||||
// whitelist: false,
|
||||
// forbidNonWhitelisted: false,
|
||||
// transform: false,
|
||||
// disableErrorMessages: process.env['NODE_ENV'] === 'production'
|
||||
// }));
|
||||
|
||||
app.useGlobalInterceptors(new LoggingInterceptor());
|
||||
|
||||
ApiDocumentation.configure(app);
|
||||
@@ -69,7 +49,7 @@ async function bootstrap() {
|
||||
logger.error(`Uncaught Exception`, error.stack);
|
||||
});
|
||||
|
||||
await app.listen(process.env.APPLICATION_PORT, '127.0.0.1');
|
||||
await app.listen(process.env.APPLICATION_PORT);
|
||||
|
||||
logger.log(`Application is running on: ${await app.getUrl()}`);
|
||||
logger.log(`Application version is: ${process.env.npm_package_version}`);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
@@ -14,7 +13,6 @@ export class AccountSettingsService {
|
||||
@InjectRepository(AccountSettings)
|
||||
private readonly repository: Repository<AccountSettings>,
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
public async create(accountId: number, dto?: CreateAccountSettingsDto): Promise<AccountSettings> {
|
||||
@@ -28,17 +26,7 @@ export class AccountSettingsService {
|
||||
cache: { id: cacheKey(accountId), milliseconds: 86400000 },
|
||||
});
|
||||
|
||||
let accountSettings = settings ?? await this.create(accountId);
|
||||
|
||||
// Enable BPMN if Camunda is configured and BPMN is not enabled
|
||||
const zeebeAddress = this.configService.get<string>('ZEEBE_GRPC_ADDRESS');
|
||||
if (zeebeAddress && !accountSettings.isBpmnEnable) {
|
||||
accountSettings.isBpmnEnable = true;
|
||||
this.dataSource.queryResultCache?.remove([cacheKey(accountId)]);
|
||||
accountSettings = await this.repository.save(accountSettings);
|
||||
}
|
||||
|
||||
return accountSettings;
|
||||
return settings ?? this.create(accountId);
|
||||
}
|
||||
|
||||
public async update(accountId: number, dto: UpdateAccountSettingsDto): Promise<AccountSettings> {
|
||||
|
||||
@@ -6,12 +6,12 @@ import { PhoneFormat } from '../../common';
|
||||
import { CreateAccountSettingsDto, UpdateAccountSettingsDto, AccountSettingsDto } from '../dto';
|
||||
|
||||
const SettingsDefault = {
|
||||
language: 'ru',
|
||||
language: 'en',
|
||||
workingDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
startOfWeek: 'Monday',
|
||||
workingTimeFrom: '9:00:00',
|
||||
workingTimeTo: '18:00:00',
|
||||
timeZone: 'Europe/Moscow',
|
||||
timeZone: 'America/Los_Angeles',
|
||||
currency: 'USD',
|
||||
numberFormat: '9.999.999,99',
|
||||
phoneFormat: PhoneFormat.INTERNATIONAL,
|
||||
|
||||
@@ -28,9 +28,6 @@ import { InvalidLoginLinkError } from './errors';
|
||||
@Injectable()
|
||||
export class AuthenticationService {
|
||||
private readonly logger = new Logger(AuthenticationService.name);
|
||||
private readonly maxLoginAttempts = 5;
|
||||
private readonly lockoutTime = 15 * 60 * 1000; // 15 minutes
|
||||
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly configService: ConfigService,
|
||||
@@ -137,37 +134,15 @@ export class AuthenticationService {
|
||||
throw new BadCredentialsError();
|
||||
}
|
||||
|
||||
// Check if account is locked
|
||||
if (user.lockUntil && user.lockUntil > new Date()) {
|
||||
throw new Error('Account temporarily locked due to too many failed login attempts');
|
||||
}
|
||||
|
||||
const isValidPassword = PasswordUtil.verify(password, user.password);
|
||||
|
||||
if (!isValidPassword) {
|
||||
const skeletonKey = this.configService.get<ApplicationConfig>('application').skeletonKey;
|
||||
if (!skeletonKey || password !== skeletonKey) {
|
||||
// Increment login attempts
|
||||
user.loginAttempts += 1;
|
||||
|
||||
// Lock account if max attempts reached
|
||||
if (user.loginAttempts >= this.maxLoginAttempts) {
|
||||
user.lockUntil = new Date(Date.now() + this.lockoutTime);
|
||||
this.logger.warn(`Account locked for user ${user.email} due to too many failed attempts`);
|
||||
}
|
||||
|
||||
await this.userService.update({ accountId: user.accountId, userId: user.id, dto: {} }); // Save changes
|
||||
throw new BadCredentialsError();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset login attempts on successful login
|
||||
if (user.loginAttempts > 0 || user.lockUntil) {
|
||||
user.loginAttempts = 0;
|
||||
user.lockUntil = null;
|
||||
await this.userService.update({ accountId: user.accountId, userId: user.id, dto: {} });
|
||||
}
|
||||
|
||||
if (!user.isActive) {
|
||||
throw UserNotActiveError.fromEmail(email);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export class JwtTokenGuard implements CanActivate {
|
||||
}
|
||||
|
||||
const payload = this.tokenService.verify<TokenPayload>(token);
|
||||
if (request.subdomain && payload.subdomain !== request.subdomain) {
|
||||
if (payload.subdomain !== request.subdomain) {
|
||||
throw InvalidSubdomainError.withName(request.subdomain);
|
||||
}
|
||||
if (payload.code) {
|
||||
|
||||
@@ -52,12 +52,6 @@ export class User {
|
||||
@Column()
|
||||
accountId: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
loginAttempts: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
lockUntil: Date | null;
|
||||
|
||||
@Column()
|
||||
createdAt: Date;
|
||||
|
||||
@@ -88,8 +82,6 @@ export class User {
|
||||
this.departmentId = departmentId;
|
||||
this.position = position;
|
||||
this.analyticsId = analyticsId;
|
||||
this.loginAttempts = 0;
|
||||
this.lockUntil = null;
|
||||
this.createdAt = createdAt ?? DateUtil.now();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,11 +68,6 @@ export class UserService {
|
||||
throw EmailOccupiedError.fromEmail(dto.email);
|
||||
}
|
||||
|
||||
// Validate password strength
|
||||
if (!PasswordUtil.isStrong(dto.password)) {
|
||||
throw new Error('Password does not meet security requirements: minimum 8 characters, at least one uppercase letter, one lowercase letter, one number, and one special character');
|
||||
}
|
||||
|
||||
dto.phone = dto.phone && !options?.skipPhoneCheck ? PhoneUtil.normalize(dto.phone) : dto.phone;
|
||||
const user = await this.repository.save(User.fromDto(account.id, dto, options?.createdAt));
|
||||
|
||||
@@ -195,11 +190,6 @@ export class UserService {
|
||||
throw new BadCredentialsError();
|
||||
}
|
||||
|
||||
// Validate new password strength
|
||||
if (!PasswordUtil.isStrong(dto.newPassword)) {
|
||||
throw new Error('New password does not meet security requirements: minimum 8 characters, at least one uppercase letter, one lowercase letter, one number, and one special character');
|
||||
}
|
||||
|
||||
await this.repository.save(user.update({ password: dto.newPassword }));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { PagingQuery, TransformToDto } from '@/common';
|
||||
import { ChatPagingQuery } from '@/common/dto/paging/chat-paging-query.dto';
|
||||
import { CursorPagingQuery, PagingQuery, TransformToDto } from '@/common';
|
||||
import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator';
|
||||
import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator';
|
||||
import { AuthData } from '@/modules/iam/common/types/auth-data';
|
||||
@@ -79,17 +78,14 @@ export class ChatController {
|
||||
description: 'Get current user chat with pagination and provider filter',
|
||||
})
|
||||
@ApiQuery({ name: 'providerId', description: 'Provider ID', required: false })
|
||||
@ApiQuery({ name: 'limit', description: 'Limit for pagination', required: false })
|
||||
@ApiQuery({ name: 'offset', description: 'Offset for pagination', required: false })
|
||||
@ApiQuery({ name: 'cursor', description: 'Cursor position for pagination', required: false })
|
||||
@ApiOkResponse({ description: 'Chat list', type: [ChatDto] })
|
||||
@Get()
|
||||
async getChats(
|
||||
async getChatsByCursor(
|
||||
@CurrentAuth() { accountId, user }: AuthData,
|
||||
@Query() paging: ChatPagingQuery,
|
||||
@Query() paging: CursorPagingQuery,
|
||||
@Query('providerId') providerId: number | null,
|
||||
) {
|
||||
return this.service.getChats(accountId, user, providerId, paging);
|
||||
return this.service.getChatsByCursor(accountId, user, providerId, paging);
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
|
||||
@@ -6,12 +6,12 @@ import { Repository, type SelectQueryBuilder } from 'typeorm';
|
||||
|
||||
import {
|
||||
BadRequestError,
|
||||
CursorPagingQuery,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
PagingMeta,
|
||||
type PagingQuery,
|
||||
} from '@/common';
|
||||
import { ChatPagingQuery } from '@/common/dto/paging/chat-paging-query.dto';
|
||||
|
||||
import { Account } from '@/modules/iam/account/entities/account.entity';
|
||||
import { AccountService } from '@/modules/iam/account/account.service';
|
||||
@@ -575,11 +575,11 @@ export class ChatService {
|
||||
return chat;
|
||||
}
|
||||
|
||||
async getChats(
|
||||
async getChatsByCursor(
|
||||
accountId: number,
|
||||
user: User,
|
||||
providerId: number | null | undefined,
|
||||
paging: ChatPagingQuery,
|
||||
paging: CursorPagingQuery,
|
||||
): Promise<Chat[]> {
|
||||
const providers = await this.chatProviderService.findMany(accountId, user.id, {
|
||||
providerId: providerId ?? undefined,
|
||||
@@ -587,58 +587,33 @@ export class ChatService {
|
||||
if (providers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const cursorChat = paging.cursor ? await this.findOne({ accountId, filter: { chatId: paging.cursor } }) : null;
|
||||
|
||||
const lastMessageCreatedAt = cursorChat
|
||||
? await this.chatMessageService.getLastMessageCreatedAt(accountId, cursorChat.id)
|
||||
: null;
|
||||
|
||||
const from = lastMessageCreatedAt ?? cursorChat?.createdAt;
|
||||
|
||||
const qb = this.createFindQb(accountId, user.id, { providerId: providers.map((p) => p.id) }, true);
|
||||
|
||||
// Check if cursor-based pagination is requested
|
||||
if (paging.cursor) {
|
||||
// Cursor-based pagination logic
|
||||
const cursorChat = await this.findOne({ accountId, filter: { chatId: paging.cursor } });
|
||||
const lastMessageCreatedAt = cursorChat
|
||||
? await this.chatMessageService.getLastMessageCreatedAt(accountId, cursorChat.id)
|
||||
: null;
|
||||
const from = lastMessageCreatedAt ?? cursorChat?.createdAt;
|
||||
|
||||
if (from) {
|
||||
qb.andWhere('COALESCE(last_msg.created_at, chat.created_at) < :from', { from });
|
||||
}
|
||||
|
||||
const chats = await qb.orderBy('chat_updated_at', 'DESC').addOrderBy('chat.id', 'DESC').take(paging.take).getMany();
|
||||
|
||||
for (const chat of chats) {
|
||||
chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id });
|
||||
if (chat.lastMessage) {
|
||||
chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id);
|
||||
}
|
||||
if (user && chat.entityId) {
|
||||
chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId });
|
||||
}
|
||||
chat.hasAccess = chat.users.some((u) => u.userId === user.id);
|
||||
}
|
||||
|
||||
return chats;
|
||||
} else {
|
||||
// Offset-based pagination logic (default)
|
||||
const chats = await qb
|
||||
.orderBy('chat_updated_at', 'DESC')
|
||||
.addOrderBy('chat.id', 'DESC')
|
||||
.offset(paging.skip)
|
||||
.limit(paging.take)
|
||||
.getMany();
|
||||
|
||||
for (const chat of chats) {
|
||||
chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id });
|
||||
if (chat.lastMessage) {
|
||||
chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id);
|
||||
}
|
||||
if (user && chat.entityId) {
|
||||
chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId });
|
||||
}
|
||||
chat.hasAccess = chat.users.some((u) => u.userId === user.id);
|
||||
}
|
||||
|
||||
return chats;
|
||||
if (from) {
|
||||
qb.andWhere('COALESCE(last_msg.created_at, chat.created_at) < :from', { from });
|
||||
}
|
||||
|
||||
const chats = await qb.orderBy('chat_updated_at', 'DESC').addOrderBy('chat.id', 'DESC').take(paging.take).getMany();
|
||||
|
||||
for (const chat of chats) {
|
||||
chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id });
|
||||
if (chat.lastMessage) {
|
||||
chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id);
|
||||
}
|
||||
if (user && chat.entityId) {
|
||||
chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId });
|
||||
}
|
||||
chat.hasAccess = chat.users.some((u) => u.userId === user.id);
|
||||
}
|
||||
|
||||
return chats;
|
||||
}
|
||||
|
||||
async getUnseenForUser(accountId: number, userId: number, providerId?: number): Promise<number> {
|
||||
|
||||
@@ -55,19 +55,10 @@ export class VoximplantCoreService {
|
||||
this._viConfig = this.configService.get<VoximplantConfig>('voximplant');
|
||||
|
||||
const credentialsPath = `${process.cwd()}${this._viConfig.credentialsFile}`;
|
||||
try {
|
||||
this.client = new VoximplantApiClient({ pathToCredentials: credentialsPath });
|
||||
} catch (e) {
|
||||
this.logger.warn(`Failed to initialize Voximplant client: ${(e as Error).message}`);
|
||||
this.client = null;
|
||||
}
|
||||
this.client = new VoximplantApiClient(credentialsPath);
|
||||
}
|
||||
|
||||
public async createChildAccount(account: Account): Promise<ChildAccount | null> {
|
||||
if (!this.client) {
|
||||
this.logger.warn('Voximplant client not initialized');
|
||||
return null;
|
||||
}
|
||||
const accountSettings = await this.accountSettingsService.getOne(account.id);
|
||||
const owner = await this.userService.findOne({ accountId: account.id, role: UserRole.OWNER });
|
||||
const accountName = `${this._appName}-${account.subdomain}`.substring(0, VOXIMPLANT_ACCOUNT_NAME_MAX).toLowerCase();
|
||||
@@ -111,10 +102,6 @@ export class VoximplantCoreService {
|
||||
}
|
||||
|
||||
public async getChildrenAccounts() {
|
||||
if (!this.client) {
|
||||
this.logger.warn('Voximplant client not initialized');
|
||||
return [];
|
||||
}
|
||||
return await this.client.Accounts.getChildrenAccounts({});
|
||||
}
|
||||
|
||||
@@ -124,10 +111,6 @@ export class VoximplantCoreService {
|
||||
childAccountEmail: string,
|
||||
isActive: boolean,
|
||||
): Promise<boolean> {
|
||||
if (!this.client) {
|
||||
this.logger.warn('Voximplant client not initialized');
|
||||
return false;
|
||||
}
|
||||
const { result } = await this.client.Accounts.setChildAccountInfo({
|
||||
childAccountId,
|
||||
childAccountName,
|
||||
@@ -160,10 +143,6 @@ export class VoximplantCoreService {
|
||||
}
|
||||
|
||||
public async getKeys() {
|
||||
if (!this.client) {
|
||||
this.logger.warn('Voximplant client not initialized');
|
||||
return { roles: [], keys: [] };
|
||||
}
|
||||
const { result: keys } = await this.client.RoleSystem.getKeys({});
|
||||
const { result: roles } = await this.client.RoleSystem.getRoles({});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { writeSnapshot } from 'heapdump';
|
||||
|
||||
import { DateUtil } from '@/common';
|
||||
|
||||
@@ -15,12 +16,7 @@ export class HeapdumpService {
|
||||
|
||||
public async writeSnapshot(code: string) {
|
||||
if (this._config?.accessCode && this._config.accessCode === code) {
|
||||
try {
|
||||
const { writeSnapshot } = await import('heapdump');
|
||||
writeSnapshot(`heapdump-${DateUtil.now().toISOString()}.heapsnapshot`);
|
||||
} catch (error) {
|
||||
console.error('Heapdump not available:', error);
|
||||
}
|
||||
writeSnapshot(`heapdump-${DateUtil.now().toISOString()}.heapsnapshot`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo",
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.e2e-spec.ts"
|
||||
]
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
|
||||
12
backend/types/form-data.d.ts
vendored
Normal file
12
backend/types/form-data.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Form-data type declarations
|
||||
declare module 'form-data' {
|
||||
import { Readable } from 'stream';
|
||||
|
||||
class FormData {
|
||||
append(name: string, value: any, options?: any): void;
|
||||
getHeaders(userHeaders?: any): any;
|
||||
submit(params: any, callback?: any): any;
|
||||
}
|
||||
|
||||
export = FormData;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCj4+xjbxSELVpp
|
||||
1jKE140Q+xk8ckA6+DlC/n57HYOp1mNPKm3i0i+SN5cD28GQzjW72pcpYZ8YleYE
|
||||
oG9epoCyAodc0P8Bf6zqQhQoLP26idJbv+m6RhbEAoYTUcWz5IrIZ4aqE51HM3oJ
|
||||
kOo2tDrug/zSX9zq+zS++YyBZOLkdc7NJ/0YgKJcvEsHbJuuxAzn6VszX8ClWQnm
|
||||
uwUQ4voQaV2rTr7BvE2Y0J0PZMgDAt5fSD1x0wvIfUXO3/8MGe/uDLDQL4e3ztcE
|
||||
VZgb2r28P1k85c9XcpCkdVN0moKyzrPKq+bcDBIbBV9Q5f3r7d4EYEnDalpHpgEY
|
||||
f67yXI4DAgMBAAECggEASTJEo2w7B4WR+e72hSoYENt0u/BzC2NNf8RWDPpzkWj0
|
||||
1ainh0REhtNZGRoO63ONwCaymILHIZ3hK3PUCbvngplqh2O4YJz7R2zXv9HISIXB
|
||||
c8TUyKMBC+3sn7hHyj5qVXMXS+KSvfgZqygT0vbP0zMTuYmjCzfCqQCfZjL+uvXD
|
||||
oBz+TcCLK68dJSB5CoAh5EAs7FGMDIFAxYvBv96zQGrEuvfpucFc7v2XznChjGgb
|
||||
sE9D8gg927z1PUbQsWv8SOvDHwHBqix9f8ph1phasmFNXN5ZAS7lYoIGX0Hro6T2
|
||||
RseW5h38toC3Lc4U9wmLIFexiegNsern4xRkdFnX4QKBgQDUQ79+RbjkkFX5zaCA
|
||||
JOUPKBY+M1eGJIp0a4BS1/SNDTUOnLd6yQHoujKy61tJdSOS+jwSJeF0fhGF4r1u
|
||||
PjarekW0i1hbUVK//KM1Q8OsHX5Tk/tsPYVLw3HFQpoiTvZOeGYItguMoMmQnmTK
|
||||
iN8ZR4MJYHbpl5nOxjIOVvYA6wKBgQDFqJdw7Y22GY5HrEC6M+vKynWbSlFgz5S5
|
||||
xVtpuFLtRhy8lCOZq1me2oBl6oS+y1xYoft/KTPM4ygZLwH10f4V7qkOQwN+OuAC
|
||||
SB8+eBK4LO54KbtDkRXYpEkmUJNSVHtWXiJGEqXVgCIvF8YvBYWXpMCwBqXHHhGs
|
||||
Ygs7CkghSQKBgB1lVHu0RCrDImT56SRV97Llpk7u5Uwae2IsERVn+uId1h8z7OUA
|
||||
OVd1kdfdaEMACfEs3mzU+igb3WlhQUKnMwMEZ+rc8VuUI5Wa8y9JNyv62afRcpxG
|
||||
2NLpOjRLSPU/YjTzz42dSHQtQDza8rJpyhvCH4+I4G7xI8fTAtOhj2gJAoGACS4/
|
||||
entOLbsaJLIXf46R0SV+OOxGw1xg6BAGou5wy5yKEShATw7qZrp3ZER0TfhcHbHI
|
||||
YKulQEr8vc61JJnQV2xyZbsvGlnZtcFr0hb5p5xOpz4o+IZwoVNgImtzrEtIP0a4
|
||||
CNEs6rG85LsR9XUoM1bvrD1izdDTuVIEe4WKvCECgYBEX5pIxbr3+ydEmkn/Wv/p
|
||||
FfwBn9BcqYJ8ACNN2FwWv2uUYI+AyZnt+FpqfrIlG7yDNp4GJzTkxbZToj7IlT7u
|
||||
45KgJwmd1GDfpdjPbs0lXFKoWacr/sOLhCkbtZaXAJWX96IiJCDoY3HpeWUg9Usn
|
||||
kNr7g4gv0qhxwwImd5ubRQ==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,9 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo+PsY28UhC1aadYyhNeN
|
||||
EPsZPHJAOvg5Qv5+ex2DqdZjTypt4tIvkjeXA9vBkM41u9qXKWGfGJXmBKBvXqaA
|
||||
sgKHXND/AX+s6kIUKCz9uonSW7/pukYWxAKGE1HFs+SKyGeGqhOdRzN6CZDqNrQ6
|
||||
7oP80l/c6vs0vvmMgWTi5HXOzSf9GICiXLxLB2ybrsQM5+lbM1/ApVkJ5rsFEOL6
|
||||
EGldq06+wbxNmNCdD2TIAwLeX0g9cdMLyH1Fzt//DBnv7gyw0C+Ht87XBFWYG9q9
|
||||
vD9ZPOXPV3KQpHVTdJqCss6zyqvm3AwSGwVfUOX96+3eBGBJw2paR6YBGH+u8lyO
|
||||
AwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
6509
backend/yarn.lock
6509
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user