Browse Source

fixed format

master
nicolas-arnaud 2 years ago
parent
commit
4bb10ffb27
  1. 25
      .env_sample
  2. 16
      back/volume/.eslintrc.js
  3. 44
      back/volume/src/app.module.ts
  4. 22
      back/volume/src/auth/42-auth.guard.ts
  5. 10
      back/volume/src/auth/42.decorator.ts
  6. 54
      back/volume/src/auth/42.strategy.ts
  7. 58
      back/volume/src/auth/auth.controller.ts
  8. 52
      back/volume/src/auth/auth.module.ts
  9. 46
      back/volume/src/auth/auth.service.ts
  10. 10
      back/volume/src/auth/session.serializer.ts
  11. 168
      back/volume/src/chat/chat.controller.ts
  12. 92
      back/volume/src/chat/chat.gateway.ts
  13. 26
      back/volume/src/chat/chat.module.ts
  14. 150
      back/volume/src/chat/chat.service.ts
  15. 8
      back/volume/src/chat/dto/connection.dto.ts
  16. 18
      back/volume/src/chat/dto/create-channel.dto.ts
  17. 8
      back/volume/src/chat/dto/create-message.dto.ts
  18. 20
      back/volume/src/chat/dto/update-channel.dto.ts
  19. 8
      back/volume/src/chat/dto/updateUser.dto.ts
  20. 38
      back/volume/src/chat/entity/channel.entity.ts
  21. 14
      back/volume/src/chat/entity/connection.entity.ts
  22. 12
      back/volume/src/chat/entity/dm.entity.ts
  23. 18
      back/volume/src/chat/entity/message.entity.ts
  24. 40
      back/volume/src/chat/message.service.ts
  25. 54
      back/volume/src/main.ts
  26. 20
      back/volume/src/pong/dtos/GameCreationDtoValidated.ts
  27. 18
      back/volume/src/pong/dtos/GameInfo.ts
  28. 10
      back/volume/src/pong/dtos/GameUpdate.ts
  29. 14
      back/volume/src/pong/dtos/MapDtoValidated.ts
  30. 2
      back/volume/src/pong/dtos/MatchmakingDto.ts
  31. 6
      back/volume/src/pong/dtos/MatchmakingDtoValidated.ts
  32. 8
      back/volume/src/pong/dtos/PointDtoValidated.ts
  33. 12
      back/volume/src/pong/dtos/RectDtoValidated.ts
  34. 2
      back/volume/src/pong/dtos/StringDto.ts
  35. 6
      back/volume/src/pong/dtos/StringDtoValidated.ts
  36. 8
      back/volume/src/pong/dtos/UserDto.ts
  37. 18
      back/volume/src/pong/entity/result.entity.ts
  38. 96
      back/volume/src/pong/game/Ball.ts
  39. 140
      back/volume/src/pong/game/Game.ts
  40. 78
      back/volume/src/pong/game/Games.ts
  41. 46
      back/volume/src/pong/game/MatchmakingQueue.ts
  42. 24
      back/volume/src/pong/game/Paddle.ts
  43. 42
      back/volume/src/pong/game/Player.ts
  44. 38
      back/volume/src/pong/game/constants.ts
  45. 54
      back/volume/src/pong/game/utils.ts
  46. 24
      back/volume/src/pong/pong.controller.ts
  47. 24
      back/volume/src/pong/pong.gateway.spec.ts
  48. 136
      back/volume/src/pong/pong.gateway.ts
  49. 16
      back/volume/src/pong/pong.module.ts
  50. 84
      back/volume/src/pong/pong.service.ts
  51. 24
      back/volume/src/pong/pong.spec.ts
  52. 10
      back/volume/src/types.d.ts
  53. 22
      back/volume/src/users/dto/user.dto.ts
  54. 62
      back/volume/src/users/entity/user.entity.ts
  55. 172
      back/volume/src/users/users.controller.ts
  56. 16
      back/volume/src/users/users.module.ts
  57. 176
      back/volume/src/users/users.service.ts
  58. 94
      front/volume/src/components/Chat.svelte

25
.env_sample

@ -0,0 +1,25 @@
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=postgres_usr
POSTGRES_PASSWORD=postgres_pw
POSTGRES_DB=transcendence
PGADMIN_DEFAULT_EMAIL=admin@pg.com
PGADMIN_DEFAULT_PASSWORD=admin
MAIL_USER=vaganiwast@gmail.com
MAIL_PASSWORD=
FRONT_FPS=144
HOST=localhost
FRONT_PORT=80
BACK_PORT=3001
HASH_SALT=10
JWT_SECRET=test
JWT_EXPIRATION_TIME=900
FT_OAUTH_CLIENT_ID=
FT_OAUTH_CLIENT_SECRET=
FT_OAUTH_CALLBACK_URL=http://$HOST:$BACK_PORT/log/inReturn

16
back/volume/.eslintrc.js

@ -1,13 +1,13 @@
module.exports = {
env: {
browser: true,
es2021: true,
es2021: true
},
extends: "standard-with-typescript",
extends: 'standard-with-typescript',
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: ["./tsconfig.json"],
tsconfigRootDir: __dirname,
},
};
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname
}
}

44
back/volume/src/app.module.ts

@ -1,13 +1,13 @@
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import * as Joi from "joi";
import { ScheduleModule } from "@nestjs/schedule";
import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { TypeOrmModule } from '@nestjs/typeorm'
import * as Joi from 'joi'
import { ScheduleModule } from '@nestjs/schedule'
import { AuthModule } from "./auth/auth.module";
import { ChatModule } from "./chat/chat.module";
import { PongModule } from "./pong/pong.module";
import { UsersModule } from "./users/users.module";
import { AuthModule } from './auth/auth.module'
import { ChatModule } from './chat/chat.module'
import { PongModule } from './pong/pong.module'
import { UsersModule } from './users/users.module'
@Module({
imports: [
@ -26,28 +26,28 @@ import { UsersModule } from "./users/users.module";
HOST: Joi.string().required(),
FRONT_PORT: Joi.number().required(),
BACK_PORT: Joi.number().required(),
HASH_SALT: Joi.number().required(),
}),
HASH_SALT: Joi.number().required()
})
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: "postgres",
host: configService.get<string>("POSTGRES_HOST"),
port: configService.get<number>("POSTGRES_PORT"),
username: configService.get<string>("POSTGRES_USER"),
password: configService.get<string>("POSTGRES_PASSWORD"),
database: configService.get<string>("POSTGRES_DB"),
jwt_secret: configService.get<string>("JWT_SECRET"),
type: 'postgres',
host: configService.get<string>('POSTGRES_HOST'),
port: configService.get<number>('POSTGRES_PORT'),
username: configService.get<string>('POSTGRES_USER'),
password: configService.get<string>('POSTGRES_PASSWORD'),
database: configService.get<string>('POSTGRES_DB'),
jwt_secret: configService.get<string>('JWT_SECRET'),
autoLoadEntities: true,
synchronize: true,
}),
synchronize: true
})
}),
AuthModule,
ChatModule,
PongModule,
UsersModule,
],
UsersModule
]
})
export class AppModule {}

22
back/volume/src/auth/42-auth.guard.ts

@ -1,25 +1,25 @@
import {
type ExecutionContext,
Injectable,
type CanActivate,
} from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { type Request } from "express";
type CanActivate
} from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { type Request } from 'express'
@Injectable()
export class FtOauthGuard extends AuthGuard("42") {
export class FtOauthGuard extends AuthGuard('42') {
async canActivate (context: ExecutionContext): Promise<boolean> {
const activate: boolean = (await super.canActivate(context)) as boolean;
const request: Request = context.switchToHttp().getRequest();
await super.logIn(request);
return activate;
const activate: boolean = (await super.canActivate(context)) as boolean
const request: Request = context.switchToHttp().getRequest()
await super.logIn(request)
return activate
}
}
@Injectable()
export class AuthenticatedGuard implements CanActivate {
async canActivate (context: ExecutionContext): Promise<boolean> {
const req: Request = context.switchToHttp().getRequest();
return req.isAuthenticated();
const req: Request = context.switchToHttp().getRequest()
return req.isAuthenticated()
}
}

10
back/volume/src/auth/42.decorator.ts

@ -1,9 +1,9 @@
import { createParamDecorator, type ExecutionContext } from "@nestjs/common";
import { type Profile } from "passport-42";
import { createParamDecorator, type ExecutionContext } from '@nestjs/common'
import { type Profile } from 'passport-42'
export const Profile42 = createParamDecorator(
(data: unknown, ctx: ExecutionContext): Profile => {
const request = ctx.switchToHttp().getRequest();
return request.user;
const request = ctx.switchToHttp().getRequest()
return request.user
}
);
)

54
back/volume/src/auth/42.strategy.ts

@ -1,25 +1,25 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy, type Profile, type VerifyCallback } from "passport-42";
import { get } from "https";
import { createWriteStream } from "fs";
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy, type Profile, type VerifyCallback } from 'passport-42'
import { get } from 'https'
import { createWriteStream } from 'fs'
import { UsersService } from "src/users/users.service";
import { User } from "src/users/entity/user.entity";
import { UsersService } from 'src/users/users.service'
import { User } from 'src/users/entity/user.entity'
@Injectable()
export class FtStrategy extends PassportStrategy(Strategy, "42") {
export class FtStrategy extends PassportStrategy(Strategy, '42') {
constructor (
private readonly configService: ConfigService,
private readonly usersService: UsersService
) {
super({
clientID: configService.get<string>("FT_OAUTH_CLIENT_ID"),
clientSecret: configService.get<string>("FT_OAUTH_CLIENT_SECRET"),
callbackURL: configService.get<string>("FT_OAUTH_CALLBACK_URL"),
passReqToCallback: true,
});
clientID: configService.get<string>('FT_OAUTH_CLIENT_ID'),
clientSecret: configService.get<string>('FT_OAUTH_CLIENT_SECRET'),
callbackURL: configService.get<string>('FT_OAUTH_CALLBACK_URL'),
passReqToCallback: true
})
}
async validate (
@ -29,21 +29,21 @@ export class FtStrategy extends PassportStrategy(Strategy, "42") {
profile: Profile,
cb: VerifyCallback
): Promise<VerifyCallback> {
request.session.accessToken = accessToken;
const ftId = profile.id as number;
console.log("Validated ", profile.username);
request.session.accessToken = accessToken
const ftId = profile.id as number
console.log('Validated ', profile.username)
if ((await this.usersService.findUser(ftId)) === null) {
const newUser = new User();
newUser.ftId = profile.id as number;
newUser.username = profile.username;
newUser.avatar = `${ftId}.jpg`;
newUser.email = profile.emails[0].value;
void this.usersService.create(newUser);
const file = createWriteStream(`avatars/${ftId}.jpg`);
const newUser = new User()
newUser.ftId = profile.id as number
newUser.username = profile.username
newUser.avatar = `${ftId}.jpg`
newUser.email = profile.emails[0].value
void this.usersService.create(newUser)
const file = createWriteStream(`avatars/${ftId}.jpg`)
get(profile._json.image.versions.small, function (response) {
response.pipe(file);
});
response.pipe(file)
})
}
return cb(null, profile);
return cb(null, profile)
}
}

58
back/volume/src/auth/auth.controller.ts

@ -6,75 +6,75 @@ import {
Res,
Req,
Post,
Body,
} from "@nestjs/common";
import { Response, Request } from "express";
Body
} from '@nestjs/common'
import { Response, Request } from 'express'
import { FtOauthGuard, AuthenticatedGuard } from "./42-auth.guard";
import { Profile } from "passport-42";
import { Profile42 } from "./42.decorator";
import { FtOauthGuard, AuthenticatedGuard } from './42-auth.guard'
import { Profile } from 'passport-42'
import { Profile42 } from './42.decorator'
import { AuthService } from "./auth.service";
import { UsersService } from "src/users/users.service";
import { AuthService } from './auth.service'
import { UsersService } from 'src/users/users.service'
const frontHost =
process.env.HOST !== undefined && process.env.HOST !== ""
process.env.HOST !== undefined && process.env.HOST !== ''
? process.env.HOST
: "localhost";
: 'localhost'
const frontPort =
process.env.PORT !== undefined && process.env.HOST !== ""
process.env.PORT !== undefined && process.env.HOST !== ''
? process.env.PORT
: "80";
: '80'
@Controller("log")
@Controller('log')
export class AuthController {
constructor (
private readonly authService: AuthService,
private readonly usersService: UsersService
) {}
@Get("in")
@Get('in')
@UseGuards(FtOauthGuard)
ftAuth (): void {}
@Get("inReturn")
@Get('inReturn')
@UseGuards(FtOauthGuard)
@Redirect(`http://${frontHost}:${frontPort}`)
ftAuthCallback (
@Res({ passthrough: true }) response: Response,
@Req() request: Request
): any {
console.log("cookie:", request.cookies["connect.sid"]);
response.cookie("connect.sid", request.cookies["connect.sid"]);
console.log('cookie:', request.cookies['connect.sid'])
response.cookie('connect.sid', request.cookies['connect.sid'])
}
@Get("/verify")
@Get('/verify')
@UseGuards(AuthenticatedGuard)
@Redirect(`http://${frontHost}:${frontPort}`)
async VerifyEmail (@Profile42() profile: Profile): Promise<void> {
const ftId: number = profile.id;
const user = await this.usersService.findUser(ftId);
if (user == null) throw new Error("User not found");
await this.authService.sendConfirmationEmail(user);
const ftId: number = profile.id
const user = await this.usersService.findUser(ftId)
if (user == null) throw new Error('User not found')
await this.authService.sendConfirmationEmail(user)
}
@Post("/verify")
@Post('/verify')
@Redirect(`http://${frontHost}:${frontPort}`)
async Verify (@Body() body: any): Promise<void> {
await this.authService.verifyAccount(body.code);
await this.authService.verifyAccount(body.code)
}
@Get("profile")
@Get('profile')
@UseGuards(AuthenticatedGuard)
profile (@Profile42() user: Profile): any {
return { user };
return { user }
}
@Get("out")
@Get('out')
@Redirect(`http://${frontHost}:${frontPort}`)
logOut (@Req() req: Request): any {
req.logOut(function (err) {
if (err != null) return err;
});
if (err != null) return err
})
}
}

52
back/volume/src/auth/auth.module.ts

@ -1,23 +1,23 @@
import { Module } from "@nestjs/common";
import { UsersModule } from "src/users/users.module";
import { PassportModule } from "@nestjs/passport";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { AuthController } from "./auth.controller";
import { FtStrategy } from "./42.strategy";
import { SessionSerializer } from "./session.serializer";
import { JwtModule } from "@nestjs/jwt";
import { MailerModule } from "@nestjs-modules/mailer";
import { AuthService } from "./auth.service";
import { HandlebarsAdapter } from "@nestjs-modules/mailer/dist/adapters/handlebars.adapter";
import { Module } from '@nestjs/common'
import { UsersModule } from 'src/users/users.module'
import { PassportModule } from '@nestjs/passport'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { AuthController } from './auth.controller'
import { FtStrategy } from './42.strategy'
import { SessionSerializer } from './session.serializer'
import { JwtModule } from '@nestjs/jwt'
import { MailerModule } from '@nestjs-modules/mailer'
import { AuthService } from './auth.service'
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'
const mailUser =
process.env.MAIL_USER !== null && process.env.MAIL_USER !== ""
process.env.MAIL_USER !== null && process.env.MAIL_USER !== ''
? process.env.MAIL_USER
: "";
: ''
const mailPass =
process.env.MAIL_PASSWORD !== null && process.env.MAIL_PASSWORD !== ""
process.env.MAIL_PASSWORD !== null && process.env.MAIL_PASSWORD !== ''
? process.env.MAIL_PASSWORD
: "";
: ''
@Module({
imports: [
@ -26,29 +26,29 @@ const mailPass =
ConfigModule.forRoot(),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "60s" },
signOptions: { expiresIn: '60s' }
}),
MailerModule.forRoot({
transport: {
service: "gmail",
service: 'gmail',
auth: {
user: mailUser,
pass: mailPass,
},
pass: mailPass
}
},
template: {
dir: "src/auth/mails",
dir: 'src/auth/mails',
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
strict: true
}
},
defaults: {
from: '"No Reply" vaganiwast@gmail.com',
},
}),
from: '"No Reply" vaganiwast@gmail.com'
}
})
],
providers: [ConfigService, FtStrategy, SessionSerializer, AuthService],
controllers: [AuthController],
controllers: [AuthController]
})
export class AuthModule {}

