mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 08:54:54 +00:00
merged dev
This commit is contained in:
59
.github/workflows/azure-webapps-node.yml
vendored
59
.github/workflows/azure-webapps-node.yml
vendored
@ -1,59 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "dev" ]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
AZURE_WEBAPP_NAME: backend-dev # set this to your application's name
|
|
||||||
AZURE_WEBAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root
|
|
||||||
NODE_VERSION: '20.x' # set this to the node version to use
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: npm install, build, and test
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
npm run build --if-present
|
|
||||||
npm run test --if-present
|
|
||||||
|
|
||||||
- name: Upload artifact for deployment job
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: node-app
|
|
||||||
path: .
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
permissions:
|
|
||||||
contents: none
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build
|
|
||||||
environment:
|
|
||||||
name: 'Development'
|
|
||||||
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download artifact from build job
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: node-app
|
|
||||||
|
|
||||||
- name: 'Deploy to Azure WebApp'
|
|
||||||
id: deploy-to-webapp
|
|
||||||
uses: azure/webapps-deploy@v2
|
|
||||||
with:
|
|
||||||
app-name: ${{ env.AZURE_WEBAPP_NAME }}
|
|
||||||
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
|
|
||||||
package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
|
|
||||||
2
libs/common/src/constants/default.profile.picture.ts
Normal file
2
libs/common/src/constants/default.profile.picture.ts
Normal file
File diff suppressed because one or more lines are too long
197
libs/common/src/constants/regions.ts
Normal file
197
libs/common/src/constants/regions.ts
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
export const allCountries = [
|
||||||
|
'Afghanistan',
|
||||||
|
'Albania',
|
||||||
|
'Algeria',
|
||||||
|
'Andorra',
|
||||||
|
'Angola',
|
||||||
|
'Antigua and Barbuda',
|
||||||
|
'Argentina',
|
||||||
|
'Armenia',
|
||||||
|
'Australia',
|
||||||
|
'Austria',
|
||||||
|
'Azerbaijan',
|
||||||
|
'Bahamas',
|
||||||
|
'Bahrain',
|
||||||
|
'Bangladesh',
|
||||||
|
'Barbados',
|
||||||
|
'Belarus',
|
||||||
|
'Belgium',
|
||||||
|
'Belize',
|
||||||
|
'Benin',
|
||||||
|
'Bhutan',
|
||||||
|
'Bolivia',
|
||||||
|
'Bosnia and Herzegovina',
|
||||||
|
'Botswana',
|
||||||
|
'Brazil',
|
||||||
|
'Brunei',
|
||||||
|
'Bulgaria',
|
||||||
|
'Burkina Faso',
|
||||||
|
'Burundi',
|
||||||
|
'Cabo Verde',
|
||||||
|
'Cambodia',
|
||||||
|
'Cameroon',
|
||||||
|
'Canada',
|
||||||
|
'Central African Republic',
|
||||||
|
'Chad',
|
||||||
|
'Chile',
|
||||||
|
'China',
|
||||||
|
'Colombia',
|
||||||
|
'Comoros',
|
||||||
|
'Congo (Congo-Brazzaville)',
|
||||||
|
'Costa Rica',
|
||||||
|
'Croatia',
|
||||||
|
'Cuba',
|
||||||
|
'Cyprus',
|
||||||
|
'Czech Republic (Czechia)',
|
||||||
|
'Democratic Republic of the Congo',
|
||||||
|
'Denmark',
|
||||||
|
'Djibouti',
|
||||||
|
'Dominica',
|
||||||
|
'Dominican Republic',
|
||||||
|
'Ecuador',
|
||||||
|
'Egypt',
|
||||||
|
'El Salvador',
|
||||||
|
'Equatorial Guinea',
|
||||||
|
'Eritrea',
|
||||||
|
'Estonia',
|
||||||
|
'Eswatini (fmr. "Swaziland")',
|
||||||
|
'Ethiopia',
|
||||||
|
'Fiji',
|
||||||
|
'Finland',
|
||||||
|
'France',
|
||||||
|
'Gabon',
|
||||||
|
'Gambia',
|
||||||
|
'Georgia',
|
||||||
|
'Germany',
|
||||||
|
'Ghana',
|
||||||
|
'Greece',
|
||||||
|
'Grenada',
|
||||||
|
'Guatemala',
|
||||||
|
'Guinea',
|
||||||
|
'Guinea-Bissau',
|
||||||
|
'Guyana',
|
||||||
|
'Haiti',
|
||||||
|
'Honduras',
|
||||||
|
'Hungary',
|
||||||
|
'Iceland',
|
||||||
|
'India',
|
||||||
|
'Indonesia',
|
||||||
|
'Iran',
|
||||||
|
'Iraq',
|
||||||
|
'Ireland',
|
||||||
|
'Israel',
|
||||||
|
'Italy',
|
||||||
|
'Jamaica',
|
||||||
|
'Japan',
|
||||||
|
'Jordan',
|
||||||
|
'Kazakhstan',
|
||||||
|
'Kenya',
|
||||||
|
'Kiribati',
|
||||||
|
'Kuwait',
|
||||||
|
'Kyrgyzstan',
|
||||||
|
'Laos',
|
||||||
|
'Latvia',
|
||||||
|
'Lebanon',
|
||||||
|
'Lesotho',
|
||||||
|
'Liberia',
|
||||||
|
'Libya',
|
||||||
|
'Liechtenstein',
|
||||||
|
'Lithuania',
|
||||||
|
'Luxembourg',
|
||||||
|
'Madagascar',
|
||||||
|
'Malawi',
|
||||||
|
'Malaysia',
|
||||||
|
'Maldives',
|
||||||
|
'Mali',
|
||||||
|
'Malta',
|
||||||
|
'Marshall Islands',
|
||||||
|
'Mauritania',
|
||||||
|
'Mauritius',
|
||||||
|
'Mexico',
|
||||||
|
'Micronesia',
|
||||||
|
'Moldova',
|
||||||
|
'Monaco',
|
||||||
|
'Mongolia',
|
||||||
|
'Montenegro',
|
||||||
|
'Morocco',
|
||||||
|
'Mozambique',
|
||||||
|
'Myanmar (formerly Burma)',
|
||||||
|
'Namibia',
|
||||||
|
'Nauru',
|
||||||
|
'Nepal',
|
||||||
|
'Netherlands',
|
||||||
|
'New Zealand',
|
||||||
|
'Nicaragua',
|
||||||
|
'Niger',
|
||||||
|
'Nigeria',
|
||||||
|
'North Korea',
|
||||||
|
'North Macedonia (formerly Macedonia)',
|
||||||
|
'Norway',
|
||||||
|
'Oman',
|
||||||
|
'Pakistan',
|
||||||
|
'Palau',
|
||||||
|
'Palestine State',
|
||||||
|
'Panama',
|
||||||
|
'Papua New Guinea',
|
||||||
|
'Paraguay',
|
||||||
|
'Peru',
|
||||||
|
'Philippines',
|
||||||
|
'Poland',
|
||||||
|
'Portugal',
|
||||||
|
'Qatar',
|
||||||
|
'Romania',
|
||||||
|
'Russia',
|
||||||
|
'Rwanda',
|
||||||
|
'Saint Kitts and Nevis',
|
||||||
|
'Saint Lucia',
|
||||||
|
'Saint Vincent and the Grenadines',
|
||||||
|
'Samoa',
|
||||||
|
'San Marino',
|
||||||
|
'Sao Tome and Principe',
|
||||||
|
'Saudi Arabia',
|
||||||
|
'Senegal',
|
||||||
|
'Serbia',
|
||||||
|
'Seychelles',
|
||||||
|
'Sierra Leone',
|
||||||
|
'Singapore',
|
||||||
|
'Slovakia',
|
||||||
|
'Slovenia',
|
||||||
|
'Solomon Islands',
|
||||||
|
'Somalia',
|
||||||
|
'South Africa',
|
||||||
|
'South Korea',
|
||||||
|
'South Sudan',
|
||||||
|
'Spain',
|
||||||
|
'Sri Lanka',
|
||||||
|
'Sudan',
|
||||||
|
'Suriname',
|
||||||
|
'Sweden',
|
||||||
|
'Switzerland',
|
||||||
|
'Syria',
|
||||||
|
'Taiwan',
|
||||||
|
'Tajikistan',
|
||||||
|
'Tanzania',
|
||||||
|
'Thailand',
|
||||||
|
'Timor-Leste',
|
||||||
|
'Togo',
|
||||||
|
'Tonga',
|
||||||
|
'Trinidad and Tobago',
|
||||||
|
'Tunisia',
|
||||||
|
'Turkey',
|
||||||
|
'Turkmenistan',
|
||||||
|
'Tuvalu',
|
||||||
|
'Uganda',
|
||||||
|
'Ukraine',
|
||||||
|
'United Arab Emirates',
|
||||||
|
'United Kingdom',
|
||||||
|
'United States of America',
|
||||||
|
'Uruguay',
|
||||||
|
'Uzbekistan',
|
||||||
|
'Vanuatu',
|
||||||
|
'Vatican City',
|
||||||
|
'Venezuela',
|
||||||
|
'Vietnam',
|
||||||
|
'Yemen',
|
||||||
|
'Zambia',
|
||||||
|
'Zimbabwe',
|
||||||
|
];
|
||||||
126
libs/common/src/constants/timezones.ts
Normal file
126
libs/common/src/constants/timezones.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
export const allTimeZones = [
|
||||||
|
{ name: 'International Date Line West', offset: 'GMT-12:00' },
|
||||||
|
{ name: 'Hawaii', offset: 'GMT-10:00' },
|
||||||
|
{ name: 'Alaska', offset: 'GMT-09:00' },
|
||||||
|
{ name: 'Pacific Time (US & Canada)', offset: 'GMT-08:00' },
|
||||||
|
{ name: 'Arizona', offset: 'GMT-07:00' },
|
||||||
|
{ name: 'Mountain Time (US & Canada)', offset: 'GMT-07:00' },
|
||||||
|
{ name: 'Central Time (US & Canada)', offset: 'GMT-06:00' },
|
||||||
|
{ name: 'Mexico City', offset: 'GMT-06:00' },
|
||||||
|
{ name: 'Saskatchewan', offset: 'GMT-06:00' },
|
||||||
|
{ name: 'Eastern Time (US & Canada)', offset: 'GMT-05:00' },
|
||||||
|
{ name: 'Bogota', offset: 'GMT-05:00' },
|
||||||
|
{ name: 'Indiana (East)', offset: 'GMT-05:00' },
|
||||||
|
{ name: 'Atlantic Time (Canada)', offset: 'GMT-04:00' },
|
||||||
|
{ name: 'Caracas', offset: 'GMT-04:00' },
|
||||||
|
{ name: 'Santiago', offset: 'GMT-04:00' },
|
||||||
|
{ name: 'Newfoundland', offset: 'GMT-03:30' },
|
||||||
|
{ name: 'Brasilia', offset: 'GMT-03:00' },
|
||||||
|
{ name: 'Buenos Aires', offset: 'GMT-03:00' },
|
||||||
|
{ name: 'Greenland', offset: 'GMT-03:00' },
|
||||||
|
{ name: 'Mid-Atlantic', offset: 'GMT-02:00' },
|
||||||
|
{ name: 'Azores', offset: 'GMT-01:00' },
|
||||||
|
{ name: 'Cape Verde Is.', offset: 'GMT-01:00' },
|
||||||
|
{ name: 'Casablanca', offset: 'GMT+00:00' },
|
||||||
|
{ name: 'Dublin', offset: 'GMT+00:00' },
|
||||||
|
{ name: 'Lisbon', offset: 'GMT+00:00' },
|
||||||
|
{ name: 'London', offset: 'GMT+00:00' },
|
||||||
|
{ name: 'Monrovia', offset: 'GMT+00:00' },
|
||||||
|
{ name: 'Amsterdam', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Belgrade', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Berlin', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Bratislava', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Brussels', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Budapest', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Copenhagen', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Madrid', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Paris', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Prague', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Rome', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Sarajevo', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Warsaw', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'West Central Africa', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Zagreb', offset: 'GMT+01:00' },
|
||||||
|
{ name: 'Athens', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Bucharest', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Cairo', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Harare', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Helsinki', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Jerusalem', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Kaliningrad', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Kyiv', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Pretoria', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Riga', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Sofia', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Tallinn', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Vilnius', offset: 'GMT+02:00' },
|
||||||
|
{ name: 'Baghdad', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Istanbul', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Kuwait', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Minsk', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Moscow', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Nairobi', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Riyadh', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'St. Petersburg', offset: 'GMT+03:00' },
|
||||||
|
{ name: 'Tehran', offset: 'GMT+03:30' },
|
||||||
|
{ name: 'Abu Dhabi', offset: 'GMT+04:00' },
|
||||||
|
{ name: 'Baku', offset: 'GMT+04:00' },
|
||||||
|
{ name: 'Muscat', offset: 'GMT+04:00' },
|
||||||
|
{ name: 'Tbilisi', offset: 'GMT+04:00' },
|
||||||
|
{ name: 'Yerevan', offset: 'GMT+04:00' },
|
||||||
|
{ name: 'Kabul', offset: 'GMT+04:30' },
|
||||||
|
{ name: 'Ekaterinburg', offset: 'GMT+05:00' },
|
||||||
|
{ name: 'Islamabad', offset: 'GMT+05:00' },
|
||||||
|
{ name: 'Karachi', offset: 'GMT+05:00' },
|
||||||
|
{ name: 'Tashkent', offset: 'GMT+05:00' },
|
||||||
|
{ name: 'Chennai', offset: 'GMT+05:30' },
|
||||||
|
{ name: 'Kolkata', offset: 'GMT+05:30' },
|
||||||
|
{ name: 'Mumbai', offset: 'GMT+05:30' },
|
||||||
|
{ name: 'New Delhi', offset: 'GMT+05:30' },
|
||||||
|
{ name: 'Sri Jayawardenepura', offset: 'GMT+05:30' },
|
||||||
|
{ name: 'Kathmandu', offset: 'GMT+05:45' },
|
||||||
|
{ name: 'Almaty', offset: 'GMT+06:00' },
|
||||||
|
{ name: 'Astana', offset: 'GMT+06:00' },
|
||||||
|
{ name: 'Dhaka', offset: 'GMT+06:00' },
|
||||||
|
{ name: 'Urumqi', offset: 'GMT+06:00' },
|
||||||
|
{ name: 'Rangoon', offset: 'GMT+06:30' },
|
||||||
|
{ name: 'Bangkok', offset: 'GMT+07:00' },
|
||||||
|
{ name: 'Hanoi', offset: 'GMT+07:00' },
|
||||||
|
{ name: 'Jakarta', offset: 'GMT+07:00' },
|
||||||
|
{ name: 'Krasnoyarsk', offset: 'GMT+07:00' },
|
||||||
|
{ name: 'Beijing', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Chongqing', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Hong Kong', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Irkutsk', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Kuala Lumpur', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Perth', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Singapore', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Taipei', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Ulaan Bataar', offset: 'GMT+08:00' },
|
||||||
|
{ name: 'Osaka', offset: 'GMT+09:00' },
|
||||||
|
{ name: 'Sapporo', offset: 'GMT+09:00' },
|
||||||
|
{ name: 'Seoul', offset: 'GMT+09:00' },
|
||||||
|
{ name: 'Tokyo', offset: 'GMT+09:00' },
|
||||||
|
{ name: 'Yakutsk', offset: 'GMT+09:00' },
|
||||||
|
{ name: 'Adelaide', offset: 'GMT+09:30' },
|
||||||
|
{ name: 'Darwin', offset: 'GMT+09:30' },
|
||||||
|
{ name: 'Brisbane', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Canberra', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Guam', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Hobart', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Melbourne', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Port Moresby', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Sydney', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Vladivostok', offset: 'GMT+10:00' },
|
||||||
|
{ name: 'Magadan', offset: 'GMT+11:00' },
|
||||||
|
{ name: 'New Caledonia', offset: 'GMT+11:00' },
|
||||||
|
{ name: 'Solomon Is.', offset: 'GMT+11:00' },
|
||||||
|
{ name: 'Kamchatka', offset: 'GMT+12:00' },
|
||||||
|
{ name: 'Marshall Is.', offset: 'GMT+12:00' },
|
||||||
|
{ name: 'Fiji', offset: 'GMT+12:00' },
|
||||||
|
{ name: 'Auckland', offset: 'GMT+12:00' },
|
||||||
|
{ name: 'Wellington', offset: 'GMT+12:00' },
|
||||||
|
{ name: "Nuku'alofa", offset: 'GMT+13:00' },
|
||||||
|
{ name: 'Samoa', offset: 'GMT+13:00' },
|
||||||
|
{ name: 'Tokelau Is.', offset: 'GMT+13:00' },
|
||||||
|
];
|
||||||
@ -16,6 +16,8 @@ import { UserRoleEntity } from '../modules/user-role/entities';
|
|||||||
import { RoleTypeEntity } from '../modules/role-type/entities';
|
import { RoleTypeEntity } from '../modules/role-type/entities';
|
||||||
import { UserNotificationEntity } from '../modules/user-notification/entities';
|
import { UserNotificationEntity } from '../modules/user-notification/entities';
|
||||||
import { DeviceNotificationEntity } from '../modules/device-notification/entities';
|
import { DeviceNotificationEntity } from '../modules/device-notification/entities';
|
||||||
|
import { RegionEntity } from '../modules/region/entities';
|
||||||
|
import { TimeZoneEntity } from '../modules/timezone/entities';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -46,6 +48,8 @@ import { DeviceNotificationEntity } from '../modules/device-notification/entitie
|
|||||||
RoleTypeEntity,
|
RoleTypeEntity,
|
||||||
UserNotificationEntity,
|
UserNotificationEntity,
|
||||||
DeviceNotificationEntity,
|
DeviceNotificationEntity,
|
||||||
|
RegionEntity,
|
||||||
|
TimeZoneEntity,
|
||||||
],
|
],
|
||||||
namingStrategy: new SnakeNamingStrategy(),
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||||
|
|||||||
3
libs/common/src/helper/removeBase64Prefix.ts
Normal file
3
libs/common/src/helper/removeBase64Prefix.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function removeBase64Prefix(dataUrl: string): string {
|
||||||
|
return dataUrl.replace(/^data:image\/[a-z]+;base64,/, '');
|
||||||
|
}
|
||||||
@ -4,12 +4,12 @@ function toSnakeCase(str) {
|
|||||||
|
|
||||||
export function convertKeysToSnakeCase(obj) {
|
export function convertKeysToSnakeCase(obj) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map((v) => convertKeysToSnakeCase(v));
|
return obj.map(convertKeysToSnakeCase);
|
||||||
} else if (obj !== null && obj.constructor === Object) {
|
} else if (obj !== null && typeof obj === 'object') {
|
||||||
return Object.keys(obj).reduce((result, key) => {
|
return Object.keys(obj).reduce((acc, key) => {
|
||||||
const snakeKey = toSnakeCase(key);
|
const snakeKey = toSnakeCase(key);
|
||||||
result[snakeKey] = convertKeysToSnakeCase(obj[key]);
|
acc[snakeKey] = convertKeysToSnakeCase(obj[key]);
|
||||||
return result;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
import { Column, Entity, ManyToOne, OneToMany, Unique, Index } from 'typeorm';
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { DeviceDto } from '../dtos/device.dto';
|
import { DeviceDto } from '../dtos/device.dto';
|
||||||
import { SpaceEntity } from '../../space/entities';
|
import { SpaceEntity } from '../../space/entities';
|
||||||
@ -50,6 +50,11 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
productDevice: ProductEntity;
|
productDevice: ProductEntity;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ nullable: false })
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
constructor(partial: Partial<DeviceEntity>) {
|
constructor(partial: Partial<DeviceEntity>) {
|
||||||
super();
|
super();
|
||||||
Object.assign(this, partial);
|
Object.assign(this, partial);
|
||||||
|
|||||||
1
libs/common/src/modules/region/dtos/index.ts
Normal file
1
libs/common/src/modules/region/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './region.dto';
|
||||||
11
libs/common/src/modules/region/dtos/region.dto.ts
Normal file
11
libs/common/src/modules/region/dtos/region.dto.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class RegionDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public regionName: string;
|
||||||
|
}
|
||||||
1
libs/common/src/modules/region/entities/index.ts
Normal file
1
libs/common/src/modules/region/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './region.entity';
|
||||||
20
libs/common/src/modules/region/entities/region.entity.ts
Normal file
20
libs/common/src/modules/region/entities/region.entity.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Column, Entity, OneToMany } from 'typeorm';
|
||||||
|
import { RegionDto } from '../dtos';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { UserEntity } from '../../user/entities';
|
||||||
|
|
||||||
|
@Entity({ name: 'region' })
|
||||||
|
export class RegionEntity extends AbstractEntity<RegionDto> {
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
regionName: string;
|
||||||
|
|
||||||
|
@OneToMany(() => UserEntity, (user) => user.region)
|
||||||
|
users: UserEntity[];
|
||||||
|
|
||||||
|
constructor(partial: Partial<RegionEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
libs/common/src/modules/region/region.repository.module.ts
Normal file
11
libs/common/src/modules/region/region.repository.module.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { RegionEntity } from './entities/region.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([RegionEntity])],
|
||||||
|
})
|
||||||
|
export class RegionRepositoryModule {}
|
||||||
1
libs/common/src/modules/region/repositories/index.ts
Normal file
1
libs/common/src/modules/region/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './region.repository';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { RegionEntity } from '../entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RegionRepository extends Repository<RegionEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(RegionEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
1
libs/common/src/modules/timezone/dtos/index.ts
Normal file
1
libs/common/src/modules/timezone/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './timezone.dto';
|
||||||
15
libs/common/src/modules/timezone/dtos/timezone.dto.ts
Normal file
15
libs/common/src/modules/timezone/dtos/timezone.dto.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class TimeZoneDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public cityName: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public timeZoneOffset: string;
|
||||||
|
}
|
||||||
1
libs/common/src/modules/timezone/entities/index.ts
Normal file
1
libs/common/src/modules/timezone/entities/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './timezone.entity';
|
||||||
23
libs/common/src/modules/timezone/entities/timezone.entity.ts
Normal file
23
libs/common/src/modules/timezone/entities/timezone.entity.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Column, Entity, OneToMany } from 'typeorm';
|
||||||
|
import { TimeZoneDto } from '../dtos';
|
||||||
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
|
import { UserEntity } from '../../user/entities';
|
||||||
|
|
||||||
|
@Entity({ name: 'timezone' })
|
||||||
|
export class TimeZoneEntity extends AbstractEntity<TimeZoneDto> {
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
cityName: string;
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
timeZoneOffset: string;
|
||||||
|
@OneToMany(() => UserEntity, (user) => user.timezone)
|
||||||
|
users: UserEntity[];
|
||||||
|
|
||||||
|
constructor(partial: Partial<TimeZoneEntity>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
libs/common/src/modules/timezone/repositories/index.ts
Normal file
1
libs/common/src/modules/timezone/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './timezone.repository';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TimeZoneEntity } from '../entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TimeZoneRepository extends Repository<TimeZoneEntity> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(TimeZoneEntity, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TimeZoneEntity } from './entities/timezone.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [],
|
||||||
|
exports: [],
|
||||||
|
controllers: [],
|
||||||
|
imports: [TypeOrmModule.forFeature([TimeZoneEntity])],
|
||||||
|
})
|
||||||
|
export class TimeZoneRepositoryModule {}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { DeviceUserPermissionEntity } from '../../device-user-permission/entities/device.user.permission.entity';
|
import { DeviceUserPermissionEntity } from '../../device-user-permission/entities/device.user.permission.entity';
|
||||||
import { Column, Entity, OneToMany } from 'typeorm';
|
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
||||||
import { UserDto } from '../dtos';
|
import { UserDto } from '../dtos';
|
||||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||||
import { UserSpaceEntity } from '../../user-space/entities';
|
import { UserSpaceEntity } from '../../user-space/entities';
|
||||||
@ -7,6 +7,9 @@ import { UserRoleEntity } from '../../user-role/entities';
|
|||||||
import { DeviceNotificationEntity } from '../../device-notification/entities';
|
import { DeviceNotificationEntity } from '../../device-notification/entities';
|
||||||
import { UserNotificationEntity } from '../../user-notification/entities';
|
import { UserNotificationEntity } from '../../user-notification/entities';
|
||||||
import { DeviceEntity } from '../../device/entities';
|
import { DeviceEntity } from '../../device/entities';
|
||||||
|
import { defaultProfilePicture } from '@app/common/constants/default.profile.picture';
|
||||||
|
import { RegionEntity } from '../../region/entities';
|
||||||
|
import { TimeZoneEntity } from '../../timezone/entities';
|
||||||
|
|
||||||
@Entity({ name: 'user' })
|
@Entity({ name: 'user' })
|
||||||
export class UserEntity extends AbstractEntity<UserDto> {
|
export class UserEntity extends AbstractEntity<UserDto> {
|
||||||
@ -17,6 +20,13 @@ export class UserEntity extends AbstractEntity<UserDto> {
|
|||||||
})
|
})
|
||||||
public uuid: string;
|
public uuid: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: true,
|
||||||
|
type: 'text',
|
||||||
|
default: defaultProfilePicture,
|
||||||
|
})
|
||||||
|
public profilePicture: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
unique: true,
|
unique: true,
|
||||||
@ -78,6 +88,12 @@ export class UserEntity extends AbstractEntity<UserDto> {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
roles: UserRoleEntity[];
|
roles: UserRoleEntity[];
|
||||||
|
@ManyToOne(() => RegionEntity, (region) => region.users, { nullable: true })
|
||||||
|
region: RegionEntity;
|
||||||
|
@ManyToOne(() => TimeZoneEntity, (timezone) => timezone.users, {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
timezone: TimeZoneEntity;
|
||||||
constructor(partial: Partial<UserEntity>) {
|
constructor(partial: Partial<UserEntity>) {
|
||||||
super();
|
super();
|
||||||
Object.assign(this, partial);
|
Object.assign(this, partial);
|
||||||
|
|||||||
@ -15,6 +15,10 @@ import { UserRepository } from '../modules/user/repositories';
|
|||||||
import { UserRoleRepository } from '../modules/user-role/repositories';
|
import { UserRoleRepository } from '../modules/user-role/repositories';
|
||||||
import { UserRoleRepositoryModule } from '../modules/user-role/user.role.repository.module';
|
import { UserRoleRepositoryModule } from '../modules/user-role/user.role.repository.module';
|
||||||
import { UserRepositoryModule } from '../modules/user/user.repository.module';
|
import { UserRepositoryModule } from '../modules/user/user.repository.module';
|
||||||
|
import { RegionSeeder } from './services/regions.seeder';
|
||||||
|
import { RegionRepository } from '../modules/region/repositories';
|
||||||
|
import { TimeZoneSeeder } from './services/timezone.seeder';
|
||||||
|
import { TimeZoneRepository } from '../modules/timezone/repositories';
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
@ -28,6 +32,10 @@ import { UserRepositoryModule } from '../modules/user/user.repository.module';
|
|||||||
SuperAdminSeeder,
|
SuperAdminSeeder,
|
||||||
UserRepository,
|
UserRepository,
|
||||||
UserRoleRepository,
|
UserRoleRepository,
|
||||||
|
RegionSeeder,
|
||||||
|
RegionRepository,
|
||||||
|
TimeZoneSeeder,
|
||||||
|
TimeZoneRepository,
|
||||||
],
|
],
|
||||||
exports: [SeederService],
|
exports: [SeederService],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
|
|||||||
40
libs/common/src/seed/services/regions.seeder.ts
Normal file
40
libs/common/src/seed/services/regions.seeder.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||||
|
import { allCountries } from '@app/common/constants/regions';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RegionSeeder {
|
||||||
|
constructor(private readonly regionRepository: RegionRepository) {}
|
||||||
|
|
||||||
|
async addRegionDataIfNotFound(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const existingRegions = await this.regionRepository.find();
|
||||||
|
|
||||||
|
const regionNames = existingRegions.map((region) => region.regionName);
|
||||||
|
|
||||||
|
const missingRegions = allCountries.filter(
|
||||||
|
(country) => !regionNames.includes(country),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (missingRegions.length > 0) {
|
||||||
|
await this.addRegionData(missingRegions);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error while checking region data:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addRegionData(regions: string[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
const regionEntities = regions.map((regionName) => ({
|
||||||
|
regionName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await this.regionRepository.save(regionEntities);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error while adding region data:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,12 +3,16 @@ import { PermissionTypeSeeder } from './permission.type.seeder';
|
|||||||
import { RoleTypeSeeder } from './role.type.seeder';
|
import { RoleTypeSeeder } from './role.type.seeder';
|
||||||
import { SpaceTypeSeeder } from './space.type.seeder';
|
import { SpaceTypeSeeder } from './space.type.seeder';
|
||||||
import { SuperAdminSeeder } from './supper.admin.seeder';
|
import { SuperAdminSeeder } from './supper.admin.seeder';
|
||||||
|
import { RegionSeeder } from './regions.seeder';
|
||||||
|
import { TimeZoneSeeder } from './timezone.seeder';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SeederService {
|
export class SeederService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly permissionTypeSeeder: PermissionTypeSeeder,
|
private readonly permissionTypeSeeder: PermissionTypeSeeder,
|
||||||
private readonly roleTypeSeeder: RoleTypeSeeder,
|
private readonly roleTypeSeeder: RoleTypeSeeder,
|
||||||
private readonly spaceTypeSeeder: SpaceTypeSeeder,
|
private readonly spaceTypeSeeder: SpaceTypeSeeder,
|
||||||
|
private readonly regionSeeder: RegionSeeder,
|
||||||
|
private readonly timeZoneSeeder: TimeZoneSeeder,
|
||||||
private readonly superAdminSeeder: SuperAdminSeeder,
|
private readonly superAdminSeeder: SuperAdminSeeder,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -16,6 +20,8 @@ export class SeederService {
|
|||||||
await this.permissionTypeSeeder.addPermissionTypeDataIfNotFound();
|
await this.permissionTypeSeeder.addPermissionTypeDataIfNotFound();
|
||||||
await this.roleTypeSeeder.addRoleTypeDataIfNotFound();
|
await this.roleTypeSeeder.addRoleTypeDataIfNotFound();
|
||||||
await this.spaceTypeSeeder.addSpaceTypeDataIfNotFound();
|
await this.spaceTypeSeeder.addSpaceTypeDataIfNotFound();
|
||||||
|
await this.regionSeeder.addRegionDataIfNotFound();
|
||||||
|
await this.timeZoneSeeder.addTimeZoneDataIfNotFound();
|
||||||
await this.superAdminSeeder.createSuperAdminIfNotFound();
|
await this.superAdminSeeder.createSuperAdminIfNotFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
libs/common/src/seed/services/timezone.seeder.ts
Normal file
42
libs/common/src/seed/services/timezone.seeder.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||||
|
import { allTimeZones } from '@app/common/constants/timezones';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TimeZoneSeeder {
|
||||||
|
constructor(private readonly timeZoneRepository: TimeZoneRepository) {}
|
||||||
|
|
||||||
|
async addTimeZoneDataIfNotFound(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const existingTimeZones = await this.timeZoneRepository.find();
|
||||||
|
const timeZoneNames = existingTimeZones.map((tz) => tz.cityName);
|
||||||
|
|
||||||
|
const missingTimeZones = allTimeZones.filter(
|
||||||
|
(tz) => !timeZoneNames.includes(tz.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (missingTimeZones.length > 0) {
|
||||||
|
await this.addTimeZoneData(missingTimeZones);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error while checking time zone data:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addTimeZoneData(
|
||||||
|
timeZones: { name: string; offset: string }[],
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const timeZoneEntities = timeZones.map((tz) => ({
|
||||||
|
cityName: tz.name,
|
||||||
|
timeZoneOffset: tz.offset,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await this.timeZoneRepository.save(timeZoneEntities);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error while adding time zone data:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,9 @@ import { SceneModule } from './scene/scene.module';
|
|||||||
import { DoorLockModule } from './door-lock/door.lock.module';
|
import { DoorLockModule } from './door-lock/door.lock.module';
|
||||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
import { LoggingInterceptor } from './interceptors/logging.interceptor';
|
import { LoggingInterceptor } from './interceptors/logging.interceptor';
|
||||||
|
import { AutomationModule } from './automation/automation.module';
|
||||||
|
import { RegionModule } from './region/region.module';
|
||||||
|
import { TimeZoneModule } from './timezone/timezone.module';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
@ -41,8 +44,10 @@ import { LoggingInterceptor } from './interceptors/logging.interceptor';
|
|||||||
UserNotificationModule,
|
UserNotificationModule,
|
||||||
SeederModule,
|
SeederModule,
|
||||||
SceneModule,
|
SceneModule,
|
||||||
|
AutomationModule,
|
||||||
DoorLockModule,
|
DoorLockModule,
|
||||||
//
|
RegionModule,
|
||||||
|
TimeZoneModule,
|
||||||
],
|
],
|
||||||
controllers: [AuthenticationController],
|
controllers: [AuthenticationController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { ApiTags } from '@nestjs/swagger';
|
|||||||
@ApiTags('Tuya Auth')
|
@ApiTags('Tuya Auth')
|
||||||
export class AuthenticationController {
|
export class AuthenticationController {
|
||||||
constructor(private readonly authenticationService: AuthenticationService) {}
|
constructor(private readonly authenticationService: AuthenticationService) {}
|
||||||
@Post('auth')
|
@Post('auth2')
|
||||||
async Authentication() {
|
async Authentication() {
|
||||||
return await this.authenticationService.main();
|
return await this.authenticationService.main();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { IsPasswordStrong } from 'src/validators/password.validator';
|
||||||
|
|
||||||
export class UserSignUpDto {
|
export class UserSignUpDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
@ -16,6 +17,10 @@ export class UserSignUpDto {
|
|||||||
})
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
@IsPasswordStrong({
|
||||||
|
message:
|
||||||
|
'password must be at least 8 characters long and include at least one uppercase letter, one lowercase letter, one numeric digit, and one special character.',
|
||||||
|
})
|
||||||
public password: string;
|
public password: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
|
|||||||
@ -1,14 +1,25 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { IsPasswordStrong } from 'src/validators/password.validator';
|
||||||
|
|
||||||
export class ForgetPasswordDto {
|
export class ForgetPasswordDto {
|
||||||
@ApiProperty()
|
@ApiProperty({
|
||||||
|
description: 'email',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
email: string;
|
public email: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({
|
||||||
|
description: 'password',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
password: string;
|
@IsPasswordStrong({
|
||||||
|
message:
|
||||||
|
'password must be at least 8 characters long and include at least one uppercase letter, one lowercase letter, one numeric digit, and one special character.',
|
||||||
|
})
|
||||||
|
public password: string;
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/automation/automation.module.ts
Normal file
23
src/automation/automation.module.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AutomationService } from './services/automation.service';
|
||||||
|
import { AutomationController } from './controllers/automation.controller';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
|
import { ProductRepository } from '@app/common/modules/product/repositories';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule, SpaceRepositoryModule],
|
||||||
|
controllers: [AutomationController],
|
||||||
|
providers: [
|
||||||
|
AutomationService,
|
||||||
|
SpaceRepository,
|
||||||
|
DeviceService,
|
||||||
|
DeviceRepository,
|
||||||
|
ProductRepository,
|
||||||
|
],
|
||||||
|
exports: [AutomationService],
|
||||||
|
})
|
||||||
|
export class AutomationModule {}
|
||||||
150
src/automation/controllers/automation.controller.ts
Normal file
150
src/automation/controllers/automation.controller.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { AutomationService } from '../services/automation.service';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
AddAutomationDto,
|
||||||
|
UpdateAutomationDto,
|
||||||
|
UpdateAutomationStatusDto,
|
||||||
|
} from '../dtos/automation.dto';
|
||||||
|
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||||
|
|
||||||
|
@ApiTags('Automation Module')
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'automation',
|
||||||
|
})
|
||||||
|
export class AutomationController {
|
||||||
|
constructor(private readonly automationService: AutomationService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Post()
|
||||||
|
async addAutomation(@Body() addAutomationDto: AddAutomationDto) {
|
||||||
|
try {
|
||||||
|
const automation =
|
||||||
|
await this.automationService.addAutomation(addAutomationDto);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'Automation added successfully',
|
||||||
|
data: automation,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get(':unitUuid')
|
||||||
|
async getAutomationByUnit(@Param('unitUuid') unitUuid: string) {
|
||||||
|
try {
|
||||||
|
const automation =
|
||||||
|
await this.automationService.getAutomationByUnit(unitUuid);
|
||||||
|
return automation;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get('details/:automationId')
|
||||||
|
async getAutomationDetails(@Param('automationId') automationId: string) {
|
||||||
|
try {
|
||||||
|
const automation =
|
||||||
|
await this.automationService.getAutomationDetails(automationId);
|
||||||
|
return automation;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
``;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Delete(':unitUuid/:automationId')
|
||||||
|
async deleteAutomation(
|
||||||
|
@Param('unitUuid') unitUuid: string,
|
||||||
|
@Param('automationId') automationId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.automationService.deleteAutomation(unitUuid, automationId);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
message: 'Automation Deleted Successfully',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Put(':automationId')
|
||||||
|
async updateAutomation(
|
||||||
|
@Body() updateAutomationDto: UpdateAutomationDto,
|
||||||
|
@Param('automationId') automationId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const automation = await this.automationService.updateAutomation(
|
||||||
|
updateAutomationDto,
|
||||||
|
automationId,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'Automation updated successfully',
|
||||||
|
data: automation,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Put('status/:automationId')
|
||||||
|
async updateAutomationStatus(
|
||||||
|
@Body() updateAutomationStatusDto: UpdateAutomationStatusDto,
|
||||||
|
@Param('automationId') automationId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.automationService.updateAutomationStatus(
|
||||||
|
updateAutomationStatusDto,
|
||||||
|
automationId,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'Automation status updated successfully',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/automation/controllers/index.ts
Normal file
1
src/automation/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './automation.controller';
|
||||||
213
src/automation/dtos/automation.dto.ts
Normal file
213
src/automation/dtos/automation.dto.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
IsNotEmpty,
|
||||||
|
IsString,
|
||||||
|
IsArray,
|
||||||
|
ValidateNested,
|
||||||
|
IsOptional,
|
||||||
|
IsNumber,
|
||||||
|
IsBoolean,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
|
class EffectiveTime {
|
||||||
|
@ApiProperty({ description: 'Start time', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public start: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'End time', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public end: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Loops', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public loops: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Expr {
|
||||||
|
@ApiProperty({ description: 'Status code', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public statusCode: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Comparator', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public comparator: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Status value', required: true })
|
||||||
|
@IsNotEmpty()
|
||||||
|
public statusValue: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Condition {
|
||||||
|
@ApiProperty({ description: 'Condition code', required: true })
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public code: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Entity ID', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public entityId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Entity type', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public entityType: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Expression', required: true, type: Expr })
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => Expr)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public expr: Expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExecutorProperty {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Function code (for device issue action)',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
public functionCode?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Function value (for device issue action)',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
public functionValue?: any;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Delay in seconds (for delay action)',
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
@IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
public delaySeconds?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Action {
|
||||||
|
@ApiProperty({ description: 'Entity ID', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public entityId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Action executor', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public actionExecutor: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Executor property',
|
||||||
|
required: false,
|
||||||
|
type: ExecutorProperty,
|
||||||
|
})
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => ExecutorProperty)
|
||||||
|
@IsOptional()
|
||||||
|
public executorProperty?: ExecutorProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddAutomationDto {
|
||||||
|
@ApiProperty({ description: 'Unit ID', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public unitUuid: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Automation name', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public automationName: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Decision expression', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public decisionExpr: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Effective time',
|
||||||
|
required: true,
|
||||||
|
type: EffectiveTime,
|
||||||
|
})
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => EffectiveTime)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public effectiveTime: EffectiveTime;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Conditions', required: true, type: [Condition] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => Condition)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public conditions: Condition[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Actions', required: true, type: [Action] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => Action)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public actions: Action[];
|
||||||
|
|
||||||
|
constructor(dto: Partial<AddAutomationDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateAutomationDto {
|
||||||
|
@ApiProperty({ description: 'Automation name', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public automationName: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Decision expression', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public decisionExpr: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Effective time',
|
||||||
|
required: true,
|
||||||
|
type: EffectiveTime,
|
||||||
|
})
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => EffectiveTime)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public effectiveTime: EffectiveTime;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Conditions', required: true, type: [Condition] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => Condition)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public conditions: Condition[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Actions', required: true, type: [Action] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => Action)
|
||||||
|
@IsNotEmpty()
|
||||||
|
public actions: Action[];
|
||||||
|
constructor(dto: Partial<UpdateAutomationDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class UpdateAutomationStatusDto {
|
||||||
|
@ApiProperty({ description: 'Unit uuid', required: true })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public unitUuid: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Is enable', required: true })
|
||||||
|
@IsBoolean()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public isEnable: boolean;
|
||||||
|
|
||||||
|
constructor(dto: Partial<UpdateAutomationStatusDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/automation/dtos/index.ts
Normal file
1
src/automation/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './automation.dto';
|
||||||
50
src/automation/interface/automation.interface.ts
Normal file
50
src/automation/interface/automation.interface.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
export interface AddAutomationInterface {
|
||||||
|
success: boolean;
|
||||||
|
msg?: string;
|
||||||
|
result: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface GetAutomationByUnitInterface {
|
||||||
|
success: boolean;
|
||||||
|
msg?: string;
|
||||||
|
result: {
|
||||||
|
list: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface DeleteAutomationInterface {
|
||||||
|
success: boolean;
|
||||||
|
msg?: string;
|
||||||
|
result: boolean;
|
||||||
|
}
|
||||||
|
export interface Action {
|
||||||
|
actionExecutor: string;
|
||||||
|
entityId: string;
|
||||||
|
[key: string]: any; // Allow additional properties
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Condition {
|
||||||
|
entityType: string;
|
||||||
|
entityId: string;
|
||||||
|
[key: string]: any; // Allow additional properties
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutomationResponseData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
spaceId?: string;
|
||||||
|
runningMode?: string;
|
||||||
|
actions: Action[];
|
||||||
|
conditions: Condition[];
|
||||||
|
[key: string]: any; // Allow additional properties
|
||||||
|
}
|
||||||
|
export interface AutomationDetailsResult {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
423
src/automation/services/automation.service.ts
Normal file
423
src/automation/services/automation.service.ts
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { SpaceRepository } from '@app/common/modules/space/repositories';
|
||||||
|
import {
|
||||||
|
AddAutomationDto,
|
||||||
|
UpdateAutomationDto,
|
||||||
|
UpdateAutomationStatusDto,
|
||||||
|
} from '../dtos';
|
||||||
|
import { GetUnitByUuidInterface } from 'src/unit/interface/unit.interface';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
||||||
|
import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter';
|
||||||
|
import { DeviceService } from 'src/device/services';
|
||||||
|
import {
|
||||||
|
AddAutomationInterface,
|
||||||
|
AutomationDetailsResult,
|
||||||
|
AutomationResponseData,
|
||||||
|
DeleteAutomationInterface,
|
||||||
|
GetAutomationByUnitInterface,
|
||||||
|
} from '../interface/automation.interface';
|
||||||
|
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AutomationService {
|
||||||
|
private tuya: TuyaContext;
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly spaceRepository: SpaceRepository,
|
||||||
|
private readonly deviceService: DeviceService,
|
||||||
|
) {
|
||||||
|
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
||||||
|
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
||||||
|
const tuyaEuUrl = this.configService.get<string>('tuya-config.TUYA_EU_URL');
|
||||||
|
this.tuya = new TuyaContext({
|
||||||
|
baseUrl: tuyaEuUrl,
|
||||||
|
accessKey,
|
||||||
|
secretKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAutomation(addAutomationDto: AddAutomationDto, spaceTuyaId = null) {
|
||||||
|
try {
|
||||||
|
let unitSpaceTuyaId;
|
||||||
|
if (!spaceTuyaId) {
|
||||||
|
const unitDetails = await this.getUnitByUuid(addAutomationDto.unitUuid);
|
||||||
|
|
||||||
|
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||||
|
if (!unitDetails) {
|
||||||
|
throw new BadRequestException('Invalid unit UUID');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unitSpaceTuyaId = spaceTuyaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = addAutomationDto.actions.map((action) =>
|
||||||
|
convertKeysToSnakeCase(action),
|
||||||
|
);
|
||||||
|
const conditions = addAutomationDto.conditions.map((condition) =>
|
||||||
|
convertKeysToSnakeCase(condition),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const action of actions) {
|
||||||
|
if (action.action_executor === 'device_issue') {
|
||||||
|
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||||
|
action.entity_id,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if (device) {
|
||||||
|
action.entity_id = device.deviceTuyaUuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const condition of conditions) {
|
||||||
|
if (condition.entity_type === 'device_report') {
|
||||||
|
const device = await this.deviceService.getDeviceByDeviceUuid(
|
||||||
|
condition.entity_id,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if (device) {
|
||||||
|
condition.entity_id = device.deviceTuyaUuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `/v2.0/cloud/scene/rule`;
|
||||||
|
const response: AddAutomationInterface = await this.tuya.request({
|
||||||
|
method: 'POST',
|
||||||
|
path,
|
||||||
|
body: {
|
||||||
|
space_id: unitSpaceTuyaId,
|
||||||
|
name: addAutomationDto.automationName,
|
||||||
|
effective_time: {
|
||||||
|
...addAutomationDto.effectiveTime,
|
||||||
|
timezone_id: 'Asia/Dubai',
|
||||||
|
},
|
||||||
|
type: 'automation',
|
||||||
|
decision_expr: addAutomationDto.decisionExpr,
|
||||||
|
conditions: conditions,
|
||||||
|
actions: actions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: response.result.id,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'Automation not found',
|
||||||
|
err.status || HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getUnitByUuid(unitUuid: string): Promise<GetUnitByUuidInterface> {
|
||||||
|
try {
|
||||||
|
const unit = await this.spaceRepository.findOne({
|
||||||
|
where: {
|
||||||
|
uuid: unitUuid,
|
||||||
|
spaceType: {
|
||||||
|
type: 'unit',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relations: ['spaceType'],
|
||||||
|
});
|
||||||
|
if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') {
|
||||||
|
throw new BadRequestException('Invalid unit UUID');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uuid: unit.uuid,
|
||||||
|
createdAt: unit.createdAt,
|
||||||
|
updatedAt: unit.updatedAt,
|
||||||
|
name: unit.spaceName,
|
||||||
|
type: unit.spaceType.type,
|
||||||
|
spaceTuyaUuid: unit.spaceTuyaUuid,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getAutomationByUnit(unitUuid: string) {
|
||||||
|
try {
|
||||||
|
const unit = await this.getUnitByUuid(unitUuid);
|
||||||
|
if (!unit.spaceTuyaUuid) {
|
||||||
|
throw new BadRequestException('Invalid unit UUID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=automation`;
|
||||||
|
const response: GetAutomationByUnitInterface = await this.tuya.request({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.result.list.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
status: item.status,
|
||||||
|
type: 'automation',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'Automation not found',
|
||||||
|
err.status || HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getTapToRunSceneDetailsTuya(
|
||||||
|
sceneId: string,
|
||||||
|
): Promise<AutomationDetailsResult> {
|
||||||
|
try {
|
||||||
|
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
const camelCaseResponse = convertKeysToCamelCase(response);
|
||||||
|
const { id, name, type } = camelCaseResponse.result;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
} as AutomationDetailsResult;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
'Scene not found for Tuya',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getAutomationDetails(automationId: string, withSpaceId = false) {
|
||||||
|
try {
|
||||||
|
const path = `/v2.0/cloud/scene/rule/${automationId}`;
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData: AutomationResponseData = convertKeysToCamelCase(
|
||||||
|
response.result,
|
||||||
|
);
|
||||||
|
|
||||||
|
const actions = responseData.actions.map((action) => ({
|
||||||
|
...action,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const action of actions) {
|
||||||
|
if (action.actionExecutor === 'device_issue') {
|
||||||
|
const device = await this.deviceService.getDeviceByDeviceTuyaUuid(
|
||||||
|
action.entityId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
action.entityId = device.uuid;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
action.actionExecutor !== 'device_issue' &&
|
||||||
|
action.actionExecutor !== 'delay'
|
||||||
|
) {
|
||||||
|
const sceneDetails = await this.getTapToRunSceneDetailsTuya(
|
||||||
|
action.entityId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sceneDetails.id) {
|
||||||
|
action.name = sceneDetails.name;
|
||||||
|
action.type = sceneDetails.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditions = responseData.conditions.map((condition) => ({
|
||||||
|
...condition,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const condition of conditions) {
|
||||||
|
if (condition.entityType === 'device_report') {
|
||||||
|
const device = await this.deviceService.getDeviceByDeviceTuyaUuid(
|
||||||
|
condition.entityId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
condition.entityId = device.uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { timeZoneId, ...effectiveTimeWithoutTimeZoneId } =
|
||||||
|
responseData.effectiveTime || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: responseData.id,
|
||||||
|
name: responseData.name,
|
||||||
|
status: responseData.status,
|
||||||
|
type: 'automation',
|
||||||
|
...(() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { spaceId, runningMode, ...rest } = responseData;
|
||||||
|
return rest;
|
||||||
|
})(),
|
||||||
|
actions,
|
||||||
|
conditions,
|
||||||
|
effectiveTime: effectiveTimeWithoutTimeZoneId, // Use modified effectiveTime
|
||||||
|
...(withSpaceId && { spaceId: responseData.spaceId }),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException('Automation not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAutomation(
|
||||||
|
unitUuid: string,
|
||||||
|
automationId: string,
|
||||||
|
spaceTuyaId = null,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
let unitSpaceTuyaId;
|
||||||
|
if (!spaceTuyaId) {
|
||||||
|
const unitDetails = await this.getUnitByUuid(unitUuid);
|
||||||
|
unitSpaceTuyaId = unitDetails.spaceTuyaUuid;
|
||||||
|
if (!unitSpaceTuyaId) {
|
||||||
|
throw new BadRequestException('Invalid unit UUID');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unitSpaceTuyaId = spaceTuyaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `/v2.0/cloud/scene/rule?ids=${automationId}&space_id=${unitSpaceTuyaId}`;
|
||||||
|
const response: DeleteAutomationInterface = await this.tuya.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException('Automation not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'Automation not found',
|
||||||
|
err.status || HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAutomation(
|
||||||
|
updateAutomationDto: UpdateAutomationDto,
|
||||||
|
automationId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const spaceTuyaId = await this.getAutomationDetails(automationId, true);
|
||||||
|
if (!spaceTuyaId.spaceId) {
|
||||||
|
throw new HttpException(
|
||||||
|
"Automation doesn't exist",
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const addAutomation = {
|
||||||
|
...updateAutomationDto,
|
||||||
|
unitUuid: null,
|
||||||
|
};
|
||||||
|
const newAutomation = await this.addAutomation(
|
||||||
|
addAutomation,
|
||||||
|
spaceTuyaId.spaceId,
|
||||||
|
);
|
||||||
|
if (newAutomation.id) {
|
||||||
|
await this.deleteAutomation(null, automationId, spaceTuyaId.spaceId);
|
||||||
|
return newAutomation;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'Automation not found',
|
||||||
|
err.status || HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async updateAutomationStatus(
|
||||||
|
updateAutomationStatusDto: UpdateAutomationStatusDto,
|
||||||
|
automationId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const unitDetails = await this.getUnitByUuid(
|
||||||
|
updateAutomationStatusDto.unitUuid,
|
||||||
|
);
|
||||||
|
if (!unitDetails.spaceTuyaUuid) {
|
||||||
|
throw new BadRequestException('Invalid unit UUID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `/v2.0/cloud/scene/rule/state?space_id=${unitDetails.spaceTuyaUuid}`;
|
||||||
|
const response: DeleteAutomationInterface = await this.tuya.request({
|
||||||
|
method: 'PUT',
|
||||||
|
path,
|
||||||
|
body: {
|
||||||
|
ids: automationId,
|
||||||
|
is_enable: updateAutomationStatusDto.isEnable,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException('Automation not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'Automation not found',
|
||||||
|
err.status || HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/automation/services/index.ts
Normal file
1
src/automation/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './automation.service';
|
||||||
@ -197,8 +197,15 @@ export class DeviceService {
|
|||||||
where: {
|
where: {
|
||||||
uuid: updateDeviceInRoomDto.deviceUuid,
|
uuid: updateDeviceInRoomDto.deviceUuid,
|
||||||
},
|
},
|
||||||
relations: ['spaceDevice'],
|
relations: ['spaceDevice', 'spaceDevice.parent'],
|
||||||
});
|
});
|
||||||
|
if (device.spaceDevice.parent.spaceTuyaUuid) {
|
||||||
|
await this.transferDeviceInSpacesTuya(
|
||||||
|
device.deviceTuyaUuid,
|
||||||
|
device.spaceDevice.parent.spaceTuyaUuid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uuid: device.uuid,
|
uuid: device.uuid,
|
||||||
roomUuid: device.spaceDevice.uuid,
|
roomUuid: device.spaceDevice.uuid,
|
||||||
@ -210,7 +217,26 @@ export class DeviceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async transferDeviceInSpacesTuya(
|
||||||
|
deviceId: string,
|
||||||
|
spaceId: string,
|
||||||
|
): Promise<controlDeviceInterface> {
|
||||||
|
try {
|
||||||
|
const path = `/v2.0/cloud/thing/${deviceId}/transfer`;
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'POST',
|
||||||
|
path,
|
||||||
|
body: { space_id: spaceId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response as controlDeviceInterface;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
'Error transferring device in spaces from Tuya',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
async controlDevice(controlDeviceDto: ControlDeviceDto, deviceUuid: string) {
|
async controlDevice(controlDeviceDto: ControlDeviceDto, deviceUuid: string) {
|
||||||
try {
|
try {
|
||||||
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false);
|
const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false);
|
||||||
|
|||||||
@ -9,11 +9,13 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
Delete,
|
Delete,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
|
Put,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
import { AddDoorLockOnlineDto } from '../dtos/add.online-temp.dto';
|
import { AddDoorLockOnlineDto } from '../dtos/add.online-temp.dto';
|
||||||
import { AddDoorLockOfflineTempDto } from '../dtos/add.offline-temp.dto';
|
import { AddDoorLockOfflineTempMultipleTimeDto } from '../dtos/add.offline-temp.dto';
|
||||||
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
|
||||||
|
import { UpdateDoorLockOfflineTempDto } from '../dtos/update.offline-temp.dto';
|
||||||
|
|
||||||
@ApiTags('Door Lock Module')
|
@ApiTags('Door Lock Module')
|
||||||
@Controller({
|
@Controller({
|
||||||
@ -55,13 +57,11 @@ export class DoorLockController {
|
|||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Post('temporary-password/offline/one-time/:doorLockUuid')
|
@Post('temporary-password/offline/one-time/:doorLockUuid')
|
||||||
async addOfflineOneTimeTemporaryPassword(
|
async addOfflineOneTimeTemporaryPassword(
|
||||||
@Body() addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
|
||||||
@Param('doorLockUuid') doorLockUuid: string,
|
@Param('doorLockUuid') doorLockUuid: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const temporaryPassword =
|
const temporaryPassword =
|
||||||
await this.doorLockService.addOfflineOneTimeTemporaryPassword(
|
await this.doorLockService.addOfflineOneTimeTemporaryPassword(
|
||||||
addDoorLockOfflineTempDto,
|
|
||||||
doorLockUuid,
|
doorLockUuid,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -82,13 +82,14 @@ export class DoorLockController {
|
|||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Post('temporary-password/offline/multiple-time/:doorLockUuid')
|
@Post('temporary-password/offline/multiple-time/:doorLockUuid')
|
||||||
async addOfflineMultipleTimeTemporaryPassword(
|
async addOfflineMultipleTimeTemporaryPassword(
|
||||||
@Body() addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
@Body()
|
||||||
|
addDoorLockOfflineTempMultipleTimeDto: AddDoorLockOfflineTempMultipleTimeDto,
|
||||||
@Param('doorLockUuid') doorLockUuid: string,
|
@Param('doorLockUuid') doorLockUuid: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const temporaryPassword =
|
const temporaryPassword =
|
||||||
await this.doorLockService.addOfflineMultipleTimeTemporaryPassword(
|
await this.doorLockService.addOfflineMultipleTimeTemporaryPassword(
|
||||||
addDoorLockOfflineTempDto,
|
addDoorLockOfflineTempMultipleTimeDto,
|
||||||
doorLockUuid,
|
doorLockUuid,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -124,6 +125,29 @@ export class DoorLockController {
|
|||||||
}
|
}
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Delete('temporary-password/online/:doorLockUuid/:passwordId')
|
||||||
|
async deleteDoorLockPassword(
|
||||||
|
@Param('doorLockUuid') doorLockUuid: string,
|
||||||
|
@Param('passwordId') passwordId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.doorLockService.deleteDoorLockPassword(
|
||||||
|
doorLockUuid,
|
||||||
|
passwordId,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.OK,
|
||||||
|
message: 'Temporary Password deleted Successfully',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get('temporary-password/offline/one-time/:doorLockUuid')
|
@Get('temporary-password/offline/one-time/:doorLockUuid')
|
||||||
async getOfflineOneTimeTemporaryPasswords(
|
async getOfflineOneTimeTemporaryPasswords(
|
||||||
@Param('doorLockUuid') doorLockUuid: string,
|
@Param('doorLockUuid') doorLockUuid: string,
|
||||||
@ -156,21 +180,29 @@ export class DoorLockController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Delete('temporary-password/:doorLockUuid/:passwordId')
|
@Put('temporary-password/:doorLockUuid/offline/:passwordId')
|
||||||
async deleteDoorLockPassword(
|
async updateOfflineTemporaryPassword(
|
||||||
|
@Body()
|
||||||
|
updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto,
|
||||||
@Param('doorLockUuid') doorLockUuid: string,
|
@Param('doorLockUuid') doorLockUuid: string,
|
||||||
@Param('passwordId') passwordId: string,
|
@Param('passwordId') passwordId: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await this.doorLockService.deleteDoorLockPassword(
|
const temporaryPassword =
|
||||||
|
await this.doorLockService.updateOfflineTemporaryPassword(
|
||||||
|
updateDoorLockOfflineTempDto,
|
||||||
doorLockUuid,
|
doorLockUuid,
|
||||||
passwordId,
|
passwordId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: HttpStatus.OK,
|
statusCode: HttpStatus.CREATED,
|
||||||
message: 'Temporary Password deleted Successfully',
|
success: true,
|
||||||
|
message: 'offline temporary password updated successfully',
|
||||||
|
data: temporaryPassword,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
|
|||||||
@ -1,23 +1,7 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsNotEmpty, IsString, Length } from 'class-validator';
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class AddDoorLockOfflineTempDto {
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'name',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
public name: string;
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'password',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
@Length(7, 7)
|
|
||||||
public password: string;
|
|
||||||
|
|
||||||
|
export class AddDoorLockOfflineTempMultipleTimeDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'effectiveTime',
|
description: 'effectiveTime',
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
12
src/door-lock/dtos/update.offline-temp.dto.ts
Normal file
12
src/door-lock/dtos/update.offline-temp.dto.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateDoorLockOfflineTempDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'name',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public name: string;
|
||||||
|
}
|
||||||
@ -55,3 +55,10 @@ export interface deleteTemporaryPasswordInterface {
|
|||||||
result: boolean;
|
result: boolean;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
}
|
}
|
||||||
|
export interface getPasswordOfflineInterface {
|
||||||
|
success: boolean;
|
||||||
|
result: {
|
||||||
|
records: [];
|
||||||
|
};
|
||||||
|
msg?: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,13 +7,15 @@ import {
|
|||||||
createTickInterface,
|
createTickInterface,
|
||||||
deleteTemporaryPasswordInterface,
|
deleteTemporaryPasswordInterface,
|
||||||
getPasswordInterface,
|
getPasswordInterface,
|
||||||
|
getPasswordOfflineInterface,
|
||||||
} from '../interfaces/door.lock.interface';
|
} from '../interfaces/door.lock.interface';
|
||||||
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
import { DeviceRepository } from '@app/common/modules/device/repositories';
|
||||||
import { ProductType } from '@app/common/constants/product-type.enum';
|
import { ProductType } from '@app/common/constants/product-type.enum';
|
||||||
|
|
||||||
import { PasswordEncryptionService } from './encryption.services';
|
import { PasswordEncryptionService } from './encryption.services';
|
||||||
import { AddDoorLockOfflineTempDto } from '../dtos/add.offline-temp.dto';
|
import { AddDoorLockOfflineTempMultipleTimeDto } from '../dtos/add.offline-temp.dto';
|
||||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||||
|
import { UpdateDoorLockOfflineTempDto } from '../dtos/update.offline-temp.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DoorLockService {
|
export class DoorLockService {
|
||||||
@ -93,18 +95,13 @@ export class DoorLockService {
|
|||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const passwords = await this.getTemporaryPasswordsTuya(
|
const passwords = await this.getTemporaryOfflinePasswordsTuya(
|
||||||
deviceDetails.deviceTuyaUuid,
|
deviceDetails.deviceTuyaUuid,
|
||||||
|
'multiple',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (passwords.result.length > 0) {
|
if (passwords.result.records.length > 0) {
|
||||||
const passwordFiltered = passwords.result.filter(
|
return convertKeysToCamelCase(passwords.result.records);
|
||||||
(item) =>
|
|
||||||
(!item.schedule_list || item.schedule_list.length === 0) &&
|
|
||||||
item.type === 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return convertKeysToCamelCase(passwordFiltered);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords;
|
return passwords;
|
||||||
@ -128,18 +125,13 @@ export class DoorLockService {
|
|||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const passwords = await this.getTemporaryPasswordsTuya(
|
const passwords = await this.getTemporaryOfflinePasswordsTuya(
|
||||||
deviceDetails.deviceTuyaUuid,
|
deviceDetails.deviceTuyaUuid,
|
||||||
|
'once',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (passwords.result.length > 0) {
|
if (passwords.result.records.length > 0) {
|
||||||
const passwordFiltered = passwords.result.filter(
|
return convertKeysToCamelCase(passwords.result.records);
|
||||||
(item) =>
|
|
||||||
(!item.schedule_list || item.schedule_list.length === 0) &&
|
|
||||||
item.type === 0, //temp solution
|
|
||||||
);
|
|
||||||
|
|
||||||
return convertKeysToCamelCase(passwordFiltered);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwords;
|
return passwords;
|
||||||
@ -162,13 +154,13 @@ export class DoorLockService {
|
|||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const passwords = await this.getTemporaryPasswordsTuya(
|
const passwords = await this.getOnlineTemporaryPasswordsTuya(
|
||||||
deviceDetails.deviceTuyaUuid,
|
deviceDetails.deviceTuyaUuid,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (passwords.result.length > 0) {
|
if (passwords.result.length > 0) {
|
||||||
const passwordFiltered = passwords.result
|
const passwordFiltered = passwords.result
|
||||||
.filter((item) => item.type === 0) //temp solution
|
.filter((item) => item.type === 0)
|
||||||
.map((password: any) => {
|
.map((password: any) => {
|
||||||
if (password.schedule_list?.length > 0) {
|
if (password.schedule_list?.length > 0) {
|
||||||
password.schedule_list = password.schedule_list.map(
|
password.schedule_list = password.schedule_list.map(
|
||||||
@ -200,7 +192,7 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getTemporaryPasswordsTuya(
|
async getOnlineTemporaryPasswordsTuya(
|
||||||
doorLockUuid: string,
|
doorLockUuid: string,
|
||||||
): Promise<getPasswordInterface> {
|
): Promise<getPasswordInterface> {
|
||||||
try {
|
try {
|
||||||
@ -219,25 +211,45 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async getTemporaryOfflinePasswordsTuya(
|
||||||
|
doorLockUuid: string,
|
||||||
|
type: string,
|
||||||
|
): Promise<getPasswordOfflineInterface> {
|
||||||
|
try {
|
||||||
|
const path = `/v1.0/devices/${doorLockUuid}/door-lock/offline-temp-password?pwd_type_codes=${type}&target_status=EFFECTIVE&page_no=1&page_size=100`;
|
||||||
|
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response as getPasswordOfflineInterface;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
'Error getting offline temporary passwords from Tuya',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
async addOfflineMultipleTimeTemporaryPassword(
|
async addOfflineMultipleTimeTemporaryPassword(
|
||||||
addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
addDoorLockOfflineTempMultipleTimeDto: AddDoorLockOfflineTempMultipleTimeDto,
|
||||||
doorLockUuid: string,
|
doorLockUuid: string,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const createOnlinePass = await this.addOnlineTemporaryPassword(
|
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||||
addDoorLockOfflineTempDto,
|
|
||||||
doorLockUuid,
|
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||||
'multiple',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if (!createOnlinePass) {
|
|
||||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||||
|
throw new HttpException(
|
||||||
|
'This is not a door lock device',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya(
|
const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya(
|
||||||
addDoorLockOfflineTempDto,
|
deviceDetails.deviceTuyaUuid,
|
||||||
createOnlinePass.id,
|
|
||||||
createOnlinePass.deviceTuyaUuid,
|
|
||||||
'multiple',
|
'multiple',
|
||||||
|
addDoorLockOfflineTempMultipleTimeDto,
|
||||||
);
|
);
|
||||||
if (!createOnceOfflinePass.success) {
|
if (!createOnceOfflinePass.success) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -255,25 +267,22 @@ export class DoorLockService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async addOfflineOneTimeTemporaryPassword(
|
async addOfflineOneTimeTemporaryPassword(doorLockUuid: string) {
|
||||||
addDoorLockOfflineTempDto: AddDoorLockOfflineTempDto,
|
|
||||||
doorLockUuid: string,
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const createOnlinePass = await this.addOnlineTemporaryPassword(
|
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||||
addDoorLockOfflineTempDto,
|
|
||||||
doorLockUuid,
|
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||||
'once',
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if (!createOnlinePass) {
|
|
||||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||||
|
throw new HttpException(
|
||||||
|
'This is not a door lock device',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya(
|
const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya(
|
||||||
addDoorLockOfflineTempDto,
|
deviceDetails.deviceTuyaUuid,
|
||||||
createOnlinePass.id,
|
|
||||||
createOnlinePass.deviceTuyaUuid,
|
|
||||||
'once',
|
'once',
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
if (!createOnceOfflinePass.success) {
|
if (!createOnceOfflinePass.success) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -292,10 +301,9 @@ export class DoorLockService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async addOfflineTemporaryPasswordTuya(
|
async addOfflineTemporaryPasswordTuya(
|
||||||
addDoorLockDto: AddDoorLockOnlineInterface,
|
|
||||||
onlinePassId: number,
|
|
||||||
doorLockUuid: string,
|
doorLockUuid: string,
|
||||||
type: string,
|
type: string,
|
||||||
|
addDoorLockOfflineTempMultipleTimeDto: AddDoorLockOfflineTempMultipleTimeDto,
|
||||||
): Promise<createTickInterface> {
|
): Promise<createTickInterface> {
|
||||||
try {
|
try {
|
||||||
const path = `/v1.1/devices/${doorLockUuid}/door-lock/offline-temp-password`;
|
const path = `/v1.1/devices/${doorLockUuid}/door-lock/offline-temp-password`;
|
||||||
@ -304,14 +312,12 @@ export class DoorLockService {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
path,
|
path,
|
||||||
body: {
|
body: {
|
||||||
name: addDoorLockDto.name,
|
|
||||||
...(type === 'multiple' && {
|
...(type === 'multiple' && {
|
||||||
effective_time: addDoorLockDto.effectiveTime,
|
effective_time: addDoorLockOfflineTempMultipleTimeDto.effectiveTime,
|
||||||
invalid_time: addDoorLockDto.invalidTime,
|
invalid_time: addDoorLockOfflineTempMultipleTimeDto.invalidTime,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
type,
|
type,
|
||||||
password_id: onlinePassId,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -326,8 +332,6 @@ export class DoorLockService {
|
|||||||
async addOnlineTemporaryPassword(
|
async addOnlineTemporaryPassword(
|
||||||
addDoorLockDto: AddDoorLockOnlineInterface,
|
addDoorLockDto: AddDoorLockOnlineInterface,
|
||||||
doorLockUuid: string,
|
doorLockUuid: string,
|
||||||
type: string = 'once',
|
|
||||||
isOnline: boolean = true,
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const passwordData = await this.getTicketAndEncryptedPassword(
|
const passwordData = await this.getTicketAndEncryptedPassword(
|
||||||
@ -348,8 +352,6 @@ export class DoorLockService {
|
|||||||
const createPass = await this.addOnlineTemporaryPasswordTuya(
|
const createPass = await this.addOnlineTemporaryPasswordTuya(
|
||||||
addDeviceObj,
|
addDeviceObj,
|
||||||
passwordData.deviceTuyaUuid,
|
passwordData.deviceTuyaUuid,
|
||||||
type,
|
|
||||||
addDeviceObj.scheduleList ? isOnline : false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!createPass.success) {
|
if (!createPass.success) {
|
||||||
@ -429,13 +431,11 @@ export class DoorLockService {
|
|||||||
async addOnlineTemporaryPasswordTuya(
|
async addOnlineTemporaryPasswordTuya(
|
||||||
addDeviceObj: addDeviceObjectInterface,
|
addDeviceObj: addDeviceObjectInterface,
|
||||||
doorLockUuid: string,
|
doorLockUuid: string,
|
||||||
type: string,
|
|
||||||
isOnline: boolean = true,
|
|
||||||
): Promise<createTickInterface> {
|
): Promise<createTickInterface> {
|
||||||
try {
|
try {
|
||||||
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`;
|
const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`;
|
||||||
let scheduleList;
|
let scheduleList;
|
||||||
if (isOnline) {
|
if (addDeviceObj.scheduleList.length > 0) {
|
||||||
scheduleList = addDeviceObj.scheduleList.map((schedule) => ({
|
scheduleList = addDeviceObj.scheduleList.map((schedule) => ({
|
||||||
effective_time: this.timeToMinutes(schedule.effectiveTime),
|
effective_time: this.timeToMinutes(schedule.effectiveTime),
|
||||||
invalid_time: this.timeToMinutes(schedule.invalidTime),
|
invalid_time: this.timeToMinutes(schedule.invalidTime),
|
||||||
@ -453,11 +453,11 @@ export class DoorLockService {
|
|||||||
invalid_time: addDeviceObj.invalidTime,
|
invalid_time: addDeviceObj.invalidTime,
|
||||||
password_type: 'ticket',
|
password_type: 'ticket',
|
||||||
ticket_id: addDeviceObj.ticketId,
|
ticket_id: addDeviceObj.ticketId,
|
||||||
...(isOnline && {
|
...(addDeviceObj.scheduleList.length > 0 && {
|
||||||
schedule_list: scheduleList,
|
schedule_list: scheduleList,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
type: '0', //temporary solution,
|
type: '0',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -579,4 +579,64 @@ export class DoorLockService {
|
|||||||
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateOfflineTemporaryPassword(
|
||||||
|
updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto,
|
||||||
|
doorLockUuid: string,
|
||||||
|
passwordId: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid);
|
||||||
|
|
||||||
|
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
|
||||||
|
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
|
||||||
|
} else if (deviceDetails.productDevice.prodType !== ProductType.DL) {
|
||||||
|
throw new HttpException(
|
||||||
|
'This is not a door lock device',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const updateOfflinePass = await this.updateOfflineTemporaryPasswordTuya(
|
||||||
|
deviceDetails.deviceTuyaUuid,
|
||||||
|
updateDoorLockOfflineTempDto,
|
||||||
|
passwordId,
|
||||||
|
);
|
||||||
|
if (!updateOfflinePass.success) {
|
||||||
|
throw new HttpException(updateOfflinePass.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: updateOfflinePass.result,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Error updating offline temporary password from Tuya',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOfflineTemporaryPasswordTuya(
|
||||||
|
doorLockUuid: string,
|
||||||
|
updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto,
|
||||||
|
passwordId: string,
|
||||||
|
): Promise<createTickInterface> {
|
||||||
|
try {
|
||||||
|
const path = `/v1.0/cloud/lock/${doorLockUuid}/door-lock/offline-temp-password/${passwordId}`;
|
||||||
|
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'PUT',
|
||||||
|
path,
|
||||||
|
body: {
|
||||||
|
password_name: updateDoorLockOfflineTempDto.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response as createTickInterface;
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
'Error updating offline temporary password from Tuya',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/guards/profile.picture.guard.ts
Normal file
45
src/guards/profile.picture.guard.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CheckProfilePictureGuard implements CanActivate {
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const req = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (req.body) {
|
||||||
|
const { profilePicture } = req.body;
|
||||||
|
if (profilePicture) {
|
||||||
|
const isBase64 = /^data:image\/[a-z]+;base64,/.test(profilePicture);
|
||||||
|
if (!isBase64) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Profile picture must be in base64 format.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the size of the base64 string (in bytes)
|
||||||
|
const base64StringLength =
|
||||||
|
profilePicture.length - 'data:image/[a-z]+;base64,'.length;
|
||||||
|
const base64ImageSizeInBytes = base64StringLength * 0.75; // Base64 encoding expands data by 33%
|
||||||
|
const maxSizeInBytes = 1 * 1024 * 1024; // 1 MB
|
||||||
|
|
||||||
|
// Check if the size exceeds the limit
|
||||||
|
if (base64ImageSizeInBytes > maxSizeInBytes) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Profile picture size exceeds the allowed limit.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if profilePicture is a base64 string
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException('Invalid request parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Profile picture guard error: ', error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ 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 { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.swagger.utils';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
import { json, urlencoded } from 'body-parser';
|
||||||
import { SeederService } from '@app/common/seed/services/seeder.service';
|
import { SeederService } from '@app/common/seed/services/seeder.service';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
@ -11,6 +12,10 @@ async function bootstrap() {
|
|||||||
|
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
|
|
||||||
|
// Set the body parser limit to 1 MB
|
||||||
|
app.use(json({ limit: '1mb' }));
|
||||||
|
app.use(urlencoded({ limit: '1mb', extended: true }));
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: 5 * 60 * 1000,
|
windowMs: 5 * 60 * 1000,
|
||||||
@ -42,7 +47,8 @@ async function bootstrap() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Seeding failed!', error);
|
console.error('Seeding failed!', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Starting auth at port ...', process.env.PORT || 4000);
|
||||||
await app.listen(process.env.PORT || 4000);
|
await app.listen(process.env.PORT || 4000);
|
||||||
}
|
}
|
||||||
console.log('Starting auth at port ...', process.env.PORT || 4000);
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
1
src/region/controllers/index.ts
Normal file
1
src/region/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './region.controller';
|
||||||
33
src/region/controllers/region.controller.ts
Normal file
33
src/region/controllers/region.controller.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { RegionService } from '../services/region.service';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
|
||||||
|
|
||||||
|
@ApiTags('Region Module')
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'region',
|
||||||
|
})
|
||||||
|
export class RegionController {
|
||||||
|
constructor(private readonly regionService: RegionService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get()
|
||||||
|
async getAllRegions() {
|
||||||
|
try {
|
||||||
|
return await this.regionService.getAllRegions();
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/region/region.module.ts
Normal file
13
src/region/region.module.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { RegionService } from './services/region.service';
|
||||||
|
import { RegionController } from './controllers/region.controller';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
controllers: [RegionController],
|
||||||
|
providers: [RegionService, RegionRepository],
|
||||||
|
exports: [RegionService],
|
||||||
|
})
|
||||||
|
export class RegionModule {}
|
||||||
1
src/region/services/index.ts
Normal file
1
src/region/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './region.service';
|
||||||
25
src/region/services/region.service.ts
Normal file
25
src/region/services/region.service.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RegionService {
|
||||||
|
constructor(private readonly regionRepository: RegionRepository) {}
|
||||||
|
async getAllRegions() {
|
||||||
|
try {
|
||||||
|
const regions = await this.regionRepository.find();
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException('Regions found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,3 +21,8 @@ export interface DeleteTapToRunSceneInterface {
|
|||||||
msg?: string;
|
msg?: string;
|
||||||
result: boolean;
|
result: boolean;
|
||||||
}
|
}
|
||||||
|
export interface SceneDetailsResult {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
AddTapToRunSceneInterface,
|
AddTapToRunSceneInterface,
|
||||||
DeleteTapToRunSceneInterface,
|
DeleteTapToRunSceneInterface,
|
||||||
GetTapToRunSceneByUnitInterface,
|
GetTapToRunSceneByUnitInterface,
|
||||||
|
SceneDetailsResult,
|
||||||
} from '../interface/scene.interface';
|
} from '../interface/scene.interface';
|
||||||
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
|
||||||
|
|
||||||
@ -256,6 +257,18 @@ export class SceneService {
|
|||||||
if (device) {
|
if (device) {
|
||||||
action.entityId = device.uuid;
|
action.entityId = device.uuid;
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
action.actionExecutor !== 'device_issue' &&
|
||||||
|
action.actionExecutor !== 'delay'
|
||||||
|
) {
|
||||||
|
const sceneDetails = await this.getTapToRunSceneDetailsTuya(
|
||||||
|
action.entityId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sceneDetails.id) {
|
||||||
|
action.name = sceneDetails.name;
|
||||||
|
action.type = sceneDetails.type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +288,38 @@ export class SceneService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async getTapToRunSceneDetailsTuya(
|
||||||
|
sceneId: string,
|
||||||
|
): Promise<SceneDetailsResult> {
|
||||||
|
try {
|
||||||
|
const path = `/v2.0/cloud/scene/rule/${sceneId}`;
|
||||||
|
const response = await this.tuya.request({
|
||||||
|
method: 'GET',
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new HttpException(response.msg, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
const camelCaseResponse = convertKeysToCamelCase(response);
|
||||||
|
const { id, name, type } = camelCaseResponse.result;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
} as SceneDetailsResult;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
'Scene not found for Tuya',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
async updateTapToRunScene(
|
async updateTapToRunScene(
|
||||||
updateSceneTapToRunDto: UpdateSceneTapToRunDto,
|
updateSceneTapToRunDto: UpdateSceneTapToRunDto,
|
||||||
sceneId: string,
|
sceneId: string,
|
||||||
|
|||||||
1
src/timezone/controllers/index.ts
Normal file
1
src/timezone/controllers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './timezone.controller';
|
||||||
33
src/timezone/controllers/timezone.controller.ts
Normal file
33
src/timezone/controllers/timezone.controller.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { TimeZoneService } from '../services/timezone.service';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
|
||||||
|
|
||||||
|
@ApiTags('TimeZone Module')
|
||||||
|
@Controller({
|
||||||
|
version: '1',
|
||||||
|
path: 'timezone',
|
||||||
|
})
|
||||||
|
export class TimeZoneController {
|
||||||
|
constructor(private readonly timeZoneService: TimeZoneService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get()
|
||||||
|
async getAllTimeZones() {
|
||||||
|
try {
|
||||||
|
return await this.timeZoneService.getAllTimeZones();
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/timezone/services/index.ts
Normal file
1
src/timezone/services/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './timezone.service';
|
||||||
25
src/timezone/services/timezone.service.ts
Normal file
25
src/timezone/services/timezone.service.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Injectable,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TimeZoneService {
|
||||||
|
constructor(private readonly timeZoneRepository: TimeZoneRepository) {}
|
||||||
|
async getAllTimeZones() {
|
||||||
|
try {
|
||||||
|
const timeZones = await this.timeZoneRepository.find();
|
||||||
|
|
||||||
|
return timeZones;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException('TimeZones found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/timezone/timezone.module.ts
Normal file
13
src/timezone/timezone.module.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TimeZoneService } from './services/timezone.service';
|
||||||
|
import { TimeZoneController } from './controllers/timezone.controller';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
controllers: [TimeZoneController],
|
||||||
|
providers: [TimeZoneService, TimeZoneRepository],
|
||||||
|
exports: [TimeZoneService],
|
||||||
|
})
|
||||||
|
export class TimeZoneModule {}
|
||||||
@ -1,8 +1,23 @@
|
|||||||
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Put,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { UserListDto } from '../dtos/user.list.dto';
|
|
||||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
import { AdminRoleGuard } from 'src/guards/admin.role.guard';
|
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
|
||||||
|
import {
|
||||||
|
UpdateNameDto,
|
||||||
|
UpdateProfilePictureDataDto,
|
||||||
|
UpdateRegionDataDto,
|
||||||
|
UpdateTimezoneDataDto,
|
||||||
|
} from '../dtos';
|
||||||
|
import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard';
|
||||||
|
|
||||||
@ApiTags('User Module')
|
@ApiTags('User Module')
|
||||||
@Controller({
|
@Controller({
|
||||||
@ -13,13 +28,116 @@ export class UserController {
|
|||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminRoleGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get('list')
|
@Get(':userUuid')
|
||||||
async userList(@Query() userListDto: UserListDto) {
|
async getUserDetailsByUserUuid(@Param('userUuid') userUuid: string) {
|
||||||
try {
|
try {
|
||||||
return await this.userService.userDetails(userListDto);
|
return await this.userService.getUserDetailsByUserUuid(userUuid);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
throw new Error(err);
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard, CheckProfilePictureGuard)
|
||||||
|
@Put('/profile-picture/:userUuid')
|
||||||
|
async updateProfilePictureByUserUuid(
|
||||||
|
@Param('userUuid') userUuid: string,
|
||||||
|
@Body() updateProfilePictureDataDto: UpdateProfilePictureDataDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userData = await this.userService.updateProfilePictureByUserUuid(
|
||||||
|
userUuid,
|
||||||
|
updateProfilePictureDataDto,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'profile picture updated successfully',
|
||||||
|
data: userData,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Put('/region/:userUuid')
|
||||||
|
async updateRegionByUserUuid(
|
||||||
|
@Param('userUuid') userUuid: string,
|
||||||
|
@Body() updateRegionDataDto: UpdateRegionDataDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userData = await this.userService.updateRegionByUserUuid(
|
||||||
|
userUuid,
|
||||||
|
updateRegionDataDto,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'region updated successfully',
|
||||||
|
data: userData,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Put('/timezone/:userUuid')
|
||||||
|
async updateNameByUserUuid(
|
||||||
|
@Param('userUuid') userUuid: string,
|
||||||
|
@Body() updateTimezoneDataDto: UpdateTimezoneDataDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userData = await this.userService.updateTimezoneByUserUuid(
|
||||||
|
userUuid,
|
||||||
|
updateTimezoneDataDto,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'timezone updated successfully',
|
||||||
|
data: userData,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Put('/name/:userUuid')
|
||||||
|
async updateTimezoneByUserUuid(
|
||||||
|
@Param('userUuid') userUuid: string,
|
||||||
|
@Body() updateNameDto: UpdateNameDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userData = await this.userService.updateNameByUserUuid(
|
||||||
|
userUuid,
|
||||||
|
updateNameDto,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
statusCode: HttpStatus.CREATED,
|
||||||
|
success: true,
|
||||||
|
message: 'name updated successfully',
|
||||||
|
data: userData,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
error.message || 'Internal server error',
|
||||||
|
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export * from './user.list.dto';
|
export * from './update.user.dto';
|
||||||
|
|||||||
61
src/users/dtos/update.user.dto.ts
Normal file
61
src/users/dtos/update.user.dto.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateProfilePictureDataDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'profilePicture',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public profilePicture: string;
|
||||||
|
|
||||||
|
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class UpdateRegionDataDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'regionUuid',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public regionUuid: string;
|
||||||
|
|
||||||
|
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class UpdateTimezoneDataDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'timezoneUuid',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public timezoneUuid: string;
|
||||||
|
|
||||||
|
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class UpdateNameDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'firstName',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public firstName: string;
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'lastName',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
public lastName: string;
|
||||||
|
constructor(dto: Partial<UpdateProfilePictureDataDto>) {
|
||||||
|
Object.assign(this, dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import {
|
|
||||||
IsNotEmpty,
|
|
||||||
IsNumberString,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
export class UserListDto {
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
schema: string;
|
|
||||||
|
|
||||||
@IsNumberString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
page_no: number;
|
|
||||||
|
|
||||||
@IsNumberString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
page_size: number;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
@IsNumberString()
|
|
||||||
@IsOptional()
|
|
||||||
start_time: number;
|
|
||||||
|
|
||||||
@IsNumberString()
|
|
||||||
@IsOptional()
|
|
||||||
end_time: number;
|
|
||||||
}
|
|
||||||
@ -1,28 +1,240 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import {
|
||||||
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
|
UpdateNameDto,
|
||||||
import { UserListDto } from '../dtos/user.list.dto';
|
UpdateProfilePictureDataDto,
|
||||||
import { ConfigService } from '@nestjs/config';
|
UpdateRegionDataDto,
|
||||||
|
UpdateTimezoneDataDto,
|
||||||
|
} from './../dtos/update.user.dto';
|
||||||
|
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';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
private tuya: TuyaContext;
|
constructor(
|
||||||
constructor(private readonly configService: ConfigService) {
|
private readonly userRepository: UserRepository,
|
||||||
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
|
private readonly regionRepository: RegionRepository,
|
||||||
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
|
private readonly timeZoneRepository: TimeZoneRepository,
|
||||||
const tuyaEuUrl = this.configService.get<string>('tuya-config.TUYA_EU_URL');
|
) {}
|
||||||
this.tuya = new TuyaContext({
|
async getUserDetailsByUserUuid(userUuid: string) {
|
||||||
baseUrl: tuyaEuUrl,
|
try {
|
||||||
accessKey,
|
const user = await this.userRepository.findOne({
|
||||||
secretKey,
|
where: {
|
||||||
|
uuid: userUuid,
|
||||||
|
},
|
||||||
|
relations: ['region', 'timezone'],
|
||||||
});
|
});
|
||||||
|
if (!user) {
|
||||||
|
throw new BadRequestException('Invalid room UUID');
|
||||||
}
|
}
|
||||||
async userDetails(userListDto: UserListDto) {
|
|
||||||
const path = `/v2.0/apps/${userListDto.schema}/users`;
|
// Use the utility function to remove the base64 prefix
|
||||||
const data = await this.tuya.request({
|
const cleanedProfilePicture = removeBase64Prefix(user.profilePicture);
|
||||||
method: 'GET',
|
|
||||||
path,
|
return {
|
||||||
query: userListDto,
|
uuid: user.uuid,
|
||||||
|
email: user.email,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
profilePicture: cleanedProfilePicture,
|
||||||
|
region: user.region,
|
||||||
|
timeZone: user.timezone,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProfilePictureByUserUuid(
|
||||||
|
userUuid: string,
|
||||||
|
updateProfilePictureDataDto: UpdateProfilePictureDataDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ uuid: userUuid },
|
||||||
|
{ ...updateProfilePictureDataDto },
|
||||||
|
);
|
||||||
|
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
// Use the utility function to remove the base64 prefix
|
||||||
|
const cleanedProfilePicture = removeBase64Prefix(
|
||||||
|
updatedUser.profilePicture,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
uuid: updatedUser.uuid,
|
||||||
|
firstName: updatedUser.firstName,
|
||||||
|
lastName: updatedUser.lastName,
|
||||||
|
profilePicture: cleanedProfilePicture,
|
||||||
|
region: updatedUser.region,
|
||||||
|
timeZoneUuid: updatedUser.timeZone,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof BadRequestException) {
|
||||||
|
throw err; // Re-throw BadRequestException
|
||||||
|
} else {
|
||||||
|
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async updateRegionByUserUuid(
|
||||||
|
userUuid: string,
|
||||||
|
updateRegionDataDto: UpdateRegionDataDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const user = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the region UUID is provided
|
||||||
|
if (!updateRegionDataDto.regionUuid) {
|
||||||
|
throw new BadRequestException('Region UUID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the region exists
|
||||||
|
const region = await this.regionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
uuid: updateRegionDataDto.regionUuid,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return data;
|
if (!region) {
|
||||||
|
throw new BadRequestException('Invalid region UUID');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ uuid: userUuid },
|
||||||
|
{
|
||||||
|
region: region,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
if (!updatedUser.region) {
|
||||||
|
throw new BadRequestException('Region update failed');
|
||||||
|
}
|
||||||
|
// Use the utility function to remove the base64 prefix
|
||||||
|
const cleanedProfilePicture = removeBase64Prefix(
|
||||||
|
updatedUser.profilePicture,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
uuid: updatedUser.uuid,
|
||||||
|
firstName: updatedUser.firstName,
|
||||||
|
lastName: updatedUser.lastName,
|
||||||
|
profilePicture: cleanedProfilePicture,
|
||||||
|
region: updatedUser.region,
|
||||||
|
timeZoneUuid: updatedUser.timeZone,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'User not found',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async updateTimezoneByUserUuid(
|
||||||
|
userUuid: string,
|
||||||
|
updateTimezoneDataDto: UpdateTimezoneDataDto,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const user = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the region UUID is provided
|
||||||
|
if (!updateTimezoneDataDto.timezoneUuid) {
|
||||||
|
throw new BadRequestException('Timezone UUID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the region exists
|
||||||
|
const timezone = await this.timeZoneRepository.findOne({
|
||||||
|
where: {
|
||||||
|
uuid: updateTimezoneDataDto.timezoneUuid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!timezone) {
|
||||||
|
throw new BadRequestException('Invalid timezone UUID');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ uuid: userUuid },
|
||||||
|
{
|
||||||
|
timezone: timezone,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
if (!updatedUser.timeZone) {
|
||||||
|
throw new BadRequestException('Timezone update failed');
|
||||||
|
}
|
||||||
|
// Use the utility function to remove the base64 prefix
|
||||||
|
const cleanedProfilePicture = removeBase64Prefix(
|
||||||
|
updatedUser.profilePicture,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
uuid: updatedUser.uuid,
|
||||||
|
firstName: updatedUser.firstName,
|
||||||
|
lastName: updatedUser.lastName,
|
||||||
|
profilePicture: cleanedProfilePicture,
|
||||||
|
region: updatedUser.region,
|
||||||
|
timeZoneUuid: updatedUser.timeZone,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'User not found',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async updateNameByUserUuid(userUuid: string, updateNameDto: UpdateNameDto) {
|
||||||
|
try {
|
||||||
|
const user = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateNameDto.firstName || !updateNameDto.lastName) {
|
||||||
|
throw new BadRequestException('First Name and Last Name is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ uuid: userUuid },
|
||||||
|
{
|
||||||
|
firstName: updateNameDto.firstName,
|
||||||
|
lastName: updateNameDto.lastName,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedUser = await this.getUserDetailsByUserUuid(userUuid);
|
||||||
|
if (!updatedUser.firstName || !updatedUser.lastName) {
|
||||||
|
throw new BadRequestException('First Name and Last Name update failed');
|
||||||
|
}
|
||||||
|
// Use the utility function to remove the base64 prefix
|
||||||
|
const cleanedProfilePicture = removeBase64Prefix(
|
||||||
|
updatedUser.profilePicture,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
uuid: updatedUser.uuid,
|
||||||
|
firstName: updatedUser.firstName,
|
||||||
|
lastName: updatedUser.lastName,
|
||||||
|
profilePicture: cleanedProfilePicture,
|
||||||
|
region: updatedUser.region,
|
||||||
|
timeZoneUuid: updatedUser.timeZone,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
throw new HttpException(
|
||||||
|
err.message || 'User not found',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,19 @@ import { Module } from '@nestjs/common';
|
|||||||
import { UserService } from './services/user.service';
|
import { UserService } from './services/user.service';
|
||||||
import { UserController } from './controllers/user.controller';
|
import { UserController } from './controllers/user.controller';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { UserRepository } from '@app/common/modules/user/repositories';
|
||||||
|
import { RegionRepository } from '@app/common/modules/region/repositories';
|
||||||
|
import { TimeZoneRepository } from '@app/common/modules/timezone/repositories';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [UserService],
|
providers: [
|
||||||
|
UserService,
|
||||||
|
UserRepository,
|
||||||
|
RegionRepository,
|
||||||
|
TimeZoneRepository,
|
||||||
|
],
|
||||||
exports: [UserService],
|
exports: [UserService],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
|||||||
33
src/validators/password.validator.ts
Normal file
33
src/validators/password.validator.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
registerDecorator,
|
||||||
|
ValidationOptions,
|
||||||
|
ValidatorConstraint,
|
||||||
|
ValidatorConstraintInterface,
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
|
@ValidatorConstraint({ async: false })
|
||||||
|
export class IsPasswordStrongConstraint
|
||||||
|
implements ValidatorConstraintInterface
|
||||||
|
{
|
||||||
|
validate(password: string) {
|
||||||
|
const regex =
|
||||||
|
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~])[A-Za-z\d!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]{8,}$/;
|
||||||
|
return regex.test(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage() {
|
||||||
|
return 'Password must be at least 8 characters long and include at least one uppercase letter, one lowercase letter, one numeric digit, and one special character from the set !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsPasswordStrong(validationOptions?: ValidationOptions) {
|
||||||
|
return function (object: Record<string, any>, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
options: validationOptions,
|
||||||
|
constraints: [],
|
||||||
|
validator: IsPasswordStrongConstraint,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user