diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b0ec605 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ + + +## Jira Ticket + +[SP-0000](https://syncrow.atlassian.net/browse/SP-0000) + +## Description + + + +## How to Test + + diff --git a/.github/workflows/main_syncrow(staging).yml b/.github/workflows/main_syncrow(staging).yml index 96a46e8..d260820 100644 --- a/.github/workflows/main_syncrow(staging).yml +++ b/.github/workflows/main_syncrow(staging).yml @@ -1,4 +1,7 @@ -name: Backend deployment to Azure App Service +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy container app to Azure Web App - syncrow(staging) on: push: @@ -6,50 +9,43 @@ on: - main workflow_dispatch: -env: - AZURE_WEB_APP_NAME: 'syncrow' - AZURE_WEB_APP_SLOT_NAME: 'staging' - ACR_REGISTRY: 'syncrow.azurecr.io' - IMAGE_NAME: 'backend' - IMAGE_TAG: 'latest' - jobs: - build_and_deploy: - runs-on: ubuntu-latest + build: + runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v2 - - name: Set up Node.js - uses: actions/setup-node@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to registry + uses: docker/login-action@v2 with: - node-version: '20' + registry: https://syncrow.azurecr.io/ + username: ${{ secrets.AzureAppService_ContainerUsername_47395803300340b49931ea82f6d80be3 }} + password: ${{ secrets.AzureAppService_ContainerPassword_e7b0ff54f54d44cba04a970a22384848 }} - - name: Install dependencies and build project - run: | - npm install - npm run build - - - name: Log in to Azure - uses: azure/login@v1 + - name: Build and push container image to registry + uses: docker/build-push-action@v3 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + push: true + tags: syncrow.azurecr.io/${{ secrets.AzureAppService_ContainerUsername_47395803300340b49931ea82f6d80be3 }}/syncrow/backend:${{ github.sha }} + file: ./Dockerfile - - name: Log in to Azure Container Registry - run: az acr login --name ${{ env.ACR_REGISTRY }} + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'staging' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} - - name: List build output - run: ls -R dist/ - - - name: Build and push Docker image - run: | - docker build . -t ${{ env.ACR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - docker push ${{ env.ACR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - - - name: Set Web App with Docker container - run: | - az webapp config container set \ - --name ${{ env.AZURE_WEB_APP_NAME }} \ - --resource-group backend \ - --docker-custom-image-name ${{ env.ACR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \ - --docker-registry-server-url https://${{ env.ACR_REGISTRY }} + steps: + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'syncrow' + slot-name: 'staging' + publish-profile: ${{ secrets.AzureAppService_PublishProfile_44f7766441ec4796b74789e9761ef589 }} + images: 'syncrow.azurecr.io/${{ secrets.AzureAppService_ContainerUsername_47395803300340b49931ea82f6d80be3 }}/syncrow/backend:${{ github.sha }}' diff --git a/.github/workflows/main_syncrow(stg).yml b/.github/workflows/main_syncrow(stg).yml new file mode 100644 index 0000000..8f38aa6 --- /dev/null +++ b/.github/workflows/main_syncrow(stg).yml @@ -0,0 +1,73 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Node.js app to Azure Web App - syncrow + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read #This is required for actions/checkout + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js version + uses: actions/setup-node@v3 + with: + node-version: '20.x' + + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm run test --if-present + + - name: Zip artifact for deployment + run: zip release.zip ./* -r + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: node-app + path: release.zip + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'stg' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + contents: read #This is required for actions/checkout + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: node-app + + - name: Unzip artifact for deployment + run: unzip release.zip + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_515C8E782CFF431AB20448C85CA0FE58 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_2AEFE5534424490387C08FAE41573CC2 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_00623C33023749FEA5F6BC36884F9C8A }} + + - name: 'Deploy to Azure Web App' + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'syncrow' + slot-name: 'stg' + package: . diff --git a/.gitignore b/.gitignore index 46ae420..3de8765 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /build #github -/.github +/.github/workflows # Logs logs diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 353a9fb..463d524 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -406,6 +406,11 @@ export class ControllerRoute { public static readonly DELETE_USER_SUMMARY = 'Delete user by UUID'; public static readonly DELETE_USER_DESCRIPTION = 'This endpoint deletes a user identified by their UUID. Accessible only by users with the Super Admin role.'; + + public static readonly DELETE_USER_PROFILE_SUMMARY = + 'Delete user profile by UUID'; + public static readonly DELETE_USER_PROFILE_DESCRIPTION = + 'This endpoint deletes a user profile identified by their UUID. Accessible only by users with the Super Admin role.'; public static readonly UPDATE_USER_WEB_AGREEMENT_SUMMARY = 'Update user web agreement by user UUID'; public static readonly UPDATE_USER_WEB_AGREEMENT_DESCRIPTION = @@ -510,7 +515,6 @@ export class ControllerRoute { }; static PowerClamp = class { public static readonly ROUTE = 'power-clamp'; - static ACTIONS = class { public static readonly GET_ENERGY_SUMMARY = 'Get power clamp historical data'; diff --git a/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts b/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts index ded0771..560943b 100644 --- a/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts +++ b/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts @@ -13,6 +13,7 @@ class StatusDto { @IsNotEmpty() value: any; + t?: string | number | Date; } export class AddDeviceStatusDto { diff --git a/libs/common/src/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index b3ef843..fd40f8b 100644 --- a/libs/common/src/firebase/devices-status/services/devices-status.service.ts +++ b/libs/common/src/firebase/devices-status/services/devices-status.service.ts @@ -22,6 +22,8 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log export class DeviceStatusFirebaseService { private tuya: TuyaContext; private firebaseDb: Database; + private readonly isDevEnv: boolean; + constructor( private readonly configService: ConfigService, private readonly deviceRepository: DeviceRepository, @@ -38,6 +40,8 @@ export class DeviceStatusFirebaseService { // Initialize firebaseDb using firebaseDataBase function this.firebaseDb = firebaseDataBase(this.configService); + this.isDevEnv = + this.configService.get('NODE_ENV') === 'development'; } async addDeviceStatusByDeviceUuid( deviceTuyaUuid: string, @@ -52,7 +56,7 @@ export class DeviceStatusFirebaseService { const deviceStatusSaved = await this.createDeviceStatusFirebase({ deviceUuid: device.uuid, deviceTuyaUuid: deviceTuyaUuid, - status: deviceStatus.status, + status: deviceStatus?.status, productUuid: deviceStatus.productUuid, productType: deviceStatus.productType, }); @@ -191,7 +195,7 @@ export class DeviceStatusFirebaseService { return { productUuid: deviceDetails.productDevice.uuid, productType: deviceDetails.productDevice.prodType, - status: deviceStatus.result[0].status, + status: deviceStatus.result[0]?.status, }; } catch (error) { throw new HttpException( @@ -256,18 +260,18 @@ export class DeviceStatusFirebaseService { if (!existingData.productType) { existingData.productType = addDeviceStatusDto.productType; } - if (!existingData.status) { + if (!existingData?.status) { existingData.status = []; } // Create a map to track existing status codes const statusMap = new Map( - existingData.status.map((item) => [item.code, item.value]), + existingData?.status.map((item) => [item.code, item.value]), ); // Update or add status codes - for (const statusItem of addDeviceStatusDto.status) { + for (const statusItem of addDeviceStatusDto?.status) { statusMap.set(statusItem.code, statusItem.value); } diff --git a/libs/common/src/logger/services/winston.logger.ts b/libs/common/src/logger/services/winston.logger.ts index 9e8fa11..34b6a75 100644 --- a/libs/common/src/logger/services/winston.logger.ts +++ b/libs/common/src/logger/services/winston.logger.ts @@ -1,43 +1,26 @@ import { utilities as nestWinstonModuleUtilities } from 'nest-winston'; import * as winston from 'winston'; -const environment = process.env.NODE_ENV || 'local'; export const winstonLoggerOptions: winston.LoggerOptions = { level: - environment === 'local' - ? 'debug' - : environment === 'development' - ? 'warn' - : 'error', + process.env.AZURE_POSTGRESQL_DATABASE === 'development' ? 'debug' : 'error', transports: [ new winston.transports.Console({ - level: - environment === 'local' - ? 'debug' - : environment === 'development' - ? 'warn' - : 'error', format: winston.format.combine( winston.format.timestamp(), nestWinstonModuleUtilities.format.nestLike('MyApp', { - prettyPrint: environment === 'local', + prettyPrint: true, }), ), }), - // Only create file logs if NOT local - ...(environment !== 'local' - ? [ - new winston.transports.File({ - filename: 'logs/error.log', - level: 'error', - format: winston.format.json(), - }), - new winston.transports.File({ - filename: 'logs/combined.log', - level: 'info', - format: winston.format.json(), - }), - ] - : []), + new winston.transports.File({ + filename: 'logs/error.log', + level: 'error', + format: winston.format.json(), + }), + new winston.transports.File({ + filename: 'logs/combined.log', + format: winston.format.json(), + }), ], }; diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 6848bd9..c798a70 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -8,14 +8,14 @@ import { Unique, } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { RoleTypeEntity } from '../../role-type/entities'; -import { UserStatusEnum } from '@app/common/constants/user-status.enum'; -import { UserEntity } from '../../user/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectEntity } from '../../project/entities'; +import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities/space.entity'; +import { UserEntity } from '../../user/entities'; +import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; @Entity({ name: 'invite-user' }) @Unique(['email', 'project']) @@ -82,7 +82,10 @@ export class InviteUserEntity extends AbstractEntity { onDelete: 'CASCADE', }) public roleType: RoleTypeEntity; - @OneToOne(() => UserEntity, (user) => user.inviteUser, { nullable: true }) + @OneToOne(() => UserEntity, (user) => user.inviteUser, { + nullable: true, + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'user_uuid' }) user: UserEntity; @OneToMany( @@ -112,7 +115,9 @@ export class InviteUserSpaceEntity extends AbstractEntity { }) public uuid: string; - @ManyToOne(() => InviteUserEntity, (inviteUser) => inviteUser.spaces) + @ManyToOne(() => InviteUserEntity, (inviteUser) => inviteUser.spaces, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'invite_user_uuid' }) public inviteUser: InviteUserEntity; diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 431222d..33f13d4 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,24 +1,24 @@ import { Column, Entity, + Index, + JoinColumn, ManyToOne, OneToMany, Unique, - Index, - JoinColumn, } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; -import { ProductEntity } from '../../product/entities'; -import { UserEntity } from '../../user/entities'; -import { DeviceNotificationDto } from '../dtos'; import { PermissionTypeEntity } from '../../permission/entities'; +import { PowerClampHourlyEntity } from '../../power-clamp/entities/power-clamp.entity'; +import { PresenceSensorDailyDeviceEntity } from '../../presence-sensor/entities'; +import { ProductEntity } from '../../product/entities'; import { SceneDeviceEntity } from '../../scene-device/entities'; import { SpaceEntity } from '../../space/entities/space.entity'; import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity'; import { NewTagEntity } from '../../tag'; -import { PowerClampHourlyEntity } from '../../power-clamp/entities/power-clamp.entity'; -import { PresenceSensorDailyDeviceEntity } from '../../presence-sensor/entities'; +import { UserEntity } from '../../user/entities'; +import { DeviceNotificationDto } from '../dtos'; +import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; @Entity({ name: 'device' }) @Unique(['deviceTuyaUuid']) @@ -111,6 +111,7 @@ export class DeviceNotificationEntity extends AbstractEntity UserEntity, (user) => user.userPermission, { nullable: false, + onDelete: 'CASCADE', }) user: UserEntity; @@ -149,6 +150,7 @@ export class DeviceUserPermissionEntity extends AbstractEntity UserEntity, (user) => user.userPermission, { nullable: false, + onDelete: 'CASCADE', }) user: UserEntity; constructor(partial: Partial) { diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 0dca072..5bf2fe7 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -1,3 +1,4 @@ +import { defaultProfilePicture } from '@app/common/constants/default.profile.picture'; import { Column, DeleteDateColumn, @@ -8,27 +9,26 @@ import { OneToOne, Unique, } from 'typeorm'; +import { OtpType } from '../../../../src/constants/otp-type.enum'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { ClientEntity } from '../../client/entities'; +import { + DeviceNotificationEntity, + DeviceUserPermissionEntity, +} from '../../device/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; +import { ProjectEntity } from '../../project/entities'; +import { RegionEntity } from '../../region/entities'; +import { RoleTypeEntity } from '../../role-type/entities'; +import { SpaceEntity } from '../../space/entities/space.entity'; +import { TimeZoneEntity } from '../../timezone/entities'; +import { VisitorPasswordEntity } from '../../visitor-password/entities'; import { UserDto, UserNotificationDto, UserOtpDto, UserSpaceDto, } from '../dtos'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { - DeviceNotificationEntity, - DeviceUserPermissionEntity, -} from '../../device/entities'; -import { defaultProfilePicture } from '@app/common/constants/default.profile.picture'; -import { RegionEntity } from '../../region/entities'; -import { TimeZoneEntity } from '../../timezone/entities'; -import { OtpType } from '../../../../src/constants/otp-type.enum'; -import { RoleTypeEntity } from '../../role-type/entities'; -import { VisitorPasswordEntity } from '../../visitor-password/entities'; -import { InviteUserEntity } from '../../Invite-user/entities'; -import { ProjectEntity } from '../../project/entities'; -import { SpaceEntity } from '../../space/entities/space.entity'; -import { ClientEntity } from '../../client/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -94,7 +94,9 @@ export class UserEntity extends AbstractEntity { @Column({ type: 'timestamp', nullable: true }) appAgreementAcceptedAt: Date; - @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user) + @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user, { + onDelete: 'CASCADE', + }) userSpaces: UserSpaceEntity[]; @OneToMany( @@ -158,6 +160,7 @@ export class UserEntity extends AbstractEntity { export class UserNotificationEntity extends AbstractEntity { @ManyToOne(() => UserEntity, (user) => user.roleType, { nullable: false, + onDelete: 'CASCADE', }) user: UserEntity; @Column({ @@ -219,7 +222,10 @@ export class UserSpaceEntity extends AbstractEntity { }) public uuid: string; - @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) + @ManyToOne(() => UserEntity, (user) => user.userSpaces, { + nullable: false, + onDelete: 'CASCADE', + }) user: UserEntity; @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { diff --git a/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts b/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts index f1c0ed6..8b97e82 100644 --- a/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts +++ b/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts @@ -1,7 +1,7 @@ -import { Column, Entity, ManyToOne, JoinColumn, Index } from 'typeorm'; -import { VisitorPasswordDto } from '../dtos'; +import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserEntity } from '../../user/entities/user.entity'; +import { VisitorPasswordDto } from '../dtos'; @Entity({ name: 'visitor-password' }) @Index('IDX_PASSWORD_TUYA_UUID', ['passwordTuyaUuid']) @@ -14,6 +14,7 @@ export class VisitorPasswordEntity extends AbstractEntity { @ManyToOne(() => UserEntity, (user) => user.visitorPasswords, { nullable: false, + onDelete: 'CASCADE', }) @JoinColumn({ name: 'authorizer_uuid' }) public user: UserEntity; diff --git a/package.json b/package.json index 6e16079..f759b41 100644 --- a/package.json +++ b/package.json @@ -89,5 +89,9 @@ "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" + }, + "engines": { + "node": "20.x", + "npm": "10.x" } } diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index d4ff99c..e657436 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -120,6 +120,7 @@ export class CommunityService { .leftJoin('c.spaces', 's', 's.disabled = false') .where('c.project = :projectUuid', { projectUuid }) .andWhere(`c.name != '${ORPHAN_COMMUNITY_NAME}-${project.name}'`) + .orderBy('c.createdAt', 'DESC') .distinct(true); if (pageable.search) { qb.andWhere( diff --git a/src/power-clamp/controllers/power-clamp.controller.ts b/src/power-clamp/controllers/power-clamp.controller.ts index eb89d11..0cbb6fd 100644 --- a/src/power-clamp/controllers/power-clamp.controller.ts +++ b/src/power-clamp/controllers/power-clamp.controller.ts @@ -1,25 +1,24 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; import { - ApiTags, ApiBearerAuth, ApiOperation, ApiParam, ApiQuery, + ApiTags, } 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 { PowerClampService } from '../services/power-clamp.service'; import { GetPowerClampBySpaceDto, GetPowerClampDto, } from '../dto/get-power-clamp.dto'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PowerClampParamsDto, ResourceParamsDto, } from '../dto/power-clamp-params.dto'; - +import { PowerClampService } from '../services/power-clamp.service'; @ApiTags('Power Clamp Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, @@ -27,7 +26,6 @@ import { }) export class PowerClampController { constructor(private readonly powerClampService: PowerClampService) {} - @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':powerClampUuid/historical') diff --git a/src/power-clamp/services/power-clamp.service.ts b/src/power-clamp/services/power-clamp.service.ts index e7e0df4..50a9461 100644 --- a/src/power-clamp/services/power-clamp.service.ts +++ b/src/power-clamp/services/power-clamp.service.ts @@ -23,10 +23,10 @@ import { SpaceDeviceService } from 'src/space/services'; 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 { filterByMonth, toMMYYYY } from '@app/common/helper/date-format'; import { ProductType } from '@app/common/constants/product-type.enum'; import { CommunityService } from 'src/community/services'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { filterByMonth, toMMYYYY } from '@app/common/helper/date-format'; @Injectable() export class PowerClampService { diff --git a/src/schedule/services/schedule.service.ts b/src/schedule/services/schedule.service.ts index 1296900..b05296a 100644 --- a/src/schedule/services/schedule.service.ts +++ b/src/schedule/services/schedule.service.ts @@ -1,5 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { AddScheduleDto, @@ -11,6 +13,7 @@ import { getDeviceScheduleInterface, } from '../interfaces/get.schedule.interface'; +import { ProductType } from '@app/common/constants/product-type.enum'; import { ProductType } from '@app/common/constants/product-type.enum'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertTimestampToDubaiTime } from '@app/common/helper/convertTimestampToDubaiTime'; @@ -19,6 +22,7 @@ import { getScheduleStatus, } from '@app/common/helper/getScheduleStatus'; import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; @Injectable() export class ScheduleService { @@ -106,6 +110,16 @@ export class ScheduleService { ); } + if ( + deviceDetails.productDevice.prodType == ProductType.CUR_2 && + addScheduleDto.category != 'Timer' + ) { + throw new HttpException( + 'Invalid category for CUR_2 devices', + HttpStatus.BAD_REQUEST, + ); + } + this.ensureProductTypeSupportedForSchedule( deviceDetails.productDevice.prodType as ProductType, ); @@ -114,6 +128,7 @@ export class ScheduleService { deviceDetails.deviceTuyaUuid, addScheduleDto, deviceDetails.productDevice.prodType as ProductType, + deviceDetails.productDevice.prodType as ProductType, ); } catch (error) { throw new HttpException( @@ -137,9 +152,14 @@ export class ScheduleService { deviceDetails.deviceTuyaUuid, category, deviceDetails.productDevice.prodType as ProductType, + deviceDetails.productDevice.prodType as ProductType, ); const result = schedules.result.map((schedule: any) => { return { + category: + deviceDetails.productDevice.prodType == ProductType.CUR_2 + ? schedule.category + : schedule.category.replace('category_', ''), category: deviceDetails.productDevice.prodType == ProductType.CUR_2 ? schedule.category @@ -184,6 +204,16 @@ export class ScheduleService { ); } + if ( + deviceDetails.productDevice.prodType == ProductType.CUR_2 && + updateScheduleDto.category != 'Timer' + ) { + throw new HttpException( + 'Invalid category for CUR_2 devices', + HttpStatus.BAD_REQUEST, + ); + } + // Corrected condition for supported device types this.ensureProductTypeSupportedForSchedule( deviceDetails.productDevice.prodType as ProductType, @@ -193,6 +223,7 @@ export class ScheduleService { deviceDetails.deviceTuyaUuid, updateScheduleDto, deviceDetails.productDevice.prodType as ProductType, + deviceDetails.productDevice.prodType as ProductType, ); } catch (error) { throw new HttpException( @@ -282,6 +313,7 @@ export class ScheduleService { deviceId: string, updateScheduleDto: UpdateScheduleDto, deviceType: ProductType, + deviceType: ProductType, ): Promise { try { const convertedTime = convertTimestampToDubaiTime(updateScheduleDto.time); diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index ce9991a..7d8bf4b 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -1,3 +1,7 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { Body, Controller, @@ -7,10 +11,12 @@ import { Param, Patch, Put, + Req, UseGuards, } from '@nestjs/common'; -import { UserService } from '../services/user.service'; -import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; +import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { UpdateNameDto, @@ -18,11 +24,7 @@ import { UpdateRegionDataDto, UpdateTimezoneDataDto, } from '../dtos'; -import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; -import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { ControllerRoute } from '@app/common/constants/controller-route'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { UserService } from '../services/user.service'; @ApiTags('User Module') @Controller({ @@ -154,6 +156,32 @@ export class UserController { }; } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete('') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.DELETE_USER_PROFILE_SUMMARY, + description: ControllerRoute.USER.ACTIONS.DELETE_USER_PROFILE_DESCRIPTION, + }) + async deleteUserProfile(@Req() req: Request) { + const userUuid = req['user']?.userUuid; + const userRole = req['user']?.role; + if (!userUuid || (userRole && userRole == RoleType.SUPER_ADMIN)) { + throw { + statusCode: HttpStatus.UNAUTHORIZED, + message: 'Unauthorized', + }; + } + await this.userService.deleteUserProfile(userUuid); + return { + statusCode: HttpStatus.OK, + data: { + userId: userUuid, + }, + message: 'User deleted successfully', + }; + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Patch('agreements/web/:userUuid') diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index bda1c3a..89c06f8 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -1,21 +1,21 @@ -import { - UpdateNameDto, - UpdateProfilePictureDataDto, - UpdateRegionDataDto, - UpdateTimezoneDataDto, -} from './../dtos/update.user.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { removeBase64Prefix } from '@app/common/helper/removeBase64Prefix'; +import { RegionRepository } from '@app/common/modules/region/repositories'; +import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; +import { UserEntity } from '@app/common/modules/user/entities'; +import { UserRepository } from '@app/common/modules/user/repositories'; import { BadRequestException, HttpException, HttpStatus, Injectable, } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { RegionRepository } from '@app/common/modules/region/repositories'; -import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; -import { removeBase64Prefix } from '@app/common/helper/removeBase64Prefix'; -import { UserEntity } from '@app/common/modules/user/entities'; -import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { + UpdateNameDto, + UpdateProfilePictureDataDto, + UpdateRegionDataDto, + UpdateTimezoneDataDto, +} from './../dtos/update.user.dto'; @Injectable() export class UserService { @@ -269,4 +269,12 @@ export class UserService { } return await this.userRepository.update({ uuid }, { isActive: false }); } + + async deleteUserProfile(uuid: string) { + const user = await this.findOneById(uuid); + if (!user) { + throw new BadRequestException('User not found'); + } + return this.userRepository.delete({ uuid }); + } }