46
back/volume/src/auth/auth.service.ts

@ -1,7 +1,7 @@
import { Injectable } from "@nestjs/common";
import { type User } from "src/users/entity/user.entity";
import { UsersService } from "src/users/users.service";
import { MailerService } from "@nestjs-modules/mailer";
import { Injectable } from '@nestjs/common'
import { type User } from 'src/users/entity/user.entity'
import { UsersService } from 'src/users/users.service'
import { MailerService } from '@nestjs-modules/mailer'
@Injectable()
export class AuthService {
@ -11,38 +11,38 @@ export class AuthService {
) {}
async sendConfirmedEmail (user: User): Promise<void> {
const { email, username } = user;
const { email, username } = user
await this.mailerService.sendMail({
to: email,
subject: "Welcome to ft_transcendence! Email Confirmed",
template: "confirmed",
subject: 'Welcome to ft_transcendence! Email Confirmed',
template: 'confirmed',
context: {
username,
email,
},
});
email
}
})
}
async sendConfirmationEmail (user: User): Promise<void> {
user.authToken = Math.floor(10000 + Math.random() * 90000).toString();
await this.usersService.save(user);
user.authToken = Math.floor(10000 + Math.random() * 90000).toString()
await this.usersService.save(user)
await this.mailerService.sendMail({
to: user.email,
subject: "Welcome to ft_transcendence! Confirm Email",
template: "confirm",
subject: 'Welcome to ft_transcendence! Confirm Email',
template: 'confirm',
context: {
username: user.username,
code: user.authToken,
},
});
code: user.authToken
}
})
}
async verifyAccount (code: string): Promise<boolean> {
const user = await this.usersService.findByCode(code);
user.authToken = "";
user.isVerified = true;
await this.usersService.save(user);
await this.sendConfirmedEmail(user);
return true;
const user = await this.usersService.findByCode(code)
user.authToken = ''
user.isVerified = true
await this.usersService.save(user)
await this.sendConfirmedEmail(user)
return true
}
}

10
back/volume/src/auth/session.serializer.ts

@ -1,6 +1,6 @@
import { Injectable } from "@nestjs/common";
import { PassportSerializer } from "@nestjs/passport";
import { type Profile } from "passport-42";
import { Injectable } from '@nestjs/common'
import { PassportSerializer } from '@nestjs/passport'
import { type Profile } from 'passport-42'
@Injectable()
export class SessionSerializer extends PassportSerializer {
@ -8,13 +8,13 @@ export class SessionSerializer extends PassportSerializer {
user: Profile,
done: (err: Error | null, user: Profile) => void
): any {
done(null, user);
done(null, user)
}
deserializeUser (
payload: Profile,
done: (err: Error | null, user: Profile) => void
): any {
done(null, payload);
done(null, payload)
}
}

168
back/volume/src/chat/chat.controller.ts

@ -7,211 +7,211 @@ import {
NotFoundException,
Param,
Post,
UseGuards,
} from "@nestjs/common";
import { AuthenticatedGuard } from "src/auth/42-auth.guard";
import { UsersService } from "src/users/users.service";
import { ChatService } from "./chat.service";
UseGuards
} from '@nestjs/common'
import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import { UsersService } from 'src/users/users.service'
import { ChatService } from './chat.service'
import { CreateChannelDto } from "./dto/create-channel.dto";
import { IdDto, PasswordDto, MuteDto } from "./dto/updateUser.dto";
import { CreateChannelDto } from './dto/create-channel.dto'
import { IdDto, PasswordDto, MuteDto } from './dto/updateUser.dto'
import type User from "src/users/entity/user.entity";
import type Channel from "./entity/channel.entity";
import { Profile42 } from "src/auth/42.decorator";
import { Profile } from "passport-42";
import type User from 'src/users/entity/user.entity'
import type Channel from './entity/channel.entity'
import { Profile42 } from 'src/auth/42.decorator'
import { Profile } from 'passport-42'
@Controller("channels")
@Controller('channels')
export class ChatController {
constructor (
private readonly channelService: ChatService,
private readonly usersService: UsersService
) {}
@Post(":id/invite")
@Post(':id/invite')
@UseGuards(AuthenticatedGuard)
async addUser (
@Param("id") id: number,
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile
) {
const channel = await this.channelService.getFullChannel(id);
const user: User | null = await this.usersService.findUser(target.id);
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id)
if (user == null) {
throw new NotFoundException(`User #${target.id} not found`);
throw new NotFoundException(`User #${target.id} not found`)
}
if (!(await this.channelService.isUser(channel.id, +profile.id))) {
throw new BadRequestException(
"You are not allowed to invite users to this channel"
);
'You are not allowed to invite users to this channel'
)
}
if (await this.channelService.isUser(channel.id, target.id)) {
throw new BadRequestException("User is already in this channel");
throw new BadRequestException('User is already in this channel')
}
if (await this.channelService.isBanned(channel.id, target.id)) {
throw new BadRequestException("User is banned from this channel");
throw new BadRequestException('User is banned from this channel')
}
channel.users.push(user);
this.channelService.save(channel);
channel.users.push(user)
this.channelService.save(channel)
}
@Delete(":id/kick")
@Delete(':id/kick')
@UseGuards(AuthenticatedGuard)
async removeUser (
@Param("id") id: number,
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile
) {
const channel = await this.channelService.getFullChannel(id);
const channel = await this.channelService.getFullChannel(id)
if (!(await this.channelService.isAdmin(channel.id, +profile.id))) {
throw new BadRequestException(
"You are not allowed to kick users from this channel"
);
'You are not allowed to kick users from this channel'
)
}
if (!(await this.channelService.isUser(channel.id, target.id))) {
throw new BadRequestException("User is not in this channel");
throw new BadRequestException('User is not in this channel')
}
if (await this.channelService.isOwner(channel.id, target.id)) {
throw new BadRequestException("You cannot kick the owner of the channel");
throw new BadRequestException('You cannot kick the owner of the channel')
}
channel.users = channel.users.filter((usr: User) => {
return usr.ftId !== target.id;
});
this.channelService.save(channel);
return usr.ftId !== target.id
})
this.channelService.save(channel)
}
@Post(":id/admin")
@Post(':id/admin')
@UseGuards(AuthenticatedGuard)
async addAdmin (
@Param("id") id: number,
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile
) {
const channel = await this.channelService.getFullChannel(id);
const user: User | null = await this.usersService.findUser(target.id);
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id)
if (user == null) {
throw new NotFoundException(`User #${target.id} not found`);
throw new NotFoundException(`User #${target.id} not found`)
}
if (!(await this.channelService.isOwner(channel.id, +profile.id))) {
throw new BadRequestException("You are not the owner of this channel");
throw new BadRequestException('You are not the owner of this channel')
}
if (!(await this.channelService.isUser(channel.id, target.id))) {
throw new BadRequestException("User is not in this channel");
throw new BadRequestException('User is not in this channel')
}
if (await this.channelService.isAdmin(channel.id, target.id)) {
throw new BadRequestException("User is already an admin of this channel");
throw new BadRequestException('User is already an admin of this channel')
}
channel.admins.push(user);
this.channelService.save(channel);
channel.admins.push(user)
this.channelService.save(channel)
}
@Delete(":id/admin")
@Delete(':id/admin')
@UseGuards(AuthenticatedGuard)
async removeAdmin (
@Param("id") id: number,
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile
) {
const channel = await this.channelService.getFullChannel(id);
const channel = await this.channelService.getFullChannel(id)
if (!(await this.channelService.isOwner(channel.id, +profile.id))) {
throw new BadRequestException("You are not the owner of this channel");
throw new BadRequestException('You are not the owner of this channel')
}
if (!(await this.channelService.isAdmin(channel.id, target.id))) {
throw new BadRequestException("User is not an admin of this channel");
throw new BadRequestException('User is not an admin of this channel')
}
channel.admins = channel.admins.filter((usr: User) => {
return usr.ftId !== target.id;
});
this.channelService.save(channel);
return usr.ftId !== target.id
})
this.channelService.save(channel)
}
@Post(":id/ban")
@Post(':id/ban')
@UseGuards(AuthenticatedGuard)
async addBan (
@Param("id") id: number,
@Param('id') id: number,
@Body() target: IdDto,
@Profile42() profile: Profile
) {
const channel = await this.channelService.getFullChannel(id);
const user: User | null = await this.usersService.findUser(target.id);
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(target.id)
if (user == null) {
throw new NotFoundException(`User #${target.id} not found`);
throw new NotFoundException(`User #${target.id} not found`)
}
if (!(await this.channelService.isAdmin(channel.id, +profile.id))) {
throw new BadRequestException(
"You are not allowed to ban users from this channel"
);
'You are not allowed to ban users from this channel'
)
}
if (await this.channelService.isOwner(channel.id, target.id)) {
throw new BadRequestException("You cannot ban the owner of the channel");
throw new BadRequestException('You cannot ban the owner of the channel')
}
if (await this.channelService.isBanned(channel.id, target.id)) {
throw new BadRequestException("User is already banned from this channel");
throw new BadRequestException('User is already banned from this channel')
}
channel.banned.push(user);
this.channelService.save(channel);
channel.banned.push(user)
this.channelService.save(channel)
}
@Post(":id/mute")
@Post(':id/mute')
@UseGuards(AuthenticatedGuard)
async addMute (
@Param("id") id: number,
@Param('id') id: number,
@Body() mute: MuteDto, // [userId, duration]
@Profile42() profile: Profile
) {
const channel = await this.channelService.getFullChannel(id);
const user: User | null = await this.usersService.findUser(mute.data[0]);
const channel = await this.channelService.getFullChannel(id)
const user: User | null = await this.usersService.findUser(mute.data[0])
if (user == null) {
throw new NotFoundException(`User #${mute.data[0]} not found`);
throw new NotFoundException(`User #${mute.data[0]} not found`)
}
if (!(await this.channelService.isAdmin(channel.id, +profile.id))) {
throw new BadRequestException(
"You are not allowed to mute users from this channel"
);
'You are not allowed to mute users from this channel'
)
}
if (await this.channelService.isOwner(channel.id, mute.data[0])) {
throw new BadRequestException("You cannot mute the owner of the channel");
throw new BadRequestException('You cannot mute the owner of the channel')
}
if (
(await this.channelService.getMuteDuration(channel.id, mute.data[0])) > 0
) {
throw new BadRequestException("User is already muted from this channel");
throw new BadRequestException('User is already muted from this channel')
}
const newMute: number[] = [mute.data[0], Date.now() + mute.data[1] * 1000];
channel.muted.push(newMute);
this.channelService.save(channel);
const newMute: number[] = [mute.data[0], Date.now() + mute.data[1] * 1000]
channel.muted.push(newMute)
this.channelService.save(channel)
}
@Delete(":id")
@Delete(':id')
@UseGuards(AuthenticatedGuard)
async deleteChannel(@Profile42() profile: Profile, @Param("id") id: number) {
async deleteChannel (@Profile42() profile: Profile, @Param('id') id: number) {
if (!(await this.channelService.isOwner(id, +profile.id))) {
throw new BadRequestException("You are not the owner of this channel");
throw new BadRequestException('You are not the owner of this channel')
}
await this.channelService.removeChannel(id);
await this.channelService.removeChannel(id)
}
@Post(":id/password")
@Post(':id/password')
@UseGuards(AuthenticatedGuard)
async updatePassword (
@Profile42() profile: Profile,
@Param("id") id: number,
@Param('id') id: number,
@Body() data: PasswordDto
) {
if (await this.channelService.isOwner(id, +profile.id)) {
throw new BadRequestException("You are not the owner of this channel");
throw new BadRequestException('You are not the owner of this channel')
}
await this.channelService.updatePassword(id, data.password);
await this.channelService.updatePassword(id, data.password)
}
@Get()
@UseGuards(AuthenticatedGuard)
async getChannelsForUser (@Profile42() profile: Profile): Promise<Channel[]> {
return await this.channelService.getChannelsForUser(+profile.id);
return await this.channelService.getChannelsForUser(+profile.id)
}
@Post()
async createChannel (@Body() channel: CreateChannelDto) {
return await this.channelService.createChannel(channel);
return await this.channelService.createChannel(channel)
}
}

92
back/volume/src/chat/chat.gateway.ts

@ -4,29 +4,29 @@ import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsException,
} from "@nestjs/websockets";
import { Socket, Server } from "socket.io";
WsException
} from '@nestjs/websockets'
import { Socket, Server } from 'socket.io'
// import { User } from 'users/user.entity';
import { UsersService } from "src/users/users.service";
import { BadRequestException } from "@nestjs/common";
import { ChatService } from "./chat.service";
import type Message from "./entity/message.entity";
import * as bcrypt from "bcrypt";
import { MessageService } from "./message.service";
import { type User } from "src/users/entity/user.entity";
import { CreateMessageDto } from "./dto/create-message.dto";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import ConnectedUser from "./entity/connection.entity";
import { ConnectionDto } from "./dto/connection.dto";
import { UsersService } from 'src/users/users.service'
import { BadRequestException } from '@nestjs/common'
import { ChatService } from './chat.service'
import type Message from './entity/message.entity'
import * as bcrypt from 'bcrypt'
import { MessageService } from './message.service'
import { type User } from 'src/users/entity/user.entity'
import { CreateMessageDto } from './dto/create-message.dto'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import ConnectedUser from './entity/connection.entity'
import { ConnectionDto } from './dto/connection.dto'
@WebSocketGateway({
cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ },
cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ }
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
server: Server
constructor (
private readonly userService: UsersService,
@ -50,75 +50,75 @@ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
}
handleDisconnect (socket: Socket): void {
socket.disconnect();
socket.disconnect()
}
@SubscribeMessage("joinChannel")
@SubscribeMessage('joinChannel')
async onJoinChannel (socket: Socket, connect: ConnectionDto): Promise<void> {
const channel = await this.chatService.getChannel(connect.ChannelId);
const channel = await this.chatService.getChannel(connect.ChannelId)
if (channel.banned.find((e) => e.id == connect.UserId) != null) {
throw new WsException("You are banned from entering this channel");
throw new WsException('You are banned from entering this channel')
}
const user = (await this.userService.findUser(connect.UserId)) as User;
const user = (await this.userService.findUser(connect.UserId)) as User
if (
channel.users.find((e) => e.id === user.id) == null &&
channel.password !== ""
channel.password !== ''
) {
if (!(await bcrypt.compare(channel.password, connect.pwd))) {
throw new BadRequestException();
throw new BadRequestException()
}
} else await this.chatService.addUserToChannel(channel, user);
} else await this.chatService.addUserToChannel(channel, user)
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
{
const conUser = new ConnectedUser();
conUser.user = user;
conUser.channel = channel;
conUser.socket = socket.id;
await this.connectedUserRepository.save(conUser);
const conUser = new ConnectedUser()
conUser.user = user
conUser.channel = channel
conUser.socket = socket.id
await this.connectedUserRepository.save(conUser)
}
const messages = await this.messageService.findMessagesInChannelForUser(
channel,
user
);
this.server.to(socket.id).emit("messages", messages);
await socket.join(channel.name);
)
this.server.to(socket.id).emit('messages', messages)
await socket.join(channel.name)
}
@SubscribeMessage("leaveChannel")
@SubscribeMessage('leaveChannel')
async onLeaveChannel (socket: Socket): Promise<void> {
const id = socket.id as any;
await this.connectedUserRepository.delete({ socket: id });
socket.disconnect();
const id = socket.id as any
await this.connectedUserRepository.delete({ socket: id })
socket.disconnect()
}
@SubscribeMessage("addMessage")
@SubscribeMessage('addMessage')
async onAddMessage (socket: Socket, message: CreateMessageDto): Promise<void> {
const channel = await this.chatService.getChannel(message.ChannelId);
const channel = await this.chatService.getChannel(message.ChannelId)
if (
(await this.chatService.getMuteDuration(channel.id, message.UserId)) > 0
) {
throw new WsException("You are muted");
throw new WsException('You are muted')
}
const createdMessage: Message = await this.messageService.createMessage(
message
);
socket.in(channel.name).emit("newMessage", createdMessage);
)
socket.in(channel.name).emit('newMessage', createdMessage)
}
@SubscribeMessage("kickUser")
@SubscribeMessage('kickUser')
async onKickUser (
socket: Socket,
chan: number,
from: number,
to: number
): Promise<void> {
const channel = await this.chatService.getChannel(chan);
const channel = await this.chatService.getChannel(chan)
if (
channel.owner.id !== from ||
channel.admins.find((e) => e.id === from) == null
) {
throw new WsException("You do not have the required privileges");
throw new WsException('You do not have the required privileges')
}
await this.onLeaveChannel(socket);
await this.onLeaveChannel(socket)
}
}

