From 30dd8318e35ace77255390e9e1051a0f8002530a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:20:37 +0300 Subject: [PATCH 1/2] Add SQL queries for hourly energy consumption tracking and refactor daily energy consumed query --- .../fact_daily_energy_consumed.sql | 5 +- .../fact_hourly_active_energy.sql | 65 +++++++++++++++++ .../fact_hourly_energy_consumed.sql | 71 +++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/sql/queries/fact_hourly_active_energy/fact_hourly_active_energy.sql create mode 100644 libs/common/src/sql/queries/fact_hourly_energy_consumed/fact_hourly_energy_consumed.sql diff --git a/libs/common/src/sql/queries/fact_daily_energy_consumed/fact_daily_energy_consumed.sql b/libs/common/src/sql/queries/fact_daily_energy_consumed/fact_daily_energy_consumed.sql index 00b0ca0..b7967ce 100644 --- a/libs/common/src/sql/queries/fact_daily_energy_consumed/fact_daily_energy_consumed.sql +++ b/libs/common/src/sql/queries/fact_daily_energy_consumed/fact_daily_energy_consumed.sql @@ -61,5 +61,8 @@ JOIN energy_phase_B and total_energy.date=energy_phase_B.date JOIN energy_phase_C ON total_energy.device_id=energy_phase_C.device_id - and total_energy.date=energy_phase_C.date + and total_energy.date=energy_phase_C.date; + + + diff --git a/libs/common/src/sql/queries/fact_hourly_active_energy/fact_hourly_active_energy.sql b/libs/common/src/sql/queries/fact_hourly_active_energy/fact_hourly_active_energy.sql new file mode 100644 index 0000000..d19b154 --- /dev/null +++ b/libs/common/src/sql/queries/fact_hourly_active_energy/fact_hourly_active_energy.sql @@ -0,0 +1,65 @@ +-- model shows the energy consumed per day per device + +WITH total_energy AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='ActivePower' + GROUP BY device_id, date +) + +, energy_phase_A AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='ActivePowerA' + GROUP BY device_id, date +) + +, energy_phase_B AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='ActivePowerB' + GROUP BY device_id, date +) + +, energy_phase_C AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='ActivePowerC' + GROUP BY device_id, date +) + + +SELECT + total_energy.device_id, + total_energy.date, + (total_energy.max_value-total_energy.min_value) as energy_consumed_kW, + (energy_phase_A.max_value-energy_phase_A.min_value) as energy_consumed_A, + (energy_phase_B.max_value-energy_phase_B.min_value) as energy_consumed_B, + (energy_phase_C.max_value-energy_phase_C.min_value) as energy_consumed_C +FROM total_energy +JOIN energy_phase_A + ON total_energy.device_id=energy_phase_A.device_id + and total_energy.date=energy_phase_A.date +JOIN energy_phase_B + ON total_energy.device_id=energy_phase_B.device_id + and total_energy.date=energy_phase_B.date +JOIN energy_phase_C + ON total_energy.device_id=energy_phase_C.device_id + and total_energy.date=energy_phase_C.date; + \ No newline at end of file diff --git a/libs/common/src/sql/queries/fact_hourly_energy_consumed/fact_hourly_energy_consumed.sql b/libs/common/src/sql/queries/fact_hourly_energy_consumed/fact_hourly_energy_consumed.sql new file mode 100644 index 0000000..60de9fe --- /dev/null +++ b/libs/common/src/sql/queries/fact_hourly_energy_consumed/fact_hourly_energy_consumed.sql @@ -0,0 +1,71 @@ +WITH total_energy AS ( + SELECT + device_id, + event_time::date AS date, + EXTRACT(HOUR FROM event_time) AS hour, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + WHERE code = 'EnergyConsumed' + GROUP BY device_id, date, hour +), + +energy_phase_A AS ( + SELECT + device_id, + event_time::date AS date, + EXTRACT(HOUR FROM event_time) AS hour, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + WHERE code = 'EnergyConsumedA' + GROUP BY device_id, date, hour +), + +energy_phase_B AS ( + SELECT + device_id, + event_time::date AS date, + EXTRACT(HOUR FROM event_time) AS hour, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + WHERE code = 'EnergyConsumedB' + GROUP BY device_id, date, hour +), + +energy_phase_C AS ( + SELECT + device_id, + event_time::date AS date, + EXTRACT(HOUR FROM event_time) AS hour, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + WHERE code = 'EnergyConsumedC' + GROUP BY device_id, date, hour +) + +SELECT + total_energy.device_id, + total_energy.date, + total_energy.hour, + (total_energy.max_value - total_energy.min_value) AS energy_consumed_kW, + (energy_phase_A.max_value - energy_phase_A.min_value) AS energy_consumed_A, + (energy_phase_B.max_value - energy_phase_B.min_value) AS energy_consumed_B, + (energy_phase_C.max_value - energy_phase_C.min_value) AS energy_consumed_C +FROM total_energy +JOIN energy_phase_A + ON total_energy.device_id = energy_phase_A.device_id + AND total_energy.date = energy_phase_A.date + AND total_energy.hour = energy_phase_A.hour +JOIN energy_phase_B + ON total_energy.device_id = energy_phase_B.device_id + AND total_energy.date = energy_phase_B.date + AND total_energy.hour = energy_phase_B.hour +JOIN energy_phase_C + ON total_energy.device_id = energy_phase_C.device_id + AND total_energy.date = energy_phase_C.date + AND total_energy.hour = energy_phase_C.hour +ORDER BY total_energy.device_id, total_energy.date, total_energy.hour; + From 32112930c8f90f904e3a29e80569ab0c06913187 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 15 Apr 2025 13:26:56 +0300 Subject: [PATCH 2/2] Implement product creation functionality with DTO and permissions --- libs/common/src/constants/controller-route.ts | 3 ++ libs/common/src/constants/role-permissions.ts | 2 ++ src/product/controllers/product.controller.ts | 19 ++++++++-- src/product/dtos/add.product.dto.ts | 36 +++++++++++++++++++ src/product/dtos/index.ts | 1 + src/product/services/product.service.ts | 30 +++++++++++++++- 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/product/dtos/add.product.dto.ts create mode 100644 src/product/dtos/index.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 1c8685d..db65dd4 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -340,6 +340,9 @@ export class ControllerRoute { static PRODUCT = class { public static readonly ROUTE = 'products'; static ACTIONS = class { + public static readonly CREATE_PRODUCT_SUMMARY = 'Create a new product'; + public static readonly CREATE_PRODUCT_DESCRIPTION = + 'This endpoint allows you to create a new product in the system.'; public static readonly LIST_PRODUCT_SUMMARY = 'Retrieve all products'; public static readonly LIST_PRODUCT_DESCRIPTION = 'Fetches a list of all products along with their associated device details'; diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index 4f3043d..cd4593c 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -55,6 +55,7 @@ export const RolePermissions = { 'USER_ADD', 'SPACE_MEMBER_ADD', 'COMMISSION_DEVICE', + 'PRODUCT_ADD', ], [RoleType.ADMIN]: [ 'DEVICE_SINGLE_CONTROL', @@ -110,6 +111,7 @@ export const RolePermissions = { 'USER_ADD', 'SPACE_MEMBER_ADD', 'COMMISSION_DEVICE', + 'PRODUCT_ADD', ], [RoleType.SPACE_MEMBER]: [ 'DEVICE_SINGLE_CONTROL', diff --git a/src/product/controllers/product.controller.ts b/src/product/controllers/product.controller.ts index f5fdf53..24a6122 100644 --- a/src/product/controllers/product.controller.ts +++ b/src/product/controllers/product.controller.ts @@ -1,10 +1,13 @@ -import { Controller, Get, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { ProductService } from '../services'; +import { Permissions } from 'src/decorators/permissions.decorator'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { AddProductDto } from '../dtos'; @ApiTags('Product Module') @Controller({ @@ -13,7 +16,19 @@ import { ProductService } from '../services'; }) export class ProductController { constructor(private readonly productService: ProductService) {} - + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('PRODUCT_ADD') + @Post() + @ApiOperation({ + summary: ControllerRoute.PRODUCT.ACTIONS.CREATE_PRODUCT_SUMMARY, + description: ControllerRoute.PRODUCT.ACTIONS.CREATE_PRODUCT_DESCRIPTION, + }) + async addNewProductType( + @Body() addProductDto: AddProductDto, + ): Promise { + return await this.productService.addNewProductType(addProductDto); + } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() diff --git a/src/product/dtos/add.product.dto.ts b/src/product/dtos/add.product.dto.ts new file mode 100644 index 0000000..dc9de81 --- /dev/null +++ b/src/product/dtos/add.product.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AddProductDto { + @ApiProperty({ + description: 'tuyaCategoryName', + required: true, + }) + @IsString() + @IsNotEmpty() + public tuyaCategoryName: string; + + @ApiProperty({ + description: 'tuyaProductId', + required: true, + }) + @IsString() + @IsNotEmpty() + public tuyaProductId: string; + + @ApiProperty({ + description: 'syncrowProductType', + required: true, + }) + @IsString() + @IsNotEmpty() + public syncrowProductType: string; + + @ApiProperty({ + description: 'syncrowProductName', + required: true, + }) + @IsString() + @IsNotEmpty() + public syncrowProductName: string; +} diff --git a/src/product/dtos/index.ts b/src/product/dtos/index.ts new file mode 100644 index 0000000..f2b2655 --- /dev/null +++ b/src/product/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.product.dto'; diff --git a/src/product/services/product.service.ts b/src/product/services/product.service.ts index 4b9377c..c67fca4 100644 --- a/src/product/services/product.service.ts +++ b/src/product/services/product.service.ts @@ -2,11 +2,39 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { AddProductDto } from '../dtos'; @Injectable() export class ProductService { constructor(private readonly productRepository: ProductRepository) {} + async addNewProductType( + addProductDto: AddProductDto, + ): Promise { + try { + const { + tuyaCategoryName, + tuyaProductId, + syncrowProductType, + syncrowProductName, + } = addProductDto; + const product = await this.productRepository.save({ + catName: tuyaCategoryName, + prodId: tuyaProductId, + prodType: syncrowProductType, + name: syncrowProductName, + }); + return new SuccessResponseDto({ + data: product, + message: 'Successfully added new product type', + }); + } catch (error) { + throw new HttpException( + `Error adding new product type: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async list(): Promise { const products = await this.productRepository.find(); @@ -37,7 +65,7 @@ export class ProductService { return new SuccessResponseDto({ data: product, - message: 'Succefully retrieved product', + message: 'Successfully retrieved product', }); } }