This commit is contained in:
Amirhossein Mahmoodi
2024-07-29 16:03:34 +03:30
parent 0ad17d2c8c
commit d09567d9d5
19 changed files with 9443 additions and 194 deletions

View File

@@ -1,24 +1,25 @@
module.exports = { module.exports = {
parser: "@typescript-eslint/parser", parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
project: "tsconfig.json", project: 'tsconfig.json',
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
sourceType: "module", sourceType: 'module',
}, },
plugins: ["@typescript-eslint/eslint-plugin"], plugins: ['@typescript-eslint/eslint-plugin'],
extends: [ extends: [
"plugin:@typescript-eslint/recommended", 'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
], ],
root: true, root: true,
env: { env: {
node: true, node: true,
jest: true, jest: true,
}, },
ignorePatterns: [".eslintrc.js"], ignorePatterns: ['.eslintrc.js'],
rules: { rules: {
"@typescript-eslint/interface-name-prefix": "off", '@typescript-eslint/interface-name-prefix': 'off',
"@typescript-eslint/explicit-function-return-type": "off", '@typescript-eslint/explicit-function-return-type': 'off',
"@typescript-eslint/explicit-module-boundary-types": "off", '@typescript-eslint/explicit-module-boundary-types': 'off',
"@typescript-eslint/no-explicit-any": "off", '@typescript-eslint/no-explicit-any': 'off',
}, },
}; };

26
.gitignore vendored
View File

@@ -1,9 +1,7 @@
# compiled output # compiled output
/dist /dist
/node_modules /node_modules
.env /build
package-lock.json
ecosystem.config.js
# Logs # Logs
logs logs
@@ -35,4 +33,24 @@ lerna-debug.log*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

View File

@@ -1,4 +1,4 @@
{ {
"singleQuote": false, "singleQuote": true,
"trailingComma": "all" "trailingComma": "all"
} }

9234
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{ {
"name": "notification-server", "name": "notification_server",
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"author": "", "author": "",
@@ -21,20 +21,18 @@
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.1.1", "@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.2.7", "@nestjs/platform-socket.io": "^10.3.10",
"@nestjs/sequelize": "^10.0.0", "@nestjs/typeorm": "^10.0.2",
"@nestjs/websockets": "^10.2.7", "@nestjs/websockets": "^10.3.10",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.1",
"mysql2": "^3.6.2", "pg": "^8.12.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"sequelize": "^6.33.0", "typeorm": "^0.3.20"
"sequelize-typescript": "^2.1.5",
"socket.io": "^4.7.2"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.0.0", "@nestjs/cli": "^10.0.0",
@@ -43,17 +41,16 @@
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jest": "^29.5.2", "@types/jest": "^29.5.2",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/sequelize": "^4.28.17", "@types/supertest": "^6.0.0",
"@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^7.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0", "eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"supertest": "^6.3.3", "supertest": "^7.0.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-loader": "^9.4.3", "ts-loader": "^9.4.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",

View File

@@ -1,25 +1,25 @@
import { Module } from "@nestjs/common"; import { Module } from '@nestjs/common';
import { NotificationModule } from "./notifications/notification.module"; import { ConfigModule } from '@nestjs/config';
import { SequelizeModule } from "@nestjs/sequelize"; import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from "@nestjs/config"; import { SocketModule } from './socket/socket.module';
import { SocketModule } from "./socket/socket.module"; import { NotificationModule } from './notifications/notification.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
SequelizeModule.forRoot({ TypeOrmModule.forRoot({
dialect: "mysql", type: 'postgres',
host: process.env.DB_HOST, host: process.env.POSTGRES_HOST,
port: parseInt(process.env.DB_PORT), port: 5432,
username: process.env.DB_USERNAME, ssl: process.env.POSTGRES_SSL === 'true',
password: process.env.DB_PASSWORD, username: process.env.POSTGRES_USER,
database: process.env.DB_NAME, password: process.env.POSTGRES_PASSWORD,
autoLoadModels: true, database: process.env.POSTGRES_DATABASE,
synchronize: true, entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: process.env.DB_SYNC === 'true',
}), }),
NotificationModule,
SocketModule, SocketModule,
NotificationModule,
], ],
}) })
export class AppModule { export class AppModule {}
}

View File