26
back/volume/src/chat/chat.module.ts

@ -1,25 +1,25 @@
import { forwardRef, Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { forwardRef, Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { AuthModule } from "src/auth/auth.module";
import { UsersModule } from "src/users/users.module";
import { ChatGateway } from "./chat.gateway";
import { ChatController } from "./chat.controller";
import { ChatService } from "./chat.service";
import { MessageService } from "./message.service";
import { AuthModule } from 'src/auth/auth.module'
import { UsersModule } from 'src/users/users.module'
import { ChatGateway } from './chat.gateway'
import { ChatController } from './chat.controller'
import { ChatService } from './chat.service'
import { MessageService } from './message.service'
import Channel from "./entity/channel.entity";
import Message from "./entity/message.entity";
import ConnectedUser from "./entity/connection.entity";
import Channel from './entity/channel.entity'
import Message from './entity/message.entity'
import ConnectedUser from './entity/connection.entity'
@Module({
imports: [
UsersModule,
AuthModule,
TypeOrmModule.forFeature([Channel, Message, ConnectedUser]),
TypeOrmModule.forFeature([Channel, Message, ConnectedUser])
],
controllers: [ChatController],
providers: [ChatService, ChatGateway, MessageService],
exports: [ChatService],
exports: [ChatService]
})
export class ChatModule {}

150
back/volume/src/chat/chat.service.ts

@ -1,13 +1,13 @@
import { Inject, Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { Inject, Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { type CreateChannelDto } from "./dto/create-channel.dto";
import { UsersService } from "src/users/users.service";
import { type CreateChannelDto } from './dto/create-channel.dto'
import { UsersService } from 'src/users/users.service'
import type User from "src/users/entity/user.entity";
import Channel from "./entity/channel.entity";
import { Cron } from "@nestjs/schedule";
import type User from 'src/users/entity/user.entity'
import Channel from './entity/channel.entity'
import { Cron } from '@nestjs/schedule'
@Injectable()
export class ChatService {
@ -18,156 +18,156 @@ export class ChatService {
) {}
async createChannel (channel: CreateChannelDto): Promise<Channel> {
const user: User | null = await this.usersService.findUser(channel.owner);
const user: User | null = await this.usersService.findUser(channel.owner)
if (user == null) {
throw new NotFoundException(`User #${channel.owner} not found`);
throw new NotFoundException(`User #${channel.owner} not found`)
}
const newChannel = new Channel();
newChannel.owner = user;
newChannel.users = [user];
newChannel.admins = [user];
newChannel.name = channel.name;
newChannel.isPrivate = channel.isPrivate;
newChannel.password = channel.password;
return await this.ChannelRepository.save(newChannel);
const newChannel = new Channel()
newChannel.owner = user
newChannel.users = [user]
newChannel.admins = [user]
newChannel.name = channel.name
newChannel.isPrivate = channel.isPrivate
newChannel.password = channel.password
return await this.ChannelRepository.save(newChannel)
}
async updatePassword (id: number, password: string) {
const channel: Channel | null = await this.ChannelRepository.findOneBy({
id,
});
id
})
if (channel === null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
channel.password = password;
await this.ChannelRepository.save(channel);
channel.password = password
await this.ChannelRepository.save(channel)
}
async getChannelsForUser (ftId: number): Promise<Channel[]> {
let rooms: Channel[] = [];
let rooms: Channel[] = []
rooms = [
...(await this.ChannelRepository.createQueryBuilder("room")
.where("room.isPrivate = false")
.getMany()),
];
...(await this.ChannelRepository.createQueryBuilder('room')
.where('room.isPrivate = false')
.getMany())
]
rooms = [
...rooms,
...(await this.ChannelRepository.createQueryBuilder("room")
.innerJoin("room.users", "users")
.where("room.isPrivate = true")
.andWhere("users.ftId = :ftId", { ftId })
.getMany()),
];
return rooms;
...(await this.ChannelRepository.createQueryBuilder('room')
.innerJoin('room.users', 'users')
.where('room.isPrivate = true')
.andWhere('users.ftId = :ftId', { ftId })
.getMany())
]
return rooms
}
@Cron("*/6 * * * * *")
@Cron('*/6 * * * * *')
async updateMutelists (): Promise<void> {
const channels = await this.ChannelRepository.find({});
const channels = await this.ChannelRepository.find({})
channels.forEach((channel) => {
channel.muted = channel.muted.filter((data) => {
return data[0] - Date.now() > 0;
});
this.ChannelRepository.save(channel);
});
return data[0] - Date.now() > 0
})
this.ChannelRepository.save(channel)
})
}
async addUserToChannel (channel: Channel, user: User): Promise<Channel> {
channel.owner = user;
return await this.ChannelRepository.save(channel);
channel.owner = user
return await this.ChannelRepository.save(channel)
}
async getChannel (id: number): Promise<Channel> {
const channel = await this.ChannelRepository.findOneBy({ id });
const channel = await this.ChannelRepository.findOneBy({ id })
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
return channel;
return channel
}
async getFullChannel (id: number): Promise<Channel> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: ["users", "admins", "banned", "muted", "owner"],
});
relations: ['users', 'admins', 'banned', 'muted', 'owner']
})
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
return channel;
return channel
}
async update (channel: Channel) {
await this.ChannelRepository.update(channel.id, channel);
await this.ChannelRepository.update(channel.id, channel)
}
async save (channel: Channel) {
await this.ChannelRepository.save(channel);
await this.ChannelRepository.save(channel)
}
async removeChannel (channelId: number) {
await this.ChannelRepository.delete(channelId);
await this.ChannelRepository.delete(channelId)
}
async isOwner (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { owner: true },
});
relations: { owner: true }
})
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
return channel.owner.ftId === userId;
return channel.owner.ftId === userId
}
async isAdmin (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { admins: true },
});
relations: { admins: true }
})
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
return channel.admins.findIndex((user) => user.ftId === userId) != -1;
return channel.admins.findIndex((user) => user.ftId === userId) != -1
}
async isUser (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { users: true },
});
relations: { users: true }
})
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
return channel.users.findIndex((user) => user.ftId === userId) != -1;
return channel.users.findIndex((user) => user.ftId === userId) != -1
}
async isBanned (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { banned: true },
});
relations: { banned: true }
})
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
return channel.banned.findIndex((user) => user.ftId === userId) != -1;
return channel.banned.findIndex((user) => user.ftId === userId) != -1
}
async getMuteDuration (id: number, userId: number): Promise<number> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { muted: true },
});
relations: { muted: true }
})
if (channel == null) {
throw new NotFoundException(`Channel #${id} not found`);
throw new NotFoundException(`Channel #${id} not found`)
}
const mutation: number[] | undefined = channel.muted.find(
(mutation) => mutation[0] === userId
);
)
if (mutation == null) {
return 0;
return 0
}
return mutation[1];
return mutation[1]
}
}

8
back/volume/src/chat/dto/connection.dto.ts

@ -1,13 +1,13 @@
import { IsNumber, IsOptional, IsString } from "class-validator";
import { IsNumber, IsOptional, IsString } from 'class-validator'
export class ConnectionDto {
@IsNumber()
UserId: number;
UserId: number
@IsNumber()
ChannelId: number;
ChannelId: number
@IsString()
@IsOptional()
pwd: string;
pwd: string
}

18
back/volume/src/chat/dto/create-channel.dto.ts

@ -1,28 +1,28 @@
import { Transform } from "class-transformer";
import { Transform } from 'class-transformer'
import {
IsPositive,
IsAlpha,
IsString,
IsOptional,
IsNumber,
IsBoolean,
} from "class-validator";
IsBoolean
} from 'class-validator'
export class CreateChannelDto {
@IsOptional()
@IsPositive()
id: number;
id: number
@IsString()
name: string;
name: string
@IsNumber()
owner: number;
owner: number
@IsOptional()
password: string;
password: string
@IsBoolean()
@Transform(({ value }) => value === "true")
isPrivate: boolean;
@Transform(({ value }) => value === 'true')
isPrivate: boolean
}

8
back/volume/src/chat/dto/create-message.dto.ts

@ -1,12 +1,12 @@
import { IsNumber, IsString } from "class-validator";
import { IsNumber, IsString } from 'class-validator'
export class CreateMessageDto {
@IsString()
text: string;
text: string
@IsNumber()
UserId: number;
UserId: number
@IsNumber()
ChannelId: number;
ChannelId: number
}

20
back/volume/src/chat/dto/update-channel.dto.ts

@ -1,30 +1,30 @@
import { PartialType } from "@nestjs/mapped-types";
import { CreateChannelDto } from "./create-channel.dto";
import { IsNumber, IsOptional, IsString } from "class-validator";
import { PartialType } from '@nestjs/mapped-types'
import { CreateChannelDto } from './create-channel.dto'
import { IsNumber, IsOptional, IsString } from 'class-validator'
export class UpdateChannelDto extends PartialType(CreateChannelDto) {
id: number;
id: number
@IsOptional()
@IsNumber()
users: [number];
users: [number]
@IsOptional()
@IsNumber()
messages: [number];
messages: [number]
@IsOptional()
@IsNumber()
owners: [number]; // user id
owners: [number] // user id
@IsOptional()
@IsNumber()
banned: [number]; // user id
banned: [number] // user id
@IsOptional()
@IsNumber()
muted: [number]; // user id
muted: [number] // user id
@IsString()
@IsOptional()
password: string;
password: string
}

8
back/volume/src/chat/dto/updateUser.dto.ts

@ -1,15 +1,15 @@
import { IsNumber, IsString } from "class-validator";
import { IsNumber, IsString } from 'class-validator'
export class IdDto {
@IsNumber()
id: number;
id: number
}
export class PasswordDto {
@IsString()
password: string;
password: string
}
export class MuteDto {
data: number[];
data: number[]
}

38
back/volume/src/chat/entity/channel.entity.ts

@ -8,54 +8,54 @@ import {
ManyToOne,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import User from "src/users/entity/user.entity";
import Message from "./message.entity";
import * as bcrypt from "bcrypt";
PrimaryGeneratedColumn
} from 'typeorm'
import User from 'src/users/entity/user.entity'
import Message from './message.entity'
import * as bcrypt from 'bcrypt'
@Entity()
export default class Channel {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column()
name: string;
name: string
@Column({ default: false })
isPrivate: boolean;
isPrivate: boolean
@Column({ select: false, default: "" })
password: string;
@Column({ select: false, default: '' })
password: string
@BeforeInsert()
async hashPassword () {
if (this.password === "") return;
if (this.password === '') return
this.password = await bcrypt.hash(
this.password,
Number(process.env.HASH_SALT)
);
)
}
@ManyToMany(() => User)
@JoinTable()
users: User[];
users: User[]
@OneToMany(() => Message, (message: Message) => message.channel)
messages: Message[];
messages: Message[]
@ManyToOne(() => User)
@JoinColumn()
owner: User;
owner: User
@ManyToMany(() => User)
@JoinTable()
admins: User[];
admins: User[]
@ManyToMany(() => User) // refuse connection
@JoinTable()
banned: User[];
banned: User[]
@Column("text", { array: true })
muted: number[][];
@Column('text', { array: true })
muted: number[][]
}

14
back/volume/src/chat/entity/connection.entity.ts

@ -3,21 +3,21 @@ import {
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
} from "typeorm";
PrimaryGeneratedColumn
} from 'typeorm'
import Channel from "./channel.entity";
import User from "src/users/entity/user.entity";
import Channel from './channel.entity'
import User from 'src/users/entity/user.entity'
@Entity()
export default class ConnectedUser {
@OneToOne(() => User)
user: User;
user: User
@OneToOne(() => Channel)
@JoinColumn()
channel: Channel;
channel: Channel
@PrimaryGeneratedColumn()
socket: string;
socket: string
}

12
back/volume/src/chat/entity/dm.entity.ts

@ -1,15 +1,15 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import Message from "./message.entity";
import type User from "src/users/entity/user.entity";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
import Message from './message.entity'
import type User from 'src/users/entity/user.entity'
@Entity()
export class Channel {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column()
users: User[];
users: User[]
@OneToMany(() => Message, (message) => message.channel)
messages: Message[];
messages: Message[]
}

18
back/volume/src/chat/entity/message.entity.ts

@ -5,27 +5,27 @@ import {
JoinColumn,
JoinTable,
ManyToOne,
PrimaryGeneratedColumn,
} from "typeorm";
import User from "src/users/entity/user.entity";
import Channel from "./channel.entity";
PrimaryGeneratedColumn
} from 'typeorm'
import User from 'src/users/entity/user.entity'
import Channel from './channel.entity'
@Entity()
export default class Message {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column()
text: string;
text: string
@ManyToOne(() => User)
@JoinColumn()
author: User;
author: User
@ManyToOne(() => Channel, (channel) => channel.messages, { cascade: true })
@JoinTable()
channel: Channel;
channel: Channel
@CreateDateColumn()
created_at: Date;
created_at: Date
}

40
back/volume/src/chat/message.service.ts

