Compare commits

..

4 Commits

9 changed files with 80 additions and 122 deletions

View File

@ -125,7 +125,7 @@ import { VisitorPasswordEntity } from '../modules/visitor-password/entities';
logger: typeOrmLogger, logger: typeOrmLogger,
extra: { extra: {
charset: 'utf8mb4', charset: 'utf8mb4',
max: 100, // set pool max size max: 20, // set pool max size
idleTimeoutMillis: 5000, // close idle clients after 5 second idleTimeoutMillis: 5000, // close idle clients after 5 second
connectionTimeoutMillis: 12_000, // return an error after 11 second if connection could not be established connectionTimeoutMillis: 12_000, // return an error after 11 second if connection could not be established
maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion) maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion)

View File

@ -1,7 +1,7 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
import axios from 'axios';
import { import {
SEND_EMAIL_API_URL_DEV, SEND_EMAIL_API_URL_DEV,
SEND_EMAIL_API_URL_PROD, SEND_EMAIL_API_URL_PROD,
@ -83,17 +83,12 @@ export class EmailService {
); );
} }
} }
async sendEmailWithTemplate({ async sendEmailWithTemplate(
email, email: string,
name, name: string,
isEnable, isEnable: boolean,
isDelete, isDelete: boolean,
}: { ): Promise<void> {
email: string;
name: string;
isEnable: boolean;
isDelete: boolean;
}): Promise<void> {
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const API_TOKEN = this.configService.get<string>( const API_TOKEN = this.configService.get<string>(
'email-config.MAILTRAP_API_TOKEN', 'email-config.MAILTRAP_API_TOKEN',

View File

@ -190,26 +190,24 @@ export class CommunityService {
.distinct(true); .distinct(true);
if (includeSpaces) { if (includeSpaces) {
qb.leftJoinAndSelect( qb.leftJoinAndSelect('c.spaces', 'space', 'space.disabled = false')
'c.spaces',
'space',
'space.disabled = :disabled AND space.spaceName != :orphanSpaceName',
{ disabled: false, orphanSpaceName: ORPHAN_SPACE_NAME },
)
.leftJoinAndSelect('space.parent', 'parent') .leftJoinAndSelect('space.parent', 'parent')
.leftJoinAndSelect( .leftJoinAndSelect(
'space.children', 'space.children',
'children', 'children',
'children.disabled = :disabled', 'children.disabled = :disabled',
{ disabled: false }, { disabled: false },
); )
// .leftJoinAndSelect('space.spaceModel', 'spaceModel') // .leftJoinAndSelect('space.spaceModel', 'spaceModel')
.andWhere('space.spaceName != :orphanSpaceName', {
orphanSpaceName: ORPHAN_SPACE_NAME,
})
.andWhere('space.disabled = :disabled', { disabled: false });
} }
if (search) { if (search) {
qb.andWhere( qb.andWhere(
`c.name ILIKE :search ${includeSpaces ? 'OR space.space_name ILIKE :search' : ''}`, `c.name ILIKE '%${search}%' ${includeSpaces ? "OR space.space_name ILIKE '%" + search + "%'" : ''}`,
{ search },
); );
} }
@ -217,21 +215,12 @@ export class CommunityService {
const { baseResponseDto, paginationResponseDto } = const { baseResponseDto, paginationResponseDto } =
await customModel.findAll({ ...pageable, modelName: 'community' }, qb); await customModel.findAll({ ...pageable, modelName: 'community' }, qb);
if (includeSpaces) {
baseResponseDto.data = baseResponseDto.data.map((community) => ({
...community,
spaces: this.spaceService.buildSpaceHierarchy(community.spaces || []),
}));
}
return new PageResponse<CommunityDto>( return new PageResponse<CommunityDto>(
baseResponseDto, baseResponseDto,
paginationResponseDto, paginationResponseDto,
); );
} catch (error) { } catch (error) {
// Generic error handling // Generic error handling
if (error instanceof HttpException) {
throw error;
}
throw new HttpException( throw new HttpException(
error.message || 'An error occurred while fetching communities.', error.message || 'An error occurred while fetching communities.',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,

View File

@ -1,11 +1,11 @@
import { ControllerRoute } from '@app/common/constants/controller-route';
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { Permissions } from 'src/decorators/permissions.decorator';
import { PermissionsGuard } from 'src/guards/permissions.guard';
import { GetDevicesFilterDto, ProjectParam } from '../dtos';
import { DeviceService } from '../services/device.service'; import { DeviceService } from '../services/device.service';
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
import { ControllerRoute } from '@app/common/constants/controller-route';
import { PermissionsGuard } from 'src/guards/permissions.guard';
import { Permissions } from 'src/decorators/permissions.decorator';
import { GetDoorLockDevices, ProjectParam } from '../dtos';
@ApiTags('Device Module') @ApiTags('Device Module')
@Controller({ @Controller({
@ -25,7 +25,7 @@ export class DeviceProjectController {
}) })
async getAllDevices( async getAllDevices(
@Param() param: ProjectParam, @Param() param: ProjectParam,
@Query() query: GetDevicesFilterDto, @Query() query: GetDoorLockDevices,
) { ) {
return await this.deviceService.getAllDevices(param, query); return await this.deviceService.getAllDevices(param, query);
} }

View File

@ -1,7 +1,6 @@
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum'; import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { import {
IsArray,
IsEnum, IsEnum,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
@ -42,7 +41,16 @@ export class GetDeviceLogsDto {
@IsOptional() @IsOptional()
public endTime: string; public endTime: string;
} }
export class GetDoorLockDevices {
@ApiProperty({
description: 'Device Type',
enum: DeviceTypeEnum,
required: false,
})
@IsEnum(DeviceTypeEnum)
@IsOptional()
public deviceType: DeviceTypeEnum;
}
export class GetDevicesBySpaceOrCommunityDto { export class GetDevicesBySpaceOrCommunityDto {
@ApiProperty({ @ApiProperty({
description: 'Device Product Type', description: 'Device Product Type',
@ -64,23 +72,3 @@ export class GetDevicesBySpaceOrCommunityDto {
@IsNotEmpty({ message: 'Either spaceUuid or communityUuid must be provided' }) @IsNotEmpty({ message: 'Either spaceUuid or communityUuid must be provided' })
requireEither?: never; // This ensures at least one of them is provided requireEither?: never; // This ensures at least one of them is provided
} }
export class GetDevicesFilterDto {
@ApiProperty({
description: 'Device Type',
enum: DeviceTypeEnum,
required: false,
})
@IsEnum(DeviceTypeEnum)
@IsOptional()
public deviceType: DeviceTypeEnum;
@ApiProperty({
description: 'List of Space IDs to filter devices',
required: false,
example: ['60d21b4667d0d8992e610c85', '60d21b4967d0d8992e610c86'],
})
@IsOptional()
@IsArray()
@IsUUID('4', { each: true })
public spaces?: string[];
}

View File

@ -53,7 +53,7 @@ import { DeviceSceneParamDto } from '../dtos/device.param.dto';
import { import {
GetDeviceLogsDto, GetDeviceLogsDto,
GetDevicesBySpaceOrCommunityDto, GetDevicesBySpaceOrCommunityDto,
GetDevicesFilterDto, GetDoorLockDevices,
} from '../dtos/get.device.dto'; } from '../dtos/get.device.dto';
import { import {
controlDeviceInterface, controlDeviceInterface,
@ -955,20 +955,19 @@ export class DeviceService {
async getAllDevices( async getAllDevices(
param: ProjectParam, param: ProjectParam,
{ deviceType, spaces }: GetDevicesFilterDto, query: GetDoorLockDevices,
): Promise<BaseResponseDto> { ): Promise<BaseResponseDto> {
try { try {
await this.validateProject(param.projectUuid); await this.validateProject(param.projectUuid);
if (deviceType === DeviceTypeEnum.DOOR_LOCK) { if (query.deviceType === DeviceTypeEnum.DOOR_LOCK) {
return await this.getDoorLockDevices(param.projectUuid, spaces); return await this.getDoorLockDevices(param.projectUuid);
} else if (!deviceType) { } else if (!query.deviceType) {
const devices = await this.deviceRepository.find({ const devices = await this.deviceRepository.find({
where: { where: {
isActive: true, isActive: true,
spaceDevice: { spaceDevice: {
uuid: spaces && spaces.length ? In(spaces) : undefined,
spaceName: Not(ORPHAN_SPACE_NAME),
community: { project: { uuid: param.projectUuid } }, community: { project: { uuid: param.projectUuid } },
spaceName: Not(ORPHAN_SPACE_NAME),
}, },
}, },
relations: [ relations: [
@ -1564,7 +1563,7 @@ export class DeviceService {
} }
} }
async getDoorLockDevices(projectUuid: string, spaces?: string[]) { async getDoorLockDevices(projectUuid: string) {
await this.validateProject(projectUuid); await this.validateProject(projectUuid);
const devices = await this.deviceRepository.find({ const devices = await this.deviceRepository.find({
@ -1574,7 +1573,6 @@ export class DeviceService {
}, },
spaceDevice: { spaceDevice: {
spaceName: Not(ORPHAN_SPACE_NAME), spaceName: Not(ORPHAN_SPACE_NAME),
uuid: spaces && spaces.length ? In(spaces) : undefined,
community: { community: {
project: { project: {
uuid: projectUuid, uuid: projectUuid,

View File

@ -1,42 +1,36 @@
import { RoleType } from '@app/common/constants/role.type.enum'; import {
import { UserStatusEnum } from '@app/common/constants/user-status.enum'; Injectable,
HttpException,
HttpStatus,
BadRequestException,
} from '@nestjs/common';
import { AddUserInvitationDto } from '../dtos';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { UserStatusEnum } from '@app/common/constants/user-status.enum';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { generateRandomString } from '@app/common/helper/randomString'; import { generateRandomString } from '@app/common/helper/randomString';
import { InviteUserEntity } from '@app/common/modules/Invite-user/entities'; import { EntityManager, In, IsNull, Not, QueryRunner } from 'typeorm';
import { DataSource } from 'typeorm';
import { UserEntity } from '@app/common/modules/user/entities';
import { RoleType } from '@app/common/constants/role.type.enum';
import { import {
InviteUserRepository, InviteUserRepository,
InviteUserSpaceRepository, InviteUserSpaceRepository,
} from '@app/common/modules/Invite-user/repositiories'; } from '@app/common/modules/Invite-user/repositiories';
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; import { CheckEmailDto } from '../dtos/check-email.dto';
import { SpaceRepository } from '@app/common/modules/space';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { UserEntity } from '@app/common/modules/user/entities';
import { UserRepository } from '@app/common/modules/user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories';
import { EmailService } from '@app/common/util/email.service'; import { EmailService } from '@app/common/util/email.service';
import { import { SpaceRepository } from '@app/common/modules/space';
BadRequestException,
HttpException,
HttpStatus,
Injectable,
} from '@nestjs/common';
import { SpaceUserService } from 'src/space/services';
import { UserSpaceService } from 'src/users/services';
import {
DataSource,
EntityManager,
In,
IsNull,
Not,
QueryRunner,
} from 'typeorm';
import { AddUserInvitationDto } from '../dtos';
import { ActivateCodeDto } from '../dtos/active-code.dto'; import { ActivateCodeDto } from '../dtos/active-code.dto';
import { CheckEmailDto } from '../dtos/check-email.dto'; import { UserSpaceService } from 'src/users/services';
import { SpaceUserService } from 'src/space/services';
import { import {
DisableUserInvitationDto, DisableUserInvitationDto,
UpdateUserInvitationDto, UpdateUserInvitationDto,
} from '../dtos/update.invite-user.dto'; } from '../dtos/update.invite-user.dto';
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
import { InviteUserEntity } from '@app/common/modules/Invite-user/entities';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
@Injectable() @Injectable()
export class InviteUserService { export class InviteUserService {
@ -664,12 +658,12 @@ export class InviteUserService {
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
await this.emailService.sendEmailWithTemplate({ await this.emailService.sendEmailWithTemplate(
email: userData.email, userData.email,
name: userData.firstName, userData.firstName,
isEnable: !disable, disable,
isDelete: false, false,
}); );
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
return new SuccessResponseDto({ return new SuccessResponseDto({
@ -803,12 +797,12 @@ export class InviteUserService {
{ isActive: false }, { isActive: false },
); );
} }
await this.emailService.sendEmailWithTemplate({ await this.emailService.sendEmailWithTemplate(
email: userData.email, userData.email,
name: userData.firstName, userData.firstName,
isEnable: false, false,
isDelete: true, true,
}); );
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
return new SuccessResponseDto({ return new SuccessResponseDto({

View File

@ -1,14 +1,15 @@
import { RequestContextMiddleware } from '@app/common/middleware/request-context.middleware';
import { SeederService } from '@app/common/seed/services/seeder.service';
import { Logger, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { json, urlencoded } from 'body-parser'; import { AppModule } from './app.module';
import rateLimit from 'express-rate-limit'; import rateLimit from 'express-rate-limit';
import helmet from 'helmet'; import helmet from 'helmet';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.swagger.utils'; import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.swagger.utils';
import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common';
import { json, urlencoded } from 'body-parser';
import { SeederService } from '@app/common/seed/services/seeder.service';
import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter'; import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter';
import { Logger } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { RequestContextMiddleware } from '@app/common/middleware/request-context.middleware';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
@ -29,13 +30,6 @@ async function bootstrap() {
}), }),
); );
app.use((req, res, next) => {
console.log('Real IP:', req.ip);
next();
});
// app.getHttpAdapter().getInstance().set('trust proxy', 1);
app.use( app.use(
helmet({ helmet({
contentSecurityPolicy: false, contentSecurityPolicy: false,

View File

@ -681,7 +681,7 @@ export class SpaceService {
} }
} }
buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] { private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] {
const map = new Map<string, SpaceEntity>(); const map = new Map<string, SpaceEntity>();
// Step 1: Create a map of spaces by UUID // Step 1: Create a map of spaces by UUID