From 6a3e921663cb8b47293248578894963841e99718 Mon Sep 17 00:00:00 2001 From: Bilaboz Date: Fri, 10 Nov 2023 19:20:08 +0100 Subject: [PATCH] notify module --- .../20231110173333_user_status/migration.sql | 13 +++++++ api/prisma/schema.prisma | 8 +++- api/src/modules/auth/auth.dto.ts | 6 ++- api/src/modules/auth/auth.service.ts | 3 +- api/src/modules/notify/index.ts | 1 + api/src/modules/notify/notify.gateway.ts | 28 +++++++------- api/src/modules/notify/notify.module.ts | 8 ++-- api/src/modules/notify/notify.service.ts | 37 ++++++++++++++++++- api/test/game/game.service.spec.ts | 6 +-- api/test/user/user.service.spec.ts | 8 ++-- 10 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 api/prisma/migrations/20231110173333_user_status/migration.sql diff --git a/api/prisma/migrations/20231110173333_user_status/migration.sql b/api/prisma/migrations/20231110173333_user_status/migration.sql new file mode 100644 index 0000000..468c309 --- /dev/null +++ b/api/prisma/migrations/20231110173333_user_status/migration.sql @@ -0,0 +1,13 @@ +/* + Warnings: + + - You are about to drop the column `isConnected` on the `User` table. All the data in the column will be lost. + - Added the required column `status` to the `User` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "UserStatus" AS ENUM ('OFFLINE', 'ONLINE', 'INGAME'); + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "isConnected", +ADD COLUMN "status" "UserStatus" NOT NULL; diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 0f17e99..17718e0 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -19,12 +19,18 @@ enum ChannelRole { OWNER } +enum UserStatus { + OFFLINE + ONLINE + INGAME +} + model User { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) login String @unique email String @unique - isConnected Boolean + status UserStatus imageUrl String? displayName String @unique firstName String diff --git a/api/src/modules/auth/auth.dto.ts b/api/src/modules/auth/auth.dto.ts index b4de276..0a2dbdd 100644 --- a/api/src/modules/auth/auth.dto.ts +++ b/api/src/modules/auth/auth.dto.ts @@ -1,4 +1,5 @@ -import { IsEmail, IsNotEmpty } from 'class-validator'; +import { UserStatus } from '@prisma/client'; +import { IsEmail, IsEnum, IsNotEmpty } from 'class-validator'; export class JwtPayload { login: string; @@ -21,7 +22,8 @@ export class CreateUserDto { @IsNotEmpty() lastName: string; - isConnected: boolean; + @IsEnum(UserStatus) + status: UserStatus; bannerUrl: string; diff --git a/api/src/modules/auth/auth.service.ts b/api/src/modules/auth/auth.service.ts index a645cbd..47b7804 100644 --- a/api/src/modules/auth/auth.service.ts +++ b/api/src/modules/auth/auth.service.ts @@ -1,5 +1,6 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { UserStatus } from '@prisma/client'; import axios from 'axios'; import { UserService } from '../user'; @@ -60,7 +61,7 @@ export class AuthService { firstName: profile.first_name, lastName: profile.last_name, description: 'No description atm.', - isConnected: true, + status: UserStatus.OFFLINE, bannerUrl: 'Good banner to place here', }; } catch (error) { diff --git a/api/src/modules/notify/index.ts b/api/src/modules/notify/index.ts index 74ce27c..371bf1f 100644 --- a/api/src/modules/notify/index.ts +++ b/api/src/modules/notify/index.ts @@ -1 +1,2 @@ export * from './notify.module'; +export * from './notify.service'; diff --git a/api/src/modules/notify/notify.gateway.ts b/api/src/modules/notify/notify.gateway.ts index 0279cf0..6189f56 100644 --- a/api/src/modules/notify/notify.gateway.ts +++ b/api/src/modules/notify/notify.gateway.ts @@ -1,11 +1,10 @@ -import { Logger, UseFilters, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common'; +import { Logger, UseFilters, UseGuards } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, WebSocketGateway, - WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @@ -13,40 +12,39 @@ import { HttpExceptionTransformationFilter } from '@/utils/ws-http-exception.fil import { WSAuthMiddleware } from '../auth/ws/ws.middleware'; import { WsJwtGuard } from '../auth/ws/ws-jwt.guard'; -import { FriendService, UserService } from '../user'; +import { UserService } from '../user'; +import { NotifyService } from './notify.service'; @UseGuards(WsJwtGuard) -@UsePipes(new ValidationPipe()) @WebSocketGateway({ namespace: 'notify' }) @UseFilters(HttpExceptionTransformationFilter) export class NotifyGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { - @WebSocketServer() - private io: Server; private logger: Logger = new Logger(NotifyGateway.name); - private socketsID: Map = new Map(); + private sockets: Map = new Map(); constructor( private jwtService: JwtService, private userService: UserService, - private friendService: FriendService, + private notifyService: NotifyService, ) {} afterInit(server: Server) { const authMiddleware = WSAuthMiddleware(this.jwtService, this.userService); server.use(authMiddleware); + + this.notifyService.sockets = this.sockets; } async handleConnection(socket: Socket) { this.logger.log('Client connected: ' + socket.id); - this.socketsID.set(socket.data.user.login, socket.id); + this.sockets.set(socket.data.user.login, socket); - const friends = await this.friendService.getFriendList(socket.data.user); - for (const friend of friends) { - const socketID = this.socketsID.get(friend.login); - } + await this.notifyService.online(socket.data.user); } - handleDisconnect(socket: Socket) { - this.socketsID.delete(socket.data.user.login); + async handleDisconnect(socket: Socket) { + await this.notifyService.offline(socket.data.user); + + this.sockets.delete(socket.data.user.login); } } diff --git a/api/src/modules/notify/notify.module.ts b/api/src/modules/notify/notify.module.ts index 5243460..4bad439 100644 --- a/api/src/modules/notify/notify.module.ts +++ b/api/src/modules/notify/notify.module.ts @@ -1,11 +1,13 @@ import { Module } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { FriendService, UserService } from '../user'; +import { UserModule } from '../user'; +import { NotifyGateway } from './notify.gateway'; import { NotifyService } from './notify.service'; @Module({ controllers: [], - providers: [NotifyService, JwtService, UserService, FriendService], + providers: [NotifyGateway, NotifyService], + imports: [UserModule], + exports: [NotifyService], }) export class NotifyModule {} diff --git a/api/src/modules/notify/notify.service.ts b/api/src/modules/notify/notify.service.ts index db0e1e8..7db6633 100644 --- a/api/src/modules/notify/notify.service.ts +++ b/api/src/modules/notify/notify.service.ts @@ -1 +1,36 @@ -export class NotifyService {} +import { Injectable } from '@nestjs/common'; +import { User, UserStatus } from '@prisma/client'; +import { Socket } from 'socket.io'; + +import { FriendService } from '../user'; + +@Injectable() +export class NotifyService { + public sockets: Map; + + constructor(private friendService: FriendService) {} + + async emitToFriends(user: User, status: UserStatus) { + const friends = await this.friendService.getFriendList(user, true); + + for (const friend of friends) { + const socket = this.sockets.get(friend.login); + + if (socket) { + socket.emit('notify', { user: user, status: status }); + } + } + } + + async online(user: User) { + await this.emitToFriends(user, UserStatus.ONLINE); + } + + async offline(user: User) { + await this.emitToFriends(user, UserStatus.OFFLINE); + } + + async inGame(user: User) { + await this.emitToFriends(user, UserStatus.INGAME); + } +} diff --git a/api/test/game/game.service.spec.ts b/api/test/game/game.service.spec.ts index 1100681..635dbfe 100644 --- a/api/test/game/game.service.spec.ts +++ b/api/test/game/game.service.spec.ts @@ -1,5 +1,5 @@ import { Test } from '@nestjs/testing'; -import { Game, PrismaClient, User } from '@prisma/client'; +import { Game, PrismaClient, User, UserStatus } from '@prisma/client'; import { DeepMockProxy, mockDeep } from 'jest-mock-extended'; import { GameService } from '../../src/modules/game'; @@ -40,7 +40,7 @@ describe('Gameservice', () => { displayName: 'testLoginOne', firstName: 'testFirstNameOne', lastName: 'testLastNameOne', - isConnected: true, + status: UserStatus.ONLINE, bannerUrl: 'bannerUrlOne', description: 'descriptionOne', createdAt: new Date(), @@ -54,7 +54,7 @@ describe('Gameservice', () => { displayName: 'testLoginTwo', firstName: 'testFirstNameTwo', lastName: 'testLastNameTwo', - isConnected: true, + status: UserStatus.ONLINE, bannerUrl: 'bannerUrlTwo', description: 'descriptionTwo', createdAt: new Date(), diff --git a/api/test/user/user.service.spec.ts b/api/test/user/user.service.spec.ts index dfccc90..acc07f6 100644 --- a/api/test/user/user.service.spec.ts +++ b/api/test/user/user.service.spec.ts @@ -1,5 +1,5 @@ import { Test } from '@nestjs/testing'; -import { PrismaClient } from '@prisma/client'; +import { PrismaClient, UserStatus } from '@prisma/client'; import { DeepMockProxy, mockDeep } from 'jest-mock-extended'; import { CreateUserDto } from '@/modules/auth'; @@ -45,7 +45,7 @@ describe('UserService', () => { displayName: 'testLogin', firstName: 'testFirstName', lastName: 'testLastName', - isConnected: true, + status: UserStatus.ONLINE, bannerUrl: 'bannerUrl', description: 'description', createdAt: new Date(), @@ -58,7 +58,7 @@ describe('UserService', () => { displayName: 'testLogin', firstName: 'testFirstName', lastName: 'testLastName', - isConnected: true, + status: UserStatus.ONLINE, bannerUrl: 'bannerUrl', description: 'description', }; @@ -101,7 +101,7 @@ describe('UserService', () => { displayName: 'newDisplayName', firstName: 'testFirstName', lastName: 'testLastName', - isConnected: true, + status: UserStatus.ONLINE, bannerUrl: 'new bannerUrl', description: 'description', createdAt: new Date(),