@ -1,13 +1,13 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { ChatService } from "./chat.service";
import { UsersService } from "src/users/users.service";
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { ChatService } from './chat.service'
import { UsersService } from 'src/users/users.service'
import { type CreateMessageDto } from "./dto/create-message.dto";
import type User from "src/users/entity/user.entity";
import type Channel from "./entity/channel.entity";
import Message from "./entity/message.entity";
import { type CreateMessageDto } from './dto/create-message.dto'
import type User from 'src/users/entity/user.entity'
import type Channel from './entity/channel.entity'
import Message from './entity/message.entity'
@Injectable()
export class MessageService {
@ -19,25 +19,25 @@ export class MessageService {
) {}
async createMessage (message: CreateMessageDto): Promise<Message> {
const msg = new Message();
msg.text = message.text;
msg.channel = await this.channelService.getChannel(message.ChannelId);
msg.author = (await this.usersService.findUser(message.UserId)) as User;
msg.channel.messages.push(msg);
const msg = new Message()
msg.text = message.text
msg.channel = await this.channelService.getChannel(message.ChannelId)
msg.author = (await this.usersService.findUser(message.UserId)) as User
msg.channel.messages.push(msg)
return await this.MessageRepository.save(
this.MessageRepository.create(msg)
);
)
}
async findMessagesInChannelForUser (
channel: Channel,
user: User
): Promise<Message[]> {
return await this.MessageRepository.createQueryBuilder("message")
.where("message.channel = :chan", { chan: channel })
.andWhere("message.author NOT IN (:...blocked)", {
blocked: user.blocked,
return await this.MessageRepository.createQueryBuilder('message')
.where('message.channel = :chan', { chan: channel })
.andWhere('message.author NOT IN (:...blocked)', {
blocked: user.blocked
})
.getMany();
.getMany()
}
}

54
back/volume/src/main.ts

@ -1,45 +1,45 @@
import { InternalServerErrorException, Logger } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import * as session from "express-session";
import * as passport from "passport";
import { type NestExpressApplication } from "@nestjs/platform-express";
import * as cookieParser from "cookie-parser";
import { IoAdapter } from "@nestjs/platform-socket.io";
import { InternalServerErrorException, Logger } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import * as session from 'express-session'
import * as passport from 'passport'
import { type NestExpressApplication } from '@nestjs/platform-express'
import * as cookieParser from 'cookie-parser'
import { IoAdapter } from '@nestjs/platform-socket.io'
async function bootstrap (): Promise<void> {
const logger = new Logger();
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const logger = new Logger()
const app = await NestFactory.create<NestExpressApplication>(AppModule)
const port =
process.env.BACK_PORT !== undefined && process.env.BACK_PORT !== ""
process.env.BACK_PORT !== undefined && process.env.BACK_PORT !== ''
? +process.env.BACK_PORT
: 3001;
: 3001
const cors = {
origin: /^(http|ws):\/\/localhost(:\d+)?$/,
methods: "GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS",
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS',
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: true,
allowedHeaders: ["Accept", "Content-Type", "Authorization"],
};
allowedHeaders: ['Accept', 'Content-Type', 'Authorization']
}
app.use(
session({
resave: false,
saveUninitialized: false,
secret:
process.env.JWT_SECRET !== undefined && process.env.JWT_SECRET !== ""
process.env.JWT_SECRET !== undefined && process.env.JWT_SECRET !== ''
? process.env.JWT_SECRET
: "secret",
: 'secret'
})
);
app.use(cookieParser());
app.use(passport.initialize());
app.use(passport.session());
app.enableCors(cors);
app.useWebSocketAdapter(new IoAdapter(app));
await app.listen(port);
logger.log(`Application listening on port ${port}`);
)
app.use(cookieParser())
app.use(passport.initialize())
app.use(passport.session())
app.enableCors(cors)
app.useWebSocketAdapter(new IoAdapter(app))
await app.listen(port)
logger.log(`Application listening on port ${port}`)
}
bootstrap().catch((e) => {
throw new InternalServerErrorException(e);
});
throw new InternalServerErrorException(e)
})

20
back/volume/src/pong/dtos/GameCreationDtoValidated.ts

@ -1,4 +1,4 @@
import { Type } from "class-transformer";
import { Type } from 'class-transformer'
import {
ArrayMaxSize,
ArrayMinSize,
@ -7,32 +7,32 @@ import {
IsString,
Max,
Min,
ValidateNested,
} from "class-validator";
ValidateNested
} from 'class-validator'
import {
DEFAULT_BALL_INITIAL_SPEED,
DEFAULT_MAX_BALL_SPEED,
} from "../game/constants";
import { MapDtoValidated } from "./MapDtoValidated";
DEFAULT_MAX_BALL_SPEED
} from '../game/constants'
import { MapDtoValidated } from './MapDtoValidated'
export class GameCreationDtoValidated {
@IsString({ each: true })
@ArrayMaxSize(2)
@ArrayMinSize(2)
playerNames!: string[];
playerNames!: string[]
@IsNotEmptyObject()
@ValidateNested()
@Type(() => MapDtoValidated)
map!: MapDtoValidated;
map!: MapDtoValidated
@IsNumber()
@Min(DEFAULT_BALL_INITIAL_SPEED.x)
@Max(DEFAULT_MAX_BALL_SPEED.x)
initialBallSpeedX!: number;
initialBallSpeedX!: number
@IsNumber()
@Min(DEFAULT_BALL_INITIAL_SPEED.y)
@Max(DEFAULT_MAX_BALL_SPEED.y)
initialBallSpeedY!: number;
initialBallSpeedY!: number
}

18
back/volume/src/pong/dtos/GameInfo.ts

@ -1,12 +1,12 @@
import { type Point, type Rect } from "../game/utils";
import { type Point, type Rect } from '../game/utils'
export class GameInfo {
mapSize!: Point;
yourPaddleIndex!: number;
gameId!: string;
walls!: Rect[];
paddleSize!: Point;
ballSize!: Point;
winScore!: number;
ranked!: boolean;
mapSize!: Point
yourPaddleIndex!: number
gameId!: string
walls!: Rect[]
paddleSize!: Point
ballSize!: Point
winScore!: number
ranked!: boolean
}

10
back/volume/src/pong/dtos/GameUpdate.ts

@ -1,8 +1,8 @@
import { type Point } from "../game/utils";
import { type Point } from '../game/utils'
export class GameUpdate {
paddlesPositions!: Point[];
ballSpeed!: Point;
ballPosition!: Point;
scores!: number[];
paddlesPositions!: Point[]
ballSpeed!: Point
ballPosition!: Point
scores!: number[]
}

14
back/volume/src/pong/dtos/MapDtoValidated.ts

@ -1,23 +1,23 @@
import { Type } from "class-transformer";
import { Type } from 'class-transformer'
import {
ArrayMaxSize,
IsArray,
IsDefined,
IsObject,
ValidateNested,
} from "class-validator";
import { PointDtoValidated } from "./PointDtoValidated";
import { RectDtoValidated } from "./RectDtoValidated";
ValidateNested
} from 'class-validator'
import { PointDtoValidated } from './PointDtoValidated'
import { RectDtoValidated } from './RectDtoValidated'
export class MapDtoValidated {
@IsObject()
@IsDefined()
@Type(() => PointDtoValidated)
size!: PointDtoValidated;
size!: PointDtoValidated
@IsArray()
@ArrayMaxSize(5)
@ValidateNested({ each: true })
@Type(() => RectDtoValidated)
walls!: RectDtoValidated[];
walls!: RectDtoValidated[]
}

2
back/volume/src/pong/dtos/MatchmakingDto.ts

@ -1,3 +1,3 @@
export class MatchmakingDto {
matchmaking!: boolean;
matchmaking!: boolean
}

6
back/volume/src/pong/dtos/MatchmakingDtoValidated.ts

@ -1,7 +1,7 @@
import { IsBoolean } from "class-validator";
import { MatchmakingDto } from "./MatchmakingDto";
import { IsBoolean } from 'class-validator'
import { MatchmakingDto } from './MatchmakingDto'
export class MatchmakingDtoValidated extends MatchmakingDto {
@IsBoolean()
matchmaking!: boolean;
matchmaking!: boolean
}

8
back/volume/src/pong/dtos/PointDtoValidated.ts

@ -1,10 +1,10 @@
import { IsNumber } from "class-validator";
import { Point } from "../game/utils";
import { IsNumber } from 'class-validator'
import { Point } from '../game/utils'
export class PointDtoValidated extends Point {
@IsNumber()
x!: number;
x!: number
@IsNumber()
y!: number;
y!: number
}

12
back/volume/src/pong/dtos/RectDtoValidated.ts

@ -1,14 +1,14 @@
import { Type } from "class-transformer";
import { ValidateNested } from "class-validator";
import { Rect } from "../game/utils";
import { PointDtoValidated } from "./PointDtoValidated";
import { Type } from 'class-transformer'
import { ValidateNested } from 'class-validator'
import { Rect } from '../game/utils'
import { PointDtoValidated } from './PointDtoValidated'
export class RectDtoValidated extends Rect {
@ValidateNested()
@Type(() => PointDtoValidated)
center!: PointDtoValidated;
center!: PointDtoValidated
@ValidateNested()
@Type(() => PointDtoValidated)
size!: PointDtoValidated;
size!: PointDtoValidated
}

2
back/volume/src/pong/dtos/StringDto.ts

@ -1,3 +1,3 @@
export class StringDto {
value!: string;
value!: string
}

6
back/volume/src/pong/dtos/StringDtoValidated.ts

@ -1,7 +1,7 @@
import { IsString } from "class-validator";
import { StringDto } from "./StringDto";
import { IsString } from 'class-validator'
import { StringDto } from './StringDto'
export class StringDtoValidated extends StringDto {
@IsString()
value!: string;
value!: string
}

8
back/volume/src/pong/dtos/UserDto.ts

@ -1,12 +1,12 @@
import { IsString } from "class-validator";
import { IsString } from 'class-validator'
export class UserDto {
@IsString()
username!: string;
username!: string
@IsString()
avatar!: string;
avatar!: string
@IsString()
status!: string;
status!: string
}

18
back/volume/src/pong/entity/result.entity.ts

@ -3,25 +3,25 @@ import {
PrimaryGeneratedColumn,
Column,
ManyToMany,
CreateDateColumn,
} from "typeorm";
CreateDateColumn
} from 'typeorm'
import User from "src/users/entity/user.entity";
import User from 'src/users/entity/user.entity'
@Entity()
export default class Result {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column({ default: false })
ranked: boolean;
ranked: boolean
@ManyToMany(() => User, (player: User) => player.results, { cascade: true })
players: Array<User | null>; // TODO: change to User[] for final version
players: Array<User | null> // TODO: change to User[] for final version
@Column("text", { array: true })
public score: number[];
@Column('text', { array: true })
public score: number[]
@CreateDateColumn()
date: Date;
date: Date
}

96
back/volume/src/pong/game/Ball.ts

@ -1,48 +1,48 @@
import { type Paddle } from "./Paddle";
import { type Point, Rect } from "./utils";
import { type MapDtoValidated } from "../dtos/MapDtoValidated";
import { type Paddle } from './Paddle'
import { type Point, Rect } from './utils'
import { type MapDtoValidated } from '../dtos/MapDtoValidated'
import {
DEFAULT_BALL_SIZE,
GAME_TICKS,
DEFAULT_BALL_SPEED_INCREMENT,
DEFAULT_MAX_BALL_SPEED,
} from "./constants";
DEFAULT_MAX_BALL_SPEED
} from './constants'
export class Ball {
rect: Rect;
initial_speed: Point;
speed: Point;
spawn: Point;
indexPlayerScored: number;
timeoutTime: number;
rect: Rect
initial_speed: Point
speed: Point
spawn: Point
indexPlayerScored: number
timeoutTime: number
constructor (
spawn: Point,
initialSpeed: Point,
size: Point = DEFAULT_BALL_SIZE.clone()
) {
this.rect = new Rect(spawn, size);
this.speed = initialSpeed.clone();
this.initial_speed = initialSpeed.clone();
this.spawn = spawn.clone();
this.indexPlayerScored = -1;
this.timeoutTime = 0;
this.rect = new Rect(spawn, size)
this.speed = initialSpeed.clone()
this.initial_speed = initialSpeed.clone()
this.spawn = spawn.clone()
this.indexPlayerScored = -1
this.timeoutTime = 0
}
getIndexPlayerScored (): number {
return this.indexPlayerScored;
return this.indexPlayerScored
}
update (canvasRect: Rect, paddles: Paddle[], map: MapDtoValidated): void {
if (!canvasRect.contains_x(this.rect)) {
this.indexPlayerScored = this.playerScored();
this.timeoutTime = 2000;
this.indexPlayerScored = this.playerScored()
this.timeoutTime = 2000
} else {
this.indexPlayerScored = -1;
this.indexPlayerScored = -1
if (this.timeoutTime <= 0) {
this.move(canvasRect, paddles, map);
this.move(canvasRect, paddles, map)
} else {
this.timeoutTime -= 1000 / GAME_TICKS;
this.timeoutTime -= 1000 / GAME_TICKS
}
}
}
@ -51,64 +51,64 @@ export class Ball {
for (const paddle of paddles) {
if (paddle.rect.collides(this.rect)) {
if (this.speed.x < 0) {
this.rect.center.x = paddle.rect.center.x + paddle.rect.size.x;
} else this.rect.center.x = paddle.rect.center.x - paddle.rect.size.x;
this.speed.x = this.speed.x * -1;
this.rect.center.x = paddle.rect.center.x + paddle.rect.size.x
} else this.rect.center.x = paddle.rect.center.x - paddle.rect.size.x
this.speed.x = this.speed.x * -1
this.speed.y =
((this.rect.center.y - paddle.rect.center.y) / paddle.rect.size.y) *
20;
break;
20
break
}
}
for (const wall of map.walls) {
if (wall.collides(this.rect)) {
if (this.speed.x < 0) {
this.rect.center.x = wall.center.x + wall.size.x;
} else this.rect.center.x = wall.center.x - wall.size.x;
this.speed.x = this.speed.x * -1;
this.rect.center.x = wall.center.x + wall.size.x
} else this.rect.center.x = wall.center.x - wall.size.x
this.speed.x = this.speed.x * -1
this.speed.y =
((this.rect.center.y - wall.center.y) / wall.size.y) * 20;
break;
((this.rect.center.y - wall.center.y) / wall.size.y) * 20
break
}
}
if (!canvasRect.contains_y(this.rect)) this.speed.y = this.speed.y * -1;
if (!canvasRect.contains_y(this.rect)) this.speed.y = this.speed.y * -1
if (this.speed.x > 0 && this.speed.x < DEFAULT_MAX_BALL_SPEED.x) {
this.speed.x += DEFAULT_BALL_SPEED_INCREMENT.x;
this.speed.x += DEFAULT_BALL_SPEED_INCREMENT.x
}
if (this.speed.x < 0 && this.speed.x > -DEFAULT_MAX_BALL_SPEED.x) {
this.speed.x -= DEFAULT_BALL_SPEED_INCREMENT.x;
this.speed.x -= DEFAULT_BALL_SPEED_INCREMENT.x
}
if (this.speed.y > 0 && this.speed.y > DEFAULT_MAX_BALL_SPEED.y) {
this.speed.y += DEFAULT_MAX_BALL_SPEED.y;
this.speed.y += DEFAULT_MAX_BALL_SPEED.y
}
if (this.speed.y < 0 && this.speed.y < -DEFAULT_MAX_BALL_SPEED.y) {
this.speed.y -= DEFAULT_MAX_BALL_SPEED.y;
this.speed.y -= DEFAULT_MAX_BALL_SPEED.y
}
this.rect.center.add_inplace(this.speed);
this.rect.center.add_inplace(this.speed)
}
playerScored (): number {
let indexPlayerScored: number;
let indexPlayerScored: number
if (this.rect.center.x <= this.spawn.x) {
indexPlayerScored = 1;
this.speed.x = this.initial_speed.x;
indexPlayerScored = 1
this.speed.x = this.initial_speed.x
} else {
indexPlayerScored = 0;
this.speed.x = -this.initial_speed.x;
indexPlayerScored = 0
this.speed.x = -this.initial_speed.x
}
if (this.speed.y < 0) {
this.speed.y = this.initial_speed.y;
this.speed.y = this.initial_speed.y
} else {
this.speed.y = -this.initial_speed.y;
this.speed.y = -this.initial_speed.y
}
this.rect.center = this.spawn.clone();
this.rect.center = this.spawn.clone()
return indexPlayerScored;
return indexPlayerScored
}
}

140
back/volume/src/pong/game/Game.ts

