mirror of
https://github.com/SyncrowIOT/backend.git
synced 2026-10-25 20:17:54 +00:00
Merge branch 'dev' into SP-197-be-retrieve-devices-in-the-gateway
This commit is contained in:
@@ -13,6 +13,7 @@ import { EmailService } from './util/email.service';
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
load: config,
|
||||
isGlobal: true,
|
||||
}),
|
||||
DatabaseModule,
|
||||
HelperModule,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import emailConfig from './email.config';
|
||||
import superAdminConfig from './super.admin.config';
|
||||
export default [emailConfig, superAdminConfig];
|
||||
import tuyaConfig from './tuya.config';
|
||||
import oneSignalConfig from './onesignal.config';
|
||||
|
||||
export default [emailConfig, superAdminConfig, tuyaConfig, oneSignalConfig];
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs(
|
||||
'onesignal-config',
|
||||
(): Record<string, any> => ({
|
||||
ONESIGNAL_APP_ID: process.env.ONESIGNAL_APP_ID,
|
||||
ONESIGNAL_API_KEY: process.env.ONESIGNAL_API_KEY,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
export enum TuyaRegionConfigEnum {
|
||||
CN = 'wss://mqe.tuyacn.com:8285/',
|
||||
US = 'wss://mqe.tuyaus.com:8285/',
|
||||
EU = 'wss://mqe.tuyaeu.com:8285/',
|
||||
IN = 'wss://mqe.tuyain.com:8285/',
|
||||
}
|
||||
|
||||
export enum TUYA_PASULAR_ENV {
|
||||
PROD = 'prod',
|
||||
TEST = 'test',
|
||||
}
|
||||
|
||||
export const TuyaEnvConfig = Object.freeze({
|
||||
[TUYA_PASULAR_ENV.PROD]: {
|
||||
name: TUYA_PASULAR_ENV.PROD,
|
||||
value: 'event',
|
||||
desc: 'online environment',
|
||||
},
|
||||
[TUYA_PASULAR_ENV.TEST]: {
|
||||
name: TUYA_PASULAR_ENV.TEST,
|
||||
value: 'event-test',
|
||||
desc: 'test environment',
|
||||
},
|
||||
});
|
||||
type IEnvConfig = typeof TuyaEnvConfig;
|
||||
export function getTuyaEnvConfig<K extends keyof IEnvConfig>(
|
||||
env: TUYA_PASULAR_ENV,
|
||||
): IEnvConfig[K] {
|
||||
return TuyaEnvConfig[env];
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { WebSocket } from 'ws';
|
||||
|
||||
import {
|
||||
TUYA_PASULAR_ENV,
|
||||
getTuyaEnvConfig,
|
||||
TuyaRegionConfigEnum,
|
||||
} from './config';
|
||||
import { getTopicUrl, buildQuery, buildPassword, decrypt } from './utils';
|
||||
|
||||
type LoggerLevel = 'INFO' | 'ERROR';
|
||||
|
||||
interface IConfig {
|
||||
accessId: string;
|
||||
accessKey: string;
|
||||
env: TUYA_PASULAR_ENV;
|
||||
url: TuyaRegionConfigEnum;
|
||||
|
||||
timeout?: number;
|
||||
maxRetryTimes?: number;
|
||||
retryTimeout?: number;
|
||||
logger?: (level: LoggerLevel, ...args: any) => void;
|
||||
}
|
||||
|
||||
class TuyaMessageSubscribeWebsocket {
|
||||
static URL = TuyaRegionConfigEnum;
|
||||
static env = TUYA_PASULAR_ENV;
|
||||
|
||||
static data = 'TUTA_DATA';
|
||||
static error = 'TUYA_ERROR';
|
||||
static open = 'TUYA_OPEN';
|
||||
static close = 'TUYA_CLOSE';
|
||||
static reconnect = 'TUYA_RECONNECT';
|
||||
static ping = 'TUYA_PING';
|
||||
static pong = 'TUYA_PONG';
|
||||
|
||||
private config: IConfig;
|
||||
private server?: WebSocket;
|
||||
private timer: any;
|
||||
private retryTimes: number;
|
||||
private event: EventEmitter;
|
||||
|
||||
constructor(config: IConfig) {
|
||||
this.config = Object.assign(
|
||||
{
|
||||
ackTimeoutMillis: 3000,
|
||||
subscriptionType: 'Failover',
|
||||
retryTimeout: 1000,
|
||||
maxRetryTimes: 100,
|
||||
timeout: 30000,
|
||||
logger: console.log,
|
||||
},
|
||||
config,
|
||||
);
|
||||
this.event = new EventEmitter();
|
||||
this.retryTimes = 0;
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.server = this._connect();
|
||||
}
|
||||
|
||||
public open(cb: (ws: WebSocket) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.open, cb);
|
||||
}
|
||||
|
||||
public message(cb: (ws: WebSocket, message: any) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.data, cb);
|
||||
}
|
||||
|
||||
public ping(cb: (ws: WebSocket) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.ping, cb);
|
||||
}
|
||||
|
||||
public pong(cb: (ws: WebSocket) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.pong, cb);
|
||||
}
|
||||
|
||||
public reconnect(cb: (ws: WebSocket) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.reconnect, cb);
|
||||
}
|
||||
|
||||
public ackMessage(messageId: string) {
|
||||
this.server && this.server.send(JSON.stringify({ messageId }));
|
||||
}
|
||||
|
||||
public error(cb: (ws: WebSocket, error: any) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.error, cb);
|
||||
}
|
||||
|
||||
public close(cb: (ws: WebSocket) => void) {
|
||||
this.event.on(TuyaMessageSubscribeWebsocket.close, cb);
|
||||
}
|
||||
|
||||
private _reconnect() {
|
||||
if (
|
||||
this.config.maxRetryTimes &&
|
||||
this.retryTimes < this.config.maxRetryTimes
|
||||
) {
|
||||
const timer = setTimeout(() => {
|
||||
clearTimeout(timer);
|
||||
this.retryTimes++;
|
||||
this._connect(false);
|
||||
}, this.config.retryTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
private _connect(isInit = true) {
|
||||
const { accessId, accessKey, env, url } = this.config;
|
||||
const topicUrl = getTopicUrl(
|
||||
url,
|
||||
accessId,
|
||||
getTuyaEnvConfig(env).value,
|
||||
`?${buildQuery({ subscriptionType: 'Failover', ackTimeoutMillis: 30000 })}`,
|
||||
);
|
||||
const password = buildPassword(accessId, accessKey);
|
||||
this.server = new WebSocket(topicUrl, {
|
||||
rejectUnauthorized: false,
|
||||
headers: { username: accessId, password },
|
||||
});
|
||||
this.subOpen(this.server, isInit);
|
||||
this.subMessage(this.server);
|
||||
this.subPing(this.server);
|
||||
this.subPong(this.server);
|
||||
this.subError(this.server);
|
||||
this.subClose(this.server);
|
||||
return this.server;
|
||||
}
|
||||
|
||||
private subOpen(server: WebSocket, isInit = true) {
|
||||
server.on('open', () => {
|
||||
if (server.readyState === server.OPEN) {
|
||||
this.retryTimes = 0;
|
||||
}
|
||||
this.keepAlive(server);
|
||||
this.event.emit(
|
||||
isInit
|
||||
? TuyaMessageSubscribeWebsocket.open
|
||||
: TuyaMessageSubscribeWebsocket.reconnect,
|
||||
this.server,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private subPing(server: WebSocket) {
|
||||
server.on('ping', () => {
|
||||
this.event.emit(TuyaMessageSubscribeWebsocket.ping, this.server);
|
||||
this.keepAlive(server);
|
||||
server.pong(this.config.accessId);
|
||||
});
|
||||
}
|
||||
|
||||
private subPong(server: WebSocket) {
|
||||
server.on('pong', () => {
|
||||
this.keepAlive(server);
|
||||
this.event.emit(TuyaMessageSubscribeWebsocket.pong, this.server);
|
||||
});
|
||||
}
|
||||
|
||||
private subMessage(server: WebSocket) {
|
||||
server.on('message', (data: any) => {
|
||||
try {
|
||||
this.keepAlive(server);
|
||||
const obj = this.handleMessage(data);
|
||||
this.event.emit(TuyaMessageSubscribeWebsocket.data, this.server, obj);
|
||||
} catch (e) {
|
||||
this.logger('ERROR', e);
|
||||
this.event.emit(TuyaMessageSubscribeWebsocket.error, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private subClose(server: WebSocket) {
|
||||
server.on('close', (...data) => {
|
||||
this._reconnect();
|
||||
this.clearKeepAlive();
|
||||
this.event.emit(TuyaMessageSubscribeWebsocket.close, ...data);
|
||||
});
|
||||
}
|
||||
|
||||
private subError(server: WebSocket) {
|
||||
server.on('error', (e) => {
|
||||
this.event.emit(TuyaMessageSubscribeWebsocket.error, this.server, e);
|
||||
});
|
||||
}
|
||||
|
||||
private clearKeepAlive() {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
private keepAlive(server: WebSocket) {
|
||||
this.clearKeepAlive();
|
||||
this.timer = setTimeout(() => {
|
||||
server.ping(this.config.accessId);
|
||||
}, this.config.timeout);
|
||||
}
|
||||
|
||||
private handleMessage(data: string) {
|
||||
const { payload, ...others } = JSON.parse(data);
|
||||
const pStr = Buffer.from(payload, 'base64').toString('utf-8');
|
||||
const pJson = JSON.parse(pStr);
|
||||
pJson.data = decrypt(pJson.data, this.config.accessKey);
|
||||
return { payload: pJson, ...others };
|
||||
}
|
||||
|
||||
private logger(level: LoggerLevel, ...info: any) {
|
||||
const realInfo = `${Date.now()} `;
|
||||
this.config.logger && this.config.logger(level, realInfo, ...info);
|
||||
}
|
||||
}
|
||||
|
||||
export default TuyaMessageSubscribeWebsocket;
|
||||
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { MD5, AES, enc, mode, pad } from 'crypto-js';
|
||||
|
||||
export function getTopicUrl(
|
||||
websocketUrl: string,
|
||||
accessId: string,
|
||||
env: string,
|
||||
query: string,
|
||||
) {
|
||||
return `${websocketUrl}ws/v2/consumer/persistent/${accessId}/out/${env}/${accessId}-sub${query}`;
|
||||
}
|
||||
|
||||
export function buildQuery(query: { [key: string]: number | string }) {
|
||||
return Object.keys(query)
|
||||
.map((key) => `${key}=${encodeURIComponent(query[key])}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export function buildPassword(accessId: string, accessKey: string) {
|
||||
const key = MD5(accessKey).toString();
|
||||
return MD5(`${accessId}${key}`).toString().substr(8, 16);
|
||||
}
|
||||
|
||||
export function decrypt(data: string, accessKey: string) {
|
||||
try {
|
||||
const realKey = enc.Utf8.parse(accessKey.substring(8, 24));
|
||||
const json = AES.decrypt(data, realKey, {
|
||||
mode: mode.ECB,
|
||||
padding: pad.Pkcs7,
|
||||
});
|
||||
const dataStr = enc.Utf8.stringify(json).toString();
|
||||
return JSON.parse(dataStr);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function encrypt(data: any, accessKey: string) {
|
||||
try {
|
||||
const realKey = enc.Utf8.parse(accessKey.substring(8, 24));
|
||||
const realData = JSON.stringify(data);
|
||||
const retData = AES.encrypt(realData, realKey, {
|
||||
mode: mode.ECB,
|
||||
padding: pad.Pkcs7,
|
||||
}).toString();
|
||||
return retData;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs(
|
||||
'tuya-config',
|
||||
(): Record<string, any> => ({
|
||||
TUYA_ACCESS_ID: process.env.TUYA_ACCESS_ID,
|
||||
TUYA_ACCESS_KEY: process.env.TUYA_ACCESS_KEY,
|
||||
TRUN_ON_TUYA_SOCKET:
|
||||
process.env.TRUN_ON_TUYA_SOCKET === 'true' ? true : false,
|
||||
}),
|
||||
);
|
||||
@@ -11,11 +11,11 @@ import { PermissionTypeEntity } from '../modules/permission/entities';
|
||||
import { SpaceEntity } from '../modules/space/entities';
|
||||
import { SpaceTypeEntity } from '../modules/space-type/entities';
|
||||
import { UserSpaceEntity } from '../modules/user-space/entities';
|
||||
import { GroupEntity } from '../modules/group/entities';
|
||||
import { GroupDeviceEntity } from '../modules/group-device/entities';
|
||||
import { DeviceUserPermissionEntity } from '../modules/device-user-permission/entities';
|
||||
import { UserRoleEntity } from '../modules/user-role/entities';
|
||||
import { RoleTypeEntity } from '../modules/role-type/entities';
|
||||
import { UserNotificationEntity } from '../modules/user-notification/entities';
|
||||
import { DeviceNotificationEntity } from '../modules/device-notification/entities';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -41,11 +41,11 @@ import { RoleTypeEntity } from '../modules/role-type/entities';
|
||||
SpaceEntity,
|
||||
SpaceTypeEntity,
|
||||
UserSpaceEntity,
|
||||
GroupEntity,
|
||||
GroupDeviceEntity,
|
||||
DeviceUserPermissionEntity,
|
||||
UserRoleEntity,
|
||||
RoleTypeEntity,
|
||||
UserNotificationEntity,
|
||||
DeviceNotificationEntity,
|
||||
],
|
||||
namingStrategy: new SnakeNamingStrategy(),
|
||||
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
|
||||
|
||||
@@ -3,12 +3,26 @@ import { HelperHashService } from './services';
|
||||
import { SpacePermissionService } from './services/space.permission.service';
|
||||
import { SpaceRepository } from '../modules/space/repositories';
|
||||
import { SpaceRepositoryModule } from '../modules/space/space.repository.module';
|
||||
import { TuyaWebSocketService } from './services/tuya.web.socket.service';
|
||||
|
||||
import { OneSignalService } from './services/onesignal.service';
|
||||
import { DeviceMessagesService } from './services/device.messages.service';
|
||||
import { DeviceNotificationRepositoryModule } from '../modules/device-notification/device.notification.module';
|
||||
import { DeviceNotificationRepository } from '../modules/device-notification/repositories';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [HelperHashService, SpacePermissionService, SpaceRepository],
|
||||
providers: [
|
||||
HelperHashService,
|
||||
SpacePermissionService,
|
||||
SpaceRepository,
|
||||
TuyaWebSocketService,
|
||||
OneSignalService,
|
||||
DeviceMessagesService,
|
||||
DeviceNotificationRepository,
|
||||
],
|
||||
exports: [HelperHashService, SpacePermissionService],
|
||||
controllers: [],
|
||||
imports: [SpaceRepositoryModule],
|
||||
imports: [SpaceRepositoryModule, DeviceNotificationRepositoryModule],
|
||||
})
|
||||
export class HelperModule {}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export function generateRandomString(length: number): string {
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let randomString = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
randomString += characters.charAt(randomIndex);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DeviceNotificationRepository } from '@app/common/modules/device-notification/repositories';
|
||||
import { OneSignalService } from './onesignal.service';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceMessagesService {
|
||||
constructor(
|
||||
private readonly deviceNotificationRepository: DeviceNotificationRepository,
|
||||
private readonly oneSignalService: OneSignalService,
|
||||
) {}
|
||||
|
||||
async getDevicesUserNotifications(deviceTuyaUuid: string, bizData: any) {
|
||||
try {
|
||||
// Retrieve notifications for the specified device
|
||||
const notifications = await this.deviceNotificationRepository.find({
|
||||
where: {
|
||||
device: {
|
||||
deviceTuyaUuid,
|
||||
},
|
||||
},
|
||||
relations: ['user', 'user.userNotification'],
|
||||
});
|
||||
|
||||
// If notifications are found, send them
|
||||
if (notifications) {
|
||||
await this.sendNotifications(notifications, bizData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching device notifications:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendNotifications(notifications: any[], bizData: any) {
|
||||
const notificationPromises = [];
|
||||
|
||||
// Iterate over each notification and its associated user notifications
|
||||
notifications.forEach((notification) => {
|
||||
notification.user.userNotification.forEach((userNotification) => {
|
||||
// Queue notification sending without awaiting
|
||||
notificationPromises.push(
|
||||
this.oneSignalService.sendNotification(
|
||||
JSON.stringify(bizData),
|
||||
'device-status',
|
||||
[userNotification.subscriptionUuid],
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Wait for all notification sending operations to complete
|
||||
await Promise.all(notificationPromises);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as OneSignal from 'onesignal-node';
|
||||
|
||||
@Injectable()
|
||||
export class OneSignalService {
|
||||
private client: any;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
// Initialize OneSignal client here
|
||||
this.client = new OneSignal.Client(
|
||||
this.configService.get<string>('onesignal-config.ONESIGNAL_APP_ID'),
|
||||
this.configService.get<string>('onesignal-config.ONESIGNAL_API_KEY'),
|
||||
);
|
||||
}
|
||||
|
||||
async sendNotification(
|
||||
content: string,
|
||||
title: string,
|
||||
subscriptionIds: string[],
|
||||
): Promise<any> {
|
||||
const notification = {
|
||||
contents: {
|
||||
en: content,
|
||||
},
|
||||
headings: {
|
||||
en: title,
|
||||
},
|
||||
include_subscription_ids: subscriptionIds,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.client.createNotification(notification);
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.error('Error:', err);
|
||||
throw new Error('Error sending notification');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import TuyaWebsocket from '../../config/tuya-web-socket-config';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { OneSignalService } from './onesignal.service';
|
||||
import { DeviceMessagesService } from './device.messages.service';
|
||||
|
||||
@Injectable()
|
||||
export class TuyaWebSocketService {
|
||||
private client: any; // Adjust type according to your TuyaWebsocket client
|
||||
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly oneSignalService: OneSignalService,
|
||||
private readonly deviceMessagesService: DeviceMessagesService,
|
||||
) {
|
||||
// Initialize the TuyaWebsocket client
|
||||
this.client = new TuyaWebsocket({
|
||||
accessId: this.configService.get<string>('tuya-config.TUYA_ACCESS_ID'),
|
||||
accessKey: this.configService.get<string>('tuya-config.TUYA_ACCESS_KEY'),
|
||||
url: TuyaWebsocket.URL.EU,
|
||||
env: TuyaWebsocket.env.TEST,
|
||||
maxRetryTimes: 100,
|
||||
});
|
||||
|
||||
if (this.configService.get<string>('tuya-config.TRUN_ON_TUYA_SOCKET')) {
|
||||
// Set up event handlers
|
||||
this.setupEventHandlers();
|
||||
|
||||
// Start receiving messages
|
||||
this.client.start();
|
||||
}
|
||||
}
|
||||
|
||||
private setupEventHandlers() {
|
||||
// Event handlers
|
||||
this.client.open(() => {
|
||||
console.log('open');
|
||||
});
|
||||
|
||||
this.client.message(async (ws: WebSocket, message: any) => {
|
||||
try {
|
||||
await this.deviceMessagesService.getDevicesUserNotifications(
|
||||
message.payload.data.bizData.devId,
|
||||
message.payload.data.bizData,
|
||||
);
|
||||
this.client.ackMessage(message.messageId);
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.reconnect(() => {
|
||||
console.log('reconnect');
|
||||
});
|
||||
|
||||
this.client.ping(() => {
|
||||
console.log('ping');
|
||||
});
|
||||
|
||||
this.client.pong(() => {
|
||||
console.log('pong');
|
||||
});
|
||||
|
||||
this.client.close((ws: WebSocket, ...args: any[]) => {
|
||||
console.log('close', ...args);
|
||||
});
|
||||
|
||||
this.client.error((ws: WebSocket, error: any) => {
|
||||
console.error('WebSocket error:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DeviceNotificationEntity } from './entities';
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([DeviceNotificationEntity])],
|
||||
})
|
||||
export class DeviceNotificationRepositoryModule {}
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class GroupDeviceDto {
|
||||
export class DeviceNotificationDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public deviceUuid: string;
|
||||
public userUuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public groupUuid: string;
|
||||
public deviceUuid: string;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './device.notification.dto';
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceNotificationDto } from '../dtos';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
|
||||
@Entity({ name: 'device-notification' })
|
||||
@Unique(['userUuid', 'deviceUuid'])
|
||||
export class DeviceNotificationEntity extends AbstractEntity<DeviceNotificationDto> {
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
public userUuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
deviceUuid: string;
|
||||
|
||||
@ManyToOne(() => DeviceEntity, (device) => device.permission, {
|
||||
nullable: false,
|
||||
})
|
||||
device: DeviceEntity;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.userPermission, {
|
||||
nullable: false,
|
||||
})
|
||||
user: UserEntity;
|
||||
constructor(partial: Partial<DeviceNotificationEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './device.notification.entity';
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DeviceNotificationEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceNotificationRepository extends Repository<DeviceNotificationEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(DeviceNotificationEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './device.notification.repository';
|
||||
@@ -9,6 +9,10 @@ export class DeviceDto {
|
||||
@IsNotEmpty()
|
||||
spaceUuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userUuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
deviceTuyaUuid: string;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceDto } from '../dtos/device.dto';
|
||||
import { GroupDeviceEntity } from '../../group-device/entities';
|
||||
import { SpaceEntity } from '../../space/entities';
|
||||
import { ProductEntity } from '../../product/entities';
|
||||
import { DeviceUserPermissionEntity } from '../../device-user-permission/entities';
|
||||
import { DeviceNotificationEntity } from '../../device-notification/entities';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
|
||||
@Entity({ name: 'device' })
|
||||
@Unique(['spaceDevice', 'deviceTuyaUuid'])
|
||||
@Unique(['deviceTuyaUuid'])
|
||||
export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
||||
@Column({
|
||||
nullable: false,
|
||||
@@ -20,6 +21,9 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
||||
})
|
||||
isActive: true;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false })
|
||||
user: UserEntity;
|
||||
|
||||
@OneToMany(
|
||||
() => DeviceUserPermissionEntity,
|
||||
(permission) => permission.device,
|
||||
@@ -28,15 +32,17 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
|
||||
},
|
||||
)
|
||||
permission: DeviceUserPermissionEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => GroupDeviceEntity,
|
||||
(userGroupDevices) => userGroupDevices.device,
|
||||
() => DeviceNotificationEntity,
|
||||
(deviceUserNotification) => deviceUserNotification.device,
|
||||
{
|
||||
nullable: true,
|
||||
},
|
||||
)
|
||||
userGroupDevices: GroupDeviceEntity[];
|
||||
deviceUserNotification: DeviceNotificationEntity[];
|
||||
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, {
|
||||
nullable: false,
|
||||
nullable: true,
|
||||
})
|
||||
spaceDevice: SpaceEntity;
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './group.device.dto';
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||
import { GroupDeviceDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
import { GroupEntity } from '../../group/entities';
|
||||
|
||||
@Entity({ name: 'group-device' })
|
||||
@Unique(['device', 'group'])
|
||||
export class GroupDeviceEntity extends AbstractEntity<GroupDeviceDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@Column({
|
||||
type: 'string',
|
||||
nullable: false,
|
||||
})
|
||||
deviceUuid: string;
|
||||
|
||||
@Column({
|
||||
type: 'string',
|
||||
nullable: false,
|
||||
})
|
||||
groupUuid: string;
|
||||
|
||||
@ManyToOne(() => DeviceEntity, (device) => device.userGroupDevices, {
|
||||
nullable: false,
|
||||
})
|
||||
device: DeviceEntity;
|
||||
|
||||
@ManyToOne(() => GroupEntity, (group) => group.groupDevices, {
|
||||
nullable: false,
|
||||
})
|
||||
group: GroupEntity;
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
default: true,
|
||||
})
|
||||
public isActive: boolean;
|
||||
constructor(partial: Partial<GroupDeviceEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './group.device.entity';
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { GroupDeviceEntity } from './entities/group.device.entity';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([GroupDeviceEntity])],
|
||||
})
|
||||
export class GroupDeviceRepositoryModule {}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GroupDeviceEntity } from '../entities/group.device.entity';
|
||||
|
||||
@Injectable()
|
||||
export class GroupDeviceRepository extends Repository<GroupDeviceEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(GroupDeviceEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './group.device.repository';
|
||||
@@ -1,11 +0,0 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class GroupDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public groupName: string;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './group.dto';
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Column, Entity, OneToMany } from 'typeorm';
|
||||
import { GroupDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { GroupDeviceEntity } from '../../group-device/entities';
|
||||
|
||||
@Entity({ name: 'group' })
|
||||
export class GroupEntity extends AbstractEntity<GroupDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value
|
||||
nullable: false,
|
||||
})
|
||||
public uuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
public groupName: string;
|
||||
|
||||
@OneToMany(() => GroupDeviceEntity, (groupDevice) => groupDevice.group, {
|
||||
cascade: true,
|
||||
})
|
||||
groupDevices: GroupDeviceEntity[];
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
default: true,
|
||||
})
|
||||
public isActive: boolean;
|
||||
constructor(partial: Partial<GroupEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './group.entity';
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { GroupEntity } from './entities/group.entity';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([GroupEntity])],
|
||||
})
|
||||
export class GroupRepositoryModule {}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GroupEntity } from '../entities/group.entity';
|
||||
|
||||
@Injectable()
|
||||
export class GroupRepository extends Repository<GroupEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(GroupEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './group.repository';
|
||||
@@ -16,4 +16,8 @@ export class SpaceDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public spaceTypeUuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public invitationCode: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
|
||||
import { SpaceDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { SpaceTypeEntity } from '../../space-type/entities';
|
||||
@@ -6,6 +6,7 @@ import { UserSpaceEntity } from '../../user-space/entities';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
|
||||
@Entity({ name: 'space' })
|
||||
@Unique(['invitationCode'])
|
||||
export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
@Column({
|
||||
type: 'uuid',
|
||||
@@ -18,6 +19,11 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
|
||||
nullable: false,
|
||||
})
|
||||
public spaceName: string;
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
public invitationCode: string;
|
||||
@ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true })
|
||||
parent: SpaceEntity;
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './user.notification.dto';
|
||||
@@ -0,0 +1,19 @@
|
||||
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UserNotificationDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public uuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public userUuid: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
public subscriptionUuid: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsNotEmpty()
|
||||
public active: boolean;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './user.notification.entity';
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { UserNotificationDto } from '../dtos';
|
||||
import { UserEntity } from '../../user/entities';
|
||||
|
||||
@Entity({ name: 'user-notification' })
|
||||
@Unique(['user', 'subscriptionUuid'])
|
||||
export class UserNotificationEntity extends AbstractEntity<UserNotificationDto> {
|
||||
@ManyToOne(() => UserEntity, (user) => user.roles, {
|
||||
nullable: false,
|
||||
})
|
||||
user: UserEntity;
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
subscriptionUuid: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
default: true,
|
||||
})
|
||||
active: boolean;
|
||||
constructor(partial: Partial<UserNotificationEntity>) {
|
||||
super();
|
||||
Object.assign(this, partial);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './user.notification.repository';
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { UserNotificationEntity } from '../entities';
|
||||
|
||||
@Injectable()
|
||||
export class UserNotificationRepository extends Repository<UserNotificationEntity> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(UserNotificationEntity, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UserNotificationEntity } from './entities';
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [],
|
||||
imports: [TypeOrmModule.forFeature([UserNotificationEntity])],
|
||||
})
|
||||
export class UserNotificationRepositoryModule {}
|
||||
@@ -4,6 +4,9 @@ import { UserDto } from '../dtos';
|
||||
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
|
||||
import { UserSpaceEntity } from '../../user-space/entities';
|
||||
import { UserRoleEntity } from '../../user-role/entities';
|
||||
import { DeviceNotificationEntity } from '../../device-notification/entities';
|
||||
import { UserNotificationEntity } from '../../user-notification/entities';
|
||||
import { DeviceEntity } from '../../device/entities';
|
||||
|
||||
@Entity({ name: 'user' })
|
||||
export class UserEntity extends AbstractEntity<UserDto> {
|
||||
@@ -53,12 +56,24 @@ export class UserEntity extends AbstractEntity<UserDto> {
|
||||
@OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user)
|
||||
userSpaces: UserSpaceEntity[];
|
||||
|
||||
@OneToMany(() => DeviceEntity, (userDevice) => userDevice.user)
|
||||
userDevice: DeviceEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => UserNotificationEntity,
|
||||
(userNotification) => userNotification.user,
|
||||
)
|
||||
userNotification: UserNotificationEntity[];
|
||||
@OneToMany(
|
||||
() => DeviceUserPermissionEntity,
|
||||
(userPermission) => userPermission.user,
|
||||
)
|
||||
userPermission: DeviceUserPermissionEntity[];
|
||||
|
||||
@OneToMany(
|
||||
() => DeviceNotificationEntity,
|
||||
(deviceUserNotification) => deviceUserNotification.user,
|
||||
)
|
||||
deviceUserNotification: DeviceNotificationEntity[];
|
||||
@OneToMany(() => UserRoleEntity, (role) => role.user, {
|
||||
nullable: true,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user