diff --git a/.env.example b/.env.example index f559c39..9f3f1d4 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,21 @@ -#app +# app PORT=3023 -#database -POSTGRES_HOST="localhost" +# database +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=postgres +DB_DATABASE=mydb + +POSTGRES_HOST=db POSTGRES_PORT=5432 -POSTGRES_USER="postgres" -POSTGRES_PASSWORD="" -POSTGRES_DATABASE="notification_db" -POSTGRES_SSL=false -POSTGRES_SYNC=true \ No newline at end of file +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=mydb + +DB_SSL=false +DB_SYNC=true + +REDIS_HOST=redis +REDIS_PORT=6379 diff --git a/docker-compose.yml b/docker-compose.yml index d5dab4c..f04dd61 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,10 +16,13 @@ services: - DB_SSL=${DB_SSL} - DB_SYNC=${DB_SYNC} - PORT=${PORT} + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} ports: - - '3023:3023' + - '3023:${PORT}' depends_on: - db + - redis restart: unless-stopped db: @@ -34,6 +37,13 @@ services: ports: - '5432:5432' restart: unless-stopped + + redis: + image: redis:7.0-alpine + container_name: redis + ports: + - '6379:6379' + restart: unless-stopped volumes: pgdata: \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index de84112..c919227 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,11 @@ "@nestjs/platform-socket.io": "^10.3.10", "@nestjs/typeorm": "^10.0.2", "@nestjs/websockets": "^10.3.10", + "@socket.io/redis-adapter": "^8.3.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "pg": "^8.12.0", + "redis": "^4.7.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "typeorm": "^0.3.20" @@ -1910,6 +1912,64 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1939,6 +1999,22 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "node_modules/@socket.io/redis-adapter": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz", + "integrity": "sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA==", + "dependencies": { + "debug": "~4.3.1", + "notepack.io": "~3.0.1", + "uid2": "1.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "socket.io-adapter": "^2.5.4" + } + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -3494,6 +3570,14 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4824,6 +4908,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6669,6 +6761,11 @@ "node": ">=0.10.0" } }, + "node_modules/notepack.io": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", + "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==" + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -7405,6 +7502,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -8806,6 +8916,14 @@ "node": ">=8" } }, + "node_modules/uid2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz", + "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 6a9982a..2a90422 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ "@nestjs/platform-socket.io": "^10.3.10", "@nestjs/typeorm": "^10.0.2", "@nestjs/websockets": "^10.3.10", + "@socket.io/redis-adapter": "^8.3.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "pg": "^8.12.0", + "redis": "^4.7.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "typeorm": "^0.3.20" diff --git a/src/main.ts b/src/main.ts index 4931575..2f6b7ec 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ +import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import { ValidationPipe } from '@nestjs/common'; -import { IoAdapter } from '@nestjs/platform-socket.io'; +import { RedisIoAdapter } from './socket-redis.adapter'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -10,7 +10,9 @@ async function bootstrap() { credentials: true, }); app.useGlobalPipes(new ValidationPipe()); - app.useWebSocketAdapter(new IoAdapter(app)); - await app.listen(parseInt(process.env.PORT)); + const redisAdapter = new RedisIoAdapter(app); + await redisAdapter.connectToRedis(); + app.useWebSocketAdapter(redisAdapter); + await app.listen(parseInt(process.env.PORT), '0.0.0.0'); } bootstrap(); diff --git a/src/socket-redis.adapter.ts b/src/socket-redis.adapter.ts new file mode 100644 index 0000000..c8828ce --- /dev/null +++ b/src/socket-redis.adapter.ts @@ -0,0 +1,25 @@ +import { IoAdapter } from '@nestjs/platform-socket.io'; +import { ServerOptions } from 'socket.io'; +import { createAdapter } from '@socket.io/redis-adapter'; +import { createClient } from 'redis'; + +export class RedisIoAdapter extends IoAdapter { + private adapterConstructor: ReturnType; + + async connectToRedis(): Promise { + const pubClient = createClient({ + url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, + }); + const subClient = pubClient.duplicate(); + + await Promise.all([pubClient.connect(), subClient.connect()]); + + this.adapterConstructor = createAdapter(pubClient, subClient); + } + + createIOServer(port: number, options?: ServerOptions) { + const server = super.createIOServer(port, options); + server.adapter(this.adapterConstructor); + return server; + } +} diff --git a/src/socket/socket.gateway.ts b/src/socket/socket.gateway.ts index 32ea9e4..bf1eb3b 100644 --- a/src/socket/socket.gateway.ts +++ b/src/socket/socket.gateway.ts @@ -7,20 +7,25 @@ import { import { Server, Socket } from 'socket.io'; import { SendMessageDto } from './dto/sendMessage.dto'; import { SocketService } from './socket.service'; +import { Logger } from '@nestjs/common'; @WebSocketGateway({ + namespace: 'notification', cors: { origin: '*', }, path: '/notification/socket.io', }) export class SocketGateway implements OnGatewayConnection { + private readonly logger = new Logger(SocketGateway.name); + @WebSocketServer() server: Server; constructor(private socketService: SocketService) {} async handleConnection(client: Socket) { + this.logger.log(`Client connected: ${client.id}`); await this.socketService.clientConnection({ client_id: client.handshake.auth.token, socket_id: client.id,