@ -1,31 +1,31 @@
import { Ball } from "./Ball";
import { type Socket } from "socket.io";
import { Point, Rect } from "./utils";
import { Player } from "./Player";
import { Ball } from './Ball'
import { type Socket } from 'socket.io'
import { Point, Rect } from './utils'
import { Player } from './Player'
import {
DEFAULT_BALL_INITIAL_SPEED,
DEFAULT_BALL_SIZE,
DEFAULT_PADDLE_SIZE,
DEFAULT_WIN_SCORE,
GAME_EVENTS,
GAME_TICKS,
} from "./constants";
import { randomUUID } from "crypto";
import { type MapDtoValidated } from "../dtos/MapDtoValidated";
import { type GameUpdate } from "../dtos/GameUpdate";
import { type GameInfo } from "../dtos/GameInfo";
import { type PongService } from "../pong.service";
GAME_TICKS
} from './constants'
import { randomUUID } from 'crypto'
import { type MapDtoValidated } from '../dtos/MapDtoValidated'
import { type GameUpdate } from '../dtos/GameUpdate'
import { type GameInfo } from '../dtos/GameInfo'
import { type PongService } from '../pong.service'
export class Game {
id: string;
timer: NodeJS.Timer | null;
map: MapDtoValidated;
ball: Ball;
players: Player[] = [];
ranked: boolean;
initialBallSpeed: Point;
waitingForTimeout: boolean;
gameStoppedCallback: (name: string) => void;
id: string
timer: NodeJS.Timer | null
map: MapDtoValidated
ball: Ball
players: Player[] = []
ranked: boolean
initialBallSpeed: Point
waitingForTimeout: boolean
gameStoppedCallback: (name: string) => void
constructor (
sockets: Socket[],
@ -37,24 +37,24 @@ export class Game {
ranked: boolean,
initialBallSpeed: Point = DEFAULT_BALL_INITIAL_SPEED.clone()
) {
this.id = randomUUID();
this.timer = null;
this.ranked = ranked;
this.waitingForTimeout = false;
this.map = map;
this.gameStoppedCallback = gameStoppedCallback;
this.initialBallSpeed = initialBallSpeed;
this.id = randomUUID()
this.timer = null
this.ranked = ranked
this.waitingForTimeout = false
this.map = map
this.gameStoppedCallback = gameStoppedCallback
this.initialBallSpeed = initialBallSpeed
this.ball = new Ball(
new Point(this.map.size.x / 2, this.map.size.y / 2),
initialBallSpeed
);
)
for (let i = 0; i < uuids.length; i++) {
this.addPlayer(sockets[i], uuids[i], names[i]);
this.addPlayer(sockets[i], uuids[i], names[i])
}
}
getGameInfo (name: string): GameInfo {
const yourPaddleIndex = this.players.findIndex((p) => p.name === name);
const yourPaddleIndex = this.players.findIndex((p) => p.name === name)
return {
mapSize: this.map.size,
yourPaddleIndex,
@ -63,36 +63,36 @@ export class Game {
paddleSize: DEFAULT_PADDLE_SIZE,
ballSize: DEFAULT_BALL_SIZE,
winScore: DEFAULT_WIN_SCORE,
ranked: this.ranked,
};
ranked: this.ranked
}
}
private addPlayer (socket: Socket, uuid: string, name: string): void {
let paddleCoords = new Point(
DEFAULT_PADDLE_SIZE.x / 2,
this.map.size.y / 2
);
)
if (this.players.length === 1) {
paddleCoords = new Point(
this.map.size.x - DEFAULT_PADDLE_SIZE.x / 2,
this.map.size.y / 2
);
)
}
this.players.push(
new Player(socket, uuid, name, paddleCoords, this.map.size)
);
)
if (this.ranked) {
this.ready(name);
this.ready(name)
}
}
ready (name: string): void {
const playerIndex: number = this.players.findIndex((p) => p.name === name);
const playerIndex: number = this.players.findIndex((p) => p.name === name)
if (playerIndex !== -1 && !this.players[playerIndex].ready) {
this.players[playerIndex].ready = true;
console.log(`${this.players[playerIndex].name} is ready`);
this.players[playerIndex].ready = true
console.log(`${this.players[playerIndex].name} is ready`)
if (this.players.length === 2 && this.players.every((p) => p.ready)) {
this.start();
this.start()
}
}
}
@ -102,73 +102,73 @@ export class Game {
this.ball = new Ball(
new Point(this.map.size.x / 2, this.map.size.y / 2),
this.initialBallSpeed
);
)
this.players.forEach((p) => {
void this.pongService.setInGame(p.name);
p.newGame();
});
this.broadcastGame(GAME_EVENTS.START_GAME);
this.timer = setInterval(this.gameLoop.bind(this), 1000 / GAME_TICKS);
console.log(`Game ${this.id} starting in 3 seconds`);
this.waitingForTimeout = true;
void this.pongService.setInGame(p.name)
p.newGame()
})
this.broadcastGame(GAME_EVENTS.START_GAME)
this.timer = setInterval(this.gameLoop.bind(this), 1000 / GAME_TICKS)
console.log(`Game ${this.id} starting in 3 seconds`)
this.waitingForTimeout = true
new Promise((resolve) => setTimeout(resolve, 3000))
.then(() => (this.waitingForTimeout = false))
.catch(() => {});
.catch(() => {})
}
}
stop (): void {
if (this.timer !== null) {
clearInterval(this.timer);
clearInterval(this.timer)
}
this.timer = null;
this.timer = null
this.pongService
.saveResult(this.players, this.ranked, DEFAULT_WIN_SCORE)
.then(() => {
this.gameStoppedCallback(this.players[0].name);
this.players = [];
this.gameStoppedCallback(this.players[0].name)
this.players = []
})
.catch(() => {
this.gameStoppedCallback(this.players[0].name);
this.players = [];
});
this.gameStoppedCallback(this.players[0].name)
this.players = []
})
}
movePaddle (name: string | undefined, position: Point): void {
const playerIndex: number = this.players.findIndex((p) => p.name === name);
const playerIndex: number = this.players.findIndex((p) => p.name === name)
if (this.timer !== null && playerIndex !== -1) {
this.players[playerIndex].paddle.move(position.y);
this.players[playerIndex].paddle.move(position.y)
}
}
private broadcastGame (event: string, data?: any): void {
this.players.forEach((p) => {
p.socket.emit(event, data);
});
p.socket.emit(event, data)
})
}
private gameLoop (): void {
if (this.waitingForTimeout) {
return;
return
}
const canvasRect: Rect = new Rect(
new Point(this.map.size.x / 2, this.map.size.y / 2),
new Point(this.map.size.x, this.map.size.y)
);
)
this.ball.update(
canvasRect,
this.players.map((p) => p.paddle),
this.map
);
const indexPlayerScored: number = this.ball.getIndexPlayerScored();
)
const indexPlayerScored: number = this.ball.getIndexPlayerScored()
if (indexPlayerScored !== -1) {
this.players[indexPlayerScored].score += 1;
this.players[indexPlayerScored].score += 1
if (this.players[indexPlayerScored].score >= DEFAULT_WIN_SCORE) {
console.log(`${this.players[indexPlayerScored].name} won`);
this.stop();
console.log(`${this.players[indexPlayerScored].name} won`)
this.stop()
}
}
@ -176,8 +176,8 @@ export class Game {
paddlesPositions: this.players.map((p) => p.paddle.rect.center),
ballSpeed: this.ball.speed,
ballPosition: this.ball.rect.center,
scores: this.players.map((p) => p.score),
};
this.broadcastGame(GAME_EVENTS.GAME_TICK, data);
scores: this.players.map((p) => p.score)
}
this.broadcastGame(GAME_EVENTS.GAME_TICK, data)
}
}

78
back/volume/src/pong/game/Games.ts

@ -1,21 +1,21 @@
import { type Socket } from "socket.io";
import { Game } from "./Game";
import { Point } from "./utils";
import { type MapDtoValidated as GameMap } from "../dtos/MapDtoValidated";
import { type GameCreationDtoValidated } from "../dtos/GameCreationDtoValidated";
import { type GameInfo } from "../dtos/GameInfo";
import { type PongService } from "../pong.service";
import { type Socket } from 'socket.io'
import { Game } from './Game'
import { Point } from './utils'
import { type MapDtoValidated as GameMap } from '../dtos/MapDtoValidated'
import { type GameCreationDtoValidated } from '../dtos/GameCreationDtoValidated'
import { type GameInfo } from '../dtos/GameInfo'
import { type PongService } from '../pong.service'
import {
DEFAULT_BALL_SIZE,
DEFAULT_MAP_SIZE,
DEFAULT_PADDLE_SIZE,
DEFAULT_WIN_SCORE,
} from "./constants";
DEFAULT_WIN_SCORE
} from './constants'
export class Games {
constructor (private readonly pongService: PongService) {}
private readonly playerNameToGameIndex = new Map<string, number>();
private readonly games = new Array<Game>();
private readonly playerNameToGameIndex = new Map<string, number>()
private readonly games = new Array<Game>()
newGame (
sockets: Socket[],
@ -23,11 +23,11 @@ export class Games {
gameCreationDto: GameCreationDtoValidated,
ranked: boolean
): void {
const names: string[] = gameCreationDto.playerNames;
const names: string[] = gameCreationDto.playerNames
const map: GameMap = {
size: DEFAULT_MAP_SIZE,
walls: gameCreationDto.map.walls,
};
walls: gameCreationDto.map.walls
}
if (!this.isInAGame(names[0]) && !this.isInAGame(names[1])) {
this.games.push(
new Game(
@ -43,76 +43,76 @@ export class Games {
gameCreationDto.initialBallSpeedY
)
)
);
this.playerNameToGameIndex.set(names[0], this.games.length - 1);
this.playerNameToGameIndex.set(names[1], this.games.length - 1);
)
this.playerNameToGameIndex.set(names[0], this.games.length - 1)
this.playerNameToGameIndex.set(names[1], this.games.length - 1)
console.log(
`Created game ${names[0]} vs ${names[1]} (${
this.games[this.games.length - 1].id
})`
);
)
}
}
ready (name: string): void {
const game: Game | undefined = this.playerGame(name);
const game: Game | undefined = this.playerGame(name)
if (game !== undefined) {
game.ready(name);
game.ready(name)
}
}
private deleteGame (name: string): void {
const game: Game | undefined = this.playerGame(name);
const game: Game | undefined = this.playerGame(name)
if (game !== undefined) {
this.games.splice(this.games.indexOf(game), 1);
this.games.splice(this.games.indexOf(game), 1)
game.players.forEach((player) => {
this.playerNameToGameIndex.delete(player.name);
});
console.log(`Game stopped: ${game.id}`);
this.playerNameToGameIndex.delete(player.name)
})
console.log(`Game stopped: ${game.id}`)
}
}
getGameInfo (name: string): GameInfo {
const game: Game | undefined = this.playerGame(name);
const game: Game | undefined = this.playerGame(name)
if (game !== undefined) {
return game.getGameInfo(name);
return game.getGameInfo(name)
}
return {
yourPaddleIndex: -2,
gameId: "",
gameId: '',
mapSize: new Point(0, 0),
walls: [],
paddleSize: DEFAULT_PADDLE_SIZE,
ballSize: DEFAULT_BALL_SIZE,
winScore: DEFAULT_WIN_SCORE,
ranked: false,
};
ranked: false
}
}
movePlayer (name: string | undefined, position: Point): void {
const game: Game | undefined = this.playerGame(name);
const game: Game | undefined = this.playerGame(name)
if (game !== undefined) {
game.movePaddle(name, position);
game.movePaddle(name, position)
}
}
isInAGame (name: string | undefined): boolean {
if (name === undefined) return false;
return this.playerNameToGameIndex.get(name) !== undefined;
if (name === undefined) return false
return this.playerNameToGameIndex.get(name) !== undefined
}
playerGame (name: string | undefined): Game | undefined {
const game: Game | undefined = this.games.find((game) =>
game.players.some((player) => player.name === name)
);
return game;
)
return game
}
async leaveGame (name: string): Promise<void> {
const game: Game | undefined = this.playerGame(name);
const game: Game | undefined = this.playerGame(name)
if (game !== undefined && !game.ranked) {
game.stop();
this.deleteGame(name);
game.stop()
this.deleteGame(name)
}
}
}

46
back/volume/src/pong/game/MatchmakingQueue.ts

@ -1,64 +1,64 @@
import { type Socket } from "socket.io";
import { type GameCreationDtoValidated } from "../dtos/GameCreationDtoValidated";
import { DEFAULT_BALL_INITIAL_SPEED, DEFAULT_MAP_SIZE } from "./constants";
import { type Games } from "./Games";
import { type Socket } from 'socket.io'
import { type GameCreationDtoValidated } from '../dtos/GameCreationDtoValidated'
import { DEFAULT_BALL_INITIAL_SPEED, DEFAULT_MAP_SIZE } from './constants'
import { type Games } from './Games'
export class MatchmakingQueue {
games: Games;
queue: Array<{ name: string; socket: Socket; uuid: string }>;
games: Games
queue: Array<{ name: string, socket: Socket, uuid: string }>
constructor (games: Games) {
this.games = games;
this.queue = [];
this.games = games
this.queue = []
}
addPlayer (name: string, socket: Socket, uuid: string): void {
if (!this.isInQueue(name)) {
console.log("Adding player to queue: ", name);
this.queue.push({ name, socket, uuid });
console.log('Adding player to queue: ', name)
this.queue.push({ name, socket, uuid })
if (this.canCreateGame()) {
this.createGame();
this.createGame()
}
}
}
removePlayer (name: string): void {
if (this.isInQueue(name)) {
console.log("Removing player from queue: ", name);
this.queue = this.queue.filter((player) => player.name !== name);
console.log('Removing player from queue: ', name)
this.queue = this.queue.filter((player) => player.name !== name)
}
}
isInQueue (name: string): boolean {
return this.queue.some((player) => player.name === name);
return this.queue.some((player) => player.name === name)
}
canCreateGame (): boolean {
return this.queue.length >= 2;
return this.queue.length >= 2
}
createGame (): void {
const player1 = this.queue.shift();
const player2 = this.queue.shift();
const player1 = this.queue.shift()
const player2 = this.queue.shift()
if (player1 === undefined || player2 === undefined) {
return;
return
}
const gameCreationDto: GameCreationDtoValidated = {
playerNames: [player1.name, player2.name],
map: {
size: DEFAULT_MAP_SIZE,
walls: [],
walls: []
},
initialBallSpeedX: DEFAULT_BALL_INITIAL_SPEED.x,
initialBallSpeedY: DEFAULT_BALL_INITIAL_SPEED.y,
};
const ranked = true;
initialBallSpeedY: DEFAULT_BALL_INITIAL_SPEED.y
}
const ranked = true
this.games.newGame(
[player1.socket, player2.socket],
[player1.uuid, player2.uuid],
gameCreationDto,
ranked
);
)
}
}

24
back/volume/src/pong/game/Paddle.ts

@ -1,32 +1,32 @@
import { DEFAULT_PADDLE_SIZE } from "./constants";
import { type Point, Rect } from "./utils";
import { DEFAULT_PADDLE_SIZE } from './constants'
import { type Point, Rect } from './utils'
export class Paddle {
rect: Rect;
color: string | CanvasGradient | CanvasPattern = "white";
mapSize: Point;
rect: Rect
color: string | CanvasGradient | CanvasPattern = 'white'
mapSize: Point
constructor (
spawn: Point,
gameSize: Point,
size: Point = DEFAULT_PADDLE_SIZE
) {
this.rect = new Rect(spawn, size);
this.mapSize = gameSize;
this.rect = new Rect(spawn, size)
this.mapSize = gameSize
}
draw (context: CanvasRenderingContext2D): void {
this.rect.draw(context, this.color);
this.rect.draw(context, this.color)
}
move (newY: number): void {
const offset: number = this.rect.size.y / 2;
const offset: number = this.rect.size.y / 2
if (newY - offset < 0) {
this.rect.center.y = offset;
this.rect.center.y = offset
} else if (newY + offset > this.mapSize.y) {
this.rect.center.y = this.mapSize.y - offset;
this.rect.center.y = this.mapSize.y - offset
} else {
this.rect.center.y = newY;
this.rect.center.y = newY
}
}
}

