Merge branch 'dev' into SP-197-be-retrieve-devices-in-the-gateway

This commit is contained in:
faris Aljohari
2024-06-11 12:03:30 +03:00
85 changed files with 2151 additions and 839 deletions
+1
View File
@@ -13,6 +13,7 @@ import { EmailService } from './util/email.service';
imports: [
ConfigModule.forRoot({
load: config,
isGlobal: true,
}),
DatabaseModule,
HelperModule,
+4 -1
View File
@@ -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 '';
}
}
+11
View File
@@ -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,
}),
);
+4 -4
View File
@@ -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'))),
+16 -2
View File
@@ -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 {}
+10
View File
@@ -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 {}
@@ -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';
@@ -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';
@@ -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,
})