mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-11 15:48:09 +00:00
Compare commits
72 Commits
daily-aqi-
...
add-queue-
Author | SHA1 | Date | |
---|---|---|---|
d1d4d529a8 | |||
cf19f08dca | |||
ff370b2baa | |||
04f64407e1 | |||
d7eef5d03e | |||
c8d691b380 | |||
75d03366c2 | |||
52cb69cc84 | |||
a6053b3971 | |||
60d2c8330b | |||
fddd06e06d | |||
3160773c2a | |||
110ed4157a | |||
aa9e90bf08 | |||
c5dd5e28fd | |||
603e74af09 | |||
0e36f32ed6 | |||
705ceeba29 | |||
a37d5bb299 | |||
689a38ee0c | |||
a91d0f22a4 | |||
0db060ae3f | |||
f2ed04f206 | |||
ea9a65178d | |||
8503ee728d | |||
4f5e1b23f6 | |||
2cb77504ca | |||
c86be27576 | |||
3a08f9f258 | |||
5c96a3b117 | |||
97e14e70f7 | |||
03d44cb14f | |||
0793441e06 | |||
b6321c2530 | |||
b8d34b0d9f | |||
c1065126aa | |||
1742454984 | |||
7eb13088ac | |||
7b97e50d2e | |||
4fb26fc131 | |||
ee0261d102 | |||
0d6de2df43 | |||
80e89dd035 | |||
466863e71f | |||
30aafdede6 | |||
01ce4d4b29 | |||
43dfaaa90d | |||
ea021ad228 | |||
cd3e9016f2 | |||
ef2245eae1 | |||
3ad81864d1 | |||
ab3efedc35 | |||
4a984ae5dd | |||
c39129f75b | |||
35ce13a67f | |||
12a9272b8b | |||
0fe6c80731 | |||
81e017430e | |||
191d0dfaf6 | |||
5b0135ba80 | |||
2fee8c055e | |||
59161d4049 | |||
b989338790 | |||
f5ed9d4fce | |||
3ac48183bd | |||
684205053d | |||
bfd92fdd87 | |||
dd54af5f46 | |||
90fc44ab53 | |||
efdf918159 | |||
25967d02f9 | |||
f44dc793a6 |
@ -21,6 +21,7 @@ module.exports = {
|
|||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
"@typescript-eslint/no-unused-vars": 'warn',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
|
import { PlatformType } from '@app/common/constants/platform-type.enum';
|
||||||
|
import { RoleType } from '@app/common/constants/role.type.enum';
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Injectable,
|
Injectable,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { HelperHashService } from '../../helper/services';
|
|
||||||
import { UserRepository } from '../../../../common/src/modules/user/repositories';
|
|
||||||
import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository';
|
|
||||||
import { UserSessionEntity } from '../../../../common/src/modules/session/entities';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { OAuth2Client } from 'google-auth-library';
|
import { OAuth2Client } from 'google-auth-library';
|
||||||
import { PlatformType } from '@app/common/constants/platform-type.enum';
|
import { UserSessionEntity } from '../../../../common/src/modules/session/entities';
|
||||||
import { RoleType } from '@app/common/constants/role.type.enum';
|
import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository';
|
||||||
|
import { UserRepository } from '../../../../common/src/modules/user/repositories';
|
||||||
|
import { HelperHashService } from '../../helper/services';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
@ -40,16 +40,17 @@ export class AuthService {
|
|||||||
},
|
},
|
||||||
relations: ['roleType', 'project'],
|
relations: ['roleType', 'project'],
|
||||||
});
|
});
|
||||||
if (
|
|
||||||
platform === PlatformType.WEB &&
|
|
||||||
(user.roleType.type === RoleType.SPACE_OWNER ||
|
|
||||||
user.roleType.type === RoleType.SPACE_MEMBER)
|
|
||||||
) {
|
|
||||||
throw new UnauthorizedException('Access denied for web platform');
|
|
||||||
}
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new BadRequestException('Invalid credentials');
|
throw new BadRequestException('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
platform === PlatformType.WEB &&
|
||||||
|
[RoleType.SPACE_OWNER, RoleType.SPACE_MEMBER].includes(
|
||||||
|
user.roleType.type as RoleType,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new UnauthorizedException('Access denied for web platform');
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.isUserVerified) {
|
if (!user.isUserVerified) {
|
||||||
throw new BadRequestException('User is not verified');
|
throw new BadRequestException('User is not verified');
|
||||||
|
@ -465,7 +465,16 @@ export class ControllerRoute {
|
|||||||
'This endpoint retrieves the terms and conditions for the application.';
|
'This endpoint retrieves the terms and conditions for the application.';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
static WEATHER = class {
|
||||||
|
public static readonly ROUTE = 'weather';
|
||||||
|
|
||||||
|
static ACTIONS = class {
|
||||||
|
public static readonly FETCH_WEATHER_DETAILS_SUMMARY =
|
||||||
|
'Fetch Weather Details';
|
||||||
|
public static readonly FETCH_WEATHER_DETAILS_DESCRIPTION =
|
||||||
|
'This endpoint retrieves the current weather details for a specified location like temperature, humidity, etc.';
|
||||||
|
};
|
||||||
|
};
|
||||||
static PRIVACY_POLICY = class {
|
static PRIVACY_POLICY = class {
|
||||||
public static readonly ROUTE = 'policy';
|
public static readonly ROUTE = 'policy';
|
||||||
|
|
||||||
@ -515,6 +524,20 @@ export class ControllerRoute {
|
|||||||
'This endpoint retrieves the occupancy heat map data based on the provided parameters.';
|
'This endpoint retrieves the occupancy heat map data based on the provided parameters.';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
static AQI = class {
|
||||||
|
public static readonly ROUTE = 'aqi';
|
||||||
|
|
||||||
|
static ACTIONS = class {
|
||||||
|
public static readonly GET_AQI_RANGE_DATA_SUMMARY = 'Get AQI range data';
|
||||||
|
public static readonly GET_AQI_RANGE_DATA_DESCRIPTION =
|
||||||
|
'This endpoint retrieves the AQI (Air Quality Index) range data based on the provided parameters.';
|
||||||
|
|
||||||
|
public static readonly GET_AQI_DISTRIBUTION_DATA_SUMMARY =
|
||||||
|
'Get AQI distribution data';
|
||||||
|
public static readonly GET_AQI_DISTRIBUTION_DATA_DESCRIPTION =
|
||||||
|
'This endpoint retrieves the AQI (Air Quality Index) distribution data based on the provided parameters.';
|
||||||
|
};
|
||||||
|
};
|
||||||
static DEVICE = class {
|
static DEVICE = class {
|
||||||
public static readonly ROUTE = 'devices';
|
public static readonly ROUTE = 'devices';
|
||||||
|
|
||||||
|
8
libs/common/src/constants/pollutants.enum.ts
Normal file
8
libs/common/src/constants/pollutants.enum.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export enum PollutantType {
|
||||||
|
AQI = 'aqi',
|
||||||
|
PM25 = 'pm25',
|
||||||
|
PM10 = 'pm10',
|
||||||
|
VOC = 'voc',
|
||||||
|
CO2 = 'co2',
|
||||||
|
CH2O = 'ch2o',
|
||||||
|
}
|
@ -19,4 +19,5 @@ export enum ProductType {
|
|||||||
FOUR_S = '4S',
|
FOUR_S = '4S',
|
||||||
SIX_S = '6S',
|
SIX_S = '6S',
|
||||||
SOS = 'SOS',
|
SOS = 'SOS',
|
||||||
|
AQI = 'AQI',
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,31 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { SnakeNamingStrategy } from './strategies';
|
|
||||||
import { UserEntity } from '../modules/user/entities/user.entity';
|
|
||||||
import { UserSessionEntity } from '../modules/session/entities/session.entity';
|
|
||||||
import { UserOtpEntity } from '../modules/user/entities';
|
|
||||||
import { ProductEntity } from '../modules/product/entities';
|
|
||||||
import { DeviceEntity } from '../modules/device/entities';
|
import { DeviceEntity } from '../modules/device/entities';
|
||||||
import { PermissionTypeEntity } from '../modules/permission/entities';
|
import { PermissionTypeEntity } from '../modules/permission/entities';
|
||||||
|
import { ProductEntity } from '../modules/product/entities';
|
||||||
|
import { UserSessionEntity } from '../modules/session/entities/session.entity';
|
||||||
|
import { UserOtpEntity } from '../modules/user/entities';
|
||||||
|
import { UserEntity } from '../modules/user/entities/user.entity';
|
||||||
|
import { SnakeNamingStrategy } from './strategies';
|
||||||
|
|
||||||
import { UserSpaceEntity } from '../modules/user/entities';
|
import { TypeOrmWinstonLogger } from '@app/common/logger/services/typeorm.logger';
|
||||||
import { DeviceUserPermissionEntity } from '../modules/device/entities';
|
import { createLogger } from 'winston';
|
||||||
import { RoleTypeEntity } from '../modules/role-type/entities';
|
import { winstonLoggerOptions } from '../logger/services/winston.logger';
|
||||||
import { UserNotificationEntity } from '../modules/user/entities';
|
import { AqiSpaceDailyPollutantStatsEntity } from '../modules/aqi/entities';
|
||||||
import { DeviceNotificationEntity } from '../modules/device/entities';
|
import { AutomationEntity } from '../modules/automation/entities';
|
||||||
import { RegionEntity } from '../modules/region/entities';
|
import { ClientEntity } from '../modules/client/entities';
|
||||||
import { TimeZoneEntity } from '../modules/timezone/entities';
|
|
||||||
import { VisitorPasswordEntity } from '../modules/visitor-password/entities';
|
|
||||||
import { CommunityEntity } from '../modules/community/entities';
|
import { CommunityEntity } from '../modules/community/entities';
|
||||||
import { DeviceStatusLogEntity } from '../modules/device-status-log/entities';
|
import { DeviceStatusLogEntity } from '../modules/device-status-log/entities';
|
||||||
import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
|
|
||||||
import { SceneDeviceEntity } from '../modules/scene-device/entities';
|
|
||||||
import { ProjectEntity } from '../modules/project/entities';
|
|
||||||
import {
|
import {
|
||||||
SpaceModelEntity,
|
DeviceNotificationEntity,
|
||||||
SubspaceModelEntity,
|
DeviceUserPermissionEntity,
|
||||||
TagModel,
|
} from '../modules/device/entities';
|
||||||
SpaceModelProductAllocationEntity,
|
|
||||||
SubspaceModelProductAllocationEntity,
|
|
||||||
} from '../modules/space-model/entities';
|
|
||||||
import {
|
import {
|
||||||
InviteUserEntity,
|
InviteUserEntity,
|
||||||
InviteUserSpaceEntity,
|
InviteUserSpaceEntity,
|
||||||
} from '../modules/Invite-user/entities';
|
} from '../modules/Invite-user/entities';
|
||||||
import { InviteSpaceEntity } from '../modules/space/entities/invite-space.entity';
|
import { SpaceDailyOccupancyDurationEntity } from '../modules/occupancy/entities';
|
||||||
import { AutomationEntity } from '../modules/automation/entities';
|
|
||||||
import { SpaceProductAllocationEntity } from '../modules/space/entities/space-product-allocation.entity';
|
|
||||||
import { NewTagEntity } from '../modules/tag/entities/tag.entity';
|
|
||||||
import { SpaceEntity } from '../modules/space/entities/space.entity';
|
|
||||||
import { SpaceLinkEntity } from '../modules/space/entities/space-link.entity';
|
|
||||||
import { SubspaceProductAllocationEntity } from '../modules/space/entities/subspace/subspace-product-allocation.entity';
|
|
||||||
import { SubspaceEntity } from '../modules/space/entities/subspace/subspace.entity';
|
|
||||||
import { TagEntity } from '../modules/space/entities/tag.entity';
|
|
||||||
import { ClientEntity } from '../modules/client/entities';
|
|
||||||
import { TypeOrmWinstonLogger } from '@app/common/logger/services/typeorm.logger';
|
|
||||||
import { createLogger } from 'winston';
|
|
||||||
import { winstonLoggerOptions } from '../logger/services/winston.logger';
|
|
||||||
import {
|
import {
|
||||||
PowerClampDailyEntity,
|
PowerClampDailyEntity,
|
||||||
PowerClampHourlyEntity,
|
PowerClampHourlyEntity,
|
||||||
@ -55,6 +35,29 @@ import {
|
|||||||
PresenceSensorDailyDeviceEntity,
|
PresenceSensorDailyDeviceEntity,
|
||||||
PresenceSensorDailySpaceEntity,
|
PresenceSensorDailySpaceEntity,
|
||||||
} from '../modules/presence-sensor/entities';
|
} from '../modules/presence-sensor/entities';
|
||||||
|
import { ProjectEntity } from '../modules/project/entities';
|
||||||
|
import { RegionEntity } from '../modules/region/entities';
|
||||||
|
import { RoleTypeEntity } from '../modules/role-type/entities';
|
||||||
|
import { SceneDeviceEntity } from '../modules/scene-device/entities';
|
||||||
|
import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
|
||||||
|
import {
|
||||||
|
SpaceModelEntity,
|
||||||
|
SpaceModelProductAllocationEntity,
|
||||||
|
SubspaceModelEntity,
|
||||||
|
SubspaceModelProductAllocationEntity,
|
||||||
|
} from '../modules/space-model/entities';
|
||||||
|
import { InviteSpaceEntity } from '../modules/space/entities/invite-space.entity';
|
||||||
|
import { SpaceProductAllocationEntity } from '../modules/space/entities/space-product-allocation.entity';
|
||||||
|
import { SpaceEntity } from '../modules/space/entities/space.entity';
|
||||||
|
import { SubspaceProductAllocationEntity } from '../modules/space/entities/subspace/subspace-product-allocation.entity';
|
||||||
|
import { SubspaceEntity } from '../modules/space/entities/subspace/subspace.entity';
|
||||||
|
import { NewTagEntity } from '../modules/tag/entities/tag.entity';
|
||||||
|
import { TimeZoneEntity } from '../modules/timezone/entities';
|
||||||
|
import {
|
||||||
|
UserNotificationEntity,
|
||||||
|
UserSpaceEntity,
|
||||||
|
} from '../modules/user/entities';
|
||||||
|
import { VisitorPasswordEntity } from '../modules/visitor-password/entities';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
@ -83,9 +86,7 @@ import {
|
|||||||
PermissionTypeEntity,
|
PermissionTypeEntity,
|
||||||
CommunityEntity,
|
CommunityEntity,
|
||||||
SpaceEntity,
|
SpaceEntity,
|
||||||
SpaceLinkEntity,
|
|
||||||
SubspaceEntity,
|
SubspaceEntity,
|
||||||
TagEntity,
|
|
||||||
UserSpaceEntity,
|
UserSpaceEntity,
|
||||||
DeviceUserPermissionEntity,
|
DeviceUserPermissionEntity,
|
||||||
RoleTypeEntity,
|
RoleTypeEntity,
|
||||||
@ -100,7 +101,6 @@ import {
|
|||||||
SceneDeviceEntity,
|
SceneDeviceEntity,
|
||||||
SpaceModelEntity,
|
SpaceModelEntity,
|
||||||
SubspaceModelEntity,
|
SubspaceModelEntity,
|
||||||
TagModel,
|
|
||||||
InviteUserEntity,
|
InviteUserEntity,
|
||||||
InviteUserSpaceEntity,
|
InviteUserSpaceEntity,
|
||||||
InviteSpaceEntity,
|
InviteSpaceEntity,
|
||||||
@ -115,6 +115,8 @@ import {
|
|||||||
PowerClampMonthlyEntity,
|
PowerClampMonthlyEntity,
|
||||||
PresenceSensorDailyDeviceEntity,
|
PresenceSensorDailyDeviceEntity,
|
||||||
PresenceSensorDailySpaceEntity,
|
PresenceSensorDailySpaceEntity,
|
||||||
|
AqiSpaceDailyPollutantStatsEntity,
|
||||||
|
SpaceDailyOccupancyDurationEntity,
|
||||||
],
|
],
|
||||||
namingStrategy: new SnakeNamingStrategy(),
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||||
@ -123,7 +125,7 @@ import {
|
|||||||
logger: typeOrmLogger,
|
logger: typeOrmLogger,
|
||||||
extra: {
|
extra: {
|
||||||
charset: 'utf8mb4',
|
charset: 'utf8mb4',
|
||||||
max: 20, // set pool max size
|
max: 100, // 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)
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { IsBoolean, IsDate, IsOptional } from 'class-validator';
|
|
||||||
import { IsPageRequestParam } from '../validators/is-page-request-param.validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsSizeRequestParam } from '../validators/is-size-request-param.validator';
|
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { parseToDate } from '../util/parseToDate';
|
import { IsBoolean, IsOptional } from 'class-validator';
|
||||||
import { BooleanValues } from '../constants/boolean-values.enum';
|
import { BooleanValues } from '../constants/boolean-values.enum';
|
||||||
|
import { IsPageRequestParam } from '../validators/is-page-request-param.validator';
|
||||||
|
import { IsSizeRequestParam } from '../validators/is-size-request-param.validator';
|
||||||
|
|
||||||
export class PaginationRequestGetListDto {
|
export class PaginationRequestGetListDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@ -19,6 +18,7 @@ export class PaginationRequestGetListDto {
|
|||||||
return value.obj.includeSpaces === BooleanValues.TRUE;
|
return value.obj.includeSpaces === BooleanValues.TRUE;
|
||||||
})
|
})
|
||||||
public includeSpaces?: boolean = false;
|
public includeSpaces?: boolean = false;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsPageRequestParam({
|
@IsPageRequestParam({
|
||||||
message: 'Page must be bigger than 0',
|
message: 'Page must be bigger than 0',
|
||||||
@ -40,40 +40,4 @@ export class PaginationRequestGetListDto {
|
|||||||
description: 'Size request',
|
description: 'Size request',
|
||||||
})
|
})
|
||||||
size?: number;
|
size?: number;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@ApiProperty({
|
|
||||||
name: 'name',
|
|
||||||
required: false,
|
|
||||||
description: 'Name to be filtered',
|
|
||||||
})
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
name: 'from',
|
|
||||||
required: false,
|
|
||||||
type: Number,
|
|
||||||
description: `Start time in UNIX timestamp format to filter`,
|
|
||||||
example: 1674172800000,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@Transform(({ value }) => parseToDate(value))
|
|
||||||
@IsDate({
|
|
||||||
message: `From must be in UNIX timestamp format in order to parse to Date instance`,
|
|
||||||
})
|
|
||||||
from?: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
name: 'to',
|
|
||||||
required: false,
|
|
||||||
type: Number,
|
|
||||||
description: `End time in UNIX timestamp format to filter`,
|
|
||||||
example: 1674259200000,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@Transform(({ value }) => parseToDate(value))
|
|
||||||
@IsDate({
|
|
||||||
message: `To must be in UNIX timestamp format in order to parse to Date instance`,
|
|
||||||
})
|
|
||||||
to?: Date;
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from '@app/common/modules/power-clamp/repositories';
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
@ -23,6 +24,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
controllers: [DeviceStatusFirebaseController],
|
controllers: [DeviceStatusFirebaseController],
|
||||||
exports: [DeviceStatusFirebaseService, DeviceStatusLogRepository],
|
exports: [DeviceStatusFirebaseService, DeviceStatusLogRepository],
|
||||||
|
@ -23,6 +23,7 @@ import { PowerClampService } from '@app/common/helper/services/power.clamp.servi
|
|||||||
import { PowerClampEnergyEnum } from '@app/common/constants/power.clamp.enargy.enum';
|
import { PowerClampEnergyEnum } from '@app/common/constants/power.clamp.enargy.enum';
|
||||||
import { PresenceSensorEnum } from '@app/common/constants/presence.sensor.enum';
|
import { PresenceSensorEnum } from '@app/common/constants/presence.sensor.enum';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceStatusFirebaseService {
|
export class DeviceStatusFirebaseService {
|
||||||
private tuya: TuyaContext;
|
private tuya: TuyaContext;
|
||||||
@ -32,6 +33,7 @@ export class DeviceStatusFirebaseService {
|
|||||||
private readonly deviceRepository: DeviceRepository,
|
private readonly deviceRepository: DeviceRepository,
|
||||||
private readonly powerClampService: PowerClampService,
|
private readonly powerClampService: PowerClampService,
|
||||||
private readonly occupancyService: OccupancyService,
|
private readonly occupancyService: OccupancyService,
|
||||||
|
private readonly aqiDataService: AqiDataService,
|
||||||
private deviceStatusLogRepository: DeviceStatusLogRepository,
|
private deviceStatusLogRepository: DeviceStatusLogRepository,
|
||||||
) {
|
) {
|
||||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||||
@ -74,6 +76,28 @@ export class DeviceStatusFirebaseService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async addDeviceStatusToOurDb(
|
||||||
|
addDeviceStatusDto: AddDeviceStatusDto,
|
||||||
|
): Promise<AddDeviceStatusDto | null> {
|
||||||
|
try {
|
||||||
|
const device = await this.getDeviceByDeviceTuyaUuid(
|
||||||
|
addDeviceStatusDto.deviceTuyaUuid,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (device?.uuid) {
|
||||||
|
return await this.createDeviceStatusInOurDb({
|
||||||
|
deviceUuid: device.uuid,
|
||||||
|
...addDeviceStatusDto,
|
||||||
|
productType: device.productDevice.prodType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Return null if device not found or no UUID
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
// Handle the error silently, perhaps log it internally or ignore it
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
async addDeviceStatusToFirebase(
|
async addDeviceStatusToFirebase(
|
||||||
addDeviceStatusDto: AddDeviceStatusDto,
|
addDeviceStatusDto: AddDeviceStatusDto,
|
||||||
): Promise<AddDeviceStatusDto | null> {
|
): Promise<AddDeviceStatusDto | null> {
|
||||||
@ -209,6 +233,13 @@ export class DeviceStatusFirebaseService {
|
|||||||
return existingData;
|
return existingData;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Return the updated data
|
||||||
|
const snapshot: DataSnapshot = await get(dataRef);
|
||||||
|
return snapshot.val();
|
||||||
|
}
|
||||||
|
async createDeviceStatusInOurDb(
|
||||||
|
addDeviceStatusDto: AddDeviceStatusDto,
|
||||||
|
): Promise<any> {
|
||||||
// Save logs to your repository
|
// Save logs to your repository
|
||||||
const newLogs = addDeviceStatusDto.log.properties.map((property) => {
|
const newLogs = addDeviceStatusDto.log.properties.map((property) => {
|
||||||
return this.deviceStatusLogRepository.create({
|
return this.deviceStatusLogRepository.create({
|
||||||
@ -262,9 +293,10 @@ export class DeviceStatusFirebaseService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (addDeviceStatusDto.productType === ProductType.AQI) {
|
||||||
// Return the updated data
|
await this.aqiDataService.updateAQISensorHistoricalData(
|
||||||
const snapshot: DataSnapshot = await get(dataRef);
|
addDeviceStatusDto.deviceUuid,
|
||||||
return snapshot.val();
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
libs/common/src/helper/services/aqi.data.service.ts
Normal file
47
libs/common/src/helper/services/aqi.data.service.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { SqlLoaderService } from './sql-loader.service';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AqiDataService {
|
||||||
|
constructor(
|
||||||
|
private readonly sqlLoader: SqlLoaderService,
|
||||||
|
private readonly dataSource: DataSource,
|
||||||
|
private readonly deviceRepository: DeviceRepository,
|
||||||
|
) {}
|
||||||
|
async updateAQISensorHistoricalData(deviceUuid: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const dateStr = now.toLocaleDateString('en-CA'); // YYYY-MM-DD
|
||||||
|
const device = await this.deviceRepository.findOne({
|
||||||
|
where: { uuid: deviceUuid },
|
||||||
|
relations: ['spaceDevice'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.executeProcedure(
|
||||||
|
'fact_daily_space_aqi',
|
||||||
|
'proceduce_update_daily_space_aqi',
|
||||||
|
[dateStr, device.spaceDevice?.uuid],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to insert or update aqi data:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeProcedure(
|
||||||
|
procedureFolderName: string,
|
||||||
|
procedureFileName: string,
|
||||||
|
params: (string | number | null)[],
|
||||||
|
): Promise<void> {
|
||||||
|
const query = this.loadQuery(procedureFolderName, procedureFileName);
|
||||||
|
await this.dataSource.query(query, params);
|
||||||
|
console.log(`Procedure ${procedureFileName} executed successfully.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadQuery(folderName: string, fileName: string): string {
|
||||||
|
return this.sqlLoader.loadQuery(folderName, fileName, SQL_PROCEDURES_PATH);
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ export class SosHandlerService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSosEvent(devId: string, logData: any): Promise<void> {
|
async handleSosEventFirebase(devId: string, logData: any): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this.deviceStatusFirebaseService.addDeviceStatusToFirebase({
|
await this.deviceStatusFirebaseService.addDeviceStatusToFirebase({
|
||||||
deviceTuyaUuid: devId,
|
deviceTuyaUuid: devId,
|
||||||
@ -39,4 +39,28 @@ export class SosHandlerService {
|
|||||||
this.logger.error('Failed to send SOS true value', err);
|
this.logger.error('Failed to send SOS true value', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSosEventOurDb(devId: string, logData: any): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.deviceStatusFirebaseService.addDeviceStatusToOurDb({
|
||||||
|
deviceTuyaUuid: devId,
|
||||||
|
status: [{ code: 'sos', value: true }],
|
||||||
|
log: logData,
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
await this.deviceStatusFirebaseService.addDeviceStatusToOurDb({
|
||||||
|
deviceTuyaUuid: devId,
|
||||||
|
status: [{ code: 'sos', value: false }],
|
||||||
|
log: logData,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Failed to send SOS false value', err);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error('Failed to send SOS true value', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,14 @@ export class TuyaWebSocketService {
|
|||||||
private client: any;
|
private client: any;
|
||||||
private readonly isDevEnv: boolean;
|
private readonly isDevEnv: boolean;
|
||||||
|
|
||||||
|
private messageQueue: {
|
||||||
|
devId: string;
|
||||||
|
status: any;
|
||||||
|
logData: any;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
private isProcessing = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService,
|
private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService,
|
||||||
@ -26,12 +34,12 @@ export class TuyaWebSocketService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.configService.get<string>('tuya-config.TRUN_ON_TUYA_SOCKET')) {
|
if (this.configService.get<string>('tuya-config.TRUN_ON_TUYA_SOCKET')) {
|
||||||
// Set up event handlers
|
|
||||||
this.setupEventHandlers();
|
this.setupEventHandlers();
|
||||||
|
|
||||||
// Start receiving messages
|
|
||||||
this.client.start();
|
this.client.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger the queue processor every 2 seconds
|
||||||
|
setInterval(() => this.processQueue(), 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupEventHandlers() {
|
private setupEventHandlers() {
|
||||||
@ -43,10 +51,10 @@ export class TuyaWebSocketService {
|
|||||||
this.client.message(async (ws: WebSocket, message: any) => {
|
this.client.message(async (ws: WebSocket, message: any) => {
|
||||||
try {
|
try {
|
||||||
const { devId, status, logData } = this.extractMessageData(message);
|
const { devId, status, logData } = this.extractMessageData(message);
|
||||||
|
|
||||||
if (this.sosHandlerService.isSosTriggered(status)) {
|
if (this.sosHandlerService.isSosTriggered(status)) {
|
||||||
await this.sosHandlerService.handleSosEvent(devId, logData);
|
await this.sosHandlerService.handleSosEventFirebase(devId, logData);
|
||||||
} else {
|
} else {
|
||||||
|
// Firebase real-time update
|
||||||
await this.deviceStatusFirebaseService.addDeviceStatusToFirebase({
|
await this.deviceStatusFirebaseService.addDeviceStatusToFirebase({
|
||||||
deviceTuyaUuid: devId,
|
deviceTuyaUuid: devId,
|
||||||
status: status,
|
status: status,
|
||||||
@ -54,9 +62,13 @@ export class TuyaWebSocketService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push to internal queue
|
||||||
|
this.messageQueue.push({ devId, status, logData });
|
||||||
|
|
||||||
|
// Acknowledge the message
|
||||||
this.client.ackMessage(message.messageId);
|
this.client.ackMessage(message.messageId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing message:', error);
|
console.error('Error receiving message:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,6 +92,38 @@ export class TuyaWebSocketService {
|
|||||||
console.error('WebSocket error:', error);
|
console.error('WebSocket error:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
private async processQueue() {
|
||||||
|
if (this.isProcessing || this.messageQueue.length === 0) return;
|
||||||
|
|
||||||
|
this.isProcessing = true;
|
||||||
|
|
||||||
|
const batch = [...this.messageQueue];
|
||||||
|
this.messageQueue = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const item of batch) {
|
||||||
|
if (this.sosHandlerService.isSosTriggered(item.status)) {
|
||||||
|
await this.sosHandlerService.handleSosEventOurDb(
|
||||||
|
item.devId,
|
||||||
|
item.logData,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.deviceStatusFirebaseService.addDeviceStatusToOurDb({
|
||||||
|
deviceTuyaUuid: item.devId,
|
||||||
|
status: item.status,
|
||||||
|
log: item.logData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing batch:', error);
|
||||||
|
// Re-add the batch to the queue for retry
|
||||||
|
this.messageQueue.unshift(...batch);
|
||||||
|
} finally {
|
||||||
|
this.isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extractMessageData(message: any): {
|
private extractMessageData(message: any): {
|
||||||
devId: string;
|
devId: string;
|
||||||
status: any;
|
status: any;
|
||||||
|
11
libs/common/src/modules/aqi/aqi.repository.module.ts
Normal file
11
libs/common/src/modules/aqi/aqi.repository.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { AqiSpaceDailyPollutantStatsEntity } from './entities/aqi.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([AqiSpaceDailyPollutantStatsEntity])],
|
||||||
|
})
|
||||||
|
export class AqiRepositoryModule {}
|
82
libs/common/src/modules/aqi/dtos/aqi.dto.ts
Normal file
82
libs/common/src/modules/aqi/dtos/aqi.dto.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class AqiSpaceDailyPollutantStatsDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
spaceUuid: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
eventDay: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsNumber()
|
||||||
|
eventHour: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm1Min: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm1Avg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm1Max: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm10Min: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm10Avg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm10Max: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm25Min: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm25Avg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
pm25Max: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
ch2oMin: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
ch2oAvg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
ch2oMax: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
vocMin: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
vocAvg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
vocMax: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
co2Min: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
co2Avg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
co2Max: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
aqiMin: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
aqiAvg: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
aqiMax: number;
|
||||||
|
}
|
1
libs/common/src/modules/aqi/dtos/index.ts
Normal file
1
libs/common/src/modules/aqi/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './aqi.dto';
|
184
libs/common/src/modules/aqi/entities/aqi.entity.ts
Normal file
184
libs/common/src/modules/aqi/entities/aqi.entity.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||||
|
import { AqiSpaceDailyPollutantStatsDto } from '../dtos';
|
||||||
|
|
||||||
|
@Entity({ name: 'space-daily-pollutant-stats' })
|
||||||
|
@Unique(['spaceUuid', 'eventDate'])
|
||||||
|
export class AqiSpaceDailyPollutantStatsEntity extends AbstractEntity<AqiSpaceDailyPollutantStatsDto> {
|
||||||
|
@Column({ nullable: false })
|
||||||
|
public spaceUuid: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => SpaceEntity, (space) => space.aqiSensorDaily)
|
||||||
|
space: SpaceEntity;
|
||||||
|
|
||||||
|
@Column({ type: 'date', nullable: false })
|
||||||
|
public eventDate: Date;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public goodAqiPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public moderateAqiPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthySensitiveAqiPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthyAqiPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public veryUnhealthyAqiPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public hazardousAqiPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyAvgAqi?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMaxAqi?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMinAqi?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public goodPm25Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public moderatePm25Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthySensitivePm25Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthyPm25Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public veryUnhealthyPm25Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public hazardousPm25Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyAvgPm25?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMaxPm25?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMinPm25?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public goodPm10Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public moderatePm10Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthySensitivePm10Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthyPm10Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public veryUnhealthyPm10Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public hazardousPm10Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyAvgPm10?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMaxPm10?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMinPm10?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public goodVocPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public moderateVocPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthySensitiveVocPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthyVocPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public veryUnhealthyVocPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public hazardousVocPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyAvgVoc?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMaxVoc?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMinVoc?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public goodCo2Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public moderateCo2Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthySensitiveCo2Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthyCo2Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public veryUnhealthyCo2Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public hazardousCo2Percentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyAvgCo2?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMaxCo2?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMinCo2?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public goodCh2oPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public moderateCh2oPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthySensitiveCh2oPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public unhealthyCh2oPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public veryUnhealthyCh2oPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public hazardousCh2oPercentage?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyAvgCh2o?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMaxCh2o?: number;
|
||||||
|
|
||||||
|
@Column('float', { nullable: true })
|
||||||
|
public dailyMinCh2o?: number;
|
||||||
|
|
||||||
|
constructor(partial: Partial<AqiSpaceDailyPollutantStatsEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
1
libs/common/src/modules/aqi/entities/index.ts
Normal file
1
libs/common/src/modules/aqi/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './aqi.entity';
|
10
libs/common/src/modules/aqi/repositories/aqi.repository.ts
Normal file
10
libs/common/src/modules/aqi/repositories/aqi.repository.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AqiSpaceDailyPollutantStatsEntity } from '../entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AqiSpaceDailyPollutantStatsRepository extends Repository<AqiSpaceDailyPollutantStatsEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(AqiSpaceDailyPollutantStatsEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
1
libs/common/src/modules/aqi/repositories/index.ts
Normal file
1
libs/common/src/modules/aqi/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './aqi.repository';
|
@ -2,15 +2,15 @@ import { SourceType } from '@app/common/constants/source-type.enum';
|
|||||||
import { Entity, Column, PrimaryColumn, Unique } from 'typeorm';
|
import { Entity, Column, PrimaryColumn, Unique } from 'typeorm';
|
||||||
|
|
||||||
@Entity('device-status-log')
|
@Entity('device-status-log')
|
||||||
@Unique('event_time_idx', ['eventTime'])
|
@Unique('event_time_idx', ['eventTime', 'deviceId', 'code', 'value'])
|
||||||
export class DeviceStatusLogEntity {
|
export class DeviceStatusLogEntity {
|
||||||
@Column({ type: 'int', generated: true, unsigned: true })
|
@PrimaryColumn({ type: 'int', generated: true, unsigned: true })
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column({ type: 'text' })
|
@Column({ type: 'text' })
|
||||||
eventId: string;
|
eventId: string;
|
||||||
|
|
||||||
@PrimaryColumn({ type: 'timestamptz' })
|
@Column({ type: 'timestamptz' })
|
||||||
eventTime: Date;
|
eventTime: Date;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
|
@ -78,8 +78,8 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
|||||||
@OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {})
|
@OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {})
|
||||||
sceneDevices: SceneDeviceEntity[];
|
sceneDevices: SceneDeviceEntity[];
|
||||||
|
|
||||||
@OneToMany(() => NewTagEntity, (tag) => tag.devices)
|
@ManyToOne(() => NewTagEntity, (tag) => tag.devices)
|
||||||
// @JoinTable({ name: 'device_tags' })
|
@JoinColumn({ name: 'tag_uuid' })
|
||||||
public tag: NewTagEntity;
|
public tag: NewTagEntity;
|
||||||
@OneToMany(() => PowerClampHourlyEntity, (powerClamp) => powerClamp.device)
|
@OneToMany(() => PowerClampHourlyEntity, (powerClamp) => powerClamp.device)
|
||||||
powerClampHourly: PowerClampHourlyEntity[];
|
powerClampHourly: PowerClampHourlyEntity[];
|
||||||
|
1
libs/common/src/modules/occupancy/dtos/index.ts
Normal file
1
libs/common/src/modules/occupancy/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './occupancy.dto';
|
23
libs/common/src/modules/occupancy/dtos/occupancy.dto.ts
Normal file
23
libs/common/src/modules/occupancy/dtos/occupancy.dto.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class SpaceDailyOccupancyDurationDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public spaceUuid: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public eventDate: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public occupancyPercentage: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public occupiedSeconds: number;
|
||||||
|
}
|
1
libs/common/src/modules/occupancy/entities/index.ts
Normal file
1
libs/common/src/modules/occupancy/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './occupancy.entity';
|
@ -0,0 +1,32 @@
|
|||||||
|
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||||
|
import { SpaceDailyOccupancyDurationDto } from '../dtos';
|
||||||
|
|
||||||
|
@Entity({ name: 'space-daily-occupancy-duration' })
|
||||||
|
@Unique(['spaceUuid', 'eventDate'])
|
||||||
|
export class SpaceDailyOccupancyDurationEntity extends AbstractEntity<SpaceDailyOccupancyDurationDto> {
|
||||||
|
@Column({ nullable: false })
|
||||||
|
public spaceUuid: string;
|
||||||
|
|
||||||
|
@Column({ nullable: false, type: 'date' })
|
||||||
|
public eventDate: string;
|
||||||
|
|
||||||
|
public CountTotalPresenceDetected: number;
|
||||||
|
|
||||||
|
@ManyToOne(() => SpaceEntity, (space) => space.presenceSensorDaily)
|
||||||
|
space: SpaceEntity;
|
||||||
|
|
||||||
|
@Column({ type: 'int' })
|
||||||
|
occupancyPercentage: number;
|
||||||
|
|
||||||
|
@Column({ type: 'int', nullable: true })
|
||||||
|
occupiedSeconds?: number;
|
||||||
|
|
||||||
|
@Column({ type: 'int', nullable: true })
|
||||||
|
deviceCount?: number;
|
||||||
|
constructor(partial: Partial<SpaceDailyOccupancyDurationEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { SpaceDailyOccupancyDurationEntity } from './entities/occupancy.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([SpaceDailyOccupancyDurationEntity])],
|
||||||
|
})
|
||||||
|
export class SpaceDailyOccupancyDurationRepositoryModule {}
|
1
libs/common/src/modules/occupancy/repositories/index.ts
Normal file
1
libs/common/src/modules/occupancy/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './occupancy.repository';
|
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { SpaceDailyOccupancyDurationEntity } from '../entities/occupancy.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SpaceDailyOccupancyDurationEntityRepository extends Repository<SpaceDailyOccupancyDurationEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(SpaceDailyOccupancyDurationEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
import { Column, Entity, OneToMany } from 'typeorm';
|
import { Column, Entity, OneToMany } from 'typeorm';
|
||||||
import { ProductDto } from '../dtos';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { DeviceEntity } from '../../device/entities';
|
import { DeviceEntity } from '../../device/entities';
|
||||||
import { TagModel } from '../../space-model';
|
import { ProductDto } from '../dtos';
|
||||||
import { TagEntity } from '../../space/entities/tag.entity';
|
|
||||||
import { NewTagEntity } from '../../tag/entities';
|
|
||||||
@Entity({ name: 'product' })
|
@Entity({ name: 'product' })
|
||||||
export class ProductEntity extends AbstractEntity<ProductDto> {
|
export class ProductEntity extends AbstractEntity<ProductDto> {
|
||||||
@Column({
|
@Column({
|
||||||
@ -28,15 +25,6 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
|
|||||||
})
|
})
|
||||||
public prodType: string;
|
public prodType: string;
|
||||||
|
|
||||||
@OneToMany(() => NewTagEntity, (tag) => tag.product, { cascade: true })
|
|
||||||
public newTags: NewTagEntity[];
|
|
||||||
|
|
||||||
@OneToMany(() => TagModel, (tag) => tag.product)
|
|
||||||
tagModels: TagModel[];
|
|
||||||
|
|
||||||
@OneToMany(() => TagEntity, (tag) => tag.product)
|
|
||||||
tags: TagEntity[];
|
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => DeviceEntity,
|
() => DeviceEntity,
|
||||||
(devicesProductEntity) => devicesProductEntity.productDevice,
|
(devicesProductEntity) => devicesProductEntity.productDevice,
|
||||||
|
@ -12,6 +12,7 @@ export class RoleTypeEntity extends AbstractEntity<RoleTypeDto> {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
enum: Object.values(RoleType),
|
enum: Object.values(RoleType),
|
||||||
})
|
})
|
||||||
|
// why is this ts-type string not enum?
|
||||||
type: string;
|
type: string;
|
||||||
@OneToMany(() => UserEntity, (inviteUser) => inviteUser.roleType, {
|
@OneToMany(() => UserEntity, (inviteUser) => inviteUser.roleType, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class TagModelDto {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
public uuid: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
public name: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
public productUuid: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
spaceModelUuid: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
subspaceModelUuid: string;
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
|
export * from './space-model-product-allocation.entity';
|
||||||
export * from './space-model.entity';
|
export * from './space-model.entity';
|
||||||
export * from './subspace-model';
|
export * from './subspace-model';
|
||||||
export * from './tag-model.entity';
|
|
||||||
export * from './space-model-product-allocation.entity';
|
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import {
|
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||||
Entity,
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
Column,
|
|
||||||
ManyToOne,
|
|
||||||
ManyToMany,
|
|
||||||
JoinTable,
|
|
||||||
OneToMany,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { SpaceModelEntity } from './space-model.entity';
|
|
||||||
import { NewTagEntity } from '../../tag/entities/tag.entity';
|
|
||||||
import { ProductEntity } from '../../product/entities/product.entity';
|
import { ProductEntity } from '../../product/entities/product.entity';
|
||||||
import { SpaceProductAllocationEntity } from '../../space/entities/space-product-allocation.entity';
|
import { SpaceProductAllocationEntity } from '../../space/entities/space-product-allocation.entity';
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { NewTagEntity } from '../../tag/entities/tag.entity';
|
||||||
|
import { SpaceModelEntity } from './space-model.entity';
|
||||||
|
|
||||||
@Entity({ name: 'space_model_product_allocation' })
|
@Entity({ name: 'space_model_product_allocation' })
|
||||||
|
@Unique(['spaceModel', 'product', 'tag'])
|
||||||
export class SpaceModelProductAllocationEntity extends AbstractEntity<SpaceModelProductAllocationEntity> {
|
export class SpaceModelProductAllocationEntity extends AbstractEntity<SpaceModelProductAllocationEntity> {
|
||||||
@Column({
|
@Column({
|
||||||
type: 'uuid',
|
type: 'uuid',
|
||||||
@ -31,9 +25,8 @@ export class SpaceModelProductAllocationEntity extends AbstractEntity<SpaceModel
|
|||||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||||
public product: ProductEntity;
|
public product: ProductEntity;
|
||||||
|
|
||||||
@ManyToMany(() => NewTagEntity, { cascade: true, onDelete: 'CASCADE' })
|
@ManyToOne(() => NewTagEntity, { nullable: true, onDelete: 'CASCADE' })
|
||||||
@JoinTable({ name: 'space_model_product_tags' })
|
public tag: NewTagEntity;
|
||||||
public tags: NewTagEntity[];
|
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => SpaceProductAllocationEntity,
|
() => SpaceProductAllocationEntity,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Entity, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
|
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { SpaceModelDto } from '../dtos';
|
|
||||||
import { SubspaceModelEntity } from './subspace-model';
|
|
||||||
import { ProjectEntity } from '../../project/entities';
|
import { ProjectEntity } from '../../project/entities';
|
||||||
import { TagModel } from './tag-model.entity';
|
|
||||||
import { SpaceModelProductAllocationEntity } from './space-model-product-allocation.entity';
|
|
||||||
import { SpaceEntity } from '../../space/entities/space.entity';
|
import { SpaceEntity } from '../../space/entities/space.entity';
|
||||||
|
import { SpaceModelDto } from '../dtos';
|
||||||
|
import { SpaceModelProductAllocationEntity } from './space-model-product-allocation.entity';
|
||||||
|
import { SubspaceModelEntity } from './subspace-model';
|
||||||
|
|
||||||
@Entity({ name: 'space-model' })
|
@Entity({ name: 'space-model' })
|
||||||
export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
|
export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
|
||||||
@ -49,9 +48,6 @@ export class SpaceModelEntity extends AbstractEntity<SpaceModelDto> {
|
|||||||
})
|
})
|
||||||
public spaces: SpaceEntity[];
|
public spaces: SpaceEntity[];
|
||||||
|
|
||||||
@OneToMany(() => TagModel, (tag) => tag.spaceModel)
|
|
||||||
tags: TagModel[];
|
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => SpaceModelProductAllocationEntity,
|
() => SpaceModelProductAllocationEntity,
|
||||||
(allocation) => allocation.spaceModel,
|
(allocation) => allocation.spaceModel,
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Entity, Column, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
|
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
||||||
import { SubspaceModelEntity } from './subspace-model.entity';
|
|
||||||
import { ProductEntity } from '@app/common/modules/product/entities/product.entity';
|
import { ProductEntity } from '@app/common/modules/product/entities/product.entity';
|
||||||
import { NewTagEntity } from '@app/common/modules/tag/entities/tag.entity';
|
import { NewTagEntity } from '@app/common/modules/tag/entities/tag.entity';
|
||||||
|
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||||
import { SubspaceModelProductAllocationDto } from '../../dtos/subspace-model/subspace-model-product-allocation.dto';
|
import { SubspaceModelProductAllocationDto } from '../../dtos/subspace-model/subspace-model-product-allocation.dto';
|
||||||
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
import { SubspaceModelEntity } from './subspace-model.entity';
|
||||||
|
|
||||||
@Entity({ name: 'subspace_model_product_allocation' })
|
@Entity({ name: 'subspace_model_product_allocation' })
|
||||||
|
@Unique(['subspaceModel', 'product', 'tag'])
|
||||||
export class SubspaceModelProductAllocationEntity extends AbstractEntity<SubspaceModelProductAllocationDto> {
|
export class SubspaceModelProductAllocationEntity extends AbstractEntity<SubspaceModelProductAllocationDto> {
|
||||||
@Column({
|
@Column({
|
||||||
type: 'uuid',
|
type: 'uuid',
|
||||||
@ -27,12 +28,8 @@ export class SubspaceModelProductAllocationEntity extends AbstractEntity<Subspac
|
|||||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||||
public product: ProductEntity;
|
public product: ProductEntity;
|
||||||
|
|
||||||
@ManyToMany(() => NewTagEntity, (tag) => tag.subspaceModelAllocations, {
|
@ManyToOne(() => NewTagEntity, { nullable: true, onDelete: 'CASCADE' })
|
||||||
cascade: true,
|
public tag: NewTagEntity;
|
||||||
onDelete: 'CASCADE',
|
|
||||||
})
|
|
||||||
@JoinTable({ name: 'subspace_model_product_tags' })
|
|
||||||
public tags: NewTagEntity[];
|
|
||||||
|
|
||||||
constructor(partial: Partial<SubspaceModelProductAllocationEntity>) {
|
constructor(partial: Partial<SubspaceModelProductAllocationEntity>) {
|
||||||
super();
|
super();
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
||||||
|
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||||
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
||||||
import { SubSpaceModelDto } from '../../dtos';
|
import { SubSpaceModelDto } from '../../dtos';
|
||||||
import { SpaceModelEntity } from '../space-model.entity';
|
import { SpaceModelEntity } from '../space-model.entity';
|
||||||
import { TagModel } from '../tag-model.entity';
|
|
||||||
import { SubspaceModelProductAllocationEntity } from './subspace-model-product-allocation.entity';
|
import { SubspaceModelProductAllocationEntity } from './subspace-model-product-allocation.entity';
|
||||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'subspace-model' })
|
@Entity({ name: 'subspace-model' })
|
||||||
export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
|
export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
|
||||||
@ -41,9 +40,6 @@ export class SubspaceModelEntity extends AbstractEntity<SubSpaceModelDto> {
|
|||||||
})
|
})
|
||||||
public disabled: boolean;
|
public disabled: boolean;
|
||||||
|
|
||||||
@OneToMany(() => TagModel, (tag) => tag.subspaceModel)
|
|
||||||
tags: TagModel[];
|
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => SubspaceModelProductAllocationEntity,
|
() => SubspaceModelProductAllocationEntity,
|
||||||
(allocation) => allocation.subspaceModel,
|
(allocation) => allocation.subspaceModel,
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
|
||||||
import { TagModelDto } from '../dtos/tag-model.dto';
|
|
||||||
import { SpaceModelEntity } from './space-model.entity';
|
|
||||||
import { SubspaceModelEntity } from './subspace-model';
|
|
||||||
import { ProductEntity } from '../../product/entities';
|
|
||||||
import { TagEntity } from '../../space/entities/tag.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'tag_model' })
|
|
||||||
export class TagModel extends AbstractEntity<TagModelDto> {
|
|
||||||
@Column({ type: 'varchar', length: 255 })
|
|
||||||
tag: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductEntity, (product) => product.tagModels, {
|
|
||||||
nullable: false,
|
|
||||||
})
|
|
||||||
@JoinColumn({ name: 'product_id' })
|
|
||||||
product: ProductEntity;
|
|
||||||
|
|
||||||
@ManyToOne(() => SpaceModelEntity, (space) => space.tags, { nullable: true })
|
|
||||||
@JoinColumn({ name: 'space_model_id' })
|
|
||||||
spaceModel: SpaceModelEntity;
|
|
||||||
|
|
||||||
@ManyToOne(() => SubspaceModelEntity, (subspace) => subspace.tags, {
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
@JoinColumn({ name: 'subspace_model_id' })
|
|
||||||
subspaceModel: SubspaceModelEntity;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: false,
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public disabled: boolean;
|
|
||||||
|
|
||||||
@OneToMany(() => TagEntity, (tag) => tag.model)
|
|
||||||
tags: TagEntity[];
|
|
||||||
}
|
|
@ -1,11 +1,10 @@
|
|||||||
import { DataSource, Repository } from 'typeorm';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
SpaceModelEntity,
|
SpaceModelEntity,
|
||||||
SpaceModelProductAllocationEntity,
|
SpaceModelProductAllocationEntity,
|
||||||
SubspaceModelEntity,
|
SubspaceModelEntity,
|
||||||
SubspaceModelProductAllocationEntity,
|
SubspaceModelProductAllocationEntity,
|
||||||
TagModel,
|
|
||||||
} from '../entities';
|
} from '../entities';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -21,13 +20,6 @@ export class SubspaceModelRepository extends Repository<SubspaceModelEntity> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TagModelRepository extends Repository<TagModel> {
|
|
||||||
constructor(private dataSource: DataSource) {
|
|
||||||
super(TagModel, dataSource.createEntityManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SpaceModelProductAllocationRepoitory extends Repository<SpaceModelProductAllocationEntity> {
|
export class SpaceModelProductAllocationRepoitory extends Repository<SpaceModelProductAllocationEntity> {
|
||||||
constructor(private dataSource: DataSource) {
|
constructor(private dataSource: DataSource) {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { SpaceModelEntity, SubspaceModelEntity, TagModel } from './entities';
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { SpaceModelEntity, SubspaceModelEntity } from './entities';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [],
|
exports: [],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
imports: [
|
imports: [TypeOrmModule.forFeature([SpaceModelEntity, SubspaceModelEntity])],
|
||||||
TypeOrmModule.forFeature([SpaceModelEntity, SubspaceModelEntity, TagModel]),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class SpaceModelRepositoryModule {}
|
export class SpaceModelRepositoryModule {}
|
||||||
|
@ -1,32 +1,3 @@
|
|||||||
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { SpaceEntity } from './space.entity';
|
|
||||||
import { Direction } from '@app/common/constants/direction.enum';
|
|
||||||
|
|
||||||
@Entity({ name: 'space-link' })
|
export class SpaceLinkEntity extends AbstractEntity {}
|
||||||
export class SpaceLinkEntity extends AbstractEntity {
|
|
||||||
@ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' })
|
|
||||||
@JoinColumn({ name: 'start_space_id' })
|
|
||||||
public startSpace: SpaceEntity;
|
|
||||||
|
|
||||||
@ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' })
|
|
||||||
@JoinColumn({ name: 'end_space_id' })
|
|
||||||
public endSpace: SpaceEntity;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: false,
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public disabled: boolean;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: false,
|
|
||||||
enum: Object.values(Direction),
|
|
||||||
})
|
|
||||||
direction: string;
|
|
||||||
|
|
||||||
constructor(partial: Partial<SpaceLinkEntity>) {
|
|
||||||
super();
|
|
||||||
Object.assign(this, partial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Entity, Column, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
|
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||||
import { SpaceEntity } from './space.entity';
|
|
||||||
import { SpaceModelProductAllocationEntity } from '../../space-model/entities/space-model-product-allocation.entity';
|
|
||||||
import { ProductEntity } from '../../product/entities/product.entity';
|
|
||||||
import { NewTagEntity } from '../../tag/entities/tag.entity';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { ProductEntity } from '../../product/entities/product.entity';
|
||||||
|
import { SpaceModelProductAllocationEntity } from '../../space-model/entities/space-model-product-allocation.entity';
|
||||||
|
import { NewTagEntity } from '../../tag/entities/tag.entity';
|
||||||
import { SpaceProductAllocationDto } from '../dtos/space-product-allocation.dto';
|
import { SpaceProductAllocationDto } from '../dtos/space-product-allocation.dto';
|
||||||
|
import { SpaceEntity } from './space.entity';
|
||||||
|
|
||||||
@Entity({ name: 'space_product_allocation' })
|
@Entity({ name: 'space_product_allocation' })
|
||||||
|
@Unique(['space', 'product', 'tag'], {})
|
||||||
export class SpaceProductAllocationEntity extends AbstractEntity<SpaceProductAllocationDto> {
|
export class SpaceProductAllocationEntity extends AbstractEntity<SpaceProductAllocationDto> {
|
||||||
@Column({
|
@Column({
|
||||||
type: 'uuid',
|
type: 'uuid',
|
||||||
@ -30,9 +31,8 @@ export class SpaceProductAllocationEntity extends AbstractEntity<SpaceProductAll
|
|||||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||||
public product: ProductEntity;
|
public product: ProductEntity;
|
||||||
|
|
||||||
@ManyToMany(() => NewTagEntity)
|
@ManyToOne(() => NewTagEntity, { nullable: true, onDelete: 'CASCADE' })
|
||||||
@JoinTable({ name: 'space_product_tags' })
|
public tag: NewTagEntity;
|
||||||
public tags: NewTagEntity[];
|
|
||||||
|
|
||||||
constructor(partial: Partial<SpaceProductAllocationEntity>) {
|
constructor(partial: Partial<SpaceProductAllocationEntity>) {
|
||||||
super();
|
super();
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||||
import { SpaceDto } from '../dtos';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { UserSpaceEntity } from '../../user/entities';
|
import { AqiSpaceDailyPollutantStatsEntity } from '../../aqi/entities';
|
||||||
import { DeviceEntity } from '../../device/entities';
|
|
||||||
import { CommunityEntity } from '../../community/entities';
|
import { CommunityEntity } from '../../community/entities';
|
||||||
import { SpaceLinkEntity } from './space-link.entity';
|
import { DeviceEntity } from '../../device/entities';
|
||||||
|
import { InviteUserSpaceEntity } from '../../Invite-user/entities';
|
||||||
|
import { SpaceDailyOccupancyDurationEntity } from '../../occupancy/entities';
|
||||||
|
import { PresenceSensorDailySpaceEntity } from '../../presence-sensor/entities';
|
||||||
import { SceneEntity } from '../../scene/entities';
|
import { SceneEntity } from '../../scene/entities';
|
||||||
import { SpaceModelEntity } from '../../space-model';
|
import { SpaceModelEntity } from '../../space-model';
|
||||||
import { InviteUserSpaceEntity } from '../../Invite-user/entities';
|
import { UserSpaceEntity } from '../../user/entities';
|
||||||
|
import { SpaceDto } from '../dtos';
|
||||||
import { SpaceProductAllocationEntity } from './space-product-allocation.entity';
|
import { SpaceProductAllocationEntity } from './space-product-allocation.entity';
|
||||||
import { SubspaceEntity } from './subspace/subspace.entity';
|
import { SubspaceEntity } from './subspace/subspace.entity';
|
||||||
import { PresenceSensorDailySpaceEntity } from '../../presence-sensor/entities';
|
|
||||||
|
|
||||||
@Entity({ name: 'space' })
|
@Entity({ name: 'space' })
|
||||||
export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||||
@ -73,16 +74,6 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
|||||||
)
|
)
|
||||||
devices: DeviceEntity[];
|
devices: DeviceEntity[];
|
||||||
|
|
||||||
@OneToMany(() => SpaceLinkEntity, (connection) => connection.startSpace, {
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public outgoingConnections: SpaceLinkEntity[];
|
|
||||||
|
|
||||||
@OneToMany(() => SpaceLinkEntity, (connection) => connection.endSpace, {
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public incomingConnections: SpaceLinkEntity[];
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -115,6 +106,15 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
|||||||
@OneToMany(() => PresenceSensorDailySpaceEntity, (sensor) => sensor.space)
|
@OneToMany(() => PresenceSensorDailySpaceEntity, (sensor) => sensor.space)
|
||||||
presenceSensorDaily: PresenceSensorDailySpaceEntity[];
|
presenceSensorDaily: PresenceSensorDailySpaceEntity[];
|
||||||
|
|
||||||
|
@OneToMany(() => AqiSpaceDailyPollutantStatsEntity, (aqi) => aqi.space)
|
||||||
|
aqiSensorDaily: AqiSpaceDailyPollutantStatsEntity[];
|
||||||
|
|
||||||
|
@OneToMany(
|
||||||
|
() => SpaceDailyOccupancyDurationEntity,
|
||||||
|
(occupancy) => occupancy.space,
|
||||||
|
)
|
||||||
|
occupancyDaily: SpaceDailyOccupancyDurationEntity[];
|
||||||
|
|
||||||
constructor(partial: Partial<SpaceEntity>) {
|
constructor(partial: Partial<SpaceEntity>) {
|
||||||
super();
|
super();
|
||||||
Object.assign(this, partial);
|
Object.assign(this, partial);
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
import {
|
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
||||||
Entity,
|
|
||||||
Column,
|
|
||||||
ManyToOne,
|
|
||||||
ManyToMany,
|
|
||||||
JoinTable,
|
|
||||||
Unique,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { SubspaceEntity } from './subspace.entity';
|
|
||||||
import { ProductEntity } from '@app/common/modules/product/entities';
|
import { ProductEntity } from '@app/common/modules/product/entities';
|
||||||
import { SubspaceModelProductAllocationEntity } from '@app/common/modules/space-model';
|
import { SubspaceModelProductAllocationEntity } from '@app/common/modules/space-model';
|
||||||
import { NewTagEntity } from '@app/common/modules/tag/entities/tag.entity';
|
import { NewTagEntity } from '@app/common/modules/tag/entities/tag.entity';
|
||||||
import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity';
|
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||||
import { SubspaceProductAllocationDto } from '../../dtos/subspace-product-allocation.dto';
|
import { SubspaceProductAllocationDto } from '../../dtos/subspace-product-allocation.dto';
|
||||||
|
import { SubspaceEntity } from './subspace.entity';
|
||||||
|
|
||||||
@Entity({ name: 'subspace_product_allocation' })
|
@Entity({ name: 'subspace_product_allocation' })
|
||||||
@Unique(['subspace', 'product'])
|
@Unique(['subspace', 'product', 'tag'])
|
||||||
export class SubspaceProductAllocationEntity extends AbstractEntity<SubspaceProductAllocationDto> {
|
export class SubspaceProductAllocationEntity extends AbstractEntity<SubspaceProductAllocationDto> {
|
||||||
@Column({
|
@Column({
|
||||||
type: 'uuid',
|
type: 'uuid',
|
||||||
@ -38,9 +31,8 @@ export class SubspaceProductAllocationEntity extends AbstractEntity<SubspaceProd
|
|||||||
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
@ManyToOne(() => ProductEntity, { nullable: false, onDelete: 'CASCADE' })
|
||||||
public product: ProductEntity;
|
public product: ProductEntity;
|
||||||
|
|
||||||
@ManyToMany(() => NewTagEntity)
|
@ManyToOne(() => NewTagEntity, { nullable: true, onDelete: 'CASCADE' })
|
||||||
@JoinTable({ name: 'subspace_product_tags' })
|
public tag: NewTagEntity;
|
||||||
public tags: NewTagEntity[];
|
|
||||||
|
|
||||||
constructor(partial: Partial<SubspaceProductAllocationEntity>) {
|
constructor(partial: Partial<SubspaceProductAllocationEntity>) {
|
||||||
super();
|
super();
|
||||||
|
@ -4,7 +4,6 @@ import { SubspaceModelEntity } from '@app/common/modules/space-model';
|
|||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
||||||
import { SubspaceDto } from '../../dtos';
|
import { SubspaceDto } from '../../dtos';
|
||||||
import { SpaceEntity } from '../space.entity';
|
import { SpaceEntity } from '../space.entity';
|
||||||
import { TagEntity } from '../tag.entity';
|
|
||||||
import { SubspaceProductAllocationEntity } from './subspace-product-allocation.entity';
|
import { SubspaceProductAllocationEntity } from './subspace-product-allocation.entity';
|
||||||
|
|
||||||
@Entity({ name: 'subspace' })
|
@Entity({ name: 'subspace' })
|
||||||
@ -43,9 +42,6 @@ export class SubspaceEntity extends AbstractEntity<SubspaceDto> {
|
|||||||
})
|
})
|
||||||
subSpaceModel?: SubspaceModelEntity;
|
subSpaceModel?: SubspaceModelEntity;
|
||||||
|
|
||||||
@OneToMany(() => TagEntity, (tag) => tag.subspace)
|
|
||||||
tags: TagEntity[];
|
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => SubspaceProductAllocationEntity,
|
() => SubspaceProductAllocationEntity,
|
||||||
(allocation) => allocation.subspace,
|
(allocation) => allocation.subspace,
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import { Entity, Column, ManyToOne, JoinColumn, OneToOne } from 'typeorm';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
|
||||||
import { ProductEntity } from '../../product/entities';
|
|
||||||
import { TagDto } from '../dtos';
|
|
||||||
import { TagModel } from '../../space-model/entities/tag-model.entity';
|
|
||||||
import { DeviceEntity } from '../../device/entities';
|
|
||||||
import { SubspaceEntity } from './subspace/subspace.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'tag' })
|
|
||||||
export class TagEntity extends AbstractEntity<TagDto> {
|
|
||||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
|
||||||
tag: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => TagModel, (model) => model.tags, {
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
model: TagModel;
|
|
||||||
|
|
||||||
@ManyToOne(() => ProductEntity, (product) => product.tags, {
|
|
||||||
nullable: false,
|
|
||||||
})
|
|
||||||
product: ProductEntity;
|
|
||||||
|
|
||||||
@ManyToOne(() => SubspaceEntity, (subspace) => subspace.tags, {
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
@JoinColumn({ name: 'subspace_id' })
|
|
||||||
subspace: SubspaceEntity;
|
|
||||||
|
|
||||||
@Column({
|
|
||||||
nullable: false,
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public disabled: boolean;
|
|
||||||
|
|
||||||
@OneToOne(() => DeviceEntity, (device) => device.tag, {
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
@JoinColumn({ name: 'device_id' })
|
|
||||||
device: DeviceEntity;
|
|
||||||
}
|
|
@ -1,10 +1,8 @@
|
|||||||
import { DataSource, Repository } from 'typeorm';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
import { InviteSpaceEntity } from '../entities/invite-space.entity';
|
import { InviteSpaceEntity } from '../entities/invite-space.entity';
|
||||||
import { SpaceLinkEntity } from '../entities/space-link.entity';
|
|
||||||
import { SpaceEntity } from '../entities/space.entity';
|
|
||||||
import { TagEntity } from '../entities/tag.entity';
|
|
||||||
import { SpaceProductAllocationEntity } from '../entities/space-product-allocation.entity';
|
import { SpaceProductAllocationEntity } from '../entities/space-product-allocation.entity';
|
||||||
|
import { SpaceEntity } from '../entities/space.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SpaceRepository extends Repository<SpaceEntity> {
|
export class SpaceRepository extends Repository<SpaceEntity> {
|
||||||
@ -14,18 +12,7 @@ export class SpaceRepository extends Repository<SpaceEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SpaceLinkRepository extends Repository<SpaceLinkEntity> {
|
export class SpaceLinkRepository {}
|
||||||
constructor(private dataSource: DataSource) {
|
|
||||||
super(SpaceLinkEntity, dataSource.createEntityManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TagRepository extends Repository<TagEntity> {
|
|
||||||
constructor(private dataSource: DataSource) {
|
|
||||||
super(TagEntity, dataSource.createEntityManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InviteSpaceRepository extends Repository<InviteSpaceEntity> {
|
export class InviteSpaceRepository extends Repository<InviteSpaceEntity> {
|
||||||
|
@ -6,7 +6,6 @@ import { SpaceProductAllocationEntity } from './entities/space-product-allocatio
|
|||||||
import { SpaceEntity } from './entities/space.entity';
|
import { SpaceEntity } from './entities/space.entity';
|
||||||
import { SubspaceProductAllocationEntity } from './entities/subspace/subspace-product-allocation.entity';
|
import { SubspaceProductAllocationEntity } from './entities/subspace/subspace-product-allocation.entity';
|
||||||
import { SubspaceEntity } from './entities/subspace/subspace.entity';
|
import { SubspaceEntity } from './entities/subspace/subspace.entity';
|
||||||
import { TagEntity } from './entities/tag.entity';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [],
|
providers: [],
|
||||||
@ -16,7 +15,6 @@ import { TagEntity } from './entities/tag.entity';
|
|||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
SpaceEntity,
|
SpaceEntity,
|
||||||
SubspaceEntity,
|
SubspaceEntity,
|
||||||
TagEntity,
|
|
||||||
InviteSpaceEntity,
|
InviteSpaceEntity,
|
||||||
SpaceProductAllocationEntity,
|
SpaceProductAllocationEntity,
|
||||||
SubspaceProductAllocationEntity,
|
SubspaceProductAllocationEntity,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Entity, Column, ManyToOne, Unique, ManyToMany } from 'typeorm';
|
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||||
import { ProductEntity } from '../../product/entities';
|
|
||||||
import { ProjectEntity } from '../../project/entities';
|
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { NewTagDto } from '../dtos/tag.dto';
|
import { DeviceEntity } from '../../device/entities/device.entity';
|
||||||
|
import { ProjectEntity } from '../../project/entities';
|
||||||
import { SpaceModelProductAllocationEntity } from '../../space-model/entities/space-model-product-allocation.entity';
|
import { SpaceModelProductAllocationEntity } from '../../space-model/entities/space-model-product-allocation.entity';
|
||||||
import { SubspaceModelProductAllocationEntity } from '../../space-model/entities/subspace-model/subspace-model-product-allocation.entity';
|
import { SubspaceModelProductAllocationEntity } from '../../space-model/entities/subspace-model/subspace-model-product-allocation.entity';
|
||||||
import { DeviceEntity } from '../../device/entities/device.entity';
|
import { NewTagDto } from '../dtos/tag.dto';
|
||||||
|
|
||||||
@Entity({ name: 'new_tag' })
|
@Entity({ name: 'new_tag' })
|
||||||
@Unique(['name', 'project'])
|
@Unique(['name', 'project'])
|
||||||
@ -24,31 +23,25 @@ export class NewTagEntity extends AbstractEntity<NewTagDto> {
|
|||||||
})
|
})
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ManyToOne(() => ProductEntity, (product) => product.newTags, {
|
|
||||||
nullable: false,
|
|
||||||
onDelete: 'CASCADE',
|
|
||||||
})
|
|
||||||
public product: ProductEntity;
|
|
||||||
|
|
||||||
@ManyToOne(() => ProjectEntity, (project) => project.tags, {
|
@ManyToOne(() => ProjectEntity, (project) => project.tags, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
})
|
})
|
||||||
public project: ProjectEntity;
|
public project: ProjectEntity;
|
||||||
|
|
||||||
@ManyToMany(
|
@OneToMany(
|
||||||
() => SpaceModelProductAllocationEntity,
|
() => SpaceModelProductAllocationEntity,
|
||||||
(allocation) => allocation.tags,
|
(allocation) => allocation.tag,
|
||||||
)
|
)
|
||||||
public spaceModelAllocations: SpaceModelProductAllocationEntity[];
|
public spaceModelAllocations: SpaceModelProductAllocationEntity[];
|
||||||
|
|
||||||
@ManyToMany(
|
@OneToMany(
|
||||||
() => SubspaceModelProductAllocationEntity,
|
() => SubspaceModelProductAllocationEntity,
|
||||||
(allocation) => allocation.tags,
|
(allocation) => allocation.tag,
|
||||||
)
|
)
|
||||||
public subspaceModelAllocations: SubspaceModelProductAllocationEntity[];
|
public subspaceModelAllocations: SubspaceModelProductAllocationEntity[];
|
||||||
|
|
||||||
@ManyToOne(() => DeviceEntity, (device) => device.tag)
|
@OneToMany(() => DeviceEntity, (device) => device.tag)
|
||||||
public devices: DeviceEntity[];
|
public devices: DeviceEntity[];
|
||||||
|
|
||||||
constructor(partial: Partial<NewTagEntity>) {
|
constructor(partial: Partial<NewTagEntity>) {
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
WITH params AS (
|
||||||
|
SELECT
|
||||||
|
$1::uuid AS space_uuid,
|
||||||
|
TO_DATE(NULLIF($2, ''), 'YYYY-MM') AS event_month
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
sdp.space_uuid,
|
||||||
|
sdp.event_date,
|
||||||
|
sdp.good_aqi_percentage, sdp.moderate_aqi_percentage, sdp.unhealthy_sensitive_aqi_percentage, sdp.unhealthy_aqi_percentage,
|
||||||
|
sdp.very_unhealthy_aqi_percentage, sdp.hazardous_aqi_percentage,
|
||||||
|
sdp.daily_avg_aqi, sdp.daily_max_aqi, sdp.daily_min_aqi,
|
||||||
|
|
||||||
|
sdp.good_pm25_percentage, sdp.moderate_pm25_percentage, sdp.unhealthy_sensitive_pm25_percentage, sdp.unhealthy_pm25_percentage,
|
||||||
|
sdp.very_unhealthy_pm25_percentage, sdp.hazardous_pm25_percentage,
|
||||||
|
sdp.daily_avg_pm25, sdp.daily_max_pm25, sdp.daily_min_pm25,
|
||||||
|
|
||||||
|
sdp.good_pm10_percentage, sdp.moderate_pm10_percentage, sdp.unhealthy_sensitive_pm10_percentage, sdp.unhealthy_pm10_percentage,
|
||||||
|
sdp.very_unhealthy_pm10_percentage, sdp.hazardous_pm10_percentage,
|
||||||
|
sdp.daily_avg_pm10, sdp.daily_max_pm10, sdp.daily_min_pm10,
|
||||||
|
|
||||||
|
sdp.good_voc_percentage, sdp.moderate_voc_percentage, sdp.unhealthy_sensitive_voc_percentage, sdp.unhealthy_voc_percentage,
|
||||||
|
sdp.very_unhealthy_voc_percentage, sdp.hazardous_voc_percentage,
|
||||||
|
sdp.daily_avg_voc, sdp.daily_max_voc, sdp.daily_min_voc,
|
||||||
|
|
||||||
|
sdp.good_co2_percentage, sdp.moderate_co2_percentage, sdp.unhealthy_sensitive_co2_percentage, sdp.unhealthy_co2_percentage,
|
||||||
|
sdp.very_unhealthy_co2_percentage, sdp.hazardous_co2_percentage,
|
||||||
|
sdp.daily_avg_co2, sdp.daily_max_co2, sdp.daily_min_co2,
|
||||||
|
|
||||||
|
sdp.good_ch2o_percentage, sdp.moderate_ch2o_percentage, sdp.unhealthy_sensitive_ch2o_percentage, sdp.unhealthy_ch2o_percentage,
|
||||||
|
sdp.very_unhealthy_ch2o_percentage, sdp.hazardous_ch2o_percentage,
|
||||||
|
sdp.daily_avg_ch2o, sdp.daily_max_ch2o, sdp.daily_min_ch2o
|
||||||
|
|
||||||
|
FROM public."space-daily-pollutant-stats" AS sdp
|
||||||
|
CROSS JOIN params p
|
||||||
|
WHERE
|
||||||
|
(p.space_uuid IS NULL OR sdp.space_uuid = p.space_uuid)
|
||||||
|
AND (p.event_month IS NULL OR TO_CHAR(sdp.event_date, 'YYYY-MM') = TO_CHAR(p.event_month, 'YYYY-MM'))
|
||||||
|
ORDER BY sdp.space_uuid, sdp.event_date;
|
@ -0,0 +1,374 @@
|
|||||||
|
WITH params AS (
|
||||||
|
SELECT
|
||||||
|
TO_DATE(NULLIF($1, ''), 'YYYY-MM-DD') AS event_date,
|
||||||
|
$2::uuid AS space_id
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Query Pipeline Starts Here
|
||||||
|
device_space AS (
|
||||||
|
SELECT
|
||||||
|
device.uuid AS device_id,
|
||||||
|
device.space_device_uuid AS space_id,
|
||||||
|
"device-status-log".event_time::timestamp AS event_time,
|
||||||
|
"device-status-log".code,
|
||||||
|
"device-status-log".value
|
||||||
|
FROM device
|
||||||
|
LEFT JOIN "device-status-log"
|
||||||
|
ON device.uuid = "device-status-log".device_id
|
||||||
|
LEFT JOIN product
|
||||||
|
ON product.uuid = device.product_device_uuid
|
||||||
|
WHERE product.cat_name = 'hjjcy'
|
||||||
|
),
|
||||||
|
|
||||||
|
average_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
event_time::date AS event_date,
|
||||||
|
date_trunc('hour', event_time) AS event_hour,
|
||||||
|
space_id,
|
||||||
|
|
||||||
|
-- PM1
|
||||||
|
MIN(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_min,
|
||||||
|
AVG(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_max,
|
||||||
|
|
||||||
|
-- PM25
|
||||||
|
MIN(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_min,
|
||||||
|
AVG(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_max,
|
||||||
|
|
||||||
|
-- PM10
|
||||||
|
MIN(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_min,
|
||||||
|
AVG(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_max,
|
||||||
|
|
||||||
|
-- VOC
|
||||||
|
MIN(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_min,
|
||||||
|
AVG(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_avg,
|
||||||
|
MAX(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_max,
|
||||||
|
|
||||||
|
-- CH2O
|
||||||
|
MIN(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_min,
|
||||||
|
AVG(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_avg,
|
||||||
|
MAX(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_max,
|
||||||
|
|
||||||
|
-- CO2
|
||||||
|
MIN(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_min,
|
||||||
|
AVG(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_avg,
|
||||||
|
MAX(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_max
|
||||||
|
|
||||||
|
FROM device_space
|
||||||
|
GROUP BY space_id, event_hour, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
filled_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
*,
|
||||||
|
-- AVG
|
||||||
|
COALESCE(pm25_avg, LAG(pm25_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_avg_f,
|
||||||
|
COALESCE(pm10_avg, LAG(pm10_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_avg_f,
|
||||||
|
COALESCE(voc_avg, LAG(voc_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_avg_f,
|
||||||
|
COALESCE(co2_avg, LAG(co2_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_avg_f,
|
||||||
|
COALESCE(ch2o_avg, LAG(ch2o_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_avg_f,
|
||||||
|
|
||||||
|
-- MIN
|
||||||
|
COALESCE(pm25_min, LAG(pm25_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_min_f,
|
||||||
|
COALESCE(pm10_min, LAG(pm10_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_min_f,
|
||||||
|
COALESCE(voc_min, LAG(voc_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_min_f,
|
||||||
|
COALESCE(co2_min, LAG(co2_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_min_f,
|
||||||
|
COALESCE(ch2o_min, LAG(ch2o_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_min_f,
|
||||||
|
|
||||||
|
-- MAX
|
||||||
|
COALESCE(pm25_max, LAG(pm25_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_max_f,
|
||||||
|
COALESCE(pm10_max, LAG(pm10_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_max_f,
|
||||||
|
COALESCE(voc_max, LAG(voc_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_max_f,
|
||||||
|
COALESCE(co2_max, LAG(co2_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_max_f,
|
||||||
|
COALESCE(ch2o_max, LAG(ch2o_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_max_f
|
||||||
|
FROM average_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
hourly_results AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
event_hour,
|
||||||
|
pm1_min, pm1_avg, pm1_max,
|
||||||
|
pm25_min_f, pm25_avg_f, pm25_max_f,
|
||||||
|
pm10_min_f, pm10_avg_f, pm10_max_f,
|
||||||
|
voc_min_f, voc_avg_f, voc_max_f,
|
||||||
|
co2_min_f, co2_avg_f, co2_max_f,
|
||||||
|
ch2o_min_f, ch2o_avg_f, ch2o_max_f,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_min_f),
|
||||||
|
calculate_aqi('pm10', pm10_min_f)
|
||||||
|
) AS hourly_min_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
) AS hourly_avg_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_max_f),
|
||||||
|
calculate_aqi('pm10', pm10_max_f)
|
||||||
|
) AS hourly_max_aqi,
|
||||||
|
|
||||||
|
classify_aqi(GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
)) AS aqi_category,
|
||||||
|
|
||||||
|
classify_aqi(calculate_aqi('pm25',pm25_avg_f)) as pm25_category,
|
||||||
|
classify_aqi(calculate_aqi('pm10',pm10_avg_f)) as pm10_category,
|
||||||
|
classify_aqi(calculate_aqi('voc',voc_avg_f)) as voc_category,
|
||||||
|
classify_aqi(calculate_aqi('co2',co2_avg_f)) as co2_category,
|
||||||
|
classify_aqi(calculate_aqi('ch2o',ch2o_avg_f)) as ch2o_category
|
||||||
|
|
||||||
|
FROM filled_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_category_counts AS (
|
||||||
|
SELECT space_id, event_date, aqi_category AS category, 'aqi' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, aqi_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, pm25_category AS category, 'pm25' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, pm25_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, pm10_category AS category, 'pm10' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, pm10_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, voc_category AS category, 'voc' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, voc_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, co2_category AS category, 'co2' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, co2_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, ch2o_category AS category, 'ch2o' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, ch2o_category
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_totals AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
SUM(category_count) AS total_count
|
||||||
|
FROM daily_category_counts
|
||||||
|
where pollutant = 'aqi'
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Pivot Categories into Columns
|
||||||
|
daily_percentages AS (
|
||||||
|
select
|
||||||
|
dt.space_id,
|
||||||
|
dt.event_date,
|
||||||
|
-- AQI CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_aqi_percentage,
|
||||||
|
-- PM25 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm25_percentage,
|
||||||
|
-- PM10 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm10_percentage,
|
||||||
|
-- VOC CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_voc_percentage,
|
||||||
|
-- CO2 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_co2_percentage,
|
||||||
|
-- CH20 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_ch2o_percentage
|
||||||
|
FROM daily_totals dt
|
||||||
|
LEFT JOIN daily_category_counts dcc
|
||||||
|
ON dt.space_id = dcc.space_id AND dt.event_date = dcc.event_date
|
||||||
|
GROUP BY dt.space_id, dt.event_date, dt.total_count
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_averages AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
-- AQI
|
||||||
|
ROUND(AVG(hourly_min_aqi)::numeric, 2) AS daily_min_aqi,
|
||||||
|
ROUND(AVG(hourly_avg_aqi)::numeric, 2) AS daily_avg_aqi,
|
||||||
|
ROUND(AVG(hourly_max_aqi)::numeric, 2) AS daily_max_aqi,
|
||||||
|
-- PM25
|
||||||
|
ROUND(AVG(pm25_min_f)::numeric, 2) AS daily_min_pm25,
|
||||||
|
ROUND(AVG(pm25_avg_f)::numeric, 2) AS daily_avg_pm25,
|
||||||
|
ROUND(AVG(pm25_max_f)::numeric, 2) AS daily_max_pm25,
|
||||||
|
-- PM10
|
||||||
|
ROUND(AVG(pm10_min_f)::numeric, 2) AS daily_min_pm10,
|
||||||
|
ROUND(AVG(pm10_avg_f)::numeric, 2) AS daily_avg_pm10,
|
||||||
|
ROUND(AVG(pm10_max_f)::numeric, 2) AS daily_max_pm10,
|
||||||
|
-- VOC
|
||||||
|
ROUND(AVG(voc_min_f)::numeric, 2) AS daily_min_voc,
|
||||||
|
ROUND(AVG(voc_avg_f)::numeric, 2) AS daily_avg_voc,
|
||||||
|
ROUND(AVG(voc_max_f)::numeric, 2) AS daily_max_voc,
|
||||||
|
-- CO2
|
||||||
|
ROUND(AVG(co2_min_f)::numeric, 2) AS daily_min_co2,
|
||||||
|
ROUND(AVG(co2_avg_f)::numeric, 2) AS daily_avg_co2,
|
||||||
|
ROUND(AVG(co2_max_f)::numeric, 2) AS daily_max_co2,
|
||||||
|
-- CH2O
|
||||||
|
ROUND(AVG(ch2o_min_f)::numeric, 2) AS daily_min_ch2o,
|
||||||
|
ROUND(AVG(ch2o_avg_f)::numeric, 2) AS daily_avg_ch2o,
|
||||||
|
ROUND(AVG(ch2o_max_f)::numeric, 2) AS daily_max_ch2o
|
||||||
|
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
final_data as(
|
||||||
|
SELECT
|
||||||
|
p.space_id,
|
||||||
|
p.event_date,
|
||||||
|
p.good_aqi_percentage, p.moderate_aqi_percentage, p.unhealthy_sensitive_aqi_percentage, p.unhealthy_aqi_percentage, p.very_unhealthy_aqi_percentage, p.hazardous_aqi_percentage,
|
||||||
|
a.daily_avg_aqi,a.daily_max_aqi, a.daily_min_aqi,
|
||||||
|
p.good_pm25_percentage, p.moderate_pm25_percentage, p.unhealthy_sensitive_pm25_percentage, p.unhealthy_pm25_percentage, p.very_unhealthy_pm25_percentage, p.hazardous_pm25_percentage,
|
||||||
|
a.daily_avg_pm25,a.daily_max_pm25, a.daily_min_pm25,
|
||||||
|
p.good_pm10_percentage, p.moderate_pm10_percentage, p.unhealthy_sensitive_pm10_percentage, p.unhealthy_pm10_percentage, p.very_unhealthy_pm10_percentage, p.hazardous_pm10_percentage,
|
||||||
|
a.daily_avg_pm10, a.daily_max_pm10, a.daily_min_pm10,
|
||||||
|
p.good_voc_percentage, p.moderate_voc_percentage, p.unhealthy_sensitive_voc_percentage, p.unhealthy_voc_percentage, p.very_unhealthy_voc_percentage, p.hazardous_voc_percentage,
|
||||||
|
a.daily_avg_voc, a.daily_max_voc, a.daily_min_voc,
|
||||||
|
p.good_co2_percentage, p.moderate_co2_percentage, p.unhealthy_sensitive_co2_percentage, p.unhealthy_co2_percentage, p.very_unhealthy_co2_percentage, p.hazardous_co2_percentage,
|
||||||
|
a.daily_avg_co2,a.daily_max_co2, a.daily_min_co2,
|
||||||
|
p.good_ch2o_percentage, p.moderate_ch2o_percentage, p.unhealthy_sensitive_ch2o_percentage, p.unhealthy_ch2o_percentage, p.very_unhealthy_ch2o_percentage, p.hazardous_ch2o_percentage,
|
||||||
|
a.daily_avg_ch2o,a.daily_max_ch2o, a.daily_min_ch2o
|
||||||
|
FROM daily_percentages p
|
||||||
|
LEFT JOIN daily_averages a
|
||||||
|
ON p.space_id = a.space_id AND p.event_date = a.event_date
|
||||||
|
ORDER BY p.space_id, p.event_date)
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public."space-daily-pollutant-stats" (
|
||||||
|
space_uuid,
|
||||||
|
event_date,
|
||||||
|
good_aqi_percentage, moderate_aqi_percentage, unhealthy_sensitive_aqi_percentage, unhealthy_aqi_percentage, very_unhealthy_aqi_percentage, hazardous_aqi_percentage,
|
||||||
|
daily_avg_aqi, daily_max_aqi, daily_min_aqi,
|
||||||
|
good_pm25_percentage, moderate_pm25_percentage, unhealthy_sensitive_pm25_percentage, unhealthy_pm25_percentage, very_unhealthy_pm25_percentage, hazardous_pm25_percentage,
|
||||||
|
daily_avg_pm25, daily_max_pm25, daily_min_pm25,
|
||||||
|
good_pm10_percentage, moderate_pm10_percentage, unhealthy_sensitive_pm10_percentage, unhealthy_pm10_percentage, very_unhealthy_pm10_percentage, hazardous_pm10_percentage,
|
||||||
|
daily_avg_pm10, daily_max_pm10, daily_min_pm10,
|
||||||
|
good_voc_percentage, moderate_voc_percentage, unhealthy_sensitive_voc_percentage, unhealthy_voc_percentage, very_unhealthy_voc_percentage, hazardous_voc_percentage,
|
||||||
|
daily_avg_voc, daily_max_voc, daily_min_voc,
|
||||||
|
good_co2_percentage, moderate_co2_percentage, unhealthy_sensitive_co2_percentage, unhealthy_co2_percentage, very_unhealthy_co2_percentage, hazardous_co2_percentage,
|
||||||
|
daily_avg_co2, daily_max_co2, daily_min_co2,
|
||||||
|
good_ch2o_percentage, moderate_ch2o_percentage, unhealthy_sensitive_ch2o_percentage, unhealthy_ch2o_percentage, very_unhealthy_ch2o_percentage, hazardous_ch2o_percentage,
|
||||||
|
daily_avg_ch2o, daily_max_ch2o, daily_min_ch2o
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
good_aqi_percentage, moderate_aqi_percentage, unhealthy_sensitive_aqi_percentage, unhealthy_aqi_percentage, very_unhealthy_aqi_percentage, hazardous_aqi_percentage,
|
||||||
|
daily_avg_aqi, daily_max_aqi, daily_min_aqi,
|
||||||
|
good_pm25_percentage, moderate_pm25_percentage, unhealthy_sensitive_pm25_percentage, unhealthy_pm25_percentage, very_unhealthy_pm25_percentage, hazardous_pm25_percentage,
|
||||||
|
daily_avg_pm25, daily_max_pm25, daily_min_pm25,
|
||||||
|
good_pm10_percentage, moderate_pm10_percentage, unhealthy_sensitive_pm10_percentage, unhealthy_pm10_percentage, very_unhealthy_pm10_percentage, hazardous_pm10_percentage,
|
||||||
|
daily_avg_pm10, daily_max_pm10, daily_min_pm10,
|
||||||
|
good_voc_percentage, moderate_voc_percentage, unhealthy_sensitive_voc_percentage, unhealthy_voc_percentage, very_unhealthy_voc_percentage, hazardous_voc_percentage,
|
||||||
|
daily_avg_voc, daily_max_voc, daily_min_voc,
|
||||||
|
good_co2_percentage, moderate_co2_percentage, unhealthy_sensitive_co2_percentage, unhealthy_co2_percentage, very_unhealthy_co2_percentage, hazardous_co2_percentage,
|
||||||
|
daily_avg_co2, daily_max_co2, daily_min_co2,
|
||||||
|
good_ch2o_percentage, moderate_ch2o_percentage, unhealthy_sensitive_ch2o_percentage, unhealthy_ch2o_percentage, very_unhealthy_ch2o_percentage, hazardous_ch2o_percentage,
|
||||||
|
daily_avg_ch2o, daily_max_ch2o, daily_min_ch2o
|
||||||
|
FROM final_data
|
||||||
|
ON CONFLICT (space_uuid, event_date) DO UPDATE
|
||||||
|
SET
|
||||||
|
good_aqi_percentage = EXCLUDED.good_aqi_percentage,
|
||||||
|
moderate_aqi_percentage = EXCLUDED.moderate_aqi_percentage,
|
||||||
|
unhealthy_sensitive_aqi_percentage = EXCLUDED.unhealthy_sensitive_aqi_percentage,
|
||||||
|
unhealthy_aqi_percentage = EXCLUDED.unhealthy_aqi_percentage,
|
||||||
|
very_unhealthy_aqi_percentage = EXCLUDED.very_unhealthy_aqi_percentage,
|
||||||
|
hazardous_aqi_percentage = EXCLUDED.hazardous_aqi_percentage,
|
||||||
|
daily_avg_aqi = EXCLUDED.daily_avg_aqi,
|
||||||
|
daily_max_aqi = EXCLUDED.daily_max_aqi,
|
||||||
|
daily_min_aqi = EXCLUDED.daily_min_aqi,
|
||||||
|
good_pm25_percentage = EXCLUDED.good_pm25_percentage,
|
||||||
|
moderate_pm25_percentage = EXCLUDED.moderate_pm25_percentage,
|
||||||
|
unhealthy_sensitive_pm25_percentage = EXCLUDED.unhealthy_sensitive_pm25_percentage,
|
||||||
|
unhealthy_pm25_percentage = EXCLUDED.unhealthy_pm25_percentage,
|
||||||
|
very_unhealthy_pm25_percentage = EXCLUDED.very_unhealthy_pm25_percentage,
|
||||||
|
hazardous_pm25_percentage = EXCLUDED.hazardous_pm25_percentage,
|
||||||
|
daily_avg_pm25 = EXCLUDED.daily_avg_pm25,
|
||||||
|
daily_max_pm25 = EXCLUDED.daily_max_pm25,
|
||||||
|
daily_min_pm25 = EXCLUDED.daily_min_pm25,
|
||||||
|
good_pm10_percentage = EXCLUDED.good_pm10_percentage,
|
||||||
|
moderate_pm10_percentage = EXCLUDED.moderate_pm10_percentage,
|
||||||
|
unhealthy_sensitive_pm10_percentage = EXCLUDED.unhealthy_sensitive_pm10_percentage,
|
||||||
|
unhealthy_pm10_percentage = EXCLUDED.unhealthy_pm10_percentage,
|
||||||
|
very_unhealthy_pm10_percentage = EXCLUDED.very_unhealthy_pm10_percentage,
|
||||||
|
hazardous_pm10_percentage = EXCLUDED.hazardous_pm10_percentage,
|
||||||
|
daily_avg_pm10 = EXCLUDED.daily_avg_pm10,
|
||||||
|
daily_max_pm10 = EXCLUDED.daily_max_pm10,
|
||||||
|
daily_min_pm10 = EXCLUDED.daily_min_pm10,
|
||||||
|
good_voc_percentage = EXCLUDED.good_voc_percentage,
|
||||||
|
moderate_voc_percentage = EXCLUDED.moderate_voc_percentage,
|
||||||
|
unhealthy_sensitive_voc_percentage = EXCLUDED.unhealthy_sensitive_voc_percentage,
|
||||||
|
unhealthy_voc_percentage = EXCLUDED.unhealthy_voc_percentage,
|
||||||
|
very_unhealthy_voc_percentage = EXCLUDED.very_unhealthy_voc_percentage,
|
||||||
|
hazardous_voc_percentage = EXCLUDED.hazardous_voc_percentage,
|
||||||
|
daily_avg_voc = EXCLUDED.daily_avg_voc,
|
||||||
|
daily_max_voc = EXCLUDED.daily_max_voc,
|
||||||
|
daily_min_voc = EXCLUDED.daily_min_voc,
|
||||||
|
good_co2_percentage = EXCLUDED.good_co2_percentage,
|
||||||
|
moderate_co2_percentage = EXCLUDED.moderate_co2_percentage,
|
||||||
|
unhealthy_sensitive_co2_percentage = EXCLUDED.unhealthy_sensitive_co2_percentage,
|
||||||
|
unhealthy_co2_percentage = EXCLUDED.unhealthy_co2_percentage,
|
||||||
|
very_unhealthy_co2_percentage = EXCLUDED.very_unhealthy_co2_percentage,
|
||||||
|
hazardous_co2_percentage = EXCLUDED.hazardous_co2_percentage,
|
||||||
|
daily_avg_co2 = EXCLUDED.daily_avg_co2,
|
||||||
|
daily_max_co2 = EXCLUDED.daily_max_co2,
|
||||||
|
daily_min_co2 = EXCLUDED.daily_min_co2,
|
||||||
|
good_ch2o_percentage = EXCLUDED.good_ch2o_percentage,
|
||||||
|
moderate_ch2o_percentage = EXCLUDED.moderate_ch2o_percentage,
|
||||||
|
unhealthy_sensitive_ch2o_percentage = EXCLUDED.unhealthy_sensitive_ch2o_percentage,
|
||||||
|
unhealthy_ch2o_percentage = EXCLUDED.unhealthy_ch2o_percentage,
|
||||||
|
very_unhealthy_ch2o_percentage = EXCLUDED.very_unhealthy_ch2o_percentage,
|
||||||
|
hazardous_ch2o_percentage = EXCLUDED.hazardous_ch2o_percentage,
|
||||||
|
daily_avg_ch2o = EXCLUDED.daily_avg_ch2o,
|
||||||
|
daily_max_ch2o = EXCLUDED.daily_max_ch2o,
|
||||||
|
daily_min_ch2o = EXCLUDED.daily_min_ch2o;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
|||||||
|
-- Query Pipeline Starts Here
|
||||||
|
WITH device_space AS (
|
||||||
|
SELECT
|
||||||
|
device.uuid AS device_id,
|
||||||
|
device.space_device_uuid AS space_id,
|
||||||
|
"device-status-log".event_time::timestamp AS event_time,
|
||||||
|
"device-status-log".code,
|
||||||
|
"device-status-log".value
|
||||||
|
FROM device
|
||||||
|
LEFT JOIN "device-status-log"
|
||||||
|
ON device.uuid = "device-status-log".device_id
|
||||||
|
LEFT JOIN product
|
||||||
|
ON product.uuid = device.product_device_uuid
|
||||||
|
WHERE product.cat_name = 'hjjcy'
|
||||||
|
),
|
||||||
|
|
||||||
|
average_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
event_time::date AS event_date,
|
||||||
|
date_trunc('hour', event_time) AS event_hour,
|
||||||
|
space_id,
|
||||||
|
|
||||||
|
-- PM1
|
||||||
|
MIN(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_min,
|
||||||
|
AVG(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_max,
|
||||||
|
|
||||||
|
-- PM25
|
||||||
|
MIN(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_min,
|
||||||
|
AVG(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_max,
|
||||||
|
|
||||||
|
-- PM10
|
||||||
|
MIN(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_min,
|
||||||
|
AVG(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_max,
|
||||||
|
|
||||||
|
-- VOC
|
||||||
|
MIN(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_min,
|
||||||
|
AVG(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_avg,
|
||||||
|
MAX(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_max,
|
||||||
|
|
||||||
|
-- CH2O
|
||||||
|
MIN(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_min,
|
||||||
|
AVG(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_avg,
|
||||||
|
MAX(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_max,
|
||||||
|
|
||||||
|
-- CO2
|
||||||
|
MIN(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_min,
|
||||||
|
AVG(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_avg,
|
||||||
|
MAX(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_max
|
||||||
|
|
||||||
|
FROM device_space
|
||||||
|
GROUP BY space_id, event_hour, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
filled_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
*,
|
||||||
|
-- AVG
|
||||||
|
COALESCE(pm25_avg, LAG(pm25_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_avg_f,
|
||||||
|
COALESCE(pm10_avg, LAG(pm10_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_avg_f,
|
||||||
|
COALESCE(voc_avg, LAG(voc_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_avg_f,
|
||||||
|
COALESCE(co2_avg, LAG(co2_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_avg_f,
|
||||||
|
COALESCE(ch2o_avg, LAG(ch2o_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_avg_f,
|
||||||
|
|
||||||
|
-- MIN
|
||||||
|
COALESCE(pm25_min, LAG(pm25_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_min_f,
|
||||||
|
COALESCE(pm10_min, LAG(pm10_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_min_f,
|
||||||
|
COALESCE(voc_min, LAG(voc_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_min_f,
|
||||||
|
COALESCE(co2_min, LAG(co2_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_min_f,
|
||||||
|
COALESCE(ch2o_min, LAG(ch2o_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_min_f,
|
||||||
|
|
||||||
|
-- MAX
|
||||||
|
COALESCE(pm25_max, LAG(pm25_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_max_f,
|
||||||
|
COALESCE(pm10_max, LAG(pm10_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_max_f,
|
||||||
|
COALESCE(voc_max, LAG(voc_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_max_f,
|
||||||
|
COALESCE(co2_max, LAG(co2_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_max_f,
|
||||||
|
COALESCE(ch2o_max, LAG(ch2o_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_max_f
|
||||||
|
FROM average_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
hourly_results AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
event_hour,
|
||||||
|
pm1_min, pm1_avg, pm1_max,
|
||||||
|
pm25_min_f, pm25_avg_f, pm25_max_f,
|
||||||
|
pm10_min_f, pm10_avg_f, pm10_max_f,
|
||||||
|
voc_min_f, voc_avg_f, voc_max_f,
|
||||||
|
co2_min_f, co2_avg_f, co2_max_f,
|
||||||
|
ch2o_min_f, ch2o_avg_f, ch2o_max_f,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_min_f),
|
||||||
|
calculate_aqi('pm10', pm10_min_f)
|
||||||
|
) AS hourly_min_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
) AS hourly_avg_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_max_f),
|
||||||
|
calculate_aqi('pm10', pm10_max_f)
|
||||||
|
) AS hourly_max_aqi,
|
||||||
|
|
||||||
|
classify_aqi(GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
)) AS aqi_category,
|
||||||
|
|
||||||
|
classify_aqi(calculate_aqi('pm25',pm25_avg_f)) as pm25_category,
|
||||||
|
classify_aqi(calculate_aqi('pm10',pm10_avg_f)) as pm10_category,
|
||||||
|
classify_aqi(calculate_aqi('voc',voc_avg_f)) as voc_category,
|
||||||
|
classify_aqi(calculate_aqi('co2',co2_avg_f)) as co2_category,
|
||||||
|
classify_aqi(calculate_aqi('ch2o',ch2o_avg_f)) as ch2o_category
|
||||||
|
|
||||||
|
FROM filled_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_category_counts AS (
|
||||||
|
SELECT space_id, event_date, aqi_category AS category, 'aqi' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, aqi_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, pm25_category AS category, 'pm25' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, pm25_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, pm10_category AS category, 'pm10' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, pm10_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, voc_category AS category, 'voc' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, voc_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, co2_category AS category, 'co2' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, co2_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, ch2o_category AS category, 'ch2o' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, ch2o_category
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_totals AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
SUM(category_count) AS total_count
|
||||||
|
FROM daily_category_counts
|
||||||
|
where pollutant = 'aqi'
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Pivot Categories into Columns
|
||||||
|
daily_percentages AS (
|
||||||
|
select
|
||||||
|
dt.space_id,
|
||||||
|
dt.event_date,
|
||||||
|
-- AQI CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_aqi_percentage,
|
||||||
|
-- PM25 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm25_percentage,
|
||||||
|
-- PM10 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm10_percentage,
|
||||||
|
-- VOC CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_voc_percentage,
|
||||||
|
-- CO2 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_co2_percentage,
|
||||||
|
-- CH20 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_ch2o_percentage
|
||||||
|
FROM daily_totals dt
|
||||||
|
LEFT JOIN daily_category_counts dcc
|
||||||
|
ON dt.space_id = dcc.space_id AND dt.event_date = dcc.event_date
|
||||||
|
GROUP BY dt.space_id, dt.event_date, dt.total_count
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_averages AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
-- AQI
|
||||||
|
ROUND(AVG(hourly_min_aqi)::numeric, 2) AS daily_min_aqi,
|
||||||
|
ROUND(AVG(hourly_avg_aqi)::numeric, 2) AS daily_avg_aqi,
|
||||||
|
ROUND(AVG(hourly_max_aqi)::numeric, 2) AS daily_max_aqi,
|
||||||
|
-- PM25
|
||||||
|
ROUND(AVG(pm25_min_f)::numeric, 2) AS daily_min_pm25,
|
||||||
|
ROUND(AVG(pm25_avg_f)::numeric, 2) AS daily_avg_pm25,
|
||||||
|
ROUND(AVG(pm25_max_f)::numeric, 2) AS daily_max_pm25,
|
||||||
|
-- PM10
|
||||||
|
ROUND(AVG(pm10_min_f)::numeric, 2) AS daily_min_pm10,
|
||||||
|
ROUND(AVG(pm10_avg_f)::numeric, 2) AS daily_avg_pm10,
|
||||||
|
ROUND(AVG(pm10_max_f)::numeric, 2) AS daily_max_pm10,
|
||||||
|
-- VOC
|
||||||
|
ROUND(AVG(voc_min_f)::numeric, 2) AS daily_min_voc,
|
||||||
|
ROUND(AVG(voc_avg_f)::numeric, 2) AS daily_avg_voc,
|
||||||
|
ROUND(AVG(voc_max_f)::numeric, 2) AS daily_max_voc,
|
||||||
|
-- CO2
|
||||||
|
ROUND(AVG(co2_min_f)::numeric, 2) AS daily_min_co2,
|
||||||
|
ROUND(AVG(co2_avg_f)::numeric, 2) AS daily_avg_co2,
|
||||||
|
ROUND(AVG(co2_max_f)::numeric, 2) AS daily_max_co2,
|
||||||
|
-- CH2O
|
||||||
|
ROUND(AVG(ch2o_min_f)::numeric, 2) AS daily_min_ch2o,
|
||||||
|
ROUND(AVG(ch2o_avg_f)::numeric, 2) AS daily_avg_ch2o,
|
||||||
|
ROUND(AVG(ch2o_max_f)::numeric, 2) AS daily_max_ch2o
|
||||||
|
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
final_data as(
|
||||||
|
SELECT
|
||||||
|
p.space_id,
|
||||||
|
p.event_date,
|
||||||
|
p.good_aqi_percentage, p.moderate_aqi_percentage, p.unhealthy_sensitive_aqi_percentage, p.unhealthy_aqi_percentage, p.very_unhealthy_aqi_percentage, p.hazardous_aqi_percentage,
|
||||||
|
a.daily_avg_aqi,a.daily_max_aqi, a.daily_min_aqi,
|
||||||
|
p.good_pm25_percentage, p.moderate_pm25_percentage, p.unhealthy_sensitive_pm25_percentage, p.unhealthy_pm25_percentage, p.very_unhealthy_pm25_percentage, p.hazardous_pm25_percentage,
|
||||||
|
a.daily_avg_pm25,a.daily_max_pm25, a.daily_min_pm25,
|
||||||
|
p.good_pm10_percentage, p.moderate_pm10_percentage, p.unhealthy_sensitive_pm10_percentage, p.unhealthy_pm10_percentage, p.very_unhealthy_pm10_percentage, p.hazardous_pm10_percentage,
|
||||||
|
a.daily_avg_pm10, a.daily_max_pm10, a.daily_min_pm10,
|
||||||
|
p.good_voc_percentage, p.moderate_voc_percentage, p.unhealthy_sensitive_voc_percentage, p.unhealthy_voc_percentage, p.very_unhealthy_voc_percentage, p.hazardous_voc_percentage,
|
||||||
|
a.daily_avg_voc, a.daily_max_voc, a.daily_min_voc,
|
||||||
|
p.good_co2_percentage, p.moderate_co2_percentage, p.unhealthy_sensitive_co2_percentage, p.unhealthy_co2_percentage, p.very_unhealthy_co2_percentage, p.hazardous_co2_percentage,
|
||||||
|
a.daily_avg_co2,a.daily_max_co2, a.daily_min_co2,
|
||||||
|
p.good_ch2o_percentage, p.moderate_ch2o_percentage, p.unhealthy_sensitive_ch2o_percentage, p.unhealthy_ch2o_percentage, p.very_unhealthy_ch2o_percentage, p.hazardous_ch2o_percentage,
|
||||||
|
a.daily_avg_ch2o,a.daily_max_ch2o, a.daily_min_ch2o
|
||||||
|
FROM daily_percentages p
|
||||||
|
LEFT JOIN daily_averages a
|
||||||
|
ON p.space_id = a.space_id AND p.event_date = a.event_date
|
||||||
|
ORDER BY p.space_id, p.event_date)
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public."space-daily-pollutant-stats" (
|
||||||
|
space_uuid,
|
||||||
|
event_date,
|
||||||
|
good_aqi_percentage, moderate_aqi_percentage, unhealthy_sensitive_aqi_percentage, unhealthy_aqi_percentage, very_unhealthy_aqi_percentage, hazardous_aqi_percentage,
|
||||||
|
daily_avg_aqi, daily_max_aqi, daily_min_aqi,
|
||||||
|
good_pm25_percentage, moderate_pm25_percentage, unhealthy_sensitive_pm25_percentage, unhealthy_pm25_percentage, very_unhealthy_pm25_percentage, hazardous_pm25_percentage,
|
||||||
|
daily_avg_pm25, daily_max_pm25, daily_min_pm25,
|
||||||
|
good_pm10_percentage, moderate_pm10_percentage, unhealthy_sensitive_pm10_percentage, unhealthy_pm10_percentage, very_unhealthy_pm10_percentage, hazardous_pm10_percentage,
|
||||||
|
daily_avg_pm10, daily_max_pm10, daily_min_pm10,
|
||||||
|
good_voc_percentage, moderate_voc_percentage, unhealthy_sensitive_voc_percentage, unhealthy_voc_percentage, very_unhealthy_voc_percentage, hazardous_voc_percentage,
|
||||||
|
daily_avg_voc, daily_max_voc, daily_min_voc,
|
||||||
|
good_co2_percentage, moderate_co2_percentage, unhealthy_sensitive_co2_percentage, unhealthy_co2_percentage, very_unhealthy_co2_percentage, hazardous_co2_percentage,
|
||||||
|
daily_avg_co2, daily_max_co2, daily_min_co2,
|
||||||
|
good_ch2o_percentage, moderate_ch2o_percentage, unhealthy_sensitive_ch2o_percentage, unhealthy_ch2o_percentage, very_unhealthy_ch2o_percentage, hazardous_ch2o_percentage,
|
||||||
|
daily_avg_ch2o, daily_max_ch2o, daily_min_ch2o
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
good_aqi_percentage, moderate_aqi_percentage, unhealthy_sensitive_aqi_percentage, unhealthy_aqi_percentage, very_unhealthy_aqi_percentage, hazardous_aqi_percentage,
|
||||||
|
daily_avg_aqi, daily_max_aqi, daily_min_aqi,
|
||||||
|
good_pm25_percentage, moderate_pm25_percentage, unhealthy_sensitive_pm25_percentage, unhealthy_pm25_percentage, very_unhealthy_pm25_percentage, hazardous_pm25_percentage,
|
||||||
|
daily_avg_pm25, daily_max_pm25, daily_min_pm25,
|
||||||
|
good_pm10_percentage, moderate_pm10_percentage, unhealthy_sensitive_pm10_percentage, unhealthy_pm10_percentage, very_unhealthy_pm10_percentage, hazardous_pm10_percentage,
|
||||||
|
daily_avg_pm10, daily_max_pm10, daily_min_pm10,
|
||||||
|
good_voc_percentage, moderate_voc_percentage, unhealthy_sensitive_voc_percentage, unhealthy_voc_percentage, very_unhealthy_voc_percentage, hazardous_voc_percentage,
|
||||||
|
daily_avg_voc, daily_max_voc, daily_min_voc,
|
||||||
|
good_co2_percentage, moderate_co2_percentage, unhealthy_sensitive_co2_percentage, unhealthy_co2_percentage, very_unhealthy_co2_percentage, hazardous_co2_percentage,
|
||||||
|
daily_avg_co2, daily_max_co2, daily_min_co2,
|
||||||
|
good_ch2o_percentage, moderate_ch2o_percentage, unhealthy_sensitive_ch2o_percentage, unhealthy_ch2o_percentage, very_unhealthy_ch2o_percentage, hazardous_ch2o_percentage,
|
||||||
|
daily_avg_ch2o, daily_max_ch2o, daily_min_ch2o
|
||||||
|
FROM final_data
|
||||||
|
ON CONFLICT (space_uuid, event_date) DO UPDATE
|
||||||
|
SET
|
||||||
|
good_aqi_percentage = EXCLUDED.good_aqi_percentage,
|
||||||
|
moderate_aqi_percentage = EXCLUDED.moderate_aqi_percentage,
|
||||||
|
unhealthy_sensitive_aqi_percentage = EXCLUDED.unhealthy_sensitive_aqi_percentage,
|
||||||
|
unhealthy_aqi_percentage = EXCLUDED.unhealthy_aqi_percentage,
|
||||||
|
very_unhealthy_aqi_percentage = EXCLUDED.very_unhealthy_aqi_percentage,
|
||||||
|
hazardous_aqi_percentage = EXCLUDED.hazardous_aqi_percentage,
|
||||||
|
daily_avg_aqi = EXCLUDED.daily_avg_aqi,
|
||||||
|
daily_max_aqi = EXCLUDED.daily_max_aqi,
|
||||||
|
daily_min_aqi = EXCLUDED.daily_min_aqi,
|
||||||
|
good_pm25_percentage = EXCLUDED.good_pm25_percentage,
|
||||||
|
moderate_pm25_percentage = EXCLUDED.moderate_pm25_percentage,
|
||||||
|
unhealthy_sensitive_pm25_percentage = EXCLUDED.unhealthy_sensitive_pm25_percentage,
|
||||||
|
unhealthy_pm25_percentage = EXCLUDED.unhealthy_pm25_percentage,
|
||||||
|
very_unhealthy_pm25_percentage = EXCLUDED.very_unhealthy_pm25_percentage,
|
||||||
|
hazardous_pm25_percentage = EXCLUDED.hazardous_pm25_percentage,
|
||||||
|
daily_avg_pm25 = EXCLUDED.daily_avg_pm25,
|
||||||
|
daily_max_pm25 = EXCLUDED.daily_max_pm25,
|
||||||
|
daily_min_pm25 = EXCLUDED.daily_min_pm25,
|
||||||
|
good_pm10_percentage = EXCLUDED.good_pm10_percentage,
|
||||||
|
moderate_pm10_percentage = EXCLUDED.moderate_pm10_percentage,
|
||||||
|
unhealthy_sensitive_pm10_percentage = EXCLUDED.unhealthy_sensitive_pm10_percentage,
|
||||||
|
unhealthy_pm10_percentage = EXCLUDED.unhealthy_pm10_percentage,
|
||||||
|
very_unhealthy_pm10_percentage = EXCLUDED.very_unhealthy_pm10_percentage,
|
||||||
|
hazardous_pm10_percentage = EXCLUDED.hazardous_pm10_percentage,
|
||||||
|
daily_avg_pm10 = EXCLUDED.daily_avg_pm10,
|
||||||
|
daily_max_pm10 = EXCLUDED.daily_max_pm10,
|
||||||
|
daily_min_pm10 = EXCLUDED.daily_min_pm10,
|
||||||
|
good_voc_percentage = EXCLUDED.good_voc_percentage,
|
||||||
|
moderate_voc_percentage = EXCLUDED.moderate_voc_percentage,
|
||||||
|
unhealthy_sensitive_voc_percentage = EXCLUDED.unhealthy_sensitive_voc_percentage,
|
||||||
|
unhealthy_voc_percentage = EXCLUDED.unhealthy_voc_percentage,
|
||||||
|
very_unhealthy_voc_percentage = EXCLUDED.very_unhealthy_voc_percentage,
|
||||||
|
hazardous_voc_percentage = EXCLUDED.hazardous_voc_percentage,
|
||||||
|
daily_avg_voc = EXCLUDED.daily_avg_voc,
|
||||||
|
daily_max_voc = EXCLUDED.daily_max_voc,
|
||||||
|
daily_min_voc = EXCLUDED.daily_min_voc,
|
||||||
|
good_co2_percentage = EXCLUDED.good_co2_percentage,
|
||||||
|
moderate_co2_percentage = EXCLUDED.moderate_co2_percentage,
|
||||||
|
unhealthy_sensitive_co2_percentage = EXCLUDED.unhealthy_sensitive_co2_percentage,
|
||||||
|
unhealthy_co2_percentage = EXCLUDED.unhealthy_co2_percentage,
|
||||||
|
very_unhealthy_co2_percentage = EXCLUDED.very_unhealthy_co2_percentage,
|
||||||
|
hazardous_co2_percentage = EXCLUDED.hazardous_co2_percentage,
|
||||||
|
daily_avg_co2 = EXCLUDED.daily_avg_co2,
|
||||||
|
daily_max_co2 = EXCLUDED.daily_max_co2,
|
||||||
|
daily_min_co2 = EXCLUDED.daily_min_co2,
|
||||||
|
good_ch2o_percentage = EXCLUDED.good_ch2o_percentage,
|
||||||
|
moderate_ch2o_percentage = EXCLUDED.moderate_ch2o_percentage,
|
||||||
|
unhealthy_sensitive_ch2o_percentage = EXCLUDED.unhealthy_sensitive_ch2o_percentage,
|
||||||
|
unhealthy_ch2o_percentage = EXCLUDED.unhealthy_ch2o_percentage,
|
||||||
|
very_unhealthy_ch2o_percentage = EXCLUDED.very_unhealthy_ch2o_percentage,
|
||||||
|
hazardous_ch2o_percentage = EXCLUDED.hazardous_ch2o_percentage,
|
||||||
|
daily_avg_ch2o = EXCLUDED.daily_avg_ch2o,
|
||||||
|
daily_max_ch2o = EXCLUDED.daily_max_ch2o,
|
||||||
|
daily_min_ch2o = EXCLUDED.daily_min_ch2o;
|
||||||
|
|
||||||
|
|
@ -1,100 +1,94 @@
|
|||||||
-- Step 1: Get device presence events with previous timestamps
|
WITH presence_logs AS (
|
||||||
WITH start_date AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
d.uuid AS device_id,
|
|
||||||
d.space_device_uuid AS space_id,
|
d.space_device_uuid AS space_id,
|
||||||
|
l.device_id,
|
||||||
|
l.event_time,
|
||||||
l.value,
|
l.value,
|
||||||
l.event_time::timestamp AS event_time,
|
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time,
|
||||||
LAG(l.event_time::timestamp) OVER (PARTITION BY d.uuid ORDER BY l.event_time) AS prev_timestamp
|
LAG(l.value) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_value
|
||||||
FROM device d
|
FROM device d
|
||||||
LEFT JOIN "device-status-log" l
|
JOIN "device-status-log" l ON d.uuid = l.device_id
|
||||||
ON d.uuid = l.device_id
|
JOIN product p ON p.uuid = d.product_device_uuid
|
||||||
LEFT JOIN product p
|
WHERE l.code = 'presence_state'
|
||||||
ON p.uuid = d.product_device_uuid
|
AND p.cat_name = 'hps'
|
||||||
WHERE p.cat_name = 'hps'
|
|
||||||
AND l.code = 'presence_state'
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 2: Identify periods when device reports "none"
|
-- Intervals when device was in 'presence' (between prev_time and event_time when value='none')
|
||||||
device_none_periods AS (
|
presence_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
device_id,
|
prev_time AS start_time,
|
||||||
event_time AS empty_from,
|
event_time AS end_time
|
||||||
LEAD(event_time) OVER (PARTITION BY device_id ORDER BY event_time) AS empty_until
|
FROM presence_logs
|
||||||
FROM start_date
|
|
||||||
WHERE value = 'none'
|
WHERE value = 'none'
|
||||||
|
AND prev_value = 'presence'
|
||||||
|
AND prev_time IS NOT NULL
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 3: Clip the "none" periods to the edges of each day
|
-- Split intervals across days
|
||||||
clipped_device_none_periods AS (
|
split_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
GREATEST(empty_from, DATE_TRUNC('day', empty_from)) AS clipped_from,
|
generate_series(
|
||||||
LEAST(empty_until, DATE_TRUNC('day', empty_until) + INTERVAL '1 day') AS clipped_until
|
date_trunc('day', start_time),
|
||||||
FROM device_none_periods
|
date_trunc('day', end_time),
|
||||||
WHERE empty_until IS NOT NULL
|
interval '1 day'
|
||||||
|
)::date AS event_date,
|
||||||
|
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
|
||||||
|
LEAST(end_time, date_trunc('day', end_time) + interval '1 day') AS interval_end
|
||||||
|
FROM presence_intervals
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 4: Break multi-day periods into daily intervals
|
-- Mark and group overlapping intervals per space per day
|
||||||
generated_daily_intervals AS (
|
ordered_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
gs::date AS day,
|
event_date,
|
||||||
GREATEST(clipped_from, gs) AS interval_start,
|
interval_start,
|
||||||
LEAST(clipped_until, gs + INTERVAL '1 day') AS interval_end
|
interval_end,
|
||||||
FROM clipped_device_none_periods,
|
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
|
||||||
LATERAL generate_series(DATE_TRUNC('day', clipped_from), DATE_TRUNC('day', clipped_until), INTERVAL '1 day') AS gs
|
FROM split_intervals
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 5: Merge overlapping or adjacent intervals per day
|
grouped_intervals AS (
|
||||||
|
SELECT *,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
|
||||||
|
FROM ordered_intervals
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Merge overlapping intervals per group
|
||||||
merged_intervals AS (
|
merged_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
day,
|
|
||||||
interval_start,
|
|
||||||
interval_end
|
|
||||||
FROM (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
day,
|
|
||||||
interval_start,
|
|
||||||
interval_end,
|
|
||||||
LAG(interval_end) OVER (PARTITION BY space_id, day ORDER BY interval_start) AS prev_end
|
|
||||||
FROM generated_daily_intervals
|
|
||||||
) sub
|
|
||||||
WHERE prev_end IS NULL OR interval_start > prev_end
|
|
||||||
),
|
|
||||||
|
|
||||||
-- Step 6: Sum up total missing seconds (device reported "none") per day
|
|
||||||
missing_seconds_per_day AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
day AS missing_date,
|
|
||||||
SUM(EXTRACT(EPOCH FROM (interval_end - interval_start))) AS total_missing_seconds
|
|
||||||
FROM merged_intervals
|
|
||||||
GROUP BY space_id, day
|
|
||||||
),
|
|
||||||
|
|
||||||
-- Step 7: Calculate total occupied time per day (86400 - missing)
|
|
||||||
occupied_seconds_per_day AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
missing_date as event_date,
|
|
||||||
86400 - total_missing_seconds AS total_occupied_seconds,
|
|
||||||
(86400 - total_missing_seconds)/86400*100 as occupancy_prct
|
|
||||||
FROM missing_seconds_per_day
|
|
||||||
)
|
|
||||||
|
|
||||||
-- Final Output
|
|
||||||
, final_data as (
|
|
||||||
SELECT space_id,
|
|
||||||
event_date,
|
event_date,
|
||||||
total_occupied_seconds,
|
MIN(interval_start) AS merged_start,
|
||||||
occupancy_prct
|
MAX(interval_end) AS merged_end
|
||||||
FROM occupied_seconds_per_day
|
FROM grouped_intervals
|
||||||
ORDER BY 1,2
|
GROUP BY space_id, event_date, grp
|
||||||
)
|
),
|
||||||
|
|
||||||
|
-- Sum durations of merged intervals
|
||||||
|
summed_intervals AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
|
||||||
|
FROM merged_intervals
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
final_data AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
|
||||||
|
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
|
||||||
|
FROM summed_intervals
|
||||||
|
ORDER BY space_id, event_date)
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO public."space-daily-occupancy-duration" (
|
INSERT INTO public."space-daily-occupancy-duration" (
|
||||||
space_uuid,
|
space_uuid,
|
||||||
@ -104,12 +98,13 @@ INSERT INTO public."space-daily-occupancy-duration" (
|
|||||||
)
|
)
|
||||||
select space_id,
|
select space_id,
|
||||||
event_date,
|
event_date,
|
||||||
total_occupied_seconds,
|
occupied_seconds,
|
||||||
occupancy_prct
|
occupancy_percentage
|
||||||
FROM final_data
|
FROM final_data
|
||||||
ON CONFLICT (space_uuid, event_date) DO UPDATE
|
ON CONFLICT (space_uuid, event_date) DO UPDATE
|
||||||
SET
|
SET
|
||||||
occupancy_percentage = EXCLUDED.occupancy_percentage;
|
occupancy_percentage = EXCLUDED.occupancy_percentage,
|
||||||
|
occupied_seconds = EXCLUDED.occupied_seconds;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2,116 +2,108 @@ WITH params AS (
|
|||||||
SELECT
|
SELECT
|
||||||
TO_DATE(NULLIF($1, ''), 'YYYY-MM-DD') AS event_date,
|
TO_DATE(NULLIF($1, ''), 'YYYY-MM-DD') AS event_date,
|
||||||
$2::uuid AS space_id
|
$2::uuid AS space_id
|
||||||
)
|
),
|
||||||
|
|
||||||
, start_date AS (
|
presence_logs AS (
|
||||||
SELECT
|
SELECT
|
||||||
d.uuid AS device_id,
|
|
||||||
d.space_device_uuid AS space_id,
|
d.space_device_uuid AS space_id,
|
||||||
|
l.device_id,
|
||||||
|
l.event_time,
|
||||||
l.value,
|
l.value,
|
||||||
l.event_time::timestamp AS event_time,
|
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time
|
||||||
LAG(l.event_time::timestamp) OVER (PARTITION BY d.uuid ORDER BY l.event_time) AS prev_timestamp
|
|
||||||
FROM device d
|
FROM device d
|
||||||
LEFT JOIN "device-status-log" l
|
JOIN "device-status-log" l ON d.uuid = l.device_id
|
||||||
ON d.uuid = l.device_id
|
JOIN product p ON p.uuid = d.product_device_uuid
|
||||||
LEFT JOIN product p
|
WHERE l.code = 'presence_state'
|
||||||
ON p.uuid = d.product_device_uuid
|
AND p.cat_name = 'hps'
|
||||||
WHERE p.cat_name = 'hps'
|
|
||||||
AND l.code = 'presence_state'
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 2: Identify periods when device reports "none"
|
presence_intervals AS (
|
||||||
device_none_periods AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
device_id,
|
prev_time AS start_time,
|
||||||
event_time AS empty_from,
|
event_time AS end_time
|
||||||
LEAD(event_time) OVER (PARTITION BY device_id ORDER BY event_time) AS empty_until
|
FROM presence_logs
|
||||||
FROM start_date
|
WHERE value = 'none' AND prev_time IS NOT NULL
|
||||||
WHERE value = 'none'
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 3: Clip the "none" periods to the edges of each day
|
split_intervals AS (
|
||||||
clipped_device_none_periods AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
GREATEST(empty_from, DATE_TRUNC('day', empty_from)) AS clipped_from,
|
generate_series(
|
||||||
LEAST(empty_until, DATE_TRUNC('day', empty_until) + INTERVAL '1 day') AS clipped_until
|
date_trunc('day', start_time),
|
||||||
FROM device_none_periods
|
date_trunc('day', end_time),
|
||||||
WHERE empty_until IS NOT NULL
|
interval '1 day'
|
||||||
|
)::date AS event_date,
|
||||||
|
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
|
||||||
|
LEAST(end_time, date_trunc('day', end_time) + INTERVAL '1 day') AS interval_end
|
||||||
|
FROM presence_intervals
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 4: Break multi-day periods into daily intervals
|
ordered_intervals AS (
|
||||||
generated_daily_intervals AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
gs::date AS day,
|
event_date,
|
||||||
GREATEST(clipped_from, gs) AS interval_start,
|
interval_start,
|
||||||
LEAST(clipped_until, gs + INTERVAL '1 day') AS interval_end
|
interval_end,
|
||||||
FROM clipped_device_none_periods,
|
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
|
||||||
LATERAL generate_series(DATE_TRUNC('day', clipped_from), DATE_TRUNC('day', clipped_until), INTERVAL '1 day') AS gs
|
FROM split_intervals
|
||||||
|
),
|
||||||
|
|
||||||
|
grouped_intervals AS (
|
||||||
|
SELECT *,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
|
||||||
|
FROM ordered_intervals
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 5: Merge overlapping or adjacent intervals per day
|
|
||||||
merged_intervals AS (
|
merged_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
day,
|
event_date,
|
||||||
interval_start,
|
MIN(interval_start) AS merged_start,
|
||||||
interval_end
|
MAX(interval_end) AS merged_end
|
||||||
FROM (
|
FROM grouped_intervals
|
||||||
SELECT
|
GROUP BY space_id, event_date, grp
|
||||||
space_id,
|
|
||||||
day,
|
|
||||||
interval_start,
|
|
||||||
interval_end,
|
|
||||||
LAG(interval_end) OVER (PARTITION BY space_id, day ORDER BY interval_start) AS prev_end
|
|
||||||
FROM generated_daily_intervals
|
|
||||||
) sub
|
|
||||||
WHERE prev_end IS NULL OR interval_start > prev_end
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 6: Sum up total missing seconds (device reported "none") per day
|
summed_intervals AS (
|
||||||
missing_seconds_per_day AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
day AS missing_date,
|
event_date,
|
||||||
SUM(EXTRACT(EPOCH FROM (interval_end - interval_start))) AS total_missing_seconds
|
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
|
||||||
FROM merged_intervals
|
FROM merged_intervals
|
||||||
GROUP BY space_id, day
|
GROUP BY space_id, event_date
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 7: Calculate total occupied time per day (86400 - missing)
|
final_data AS (
|
||||||
occupied_seconds_per_day AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
s.space_id,
|
||||||
missing_date as event_date,
|
s.event_date,
|
||||||
86400 - total_missing_seconds AS total_occupied_seconds,
|
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
|
||||||
(86400 - total_missing_seconds)/86400*100 as occupancy_percentage
|
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
|
||||||
FROM missing_seconds_per_day
|
FROM summed_intervals s
|
||||||
)
|
JOIN params p
|
||||||
|
ON p.space_id = s.space_id
|
||||||
-- Final Output
|
AND p.event_date = s.event_date
|
||||||
, final_data as (
|
|
||||||
SELECT occupied_seconds_per_day.space_id,
|
|
||||||
occupied_seconds_per_day.event_date,
|
|
||||||
occupied_seconds_per_day.occupancy_percentage
|
|
||||||
FROM occupied_seconds_per_day
|
|
||||||
join params p on true
|
|
||||||
and p.space_id = occupied_seconds_per_day.space_id
|
|
||||||
and p.event_date = occupied_seconds_per_day.event_date
|
|
||||||
ORDER BY 1,2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
INSERT INTO public."space-daily-occupancy-duration" (
|
INSERT INTO public."space-daily-occupancy-duration" (
|
||||||
space_uuid,
|
space_uuid,
|
||||||
event_date,
|
event_date,
|
||||||
|
occupied_seconds,
|
||||||
occupancy_percentage
|
occupancy_percentage
|
||||||
)
|
)
|
||||||
select space_id,
|
SELECT
|
||||||
|
space_id,
|
||||||
event_date,
|
event_date,
|
||||||
|
occupied_seconds,
|
||||||
occupancy_percentage
|
occupancy_percentage
|
||||||
FROM final_data
|
FROM final_data
|
||||||
ON CONFLICT (space_uuid, event_date) DO UPDATE
|
ON CONFLICT (space_uuid, event_date) DO UPDATE
|
||||||
SET
|
SET
|
||||||
occupancy_percentage = EXCLUDED.occupancy_percentage;
|
occupancy_percentage = EXCLUDED.occupancy_percentage,
|
||||||
|
occupied_seconds = EXCLUDED.occupied_seconds;
|
||||||
|
|
||||||
|
@ -16,4 +16,5 @@ WITH params AS (
|
|||||||
WHERE A.device_uuid::text = ANY(P.device_ids)
|
WHERE A.device_uuid::text = ANY(P.device_ids)
|
||||||
AND (P.month IS NULL
|
AND (P.month IS NULL
|
||||||
OR date_trunc('month', A.event_date) = P.month
|
OR date_trunc('month', A.event_date) = P.month
|
||||||
)
|
);
|
||||||
|
|
@ -26,42 +26,67 @@ BEGIN
|
|||||||
('pm10', 255, 354, 151, 200),
|
('pm10', 255, 354, 151, 200),
|
||||||
|
|
||||||
-- VOC
|
-- VOC
|
||||||
('voc_value', 0, 200, 0, 50),
|
('voc', 0, 200, 0, 50),
|
||||||
('voc_value', 201, 400, 51, 100),
|
('voc', 201, 400, 51, 100),
|
||||||
('voc_value', 401, 600, 101, 150),
|
('voc', 401, 600, 101, 150),
|
||||||
('voc_value', 601, 1000, 151, 200),
|
('voc', 601, 1000, 151, 200),
|
||||||
|
|
||||||
-- CH2O
|
-- CH2O
|
||||||
('ch2o_value', 0, 2, 0, 50),
|
('ch2o', 0, 2, 0, 50),
|
||||||
('ch2o_value', 2.1, 4, 51, 100),
|
('ch2o', 2.1, 4, 51, 100),
|
||||||
('ch2o_value', 4.1, 6, 101, 150),
|
('ch2o', 4.1, 6, 101, 150),
|
||||||
|
|
||||||
-- CO2
|
-- CO2
|
||||||
('co2_value', 350, 1000, 0, 50),
|
('co2', 350, 1000, 0, 50),
|
||||||
('co2_value', 1001, 1250, 51, 100),
|
('co2', 1001, 1250, 51, 100),
|
||||||
('co2_value', 1251, 1500, 101, 150),
|
('co2', 1251, 1500, 101, 150),
|
||||||
('co2_value', 1501, 2000, 151, 200)
|
('co2', 1501, 2000, 151, 200)
|
||||||
) AS v(pollutant, c_low, c_high, i_low, i_high)
|
) AS v(pollutant, c_low, c_high, i_low, i_high)
|
||||||
WHERE v.pollutant = LOWER(p_pollutant)
|
WHERE v.pollutant = LOWER(p_pollutant)
|
||||||
AND concentration BETWEEN v.c_low AND v.c_high
|
AND concentration BETWEEN v.c_low AND v.c_high
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
|
|
||||||
-- Linear interpolation
|
|
||||||
RETURN ROUND(((i_high - i_low) * (concentration - c_low) / (c_high - c_low)) + i_low);
|
RETURN ROUND(((i_high - i_low) * (concentration - c_low) / (c_high - c_low)) + i_low);
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
-- CTE for device + status log + space
|
|
||||||
|
-- Function to classify AQI
|
||||||
|
CREATE OR REPLACE FUNCTION classify_aqi(aqi NUMERIC)
|
||||||
|
RETURNS TEXT AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN CASE
|
||||||
|
WHEN aqi BETWEEN 0 AND 50 THEN 'Good'
|
||||||
|
WHEN aqi BETWEEN 51 AND 100 THEN 'Moderate'
|
||||||
|
WHEN aqi BETWEEN 101 AND 150 THEN 'Unhealthy for Sensitive Groups'
|
||||||
|
WHEN aqi BETWEEN 151 AND 200 THEN 'Unhealthy'
|
||||||
|
WHEN aqi BETWEEN 201 AND 300 THEN 'Very Unhealthy'
|
||||||
|
WHEN aqi >= 301 THEN 'Hazardous'
|
||||||
|
ELSE NULL
|
||||||
|
END;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
||||||
|
-- Function to convert AQI level string to number
|
||||||
|
CREATE OR REPLACE FUNCTION level_to_numeric(level_text TEXT)
|
||||||
|
RETURNS NUMERIC AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN CAST(regexp_replace(level_text, '[^0-9]', '', 'g') AS NUMERIC);
|
||||||
|
EXCEPTION WHEN others THEN
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
|
||||||
|
-- Query Pipeline Starts Here
|
||||||
WITH device_space AS (
|
WITH device_space AS (
|
||||||
SELECT
|
SELECT
|
||||||
device.uuid AS device_id,
|
device.uuid AS device_id,
|
||||||
device.created_at,
|
|
||||||
device.space_device_uuid AS space_id,
|
device.space_device_uuid AS space_id,
|
||||||
"device-status-log".event_id,
|
"device-status-log".event_time::timestamp AS event_time,
|
||||||
"device-status-log".event_time::date,
|
|
||||||
"device-status-log".code,
|
"device-status-log".code,
|
||||||
"device-status-log".value,
|
"device-status-log".value
|
||||||
"device-status-log".log
|
|
||||||
FROM device
|
FROM device
|
||||||
LEFT JOIN "device-status-log"
|
LEFT JOIN "device-status-log"
|
||||||
ON device.uuid = "device-status-log".device_id
|
ON device.uuid = "device-status-log".device_id
|
||||||
@ -70,75 +95,268 @@ WITH device_space AS (
|
|||||||
WHERE product.cat_name = 'hjjcy'
|
WHERE product.cat_name = 'hjjcy'
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Aggregate air sensor data per device per day
|
average_pollutants AS (
|
||||||
air_data AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
event_time AS date,
|
event_time::date AS event_date,
|
||||||
|
date_trunc('hour', event_time) AS event_hour,
|
||||||
device_id,
|
device_id,
|
||||||
space_id,
|
space_id,
|
||||||
|
|
||||||
-- VOC
|
|
||||||
MIN(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_min,
|
|
||||||
MAX(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_max,
|
|
||||||
AVG(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_avg,
|
|
||||||
|
|
||||||
-- PM1
|
-- PM1
|
||||||
MIN(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_min,
|
MIN(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_min,
|
||||||
MAX(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_max,
|
|
||||||
AVG(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_avg,
|
AVG(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_max,
|
||||||
|
|
||||||
-- PM2.5
|
-- PM25
|
||||||
MIN(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_min,
|
MIN(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_min,
|
||||||
MAX(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_max,
|
|
||||||
AVG(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_avg,
|
AVG(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_max,
|
||||||
|
|
||||||
-- PM10
|
-- PM10
|
||||||
MIN(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_min,
|
MIN(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_min,
|
||||||
MAX(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_max,
|
|
||||||
AVG(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_avg,
|
AVG(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_max,
|
||||||
|
|
||||||
|
-- VOC
|
||||||
|
MIN(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_min,
|
||||||
|
AVG(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_avg,
|
||||||
|
MAX(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_max,
|
||||||
|
|
||||||
-- CH2O
|
-- CH2O
|
||||||
MIN(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_min,
|
MIN(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_min,
|
||||||
MAX(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_max,
|
|
||||||
AVG(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_avg,
|
AVG(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_avg,
|
||||||
|
MAX(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_max,
|
||||||
-- Humidity
|
|
||||||
MIN(CASE WHEN code = 'humidity_value' THEN value::numeric END) AS humidity_low,
|
|
||||||
MAX(CASE WHEN code = 'humidity_value' THEN value::numeric END) AS humidity_high,
|
|
||||||
AVG(CASE WHEN code = 'humidity_value' THEN value::numeric END) AS humidity_avg,
|
|
||||||
|
|
||||||
-- Temperature
|
|
||||||
MIN(CASE WHEN code = 'temp_current' THEN value::numeric END) AS temp_low,
|
|
||||||
MAX(CASE WHEN code = 'temp_current' THEN value::numeric END) AS temp_high,
|
|
||||||
AVG(CASE WHEN code = 'temp_current' THEN value::numeric END) AS temp_avg,
|
|
||||||
|
|
||||||
-- CO2
|
-- CO2
|
||||||
MIN(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_min,
|
MIN(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_min,
|
||||||
MAX(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_max,
|
AVG(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_avg,
|
||||||
AVG(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_avg
|
MAX(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_max
|
||||||
|
|
||||||
FROM device_space
|
FROM device_space
|
||||||
GROUP BY date, device_id, space_id
|
GROUP BY device_id, space_id, event_hour, event_date
|
||||||
)
|
),
|
||||||
|
|
||||||
-- Final select with AQI calculation
|
filled_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
*,
|
||||||
|
-- AVG
|
||||||
|
COALESCE(pm25_avg, LAG(pm25_avg) OVER (PARTITION BY device_id ORDER BY event_hour)) AS pm25_avg_f,
|
||||||
|
COALESCE(pm10_avg, LAG(pm10_avg) OVER (PARTITION BY device_id ORDER BY event_hour)) AS pm10_avg_f,
|
||||||
|
COALESCE(voc_avg, LAG(voc_avg) OVER (PARTITION BY device_id ORDER BY event_hour)) AS voc_avg_f,
|
||||||
|
COALESCE(co2_avg, LAG(co2_avg) OVER (PARTITION BY device_id ORDER BY event_hour)) AS co2_avg_f,
|
||||||
|
COALESCE(ch2o_avg, LAG(ch2o_avg) OVER (PARTITION BY device_id ORDER BY event_hour)) AS ch2o_avg_f,
|
||||||
|
|
||||||
|
-- MIN
|
||||||
|
COALESCE(pm25_min, LAG(pm25_min) OVER (PARTITION BY device_id ORDER BY event_hour)) AS pm25_min_f,
|
||||||
|
COALESCE(pm10_min, LAG(pm10_min) OVER (PARTITION BY device_id ORDER BY event_hour)) AS pm10_min_f,
|
||||||
|
COALESCE(voc_min, LAG(voc_min) OVER (PARTITION BY device_id ORDER BY event_hour)) AS voc_min_f,
|
||||||
|
COALESCE(co2_min, LAG(co2_min) OVER (PARTITION BY device_id ORDER BY event_hour)) AS co2_min_f,
|
||||||
|
COALESCE(ch2o_min, LAG(ch2o_min) OVER (PARTITION BY device_id ORDER BY event_hour)) AS ch2o_min_f,
|
||||||
|
|
||||||
|
-- MAX
|
||||||
|
COALESCE(pm25_max, LAG(pm25_max) OVER (PARTITION BY device_id ORDER BY event_hour)) AS pm25_max_f,
|
||||||
|
COALESCE(pm10_max, LAG(pm10_max) OVER (PARTITION BY device_id ORDER BY event_hour)) AS pm10_max_f,
|
||||||
|
COALESCE(voc_max, LAG(voc_max) OVER (PARTITION BY device_id ORDER BY event_hour)) AS voc_max_f,
|
||||||
|
COALESCE(co2_max, LAG(co2_max) OVER (PARTITION BY device_id ORDER BY event_hour)) AS co2_max_f,
|
||||||
|
COALESCE(ch2o_max, LAG(ch2o_max) OVER (PARTITION BY device_id ORDER BY event_hour)) AS ch2o_max_f
|
||||||
|
FROM average_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
hourly_results AS (
|
||||||
SELECT
|
SELECT
|
||||||
date,
|
|
||||||
device_id,
|
device_id,
|
||||||
space_id,
|
space_id,
|
||||||
voc_min, voc_max, voc_avg,
|
event_date,
|
||||||
pm1_min, pm1_max, pm1_avg,
|
event_hour,
|
||||||
pm25_min, pm25_max, pm25_avg,
|
pm1_min, pm1_avg, pm1_max,
|
||||||
pm10_min, pm10_max, pm10_avg,
|
pm25_min_f, pm25_avg_f, pm25_max_f,
|
||||||
ch2o_min, ch2o_max, ch2o_avg,
|
pm10_min_f, pm10_avg_f, pm10_max_f,
|
||||||
humidity_low, humidity_high, humidity_avg,
|
voc_min_f, voc_avg_f, voc_max_f,
|
||||||
temp_low, temp_high, temp_avg,
|
co2_min_f, co2_avg_f, co2_max_f,
|
||||||
co2_min, co2_max, co2_avg,
|
ch2o_min_f, ch2o_avg_f, ch2o_max_f,
|
||||||
|
|
||||||
GREATEST(
|
GREATEST(
|
||||||
calculate_aqi('pm25', pm25_avg),
|
calculate_aqi('pm25', pm25_min_f),
|
||||||
calculate_aqi('pm10', pm10_avg),
|
calculate_aqi('pm10', pm10_min_f)
|
||||||
calculate_aqi('voc_value', voc_avg),
|
) AS hourly_min_aqi,
|
||||||
calculate_aqi('co2_value', co2_avg),
|
|
||||||
calculate_aqi('ch2o_value', ch2o_avg)
|
GREATEST(
|
||||||
) AS overall_AQI
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
FROM air_data;
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
) AS hourly_avg_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_max_f),
|
||||||
|
calculate_aqi('pm10', pm10_max_f)
|
||||||
|
) AS hourly_max_aqi,
|
||||||
|
|
||||||
|
classify_aqi(GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
)) AS aqi_category,
|
||||||
|
|
||||||
|
classify_aqi(calculate_aqi('pm25',pm25_avg_f)) as pm25_category,
|
||||||
|
classify_aqi(calculate_aqi('pm10',pm10_avg_f)) as pm10_category,
|
||||||
|
classify_aqi(calculate_aqi('voc',voc_avg_f)) as voc_category,
|
||||||
|
classify_aqi(calculate_aqi('co2',co2_avg_f)) as co2_category,
|
||||||
|
classify_aqi(calculate_aqi('ch2o',ch2o_avg_f)) as ch2o_category
|
||||||
|
|
||||||
|
FROM filled_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_category_counts AS (
|
||||||
|
SELECT device_id, space_id, event_date, aqi_category AS category, 'aqi' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date, aqi_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT device_id, space_id, event_date, pm25_category AS category, 'pm25' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date, pm25_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT device_id, space_id, event_date, pm10_category AS category, 'pm10' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date, pm10_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT device_id, space_id, event_date, voc_category AS category, 'voc' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date, voc_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT device_id, space_id, event_date, co2_category AS category, 'co2' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date, co2_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT device_id, space_id, event_date, ch2o_category AS category, 'ch2o' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date, ch2o_category
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_totals AS (
|
||||||
|
SELECT
|
||||||
|
device_id,
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
SUM(category_count) AS total_count
|
||||||
|
FROM daily_category_counts
|
||||||
|
where pollutant = 'aqi'
|
||||||
|
GROUP BY device_id, space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Pivot Categories into Columns
|
||||||
|
daily_percentages AS (
|
||||||
|
select
|
||||||
|
dt.device_id,
|
||||||
|
dt.space_id,
|
||||||
|
dt.event_date,
|
||||||
|
-- AQI CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_aqi_percentage,
|
||||||
|
-- PM25 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm25_percentage,
|
||||||
|
-- PM10 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm10_percentage,
|
||||||
|
-- VOC CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_voc_percentage,
|
||||||
|
-- CO2 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_co2_percentage,
|
||||||
|
-- CH20 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_ch2o_percentage
|
||||||
|
FROM daily_totals dt
|
||||||
|
LEFT JOIN daily_category_counts dcc
|
||||||
|
ON dt.device_id = dcc.device_id AND dt.event_date = dcc.event_date
|
||||||
|
GROUP BY dt.device_id, dt.space_id, dt.event_date, dt.total_count
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_averages AS (
|
||||||
|
SELECT
|
||||||
|
device_id,
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
-- AQI
|
||||||
|
ROUND(AVG(hourly_min_aqi)::numeric, 2) AS daily_min_aqi,
|
||||||
|
ROUND(AVG(hourly_avg_aqi)::numeric, 2) AS daily_avg_aqi,
|
||||||
|
ROUND(AVG(hourly_max_aqi)::numeric, 2) AS daily_max_aqi,
|
||||||
|
-- PM25
|
||||||
|
ROUND(AVG(pm25_min_f)::numeric, 2) AS daily_min_pm25,
|
||||||
|
ROUND(AVG(pm25_avg_f)::numeric, 2) AS daily_avg_pm25,
|
||||||
|
ROUND(AVG(pm25_max_f)::numeric, 2) AS daily_max_pm25,
|
||||||
|
-- PM10
|
||||||
|
ROUND(AVG(pm10_min_f)::numeric, 2) AS daily_min_pm10,
|
||||||
|
ROUND(AVG(pm10_avg_f)::numeric, 2) AS daily_avg_pm10,
|
||||||
|
ROUND(AVG(pm10_max_f)::numeric, 2) AS daily_max_pm10,
|
||||||
|
-- VOC
|
||||||
|
ROUND(AVG(voc_min_f)::numeric, 2) AS daily_min_voc,
|
||||||
|
ROUND(AVG(voc_avg_f)::numeric, 2) AS daily_avg_voc,
|
||||||
|
ROUND(AVG(voc_max_f)::numeric, 2) AS daily_max_voc,
|
||||||
|
-- CO2
|
||||||
|
ROUND(AVG(co2_min_f)::numeric, 2) AS daily_min_co2,
|
||||||
|
ROUND(AVG(co2_avg_f)::numeric, 2) AS daily_avg_co2,
|
||||||
|
ROUND(AVG(co2_max_f)::numeric, 2) AS daily_max_co2,
|
||||||
|
-- CH2O
|
||||||
|
ROUND(AVG(ch2o_min_f)::numeric, 2) AS daily_min_ch2o,
|
||||||
|
ROUND(AVG(ch2o_avg_f)::numeric, 2) AS daily_avg_ch2o,
|
||||||
|
ROUND(AVG(ch2o_max_f)::numeric, 2) AS daily_max_ch2o
|
||||||
|
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY device_id, space_id, event_date
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
p.device_id,
|
||||||
|
p.space_id,
|
||||||
|
p.event_date,
|
||||||
|
p.good_aqi_percentage, p.moderate_aqi_percentage, p.unhealthy_sensitive_aqi_percentage, p.unhealthy_aqi_percentage, p.very_unhealthy_aqi_percentage, p.hazardous_aqi_percentage,
|
||||||
|
a.daily_avg_aqi,a.daily_max_aqi, a.daily_min_aqi,
|
||||||
|
p.good_pm25_percentage, p.moderate_pm25_percentage, p.unhealthy_sensitive_pm25_percentage, p.unhealthy_pm25_percentage, p.very_unhealthy_pm25_percentage, p.hazardous_pm25_percentage,
|
||||||
|
a.daily_avg_pm25,a.daily_max_pm25, a.daily_min_pm25,
|
||||||
|
p.good_pm10_percentage, p.moderate_pm10_percentage, p.unhealthy_sensitive_pm10_percentage, p.unhealthy_pm10_percentage, p.very_unhealthy_pm10_percentage, p.hazardous_pm10_percentage,
|
||||||
|
a.daily_avg_pm10, a.daily_max_pm10, a.daily_min_pm10,
|
||||||
|
p.good_voc_percentage, p.moderate_voc_percentage, p.unhealthy_sensitive_voc_percentage, p.unhealthy_voc_percentage, p.very_unhealthy_voc_percentage, p.hazardous_voc_percentage,
|
||||||
|
a.daily_avg_voc, a.daily_max_voc, a.daily_min_voc,
|
||||||
|
p.good_co2_percentage, p.moderate_co2_percentage, p.unhealthy_sensitive_co2_percentage, p.unhealthy_co2_percentage, p.very_unhealthy_co2_percentage, p.hazardous_co2_percentage,
|
||||||
|
a.daily_avg_co2,a.daily_max_co2, a.daily_min_co2,
|
||||||
|
p.good_ch2o_percentage, p.moderate_ch2o_percentage, p.unhealthy_sensitive_ch2o_percentage, p.unhealthy_ch2o_percentage, p.very_unhealthy_ch2o_percentage, p.hazardous_ch2o_percentage,
|
||||||
|
a.daily_avg_ch2o,a.daily_max_ch2o, a.daily_min_ch2o
|
||||||
|
FROM daily_percentages p
|
||||||
|
LEFT JOIN daily_averages a
|
||||||
|
ON p.device_id = a.device_id AND p.event_date = a.event_date
|
||||||
|
ORDER BY p.space_id, p.event_date;
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,275 @@
|
|||||||
|
-- Query Pipeline Starts Here
|
||||||
|
WITH device_space AS (
|
||||||
|
SELECT
|
||||||
|
device.uuid AS device_id,
|
||||||
|
device.space_device_uuid AS space_id,
|
||||||
|
"device-status-log".event_time::timestamp AS event_time,
|
||||||
|
"device-status-log".code,
|
||||||
|
"device-status-log".value
|
||||||
|
FROM device
|
||||||
|
LEFT JOIN "device-status-log"
|
||||||
|
ON device.uuid = "device-status-log".device_id
|
||||||
|
LEFT JOIN product
|
||||||
|
ON product.uuid = device.product_device_uuid
|
||||||
|
WHERE product.cat_name = 'hjjcy'
|
||||||
|
),
|
||||||
|
|
||||||
|
average_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
event_time::date AS event_date,
|
||||||
|
date_trunc('hour', event_time) AS event_hour,
|
||||||
|
space_id,
|
||||||
|
|
||||||
|
-- PM1
|
||||||
|
MIN(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_min,
|
||||||
|
AVG(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm1' THEN value::numeric END) AS pm1_max,
|
||||||
|
|
||||||
|
-- PM25
|
||||||
|
MIN(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_min,
|
||||||
|
AVG(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm25_value' THEN value::numeric END) AS pm25_max,
|
||||||
|
|
||||||
|
-- PM10
|
||||||
|
MIN(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_min,
|
||||||
|
AVG(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_avg,
|
||||||
|
MAX(CASE WHEN code = 'pm10' THEN value::numeric END) AS pm10_max,
|
||||||
|
|
||||||
|
-- VOC
|
||||||
|
MIN(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_min,
|
||||||
|
AVG(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_avg,
|
||||||
|
MAX(CASE WHEN code = 'voc_value' THEN value::numeric END) AS voc_max,
|
||||||
|
|
||||||
|
-- CH2O
|
||||||
|
MIN(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_min,
|
||||||
|
AVG(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_avg,
|
||||||
|
MAX(CASE WHEN code = 'ch2o_value' THEN value::numeric END) AS ch2o_max,
|
||||||
|
|
||||||
|
-- CO2
|
||||||
|
MIN(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_min,
|
||||||
|
AVG(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_avg,
|
||||||
|
MAX(CASE WHEN code = 'co2_value' THEN value::numeric END) AS co2_max
|
||||||
|
|
||||||
|
FROM device_space
|
||||||
|
GROUP BY space_id, event_hour, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
filled_pollutants AS (
|
||||||
|
SELECT
|
||||||
|
*,
|
||||||
|
-- AVG
|
||||||
|
COALESCE(pm25_avg, LAG(pm25_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_avg_f,
|
||||||
|
COALESCE(pm10_avg, LAG(pm10_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_avg_f,
|
||||||
|
COALESCE(voc_avg, LAG(voc_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_avg_f,
|
||||||
|
COALESCE(co2_avg, LAG(co2_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_avg_f,
|
||||||
|
COALESCE(ch2o_avg, LAG(ch2o_avg) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_avg_f,
|
||||||
|
|
||||||
|
-- MIN
|
||||||
|
COALESCE(pm25_min, LAG(pm25_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_min_f,
|
||||||
|
COALESCE(pm10_min, LAG(pm10_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_min_f,
|
||||||
|
COALESCE(voc_min, LAG(voc_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_min_f,
|
||||||
|
COALESCE(co2_min, LAG(co2_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_min_f,
|
||||||
|
COALESCE(ch2o_min, LAG(ch2o_min) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_min_f,
|
||||||
|
|
||||||
|
-- MAX
|
||||||
|
COALESCE(pm25_max, LAG(pm25_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm25_max_f,
|
||||||
|
COALESCE(pm10_max, LAG(pm10_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS pm10_max_f,
|
||||||
|
COALESCE(voc_max, LAG(voc_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS voc_max_f,
|
||||||
|
COALESCE(co2_max, LAG(co2_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS co2_max_f,
|
||||||
|
COALESCE(ch2o_max, LAG(ch2o_max) OVER (PARTITION BY space_id ORDER BY event_hour)) AS ch2o_max_f
|
||||||
|
FROM average_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
hourly_results AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
event_hour,
|
||||||
|
pm1_min, pm1_avg, pm1_max,
|
||||||
|
pm25_min_f, pm25_avg_f, pm25_max_f,
|
||||||
|
pm10_min_f, pm10_avg_f, pm10_max_f,
|
||||||
|
voc_min_f, voc_avg_f, voc_max_f,
|
||||||
|
co2_min_f, co2_avg_f, co2_max_f,
|
||||||
|
ch2o_min_f, ch2o_avg_f, ch2o_max_f,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_min_f),
|
||||||
|
calculate_aqi('pm10', pm10_min_f)
|
||||||
|
) AS hourly_min_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
) AS hourly_avg_aqi,
|
||||||
|
|
||||||
|
GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_max_f),
|
||||||
|
calculate_aqi('pm10', pm10_max_f)
|
||||||
|
) AS hourly_max_aqi,
|
||||||
|
|
||||||
|
classify_aqi(GREATEST(
|
||||||
|
calculate_aqi('pm25', pm25_avg_f),
|
||||||
|
calculate_aqi('pm10', pm10_avg_f)
|
||||||
|
)) AS aqi_category,
|
||||||
|
|
||||||
|
classify_aqi(calculate_aqi('pm25',pm25_avg_f)) as pm25_category,
|
||||||
|
classify_aqi(calculate_aqi('pm10',pm10_avg_f)) as pm10_category,
|
||||||
|
classify_aqi(calculate_aqi('voc',voc_avg_f)) as voc_category,
|
||||||
|
classify_aqi(calculate_aqi('co2',co2_avg_f)) as co2_category,
|
||||||
|
classify_aqi(calculate_aqi('ch2o',ch2o_avg_f)) as ch2o_category
|
||||||
|
|
||||||
|
FROM filled_pollutants
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_category_counts AS (
|
||||||
|
SELECT space_id, event_date, aqi_category AS category, 'aqi' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, aqi_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, pm25_category AS category, 'pm25' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, pm25_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, pm10_category AS category, 'pm10' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, pm10_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, voc_category AS category, 'voc' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, voc_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, co2_category AS category, 'co2' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, co2_category
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT space_id, event_date, ch2o_category AS category, 'ch2o' AS pollutant, COUNT(*) AS category_count
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date, ch2o_category
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_totals AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
SUM(category_count) AS total_count
|
||||||
|
FROM daily_category_counts
|
||||||
|
where pollutant = 'aqi'
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Pivot Categories into Columns
|
||||||
|
daily_percentages AS (
|
||||||
|
select
|
||||||
|
dt.space_id,
|
||||||
|
dt.event_date,
|
||||||
|
-- AQI CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_aqi_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'aqi' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_aqi_percentage,
|
||||||
|
-- PM25 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm25_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm25' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm25_percentage,
|
||||||
|
-- PM10 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_pm10_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'pm10' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_pm10_percentage,
|
||||||
|
-- VOC CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_voc_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'voc' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_voc_percentage,
|
||||||
|
-- CO2 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_co2_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'co2' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_co2_percentage,
|
||||||
|
-- CH20 CATEGORIES
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Good' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS good_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Moderate' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS moderate_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy for Sensitive Groups' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_sensitive_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Very Unhealthy' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS very_unhealthy_ch2o_percentage,
|
||||||
|
ROUND(COALESCE(SUM(CASE WHEN dcc.category = 'Hazardous' and dcc.pollutant = 'ch2o' THEN dcc.category_count ELSE 0 END) * 100.0 / dt.total_count, 0), 2) AS hazardous_ch2o_percentage
|
||||||
|
FROM daily_totals dt
|
||||||
|
LEFT JOIN daily_category_counts dcc
|
||||||
|
ON dt.space_id = dcc.space_id AND dt.event_date = dcc.event_date
|
||||||
|
GROUP BY dt.space_id, dt.event_date, dt.total_count
|
||||||
|
),
|
||||||
|
|
||||||
|
daily_averages AS (
|
||||||
|
SELECT
|
||||||
|
space_id,
|
||||||
|
event_date,
|
||||||
|
-- AQI
|
||||||
|
ROUND(AVG(hourly_min_aqi)::numeric, 2) AS daily_min_aqi,
|
||||||
|
ROUND(AVG(hourly_avg_aqi)::numeric, 2) AS daily_avg_aqi,
|
||||||
|
ROUND(AVG(hourly_max_aqi)::numeric, 2) AS daily_max_aqi,
|
||||||
|
-- PM25
|
||||||
|
ROUND(AVG(pm25_min_f)::numeric, 2) AS daily_min_pm25,
|
||||||
|
ROUND(AVG(pm25_avg_f)::numeric, 2) AS daily_avg_pm25,
|
||||||
|
ROUND(AVG(pm25_max_f)::numeric, 2) AS daily_max_pm25,
|
||||||
|
-- PM10
|
||||||
|
ROUND(AVG(pm10_min_f)::numeric, 2) AS daily_min_pm10,
|
||||||
|
ROUND(AVG(pm10_avg_f)::numeric, 2) AS daily_avg_pm10,
|
||||||
|
ROUND(AVG(pm10_max_f)::numeric, 2) AS daily_max_pm10,
|
||||||
|
-- VOC
|
||||||
|
ROUND(AVG(voc_min_f)::numeric, 2) AS daily_min_voc,
|
||||||
|
ROUND(AVG(voc_avg_f)::numeric, 2) AS daily_avg_voc,
|
||||||
|
ROUND(AVG(voc_max_f)::numeric, 2) AS daily_max_voc,
|
||||||
|
-- CO2
|
||||||
|
ROUND(AVG(co2_min_f)::numeric, 2) AS daily_min_co2,
|
||||||
|
ROUND(AVG(co2_avg_f)::numeric, 2) AS daily_avg_co2,
|
||||||
|
ROUND(AVG(co2_max_f)::numeric, 2) AS daily_max_co2,
|
||||||
|
-- CH2O
|
||||||
|
ROUND(AVG(ch2o_min_f)::numeric, 2) AS daily_min_ch2o,
|
||||||
|
ROUND(AVG(ch2o_avg_f)::numeric, 2) AS daily_avg_ch2o,
|
||||||
|
ROUND(AVG(ch2o_max_f)::numeric, 2) AS daily_max_ch2o
|
||||||
|
|
||||||
|
FROM hourly_results
|
||||||
|
GROUP BY space_id, event_date
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
p.space_id,
|
||||||
|
p.event_date,
|
||||||
|
p.good_aqi_percentage, p.moderate_aqi_percentage, p.unhealthy_sensitive_aqi_percentage, p.unhealthy_aqi_percentage, p.very_unhealthy_aqi_percentage, p.hazardous_aqi_percentage,
|
||||||
|
a.daily_avg_aqi,a.daily_max_aqi, a.daily_min_aqi,
|
||||||
|
p.good_pm25_percentage, p.moderate_pm25_percentage, p.unhealthy_sensitive_pm25_percentage, p.unhealthy_pm25_percentage, p.very_unhealthy_pm25_percentage, p.hazardous_pm25_percentage,
|
||||||
|
a.daily_avg_pm25,a.daily_max_pm25, a.daily_min_pm25,
|
||||||
|
p.good_pm10_percentage, p.moderate_pm10_percentage, p.unhealthy_sensitive_pm10_percentage, p.unhealthy_pm10_percentage, p.very_unhealthy_pm10_percentage, p.hazardous_pm10_percentage,
|
||||||
|
a.daily_avg_pm10, a.daily_max_pm10, a.daily_min_pm10,
|
||||||
|
p.good_voc_percentage, p.moderate_voc_percentage, p.unhealthy_sensitive_voc_percentage, p.unhealthy_voc_percentage, p.very_unhealthy_voc_percentage, p.hazardous_voc_percentage,
|
||||||
|
a.daily_avg_voc, a.daily_max_voc, a.daily_min_voc,
|
||||||
|
p.good_co2_percentage, p.moderate_co2_percentage, p.unhealthy_sensitive_co2_percentage, p.unhealthy_co2_percentage, p.very_unhealthy_co2_percentage, p.hazardous_co2_percentage,
|
||||||
|
a.daily_avg_co2,a.daily_max_co2, a.daily_min_co2,
|
||||||
|
p.good_ch2o_percentage, p.moderate_ch2o_percentage, p.unhealthy_sensitive_ch2o_percentage, p.unhealthy_ch2o_percentage, p.very_unhealthy_ch2o_percentage, p.hazardous_ch2o_percentage,
|
||||||
|
a.daily_avg_ch2o,a.daily_max_ch2o, a.daily_min_ch2o
|
||||||
|
FROM daily_percentages p
|
||||||
|
LEFT JOIN daily_averages a
|
||||||
|
ON p.space_id = a.space_id AND p.event_date = a.event_date
|
||||||
|
ORDER BY p.space_id, p.event_date;
|
||||||
|
|
||||||
|
|
@ -1,91 +1,90 @@
|
|||||||
-- Step 1: Get device presence events with previous timestamps
|
WITH presence_logs AS (
|
||||||
WITH start_date AS (
|
|
||||||
SELECT
|
SELECT
|
||||||
d.uuid AS device_id,
|
|
||||||
d.space_device_uuid AS space_id,
|
d.space_device_uuid AS space_id,
|
||||||
|
l.device_id,
|
||||||
|
l.event_time,
|
||||||
l.value,
|
l.value,
|
||||||
l.event_time::timestamp AS event_time,
|
LAG(l.event_time) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_time,
|
||||||
LAG(l.event_time::timestamp) OVER (PARTITION BY d.uuid ORDER BY l.event_time) AS prev_timestamp
|
LAG(l.value) OVER (PARTITION BY l.device_id ORDER BY l.event_time) AS prev_value
|
||||||
FROM device d
|
FROM device d
|
||||||
LEFT JOIN "device-status-log" l
|
JOIN "device-status-log" l ON d.uuid = l.device_id
|
||||||
ON d.uuid = l.device_id
|
JOIN product p ON p.uuid = d.product_device_uuid
|
||||||
LEFT JOIN product p
|
WHERE l.code = 'presence_state'
|
||||||
ON p.uuid = d.product_device_uuid
|
AND p.cat_name = 'hps'
|
||||||
WHERE p.cat_name = 'hps'
|
|
||||||
AND l.code = 'presence_state'
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 2: Identify periods when device reports "none"
|
-- Intervals when device was in 'presence' (between prev_time and event_time when value='none')
|
||||||
device_none_periods AS (
|
presence_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
device_id,
|
prev_time AS start_time,
|
||||||
event_time AS empty_from,
|
event_time AS end_time
|
||||||
LEAD(event_time) OVER (PARTITION BY device_id ORDER BY event_time) AS empty_until
|
FROM presence_logs
|
||||||
FROM start_date
|
|
||||||
WHERE value = 'none'
|
WHERE value = 'none'
|
||||||
|
AND prev_value = 'presence'
|
||||||
|
AND prev_time IS NOT NULL
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 3: Clip the "none" periods to the edges of each day
|
-- Split intervals across days
|
||||||
clipped_device_none_periods AS (
|
split_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
GREATEST(empty_from, DATE_TRUNC('day', empty_from)) AS clipped_from,
|
generate_series(
|
||||||
LEAST(empty_until, DATE_TRUNC('day', empty_until) + INTERVAL '1 day') AS clipped_until
|
date_trunc('day', start_time),
|
||||||
FROM device_none_periods
|
date_trunc('day', end_time),
|
||||||
WHERE empty_until IS NOT NULL
|
interval '1 day'
|
||||||
|
)::date AS event_date,
|
||||||
|
GREATEST(start_time, date_trunc('day', start_time)) AS interval_start,
|
||||||
|
LEAST(end_time, date_trunc('day', end_time) + interval '1 day') AS interval_end
|
||||||
|
FROM presence_intervals
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 4: Break multi-day periods into daily intervals
|
-- Mark and group overlapping intervals per space per day
|
||||||
generated_daily_intervals AS (
|
ordered_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
gs::date AS day,
|
event_date,
|
||||||
GREATEST(clipped_from, gs) AS interval_start,
|
interval_start,
|
||||||
LEAST(clipped_until, gs + INTERVAL '1 day') AS interval_end
|
interval_end,
|
||||||
FROM clipped_device_none_periods,
|
LAG(interval_end) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS prev_end
|
||||||
LATERAL generate_series(DATE_TRUNC('day', clipped_from), DATE_TRUNC('day', clipped_until), INTERVAL '1 day') AS gs
|
FROM split_intervals
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 5: Merge overlapping or adjacent intervals per day
|
grouped_intervals AS (
|
||||||
|
SELECT *,
|
||||||
|
SUM(CASE
|
||||||
|
WHEN prev_end IS NULL OR interval_start > prev_end THEN 1
|
||||||
|
ELSE 0
|
||||||
|
END) OVER (PARTITION BY space_id, event_date ORDER BY interval_start) AS grp
|
||||||
|
FROM ordered_intervals
|
||||||
|
),
|
||||||
|
|
||||||
|
-- Merge overlapping intervals per group
|
||||||
merged_intervals AS (
|
merged_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
day,
|
event_date,
|
||||||
interval_start,
|
MIN(interval_start) AS merged_start,
|
||||||
interval_end
|
MAX(interval_end) AS merged_end
|
||||||
FROM (
|
FROM grouped_intervals
|
||||||
SELECT
|
GROUP BY space_id, event_date, grp
|
||||||
space_id,
|
|
||||||
day,
|
|
||||||
interval_start,
|
|
||||||
interval_end,
|
|
||||||
LAG(interval_end) OVER (PARTITION BY space_id, day ORDER BY interval_start) AS prev_end
|
|
||||||
FROM generated_daily_intervals
|
|
||||||
) sub
|
|
||||||
WHERE prev_end IS NULL OR interval_start > prev_end
|
|
||||||
),
|
),
|
||||||
|
|
||||||
-- Step 6: Sum up total missing seconds (device reported "none") per day
|
-- Sum durations of merged intervals
|
||||||
missing_seconds_per_day AS (
|
summed_intervals AS (
|
||||||
SELECT
|
SELECT
|
||||||
space_id,
|
space_id,
|
||||||
day AS missing_date,
|
event_date,
|
||||||
SUM(EXTRACT(EPOCH FROM (interval_end - interval_start))) AS total_missing_seconds
|
SUM(EXTRACT(EPOCH FROM (merged_end - merged_start))) AS raw_occupied_seconds
|
||||||
FROM merged_intervals
|
FROM merged_intervals
|
||||||
GROUP BY space_id, day
|
GROUP BY space_id, event_date
|
||||||
),
|
|
||||||
|
|
||||||
-- Step 7: Calculate total occupied time per day (86400 - missing)
|
|
||||||
occupied_seconds_per_day AS (
|
|
||||||
SELECT
|
|
||||||
space_id,
|
|
||||||
missing_date as date,
|
|
||||||
86400 - total_missing_seconds AS total_occupied_seconds
|
|
||||||
FROM missing_seconds_per_day
|
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Final Output
|
-- Final output with capped seconds and percentage
|
||||||
SELECT *
|
SELECT
|
||||||
FROM occupied_seconds_per_day
|
space_id,
|
||||||
ORDER BY 1,2;
|
event_date,
|
||||||
|
LEAST(raw_occupied_seconds, 86400) AS occupied_seconds,
|
||||||
|
ROUND(LEAST(raw_occupied_seconds, 86400) / 86400.0 * 100, 2) AS occupancy_percentage
|
||||||
|
FROM summed_intervals
|
||||||
|
ORDER BY space_id, event_date;
|
18
libs/common/src/util/calculate.aqi.ts
Normal file
18
libs/common/src/util/calculate.aqi.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export function calculateAQI(pm2_5: number): number {
|
||||||
|
const breakpoints = [
|
||||||
|
{ pmLow: 0.0, pmHigh: 12.0, aqiLow: 0, aqiHigh: 50 },
|
||||||
|
{ pmLow: 12.1, pmHigh: 35.4, aqiLow: 51, aqiHigh: 100 },
|
||||||
|
{ pmLow: 35.5, pmHigh: 55.4, aqiLow: 101, aqiHigh: 150 },
|
||||||
|
{ pmLow: 55.5, pmHigh: 150.4, aqiLow: 151, aqiHigh: 200 },
|
||||||
|
{ pmLow: 150.5, pmHigh: 250.4, aqiLow: 201, aqiHigh: 300 },
|
||||||
|
{ pmLow: 250.5, pmHigh: 500.4, aqiLow: 301, aqiHigh: 500 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const bp = breakpoints.find((b) => pm2_5 >= b.pmLow && pm2_5 <= b.pmHigh);
|
||||||
|
if (!bp) return pm2_5 > 500.4 ? 500 : 0; // Handle out-of-range values
|
||||||
|
|
||||||
|
return Math.round(
|
||||||
|
((bp.aqiHigh - bp.aqiLow) / (bp.pmHigh - bp.pmLow)) * (pm2_5 - bp.pmLow) +
|
||||||
|
bp.aqiLow,
|
||||||
|
);
|
||||||
|
}
|
11
libs/common/src/util/device-utils.ts
Normal file
11
libs/common/src/util/device-utils.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { DeviceEntity } from '../modules/device/entities';
|
||||||
|
|
||||||
|
export function addSpaceUuidToDevices(
|
||||||
|
devices: DeviceEntity[],
|
||||||
|
spaceUuid: string,
|
||||||
|
): DeviceEntity[] {
|
||||||
|
return devices.map((device) => {
|
||||||
|
(device as any).spaceUuid = spaceUuid;
|
||||||
|
return device;
|
||||||
|
});
|
||||||
|
}
|
@ -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 * as nodemailer from 'nodemailer';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import * as nodemailer from 'nodemailer';
|
||||||
import {
|
import {
|
||||||
SEND_EMAIL_API_URL_DEV,
|
SEND_EMAIL_API_URL_DEV,
|
||||||
SEND_EMAIL_API_URL_PROD,
|
SEND_EMAIL_API_URL_PROD,
|
||||||
@ -83,12 +83,17 @@ export class EmailService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async sendEmailWithTemplate(
|
async sendEmailWithTemplate({
|
||||||
email: string,
|
email,
|
||||||
name: string,
|
name,
|
||||||
isEnable: boolean,
|
isEnable,
|
||||||
isDelete: boolean,
|
isDelete,
|
||||||
): 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',
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
|
"format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
|
||||||
"start": "npm run test && node dist/main",
|
"start": "npm run test && node dist/main",
|
||||||
"start:dev": "npm run test && npx nest start --watch",
|
"start:dev": "npm run test && npx nest start --watch",
|
||||||
|
"dev": "npx nest start --watch",
|
||||||
"start:debug": "npm run test && npx nest start --debug --watch",
|
"start:debug": "npm run test && npx nest start --debug --watch",
|
||||||
"start:prod": "npm run test && node dist/main",
|
"start:prod": "npm run test && node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
@ -1,43 +1,44 @@
|
|||||||
|
import { SeederModule } from '@app/common/seed/seeder.module';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import config from './config';
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
import { WinstonModule } from 'nest-winston';
|
||||||
import { AuthenticationModule } from './auth/auth.module';
|
import { AuthenticationModule } from './auth/auth.module';
|
||||||
import { UserModule } from './users/user.module';
|
|
||||||
import { GroupModule } from './group/group.module';
|
|
||||||
import { DeviceModule } from './device/device.module';
|
|
||||||
import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module';
|
|
||||||
import { CommunityModule } from './community/community.module';
|
|
||||||
import { SeederModule } from '@app/common/seed/seeder.module';
|
|
||||||
import { UserNotificationModule } from './user-notification/user-notification.module';
|
|
||||||
import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module';
|
|
||||||
import { SceneModule } from './scene/scene.module';
|
|
||||||
import { DoorLockModule } from './door-lock/door.lock.module';
|
|
||||||
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
|
||||||
import { LoggingInterceptor } from './interceptors/logging.interceptor';
|
|
||||||
import { AutomationModule } from './automation/automation.module';
|
import { AutomationModule } from './automation/automation.module';
|
||||||
import { RegionModule } from './region/region.module';
|
|
||||||
import { TimeZoneModule } from './timezone/timezone.module';
|
|
||||||
import { VisitorPasswordModule } from './vistor-password/visitor-password.module';
|
|
||||||
import { ScheduleModule } from './schedule/schedule.module';
|
|
||||||
import { SpaceModule } from './space/space.module';
|
|
||||||
import { ProductModule } from './product';
|
|
||||||
import { ProjectModule } from './project';
|
|
||||||
import { SpaceModelModule } from './space-model';
|
|
||||||
import { InviteUserModule } from './invite-user/invite-user.module';
|
|
||||||
import { PermissionModule } from './permission/permission.module';
|
|
||||||
import { RoleModule } from './role/role.module';
|
|
||||||
import { TermsConditionsModule } from './terms-conditions/terms-conditions.module';
|
|
||||||
import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module';
|
|
||||||
import { TagModule } from './tags/tags.module';
|
|
||||||
import { ClientModule } from './client/client.module';
|
import { ClientModule } from './client/client.module';
|
||||||
import { DeviceCommissionModule } from './commission-device/commission-device.module';
|
import { DeviceCommissionModule } from './commission-device/commission-device.module';
|
||||||
import { PowerClampModule } from './power-clamp/power-clamp.module';
|
import { CommunityModule } from './community/community.module';
|
||||||
import { WinstonModule } from 'nest-winston';
|
import config from './config';
|
||||||
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
|
import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module';
|
||||||
|
import { DeviceModule } from './device/device.module';
|
||||||
|
import { DoorLockModule } from './door-lock/door.lock.module';
|
||||||
|
import { GroupModule } from './group/group.module';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
|
import { LoggingInterceptor } from './interceptors/logging.interceptor';
|
||||||
|
import { InviteUserModule } from './invite-user/invite-user.module';
|
||||||
|
import { PermissionModule } from './permission/permission.module';
|
||||||
|
import { PowerClampModule } from './power-clamp/power-clamp.module';
|
||||||
|
import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module';
|
||||||
|
import { ProductModule } from './product';
|
||||||
|
import { ProjectModule } from './project';
|
||||||
|
import { RegionModule } from './region/region.module';
|
||||||
|
import { RoleModule } from './role/role.module';
|
||||||
|
import { SceneModule } from './scene/scene.module';
|
||||||
|
import { ScheduleModule } from './schedule/schedule.module';
|
||||||
|
import { SpaceModelModule } from './space-model';
|
||||||
|
import { SpaceModule } from './space/space.module';
|
||||||
|
import { TagModule } from './tags/tags.module';
|
||||||
|
import { TermsConditionsModule } from './terms-conditions/terms-conditions.module';
|
||||||
|
import { TimeZoneModule } from './timezone/timezone.module';
|
||||||
|
import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module';
|
||||||
|
import { UserNotificationModule } from './user-notification/user-notification.module';
|
||||||
|
import { UserModule } from './users/user.module';
|
||||||
|
import { VisitorPasswordModule } from './vistor-password/visitor-password.module';
|
||||||
|
|
||||||
import { winstonLoggerOptions } from '../libs/common/src/logger/services/winston.logger';
|
import { winstonLoggerOptions } from '../libs/common/src/logger/services/winston.logger';
|
||||||
|
import { AqiModule } from './aqi/aqi.module';
|
||||||
import { OccupancyModule } from './occupancy/occupancy.module';
|
import { OccupancyModule } from './occupancy/occupancy.module';
|
||||||
|
import { WeatherModule } from './weather/weather.module';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
@ -79,6 +80,8 @@ import { OccupancyModule } from './occupancy/occupancy.module';
|
|||||||
PowerClampModule,
|
PowerClampModule,
|
||||||
HealthModule,
|
HealthModule,
|
||||||
OccupancyModule,
|
OccupancyModule,
|
||||||
|
WeatherModule,
|
||||||
|
AqiModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
11
src/aqi/aqi.module.ts
Normal file
11
src/aqi/aqi.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
|
import { AqiService } from './services';
|
||||||
|
import { AqiController } from './controllers';
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
controllers: [AqiController],
|
||||||
|
providers: [AqiService, SqlLoaderService],
|
||||||
|
})
|
||||||
|
export class AqiModule {}
|
64
src/aqi/controllers/aqi.controller.ts
Normal file
64
src/aqi/controllers/aqi.controller.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOperation,
|
||||||
|
ApiParam,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
|
||||||
|
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||||
|
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||||
|
import { AqiService } from '../services/aqi.service';
|
||||||
|
import {
|
||||||
|
GetAqiDailyBySpaceDto,
|
||||||
|
GetAqiPollutantBySpaceDto,
|
||||||
|
} from '../dto/get-aqi.dto';
|
||||||
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
|
import { SpaceParamsDto } from '../dto/aqi-params.dto';
|
||||||
|
|
||||||
|
@ApiTags('AQI Module')
|
||||||
|
@Controller({
|
||||||
|
version: EnableDisableStatusEnum.ENABLED,
|
||||||
|
path: ControllerRoute.AQI.ROUTE,
|
||||||
|
})
|
||||||
|
export class AqiController {
|
||||||
|
constructor(private readonly aqiService: AqiService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get('range/space/:spaceUuid')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: ControllerRoute.AQI.ACTIONS.GET_AQI_RANGE_DATA_SUMMARY,
|
||||||
|
description: ControllerRoute.AQI.ACTIONS.GET_AQI_RANGE_DATA_DESCRIPTION,
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'spaceUuid',
|
||||||
|
description: 'UUID of the Space',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
async getAQIRangeDataBySpace(
|
||||||
|
@Param() params: SpaceParamsDto,
|
||||||
|
@Query() query: GetAqiDailyBySpaceDto,
|
||||||
|
): Promise<BaseResponseDto> {
|
||||||
|
return await this.aqiService.getAQIRangeDataBySpace(params, query);
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get('distribution/space/:spaceUuid')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: ControllerRoute.AQI.ACTIONS.GET_AQI_DISTRIBUTION_DATA_SUMMARY,
|
||||||
|
description:
|
||||||
|
ControllerRoute.AQI.ACTIONS.GET_AQI_DISTRIBUTION_DATA_DESCRIPTION,
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'spaceUuid',
|
||||||
|
description: 'UUID of the Space',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
async getAQIDistributionDataBySpace(
|
||||||
|
@Param() params: SpaceParamsDto,
|
||||||
|
@Query() query: GetAqiPollutantBySpaceDto,
|
||||||
|
): Promise<BaseResponseDto> {
|
||||||
|
return await this.aqiService.getAQIDistributionDataBySpace(params, query);
|
||||||
|
}
|
||||||
|
}
|
1
src/aqi/controllers/index.ts
Normal file
1
src/aqi/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './aqi.controller';
|
7
src/aqi/dto/aqi-params.dto.ts
Normal file
7
src/aqi/dto/aqi-params.dto.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { IsNotEmpty, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
|
export class SpaceParamsDto {
|
||||||
|
@IsUUID('4', { message: 'Invalid UUID format' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
spaceUuid: string;
|
||||||
|
}
|
37
src/aqi/dto/get-aqi.dto.ts
Normal file
37
src/aqi/dto/get-aqi.dto.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { PollutantType } from '@app/common/constants/pollutants.enum';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Matches, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class GetAqiDailyBySpaceDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Month and year in format YYYY-MM',
|
||||||
|
example: '2025-03',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@Matches(/^\d{4}-(0[1-9]|1[0-2])$/, {
|
||||||
|
message: 'monthDate must be in YYYY-MM format',
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
monthDate: string;
|
||||||
|
}
|
||||||
|
export class GetAqiPollutantBySpaceDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Pollutant Type',
|
||||||
|
enum: PollutantType,
|
||||||
|
example: PollutantType.AQI,
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public pollutantType: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Month and year in format YYYY-MM',
|
||||||
|
example: '2025-03',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@Matches(/^\d{4}-(0[1-9]|1[0-2])$/, {
|
||||||
|
message: 'monthDate must be in YYYY-MM format',
|
||||||
|
})
|
||||||
|
@IsNotEmpty()
|
||||||
|
monthDate: string;
|
||||||
|
}
|
138
src/aqi/services/aqi.service.ts
Normal file
138
src/aqi/services/aqi.service.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
GetAqiDailyBySpaceDto,
|
||||||
|
GetAqiPollutantBySpaceDto,
|
||||||
|
} from '../dto/get-aqi.dto';
|
||||||
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||||
|
import { SpaceParamsDto } from '../dto/aqi-params.dto';
|
||||||
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path';
|
||||||
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
|
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||||
|
import { PollutantType } from '@app/common/constants/pollutants.enum';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AqiService {
|
||||||
|
constructor(
|
||||||
|
private readonly sqlLoader: SqlLoaderService,
|
||||||
|
private readonly dataSource: DataSource,
|
||||||
|
) {}
|
||||||
|
async getAQIDistributionDataBySpace(
|
||||||
|
params: SpaceParamsDto,
|
||||||
|
query: GetAqiPollutantBySpaceDto,
|
||||||
|
): Promise<BaseResponseDto> {
|
||||||
|
const { monthDate, pollutantType } = query;
|
||||||
|
const { spaceUuid } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.executeProcedure(
|
||||||
|
'fact_daily_space_aqi',
|
||||||
|
'proceduce_select_daily_space_aqi',
|
||||||
|
[spaceUuid, monthDate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
'good',
|
||||||
|
'moderate',
|
||||||
|
'unhealthy_sensitive',
|
||||||
|
'unhealthy',
|
||||||
|
'very_unhealthy',
|
||||||
|
'hazardous',
|
||||||
|
];
|
||||||
|
|
||||||
|
const transformedData = data.map((item) => {
|
||||||
|
const date = new Date(item.event_date).toLocaleDateString('en-CA'); // YYYY-MM-DD
|
||||||
|
|
||||||
|
const categoryData = categories.map((category) => {
|
||||||
|
const key = `${category}_${pollutantType.toLowerCase()}_percentage`;
|
||||||
|
return {
|
||||||
|
type: category,
|
||||||
|
percentage: item[key] ?? 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { date, data: categoryData };
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = this.buildResponse(
|
||||||
|
`AQI distribution data fetched successfully for ${spaceUuid} space and pollutant ${pollutantType}`,
|
||||||
|
transformedData,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch AQI distribution data', {
|
||||||
|
error,
|
||||||
|
spaceUuid,
|
||||||
|
});
|
||||||
|
throw new HttpException(
|
||||||
|
error.response?.message || 'Failed to fetch AQI distribution data',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAQIRangeDataBySpace(
|
||||||
|
params: SpaceParamsDto,
|
||||||
|
query: GetAqiDailyBySpaceDto,
|
||||||
|
): Promise<BaseResponseDto> {
|
||||||
|
const { monthDate } = query;
|
||||||
|
const { spaceUuid } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.executeProcedure(
|
||||||
|
'fact_daily_space_aqi',
|
||||||
|
'proceduce_select_daily_space_aqi',
|
||||||
|
[spaceUuid, monthDate],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Define pollutants dynamically
|
||||||
|
const pollutants = Object.values(PollutantType);
|
||||||
|
|
||||||
|
const transformedData = data.map((item) => {
|
||||||
|
const date = new Date(item.event_date).toLocaleDateString('en-CA'); // YYYY-MM-DD
|
||||||
|
const dailyData = pollutants.map((type) => ({
|
||||||
|
type,
|
||||||
|
min: item[`daily_min_${type}`],
|
||||||
|
max: item[`daily_max_${type}`],
|
||||||
|
average: item[`daily_avg_${type}`],
|
||||||
|
}));
|
||||||
|
return { date, data: dailyData };
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = this.buildResponse(
|
||||||
|
`AQI data fetched successfully for ${spaceUuid} space`,
|
||||||
|
transformedData,
|
||||||
|
);
|
||||||
|
return convertKeysToCamelCase(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch AQI data', {
|
||||||
|
error,
|
||||||
|
spaceUuid,
|
||||||
|
});
|
||||||
|
throw new HttpException(
|
||||||
|
error.response?.message || 'Failed to fetch AQI data',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildResponse(message: string, data: any[]) {
|
||||||
|
return new SuccessResponseDto({
|
||||||
|
message,
|
||||||
|
data,
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private async executeProcedure(
|
||||||
|
procedureFolderName: string,
|
||||||
|
procedureFileName: string,
|
||||||
|
params: (string | number | null)[],
|
||||||
|
): Promise<any[]> {
|
||||||
|
const query = this.loadQuery(procedureFolderName, procedureFileName);
|
||||||
|
return await this.dataSource.query(query, params);
|
||||||
|
}
|
||||||
|
private loadQuery(folderName: string, fileName: string): string {
|
||||||
|
return this.sqlLoader.loadQuery(folderName, fileName, SQL_PROCEDURES_PATH);
|
||||||
|
}
|
||||||
|
}
|
1
src/aqi/services/index.ts
Normal file
1
src/aqi/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './aqi.service';
|
@ -29,6 +29,7 @@ import {
|
|||||||
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, SpaceRepositoryModule],
|
imports: [ConfigModule, SpaceRepositoryModule],
|
||||||
@ -57,6 +58,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import * as fs from 'fs';
|
|
||||||
import * as csv from 'csv-parser';
|
import * as csv from 'csv-parser';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
import { ProjectParam } from '@app/common/dto/project-param.dto';
|
||||||
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space';
|
||||||
|
import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities/space-product-allocation.entity';
|
||||||
|
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
|
||||||
|
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
||||||
|
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { DeviceService } from 'src/device/services';
|
import { DeviceService } from 'src/device/services';
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
|
||||||
import { SpaceRepository } from '@app/common/modules/space';
|
|
||||||
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
|
|
||||||
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
|
|
||||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
|
||||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
||||||
import { ProjectParam } from '@app/common/dto/project-param.dto';
|
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceCommissionService {
|
export class DeviceCommissionService {
|
||||||
@ -118,7 +120,7 @@ export class DeviceCommissionService {
|
|||||||
where: { uuid: spaceId },
|
where: { uuid: spaceId },
|
||||||
relations: [
|
relations: [
|
||||||
'productAllocations',
|
'productAllocations',
|
||||||
'productAllocations.tags',
|
'productAllocations.tag',
|
||||||
'productAllocations.product',
|
'productAllocations.product',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -135,7 +137,7 @@ export class DeviceCommissionService {
|
|||||||
where: { uuid: subspaceId },
|
where: { uuid: subspaceId },
|
||||||
relations: [
|
relations: [
|
||||||
'productAllocations',
|
'productAllocations',
|
||||||
'productAllocations.tags',
|
'productAllocations.tag',
|
||||||
'productAllocations.product',
|
'productAllocations.product',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -151,19 +153,23 @@ export class DeviceCommissionService {
|
|||||||
subspace?.productAllocations || space.productAllocations;
|
subspace?.productAllocations || space.productAllocations;
|
||||||
|
|
||||||
const match = allocations
|
const match = allocations
|
||||||
.flatMap((pa) =>
|
.map(
|
||||||
(pa.tags || []).map((tag) => ({ product: pa.product, tag })),
|
({
|
||||||
|
product,
|
||||||
|
tag,
|
||||||
|
}:
|
||||||
|
| SpaceProductAllocationEntity
|
||||||
|
| SubspaceProductAllocationEntity) => ({ product, tag }),
|
||||||
)
|
)
|
||||||
.find(({ tag }) => tag.name === tagName);
|
.find(
|
||||||
|
({ tag, product }) =>
|
||||||
|
tag.name === tagName && product.name === productName,
|
||||||
|
);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
console.error(`No matching tag found for Device ID: ${rawDeviceId}`);
|
console.error(
|
||||||
failureCount.value++;
|
`No matching tag-product combination found for Device ID: ${rawDeviceId}`,
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (match.product.name !== productName) {
|
|
||||||
console.error(`Product name mismatch for Device ID: ${rawDeviceId}`);
|
|
||||||
failureCount.value++;
|
failureCount.value++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
SpaceLinkRepository,
|
SpaceLinkRepository,
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
TagRepository,
|
|
||||||
} from '@app/common/modules/space/repositories';
|
} from '@app/common/modules/space/repositories';
|
||||||
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
import { UserSpaceRepository } from '@app/common/modules/user/repositories';
|
||||||
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
|
||||||
@ -64,6 +63,7 @@ import {
|
|||||||
} from '@app/common/modules/power-clamp/repositories';
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule],
|
||||||
@ -78,6 +78,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
ProjectRepository,
|
ProjectRepository,
|
||||||
SpaceService,
|
SpaceService,
|
||||||
InviteSpaceRepository,
|
InviteSpaceRepository,
|
||||||
|
// Todo: find out why this is needed
|
||||||
SpaceLinkService,
|
SpaceLinkService,
|
||||||
SubSpaceService,
|
SubSpaceService,
|
||||||
ValidationService,
|
ValidationService,
|
||||||
@ -86,6 +87,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
SpaceProductAllocationService,
|
SpaceProductAllocationService,
|
||||||
SpaceLinkRepository,
|
SpaceLinkRepository,
|
||||||
SubspaceRepository,
|
SubspaceRepository,
|
||||||
|
// Todo: find out why this is needed
|
||||||
TagService,
|
TagService,
|
||||||
SubspaceDeviceService,
|
SubspaceDeviceService,
|
||||||
SubspaceProductAllocationService,
|
SubspaceProductAllocationService,
|
||||||
@ -97,7 +99,6 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
SpaceModelProductAllocationService,
|
SpaceModelProductAllocationService,
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SubspaceProductAllocationRepository,
|
SubspaceProductAllocationRepository,
|
||||||
TagRepository,
|
|
||||||
SubspaceModelRepository,
|
SubspaceModelRepository,
|
||||||
SubspaceModelProductAllocationService,
|
SubspaceModelProductAllocationService,
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
@ -116,6 +117,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [CommunityService, SpacePermissionService],
|
exports: [CommunityService, SpacePermissionService],
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { CommunityService } from '../services/community.service';
|
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
@ -10,17 +9,18 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { AddCommunityDto } from '../dtos/add.community.dto';
|
import { AddCommunityDto } from '../dtos/add.community.dto';
|
||||||
import { GetCommunityParams } from '../dtos/get.community.dto';
|
import { GetCommunityParams } from '../dtos/get.community.dto';
|
||||||
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
||||||
|
import { CommunityService } from '../services/community.service';
|
||||||
// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard';
|
// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard';
|
||||||
import { ControllerRoute } from '@app/common/constants/controller-route';
|
import { ControllerRoute } from '@app/common/constants/controller-route';
|
||||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
import { ProjectParam } from '../dtos';
|
|
||||||
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
|
||||||
import { Permissions } from 'src/decorators/permissions.decorator';
|
|
||||||
import { PaginationRequestWithSearchGetListDto } from '@app/common/dto/pagination-with-search.request.dto';
|
import { PaginationRequestWithSearchGetListDto } from '@app/common/dto/pagination-with-search.request.dto';
|
||||||
|
import { Permissions } from 'src/decorators/permissions.decorator';
|
||||||
|
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
||||||
|
import { ProjectParam } from '../dtos';
|
||||||
|
|
||||||
@ApiTags('Community Module')
|
@ApiTags('Community Module')
|
||||||
@Controller({
|
@Controller({
|
||||||
@ -45,6 +45,21 @@ export class CommunityController {
|
|||||||
return await this.communityService.createCommunity(param, addCommunityDto);
|
return await this.communityService.createCommunity(param, addCommunityDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(PermissionsGuard)
|
||||||
|
@Permissions('COMMUNITY_VIEW')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_SUMMARY,
|
||||||
|
description: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_DESCRIPTION,
|
||||||
|
})
|
||||||
|
@Get('v2')
|
||||||
|
async getCommunitiesV2(
|
||||||
|
@Param() param: ProjectParam,
|
||||||
|
@Query() query: PaginationRequestWithSearchGetListDto,
|
||||||
|
): Promise<any> {
|
||||||
|
return this.communityService.getCommunitiesV2(param, query);
|
||||||
|
}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(PermissionsGuard)
|
@UseGuards(PermissionsGuard)
|
||||||
@Permissions('COMMUNITY_VIEW')
|
@Permissions('COMMUNITY_VIEW')
|
||||||
|
@ -1,28 +1,33 @@
|
|||||||
import {
|
import {
|
||||||
Injectable,
|
ORPHAN_COMMUNITY_NAME,
|
||||||
HttpException,
|
ORPHAN_SPACE_NAME,
|
||||||
HttpStatus,
|
} from '@app/common/constants/orphan-constant';
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AddCommunityDto, GetCommunityParams, ProjectParam } from '../dtos';
|
|
||||||
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
|
||||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
|
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||||
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||||
|
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||||
import {
|
import {
|
||||||
ExtendedTypeORMCustomModelFindAllQuery,
|
ExtendedTypeORMCustomModelFindAllQuery,
|
||||||
TypeORMCustomModel,
|
TypeORMCustomModel,
|
||||||
} from '@app/common/models/typeOrmCustom.model';
|
} from '@app/common/models/typeOrmCustom.model';
|
||||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
|
||||||
import { CommunityDto } from '@app/common/modules/community/dtos';
|
import { CommunityDto } from '@app/common/modules/community/dtos';
|
||||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
import { CommunityEntity } from '@app/common/modules/community/entities';
|
||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|
||||||
import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant';
|
|
||||||
import { ILike, In, Not } from 'typeorm';
|
|
||||||
import { SpaceService } from 'src/space/services';
|
|
||||||
import { SpaceRepository } from '@app/common/modules/space';
|
|
||||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space';
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||||
|
import { addSpaceUuidToDevices } from '@app/common/util/device-utils';
|
||||||
|
import {
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { SpaceService } from 'src/space/services';
|
||||||
|
import { QueryRunner, SelectQueryBuilder } from 'typeorm';
|
||||||
|
import { AddCommunityDto, GetCommunityParams, ProjectParam } from '../dtos';
|
||||||
|
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommunityService {
|
export class CommunityService {
|
||||||
@ -67,12 +72,18 @@ export class CommunityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCommunityById(params: GetCommunityParams): Promise<BaseResponseDto> {
|
async getCommunityById(
|
||||||
|
params: GetCommunityParams,
|
||||||
|
queryRunner?: QueryRunner,
|
||||||
|
): Promise<BaseResponseDto> {
|
||||||
const { communityUuid, projectUuid } = params;
|
const { communityUuid, projectUuid } = params;
|
||||||
|
|
||||||
await this.validateProject(projectUuid);
|
await this.validateProject(projectUuid);
|
||||||
|
|
||||||
const community = await this.communityRepository.findOneBy({
|
const communityRepository =
|
||||||
|
queryRunner?.manager.getRepository(CommunityEntity) ||
|
||||||
|
this.communityRepository;
|
||||||
|
const community = await communityRepository.findOneBy({
|
||||||
uuid: communityUuid,
|
uuid: communityUuid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,56 +103,36 @@ export class CommunityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCommunities(
|
async getCommunities(
|
||||||
param: ProjectParam,
|
{ projectUuid }: ProjectParam,
|
||||||
pageable: Partial<ExtendedTypeORMCustomModelFindAllQuery>,
|
pageable: Partial<ExtendedTypeORMCustomModelFindAllQuery>,
|
||||||
): Promise<BaseResponseDto> {
|
): Promise<BaseResponseDto> {
|
||||||
try {
|
try {
|
||||||
const project = await this.validateProject(param.projectUuid);
|
const project = await this.validateProject(projectUuid);
|
||||||
|
|
||||||
pageable.modelName = 'community';
|
/**
|
||||||
pageable.where = {
|
* TODO: removing this breaks the code (should be fixed when refactoring @see TypeORMCustomModel
|
||||||
project: { uuid: param.projectUuid },
|
*/
|
||||||
name: Not(`${ORPHAN_COMMUNITY_NAME}-${project.name}`),
|
pageable.where = {};
|
||||||
};
|
let qb: undefined | SelectQueryBuilder<CommunityEntity> = undefined;
|
||||||
|
|
||||||
|
qb = this.communityRepository
|
||||||
|
.createQueryBuilder('c')
|
||||||
|
.leftJoin('c.spaces', 's', 's.disabled = false')
|
||||||
|
.where('c.project = :projectUuid', { projectUuid })
|
||||||
|
.andWhere(`c.name != '${ORPHAN_COMMUNITY_NAME}-${project.name}'`)
|
||||||
|
.distinct(true);
|
||||||
if (pageable.search) {
|
if (pageable.search) {
|
||||||
const matchingCommunities = await this.communityRepository.find({
|
qb.andWhere(
|
||||||
where: {
|
`c.name ILIKE '%${pageable.search}%' OR s.space_name ILIKE '%${pageable.search}%'`,
|
||||||
project: { uuid: param.projectUuid },
|
);
|
||||||
name: ILike(`%${pageable.search}%`),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const matchingSpaces = await this.spaceRepository.find({
|
|
||||||
where: {
|
|
||||||
spaceName: ILike(`%${pageable.search}%`),
|
|
||||||
disabled: false,
|
|
||||||
community: { project: { uuid: param.projectUuid } },
|
|
||||||
},
|
|
||||||
relations: ['community'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const spaceCommunityUuids = [
|
|
||||||
...new Set(matchingSpaces.map((space) => space.community.uuid)),
|
|
||||||
];
|
|
||||||
|
|
||||||
const allMatchedCommunityUuids = [
|
|
||||||
...new Set([
|
|
||||||
...matchingCommunities.map((c) => c.uuid),
|
|
||||||
...spaceCommunityUuids,
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
|
|
||||||
pageable.where = {
|
|
||||||
...pageable.where,
|
|
||||||
uuid: In(allMatchedCommunityUuids),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const customModel = TypeORMCustomModel(this.communityRepository);
|
const customModel = TypeORMCustomModel(this.communityRepository);
|
||||||
|
|
||||||
const { baseResponseDto, paginationResponseDto } =
|
const { baseResponseDto, paginationResponseDto } =
|
||||||
await customModel.findAll(pageable);
|
await customModel.findAll({ ...pageable, modelName: 'community' }, qb);
|
||||||
|
|
||||||
|
// todo: refactor this to minimize the number of queries
|
||||||
if (pageable.includeSpaces) {
|
if (pageable.includeSpaces) {
|
||||||
const communitiesWithSpaces = await Promise.all(
|
const communitiesWithSpaces = await Promise.all(
|
||||||
baseResponseDto.data.map(async (community: CommunityDto) => {
|
baseResponseDto.data.map(async (community: CommunityDto) => {
|
||||||
@ -149,7 +140,7 @@ export class CommunityService {
|
|||||||
await this.spaceService.getSpacesHierarchyForCommunity(
|
await this.spaceService.getSpacesHierarchyForCommunity(
|
||||||
{
|
{
|
||||||
communityUuid: community.uuid,
|
communityUuid: community.uuid,
|
||||||
projectUuid: param.projectUuid,
|
projectUuid: projectUuid,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onlyWithDevices: false,
|
onlyWithDevices: false,
|
||||||
@ -179,6 +170,75 @@ export class CommunityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCommunitiesV2(
|
||||||
|
{ projectUuid }: ProjectParam,
|
||||||
|
{
|
||||||
|
search,
|
||||||
|
includeSpaces,
|
||||||
|
...pageable
|
||||||
|
}: Partial<ExtendedTypeORMCustomModelFindAllQuery>,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const project = await this.validateProject(projectUuid);
|
||||||
|
|
||||||
|
let qb: undefined | SelectQueryBuilder<CommunityEntity> = undefined;
|
||||||
|
|
||||||
|
qb = this.communityRepository
|
||||||
|
.createQueryBuilder('c')
|
||||||
|
.where('c.project = :projectUuid', { projectUuid })
|
||||||
|
.andWhere(`c.name != '${ORPHAN_COMMUNITY_NAME}-${project.name}'`)
|
||||||
|
.distinct(true);
|
||||||
|
|
||||||
|
if (includeSpaces) {
|
||||||
|
qb.leftJoinAndSelect(
|
||||||
|
'c.spaces',
|
||||||
|
'space',
|
||||||
|
'space.disabled = :disabled AND space.spaceName != :orphanSpaceName',
|
||||||
|
{ disabled: false, orphanSpaceName: ORPHAN_SPACE_NAME },
|
||||||
|
)
|
||||||
|
.leftJoinAndSelect('space.parent', 'parent')
|
||||||
|
.leftJoinAndSelect(
|
||||||
|
'space.children',
|
||||||
|
'children',
|
||||||
|
'children.disabled = :disabled',
|
||||||
|
{ disabled: false },
|
||||||
|
);
|
||||||
|
// .leftJoinAndSelect('space.spaceModel', 'spaceModel')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
qb.andWhere(
|
||||||
|
`c.name ILIKE :search ${includeSpaces ? 'OR space.space_name ILIKE :search' : ''}`,
|
||||||
|
{ search },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customModel = TypeORMCustomModel(this.communityRepository);
|
||||||
|
|
||||||
|
const { baseResponseDto, paginationResponseDto } =
|
||||||
|
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>(
|
||||||
|
baseResponseDto,
|
||||||
|
paginationResponseDto,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Generic error handling
|
||||||
|
if (error instanceof HttpException) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'An error occurred while fetching communities.',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateCommunity(
|
async updateCommunity(
|
||||||
params: GetCommunityParams,
|
params: GetCommunityParams,
|
||||||
updateCommunityDto: UpdateCommunityNameDto,
|
updateCommunityDto: UpdateCommunityNameDto,
|
||||||
@ -336,7 +396,7 @@ export class CommunityService {
|
|||||||
visitedSpaceUuids.add(space.uuid);
|
visitedSpaceUuids.add(space.uuid);
|
||||||
|
|
||||||
if (space.devices?.length) {
|
if (space.devices?.length) {
|
||||||
allDevices.push(...space.devices);
|
allDevices.push(...addSpaceUuidToDevices(space.devices, space.uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space.children?.length) {
|
if (space.children?.length) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import AuthConfig from './auth.config';
|
import AuthConfig from './auth.config';
|
||||||
import AppConfig from './app.config';
|
import AppConfig from './app.config';
|
||||||
import JwtConfig from './jwt.config';
|
import JwtConfig from './jwt.config';
|
||||||
export default [AuthConfig, AppConfig, JwtConfig];
|
import WeatherOpenConfig from './weather.open.config';
|
||||||
|
export default [AuthConfig, AppConfig, JwtConfig, WeatherOpenConfig];
|
||||||
|
9
src/config/weather.open.config.ts
Normal file
9
src/config/weather.open.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs(
|
||||||
|
'openweather-config',
|
||||||
|
(): Record<string, any> => ({
|
||||||
|
OPEN_WEATHER_MAP_API_KEY: process.env.OPEN_WEATHER_MAP_API_KEY,
|
||||||
|
WEATHER_API_URL: process.env.WEATHER_API_URL,
|
||||||
|
}),
|
||||||
|
);
|
@ -1,11 +1,11 @@
|
|||||||
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 { ControllerRoute } from '@app/common/constants/controller-route';
|
||||||
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
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 { Permissions } from 'src/decorators/permissions.decorator';
|
||||||
import { GetDoorLockDevices, ProjectParam } from '../dtos';
|
import { PermissionsGuard } from 'src/guards/permissions.guard';
|
||||||
|
import { GetDevicesFilterDto, ProjectParam } from '../dtos';
|
||||||
|
import { DeviceService } from '../services/device.service';
|
||||||
|
|
||||||
@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: GetDoorLockDevices,
|
@Query() query: GetDevicesFilterDto,
|
||||||
) {
|
) {
|
||||||
return await this.deviceService.getAllDevices(param, query);
|
return await this.deviceService.getAllDevices(param, query);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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,
|
||||||
@ -41,16 +42,7 @@ 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',
|
||||||
@ -72,3 +64,23 @@ 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[];
|
||||||
|
}
|
||||||
|
@ -1,39 +1,46 @@
|
|||||||
import { ORPHAN_SPACE_NAME } from './../../../libs/common/src/constants/orphan-constant';
|
import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum';
|
||||||
import { ProductRepository } from './../../../libs/common/src/modules/product/repositories/product.repository';
|
import { BatchDeviceTypeEnum } from '@app/common/constants/batch-device.enum';
|
||||||
|
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
|
||||||
|
import { DeviceStatuses } from '@app/common/constants/device-status.enum';
|
||||||
|
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
||||||
|
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
||||||
|
import { ProductType } from '@app/common/constants/product-type.enum';
|
||||||
|
import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum';
|
||||||
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||||
|
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||||
|
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||||
|
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { DeviceEntity } from '@app/common/modules/device/entities';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||||
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||||
|
import { addSpaceUuidToDevices } from '@app/common/util/device-utils';
|
||||||
import {
|
import {
|
||||||
Injectable,
|
|
||||||
HttpException,
|
|
||||||
HttpStatus,
|
|
||||||
NotFoundException,
|
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
Inject,
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||||
|
import { AddAutomationDto } from 'src/automation/dtos';
|
||||||
|
import { SceneService } from 'src/scene/services';
|
||||||
|
import { In, Not, QueryRunner } from 'typeorm';
|
||||||
|
import { ProjectParam } from '../dtos';
|
||||||
import {
|
import {
|
||||||
AddDeviceDto,
|
AddDeviceDto,
|
||||||
AddSceneToFourSceneDeviceDto,
|
AddSceneToFourSceneDeviceDto,
|
||||||
UpdateDeviceDto,
|
|
||||||
AssignDeviceToSpaceDto,
|
AssignDeviceToSpaceDto,
|
||||||
|
UpdateDeviceDto,
|
||||||
} from '../dtos/add.device.dto';
|
} from '../dtos/add.device.dto';
|
||||||
import {
|
|
||||||
DeviceInstructionResponse,
|
|
||||||
GetDeviceDetailsFunctionsInterface,
|
|
||||||
GetDeviceDetailsFunctionsStatusInterface,
|
|
||||||
GetDeviceDetailsInterface,
|
|
||||||
GetMacAddressInterface,
|
|
||||||
GetPowerClampFunctionsStatusInterface,
|
|
||||||
controlDeviceInterface,
|
|
||||||
getDeviceLogsInterface,
|
|
||||||
updateDeviceFirmwareInterface,
|
|
||||||
} from '../interfaces/get.device.interface';
|
|
||||||
import {
|
|
||||||
GetDeviceBySpaceUuidDto,
|
|
||||||
GetDeviceLogsDto,
|
|
||||||
GetDevicesBySpaceOrCommunityDto,
|
|
||||||
GetDoorLockDevices,
|
|
||||||
} from '../dtos/get.device.dto';
|
|
||||||
import {
|
import {
|
||||||
BatchControlDevicesDto,
|
BatchControlDevicesDto,
|
||||||
BatchFactoryResetDevicesDto,
|
BatchFactoryResetDevicesDto,
|
||||||
@ -41,32 +48,29 @@ import {
|
|||||||
ControlDeviceDto,
|
ControlDeviceDto,
|
||||||
GetSceneFourSceneDeviceDto,
|
GetSceneFourSceneDeviceDto,
|
||||||
} from '../dtos/control.device.dto';
|
} from '../dtos/control.device.dto';
|
||||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
|
||||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
|
||||||
import { In, Not, QueryRunner } from 'typeorm';
|
|
||||||
import { ProductType } from '@app/common/constants/product-type.enum';
|
|
||||||
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
|
||||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
|
||||||
import { DeviceStatuses } from '@app/common/constants/device-status.enum';
|
|
||||||
import { CommonErrorCodes } from '@app/common/constants/error-codes.enum';
|
|
||||||
import { BatteryStatus } from '@app/common/constants/battery-status.enum';
|
|
||||||
import { SceneService } from 'src/scene/services';
|
|
||||||
import { AddAutomationDto } from 'src/automation/dtos';
|
|
||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
|
||||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
|
||||||
import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum';
|
|
||||||
import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum';
|
|
||||||
import { DeviceSceneParamDto } from '../dtos/device.param.dto';
|
|
||||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
||||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
||||||
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto';
|
||||||
import { DeviceEntity } from '@app/common/modules/device/entities';
|
import { DeviceSceneParamDto } from '../dtos/device.param.dto';
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
import {
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
GetDeviceLogsDto,
|
||||||
import { ProjectParam } from '../dtos';
|
GetDevicesBySpaceOrCommunityDto,
|
||||||
import { BatchDeviceTypeEnum } from '@app/common/constants/batch-device.enum';
|
GetDevicesFilterDto,
|
||||||
import { DeviceTypeEnum } from '@app/common/constants/device-type.enum';
|
} from '../dtos/get.device.dto';
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
import {
|
||||||
|
controlDeviceInterface,
|
||||||
|
DeviceInstructionResponse,
|
||||||
|
GetDeviceDetailsFunctionsInterface,
|
||||||
|
GetDeviceDetailsFunctionsStatusInterface,
|
||||||
|
GetDeviceDetailsInterface,
|
||||||
|
getDeviceLogsInterface,
|
||||||
|
GetMacAddressInterface,
|
||||||
|
GetPowerClampFunctionsStatusInterface,
|
||||||
|
updateDeviceFirmwareInterface,
|
||||||
|
} from '../interfaces/get.device.interface';
|
||||||
|
import {
|
||||||
|
ORPHAN_COMMUNITY_NAME,
|
||||||
|
ORPHAN_SPACE_NAME,
|
||||||
|
} from './../../../libs/common/src/constants/orphan-constant';
|
||||||
|
import { ProductRepository } from './../../../libs/common/src/modules/product/repositories/product.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceService {
|
export class DeviceService {
|
||||||
@ -197,46 +201,6 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDevicesBySpaceId(
|
|
||||||
getDeviceBySpaceUuidDto: GetDeviceBySpaceUuidDto,
|
|
||||||
): Promise<GetDeviceDetailsInterface[]> {
|
|
||||||
try {
|
|
||||||
const devices = await this.deviceRepository.find({
|
|
||||||
where: {
|
|
||||||
spaceDevice: { uuid: getDeviceBySpaceUuidDto.spaceUuid },
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
relations: [
|
|
||||||
'spaceDevice',
|
|
||||||
'productDevice',
|
|
||||||
'permission',
|
|
||||||
'permission.permissionType',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const devicesData = await Promise.all(
|
|
||||||
devices.map(async (device) => {
|
|
||||||
return {
|
|
||||||
haveRoom: device.spaceDevice ? true : false,
|
|
||||||
productUuid: device.productDevice.uuid,
|
|
||||||
productType: device.productDevice.prodType,
|
|
||||||
permissionType: device.permission[0].permissionType.type,
|
|
||||||
...(await this.getDeviceDetailsByDeviceIdTuya(
|
|
||||||
device.deviceTuyaUuid,
|
|
||||||
)),
|
|
||||||
uuid: device.uuid,
|
|
||||||
} as GetDeviceDetailsInterface;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return devicesData;
|
|
||||||
} catch (error) {
|
|
||||||
// Handle the error here
|
|
||||||
throw new HttpException(
|
|
||||||
'Error fetching devices by space',
|
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async transferDeviceInSpaces(
|
async transferDeviceInSpaces(
|
||||||
assignDeviceToSpaceDto: AssignDeviceToSpaceDto,
|
assignDeviceToSpaceDto: AssignDeviceToSpaceDto,
|
||||||
projectUuid: string,
|
projectUuid: string,
|
||||||
@ -991,19 +955,20 @@ export class DeviceService {
|
|||||||
|
|
||||||
async getAllDevices(
|
async getAllDevices(
|
||||||
param: ProjectParam,
|
param: ProjectParam,
|
||||||
query: GetDoorLockDevices,
|
{ deviceType, spaces }: GetDevicesFilterDto,
|
||||||
): Promise<BaseResponseDto> {
|
): Promise<BaseResponseDto> {
|
||||||
try {
|
try {
|
||||||
await this.validateProject(param.projectUuid);
|
await this.validateProject(param.projectUuid);
|
||||||
if (query.deviceType === DeviceTypeEnum.DOOR_LOCK) {
|
if (deviceType === DeviceTypeEnum.DOOR_LOCK) {
|
||||||
return await this.getDoorLockDevices(param.projectUuid);
|
return await this.getDoorLockDevices(param.projectUuid, spaces);
|
||||||
} else if (!query.deviceType) {
|
} else if (!deviceType) {
|
||||||
const devices = await this.deviceRepository.find({
|
const devices = await this.deviceRepository.find({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
spaceDevice: {
|
spaceDevice: {
|
||||||
community: { project: { uuid: param.projectUuid } },
|
uuid: spaces && spaces.length ? In(spaces) : undefined,
|
||||||
spaceName: Not(ORPHAN_SPACE_NAME),
|
spaceName: Not(ORPHAN_SPACE_NAME),
|
||||||
|
community: { project: { uuid: param.projectUuid } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relations: [
|
relations: [
|
||||||
@ -1198,39 +1163,6 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFullSpaceHierarchy(
|
|
||||||
space: SpaceEntity,
|
|
||||||
): Promise<{ uuid: string; spaceName: string }[]> {
|
|
||||||
try {
|
|
||||||
// Fetch only the relevant spaces, starting with the target space
|
|
||||||
const targetSpace = await this.spaceRepository.findOne({
|
|
||||||
where: { uuid: space.uuid },
|
|
||||||
relations: ['parent', 'children'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch only the ancestors of the target space
|
|
||||||
const ancestors = await this.fetchAncestors(targetSpace);
|
|
||||||
|
|
||||||
// Optionally, fetch descendants if required
|
|
||||||
const descendants = await this.fetchDescendants(targetSpace);
|
|
||||||
|
|
||||||
const fullHierarchy = [...ancestors, targetSpace, ...descendants].map(
|
|
||||||
(space) => ({
|
|
||||||
uuid: space.uuid,
|
|
||||||
spaceName: space.spaceName,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return fullHierarchy;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching space hierarchy:', error.message);
|
|
||||||
throw new HttpException(
|
|
||||||
'Error fetching space hierarchy',
|
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPowerClampInstructionStatus(deviceDetails: any) {
|
async getPowerClampInstructionStatus(deviceDetails: any) {
|
||||||
try {
|
try {
|
||||||
const deviceStatus = await this.getPowerClampInstructionStatusTuya(
|
const deviceStatus = await this.getPowerClampInstructionStatusTuya(
|
||||||
@ -1330,27 +1262,6 @@ export class DeviceService {
|
|||||||
return ancestors.reverse();
|
return ancestors.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchDescendants(space: SpaceEntity): Promise<SpaceEntity[]> {
|
|
||||||
const descendants: SpaceEntity[] = [];
|
|
||||||
|
|
||||||
// Fetch the immediate children of the current space
|
|
||||||
const children = await this.spaceRepository.find({
|
|
||||||
where: { parent: { uuid: space.uuid } },
|
|
||||||
relations: ['children'], // To continue fetching downwards
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const child of children) {
|
|
||||||
// Add the child to the descendants list
|
|
||||||
descendants.push(child);
|
|
||||||
|
|
||||||
// Recursively fetch the child's descendants
|
|
||||||
const childDescendants = await this.fetchDescendants(child);
|
|
||||||
descendants.push(...childDescendants);
|
|
||||||
}
|
|
||||||
|
|
||||||
return descendants;
|
|
||||||
}
|
|
||||||
|
|
||||||
async addSceneToSceneDevice(
|
async addSceneToSceneDevice(
|
||||||
deviceUuid: string,
|
deviceUuid: string,
|
||||||
addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto,
|
addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto,
|
||||||
@ -1653,23 +1564,7 @@ export class DeviceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveDevicesToSpace(
|
async getDoorLockDevices(projectUuid: string, spaces?: string[]) {
|
||||||
targetSpace: SpaceEntity,
|
|
||||||
deviceIds: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
if (!deviceIds || deviceIds.length === 0) {
|
|
||||||
throw new HttpException(
|
|
||||||
'No device IDs provided for transfer',
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.deviceRepository.update(
|
|
||||||
{ uuid: In(deviceIds) },
|
|
||||||
{ spaceDevice: targetSpace },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
async getDoorLockDevices(projectUuid: string) {
|
|
||||||
await this.validateProject(projectUuid);
|
await this.validateProject(projectUuid);
|
||||||
|
|
||||||
const devices = await this.deviceRepository.find({
|
const devices = await this.deviceRepository.find({
|
||||||
@ -1679,6 +1574,7 @@ 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,
|
||||||
@ -1786,7 +1682,8 @@ export class DeviceService {
|
|||||||
throw new NotFoundException('Space not found');
|
throw new NotFoundException('Space not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allDevices: DeviceEntity[] = [...space.devices];
|
const allDevices: DeviceEntity[] = [];
|
||||||
|
allDevices.push(...addSpaceUuidToDevices(space.devices, space.uuid));
|
||||||
|
|
||||||
// Recursive fetch function
|
// Recursive fetch function
|
||||||
const fetchChildren = async (parentSpace: SpaceEntity) => {
|
const fetchChildren = async (parentSpace: SpaceEntity) => {
|
||||||
@ -1796,7 +1693,7 @@ export class DeviceService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
allDevices.push(...child.devices);
|
allDevices.push(...addSpaceUuidToDevices(child.devices, child.uuid));
|
||||||
|
|
||||||
if (child.children.length > 0) {
|
if (child.children.length > 0) {
|
||||||
await fetchChildren(child);
|
await fetchChildren(child);
|
||||||
@ -1835,7 +1732,7 @@ export class DeviceService {
|
|||||||
visitedSpaceUuids.add(space.uuid);
|
visitedSpaceUuids.add(space.uuid);
|
||||||
|
|
||||||
if (space.devices?.length) {
|
if (space.devices?.length) {
|
||||||
allDevices.push(...space.devices);
|
allDevices.push(...addSpaceUuidToDevices(space.devices, space.uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space.children?.length) {
|
if (space.children?.length) {
|
||||||
@ -1858,4 +1755,39 @@ export class DeviceService {
|
|||||||
|
|
||||||
return allDevices;
|
return allDevices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addDevicesToOrphanSpace(
|
||||||
|
space: SpaceEntity,
|
||||||
|
project: ProjectEntity,
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
) {
|
||||||
|
const spaceRepository = queryRunner.manager.getRepository(SpaceEntity);
|
||||||
|
const deviceRepository = queryRunner.manager.getRepository(DeviceEntity);
|
||||||
|
try {
|
||||||
|
const orphanSpace = await spaceRepository.findOne({
|
||||||
|
where: {
|
||||||
|
community: {
|
||||||
|
name: `${ORPHAN_COMMUNITY_NAME}-${project.name}`,
|
||||||
|
},
|
||||||
|
spaceName: ORPHAN_SPACE_NAME,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!orphanSpace) {
|
||||||
|
throw new HttpException(
|
||||||
|
`Orphan space not found in community ${project.name}`,
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await deviceRepository.update(
|
||||||
|
{ uuid: In(space.devices.map((device) => device.uuid)) },
|
||||||
|
{ spaceDevice: orphanSpace },
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to add devices to orphan spaces: ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule],
|
imports: [ConfigModule, DeviceRepositoryModule],
|
||||||
controllers: [DoorLockController],
|
controllers: [DoorLockController],
|
||||||
@ -56,6 +57,7 @@ import { CommunityRepository } from '@app/common/modules/community/repositories'
|
|||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
CommunityRepository,
|
CommunityRepository,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [DoorLockService],
|
exports: [DoorLockService],
|
||||||
})
|
})
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, DeviceRepositoryModule],
|
imports: [ConfigModule, DeviceRepositoryModule],
|
||||||
controllers: [GroupController],
|
controllers: [GroupController],
|
||||||
@ -53,6 +54,7 @@ import { CommunityRepository } from '@app/common/modules/community/repositories'
|
|||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
CommunityRepository,
|
CommunityRepository,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [GroupService],
|
exports: [GroupService],
|
||||||
})
|
})
|
||||||
|
@ -1,89 +1,87 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { InviteUserService } from './services/invite-user.service';
|
|
||||||
import { InviteUserController } from './controllers/invite-user.controller';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { InviteUserController } from './controllers/invite-user.controller';
|
||||||
|
import { InviteUserService } from './services/invite-user.service';
|
||||||
|
|
||||||
|
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||||
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
|
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
||||||
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
|
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||||
|
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||||
import {
|
import {
|
||||||
UserRepository,
|
DeviceRepository,
|
||||||
UserSpaceRepository,
|
DeviceUserPermissionRepository,
|
||||||
} from '@app/common/modules/user/repositories';
|
} from '@app/common/modules/device/repositories';
|
||||||
import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module';
|
import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module';
|
||||||
import {
|
import {
|
||||||
InviteUserRepository,
|
InviteUserRepository,
|
||||||
InviteUserSpaceRepository,
|
InviteUserSpaceRepository,
|
||||||
} from '@app/common/modules/Invite-user/repositiories';
|
} from '@app/common/modules/Invite-user/repositiories';
|
||||||
import { EmailService } from '@app/common/util/email.service';
|
import { PermissionTypeRepository } from '@app/common/modules/permission/repositories';
|
||||||
|
import {
|
||||||
|
PowerClampDailyRepository,
|
||||||
|
PowerClampHourlyRepository,
|
||||||
|
PowerClampMonthlyRepository,
|
||||||
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
|
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||||
|
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
|
||||||
|
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||||
|
import {
|
||||||
|
SceneIconRepository,
|
||||||
|
SceneRepository,
|
||||||
|
} from '@app/common/modules/scene/repositories';
|
||||||
|
import {
|
||||||
|
InviteSpaceRepository,
|
||||||
|
SpaceLinkRepository,
|
||||||
|
SpaceProductAllocationRepository,
|
||||||
|
SpaceRepository,
|
||||||
|
} from '@app/common/modules/space';
|
||||||
|
import {
|
||||||
|
SpaceModelProductAllocationRepoitory,
|
||||||
|
SpaceModelRepository,
|
||||||
|
SubspaceModelProductAllocationRepoitory,
|
||||||
|
SubspaceModelRepository,
|
||||||
|
} from '@app/common/modules/space-model';
|
||||||
|
import {
|
||||||
|
SubspaceProductAllocationRepository,
|
||||||
|
SubspaceRepository,
|
||||||
|
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||||
|
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
||||||
|
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||||
|
import {
|
||||||
|
UserRepository,
|
||||||
|
UserSpaceRepository,
|
||||||
|
} from '@app/common/modules/user/repositories';
|
||||||
|
import { EmailService } from '@app/common/util/email.service';
|
||||||
|
import { CommunityModule } from 'src/community/community.module';
|
||||||
|
import { CommunityService } from 'src/community/services';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
|
import { ProjectUserService } from 'src/project/services/project-user.service';
|
||||||
|
import { SceneService } from 'src/scene/services';
|
||||||
|
import {
|
||||||
|
SpaceModelService,
|
||||||
|
SubSpaceModelService,
|
||||||
|
} from 'src/space-model/services';
|
||||||
|
import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service';
|
||||||
|
import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service';
|
||||||
import {
|
import {
|
||||||
SpaceLinkService,
|
|
||||||
SpaceService,
|
SpaceService,
|
||||||
SpaceUserService,
|
SpaceUserService,
|
||||||
SubspaceDeviceService,
|
SubspaceDeviceService,
|
||||||
SubSpaceService,
|
SubSpaceService,
|
||||||
ValidationService,
|
ValidationService,
|
||||||
} from 'src/space/services';
|
} from 'src/space/services';
|
||||||
import { CommunityService } from 'src/community/services';
|
|
||||||
import {
|
|
||||||
InviteSpaceRepository,
|
|
||||||
SpaceLinkRepository,
|
|
||||||
SpaceProductAllocationRepository,
|
|
||||||
SpaceRepository,
|
|
||||||
TagRepository,
|
|
||||||
} from '@app/common/modules/space';
|
|
||||||
import { SpaceModelRepository } from '@app/common/modules/space-model';
|
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|
||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
|
||||||
import { UserService, UserSpaceService } from 'src/users/services';
|
|
||||||
import { UserDevicePermissionService } from 'src/user-device-permission/services';
|
|
||||||
import {
|
|
||||||
DeviceRepository,
|
|
||||||
DeviceUserPermissionRepository,
|
|
||||||
} from '@app/common/modules/device/repositories';
|
|
||||||
import { PermissionTypeRepository } from '@app/common/modules/permission/repositories';
|
|
||||||
import { ProjectUserService } from 'src/project/services/project-user.service';
|
|
||||||
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
|
|
||||||
import { RegionRepository } from '@app/common/modules/region/repositories';
|
|
||||||
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
|
||||||
import { CommunityModule } from 'src/community/community.module';
|
|
||||||
import { TagService as NewTagService } from 'src/tags/services';
|
|
||||||
import { TagService } from 'src/space/services/tag';
|
|
||||||
import {
|
|
||||||
SpaceModelService,
|
|
||||||
SubSpaceModelService,
|
|
||||||
} from 'src/space-model/services';
|
|
||||||
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
||||||
import {
|
|
||||||
SubspaceProductAllocationRepository,
|
|
||||||
SubspaceRepository,
|
|
||||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
|
||||||
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
||||||
import {
|
import { TagService as NewTagService } from 'src/tags/services';
|
||||||
SpaceModelProductAllocationRepoitory,
|
import { UserDevicePermissionService } from 'src/user-device-permission/services';
|
||||||
SubspaceModelProductAllocationRepoitory,
|
import { UserService, UserSpaceService } from 'src/users/services';
|
||||||
SubspaceModelRepository,
|
|
||||||
} from '@app/common/modules/space-model';
|
|
||||||
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
|
||||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
|
||||||
import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service';
|
|
||||||
import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service';
|
|
||||||
import { DeviceService } from 'src/device/services';
|
|
||||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
|
||||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
|
||||||
import { SceneService } from 'src/scene/services';
|
|
||||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
|
||||||
import {
|
|
||||||
SceneIconRepository,
|
|
||||||
SceneRepository,
|
|
||||||
} from '@app/common/modules/scene/repositories';
|
|
||||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
|
||||||
import {
|
|
||||||
PowerClampHourlyRepository,
|
|
||||||
PowerClampDailyRepository,
|
|
||||||
PowerClampMonthlyRepository,
|
|
||||||
} from '@app/common/modules/power-clamp/repositories';
|
|
||||||
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, InviteUserRepositoryModule, CommunityModule],
|
imports: [ConfigModule, InviteUserRepositoryModule, CommunityModule],
|
||||||
@ -116,7 +114,6 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
TimeZoneRepository,
|
TimeZoneRepository,
|
||||||
SpaceService,
|
SpaceService,
|
||||||
InviteSpaceRepository,
|
InviteSpaceRepository,
|
||||||
SpaceLinkService,
|
|
||||||
SubSpaceService,
|
SubSpaceService,
|
||||||
ValidationService,
|
ValidationService,
|
||||||
NewTagService,
|
NewTagService,
|
||||||
@ -124,7 +121,6 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
SpaceProductAllocationService,
|
SpaceProductAllocationService,
|
||||||
SpaceLinkRepository,
|
SpaceLinkRepository,
|
||||||
SubspaceRepository,
|
SubspaceRepository,
|
||||||
TagService,
|
|
||||||
SubspaceDeviceService,
|
SubspaceDeviceService,
|
||||||
SubspaceProductAllocationService,
|
SubspaceProductAllocationService,
|
||||||
SpaceModelRepository,
|
SpaceModelRepository,
|
||||||
@ -135,7 +131,6 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
SpaceModelProductAllocationService,
|
SpaceModelProductAllocationService,
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SubspaceProductAllocationRepository,
|
SubspaceProductAllocationRepository,
|
||||||
TagRepository,
|
|
||||||
SubspaceModelRepository,
|
SubspaceModelRepository,
|
||||||
SubspaceModelProductAllocationService,
|
SubspaceModelProductAllocationService,
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
@ -154,6 +149,7 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [InviteUserService],
|
exports: [InviteUserService],
|
||||||
})
|
})
|
||||||
|
@ -1,36 +1,42 @@
|
|||||||
import {
|
import { RoleType } from '@app/common/constants/role.type.enum';
|
||||||
Injectable,
|
|
||||||
HttpException,
|
|
||||||
HttpStatus,
|
|
||||||
BadRequestException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AddUserInvitationDto } from '../dtos';
|
|
||||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
||||||
import { UserStatusEnum } from '@app/common/constants/user-status.enum';
|
import { UserStatusEnum } from '@app/common/constants/user-status.enum';
|
||||||
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
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 { EntityManager, In, IsNull, Not, QueryRunner } from 'typeorm';
|
import { InviteUserEntity } from '@app/common/modules/Invite-user/entities';
|
||||||
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 { CheckEmailDto } from '../dtos/check-email.dto';
|
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
|
||||||
|
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 { SpaceRepository } from '@app/common/modules/space';
|
import {
|
||||||
import { ActivateCodeDto } from '../dtos/active-code.dto';
|
BadRequestException,
|
||||||
import { UserSpaceService } from 'src/users/services';
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { SpaceUserService } from 'src/space/services';
|
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 { CheckEmailDto } from '../dtos/check-email.dto';
|
||||||
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 {
|
||||||
@ -658,12 +664,12 @@ export class InviteUserService {
|
|||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.emailService.sendEmailWithTemplate(
|
await this.emailService.sendEmailWithTemplate({
|
||||||
userData.email,
|
email: userData.email,
|
||||||
userData.firstName,
|
name: userData.firstName,
|
||||||
disable,
|
isEnable: !disable,
|
||||||
false,
|
isDelete: false,
|
||||||
);
|
});
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
return new SuccessResponseDto({
|
return new SuccessResponseDto({
|
||||||
@ -797,12 +803,12 @@ export class InviteUserService {
|
|||||||
{ isActive: false },
|
{ isActive: false },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.emailService.sendEmailWithTemplate(
|
await this.emailService.sendEmailWithTemplate({
|
||||||
userData.email,
|
email: userData.email,
|
||||||
userData.firstName,
|
name: userData.firstName,
|
||||||
false,
|
isEnable: false,
|
||||||
true,
|
isDelete: true,
|
||||||
);
|
});
|
||||||
await queryRunner.commitTransaction();
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
return new SuccessResponseDto({
|
return new SuccessResponseDto({
|
||||||
|
22
src/main.ts
22
src/main.ts
@ -1,15 +1,14 @@
|
|||||||
|
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 { AppModule } from './app.module';
|
import { json, urlencoded } from 'body-parser';
|
||||||
import rateLimit from 'express-rate-limit';
|
import rateLimit from 'express-rate-limit';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.swagger.utils';
|
|
||||||
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 { Logger } from '@nestjs/common';
|
|
||||||
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
|
||||||
import { RequestContextMiddleware } from '@app/common/middleware/request-context.middleware';
|
import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.swagger.utils';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
@ -30,6 +29,13 @@ 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,
|
||||||
|
@ -1,66 +1,65 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
import { PowerClampService as PowerClamp } from './services/power-clamp.service';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
import { PowerClampController } from './controllers';
|
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
||||||
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
|
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||||
|
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
import {
|
import {
|
||||||
PowerClampDailyRepository,
|
PowerClampDailyRepository,
|
||||||
PowerClampHourlyRepository,
|
PowerClampHourlyRepository,
|
||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
} from '@app/common/modules/power-clamp/repositories';
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||||
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||||
import {
|
import {
|
||||||
SpaceDeviceService,
|
SceneIconRepository,
|
||||||
SpaceLinkService,
|
SceneRepository,
|
||||||
SpaceService,
|
} from '@app/common/modules/scene/repositories';
|
||||||
SubspaceDeviceService,
|
|
||||||
SubSpaceService,
|
|
||||||
ValidationService,
|
|
||||||
} from 'src/space/services';
|
|
||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
|
||||||
import { DeviceService } from 'src/device/services';
|
|
||||||
import {
|
import {
|
||||||
InviteSpaceRepository,
|
InviteSpaceRepository,
|
||||||
SpaceLinkRepository,
|
SpaceLinkRepository,
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
TagRepository,
|
|
||||||
} from '@app/common/modules/space';
|
} from '@app/common/modules/space';
|
||||||
import { CommunityService } from 'src/community/services';
|
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
|
||||||
import {
|
import {
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
SpaceModelRepository,
|
SpaceModelRepository,
|
||||||
SubspaceModelProductAllocationRepoitory,
|
SubspaceModelProductAllocationRepoitory,
|
||||||
SubspaceModelRepository,
|
SubspaceModelRepository,
|
||||||
} from '@app/common/modules/space-model';
|
} from '@app/common/modules/space-model';
|
||||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
|
||||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
|
||||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
|
||||||
import { SceneService } from 'src/scene/services';
|
|
||||||
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
|
||||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
|
||||||
import {
|
|
||||||
SceneIconRepository,
|
|
||||||
SceneRepository,
|
|
||||||
} from '@app/common/modules/scene/repositories';
|
|
||||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
|
||||||
import { TagService } from 'src/tags/services';
|
|
||||||
import {
|
|
||||||
SpaceModelService,
|
|
||||||
SubSpaceModelService,
|
|
||||||
} from 'src/space-model/services';
|
|
||||||
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
|
||||||
import {
|
import {
|
||||||
SubspaceProductAllocationRepository,
|
SubspaceProductAllocationRepository,
|
||||||
SubspaceRepository,
|
SubspaceRepository,
|
||||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||||
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
|
||||||
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { CommunityService } from 'src/community/services';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
|
import { SceneService } from 'src/scene/services';
|
||||||
|
import {
|
||||||
|
SpaceModelService,
|
||||||
|
SubSpaceModelService,
|
||||||
|
} from 'src/space-model/services';
|
||||||
import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service';
|
import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service';
|
||||||
import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service';
|
import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import {
|
||||||
|
SpaceDeviceService,
|
||||||
|
SpaceService,
|
||||||
|
SubspaceDeviceService,
|
||||||
|
SubSpaceService,
|
||||||
|
ValidationService,
|
||||||
|
} from 'src/space/services';
|
||||||
|
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
||||||
|
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
||||||
|
import { TagService } from 'src/tags/services';
|
||||||
|
import { PowerClampController } from './controllers';
|
||||||
|
import { PowerClampService as PowerClamp } from './services/power-clamp.service';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
controllers: [PowerClampController],
|
controllers: [PowerClampController],
|
||||||
@ -90,7 +89,6 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
SceneRepository,
|
SceneRepository,
|
||||||
AutomationRepository,
|
AutomationRepository,
|
||||||
InviteSpaceRepository,
|
InviteSpaceRepository,
|
||||||
SpaceLinkService,
|
|
||||||
SubSpaceService,
|
SubSpaceService,
|
||||||
TagService,
|
TagService,
|
||||||
SpaceModelService,
|
SpaceModelService,
|
||||||
@ -105,12 +103,12 @@ import { OccupancyService } from '@app/common/helper/services/occupancy.service'
|
|||||||
SpaceModelProductAllocationService,
|
SpaceModelProductAllocationService,
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SubspaceProductAllocationRepository,
|
SubspaceProductAllocationRepository,
|
||||||
TagRepository,
|
|
||||||
SubspaceModelRepository,
|
SubspaceModelRepository,
|
||||||
SubspaceModelProductAllocationService,
|
SubspaceModelProductAllocationService,
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
SubspaceModelProductAllocationRepoitory,
|
SubspaceModelProductAllocationRepoitory,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [PowerClamp],
|
exports: [PowerClamp],
|
||||||
})
|
})
|
||||||
|
@ -1,73 +1,72 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { AqiDataService } from '@app/common/helper/services/aqi.data.service';
|
||||||
import { ProjectController } from './controllers';
|
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
||||||
import { ProjectService } from './services';
|
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
||||||
|
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
||||||
|
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
||||||
|
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
||||||
|
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
||||||
|
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories';
|
||||||
|
import {
|
||||||
|
PowerClampDailyRepository,
|
||||||
|
PowerClampHourlyRepository,
|
||||||
|
PowerClampMonthlyRepository,
|
||||||
|
} from '@app/common/modules/power-clamp/repositories';
|
||||||
|
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
import { CreateOrphanSpaceHandler } from './handler';
|
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
||||||
|
import {
|
||||||
|
SceneIconRepository,
|
||||||
|
SceneRepository,
|
||||||
|
} from '@app/common/modules/scene/repositories';
|
||||||
import {
|
import {
|
||||||
InviteSpaceRepository,
|
InviteSpaceRepository,
|
||||||
SpaceLinkRepository,
|
SpaceLinkRepository,
|
||||||
SpaceProductAllocationRepository,
|
SpaceProductAllocationRepository,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
TagRepository,
|
|
||||||
} from '@app/common/modules/space';
|
} from '@app/common/modules/space';
|
||||||
import { CommunityRepository } from '@app/common/modules/community/repositories';
|
|
||||||
import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories';
|
|
||||||
import { ProjectUserController } from './controllers/project-user.controller';
|
|
||||||
import { ProjectUserService } from './services/project-user.service';
|
|
||||||
import {
|
|
||||||
UserRepository,
|
|
||||||
UserSpaceRepository,
|
|
||||||
} from '@app/common/modules/user/repositories';
|
|
||||||
import {
|
|
||||||
SpaceLinkService,
|
|
||||||
SpaceService,
|
|
||||||
SubspaceDeviceService,
|
|
||||||
SubSpaceService,
|
|
||||||
ValidationService,
|
|
||||||
} from 'src/space/services';
|
|
||||||
import { TagService } from 'src/tags/services';
|
|
||||||
import {
|
|
||||||
SpaceModelService,
|
|
||||||
SubSpaceModelService,
|
|
||||||
} from 'src/space-model/services';
|
|
||||||
import { DeviceService } from 'src/device/services';
|
|
||||||
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
|
||||||
import {
|
|
||||||
SubspaceProductAllocationRepository,
|
|
||||||
SubspaceRepository,
|
|
||||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
|
||||||
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
|
||||||
import { CommunityService } from 'src/community/services';
|
|
||||||
import {
|
import {
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
SpaceModelRepository,
|
SpaceModelRepository,
|
||||||
SubspaceModelProductAllocationRepoitory,
|
SubspaceModelProductAllocationRepoitory,
|
||||||
SubspaceModelRepository,
|
SubspaceModelRepository,
|
||||||
} from '@app/common/modules/space-model';
|
} from '@app/common/modules/space-model';
|
||||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
import {
|
||||||
|
SubspaceProductAllocationRepository,
|
||||||
|
SubspaceRepository,
|
||||||
|
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||||
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
import { NewTagRepository } from '@app/common/modules/tag/repositories/tag-repository';
|
||||||
import { ProductRepository } from '@app/common/modules/product/repositories';
|
import {
|
||||||
import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service';
|
UserRepository,
|
||||||
import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories';
|
UserSpaceRepository,
|
||||||
import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service';
|
} from '@app/common/modules/user/repositories';
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { CommunityService } from 'src/community/services';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
import { SceneService } from 'src/scene/services';
|
import { SceneService } from 'src/scene/services';
|
||||||
import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service';
|
import {
|
||||||
|
SpaceModelService,
|
||||||
|
SubSpaceModelService,
|
||||||
|
} from 'src/space-model/services';
|
||||||
|
import { SpaceModelProductAllocationService } from 'src/space-model/services/space-model-product-allocation.service';
|
||||||
import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service';
|
import { SubspaceModelProductAllocationService } from 'src/space-model/services/subspace/subspace-model-product-allocation.service';
|
||||||
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
|
|
||||||
import {
|
import {
|
||||||
SceneIconRepository,
|
SpaceService,
|
||||||
SceneRepository,
|
SubspaceDeviceService,
|
||||||
} from '@app/common/modules/scene/repositories';
|
SubSpaceService,
|
||||||
import { AutomationRepository } from '@app/common/modules/automation/repositories';
|
ValidationService,
|
||||||
import { PowerClampService } from '@app/common/helper/services/power.clamp.service';
|
} from 'src/space/services';
|
||||||
import {
|
import { SpaceProductAllocationService } from 'src/space/services/space-product-allocation.service';
|
||||||
PowerClampDailyRepository,
|
import { SubspaceProductAllocationService } from 'src/space/services/subspace/subspace-product-allocation.service';
|
||||||
PowerClampHourlyRepository,
|
import { TagService } from 'src/tags/services';
|
||||||
PowerClampMonthlyRepository,
|
import { ProjectController } from './controllers';
|
||||||
} from '@app/common/modules/power-clamp/repositories';
|
import { ProjectUserController } from './controllers/project-user.controller';
|
||||||
import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
|
import { CreateOrphanSpaceHandler } from './handler';
|
||||||
import { OccupancyService } from '@app/common/helper/services/occupancy.service';
|
import { ProjectService } from './services';
|
||||||
|
import { ProjectUserService } from './services/project-user.service';
|
||||||
|
|
||||||
const CommandHandlers = [CreateOrphanSpaceHandler];
|
const CommandHandlers = [CreateOrphanSpaceHandler];
|
||||||
|
|
||||||
@ -87,7 +86,6 @@ const CommandHandlers = [CreateOrphanSpaceHandler];
|
|||||||
UserRepository,
|
UserRepository,
|
||||||
SpaceService,
|
SpaceService,
|
||||||
InviteSpaceRepository,
|
InviteSpaceRepository,
|
||||||
SpaceLinkService,
|
|
||||||
SubSpaceService,
|
SubSpaceService,
|
||||||
ValidationService,
|
ValidationService,
|
||||||
TagService,
|
TagService,
|
||||||
@ -111,7 +109,6 @@ const CommandHandlers = [CreateOrphanSpaceHandler];
|
|||||||
DeviceStatusFirebaseService,
|
DeviceStatusFirebaseService,
|
||||||
SceneService,
|
SceneService,
|
||||||
TuyaService,
|
TuyaService,
|
||||||
TagRepository,
|
|
||||||
SubspaceModelRepository,
|
SubspaceModelRepository,
|
||||||
SubspaceModelProductAllocationService,
|
SubspaceModelProductAllocationService,
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
@ -126,6 +123,7 @@ const CommandHandlers = [CreateOrphanSpaceHandler];
|
|||||||
PowerClampMonthlyRepository,
|
PowerClampMonthlyRepository,
|
||||||
SqlLoaderService,
|
SqlLoaderService,
|
||||||
OccupancyService,
|
OccupancyService,
|
||||||
|
AqiDataService,
|
||||||
],
|
],
|
||||||
exports: [ProjectService, CqrsModule],
|
exports: [ProjectService, CqrsModule],
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,17 @@
|
|||||||
|
import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant';
|
||||||
|
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
||||||
|
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
||||||
|
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
||||||
|
import {
|
||||||
|
TypeORMCustomModel,
|
||||||
|
TypeORMCustomModelFindAllQuery,
|
||||||
|
} from '@app/common/models/typeOrmCustom.model';
|
||||||
|
import { ProjectDto } from '@app/common/modules/project/dtos';
|
||||||
|
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||||
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
import { ProjectRepository } from '@app/common/modules/project/repositiories';
|
||||||
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||||
|
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||||
|
import { format } from '@fast-csv/format';
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
HttpException,
|
HttpException,
|
||||||
@ -6,24 +19,12 @@ import {
|
|||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { CreateProjectDto, GetProjectParam } from '../dto';
|
|
||||||
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
|
|
||||||
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
|
|
||||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
|
||||||
import {
|
|
||||||
TypeORMCustomModel,
|
|
||||||
TypeORMCustomModelFindAllQuery,
|
|
||||||
} from '@app/common/models/typeOrmCustom.model';
|
|
||||||
import { ProjectDto } from '@app/common/modules/project/dtos';
|
|
||||||
import { PageResponse } from '@app/common/dto/pagination.response.dto';
|
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command';
|
|
||||||
import { UserRepository } from '@app/common/modules/user/repositories';
|
|
||||||
import { format } from '@fast-csv/format';
|
|
||||||
import { PassThrough } from 'stream';
|
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
|
||||||
import { SpaceService } from 'src/space/services';
|
import { SpaceService } from 'src/space/services';
|
||||||
import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant';
|
import { PassThrough } from 'stream';
|
||||||
|
import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command';
|
||||||
|
import { CreateProjectDto, GetProjectParam } from '../dto';
|
||||||
|
import { QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectService {
|
export class ProjectService {
|
||||||
@ -212,8 +213,14 @@ export class ProjectService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(uuid: string): Promise<ProjectEntity> {
|
async findOne(
|
||||||
const project = await this.projectRepository.findOne({ where: { uuid } });
|
uuid: string,
|
||||||
|
queryRunner?: QueryRunner,
|
||||||
|
): Promise<ProjectEntity> {
|
||||||
|
const projectRepository = queryRunner
|
||||||
|
? queryRunner.manager.getRepository(ProjectEntity)
|
||||||
|
: this.projectRepository;
|
||||||
|
const project = await projectRepository.findOne({ where: { uuid } });
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
`Invalid project with uuid ${uuid}`,
|
`Invalid project with uuid ${uuid}`,
|
||||||
@ -236,11 +243,11 @@ export class ProjectService {
|
|||||||
'communities.spaces.parent',
|
'communities.spaces.parent',
|
||||||
'communities.spaces.productAllocations',
|
'communities.spaces.productAllocations',
|
||||||
'communities.spaces.productAllocations.product',
|
'communities.spaces.productAllocations.product',
|
||||||
'communities.spaces.productAllocations.tags',
|
'communities.spaces.productAllocations.tag',
|
||||||
'communities.spaces.subspaces',
|
'communities.spaces.subspaces',
|
||||||
'communities.spaces.subspaces.productAllocations',
|
'communities.spaces.subspaces.productAllocations',
|
||||||
'communities.spaces.subspaces.productAllocations.product',
|
'communities.spaces.subspaces.productAllocations.product',
|
||||||
'communities.spaces.subspaces.productAllocations.tags',
|
'communities.spaces.subspaces.productAllocations.tag',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,14 +310,13 @@ export class ProjectService {
|
|||||||
if (subspace.disabled) continue;
|
if (subspace.disabled) continue;
|
||||||
|
|
||||||
for (const productAllocation of subspace.productAllocations || []) {
|
for (const productAllocation of subspace.productAllocations || []) {
|
||||||
for (const tag of productAllocation.tags || []) {
|
|
||||||
csvStream.write({
|
csvStream.write({
|
||||||
'Device ID': '',
|
'Device ID': '',
|
||||||
'Community Name': space.community?.name || '',
|
'Community Name': space.community?.name || '',
|
||||||
'Space Name': space.spaceName,
|
'Space Name': space.spaceName,
|
||||||
'Space Location': spaceLocation,
|
'Space Location': spaceLocation,
|
||||||
'Subspace Name': subspace.subspaceName || '',
|
'Subspace Name': subspace.subspaceName || '',
|
||||||
Tag: tag.name,
|
Tag: productAllocation.tag.name,
|
||||||
'Product Name': productAllocation.product.name || '',
|
'Product Name': productAllocation.product.name || '',
|
||||||
'Community UUID': space.community?.uuid || '',
|
'Community UUID': space.community?.uuid || '',
|
||||||
'Space UUID': space.uuid,
|
'Space UUID': space.uuid,
|
||||||
@ -318,17 +324,15 @@ export class ProjectService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const productAllocation of space.productAllocations || []) {
|
for (const productAllocation of space.productAllocations || []) {
|
||||||
for (const tag of productAllocation.tags || []) {
|
|
||||||
csvStream.write({
|
csvStream.write({
|
||||||
'Device ID': '',
|
'Device ID': '',
|
||||||
'Community Name': space.community?.name || '',
|
'Community Name': space.community?.name || '',
|
||||||
'Space Name': space.spaceName,
|
'Space Name': space.spaceName,
|
||||||
'Space Location': spaceLocation,
|
'Space Location': spaceLocation,
|
||||||
'Subspace Name': '',
|
'Subspace Name': '',
|
||||||
Tag: tag.name,
|
Tag: productAllocation.tag.name,
|
||||||
'Product Name': productAllocation.product.name || '',
|
'Product Name': productAllocation.product.name || '',
|
||||||
'Community UUID': space.community?.uuid || '',
|
'Community UUID': space.community?.uuid || '',
|
||||||
'Space UUID': space.uuid,
|
'Space UUID': space.uuid,
|
||||||
@ -336,19 +340,8 @@ export class ProjectService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
csvStream.end();
|
csvStream.end();
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSpaceLocation(space: SpaceEntity): string {
|
|
||||||
const names = [];
|
|
||||||
let current = space.parent;
|
|
||||||
while (current) {
|
|
||||||
names.unshift(current.spaceName);
|
|
||||||
current = current.parent;
|
|
||||||
}
|
|
||||||
return names.join(' > ');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
export * from './create-space-model.dto';
|
export * from './create-space-model.dto';
|
||||||
|
export * from './link-space-model.dto';
|
||||||
export * from './project-param.dto';
|
export * from './project-param.dto';
|
||||||
export * from './update-space-model.dto';
|
|
||||||
export * from './space-model-param';
|
export * from './space-model-param';
|
||||||
export * from './subspaces-model-dtos';
|
export * from './subspaces-model-dtos';
|
||||||
export * from './tag-model-dtos';
|
export * from './update-space-model.dto';
|
||||||
export * from './link-space-model.dto';
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsString,
|
|
||||||
IsOptional,
|
|
||||||
IsArray,
|
IsArray,
|
||||||
ValidateNested,
|
|
||||||
IsEnum,
|
IsEnum,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { ModifyTagModelDto } from '../tag-model-dtos';
|
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
||||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
||||||
|
|
||||||
export class ModifySubspaceModelDto {
|
export class ModifySubspaceModelDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@ -37,11 +37,11 @@ export class ModifySubspaceModelDto {
|
|||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description:
|
description:
|
||||||
'List of tag modifications (add/update/delete) for the subspace',
|
'List of tag modifications (add/update/delete) for the subspace',
|
||||||
type: [ModifyTagModelDto],
|
type: [ModifyTagDto],
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ModifyTagModelDto)
|
@Type(() => ModifyTagDto)
|
||||||
tags?: ModifyTagModelDto[];
|
tags?: ModifyTagDto[];
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateTagModelDto {
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'Tag associated with the space or subspace models',
|
|
||||||
example: 'Temperature Control',
|
|
||||||
})
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
tag: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'UUID of the tag model (required for update/delete)',
|
|
||||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
uuid?: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'ID of the product associated with the tag',
|
|
||||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
|
||||||
})
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
productUuid: string;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export * from './create-tag-model.dto';
|
|
||||||
export * from './update-tag-model.dto';
|
|
||||||
export * from './modify-tag-model.dto';
|
|
@ -1,46 +0,0 @@
|
|||||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
import { IsString, IsOptional, IsEnum, IsUUID } from 'class-validator';
|
|
||||||
|
|
||||||
export class ModifyTagModelDto {
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'Action to perform: add, update, or delete',
|
|
||||||
example: ModifyAction.ADD,
|
|
||||||
})
|
|
||||||
@IsEnum(ModifyAction)
|
|
||||||
action: ModifyAction;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'UUID of the new tag',
|
|
||||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
newTagUuid: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description:
|
|
||||||
'UUID of an existing tag (required for update/delete, optional for add)',
|
|
||||||
example: 'a1b2c3d4-5678-90ef-abcd-1234567890ef',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
tagUuid?: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description: 'Name of the tag (required for add/update)',
|
|
||||||
example: 'Temperature Sensor',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
description:
|
|
||||||
'UUID of the product associated with the tag (required for add)',
|
|
||||||
example: 'c789a91e-549a-4753-9006-02f89e8170e0',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsUUID()
|
|
||||||
productUuid?: string;
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
|
||||||
|
|
||||||
export class UpdateTagModelDto {
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'UUID of the tag to be updated',
|
|
||||||
example: '123e4567-e89b-12d3-a456-426614174000',
|
|
||||||
})
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsUUID()
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'Updated name of the tag',
|
|
||||||
example: 'Updated Tag Name',
|
|
||||||
required: false,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
tag?: string;
|
|
||||||
}
|
|
@ -1,48 +1,13 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator';
|
|
||||||
import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto';
|
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||||
import {
|
import {
|
||||||
DeleteSubspaceModelDto,
|
DeleteSubspaceModelDto,
|
||||||
ModifySubspaceModelDto,
|
ModifySubspaceModelDto,
|
||||||
UpdateSubspaceModelDto,
|
UpdateSubspaceModelDto,
|
||||||
} from './subspaces-model-dtos';
|
} from './subspaces-model-dtos';
|
||||||
import { ModifyTagModelDto } from './tag-model-dtos';
|
import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto';
|
||||||
|
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
||||||
export class ModifySubspacesModelDto {
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'List of subspaces to add',
|
|
||||||
type: [CreateSubspaceModelDto],
|
|
||||||
required: false,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => CreateSubspaceModelDto)
|
|
||||||
add?: CreateSubspaceModelDto[];
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'List of subspaces to add',
|
|
||||||
type: [CreateSubspaceModelDto],
|
|
||||||
required: false,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => UpdateSubspaceModelDto)
|
|
||||||
update?: UpdateSubspaceModelDto[];
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'List of subspaces to delete',
|
|
||||||
type: [DeleteSubspaceModelDto],
|
|
||||||
required: false,
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => DeleteSubspaceModelDto)
|
|
||||||
delete?: DeleteSubspaceModelDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateSpaceModelDto {
|
export class UpdateSpaceModelDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@ -66,11 +31,11 @@ export class UpdateSpaceModelDto {
|
|||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description:
|
description:
|
||||||
'List of tag modifications (add/update/delete) for the space model',
|
'List of tag modifications (add/update/delete) for the space model',
|
||||||
type: [ModifyTagModelDto],
|
type: [ModifyTagDto],
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => ModifyTagModelDto)
|
@Type(() => ModifyTagDto)
|
||||||
tags?: ModifyTagModelDto[];
|
tags?: ModifyTagDto[];
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { PropogateUpdateSpaceModelProductAllocationCommand } from '../commands';
|
|
||||||
import { SpaceProductAllocationRepository } from '@app/common/modules/space';
|
import { SpaceProductAllocationRepository } from '@app/common/modules/space';
|
||||||
import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model';
|
import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model';
|
||||||
import {
|
import {
|
||||||
SubspaceRepository,
|
|
||||||
SubspaceProductAllocationRepository,
|
SubspaceProductAllocationRepository,
|
||||||
|
SubspaceRepository,
|
||||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { PropogateUpdateSpaceModelProductAllocationCommand } from '../commands';
|
||||||
|
|
||||||
@CommandHandler(PropogateUpdateSpaceModelProductAllocationCommand)
|
@CommandHandler(PropogateUpdateSpaceModelProductAllocationCommand)
|
||||||
export class PropogateUpdateSpaceModelProductAllocationHandler
|
export class PropogateUpdateSpaceModelProductAllocationHandler
|
||||||
@ -31,89 +31,89 @@ export class PropogateUpdateSpaceModelProductAllocationHandler
|
|||||||
|
|
||||||
console.log(`Processing ${updatedAllocations.length} allocations...`);
|
console.log(`Processing ${updatedAllocations.length} allocations...`);
|
||||||
|
|
||||||
for (const allocation of updatedAllocations) {
|
// for (const allocation of updatedAllocations) {
|
||||||
try {
|
// try {
|
||||||
if (allocation.allocation) {
|
// if (allocation.allocation) {
|
||||||
const spaceAllocations = await this.spaceProductRepository.find({
|
// const spaceAllocations = await this.spaceProductRepository.find({
|
||||||
where: { uuid: allocation.allocation.uuid },
|
// where: { uuid: allocation.allocation.uuid },
|
||||||
relations: ['tags'],
|
// relations: ['tags'],
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!spaceAllocations || spaceAllocations.length === 0) {
|
// if (!spaceAllocations || spaceAllocations.length === 0) {
|
||||||
console.warn(
|
// console.warn(
|
||||||
`No space allocations found for UUID: ${allocation.allocation.uuid}`,
|
// `No space allocations found for UUID: ${allocation.allocation.uuid}`,
|
||||||
);
|
// );
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (allocation.tagsAdded?.length) {
|
// if (allocation.tagsAdded?.length) {
|
||||||
for (const spaceAllocation of spaceAllocations) {
|
// for (const spaceAllocation of spaceAllocations) {
|
||||||
spaceAllocation.tags.push(...allocation.tagsAdded);
|
// spaceAllocation.tags.push(...allocation.tagsAdded);
|
||||||
}
|
// }
|
||||||
await this.spaceProductRepository.save(spaceAllocations);
|
// await this.spaceProductRepository.save(spaceAllocations);
|
||||||
console.log(
|
// console.log(
|
||||||
`Added tags to ${spaceAllocations.length} space allocations.`,
|
// `Added tags to ${spaceAllocations.length} space allocations.`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (allocation.tagsRemoved?.length) {
|
// if (allocation.tagsRemoved?.length) {
|
||||||
const tagsToRemoveUUIDs = new Set(
|
// const tagsToRemoveUUIDs = new Set(
|
||||||
allocation.tagsRemoved.map((tag) => tag.uuid),
|
// allocation.tagsRemoved.map((tag) => tag.uuid),
|
||||||
);
|
// );
|
||||||
|
|
||||||
for (const spaceAllocation of spaceAllocations) {
|
// for (const spaceAllocation of spaceAllocations) {
|
||||||
spaceAllocation.tags = spaceAllocation.tags.filter(
|
// spaceAllocation.tags = spaceAllocation.tags.filter(
|
||||||
(tag) => !tagsToRemoveUUIDs.has(tag.uuid),
|
// (tag) => !tagsToRemoveUUIDs.has(tag.uuid),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
await this.spaceProductRepository.save(spaceAllocations);
|
// await this.spaceProductRepository.save(spaceAllocations);
|
||||||
console.log(
|
// console.log(
|
||||||
`Removed tags from ${spaceAllocations.length} space allocations.`,
|
// `Removed tags from ${spaceAllocations.length} space allocations.`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (allocation.deletedAllocation) {
|
// if (allocation.deletedAllocation) {
|
||||||
const spaceAllocations = await this.spaceProductRepository.find({
|
// const spaceAllocations = await this.spaceProductRepository.find({
|
||||||
where: { uuid: allocation.deletedAllocation.uuid },
|
// where: { uuid: allocation.deletedAllocation.uuid },
|
||||||
relations: ['tags'],
|
// relations: ['tags'],
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!spaceAllocations || spaceAllocations.length === 0) {
|
// if (!spaceAllocations || spaceAllocations.length === 0) {
|
||||||
console.warn(
|
// console.warn(
|
||||||
`No space allocations found to delete for UUID: ${allocation.deletedAllocation.uuid}`,
|
// `No space allocations found to delete for UUID: ${allocation.deletedAllocation.uuid}`,
|
||||||
);
|
// );
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
await this.spaceProductRepository.remove(spaceAllocations);
|
// await this.spaceProductRepository.remove(spaceAllocations);
|
||||||
console.log(
|
// console.log(
|
||||||
`Deleted ${spaceAllocations.length} space allocations.`,
|
// `Deleted ${spaceAllocations.length} space allocations.`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (allocation.newAllocation) {
|
// if (allocation.newAllocation) {
|
||||||
const newAllocations = spaces.map((space) =>
|
// const newAllocations = spaces.map((space) =>
|
||||||
this.spaceProductRepository.create({
|
// this.spaceProductRepository.create({
|
||||||
space,
|
// space,
|
||||||
product: allocation.newAllocation.product,
|
// product: allocation.newAllocation.product,
|
||||||
tags: allocation.newAllocation.tags,
|
// tag: allocation.newAllocation.tag,
|
||||||
inheritedFromModel: allocation.newAllocation,
|
// inheritedFromModel: allocation.newAllocation,
|
||||||
}),
|
// }),
|
||||||
);
|
// );
|
||||||
|
|
||||||
await this.spaceProductRepository.save(newAllocations);
|
// await this.spaceProductRepository.save(newAllocations);
|
||||||
console.log(
|
// console.log(
|
||||||
`Created ${newAllocations.length} new space allocations.`,
|
// `Created ${newAllocations.length} new space allocations.`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error(
|
// console.error(
|
||||||
`Error processing allocation update: ${JSON.stringify(allocation)}`,
|
// `Error processing allocation update: ${JSON.stringify(allocation)}`,
|
||||||
error,
|
// error,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
console.log('Finished processing all allocations.');
|
console.log('Finished processing all allocations.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||||
import { PropogateUpdateSpaceModelCommand } from '../commands';
|
|
||||||
import { SpaceProductAllocationRepository } from '@app/common/modules/space';
|
import { SpaceProductAllocationRepository } from '@app/common/modules/space';
|
||||||
|
import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model';
|
||||||
|
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
||||||
import {
|
import {
|
||||||
SubspaceProductAllocationRepository,
|
SubspaceProductAllocationRepository,
|
||||||
SubspaceRepository,
|
SubspaceRepository,
|
||||||
} from '@app/common/modules/space/repositories/subspace.repository';
|
} from '@app/common/modules/space/repositories/subspace.repository';
|
||||||
import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model';
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { In } from 'typeorm';
|
import { PropogateUpdateSpaceModelCommand } from '../commands';
|
||||||
import { ISingleSubspaceModel } from '../interfaces';
|
import { ISingleSubspaceModel } from '../interfaces';
|
||||||
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
|
|
||||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
||||||
import { IUpdatedAllocations } from '../interfaces/subspace-product-allocation-update-result.interface';
|
import { IUpdatedAllocations } from '../interfaces/subspace-product-allocation-update-result.interface';
|
||||||
|
|
||||||
@CommandHandler(PropogateUpdateSpaceModelCommand)
|
@CommandHandler(PropogateUpdateSpaceModelCommand)
|
||||||
@ -72,48 +71,48 @@ export class PropogateUpdateSpaceModelHandler
|
|||||||
for (const allocation of allocations) {
|
for (const allocation of allocations) {
|
||||||
if (!allocation) continue;
|
if (!allocation) continue;
|
||||||
|
|
||||||
if (allocation.allocation) {
|
// if (allocation.allocation) {
|
||||||
try {
|
// try {
|
||||||
const subspaceAllocations =
|
// const subspaceAllocations =
|
||||||
await this.subspaceProductRepository.find({
|
// await this.subspaceProductRepository.find({
|
||||||
where: {
|
// where: {
|
||||||
inheritedFromModel: { uuid: allocation.allocation.uuid },
|
// inheritedFromModel: { uuid: allocation.allocation.uuid },
|
||||||
},
|
// },
|
||||||
relations: ['tags'],
|
// relations: ['tags'],
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!subspaceAllocations || subspaceAllocations.length === 0)
|
// if (!subspaceAllocations || subspaceAllocations.length === 0)
|
||||||
continue;
|
// continue;
|
||||||
|
|
||||||
if (allocation.tagsAdded?.length) {
|
// if (allocation.tagsAdded?.length) {
|
||||||
for (const subspaceAllocation of subspaceAllocations) {
|
// for (const subspaceAllocation of subspaceAllocations) {
|
||||||
subspaceAllocation.tags.push(...allocation.tagsAdded);
|
// subspaceAllocation.tags.push(...allocation.tagsAdded);
|
||||||
}
|
// }
|
||||||
await this.subspaceProductRepository.save(subspaceAllocations);
|
// await this.subspaceProductRepository.save(subspaceAllocations);
|
||||||
console.log(
|
// console.log(
|
||||||
`Added tags to ${subspaceAllocations.length} subspace allocations.`,
|
// `Added tags to ${subspaceAllocations.length} subspace allocations.`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (allocation.tagsRemoved?.length) {
|
// if (allocation.tagsRemoved?.length) {
|
||||||
const tagsToRemoveUUIDs = allocation.tagsRemoved.map(
|
// const tagsToRemoveUUIDs = allocation.tagsRemoved.map(
|
||||||
(tag) => tag.uuid,
|
// (tag) => tag.uuid,
|
||||||
);
|
// );
|
||||||
|
|
||||||
for (const subspaceAllocation of subspaceAllocations) {
|
// for (const subspaceAllocation of subspaceAllocations) {
|
||||||
subspaceAllocation.tags = subspaceAllocation.tags.filter(
|
// subspaceAllocation.tags = subspaceAllocation.tags.filter(
|
||||||
(tag) => !tagsToRemoveUUIDs.includes(tag.uuid),
|
// (tag) => !tagsToRemoveUUIDs.includes(tag.uuid),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
await this.subspaceProductRepository.save(subspaceAllocations);
|
// await this.subspaceProductRepository.save(subspaceAllocations);
|
||||||
console.log(
|
// console.log(
|
||||||
`Removed tags from ${subspaceAllocations.length} subspace allocations.`,
|
// `Removed tags from ${subspaceAllocations.length} subspace allocations.`,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('Error processing allocation update:', error);
|
// console.error('Error processing allocation update:', error);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (allocation.newAllocation) {
|
if (allocation.newAllocation) {
|
||||||
try {
|
try {
|
||||||
@ -127,7 +126,7 @@ export class PropogateUpdateSpaceModelHandler
|
|||||||
const newAllocations = subspaces.map((subspace) =>
|
const newAllocations = subspaces.map((subspace) =>
|
||||||
this.subspaceProductRepository.create({
|
this.subspaceProductRepository.create({
|
||||||
product: allocation.newAllocation.product,
|
product: allocation.newAllocation.product,
|
||||||
tags: allocation.newAllocation.tags,
|
tag: allocation.newAllocation.tag,
|
||||||
subspace,
|
subspace,
|
||||||
inheritedFromModel: allocation.newAllocation,
|
inheritedFromModel: allocation.newAllocation,
|
||||||
}),
|
}),
|
||||||
@ -198,7 +197,7 @@ export class PropogateUpdateSpaceModelHandler
|
|||||||
const subspaceAllocation = this.subspaceProductRepository.create({
|
const subspaceAllocation = this.subspaceProductRepository.create({
|
||||||
subspace: subspace,
|
subspace: subspace,
|
||||||
product: allocation.product,
|
product: allocation.product,
|
||||||
tags: allocation.tags,
|
tag: allocation.tag,
|
||||||
inheritedFromModel: allocation,
|
inheritedFromModel: allocation,
|
||||||
});
|
});
|
||||||
await this.subspaceProductRepository.save(subspaceAllocation);
|
await this.subspaceProductRepository.save(subspaceAllocation);
|
||||||
@ -211,67 +210,59 @@ export class PropogateUpdateSpaceModelHandler
|
|||||||
subspaceModel: ISingleSubspaceModel,
|
subspaceModel: ISingleSubspaceModel,
|
||||||
spaces: SpaceEntity[],
|
spaces: SpaceEntity[],
|
||||||
) {
|
) {
|
||||||
const subspaces = await this.subspaceRepository.find({
|
// const subspaces = await this.subspaceRepository.find({
|
||||||
where: {
|
// where: {
|
||||||
subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
|
// subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
|
||||||
disabled: false,
|
// disabled: false,
|
||||||
},
|
// },
|
||||||
relations: [
|
// relations: [
|
||||||
'productAllocations',
|
// 'productAllocations',
|
||||||
'productAllocations.product',
|
// 'productAllocations.product',
|
||||||
'productAllocations.tags',
|
// 'productAllocations.tags',
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
|
// if (!subspaces.length) {
|
||||||
if (!subspaces.length) {
|
// return;
|
||||||
return;
|
// }
|
||||||
}
|
// const allocationUuidsToRemove = subspaces.flatMap((subspace) =>
|
||||||
|
// subspace.productAllocations.map((allocation) => allocation.uuid),
|
||||||
const allocationUuidsToRemove = subspaces.flatMap((subspace) =>
|
// );
|
||||||
subspace.productAllocations.map((allocation) => allocation.uuid),
|
// if (allocationUuidsToRemove.length) {
|
||||||
);
|
// await this.subspaceProductRepository.delete(allocationUuidsToRemove);
|
||||||
|
// }
|
||||||
if (allocationUuidsToRemove.length) {
|
// await this.subspaceRepository.update(
|
||||||
await this.subspaceProductRepository.delete(allocationUuidsToRemove);
|
// { uuid: In(subspaces.map((s) => s.uuid)) },
|
||||||
}
|
// { disabled: true },
|
||||||
|
// );
|
||||||
await this.subspaceRepository.update(
|
// const relocatedAllocations = subspaceModel.relocatedAllocations || [];
|
||||||
{ uuid: In(subspaces.map((s) => s.uuid)) },
|
// if (!relocatedAllocations.length) {
|
||||||
{ disabled: true },
|
// return;
|
||||||
);
|
// }
|
||||||
|
// for (const space of spaces) {
|
||||||
const relocatedAllocations = subspaceModel.relocatedAllocations || [];
|
// for (const { allocation, tags = [] } of relocatedAllocations) {
|
||||||
|
// const spaceAllocation = await this.spaceProductRepository.findOne({
|
||||||
if (!relocatedAllocations.length) {
|
// where: {
|
||||||
return;
|
// inheritedFromModel: { uuid: allocation.uuid },
|
||||||
}
|
// space: { uuid: space.uuid },
|
||||||
|
// },
|
||||||
for (const space of spaces) {
|
// relations: ['tags'],
|
||||||
for (const { allocation, tags = [] } of relocatedAllocations) {
|
// });
|
||||||
const spaceAllocation = await this.spaceProductRepository.findOne({
|
// if (spaceAllocation) {
|
||||||
where: {
|
// if (tags.length) {
|
||||||
inheritedFromModel: { uuid: allocation.uuid },
|
// spaceAllocation.tags.push(...tags);
|
||||||
space: { uuid: space.uuid },
|
// await this.spaceProductRepository.save(spaceAllocation);
|
||||||
},
|
// }
|
||||||
relations: ['tags'],
|
// } else {
|
||||||
});
|
// const newSpaceAllocation = this.spaceProductRepository.create({
|
||||||
|
// space,
|
||||||
if (spaceAllocation) {
|
// inheritedFromModel: allocation,
|
||||||
if (tags.length) {
|
// tag: allocation.tag,
|
||||||
spaceAllocation.tags.push(...tags);
|
// product: allocation.product,
|
||||||
await this.spaceProductRepository.save(spaceAllocation);
|
// });
|
||||||
}
|
// await this.spaceProductRepository.save(newSpaceAllocation);
|
||||||
} else {
|
// }
|
||||||
const newSpaceAllocation = this.spaceProductRepository.create({
|
// }
|
||||||
space,
|
// }
|
||||||
inheritedFromModel: allocation,
|
|
||||||
tags: allocation.tags,
|
|
||||||
product: allocation.product,
|
|
||||||
});
|
|
||||||
await this.spaceProductRepository.save(newSpaceAllocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSubspaceModel(subspaceModel: ISingleSubspaceModel) {
|
async updateSubspaceModel(subspaceModel: ISingleSubspaceModel) {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export * from './update-subspace.interface';
|
|
||||||
export * from './modify-subspace.interface';
|
|
||||||
export * from './single-subspace.interface';
|
export * from './single-subspace.interface';
|
||||||
export * from './space-product-allocation.interface';
|
export * from './space-product-allocation.interface';
|
||||||
|
export * from './update-subspace.interface';
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { SubspaceModelEntity, TagModel } from '@app/common/modules/space-model';
|
|
||||||
|
|
||||||
export interface ModifyspaceModelPayload {
|
|
||||||
modifiedSubspaceModels?: ModifySubspaceModelPayload;
|
|
||||||
modifiedTags?: ModifiedTagsModelPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModifySubspaceModelPayload {
|
|
||||||
addedSubspaceModels?: SubspaceModelEntity[];
|
|
||||||
updatedSubspaceModels?: UpdatedSubspaceModelPayload[];
|
|
||||||
deletedSubspaceModels?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdatedSubspaceModelPayload {
|
|
||||||
subspaceName?: string;
|
|
||||||
modifiedTags?: ModifiedTagsModelPayload;
|
|
||||||
subspaceModelUuid: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModifiedTagsModelPayload {
|
|
||||||
added?: TagModel[];
|
|
||||||
updated?: TagModel[];
|
|
||||||
deleted?: string[];
|
|
||||||
}
|
|
@ -1,11 +1,11 @@
|
|||||||
|
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
||||||
import {
|
import {
|
||||||
SpaceModelProductAllocationEntity,
|
SpaceModelProductAllocationEntity,
|
||||||
SubspaceModelEntity,
|
SubspaceModelEntity,
|
||||||
} from '@app/common/modules/space-model';
|
} from '@app/common/modules/space-model';
|
||||||
import { ModifyTagModelDto } from '../dtos';
|
|
||||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
||||||
import { NewTagEntity } from '@app/common/modules/tag';
|
import { NewTagEntity } from '@app/common/modules/tag';
|
||||||
import { IUpdatedAllocations } from './subspace-product-allocation-update-result.interface';
|
import { IUpdatedAllocations } from './subspace-product-allocation-update-result.interface';
|
||||||
|
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
||||||
|
|
||||||
export interface IRelocatedAllocation {
|
export interface IRelocatedAllocation {
|
||||||
allocation: SpaceModelProductAllocationEntity;
|
allocation: SpaceModelProductAllocationEntity;
|
||||||
@ -14,7 +14,7 @@ export interface IRelocatedAllocation {
|
|||||||
export interface ISingleSubspaceModel {
|
export interface ISingleSubspaceModel {
|
||||||
subspaceModel: SubspaceModelEntity;
|
subspaceModel: SubspaceModelEntity;
|
||||||
action: ModifyAction;
|
action: ModifyAction;
|
||||||
tags?: ModifyTagModelDto[];
|
tags?: ModifyTagDto[];
|
||||||
relocatedAllocations?: IRelocatedAllocation[];
|
relocatedAllocations?: IRelocatedAllocation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import { In, QueryRunner } from 'typeorm';
|
|
||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { In, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
import { ProductEntity } from '@app/common/modules/product/entities';
|
||||||
|
import { ProjectEntity } from '@app/common/modules/project/entities';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space';
|
||||||
import {
|
import {
|
||||||
SpaceModelEntity,
|
SpaceModelEntity,
|
||||||
SpaceModelProductAllocationEntity,
|
SpaceModelProductAllocationEntity,
|
||||||
SpaceModelProductAllocationRepoitory,
|
SpaceModelProductAllocationRepoitory,
|
||||||
SubspaceModelProductAllocationEntity,
|
|
||||||
} from '@app/common/modules/space-model';
|
} from '@app/common/modules/space-model';
|
||||||
import { TagService as NewTagService } from 'src/tags/services';
|
|
||||||
import { ProcessTagDto } from 'src/tags/dtos';
|
|
||||||
import { ModifySubspaceModelDto, ModifyTagModelDto } from '../dtos';
|
|
||||||
import { ModifyAction } from '@app/common/constants/modify-action.enum';
|
|
||||||
import { NewTagEntity } from '@app/common/modules/tag';
|
import { NewTagEntity } from '@app/common/modules/tag';
|
||||||
import { ProductEntity } from '@app/common/modules/product/entities';
|
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
|
||||||
import { SpaceRepository } from '@app/common/modules/space';
|
import { ProcessTagDto } from 'src/tags/dtos';
|
||||||
import { ProjectEntity } from '@app/common/modules/project/entities';
|
import { TagService as NewTagService } from 'src/tags/services';
|
||||||
|
import { ModifySubspaceModelDto } from '../dtos';
|
||||||
import { IUpdatedSpaceAllocations } from '../interfaces';
|
import { IUpdatedSpaceAllocations } from '../interfaces';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -32,225 +31,220 @@ export class SpaceModelProductAllocationService {
|
|||||||
queryRunner?: QueryRunner,
|
queryRunner?: QueryRunner,
|
||||||
modifySubspaceModels?: ModifySubspaceModelDto[],
|
modifySubspaceModels?: ModifySubspaceModelDto[],
|
||||||
): Promise<IUpdatedSpaceAllocations[]> {
|
): Promise<IUpdatedSpaceAllocations[]> {
|
||||||
try {
|
// try {
|
||||||
if (!tags.length) return [];
|
if (!tags.length) return [];
|
||||||
|
|
||||||
const allocationUpdates: IUpdatedSpaceAllocations[] = [];
|
const allocationUpdates: IUpdatedSpaceAllocations[] = [];
|
||||||
|
|
||||||
const processedTags = await this.tagService.processTags(
|
|
||||||
tags,
|
|
||||||
projectUuid,
|
|
||||||
queryRunner,
|
|
||||||
);
|
|
||||||
|
|
||||||
const productAllocations: SpaceModelProductAllocationEntity[] = [];
|
|
||||||
const existingAllocations = new Map<
|
|
||||||
string,
|
|
||||||
SpaceModelProductAllocationEntity
|
|
||||||
>();
|
|
||||||
|
|
||||||
for (const tag of processedTags) {
|
|
||||||
let isTagNeeded = true;
|
|
||||||
|
|
||||||
if (modifySubspaceModels) {
|
|
||||||
const relatedSubspaces = await queryRunner.manager.find(
|
|
||||||
SubspaceModelProductAllocationEntity,
|
|
||||||
{
|
|
||||||
where: {
|
|
||||||
product: { uuid: tag.product.uuid },
|
|
||||||
subspaceModel: { spaceModel: { uuid: spaceModel.uuid } },
|
|
||||||
tags: { uuid: tag.uuid },
|
|
||||||
},
|
|
||||||
relations: ['subspaceModel', 'tags'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const subspaceWithTag of relatedSubspaces) {
|
|
||||||
const modifyingSubspace = modifySubspaceModels.find(
|
|
||||||
(subspace) =>
|
|
||||||
subspace.action === ModifyAction.UPDATE &&
|
|
||||||
subspace.uuid === subspaceWithTag.subspaceModel.uuid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
modifyingSubspace &&
|
|
||||||
modifyingSubspace.tags &&
|
|
||||||
modifyingSubspace.tags.some(
|
|
||||||
(subspaceTag) =>
|
|
||||||
subspaceTag.action === ModifyAction.DELETE &&
|
|
||||||
subspaceTag.tagUuid === tag.uuid,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
isTagNeeded = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTagNeeded) {
|
|
||||||
const hasTags = await this.validateTagWithinSpaceModel(
|
|
||||||
queryRunner,
|
|
||||||
tag,
|
|
||||||
spaceModel,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasTags) continue;
|
|
||||||
|
|
||||||
let allocation = existingAllocations.get(tag.product.uuid);
|
|
||||||
if (!allocation) {
|
|
||||||
allocation = await this.getAllocationByProduct(
|
|
||||||
tag.product,
|
|
||||||
spaceModel,
|
|
||||||
queryRunner,
|
|
||||||
);
|
|
||||||
if (allocation) {
|
|
||||||
existingAllocations.set(tag.product.uuid, allocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allocation) {
|
|
||||||
allocation = this.createNewAllocation(spaceModel, tag, queryRunner);
|
|
||||||
productAllocations.push(allocation);
|
|
||||||
allocationUpdates.push({
|
|
||||||
newAllocation: allocation,
|
|
||||||
});
|
|
||||||
} else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) {
|
|
||||||
allocation.tags.push(tag);
|
|
||||||
allocationUpdates.push({
|
|
||||||
allocation: allocation,
|
|
||||||
tagsAdded: [tag],
|
|
||||||
});
|
|
||||||
await this.saveAllocation(allocation, queryRunner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (productAllocations.length > 0) {
|
|
||||||
await this.saveAllocations(productAllocations, queryRunner);
|
|
||||||
}
|
|
||||||
|
|
||||||
return allocationUpdates;
|
return allocationUpdates;
|
||||||
} catch (error) {
|
// const processedTags = await this.tagService.processTags(
|
||||||
throw this.handleError(error, 'Failed to create product allocations');
|
// tags,
|
||||||
}
|
// projectUuid,
|
||||||
|
// queryRunner,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const productAllocations: SpaceModelProductAllocationEntity[] = [];
|
||||||
|
// const existingAllocations = new Map<
|
||||||
|
// string,
|
||||||
|
// SpaceModelProductAllocationEntity
|
||||||
|
// >();
|
||||||
|
|
||||||
|
// for (const tag of processedTags) {
|
||||||
|
// let isTagNeeded = true;
|
||||||
|
|
||||||
|
// if (modifySubspaceModels) {
|
||||||
|
// const relatedSubspaces = await queryRunner.manager.find(
|
||||||
|
// SubspaceModelProductAllocationEntity,
|
||||||
|
// {
|
||||||
|
// where: {
|
||||||
|
// product: { uuid: tag.product.uuid },
|
||||||
|
// subspaceModel: { spaceModel: { uuid: spaceModel.uuid } },
|
||||||
|
// tags: { uuid: tag.uuid },
|
||||||
|
// },
|
||||||
|
// relations: ['subspaceModel', 'tags'],
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// for (const subspaceWithTag of relatedSubspaces) {
|
||||||
|
// const modifyingSubspace = modifySubspaceModels.find(
|
||||||
|
// (subspace) =>
|
||||||
|
// subspace.action === ModifyAction.UPDATE &&
|
||||||
|
// subspace.uuid === subspaceWithTag.subspaceModel.uuid,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (
|
||||||
|
// modifyingSubspace &&
|
||||||
|
// modifyingSubspace.tags &&
|
||||||
|
// modifyingSubspace.tags.some(
|
||||||
|
// (subspaceTag) =>
|
||||||
|
// subspaceTag.action === ModifyAction.DELETE &&
|
||||||
|
// subspaceTag.tagUuid === tag.uuid,
|
||||||
|
// )
|
||||||
|
// ) {
|
||||||
|
// isTagNeeded = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (isTagNeeded) {
|
||||||
|
// const hasTags = await this.validateTagWithinSpaceModel(
|
||||||
|
// queryRunner,
|
||||||
|
// tag,
|
||||||
|
// spaceModel,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (hasTags) continue;
|
||||||
|
|
||||||
|
// let allocation = existingAllocations.get(tag.product.uuid);
|
||||||
|
// if (!allocation) {
|
||||||
|
// allocation = await this.getAllocationByProduct(
|
||||||
|
// tag.product,
|
||||||
|
// spaceModel,
|
||||||
|
// queryRunner,
|
||||||
|
// );
|
||||||
|
// if (allocation) {
|
||||||
|
// existingAllocations.set(tag.product.uuid, allocation);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!allocation) {
|
||||||
|
// allocation = this.createNewAllocation(spaceModel, tag, queryRunner);
|
||||||
|
// productAllocations.push(allocation);
|
||||||
|
// allocationUpdates.push({
|
||||||
|
// newAllocation: allocation,
|
||||||
|
// });
|
||||||
|
// } else if (!allocation.tags.some((t) => t.uuid === tag.uuid)) {
|
||||||
|
// allocation.tags.push(tag);
|
||||||
|
// allocationUpdates.push({
|
||||||
|
// allocation: allocation,
|
||||||
|
// tagsAdded: [tag],
|
||||||
|
// });
|
||||||
|
// await this.saveAllocation(allocation, queryRunner);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (productAllocations.length > 0) {
|
||||||
|
// await this.saveAllocations(productAllocations, queryRunner);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return allocationUpdates;
|
||||||
|
// } catch (error) {
|
||||||
|
// throw this.handleError(error, 'Failed to create product allocations');
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProductAllocations(
|
async updateProductAllocations(
|
||||||
dtos: ModifyTagModelDto[],
|
dtos: ModifyTagDto[],
|
||||||
project: ProjectEntity,
|
project: ProjectEntity,
|
||||||
spaceModel: SpaceModelEntity,
|
spaceModel: SpaceModelEntity,
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
modifySubspaceModels?: ModifySubspaceModelDto[],
|
modifySubspaceModels?: ModifySubspaceModelDto[],
|
||||||
): Promise<IUpdatedSpaceAllocations[]> {
|
): Promise<IUpdatedSpaceAllocations[]> {
|
||||||
try {
|
const allocationUpdates: IUpdatedSpaceAllocations[] = [];
|
||||||
const addDtos = dtos.filter((dto) => dto.action === ModifyAction.ADD);
|
return allocationUpdates;
|
||||||
const deleteDtos = dtos.filter(
|
// try {
|
||||||
(dto) => dto.action === ModifyAction.DELETE,
|
// const addDtos = dtos.filter((dto) => dto.action === ModifyAction.ADD);
|
||||||
);
|
// const deleteDtos = dtos.filter(
|
||||||
|
// (dto) => dto.action === ModifyAction.DELETE,
|
||||||
const addTagDtos: ProcessTagDto[] = addDtos.map((dto) => ({
|
// );
|
||||||
name: dto.name,
|
// const addTagDtos: ProcessTagDto[] = addDtos.map((dto) => ({
|
||||||
productUuid: dto.productUuid,
|
// name: dto.name,
|
||||||
uuid: dto.newTagUuid,
|
// productUuid: dto.productUuid,
|
||||||
}));
|
// uuid: dto.newTagUuid,
|
||||||
|
// }));
|
||||||
// Process added tags
|
// // Process added tags
|
||||||
const processedTags = await this.tagService.processTags(
|
// const processedTags = await this.tagService.processTags(
|
||||||
addTagDtos,
|
// addTagDtos,
|
||||||
project.uuid,
|
// project.uuid,
|
||||||
queryRunner,
|
// queryRunner,
|
||||||
);
|
// );
|
||||||
|
// const addTagUuidMap = new Map<string, ModifyTagModelDto>();
|
||||||
const addTagUuidMap = new Map<string, ModifyTagModelDto>();
|
// processedTags.forEach((tag, index) => {
|
||||||
processedTags.forEach((tag, index) => {
|
// addTagUuidMap.set(tag.uuid, addDtos[index]);
|
||||||
addTagUuidMap.set(tag.uuid, addDtos[index]);
|
// });
|
||||||
});
|
// const addTagUuids = new Set(processedTags.map((tag) => tag.uuid));
|
||||||
|
// const deleteTagUuids = new Set(deleteDtos.map((dto) => dto.tagUuid));
|
||||||
const addTagUuids = new Set(processedTags.map((tag) => tag.uuid));
|
// const tagsToIgnore = new Set(
|
||||||
const deleteTagUuids = new Set(deleteDtos.map((dto) => dto.tagUuid));
|
// [...addTagUuids].filter((uuid) => deleteTagUuids.has(uuid)),
|
||||||
|
// );
|
||||||
const tagsToIgnore = new Set(
|
// // Filter out tags that are added and deleted in the same request
|
||||||
[...addTagUuids].filter((uuid) => deleteTagUuids.has(uuid)),
|
// const filteredDtos = dtos.filter(
|
||||||
);
|
// (dto) =>
|
||||||
|
// !(
|
||||||
// Filter out tags that are added and deleted in the same request
|
// tagsToIgnore.has(dto.tagUuid) ||
|
||||||
const filteredDtos = dtos.filter(
|
// (dto.action === ModifyAction.ADD &&
|
||||||
(dto) =>
|
// tagsToIgnore.has(
|
||||||
!(
|
// [...addTagUuidMap.keys()].find(
|
||||||
tagsToIgnore.has(dto.tagUuid) ||
|
// (uuid) => addTagUuidMap.get(uuid) === dto,
|
||||||
(dto.action === ModifyAction.ADD &&
|
// ),
|
||||||
tagsToIgnore.has(
|
// ))
|
||||||
[...addTagUuidMap.keys()].find(
|
// ),
|
||||||
(uuid) => addTagUuidMap.get(uuid) === dto,
|
// );
|
||||||
),
|
// // Process add and delete actions concurrently
|
||||||
))
|
// const [updatedAllocations, deletedAllocations] = await Promise.all([
|
||||||
),
|
// this.processAddActions(
|
||||||
);
|
// filteredDtos,
|
||||||
|
// project.uuid,
|
||||||
// Process add and delete actions concurrently
|
// spaceModel,
|
||||||
const [updatedAllocations, deletedAllocations] = await Promise.all([
|
// queryRunner,
|
||||||
this.processAddActions(
|
// modifySubspaceModels,
|
||||||
filteredDtos,
|
// ),
|
||||||
project.uuid,
|
// this.processDeleteActions(filteredDtos, queryRunner, spaceModel),
|
||||||
spaceModel,
|
// ]);
|
||||||
queryRunner,
|
// // Combine results and return
|
||||||
modifySubspaceModels,
|
// return [...updatedAllocations, ...deletedAllocations];
|
||||||
),
|
// } catch (error) {
|
||||||
this.processDeleteActions(filteredDtos, queryRunner, spaceModel),
|
// throw this.handleError(error, 'Error while updating product allocations');
|
||||||
]);
|
// }
|
||||||
|
|
||||||
// Combine results and return
|
|
||||||
return [...updatedAllocations, ...deletedAllocations];
|
|
||||||
} catch (error) {
|
|
||||||
throw this.handleError(error, 'Error while updating product allocations');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processAddActions(
|
private async processAddActions(
|
||||||
dtos: ModifyTagModelDto[],
|
dtos: ModifyTagDto[],
|
||||||
projectUuid: string,
|
projectUuid: string,
|
||||||
spaceModel: SpaceModelEntity,
|
spaceModel: SpaceModelEntity,
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
modifySubspaceModels?: ModifySubspaceModelDto[],
|
modifySubspaceModels?: ModifySubspaceModelDto[],
|
||||||
): Promise<IUpdatedSpaceAllocations[]> {
|
): Promise<IUpdatedSpaceAllocations[]> {
|
||||||
let allocationUpdates: IUpdatedSpaceAllocations[] = [];
|
const allocationUpdates: IUpdatedSpaceAllocations[] = [];
|
||||||
|
|
||||||
const addDtos: ProcessTagDto[] = dtos
|
|
||||||
.filter((dto) => dto.action === ModifyAction.ADD)
|
|
||||||
.map((dto) => ({
|
|
||||||
name: dto.name,
|
|
||||||
productUuid: dto.productUuid,
|
|
||||||
uuid: dto.newTagUuid,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (addDtos.length > 0) {
|
|
||||||
allocationUpdates = await this.createProductAllocations(
|
|
||||||
projectUuid,
|
|
||||||
spaceModel,
|
|
||||||
addDtos,
|
|
||||||
queryRunner,
|
|
||||||
modifySubspaceModels,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return allocationUpdates;
|
return allocationUpdates;
|
||||||
|
// const addDtos: ProcessTagDto[] = dtos
|
||||||
|
// .filter((dto) => dto.action === ModifyAction.ADD)
|
||||||
|
// .map((dto) => ({
|
||||||
|
// name: dto.name,
|
||||||
|
// productUuid: dto.productUuid,
|
||||||
|
// uuid: dto.newTagUuid,
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// if (addDtos.length > 0) {
|
||||||
|
// allocationUpdates = await this.createProductAllocations(
|
||||||
|
// projectUuid,
|
||||||
|
// spaceModel,
|
||||||
|
// addDtos,
|
||||||
|
// queryRunner,
|
||||||
|
// modifySubspaceModels,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return allocationUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createNewAllocation(
|
private createNewAllocation(
|
||||||
spaceModel: SpaceModelEntity,
|
spaceModel: SpaceModelEntity,
|
||||||
tag: NewTagEntity,
|
tag: NewTagEntity,
|
||||||
queryRunner?: QueryRunner,
|
queryRunner?: QueryRunner,
|
||||||
): SpaceModelProductAllocationEntity {
|
) {
|
||||||
return queryRunner
|
// : SpaceModelProductAllocationEntity
|
||||||
? queryRunner.manager.create(SpaceModelProductAllocationEntity, {
|
// return queryRunner
|
||||||
spaceModel,
|
// ? queryRunner.manager.create(SpaceModelProductAllocationEntity, {
|
||||||
product: tag.product,
|
// spaceModel,
|
||||||
tags: [tag],
|
// product: tag.product,
|
||||||
})
|
// tags: [tag],
|
||||||
: this.spaceModelProductAllocationRepository.create({
|
// })
|
||||||
spaceModel,
|
// : this.spaceModelProductAllocationRepository.create({
|
||||||
product: tag.product,
|
// spaceModel,
|
||||||
tags: [tag],
|
// product: tag.product,
|
||||||
});
|
// tags: [tag],
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getAllocationByProduct(
|
private async getAllocationByProduct(
|
||||||
@ -309,7 +303,7 @@ export class SpaceModelProductAllocationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async processDeleteActions(
|
private async processDeleteActions(
|
||||||
dtos: ModifyTagModelDto[],
|
dtos: ModifyTagDto[],
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
spaceModel: SpaceModelEntity,
|
spaceModel: SpaceModelEntity,
|
||||||
): Promise<IUpdatedSpaceAllocations[]> {
|
): Promise<IUpdatedSpaceAllocations[]> {
|
||||||
@ -317,10 +311,10 @@ export class SpaceModelProductAllocationService {
|
|||||||
if (!dtos || dtos.length === 0) {
|
if (!dtos || dtos.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let allocationUpdateToPropagate: IUpdatedSpaceAllocations[] = [];
|
const allocationUpdateToPropagate: IUpdatedSpaceAllocations[] = [];
|
||||||
|
|
||||||
const tagUuidsToDelete = dtos
|
const tagUuidsToDelete = dtos
|
||||||
.filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid)
|
// .filter((dto) => dto.action === ModifyAction.DELETE && dto.tagUuid)
|
||||||
.map((dto) => dto.tagUuid);
|
.map((dto) => dto.tagUuid);
|
||||||
|
|
||||||
if (tagUuidsToDelete.length === 0) return [];
|
if (tagUuidsToDelete.length === 0) return [];
|
||||||
@ -329,7 +323,7 @@ export class SpaceModelProductAllocationService {
|
|||||||
SpaceModelProductAllocationEntity,
|
SpaceModelProductAllocationEntity,
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
tags: { uuid: In(tagUuidsToDelete) },
|
tag: In(tagUuidsToDelete),
|
||||||
spaceModel: {
|
spaceModel: {
|
||||||
uuid: spaceModel.uuid,
|
uuid: spaceModel.uuid,
|
||||||
},
|
},
|
||||||
@ -348,31 +342,28 @@ export class SpaceModelProductAllocationService {
|
|||||||
const allocationUpdates: SpaceModelProductAllocationEntity[] = [];
|
const allocationUpdates: SpaceModelProductAllocationEntity[] = [];
|
||||||
|
|
||||||
for (const allocation of allocationsToUpdate) {
|
for (const allocation of allocationsToUpdate) {
|
||||||
const updatedTags = allocation.tags.filter(
|
// const updatedTags = allocation.tags.filter(
|
||||||
(tag) => !tagUuidsToDelete.includes(tag.uuid),
|
// (tag) => !tagUuidsToDelete.includes(tag.uuid),
|
||||||
);
|
// );
|
||||||
|
// const deletedTags = allocation.tags.filter((tag) =>
|
||||||
const deletedTags = allocation.tags.filter((tag) =>
|
// tagUuidsToDelete.includes(tag.uuid),
|
||||||
tagUuidsToDelete.includes(tag.uuid),
|
// );
|
||||||
);
|
// if (updatedTags.length === allocation.tags.length) {
|
||||||
|
// continue;
|
||||||
if (updatedTags.length === allocation.tags.length) {
|
// }
|
||||||
continue;
|
// if (updatedTags.length === 0) {
|
||||||
}
|
// deletedAllocations.push(allocation);
|
||||||
|
// allocationUpdateToPropagate.push({
|
||||||
if (updatedTags.length === 0) {
|
// deletedAllocation: allocation,
|
||||||
deletedAllocations.push(allocation);
|
// });
|
||||||
allocationUpdateToPropagate.push({
|
// } else {
|
||||||
deletedAllocation: allocation,
|
// allocation.tags = updatedTags;
|
||||||
});
|
// allocationUpdates.push(allocation);
|
||||||
} else {
|
// allocationUpdateToPropagate.push({
|
||||||
allocation.tags = updatedTags;
|
// allocation: allocation,
|
||||||
allocationUpdates.push(allocation);
|
// tagsRemoved: deletedTags,
|
||||||
allocationUpdateToPropagate.push({
|
// });
|
||||||
allocation: allocation,
|
// }
|
||||||
tagsRemoved: deletedTags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allocationUpdates.length > 0) {
|
if (allocationUpdates.length > 0) {
|
||||||
@ -415,33 +406,34 @@ export class SpaceModelProductAllocationService {
|
|||||||
tag: NewTagEntity,
|
tag: NewTagEntity,
|
||||||
spaceModel: SpaceModelEntity,
|
spaceModel: SpaceModelEntity,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const existingAllocationsForProduct = await queryRunner.manager.find(
|
|
||||||
SpaceModelProductAllocationEntity,
|
|
||||||
{
|
|
||||||
where: {
|
|
||||||
spaceModel: {
|
|
||||||
uuid: spaceModel.uuid,
|
|
||||||
},
|
|
||||||
product: {
|
|
||||||
uuid: tag.product.uuid,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: ['tags'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingTagsForProduct = existingAllocationsForProduct.flatMap(
|
|
||||||
(allocation) => allocation.tags,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDuplicateTag = existingTagsForProduct.some(
|
|
||||||
(existingTag) => existingTag.uuid === tag.uuid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDuplicateTag) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
// const existingAllocationsForProduct = await queryRunner.manager.find(
|
||||||
return false;
|
// SpaceModelProductAllocationEntity,
|
||||||
|
// {
|
||||||
|
// where: {
|
||||||
|
// spaceModel: {
|
||||||
|
// uuid: spaceModel.uuid,
|
||||||
|
// },
|
||||||
|
// product: {
|
||||||
|
// uuid: tag.product.uuid,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// relations: ['tags'],
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const existingTagsForProduct = existingAllocationsForProduct.flatMap(
|
||||||
|
// (allocation) => allocation.tags,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const isDuplicateTag = existingTagsForProduct.some(
|
||||||
|
// (existingTag) => existingTag.uuid === tag.uuid,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (isDuplicateTag) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearAllAllocations(spaceModelUuid: string, queryRunner: QueryRunner) {
|
async clearAllAllocations(spaceModelUuid: string, queryRunner: QueryRunner) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user