42
back/volume/src/pong/game/Player.ts

@ -1,16 +1,16 @@
import { type Socket } from "socket.io";
import { Paddle } from "./Paddle";
import { type Point } from "./utils";
import { type Socket } from 'socket.io'
import { Paddle } from './Paddle'
import { type Point } from './utils'
export class Player {
socket: Socket;
uuid: string;
name: string;
ready: boolean;
paddle: Paddle;
paddleCoords: Point;
mapSize: Point;
score: number;
socket: Socket
uuid: string
name: string
ready: boolean
paddle: Paddle
paddleCoords: Point
mapSize: Point
score: number
constructor (
socket: Socket,
@ -19,18 +19,18 @@ export class Player {
paddleCoords: Point,
mapSize: Point
) {
this.socket = socket;
this.uuid = uuid;
this.name = name;
this.ready = false;
this.paddle = new Paddle(paddleCoords, mapSize);
this.paddleCoords = paddleCoords;
this.mapSize = mapSize;
this.score = 0;
this.socket = socket
this.uuid = uuid
this.name = name
this.ready = false
this.paddle = new Paddle(paddleCoords, mapSize)
this.paddleCoords = paddleCoords
this.mapSize = mapSize
this.score = 0
}
newGame (): void {
this.score = 0;
this.paddle = new Paddle(this.paddleCoords, this.mapSize);
this.score = 0
this.paddle = new Paddle(this.paddleCoords, this.mapSize)
}
}

38
back/volume/src/pong/game/constants.ts

@ -1,22 +1,22 @@
import { Point } from "./utils";
import { Point } from './utils'
export const GAME_EVENTS = {
START_GAME: "START_GAME",
READY: "READY",
GAME_TICK: "GAME_TICK",
PLAYER_MOVE: "PLAYER_MOVE",
GET_GAME_INFO: "GET_GAME_INFO",
CREATE_GAME: "CREATE_GAME",
REGISTER_PLAYER: "REGISTER_PLAYER",
MATCHMAKING: "MATCHMAKING",
LEAVE_GAME: "LEAVE_GAME",
};
START_GAME: 'START_GAME',
READY: 'READY',
GAME_TICK: 'GAME_TICK',
PLAYER_MOVE: 'PLAYER_MOVE',
GET_GAME_INFO: 'GET_GAME_INFO',
CREATE_GAME: 'CREATE_GAME',
REGISTER_PLAYER: 'REGISTER_PLAYER',
MATCHMAKING: 'MATCHMAKING',
LEAVE_GAME: 'LEAVE_GAME'
}
export const DEFAULT_MAP_SIZE = new Point(500, 400);
export const DEFAULT_PADDLE_SIZE = new Point(30, 50);
export const DEFAULT_BALL_SIZE = new Point(10, 10);
export const DEFAULT_BALL_INITIAL_SPEED = new Point(10, 2);
export const DEFAULT_MAX_BALL_SPEED = new Point(30, 20);
export const DEFAULT_BALL_SPEED_INCREMENT = new Point(0.05, 0);
export const DEFAULT_WIN_SCORE = 5;
export const GAME_TICKS = 30;
export const DEFAULT_MAP_SIZE = new Point(500, 400)
export const DEFAULT_PADDLE_SIZE = new Point(30, 50)
export const DEFAULT_BALL_SIZE = new Point(10, 10)
export const DEFAULT_BALL_INITIAL_SPEED = new Point(10, 2)
export const DEFAULT_MAX_BALL_SPEED = new Point(30, 20)
export const DEFAULT_BALL_SPEED_INCREMENT = new Point(0.05, 0)
export const DEFAULT_WIN_SCORE = 5
export const GAME_TICKS = 30

54
back/volume/src/pong/game/utils.ts

@ -1,83 +1,83 @@
export class Point {
x: number;
y: number;
x: number
y: number
constructor (x: number, y: number) {
this.x = x;
this.y = y;
this.x = x
this.y = y
}
// Returns a new point
add (other: Point): Point {
return new Point(this.x + other.x, this.y + other.y);
return new Point(this.x + other.x, this.y + other.y)
}
// Modifies `this` point
add_inplace (other: Point): void {
this.x += other.x;
this.y += other.y;
this.x += other.x
this.y += other.y
}
clone (): Point {
return new Point(this.x, this.y);
return new Point(this.x, this.y)
}
}
export class Rect {
center: Point;
size: Point;
center: Point
size: Point
constructor (center: Point, size: Point) {
this.center = center;
this.size = size;
this.center = center
this.size = size
}
draw (
context: CanvasRenderingContext2D,
color: string | CanvasGradient | CanvasPattern
): void {
const offset: Point = new Point(this.size.x / 2, this.size.y / 2);
const offset: Point = new Point(this.size.x / 2, this.size.y / 2)
context.fillStyle = color;
context.fillStyle = color
context.fillRect(
this.center.x - offset.x,
this.center.y - offset.y,
this.size.x,
this.size.y
);
)
}
// True if `this` rect contains `other` rect in the x-axis
contains_x (other: Rect): boolean {
const offset: number = this.size.x / 2;
const offsetOther: number = other.size.x / 2;
const offset: number = this.size.x / 2
const offsetOther: number = other.size.x / 2
if (
this.center.x - offset <= other.center.x - offsetOther &&
this.center.x + offset >= other.center.x + offsetOther
) {
return true;
return true
}
return false;
return false
}
// True if `this` rect contains `other` rect in the y-axis
contains_y (other: Rect): boolean {
const offset: number = this.size.y / 2;
const offsetOther: number = other.size.y / 2;
const offset: number = this.size.y / 2
const offsetOther: number = other.size.y / 2
if (
this.center.y - offset <= other.center.y - offsetOther &&
this.center.y + offset >= other.center.y + offsetOther
) {
return true;
return true
}
return false;
return false
}
collides (other: Rect): boolean {
const offset: Point = new Point(this.size.x / 2, this.size.y / 2);
const offsetOther: Point = new Point(other.size.x / 2, other.size.y / 2);
const offset: Point = new Point(this.size.x / 2, this.size.y / 2)
const offsetOther: Point = new Point(other.size.x / 2, other.size.y / 2)
if (
this.center.x - offset.x < other.center.x + offsetOther.x &&
@ -85,8 +85,8 @@ export class Rect {
this.center.y - offset.y < other.center.y + offsetOther.y &&
this.center.y + offset.y > other.center.y - offsetOther.y
) {
return true;
return true
}
return false;
return false
}
}

24
back/volume/src/pong/pong.controller.ts

@ -3,31 +3,31 @@ import {
Get,
Param,
ParseIntPipe,
UseGuards,
} from "@nestjs/common";
import { Paginate, type Paginated, PaginateQuery } from "nestjs-paginate";
import { AuthenticatedGuard } from "src/auth/42-auth.guard";
import type Result from "./entity/result.entity";
import { PongService } from "./pong.service";
UseGuards
} from '@nestjs/common'
import { Paginate, type Paginated, PaginateQuery } from 'nestjs-paginate'
import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import type Result from './entity/result.entity'
import { PongService } from './pong.service'
@Controller("results")
@Controller('results')
export class PongController {
constructor (private readonly pongService: PongService) {}
@Get("global")
@Get('global')
@UseGuards(AuthenticatedGuard)
async getGlobalHistory (
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, 0);
return await this.pongService.getHistory(query, 0)
}
@Get(":id")
@Get(':id')
@UseGuards(AuthenticatedGuard)
async getHistoryById (
@Param("id", ParseIntPipe) id: number,
@Param('id', ParseIntPipe) id: number,
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, id);
return await this.pongService.getHistory(query, id)
}
}

24
back/volume/src/pong/pong.gateway.spec.ts

@ -1,18 +1,18 @@
import { Test, type TestingModule } from "@nestjs/testing";
import { PongGateway } from "./pong.gateway";
import { Test, type TestingModule } from '@nestjs/testing'
import { PongGateway } from './pong.gateway'
describe("PongGateway", () => {
let gateway: PongGateway;
describe('PongGateway', () => {
let gateway: PongGateway
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PongGateway],
}).compile();
providers: [PongGateway]
}).compile()
gateway = module.get<PongGateway>(PongGateway);
});
gateway = module.get<PongGateway>(PongGateway)
})
it("should be defined", () => {
expect(gateway).toBeDefined();
});
});
it('should be defined', () => {
expect(gateway).toBeDefined()
})
})

136
back/volume/src/pong/pong.gateway.ts

@ -1,28 +1,28 @@
import { UsePipes, ValidationPipe } from "@nestjs/common";
import { Socket } from "socket.io";
import { UsePipes, ValidationPipe } from '@nestjs/common'
import { Socket } from 'socket.io'
import {
ConnectedSocket,
MessageBody,
type OnGatewayConnection,
type OnGatewayDisconnect,
SubscribeMessage,
WebSocketGateway,
} from "@nestjs/websockets";
import { Games } from "./game/Games";
import { GAME_EVENTS } from "./game/constants";
import { GameCreationDtoValidated } from "./dtos/GameCreationDtoValidated";
import { type Game } from "./game/Game";
import { plainToClass } from "class-transformer";
import { PointDtoValidated } from "./dtos/PointDtoValidated";
import { StringDtoValidated } from "./dtos/StringDtoValidated";
import { MatchmakingQueue } from "./game/MatchmakingQueue";
import { MatchmakingDtoValidated } from "./dtos/MatchmakingDtoValidated";
import { PongService } from "./pong.service";
import { UsersService } from "src/users/users.service";
WebSocketGateway
} from '@nestjs/websockets'
import { Games } from './game/Games'
import { GAME_EVENTS } from './game/constants'
import { GameCreationDtoValidated } from './dtos/GameCreationDtoValidated'
import { type Game } from './game/Game'
import { plainToClass } from 'class-transformer'
import { PointDtoValidated } from './dtos/PointDtoValidated'
import { StringDtoValidated } from './dtos/StringDtoValidated'
import { MatchmakingQueue } from './game/MatchmakingQueue'
import { MatchmakingDtoValidated } from './dtos/MatchmakingDtoValidated'
import { PongService } from './pong.service'
import { UsersService } from 'src/users/users.service'
@WebSocketGateway({
cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ },
cors: { origin: /^(http|ws):\/\/localhost(:\d+)?$/ }
})
export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
constructor (
@ -30,12 +30,12 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
private readonly usersService: UsersService
) {}
private readonly games: Games = new Games(this.pongService);
private readonly socketToPlayerName = new Map<Socket, string>();
private readonly matchmakingQueue = new MatchmakingQueue(this.games);
private readonly games: Games = new Games(this.pongService)
private readonly socketToPlayerName = new Map<Socket, string>()
private readonly matchmakingQueue = new MatchmakingQueue(this.games)
playerIsRegistered (name: string): boolean {
return Array.from(this.socketToPlayerName.values()).includes(name);
return Array.from(this.socketToPlayerName.values()).includes(name)
}
handleConnection (): void {}
@ -44,15 +44,15 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
@ConnectedSocket()
client: Socket
): void {
const name: string | undefined = this.socketToPlayerName.get(client);
const game: Game | undefined = this.games.playerGame(name);
const name: string | undefined = this.socketToPlayerName.get(client)
const game: Game | undefined = this.games.playerGame(name)
if (game !== undefined) {
game.stop();
game.stop()
}
if (name !== undefined) {
console.log("Disconnected ", this.socketToPlayerName.get(client));
this.matchmakingQueue.removePlayer(name);
this.socketToPlayerName.delete(client);
console.log('Disconnected ', this.socketToPlayerName.get(client))
this.matchmakingQueue.removePlayer(name)
this.socketToPlayerName.delete(client)
}
}
@ -61,30 +61,30 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
async registerPlayer (
@ConnectedSocket()
client: Socket,
@MessageBody("playerName") playerName: StringDtoValidated,
@MessageBody("socketKey") socketKey: StringDtoValidated
): Promise<{ event: string; data: boolean }> {
let succeeded: boolean = false;
const user = await this.usersService.findUserByName(playerName.value);
@MessageBody('playerName') playerName: StringDtoValidated,
@MessageBody('socketKey') socketKey: StringDtoValidated
): Promise<{ event: string, data: boolean }> {
let succeeded: boolean = false
const user = await this.usersService.findUserByName(playerName.value)
if (
user !== null &&
user.socketKey === socketKey.value &&
!this.playerIsRegistered(playerName.value)
) {
this.socketToPlayerName.set(client, playerName.value);
succeeded = true;
console.log("Registered player", playerName.value);
this.socketToPlayerName.set(client, playerName.value)
succeeded = true
console.log('Registered player', playerName.value)
} else {
console.log("Failed to register player", playerName.value);
console.log('Failed to register player', playerName.value)
}
return { event: GAME_EVENTS.REGISTER_PLAYER, data: succeeded };
return { event: GAME_EVENTS.REGISTER_PLAYER, data: succeeded }
}
@SubscribeMessage(GAME_EVENTS.GET_GAME_INFO)
getPlayerCount (@ConnectedSocket() client: Socket): void {
const name: string | undefined = this.socketToPlayerName.get(client);
const name: string | undefined = this.socketToPlayerName.get(client)
if (name !== undefined) {
client.emit(GAME_EVENTS.GET_GAME_INFO, this.games.getGameInfo(name));
client.emit(GAME_EVENTS.GET_GAME_INFO, this.games.getGameInfo(name))
}
}
@ -98,9 +98,9 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
const realPosition: PointDtoValidated = plainToClass(
PointDtoValidated,
position
);
const name: string | undefined = this.socketToPlayerName.get(client);
this.games.movePlayer(name, realPosition);
)
const name: string | undefined = this.socketToPlayerName.get(client)
this.games.movePlayer(name, realPosition)
}
@UsePipes(new ValidationPipe({ whitelist: true }))
@ -109,11 +109,11 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
@ConnectedSocket()
client: Socket,
@MessageBody() gameCreationDto: GameCreationDtoValidated
): { event: string; data: boolean } {
): { event: string, data: boolean } {
const realGameCreationDto: GameCreationDtoValidated = plainToClass(
GameCreationDtoValidated,
gameCreationDto
);
)
if (this.socketToPlayerName.size >= 2) {
const player1Socket: Socket | undefined = Array.from(
@ -122,14 +122,14 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
(key) =>
this.socketToPlayerName.get(key) ===
realGameCreationDto.playerNames[0]
);
)
const player2Socket: Socket | undefined = Array.from(
this.socketToPlayerName.keys()
).find(
(key) =>
this.socketToPlayerName.get(key) ===
realGameCreationDto.playerNames[1]
);
)
if (
player1Socket !== undefined &&
@ -137,34 +137,34 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
(client.id === player1Socket.id || client.id === player2Socket.id) &&
player1Socket.id !== player2Socket.id
) {
this.matchmakingQueue.removePlayer(realGameCreationDto.playerNames[0]);
this.matchmakingQueue.removePlayer(realGameCreationDto.playerNames[1]);
this.matchmakingQueue.removePlayer(realGameCreationDto.playerNames[0])
this.matchmakingQueue.removePlayer(realGameCreationDto.playerNames[1])
const ranked = false;
const ranked = false
this.games.newGame(
[player1Socket, player2Socket],
[player1Socket.id, player2Socket.id],
realGameCreationDto,
ranked
);
return { event: GAME_EVENTS.CREATE_GAME, data: true };
)
return { event: GAME_EVENTS.CREATE_GAME, data: true }
}
}
return { event: GAME_EVENTS.CREATE_GAME, data: false };
return { event: GAME_EVENTS.CREATE_GAME, data: false }
}
@SubscribeMessage(GAME_EVENTS.READY)
ready (
@ConnectedSocket()
client: Socket
): { event: string; data: boolean } {
let succeeded: boolean = false;
const name: string | undefined = this.socketToPlayerName.get(client);
): { event: string, data: boolean } {
let succeeded: boolean = false
const name: string | undefined = this.socketToPlayerName.get(client)
if (name !== undefined) {
this.games.ready(name);
succeeded = true;
this.games.ready(name)
succeeded = true
}
return { event: GAME_EVENTS.READY, data: succeeded };
return { event: GAME_EVENTS.READY, data: succeeded }
}
@UsePipes(new ValidationPipe({ whitelist: true }))
@ -173,21 +173,21 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
@ConnectedSocket()
client: Socket,
@MessageBody() matchmakingUpdateData: MatchmakingDtoValidated
): { event: string; data: MatchmakingDtoValidated } {
let matchmaking: boolean = false;
const name: string | undefined = this.socketToPlayerName.get(client);
): { event: string, data: MatchmakingDtoValidated } {
let matchmaking: boolean = false
const name: string | undefined = this.socketToPlayerName.get(client)
if (name !== undefined) {
if (matchmakingUpdateData.matchmaking && !this.games.isInAGame(name)) {
this.matchmakingQueue.addPlayer(name, client, client.id);
this.matchmakingQueue.addPlayer(name, client, client.id)
} else {
this.matchmakingQueue.removePlayer(name);
this.matchmakingQueue.removePlayer(name)
}
matchmaking = this.matchmakingQueue.isInQueue(name);
matchmaking = this.matchmakingQueue.isInQueue(name)
}
return {
event: GAME_EVENTS.MATCHMAKING,
data: { matchmaking },
};
data: { matchmaking }
}
}
@UsePipes(new ValidationPipe({ whitelist: true }))
@ -196,9 +196,9 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
@ConnectedSocket()
client: Socket
): void {
const name: string | undefined = this.socketToPlayerName.get(client);
const name: string | undefined = this.socketToPlayerName.get(client)
if (name !== undefined) {
void this.games.leaveGame(name);
void this.games.leaveGame(name)
}
}
}