@@ -1,7 +1,7 @@
import { NestFactory } from "@nestjs/core"; import { NestFactory } from '@nestjs/core';
import { AppModule } from "./app.module"; import { AppModule } from './app.module';
import { ValidationPipe } from "@nestjs/common"; import { ValidationPipe } from '@nestjs/common';
import { IoAdapter } from "@nestjs/platform-socket.io"; import { IoAdapter } from '@nestjs/platform-socket.io';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
@@ -9,5 +9,4 @@ async function bootstrap() {
app.useWebSocketAdapter(new IoAdapter(app)); app.useWebSocketAdapter(new IoAdapter(app));
await app.listen(parseInt(process.env.PORT)); await app.listen(parseInt(process.env.PORT));
} }
bootstrap(); bootstrap();

View File

@@ -1,16 +1,13 @@
import { Body, Controller, Post } from "@nestjs/common"; import { Body, Controller, Post } from '@nestjs/common';
import { NotificationDto } from "./notification.dto"; import { NotificationDto } from './notification.dto';
import { NotificationService } from "./notification.service"; import { NotificationService } from './notification.service';
@Controller("notifications") @Controller('notifications')
export class NotificationController { export class NotificationController {
constructor(private notificationService: NotificationService) {}
constructor(private notificationService: NotificationService) { @Post('send')
}
@Post("send")
async sendNotification(@Body() notificationData: NotificationDto) { async sendNotification(@Body() notificationData: NotificationDto) {
return await this.notificationService.sendNotification(notificationData); return await this.notificationService.sendNotification(notificationData);
} }
} }

View File

@@ -1,5 +0,0 @@
export class NotificationInterface {
call_id: number;
telephone_id: string;
phone_number: string;
}

View File

@@ -1,15 +1,18 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from '@nestjs/common';
import { SocketGateway } from "../socket/socket.gateway"; import { SocketGateway } from '../socket/socket.gateway';
import { NotificationInterface } from "./notification.interface"; import { NotificationDto } from './notification.dto';
@Injectable() @Injectable()
export class NotificationService { export class NotificationService {
constructor( constructor(private socketGateway: SocketGateway) {}
private socketGateway: SocketGateway,
) {
}
async sendNotification(notificationData: NotificationInterface) { async sendNotification(notificationData: NotificationDto) {
return await this.socketGateway.handleSendNotification(notificationData); const auth: string[] = [];
auth.push(notificationData.telephone_id);
return await this.socketGateway.handleSendMessage({
auth,
data: notificationData,
event: 'answer',
});
} }
} }

View File

@@ -0,0 +1,6 @@
export class ClientConnectionDto {
client_id: string;
socket_id: string;
event: string;
description?: string;
}

View File

@@ -0,0 +1,5 @@
export class SendMessageDto {
auth: string[];
data: any;
event: string;
}

View File

@@ -0,0 +1,19 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class SocketConnection {
@PrimaryGeneratedColumn()
id: number;
@Column()
client_id: string;
@Column()
socket_id: string;
@Column()
event: string;
@Column({ nullable: true })
description: string;
}

View File

