Skip to content

Commit

Permalink
Merge pull request #57 from B-ki/notify
Browse files Browse the repository at this point in the history
Notify
  • Loading branch information
Bilaboz authored Nov 13, 2023
2 parents e874cb2 + 6a3e921 commit 889616f
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 43 deletions.
13 changes: 13 additions & 0 deletions api/prisma/migrations/20231110173333_user_status/migration.sql
Original file line number Diff line number Diff line change
@@ -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;
8 changes: 7 additions & 1 deletion api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { loggingMiddleware, PrismaModule } from 'nestjs-prisma';
import { AuthModule } from './modules/auth';
import { ChatModule } from './modules/chat';
import { GameModule } from './modules/game';
import { NotifyModule } from './modules/notify';
import { UserModule } from './modules/user';

@Module({
Expand All @@ -12,6 +13,7 @@ import { UserModule } from './modules/user';
AuthModule,
GameModule,
ChatModule,
NotifyModule,
PrismaModule.forRoot({
isGlobal: true,
prismaServiceOptions: {
Expand Down
6 changes: 4 additions & 2 deletions api/src/modules/auth/auth.dto.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,7 +22,8 @@ export class CreateUserDto {
@IsNotEmpty()
lastName: string;

isConnected: boolean;
@IsEnum(UserStatus)
status: UserStatus;

bannerUrl: string;

Expand Down
3 changes: 2 additions & 1 deletion api/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions api/src/modules/notify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './notify.module';
export * from './notify.service';
5 changes: 5 additions & 0 deletions api/src/modules/notify/notify.events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum NotifyEvent {
Online = 'online',
Offline = 'offline',
InGame = 'inGame',
}
50 changes: 50 additions & 0 deletions api/src/modules/notify/notify.gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Logger, UseFilters, UseGuards } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import {
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
WebSocketGateway,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

import { HttpExceptionTransformationFilter } from '@/utils/ws-http-exception.filter';

import { WSAuthMiddleware } from '../auth/ws/ws.middleware';
import { WsJwtGuard } from '../auth/ws/ws-jwt.guard';
import { UserService } from '../user';
import { NotifyService } from './notify.service';

@UseGuards(WsJwtGuard)
@WebSocketGateway({ namespace: 'notify' })
@UseFilters(HttpExceptionTransformationFilter)
export class NotifyGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
private logger: Logger = new Logger(NotifyGateway.name);
private sockets: Map<string, Socket> = new Map<string, Socket>();

constructor(
private jwtService: JwtService,
private userService: UserService,
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.sockets.set(socket.data.user.login, socket);

await this.notifyService.online(socket.data.user);
}

async handleDisconnect(socket: Socket) {
await this.notifyService.offline(socket.data.user);

this.sockets.delete(socket.data.user.login);
}
}
13 changes: 13 additions & 0 deletions api/src/modules/notify/notify.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';

import { UserModule } from '../user';
import { NotifyGateway } from './notify.gateway';
import { NotifyService } from './notify.service';

@Module({
controllers: [],
providers: [NotifyGateway, NotifyService],
imports: [UserModule],
exports: [NotifyService],
})
export class NotifyModule {}
36 changes: 36 additions & 0 deletions api/src/modules/notify/notify.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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<string, Socket>;

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);
}
}
39 changes: 7 additions & 32 deletions api/src/modules/user/friend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export class FriendService {
});

if (!newFriend) {
throw new NotFoundException('Friend doesnt exists');
throw new NotFoundException(`User ${friendLogin} doesn't exist`);
}

if (user === newFriend) {
throw new BadRequestException('Cant add user to its own friendlist');
if (user.id === newFriend.id) {
throw new BadRequestException('You cannot add yourself as a friend');
}

if (newFriend.friendOf.find((f) => f.id === user.id)) {
Expand All @@ -42,19 +42,6 @@ export class FriendService {
},
});

await this.prisma.user.update({
where: {
id: newFriend.id,
},
data: {
friendOf: {
connect: {
id: user.id,
},
},
},
});

return { success: true };
}

Expand All @@ -69,7 +56,7 @@ export class FriendService {
});

if (!friendToRemove) {
throw new NotFoundException('Friend doesnt exists');
throw new NotFoundException(`User ${friendToRemoveLogin} doesn't exist`);
}

if (!friendToRemove.friendOf.find((f) => f.id === user.id)) {
Expand All @@ -89,33 +76,21 @@ export class FriendService {
},
});

await this.prisma.user.update({
where: {
id: friendToRemove.id,
},
data: {
friendOf: {
disconnect: {
id: user.id,
},
},
},
});

return { success: true };
}

async getFriendList(user: User) {
async getFriendList(user: User, friendOf: boolean = false) {
const u = await this.prisma.user.findUnique({
where: {
id: user.id,
},
include: {
friends: true,
friendOf: true,
},
});

// User is never null since he is authenticated
return u!.friends;
return friendOf ? u!.friendOf : u!.friends;
}
}
6 changes: 3 additions & 3 deletions api/test/game/game.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -40,7 +40,7 @@ describe('Gameservice', () => {
displayName: 'testLoginOne',
firstName: 'testFirstNameOne',
lastName: 'testLastNameOne',
isConnected: true,
status: UserStatus.ONLINE,
bannerUrl: 'bannerUrlOne',
description: 'descriptionOne',
createdAt: new Date(),
Expand All @@ -54,7 +54,7 @@ describe('Gameservice', () => {
displayName: 'testLoginTwo',
firstName: 'testFirstNameTwo',
lastName: 'testLastNameTwo',
isConnected: true,
status: UserStatus.ONLINE,
bannerUrl: 'bannerUrlTwo',
description: 'descriptionTwo',
createdAt: new Date(),
Expand Down
8 changes: 4 additions & 4 deletions api/test/user/user.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('UserService', () => {
displayName: 'testLogin',
firstName: 'testFirstName',
lastName: 'testLastName',
isConnected: true,
status: UserStatus.ONLINE,
bannerUrl: 'bannerUrl',
description: 'description',
createdAt: new Date(),
Expand All @@ -58,7 +58,7 @@ describe('UserService', () => {
displayName: 'testLogin',
firstName: 'testFirstName',
lastName: 'testLastName',
isConnected: true,
status: UserStatus.ONLINE,
bannerUrl: 'bannerUrl',
description: 'description',
};
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 889616f

Please sign in to comment.