16
back/volume/src/pong/pong.module.ts

@ -1,15 +1,15 @@
import { forwardRef, Module } from "@nestjs/common";
import { PongGateway } from "./pong.gateway";
import Result from "./entity/result.entity";
import { TypeOrmModule } from "@nestjs/typeorm";
import { PongService } from "./pong.service";
import { UsersModule } from "src/users/users.module";
import { PongController } from "./pong.controller";
import { forwardRef, Module } from '@nestjs/common'
import { PongGateway } from './pong.gateway'
import Result from './entity/result.entity'
import { TypeOrmModule } from '@nestjs/typeorm'
import { PongService } from './pong.service'
import { UsersModule } from 'src/users/users.module'
import { PongController } from './pong.controller'
@Module({
imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])],
providers: [PongGateway, PongService],
controllers: [PongController],
exports: [PongService],
exports: [PongService]
})
export class PongModule {}

84
back/volume/src/pong/pong.service.ts

@ -1,11 +1,11 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { UsersService } from "src/users/users.service";
import Result from "./entity/result.entity";
import type User from "src/users/entity/user.entity";
import { type Player } from "./game/Player";
import { type PaginateQuery, paginate, type Paginated } from "nestjs-paginate";
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { UsersService } from 'src/users/users.service'
import Result from './entity/result.entity'
import type User from 'src/users/entity/user.entity'
import { type Player } from './game/Player'
import { type PaginateQuery, paginate, type Paginated } from 'nestjs-paginate'
@Injectable()
export class PongService {
@ -21,10 +21,10 @@ export class PongService {
result: Result,
maxScore: number
): Promise<void> {
player.matchs++;
if (result.score[i] === maxScore) player.wins++;
else player.looses++;
player.winrate = (100 * player.wins) / player.matchs;
player.matchs++
if (result.score[i] === maxScore) player.wins++
else player.looses++
player.winrate = (100 * player.wins) / player.matchs
}
async updatePlayer (
@ -32,18 +32,18 @@ export class PongService {
result: Result,
maxScore: number
): Promise<void> {
const player: User | null = result.players[i];
if (player == null) return;
if (result.ranked) await this.updateStats(player, i, result, maxScore);
player.results.push(result);
player.status = "online";
await this.usersService.save(player);
const player: User | null = result.players[i]
if (player == null) return
if (result.ranked) await this.updateStats(player, i, result, maxScore)
player.results.push(result)
player.status = 'online'
await this.usersService.save(player)
}
async setInGame (playerName: string): Promise<void> {
const player = await this.usersService.findUserByName(playerName);
player.status = "in-game";
await this.usersService.save(player);
const player = await this.usersService.findUserByName(playerName)
player.status = 'in-game'
await this.usersService.save(player)
}
async saveResult (
@ -51,39 +51,39 @@ export class PongService {
ranked: boolean,
maxScore: number
): Promise<void> {
const result = new Result();
const ply = new Array<User | null>();
ply.push(await this.usersService.findUserByName(players[0].name));
ply.push(await this.usersService.findUserByName(players[1].name));
result.ranked = ranked;
result.players = ply;
result.score = [players[0].score, players[1].score];
await this.resultsRepository.save(result);
await this.updatePlayer(0, result, maxScore);
await this.updatePlayer(1, result, maxScore);
const result = new Result()
const ply = new Array<User | null>()
ply.push(await this.usersService.findUserByName(players[0].name))
ply.push(await this.usersService.findUserByName(players[1].name))
result.ranked = ranked
result.players = ply
result.score = [players[0].score, players[1].score]
await this.resultsRepository.save(result)
await this.updatePlayer(0, result, maxScore)
await this.updatePlayer(1, result, maxScore)
}
async getHistory (
query: PaginateQuery,
ftId: number
): Promise<Paginated<Result>> {
let queryBuilder;
let queryBuilder
if (ftId !== 0) {
queryBuilder = this.resultsRepository
.createQueryBuilder("result")
.innerJoin("result.players", "player", "player.ftId = :ftId", { ftId });
.createQueryBuilder('result')
.innerJoin('result.players', 'player', 'player.ftId = :ftId', { ftId })
} else {
queryBuilder = this.resultsRepository
.createQueryBuilder("result")
.where("result.ranked = :ranked", { ranked: true });
.createQueryBuilder('result')
.where('result.ranked = :ranked', { ranked: true })
}
return await paginate(query, queryBuilder, {
nullSort: "last",
relations: ["players"],
defaultSortBy: [["date", "DESC"]],
sortableColumns: ["date"],
maxLimit: 10,
});
nullSort: 'last',
relations: ['players'],
defaultSortBy: [['date', 'DESC']],
sortableColumns: ['date'],
maxLimit: 10
})
}
}

24
back/volume/src/pong/pong.spec.ts

@ -1,18 +1,18 @@
import { Test, type TestingModule } from "@nestjs/testing";
import { Games } from "./game/Games";
import { Test, type TestingModule } from '@nestjs/testing'
import { Games } from './game/Games'
describe("Pong", () => {
let provider: Games;
describe('Pong', () => {
let provider: Games
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [Games],
}).compile();
providers: [Games]
}).compile()
provider = module.get<Games>(Games);
});
provider = module.get<Games>(Games)
})
it("should be defined", () => {
expect(provider).toBeDefined();
});
});
it('should be defined', () => {
expect(provider).toBeDefined()
})
})

10
back/volume/src/types.d.ts

@ -1,8 +1,8 @@
declare module "passport-42" {
export type Profile = any;
export type VerifyCallback = any;
declare module 'passport-42' {
export type Profile = any
export type VerifyCallback = any
export class Strategy {
constructor(options: any, verify: any);
authenticate(req: any, options: any): any;
constructor (options: any, verify: any)
authenticate (req: any, options: any): any
}
}

22
back/volume/src/users/dto/user.dto.ts

@ -1,31 +1,31 @@
import { IsString, IsNotEmpty, IsPositive, IsOptional } from "class-validator";
import { IsString, IsNotEmpty, IsPositive, IsOptional } from 'class-validator'
import { ApiProperty } from "@nestjs/swagger";
import { Express } from "express";
import { ApiProperty } from '@nestjs/swagger'
import { Express } from 'express'
export class UserDto {
@IsPositive()
@IsOptional()
readonly ftId: number;
readonly ftId: number
@IsString()
@IsNotEmpty()
readonly username: string;
readonly username: string
@IsOptional()
readonly status: string;
readonly status: string
@IsOptional()
readonly avatar: string;
readonly avatar: string
@IsOptional()
readonly authToken: string;
readonly authToken: string
@IsOptional()
readonly isVerified: boolean;
readonly isVerified: boolean
}
export class AvatarUploadDto {
@ApiProperty({ type: "string", format: "binary" })
file: Express.Multer.File;
@ApiProperty({ type: 'string', format: 'binary' })
file: Express.Multer.File
}

62
back/volume/src/users/entity/user.entity.ts

@ -4,78 +4,78 @@ import {
Column,
OneToMany,
ManyToMany,
JoinTable,
} from "typeorm";
JoinTable
} from 'typeorm'
import Message from "src/chat/entity/message.entity";
import Channel from "src/chat/entity/channel.entity";
import Result from "src/pong/entity/result.entity";
import Message from 'src/chat/entity/message.entity'
import Channel from 'src/chat/entity/channel.entity'
import Result from 'src/pong/entity/result.entity'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
id: number
@Column({ type: "bigint", default: Date.now() })
lastAccess: number;
@Column({ type: 'bigint', default: Date.now() })
lastAccess: number
@Column({ unique: true })
ftId: number;
ftId: number
@Column({ unique: true, nullable: true })
email: string;
email: string
@Column({ select: false, nullable: true })
authToken: string;
authToken: string
@Column({ default: false })
twoFA: boolean;
twoFA: boolean
@Column({ default: false, nullable: true })
isVerified: boolean;
isVerified: boolean
@Column("uuid", { unique: true })
socketKey: string;
@Column('uuid', { unique: true })
socketKey: string
@Column({ unique: true })
username: string;
username: string
@Column({ default: "online" })
status: string;
@Column({ default: 'online' })
status: string
@Column({ name: "avatar" })
avatar: string;
@Column({ name: 'avatar' })
avatar: string
@Column({ default: 0 })
wins: number;
wins: number
@Column({ default: 0 })
looses: number;
looses: number
@Column({ default: 0 })
matchs: number;
matchs: number
@Column({ default: 0 })
rank: number;
rank: number
@Column({ default: 0, type: "double precision" })
winrate: number;
@Column({ default: 0, type: 'double precision' })
winrate: number
@ManyToMany(() => Result, (result: Result) => result.players)
@JoinTable()
results: Result[];
results: Result[]
@ManyToMany(() => User)
@JoinTable()
blocked: User[];
blocked: User[]
@ManyToMany(() => User)
@JoinTable()
followers: User[];
followers: User[]
@ManyToMany(() => User)
@JoinTable()
friends: User[];
friends: User[]
}
export default User;
export default User

172
back/volume/src/users/users.controller.ts