@@ -1,35 +1,61 @@
import { OnGatewayConnection, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; import {
import { Server, Socket } from "socket.io"; OnGatewayConnection,
import { NotificationInterface } from "../notifications/notification.interface"; SubscribeMessage,
import { SocketService } from "./socket.service"; WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { SocketService } from './socket.service';
import { SendMessageDto } from './dto/sendMessage.dto';
@WebSocketGateway() @WebSocketGateway()
export class SocketGateway implements OnGatewayConnection { export class SocketGateway implements OnGatewayConnection {
@WebSocketServer() server: Server; @WebSocketServer() server: Server;
constructor( constructor(private socketService: SocketService) {}
private socketService: SocketService,
) {
}
handleConnection(client: Socket) { async handleConnection(client: Socket) {
this.socketService.ClientConnection({ await this.socketService.ClientConnection({
client_id: client.handshake.auth.token, client_id: client.handshake.auth.token,
socket_id: client.id, socket_id: client.id,
event: "connected", event: 'connected',
}); });
client.on("disconnect", (reason) => { client.on('disconnect', async (reason) => {
this.socketService.ClientConnection({ await this.socketService.ClientConnection({
client_id: client.handshake.auth.token, client_id: client.handshake.auth.token,
socket_id: client.id, socket_id: client.id,
event: "disconnected", event: 'disconnected',
description: reason, description: reason,
}); });
}); });
} }
@SubscribeMessage("sendNotificationToClients") @SubscribeMessage('sendMessageToClients')
handleSendNotification(notificationData: NotificationInterface) { handleSendMessage(sendMessageDto: SendMessageDto) {
return this.socketService.sendNotificationToClients(this.server, notificationData); const clients = this.getConnectedClients(sendMessageDto.auth);
if (clients.length === 0) {
return {
status: 0,
message: 'Failed to find the clients!',
};
}
return this.socketService.sendMessageToClients(
clients,
sendMessageDto.data,
sendMessageDto.event,
);
} }
}
getConnectedClients(auth: string[]): Socket[] {
const connectedClients = this.server.sockets.sockets;
const clients = [];
for (const connectedClient of connectedClients) {
if (auth.includes(connectedClient[1].handshake.auth.token)) {
clients.push(connectedClient[1]);
}
}
return clients;
}
}

View File

@@ -1,24 +0,0 @@
import { Column, DataType, Model, Table } from "sequelize-typescript";
@Table({ updatedAt: false, tableName: "notifications" })
export class SocketModel extends Model {
@Column({
type: DataType.BIGINT,
allowNull: false,
autoIncrement: true,
primaryKey: true,
})
id: number;
@Column({ allowNull: false })
client_id: string;
@Column({ allowNull: false })
socket_id: string;
@Column({ allowNull: false })
event: string;
@Column
description: string;
}

View File

@@ -1,13 +1,12 @@
import { Module } from "@nestjs/common"; import { Module } from '@nestjs/common';
import { SocketGateway } from "./socket.gateway"; import { TypeOrmModule } from '@nestjs/typeorm';
import { SocketService } from "./socket.service"; import { SocketConnection } from './socket.entity';
import { SequelizeModule } from "@nestjs/sequelize"; import { SocketGateway } from './socket.gateway';
import { SocketModel } from "./socket.model"; import { SocketService } from './socket.service';
@Module({ @Module({
imports: [SequelizeModule.forFeature([SocketModel])], imports: [TypeOrmModule.forFeature([SocketConnection])],
providers: [SocketGateway, SocketService], providers: [SocketGateway, SocketService],
exports: [SocketGateway], exports: [SocketGateway],
}) })
export class SocketModule { export class SocketModule {}
}

View File

@@ -1,53 +1,60 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from '@nestjs/common';
import { InjectModel } from "@nestjs/sequelize"; import { InjectRepository } from '@nestjs/typeorm';
import { SocketModel } from "./socket.model"; import { Socket } from 'socket.io';
import { Server } from "socket.io"; import { Repository } from 'typeorm';
import { NotificationInterface } from "../notifications/notification.interface"; import { ClientConnectionDto } from './dto/clientConnection.dto';
import { SocketConnection } from './socket.entity';
@Injectable() @Injectable()
export class SocketService { export class SocketService {
constructor( constructor(
@InjectModel(SocketModel) @InjectRepository(SocketConnection)
private socketModel: typeof SocketModel, private socketConnectionRepository: Repository<SocketConnection>,
) { ) {}
async ClientConnection(data: ClientConnectionDto) {
const client = this.socketConnectionRepository.create(data);
await this.socketConnectionRepository.save(client);
} }
ClientConnection(data: any) { sendMessageToClients(clients: Socket[], data: any, event: string) {
this.socketModel.create(data);
}
sendNotificationToClients(server: Server, notificationData: NotificationInterface) {
return new Promise(async (resolveHandlerSend) => { return new Promise(async (resolveHandlerSend) => {
const connectedClients = server.sockets.sockets; let sent: boolean = false;
if (connectedClients.size === 0) { try {
resolveHandlerSend({ status: 1, message: "Failed to find the clients!" }); for (const client of clients) {
return;
}
let sent: any = false;
for (const connectedClient of connectedClients) {
if (connectedClient[1].handshake.auth.token == notificationData.telephone_id) {
sent = await new Promise((resolveSend) => { sent = await new Promise((resolveSend) => {
connectedClient[1].timeout(2000).emit("answer", JSON.stringify(notificationData), (err: any) => { client
if (err) { .timeout(2000)
resolveSend(false); .emit(event, JSON.stringify(data), (err: any) => {
} else { if (err) {
resolveSend(true); resolveSend(false);
} } else {
}); resolveSend(true);
}
});
}); });
} }
} catch (error) {
resolveHandlerSend({
status: 0,
message: 'Failed to send to the clients!',
});
return;
} }
if (!sent) { if (!sent) {
resolveHandlerSend({ status: 2, message: "Failed to send to the clients!" }); resolveHandlerSend({
status: 0,
message: 'Failed to send to the clients!',
});
return; return;
} }
resolveHandlerSend({ status: 0, message: "Success in sending to the clients!" }); resolveHandlerSend({
status: 1,
message: 'Success in sending to the clients!',
});
}); });
} }
} }

View File

@@ -1,24 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

@@ -1,9 +0,0 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}