@ -11,46 +11,48 @@ import {
Res,
StreamableFile,
BadRequestException,
Redirect,
} from "@nestjs/common";
Redirect
} from '@nestjs/common'
import { FileInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer";
import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer'
import { type User } from "./entity/user.entity";
import { UsersService } from "./users.service";
import { UserDto, AvatarUploadDto } from "./dto/user.dto";
import { PongService } from "src/pong/pong.service";
import { type User } from './entity/user.entity'
import { UsersService } from './users.service'
import { UserDto, AvatarUploadDto } from './dto/user.dto'
import { AuthenticatedGuard } from "src/auth/42-auth.guard";
import { Profile42 } from "src/auth/42.decorator";
import { Profile } from "passport-42";
import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import { Profile42 } from 'src/auth/42.decorator'
import { Profile } from 'passport-42'
import { ApiBody, ApiConsumes } from "@nestjs/swagger";
import { type Request, Response } from "express";
import { createReadStream } from "fs";
import { join } from "path";
import { ApiBody, ApiConsumes } from '@nestjs/swagger'
import { type Request, Response } from 'express'
import { createReadStream } from 'fs'
import { join } from 'path'
@Controller("users")
@Controller('users')
export class UsersController {
constructor (private readonly usersService: UsersService) {}
@Post('block/:id')
@UseGuards(AuthenticatedGuard)
@Post("block/:id")
async blockUser(@Profile42() profile :Profile, @Param('id') id:number) {
const user = await this.usersService.findUser(id) as User
async blockUser (
@Profile42() profile: Profile,
@Param('id', ParseIntPipe) id: number
) {
const user = (await this.usersService.findUser(id)) as User
user.blocked.push((await this.usersService.findUser(+profile.id)) as User)
this.usersService.save(user);
this.usersService.save(user)
}
@Post('unblock/:id')
@UseGuards(AuthenticatedGuard)
@Post("unblock/:id")
async unblockUser(@Profile42() profile :Profile, @Param('id') id:number) {
const user = await this.usersService.findUser(id) as User
async unblockUser (@Param('id', ParseIntPipe) id: number) {
const user = (await this.usersService.findUser(id)) as User
user.blocked = user.blocked.filter((usr: User) => {
return usr.id !== id
})
this.usersService.save(user);
this.usersService.save(user)
}
@Get('all')
@ -58,141 +60,135 @@ export class UsersController {
return await this.usersService.findUsers()
}
@Get("online")
@Get('online')
async getOnlineUsers (): Promise<User[]> {
return await this.usersService.findOnlineUsers();
return await this.usersService.findOnlineUsers()
}
@Get("friends")
@Get('friends')
@UseGuards(AuthenticatedGuard)
async getFriends (@Profile42() profile: Profile): Promise<User[]> {
return await this.usersService.getFriends(profile.id);
return await this.usersService.getFriends(profile.id)
}
@Get("invits")
@Get('invits')
@UseGuards(AuthenticatedGuard)
async getInvits (@Profile42() profile: Profile): Promise<User[]> {
return await this.usersService.getInvits(profile.id);
return await this.usersService.getInvits(profile.id)
}
@Get("leaderboard")
@Get('leaderboard')
@UseGuards(AuthenticatedGuard)
async getLeaderboard (): Promise<User[]> {
return await this.usersService.getLeaderboard();
return await this.usersService.getLeaderboard()
}
@Post("avatar")
@Post('avatar')
@UseGuards(AuthenticatedGuard)
@Redirect("http://localhost")
@Redirect('http://localhost')
@UseInterceptors(
FileInterceptor("avatar", {
FileInterceptor('avatar', {
storage: diskStorage({
destination: "avatars/",
destination: 'avatars/'
}),
fileFilter: (request: Request, file: Express.Multer.File, callback) => {
<<<<<<< HEAD
if (!file.mimetype.includes('image')) {
callback(null, false)
return
=======
if (!file.mimetype.includes("image")) {
callback(null, false);
return;
>>>>>>> ouai c un peu la merde mais bon
}
callback(null, true);
},
}
callback(null, true)
}
})
)
@ApiConsumes("multipart/form-data")
@ApiConsumes('multipart/form-data')
@ApiBody({
description: "A new avatar for the user",
type: AvatarUploadDto,
description: 'A new avatar for the user',
type: AvatarUploadDto
})
async changeAvatar (
@Profile42() profile: Profile,
@UploadedFile() file: Express.Multer.File
): Promise<void> {
if (file === undefined) return;
await this.usersService.addAvatar(profile.id, file.filename);
if (file === undefined) return
await this.usersService.addAvatar(profile.id, file.filename)
}
@Get("avatar")
@Get('avatar')
@UseGuards(AuthenticatedGuard)
async getAvatar (
@Profile42() profile: Profile,
@Res({ passthrough: true }) response: Response
): Promise<StreamableFile> {
return await this.getAvatarById(profile.id, response);
return await this.getAvatarById(profile.id, response)
}
@Get(":name/byname")
async getUserByName(@Param("name") username: string): Promise<User> {
const user = await this.usersService.findUserByName(username);
user.socketKey = "";
return user;
@Get(':name/byname')
async getUserByName (@Param('name') username: string): Promise<User> {
const user = await this.usersService.findUserByName(username)
user.socketKey = ''
return user
}
@Get("invit/:username")
@Get('invit/:username')
@UseGuards(AuthenticatedGuard)
async invitUser (
@Profile42() profile: Profile,
@Param("username") username: string
@Param('username') username: string
): Promise<void> {
const target: User | null = await this.usersService.findUserByName(
username
);
)
if (target === null) {
throw new BadRequestException(`User ${username} not found.`);
throw new BadRequestException(`User ${username} not found.`)
}
if (+profile.id === +target.ftId) {
throw new BadRequestException("You can't invite yourself.");
throw new BadRequestException("You can't invite yourself.")
}
const ret: string = await this.usersService.invit(profile.id, target.ftId);
if (ret !== "OK") throw new BadRequestException(ret);
const ret: string = await this.usersService.invit(profile.id, target.ftId)
if (ret !== 'OK') throw new BadRequestException(ret)
}
@Get(":id/avatar")
@Get(':id/avatar')
async getAvatarById (
@Param("id", ParseIntPipe) ftId: number,
@Param('id', ParseIntPipe) ftId: number,
@Res({ passthrough: true }) response: Response
): Promise<StreamableFile> {
const user: User | null = await this.usersService.findUser(ftId);
if (user === null) throw new BadRequestException("User unknown.");
const filename = user.avatar;
const stream = createReadStream(join(process.cwd(), "avatars/" + filename));
const user: User | null = await this.usersService.findUser(ftId)
if (user === null) throw new BadRequestException('User unknown.')
const filename = user.avatar
const stream = createReadStream(join(process.cwd(), 'avatars/' + filename))
response.set({
"Content-Diposition": `inline; filename='${filename}'`,
"Content-Type": "image/jpg",
});
return new StreamableFile(stream);
'Content-Diposition': `inline; filename='${filename}'`,
'Content-Type': 'image/jpg'
})
return new StreamableFile(stream)
}
@Get(":id")
@Get(':id')
async getUserById (
@Param("id", ParseIntPipe) ftId: number
@Param('id', ParseIntPipe) ftId: number
): Promise<User | null> {
const user = await this.usersService.findUser(ftId);
if (user == null) throw new BadRequestException("User unknown.");
user.socketKey = "";
return user;
const user = await this.usersService.findUser(ftId)
if (user == null) throw new BadRequestException('User unknown.')
user.socketKey = ''
return user
}
@Post(":id")
@Post(':id')
@UseGuards(AuthenticatedGuard)
async createById (@Body() payload: UserDto): Promise<void> {
const user = await this.usersService.findUser(payload.ftId);
const user = await this.usersService.findUser(payload.ftId)
if (user != null) {
await this.usersService.update(user, payload);
await this.usersService.update(user, payload)
} else {
await this.usersService.create(payload);
await this.usersService.create(payload)
}
}
@Get()
@UseGuards(AuthenticatedGuard)
async getUser (@Profile42() profile: Profile): Promise<User | null> {
return await this.usersService.findUser(profile.id);
return await this.usersService.findUser(profile.id)
}
@Post()
@ -201,8 +197,8 @@ export class UsersController {
@Body() payload: UserDto,
@Profile42() profile: Profile
): Promise<BadRequestException | User | null> {
const user = await this.usersService.findUser(profile.id);
if (user == null) throw new BadRequestException("User not found.");
return await this.usersService.update(user, payload);
const user = await this.usersService.findUser(profile.id)
if (user == null) throw new BadRequestException('User not found.')
return await this.usersService.update(user, payload)
}
}

16
back/volume/src/users/users.module.ts

@ -1,15 +1,15 @@
import { forwardRef, Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entity/user.entity";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
import { PongModule } from "src/pong/pong.module";
import { ChatModule } from "src/chat/chat.module";
import { forwardRef, Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from './entity/user.entity'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
import { PongModule } from 'src/pong/pong.module'
import { ChatModule } from 'src/chat/chat.module'
@Module({
imports: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
exports: [UsersService]
})
export class UsersModule {}

176
back/volume/src/users/users.service.ts

@ -1,12 +1,12 @@
import { BadRequestException, Catch, Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { EntityNotFoundError, QueryFailedError, Repository } from "typeorm";
import { Cron } from "@nestjs/schedule";
import { randomUUID } from "crypto";
import { BadRequestException, Catch, Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { EntityNotFoundError, QueryFailedError, Repository } from 'typeorm'
import { Cron } from '@nestjs/schedule'
import { randomUUID } from 'crypto'
import { type UserDto } from "./dto/user.dto";
import type Channel from "src/chat/entity/channel.entity";
import User from "./entity/user.entity";
import { type UserDto } from './dto/user.dto'
import type Channel from 'src/chat/entity/channel.entity'
import User from './entity/user.entity'
@Injectable()
@Catch(QueryFailedError, EntityNotFoundError)
@ -16,119 +16,119 @@ export class UsersService {
) {}
async save (user: User): Promise<void> {
await this.usersRepository.save(user);
await this.usersRepository.save(user)
}
async findUsers (): Promise<User[]> {
const users = await this.usersRepository.find({});
users.forEach((usr) => (usr.socketKey = ""));
return users;
const users = await this.usersRepository.find({})
users.forEach((usr) => (usr.socketKey = ''))
return users
}
async findUserByName (username: string): Promise<User> {
const user = await this.usersRepository.findOne({
where: { username },
relations: { results: true },
});
if (user == null) throw new BadRequestException("User not found.");
return user;
relations: { results: true }
})
if (user == null) throw new BadRequestException('User not found.')
return user
}
@Cron("0 * * * * *")
@Cron('0 * * * * *')
async updateStatus (): Promise<void> {
const users = await this.usersRepository.find({});
const users = await this.usersRepository.find({})
users.forEach((usr) => {
if (Date.now() - usr.lastAccess > 60000) {
usr.isVerified = false;
usr.status = "offline";
usr.isVerified = false
usr.status = 'offline'
this.usersRepository.save(usr).catch((err) => {
console.log(err);
});
console.log(err)
})
}
});
this.getLeaderboard();
})
this.getLeaderboard()
}
async findUser (ftId: number): Promise<User | null> {
const user = await this.usersRepository.findOneBy({ ftId });
if (user == null) return null;
user.lastAccess = Date.now();
if (user.status === "offline") user.status = "online";
await this.usersRepository.save(user);
return user;
const user = await this.usersRepository.findOneBy({ ftId })
if (user == null) return null
user.lastAccess = Date.now()
if (user.status === 'offline') user.status = 'online'
await this.usersRepository.save(user)
return user
}
async findOnlineUsers (): Promise<User[]> {
const users = await this.usersRepository.find({
where: { status: "online" },
});
users.forEach((usr) => (usr.socketKey = ""));
return users;
where: { status: 'online' }
})
users.forEach((usr) => (usr.socketKey = ''))
return users
}
async create (userData: UserDto): Promise<User | null> {
try {
const newUser = this.usersRepository.create(userData);
newUser.socketKey = randomUUID();
return await this.usersRepository.save(newUser);
const newUser = this.usersRepository.create(userData)
newUser.socketKey = randomUUID()
return await this.usersRepository.save(newUser)
} catch (err) {
throw new BadRequestException("User already exists.");
throw new BadRequestException('User already exists.')
}
}
async findOnlineInChannel (channel: Channel): Promise<User[]> {
return await this.usersRepository
.createQueryBuilder("user")
.where("user.channel = :chan", { chan: channel })
.andWhere("user.status := status)", { status: "online" })
.getMany();
.createQueryBuilder('user')
.where('user.channel = :chan', { chan: channel })
.andWhere('user.status := status)', { status: 'online' })
.getMany()
}
async update (user: User, changes: UserDto): Promise<User | null> {
this.usersRepository.merge(user, changes);
return await this.usersRepository.save(user);
this.usersRepository.merge(user, changes)
return await this.usersRepository.save(user)
}
async addAvatar (ftId: number, filename: string): Promise<void> {
await this.usersRepository.update({ ftId }, { avatar: filename });
await this.usersRepository.update({ ftId }, { avatar: filename })
}
async getFriends (ftId: number): Promise<User[]> {
const user = await this.usersRepository.findOne({
where: { ftId },
relations: { friends: true },
});
if (user == null) throw new BadRequestException("User not found.");
user.friends.forEach((friend) => (friend.socketKey = ""));
return user.friends;
relations: { friends: true }
})
if (user == null) throw new BadRequestException('User not found.')
user.friends.forEach((friend) => (friend.socketKey = ''))
return user.friends
}
async getInvits (ftId: number): Promise<User[]> {
const user = await this.usersRepository.findOne({
where: { ftId },
relations: {
followers: true,
},
});
if (user == null) throw new BadRequestException("User not found.");
user.followers.forEach((follower) => (follower.socketKey = ""));
return user.followers;
followers: true
}
})
if (user == null) throw new BadRequestException('User not found.')
user.followers.forEach((follower) => (follower.socketKey = ''))
return user.followers
}
async getLeaderboard (): Promise<User[]> {
const leaderboard = await this.usersRepository.find({
order: {
winrate: "ASC",
},
});
const ret = leaderboard.filter((user) => user.matchs !== 0);
let r = 0;
winrate: 'ASC'
}
})
const ret = leaderboard.filter((user) => user.matchs !== 0)
let r = 0
ret.forEach((usr) => {
usr.rank = r++;
this.usersRepository.save(usr);
usr.socketKey = "";
});
return ret;
usr.rank = r++
this.usersRepository.save(usr)
usr.socketKey = ''
})
return ret
}
async invit (ftId: number, targetFtId: number): Promise<string> {
@ -136,49 +136,49 @@ export class UsersService {
where: { ftId },
relations: {
followers: true,
friends: true,
},
});
if (user === null) throw new BadRequestException("User not found.");
friends: true
}
})
if (user === null) throw new BadRequestException('User not found.')
if (user.friends.findIndex((friend) => friend.ftId === targetFtId) !== -1) {
return "You are already friends.";
return 'You are already friends.'
}
const target: User | null = await this.usersRepository.findOne({
where: { ftId: targetFtId },
relations: {
followers: true,
friends: true,
},
});
if (target == null) return "Target not found.";
friends: true
}
})
if (target == null) return 'Target not found.'
const id = user.followers.findIndex(
(follower) => follower.ftId === targetFtId
);
)
if (
target.followers.findIndex((follower) => follower.ftId === user.ftId) !==
-1
) {
return "Invitation already sent.";
return 'Invitation already sent.'
} else if (
user.followers.findIndex((follower) => follower.ftId === targetFtId) !==
-1
) {
user.friends.push(target);
target.friends.push(user);
user.followers.splice(id, 1);
await this.usersRepository.save(user);
} else target.followers.push(user);
await this.usersRepository.save(target);
return "OK";
user.friends.push(target)
target.friends.push(user)
user.followers.splice(id, 1)
await this.usersRepository.save(user)
} else target.followers.push(user)
await this.usersRepository.save(target)
return 'OK'
}
async findByCode (code: string): Promise<User> {
const user = await this.usersRepository.findOneBy({ authToken: code });
if (user == null) throw new BadRequestException("User not found");
return user;
const user = await this.usersRepository.findOneBy({ authToken: code })
if (user == null) throw new BadRequestException('User not found')
return user
}
async turnOnTwoFactorAuthentication (ftId: number): Promise<void> {
await this.usersRepository.update({ ftId }, { twoFA: true });
await this.usersRepository.update({ ftId }, { twoFA: true })
}
}

94
front/volume/src/components/Chat.svelte

@ -6,14 +6,12 @@
}
import { createEventDispatcher, onDestroy, onMount } from "svelte";
import { store, API_URL } from "../Auth";
import { io } from "../socket"
import { io } from "../socket";
import type { ChannelsType } from "./Channels.svelte";
import type { User } from "./Profile.svelte";
import type User from "./Profile.svelte";
</script>
<script lang="ts">
//--------------------------------------------------------------------------------/
let blockedUsers: Array<User> = [];
let chatMembers: Array<User> = [];
let chatMessages: Array<chatMessagesType> = [];
@ -244,11 +242,9 @@ style = "cursor: pointer;"
>
{message.author}
</span>: {message.text}
{
/if}
{/if}
</p>
{
/each}
{/each}
</div>
{#if showProfileMenu}
<div
@ -258,40 +254,40 @@ style = "cursor: pointer;"
>
<ul>
<li>
<button on: click = {() => dispatch("send-message", selectedUser)
}
> Send Message < /button
>
<button on:click={() => dispatch("send-message", selectedUser)}>
Send Message
</button>
</li>
<li>
<button on: click = {() => dispatch("view-profile", selectedUser)
}
> View Profile < /button
>
<button on:click={() => dispatch("view-profile", selectedUser)}>
View Profile
</button>
</li>
<li>
<button on: click = {() => dispatch("add-friend", selectedUser)}
> Add Friend < /button
>
<button on:click={() => dispatch("add-friend", selectedUser)}>
Add Friend
</button>
</li>
<li>
<button on: click = {() => dispatch("invite-to-game", selectedUser)}
> Invite to Game < /button
>
<button on:click={() => dispatch("invite-to-game", selectedUser)}>
Invite to Game
</button>
</li>
<li>
{ #if!blockedUsers.filter((user) => user.username = selectedUser).length }
<button on: click = {() => blockUser(selectedUser)}> Block User < /button>
{#if !blockedUsers.filter((user) => (user.username = selectedUser)).length}
<button on:click={() => blockUser(selectedUser)}>
Block User
</button>
{:else}
<button on: click = {() => unblockUser(selectedUser)}> Unblock User < /button>
{
/if}
<button on:click={() => unblockUser(selectedUser)}>
Unblock User
</button>
{/if}
</li>
<li><button on:click={closeProfileMenu}> Close </button></li>
</ul>
</div>
{
/if}
{/if}
<form on:submit|preventDefault={sendMessage}>
<input type="text" placeholder="Type a message..." bind:value={newText} />
<button>
@ -300,8 +296,10 @@ style = "cursor: pointer;"
</form>
<button
on:click|stopPropagation={toggleChatMembers}
on: keydown | stopPropagation > Chat Members < /button
on:keydown|stopPropagation
>
Chat Members
</button>
{#if showChatMembers}
<div
class="chatMembers"
@ -314,33 +312,31 @@ style = "cursor: pointer;"
<li>
<p>
{member.username}
< button on: click = {() => banUser(member.username)
}> ban < /button>
< button on: click = {() => kickUser(member.username)
}
> kick < /button
>
<button on: click = {() => muteUser(member.username)}
> mute < /button
>
<button on: click = {() => adminUser(member.username)}
> promote < /button
>
<button on: click = {() => removeAdminUser(member.username)}
> demote < /button
>
<button on:click={() => banUser(member.username)}>
ban
</button>
<button on:click={() => kickUser(member.username)}>
kick
</button>
<button on:click={() => muteUser(member.username)}>
mute
</button>
<button on:click={() => adminUser(member.username)}>
promote
</button>
<button on:click={() => removeAdminUser(member.username)}>
demote
</button>
</p>
<p>
-----------------------------------------------------------------------------------
</p>
</li>
{
/each}
{/each}
</ul>
</div>
</div>
{
/if}
{/if}
</div>
</div>

Loading…
Cancel
Save