Walid Bekkal
2 years ago
63 changed files with 1850 additions and 1737 deletions
@ -1,25 +0,0 @@ |
|||||
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" |
|
@ -1,13 +1,13 @@ |
|||||
module.exports = { |
module.exports = { |
||||
env: { |
env: { |
||||
browser: true, |
browser: true, |
||||
es2021: true |
es2021: true, |
||||
}, |
}, |
||||
extends: 'standard-with-typescript', |
extends: "standard-with-typescript", |
||||
parserOptions: { |
parserOptions: { |
||||
ecmaVersion: 'latest', |
ecmaVersion: "latest", |
||||
sourceType: 'module', |
sourceType: "module", |
||||
project: ['./tsconfig.json'], |
project: ["./tsconfig.json"], |
||||
tsconfigRootDir: __dirname |
tsconfigRootDir: __dirname, |
||||
} |
}, |
||||
} |
}; |
||||
|
@ -1,25 +1,25 @@ |
|||||
import { |
import { |
||||
type ExecutionContext, |
type ExecutionContext, |
||||
Injectable, |
Injectable, |
||||
type CanActivate |
type CanActivate, |
||||
} from '@nestjs/common' |
} from "@nestjs/common"; |
||||
import { AuthGuard } from '@nestjs/passport' |
import { AuthGuard } from "@nestjs/passport"; |
||||
import { type Request } from 'express' |
import { type Request } from "express"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class FtOauthGuard extends AuthGuard('42') { |
export class FtOauthGuard extends AuthGuard("42") { |
||||
async canActivate (context: ExecutionContext): Promise<boolean> { |
async canActivate(context: ExecutionContext): Promise<boolean> { |
||||
const activate: boolean = (await super.canActivate(context)) as boolean |
const activate: boolean = (await super.canActivate(context)) as boolean; |
||||
const request: Request = context.switchToHttp().getRequest() |
const request: Request = context.switchToHttp().getRequest(); |
||||
await super.logIn(request) |
await super.logIn(request); |
||||
return activate |
return activate; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class AuthenticatedGuard implements CanActivate { |
export class AuthenticatedGuard implements CanActivate { |
||||
async canActivate (context: ExecutionContext): Promise<boolean> { |
async canActivate(context: ExecutionContext): Promise<boolean> { |
||||
const req: Request = context.switchToHttp().getRequest() |
const req: Request = context.switchToHttp().getRequest(); |
||||
return req.isAuthenticated() |
return req.isAuthenticated(); |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,9 +1,9 @@ |
|||||
import { createParamDecorator, type ExecutionContext } from '@nestjs/common' |
import { createParamDecorator, type ExecutionContext } from "@nestjs/common"; |
||||
import { type Profile } from 'passport-42' |
import { type Profile } from "passport-42"; |
||||
|
|
||||
export const Profile42 = createParamDecorator( |
export const Profile42 = createParamDecorator( |
||||
(data: unknown, ctx: ExecutionContext): Profile => { |
(data: unknown, ctx: ExecutionContext): Profile => { |
||||
const request = ctx.switchToHttp().getRequest() |
const request = ctx.switchToHttp().getRequest(); |
||||
return request.user |
return request.user; |
||||
} |
} |
||||
) |
); |
||||
|
@ -1,49 +1,49 @@ |
|||||
import { Injectable } from '@nestjs/common' |
import { Injectable } from "@nestjs/common"; |
||||
import { ConfigService } from '@nestjs/config' |
import { ConfigService } from "@nestjs/config"; |
||||
import { PassportStrategy } from '@nestjs/passport' |
import { PassportStrategy } from "@nestjs/passport"; |
||||
import { Strategy, type Profile, type VerifyCallback } from 'passport-42' |
import { Strategy, type Profile, type VerifyCallback } from "passport-42"; |
||||
import { get } from 'https' |
import { get } from "https"; |
||||
import { createWriteStream } from 'fs' |
import { createWriteStream } from "fs"; |
||||
|
|
||||
import { UsersService } from 'src/users/users.service' |
import { UsersService } from "src/users/users.service"; |
||||
import { User } from 'src/users/entity/user.entity' |
import { User } from "src/users/entity/user.entity"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class FtStrategy extends PassportStrategy(Strategy, '42') { |
export class FtStrategy extends PassportStrategy(Strategy, "42") { |
||||
constructor ( |
constructor( |
||||
private readonly configService: ConfigService, |
private readonly configService: ConfigService, |
||||
private readonly usersService: UsersService |
private readonly usersService: UsersService |
||||
) { |
) { |
||||
super({ |
super({ |
||||
clientID: configService.get<string>('FT_OAUTH_CLIENT_ID'), |
clientID: configService.get<string>("FT_OAUTH_CLIENT_ID"), |
||||
clientSecret: configService.get<string>('FT_OAUTH_CLIENT_SECRET'), |
clientSecret: configService.get<string>("FT_OAUTH_CLIENT_SECRET"), |
||||
callbackURL: configService.get<string>('FT_OAUTH_CALLBACK_URL'), |
callbackURL: configService.get<string>("FT_OAUTH_CALLBACK_URL"), |
||||
passReqToCallback: true |
passReqToCallback: true, |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
async validate ( |
async validate( |
||||
request: { session: { accessToken: string } }, |
request: { session: { accessToken: string } }, |
||||
accessToken: string, |
accessToken: string, |
||||
refreshToken: string, |
refreshToken: string, |
||||
profile: Profile, |
profile: Profile, |
||||
cb: VerifyCallback |
cb: VerifyCallback |
||||
): Promise<VerifyCallback> { |
): Promise<VerifyCallback> { |
||||
request.session.accessToken = accessToken |
request.session.accessToken = accessToken; |
||||
const ftId = profile.id as number |
const ftId = profile.id as number; |
||||
console.log('Validated ', profile.username) |
console.log("Validated ", profile.username); |
||||
if ((await this.usersService.findUser(ftId)) === null) { |
if ((await this.usersService.findUser(ftId)) === null) { |
||||
const newUser = new User() |
const newUser = new User(); |
||||
newUser.ftId = profile.id as number |
newUser.ftId = profile.id as number; |
||||
newUser.username = profile.username |
newUser.username = profile.username; |
||||
newUser.avatar = `${ftId}.jpg` |
newUser.avatar = `${ftId}.jpg`; |
||||
newUser.email = profile.emails[0].value |
newUser.email = profile.emails[0].value; |
||||
void this.usersService.create(newUser) |
void this.usersService.create(newUser); |
||||
const file = createWriteStream(`avatars/${ftId}.jpg`) |
const file = createWriteStream(`avatars/${ftId}.jpg`); |
||||
get(profile._json.image.versions.small, function (response) { |
get(profile._json.image.versions.small, function (response) { |
||||
response.pipe(file) |
response.pipe(file); |
||||
}) |
}); |
||||
} |
} |
||||
return cb(null, profile) |
return cb(null, profile); |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,48 +1,48 @@ |
|||||
import { Injectable } from '@nestjs/common' |
import { Injectable } from "@nestjs/common"; |
||||
import { type User } from 'src/users/entity/user.entity' |
import { type User } from "src/users/entity/user.entity"; |
||||
import { UsersService } from 'src/users/users.service' |
import { UsersService } from "src/users/users.service"; |
||||
import { MailerService } from '@nestjs-modules/mailer' |
import { MailerService } from "@nestjs-modules/mailer"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class AuthService { |
export class AuthService { |
||||
constructor ( |
constructor( |
||||
private readonly usersService: UsersService, |
private readonly usersService: UsersService, |
||||
private readonly mailerService: MailerService |
private readonly mailerService: MailerService |
||||
) {} |
) {} |
||||
|
|
||||
async sendConfirmedEmail (user: User): Promise<void> { |
async sendConfirmedEmail(user: User): Promise<void> { |
||||
const { email, username } = user |
const { email, username } = user; |
||||
await this.mailerService.sendMail({ |
await this.mailerService.sendMail({ |
||||
to: email, |
to: email, |
||||
subject: 'Welcome to ft_transcendence! Email Confirmed', |
subject: "Welcome to ft_transcendence! Email Confirmed", |
||||
template: 'confirmed', |
template: "confirmed", |
||||
context: { |
context: { |
||||
username, |
username, |
||||
email |
email, |
||||
} |
}, |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
async sendConfirmationEmail (user: User): Promise<void> { |
async sendConfirmationEmail(user: User): Promise<void> { |
||||
user.authToken = Math.floor(10000 + Math.random() * 90000).toString() |
user.authToken = Math.floor(10000 + Math.random() * 90000).toString(); |
||||
await this.usersService.save(user) |
await this.usersService.save(user); |
||||
await this.mailerService.sendMail({ |
await this.mailerService.sendMail({ |
||||
to: user.email, |
to: user.email, |
||||
subject: 'Welcome to ft_transcendence! Confirm Email', |
subject: "Welcome to ft_transcendence! Confirm Email", |
||||
template: 'confirm', |
template: "confirm", |
||||
context: { |
context: { |
||||
username: user.username, |
username: user.username, |
||||
code: user.authToken |
code: user.authToken, |
||||
} |
}, |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
async verifyAccount (code: string): Promise<boolean> { |
async verifyAccount(code: string): Promise<boolean> { |
||||
const user = await this.usersService.findByCode(code) |
const user = await this.usersService.findByCode(code); |
||||
user.authToken = '' |
user.authToken = ""; |
||||
user.isVerified = true |
user.isVerified = true; |
||||
await this.usersService.save(user) |
await this.usersService.save(user); |
||||
await this.sendConfirmedEmail(user) |
await this.sendConfirmedEmail(user); |
||||
return true |
return true; |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,20 +1,20 @@ |
|||||
import { Injectable } from '@nestjs/common' |
import { Injectable } from "@nestjs/common"; |
||||
import { PassportSerializer } from '@nestjs/passport' |
import { PassportSerializer } from "@nestjs/passport"; |
||||
import { type Profile } from 'passport-42' |
import { type Profile } from "passport-42"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class SessionSerializer extends PassportSerializer { |
export class SessionSerializer extends PassportSerializer { |
||||
serializeUser ( |
serializeUser( |
||||
user: Profile, |
user: Profile, |
||||
done: (err: Error | null, user: Profile) => void |
done: (err: Error | null, user: Profile) => void |
||||
): any { |
): any { |
||||
done(null, user) |
done(null, user); |
||||
} |
} |
||||
|
|
||||
deserializeUser ( |
deserializeUser( |
||||
payload: Profile, |
payload: Profile, |
||||
done: (err: Error | null, user: Profile) => void |
done: (err: Error | null, user: Profile) => void |
||||
): any { |
): any { |
||||
done(null, payload) |
done(null, payload); |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,151 +1,173 @@ |
|||||
import { Inject, Injectable, NotFoundException } from '@nestjs/common' |
import { Inject, Injectable, NotFoundException } from "@nestjs/common"; |
||||
import { InjectRepository } from '@nestjs/typeorm' |
import { InjectRepository } from "@nestjs/typeorm"; |
||||
import { Repository } from 'typeorm' |
import { Repository } from "typeorm"; |
||||
|
|
||||
import { type CreateChannelDto } from './dto/create-channel.dto' |
import { type CreateChannelDto } from "./dto/create-channel.dto"; |
||||
import { UsersService } from 'src/users/users.service' |
import { UsersService } from "src/users/users.service"; |
||||
|
|
||||
import type User from 'src/users/entity/user.entity' |
import type User from "src/users/entity/user.entity"; |
||||
import Channel from './entity/channel.entity' |
import Channel from "./entity/channel.entity"; |
||||
import { Cron } from '@nestjs/schedule' |
import { Cron } from "@nestjs/schedule"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class ChatService { |
export class ChatService { |
||||
constructor ( |
constructor( |
||||
@InjectRepository(Channel) |
@InjectRepository(Channel) |
||||
private readonly ChannelRepository: Repository<Channel>, |
private readonly ChannelRepository: Repository<Channel>, |
||||
private readonly usersService: UsersService |
private readonly usersService: UsersService |
||||
) {} |
) {} |
||||
|
|
||||
async createChannel (channel: CreateChannelDto): Promise<Channel> { |
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) |
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 |
const newChannel = new Channel(); |
||||
newChannel.users = [user] |
newChannel.owner = user; |
||||
newChannel.admins = [user] |
newChannel.users = [user]; |
||||
newChannel.name = channel.name |
newChannel.admins = [user]; |
||||
newChannel.isPrivate = channel.isPrivate |
newChannel.name = channel.name; |
||||
newChannel.password = channel.password |
newChannel.isPrivate = channel.isPrivate; |
||||
return await this.ChannelRepository.save(newChannel) |
newChannel.password = channel.password; |
||||
|
return await this.ChannelRepository.save(newChannel); |
||||
} |
} |
||||
|
|
||||
|
async updatePassword(id: number, password: string) { |
||||
|
const channel: Channel | null = await this.ChannelRepository.findOneBy({ |
||||
async updatePassword (id: number, password: string) { |
id, |
||||
let channel: Channel | null = await this.ChannelRepository.findOneBy({id}) |
}); |
||||
if (channel === null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel === null) { |
||||
channel.password = password |
throw new NotFoundException(`Channel #${id} not found`); |
||||
await this.ChannelRepository.save(channel) |
} |
||||
|
channel.password = password; |
||||
|
await this.ChannelRepository.save(channel); |
||||
} |
} |
||||
|
|
||||
async getChannelsForUser (ftId: number): Promise<Channel[]> { |
async getChannelsForUser(ftId: number): Promise<Channel[]> { |
||||
let rooms: Channel[] = [] |
let rooms: Channel[] = []; |
||||
rooms = [ |
rooms = [ |
||||
...(await this.ChannelRepository.createQueryBuilder('room') |
...(await this.ChannelRepository.createQueryBuilder("room") |
||||
.where('room.isPrivate = false') |
.where("room.isPrivate = false") |
||||
.getMany()) |
.getMany()), |
||||
] |
]; |
||||
|
|
||||
rooms = [ |
rooms = [ |
||||
...rooms, |
...rooms, |
||||
...(await this.ChannelRepository.createQueryBuilder('room') |
...(await this.ChannelRepository.createQueryBuilder("room") |
||||
.innerJoin('room.users', 'users') |
.innerJoin("room.users", "users") |
||||
.where('room.isPrivate = true') |
.where("room.isPrivate = true") |
||||
.andWhere('users.ftId = :ftId', { ftId }) |
.andWhere("users.ftId = :ftId", { ftId }) |
||||
.getMany()) |
.getMany()), |
||||
] |
]; |
||||
return rooms |
return rooms; |
||||
} |
} |
||||
|
|
||||
@Cron('*/6 * * * * *') |
@Cron("*/6 * * * * *") |
||||
async updateMutelists(): Promise<void> { |
async updateMutelists(): Promise<void> { |
||||
let channels = await this.ChannelRepository.find({}) |
const channels = await this.ChannelRepository.find({}); |
||||
channels.forEach((channel) => { |
channels.forEach((channel) => { |
||||
channel.muted = channel.muted.filter((data) => { return (data[0] - Date.now()) > 0;}); |
channel.muted = channel.muted.filter((data) => { |
||||
|
return data[0] - Date.now() > 0; |
||||
|
}); |
||||
this.ChannelRepository.save(channel); |
this.ChannelRepository.save(channel); |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
async addUserToChannel (channel: Channel, user: User): Promise<Channel> { |
async addUserToChannel(channel: Channel, user: User): Promise<Channel> { |
||||
channel.owner = user |
channel.owner = user; |
||||
return await this.ChannelRepository.save(channel) |
return await this.ChannelRepository.save(channel); |
||||
} |
} |
||||
|
|
||||
async getChannel (id: number): Promise<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`) } |
if (channel == null) { |
||||
return channel |
throw new NotFoundException(`Channel #${id} not found`); |
||||
|
} |
||||
|
return channel; |
||||
} |
} |
||||
|
|
||||
async getFullChannel (id: number): Promise<Channel> { |
async getFullChannel(id: number): Promise<Channel> { |
||||
const channel = await this.ChannelRepository.findOne({ |
const channel = await this.ChannelRepository.findOne({ |
||||
where: { id }, |
where: { id }, |
||||
relations: ['users', 'admins', 'banned', 'muted', 'owner'] |
relations: ["users", "admins", "banned", "muted", "owner"], |
||||
}) |
}); |
||||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel == null) { |
||||
return channel |
throw new NotFoundException(`Channel #${id} not found`); |
||||
|
} |
||||
|
return channel; |
||||
} |
} |
||||
|
|
||||
async update (channel: Channel) { |
async update(channel: Channel) { |
||||
await this.ChannelRepository.update(channel.id, channel) |
await this.ChannelRepository.update(channel.id, channel); |
||||
} |
} |
||||
|
|
||||
async save (channel: Channel) { |
async save(channel: Channel) { |
||||
await this.ChannelRepository.save(channel) |
await this.ChannelRepository.save(channel); |
||||
} |
} |
||||
|
|
||||
async removeChannel (channelId: number) { |
async removeChannel(channelId: number) { |
||||
await this.ChannelRepository.delete(channelId) |
await this.ChannelRepository.delete(channelId); |
||||
} |
} |
||||
|
|
||||
async isOwner (id: number, userId: number): Promise<boolean> { |
async isOwner(id: number, userId: number): Promise<boolean> { |
||||
const channel = await this.ChannelRepository.findOne({ |
const channel = await this.ChannelRepository.findOne({ |
||||
where: { id }, |
where: { id }, |
||||
relations: { owner: true } |
relations: { owner: true }, |
||||
}) |
}); |
||||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel == null) { |
||||
return channel.owner.ftId === userId |
throw new NotFoundException(`Channel #${id} not found`); |
||||
|
} |
||||
|
return channel.owner.ftId === userId; |
||||
} |
} |
||||
|
|
||||
async isAdmin (id: number, userId: number): Promise<boolean> { |
async isAdmin(id: number, userId: number): Promise<boolean> { |
||||
const channel = await this.ChannelRepository.findOne({ |
const channel = await this.ChannelRepository.findOne({ |
||||
where: { id }, |
where: { id }, |
||||
relations: { admins: true } |
relations: { admins: true }, |
||||
}) |
}); |
||||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel == null) { |
||||
return channel.admins.findIndex((user) => user.ftId === userId) != -1 |
throw new NotFoundException(`Channel #${id} not found`); |
||||
|
} |
||||
|
return channel.admins.findIndex((user) => user.ftId === userId) != -1; |
||||
} |
} |
||||
|
|
||||
async isUser (id: number, userId: number): Promise<boolean> { |
async isUser(id: number, userId: number): Promise<boolean> { |
||||
const channel = await this.ChannelRepository.findOne({ |
const channel = await this.ChannelRepository.findOne({ |
||||
where: { id }, |
where: { id }, |
||||
relations: { users: true } |
relations: { users: true }, |
||||
}) |
}); |
||||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel == null) { |
||||
return channel.users.findIndex((user) => user.ftId === userId) != -1 |
throw new NotFoundException(`Channel #${id} not found`); |
||||
|
} |
||||
|
return channel.users.findIndex((user) => user.ftId === userId) != -1; |
||||
} |
} |
||||
|
|
||||
async isBanned (id: number, userId: number): Promise<boolean> { |
async isBanned(id: number, userId: number): Promise<boolean> { |
||||
const channel = await this.ChannelRepository.findOne({ |
const channel = await this.ChannelRepository.findOne({ |
||||
where: { id }, |
where: { id }, |
||||
relations: { banned: true } |
relations: { banned: true }, |
||||
}) |
}); |
||||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel == null) { |
||||
return channel.banned.findIndex((user) => user.ftId === userId) != -1 |
throw new NotFoundException(`Channel #${id} not found`); |
||||
|
} |
||||
|
return channel.banned.findIndex((user) => user.ftId === userId) != -1; |
||||
} |
} |
||||
|
|
||||
async getMuteDuration (id: number, userId: number): Promise<number> { |
async getMuteDuration(id: number, userId: number): Promise<number> { |
||||
const channel = await this.ChannelRepository.findOne({ |
const channel = await this.ChannelRepository.findOne({ |
||||
where: { id }, |
where: { id }, |
||||
relations: { muted: true } |
relations: { muted: true }, |
||||
}) |
}); |
||||
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) } |
if (channel == null) { |
||||
|
throw new NotFoundException(`Channel #${id} not found`); |
||||
const mutation: Array<number> | undefined = channel.muted.find((mutation) => mutation[0] === userId) |
} |
||||
if (mutation == null) { return 0 } |
|
||||
return mutation[1] |
|
||||
|
|
||||
|
const mutation: number[] | undefined = channel.muted.find( |
||||
|
(mutation) => mutation[0] === userId |
||||
|
); |
||||
|
if (mutation == null) { |
||||
|
return 0; |
||||
|
} |
||||
|
return mutation[1]; |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,13 +1,13 @@ |
|||||
import { IsNumber, IsOptional, IsString } from 'class-validator' |
import { IsNumber, IsOptional, IsString } from "class-validator"; |
||||
|
|
||||
export class ConnectionDto { |
export class ConnectionDto { |
||||
@IsNumber() |
@IsNumber() |
||||
UserId: number |
UserId: number; |
||||
|
|
||||
@IsNumber() |
@IsNumber() |
||||
ChannelId: number |
ChannelId: number; |
||||
|
|
||||
@IsString() |
@IsString() |
||||
@IsOptional() |
@IsOptional() |
||||
pwd: string |
pwd: string; |
||||
} |
} |
||||
|
@ -1,28 +1,28 @@ |
|||||
import { Transform } from 'class-transformer' |
import { Transform } from "class-transformer"; |
||||
import { |
import { |
||||
IsPositive, |
IsPositive, |
||||
IsAlpha, |
IsAlpha, |
||||
IsString, |
IsString, |
||||
IsOptional, |
IsOptional, |
||||
IsNumber, |
IsNumber, |
||||
IsBoolean |
IsBoolean, |
||||
} from 'class-validator' |
} from "class-validator"; |
||||
|
|
||||
export class CreateChannelDto { |
export class CreateChannelDto { |
||||
@IsOptional() |
@IsOptional() |
||||
@IsPositive() |
@IsPositive() |
||||
id: number |
id: number; |
||||
|
|
||||
@IsString() |
@IsString() |
||||
name: string |
name: string; |
||||
|
|
||||
@IsNumber() |
@IsNumber() |
||||
owner: number |
owner: number; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
password: string |
password: string; |
||||
|
|
||||
@IsBoolean() |
@IsBoolean() |
||||
@Transform(({ value }) => value === 'true') |
@Transform(({ value }) => value === "true") |
||||
isPrivate: boolean |
isPrivate: boolean; |
||||
} |
} |
||||
|
@ -1,12 +1,12 @@ |
|||||
import { IsNumber, IsString } from 'class-validator' |
import { IsNumber, IsString } from "class-validator"; |
||||
|
|
||||
export class CreateMessageDto { |
export class CreateMessageDto { |
||||
@IsString() |
@IsString() |
||||
text: string |
text: string; |
||||
|
|
||||
@IsNumber() |
@IsNumber() |
||||
UserId: number |
UserId: number; |
||||
|
|
||||
@IsNumber() |
@IsNumber() |
||||
ChannelId: number |
ChannelId: number; |
||||
} |
} |
||||
|
@ -1,30 +1,30 @@ |
|||||
import { PartialType } from '@nestjs/mapped-types' |
import { PartialType } from "@nestjs/mapped-types"; |
||||
import { CreateChannelDto } from './create-channel.dto' |
import { CreateChannelDto } from "./create-channel.dto"; |
||||
import { IsNumber, IsOptional, IsString } from 'class-validator' |
import { IsNumber, IsOptional, IsString } from "class-validator"; |
||||
|
|
||||
export class UpdateChannelDto extends PartialType(CreateChannelDto) { |
export class UpdateChannelDto extends PartialType(CreateChannelDto) { |
||||
id: number |
id: number; |
||||
@IsOptional() |
@IsOptional() |
||||
@IsNumber() |
@IsNumber() |
||||
users: [number] |
users: [number]; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
@IsNumber() |
@IsNumber() |
||||
messages: [number] |
messages: [number]; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
@IsNumber() |
@IsNumber() |
||||
owners: [number] // user id
|
owners: [number]; // user id
|
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
@IsNumber() |
@IsNumber() |
||||
banned: [number] // user id
|
banned: [number]; // user id
|
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
@IsNumber() |
@IsNumber() |
||||
muted: [number] // user id
|
muted: [number]; // user id
|
||||
|
|
||||
@IsString() |
@IsString() |
||||
@IsOptional() |
@IsOptional() |
||||
password: string |
password: string; |
||||
} |
} |
||||
|
@ -1,16 +1,15 @@ |
|||||
|
import { IsNumber, IsString } from "class-validator"; |
||||
import { IsNumber, IsString} from 'class-validator' |
|
||||
|
|
||||
export class IdDto { |
export class IdDto { |
||||
@IsNumber() |
@IsNumber() |
||||
id: number |
id: number; |
||||
} |
} |
||||
|
|
||||
export class PasswordDto { |
export class PasswordDto { |
||||
@IsString() |
@IsString() |
||||
password: string |
password: string; |
||||
} |
} |
||||
|
|
||||
export class MuteDto { |
export class MuteDto { |
||||
data: Array<number> |
data: number[]; |
||||
} |
} |
||||
|
@ -1,17 +1,23 @@ |
|||||
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm' |
import { |
||||
|
Column, |
||||
|
Entity, |
||||
|
JoinColumn, |
||||
|
OneToOne, |
||||
|
PrimaryGeneratedColumn, |
||||
|
} from "typeorm"; |
||||
|
|
||||
import Channel from './channel.entity' |
import Channel from "./channel.entity"; |
||||
import User from 'src/users/entity/user.entity' |
import User from "src/users/entity/user.entity"; |
||||
|
|
||||
@Entity() |
@Entity() |
||||
export default class ConnectedUser { |
export default class ConnectedUser { |
||||
@OneToOne(() => User) |
@OneToOne(() => User) |
||||
user: User |
user: User; |
||||
|
|
||||
@OneToOne(() => Channel) |
@OneToOne(() => Channel) |
||||
@JoinColumn() |
@JoinColumn() |
||||
channel: Channel |
channel: Channel; |
||||
|
|
||||
@PrimaryGeneratedColumn() |
@PrimaryGeneratedColumn() |
||||
socket: string |
socket: string; |
||||
} |
} |
||||
|
@ -1,15 +1,15 @@ |
|||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm' |
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; |
||||
import Message from './message.entity' |
import Message from "./message.entity"; |
||||
import type User from 'src/users/entity/user.entity' |
import type User from "src/users/entity/user.entity"; |
||||
|
|
||||
@Entity() |
@Entity() |
||||
export class Channel { |
export class Channel { |
||||
@PrimaryGeneratedColumn() |
@PrimaryGeneratedColumn() |
||||
id: number |
id: number; |
||||
|
|
||||
@Column() |
@Column() |
||||
users: User[] |
users: User[]; |
||||
|
|
||||
@OneToMany(() => Message, (message) => message.channel) |
@OneToMany(() => Message, (message) => message.channel) |
||||
messages: Message[] |
messages: Message[]; |
||||
} |
} |
||||
|
@ -1,45 +1,45 @@ |
|||||
import { InternalServerErrorException, Logger } from '@nestjs/common' |
import { InternalServerErrorException, Logger } from "@nestjs/common"; |
||||
import { NestFactory } from '@nestjs/core' |
import { NestFactory } from "@nestjs/core"; |
||||
import { AppModule } from './app.module' |
import { AppModule } from "./app.module"; |
||||
import * as session from 'express-session' |
import * as session from "express-session"; |
||||
import * as passport from 'passport' |
import * as passport from "passport"; |
||||
import { type NestExpressApplication } from '@nestjs/platform-express' |
import { type NestExpressApplication } from "@nestjs/platform-express"; |
||||
import * as cookieParser from 'cookie-parser' |
import * as cookieParser from "cookie-parser"; |
||||
import { IoAdapter } from '@nestjs/platform-socket.io' |
import { IoAdapter } from "@nestjs/platform-socket.io"; |
||||
|
|
||||
async function bootstrap (): Promise<void> { |
async function bootstrap(): Promise<void> { |
||||
const logger = new Logger() |
const logger = new Logger(); |
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule) |
const app = await NestFactory.create<NestExpressApplication>(AppModule); |
||||
const port = |
const port = |
||||
process.env.BACK_PORT !== undefined && process.env.BACK_PORT !== '' |
process.env.BACK_PORT !== undefined && process.env.BACK_PORT !== "" |
||||
? +process.env.BACK_PORT |
? +process.env.BACK_PORT |
||||
: 3001 |
: 3001; |
||||
const cors = { |
const cors = { |
||||
origin: /^(http|ws):\/\/localhost(:\d+)?$/, |
origin: /^(http|ws):\/\/localhost(:\d+)?$/, |
||||
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS', |
methods: "GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS", |
||||
preflightContinue: false, |
preflightContinue: false, |
||||
optionsSuccessStatus: 204, |
optionsSuccessStatus: 204, |
||||
credentials: true, |
credentials: true, |
||||
allowedHeaders: ['Accept', 'Content-Type', 'Authorization'] |
allowedHeaders: ["Accept", "Content-Type", "Authorization"], |
||||
} |
}; |
||||
app.use( |
app.use( |
||||
session({ |
session({ |
||||
resave: false, |
resave: false, |
||||
saveUninitialized: false, |
saveUninitialized: false, |
||||
secret: |
secret: |
||||
process.env.JWT_SECRET !== undefined && process.env.JWT_SECRET !== '' |
process.env.JWT_SECRET !== undefined && process.env.JWT_SECRET !== "" |
||||
? process.env.JWT_SECRET |
? process.env.JWT_SECRET |
||||
: 'secret' |
: "secret", |
||||
}) |
}) |
||||
) |
); |
||||
app.use(cookieParser()) |
app.use(cookieParser()); |
||||
app.use(passport.initialize()) |
app.use(passport.initialize()); |
||||
app.use(passport.session()) |
app.use(passport.session()); |
||||
app.enableCors(cors) |
app.enableCors(cors); |
||||
app.useWebSocketAdapter(new IoAdapter(app)) |
app.useWebSocketAdapter(new IoAdapter(app)); |
||||
await app.listen(port) |
await app.listen(port); |
||||
logger.log(`Application listening on port ${port}`) |
logger.log(`Application listening on port ${port}`); |
||||
} |
} |
||||
bootstrap().catch((e) => { |
bootstrap().catch((e) => { |
||||
throw new InternalServerErrorException(e) |
throw new InternalServerErrorException(e); |
||||
}) |
}); |
||||
|
@ -1,12 +1,12 @@ |
|||||
import { type Point, type Rect } from '../game/utils' |
import { type Point, type Rect } from "../game/utils"; |
||||
|
|
||||
export class GameInfo { |
export class GameInfo { |
||||
mapSize!: Point |
mapSize!: Point; |
||||
yourPaddleIndex!: number |
yourPaddleIndex!: number; |
||||
gameId!: string |
gameId!: string; |
||||
walls!: Rect[] |
walls!: Rect[]; |
||||
paddleSize!: Point |
paddleSize!: Point; |
||||
ballSize!: Point |
ballSize!: Point; |
||||
winScore!: number |
winScore!: number; |
||||
ranked!: boolean |
ranked!: boolean; |
||||
} |
} |
||||
|
@ -1,8 +1,8 @@ |
|||||
import { type Point } from '../game/utils' |
import { type Point } from "../game/utils"; |
||||
|
|
||||
export class GameUpdate { |
export class GameUpdate { |
||||
paddlesPositions!: Point[] |
paddlesPositions!: Point[]; |
||||
ballSpeed!: Point |
ballSpeed!: Point; |
||||
ballPosition!: Point |
ballPosition!: Point; |
||||
scores!: number[] |
scores!: number[]; |
||||
} |
} |
||||
|
@ -1,23 +1,23 @@ |
|||||
import { Type } from 'class-transformer' |
import { Type } from "class-transformer"; |
||||
import { |
import { |
||||
ArrayMaxSize, |
ArrayMaxSize, |
||||
IsArray, |
IsArray, |
||||
IsDefined, |
IsDefined, |
||||
IsObject, |
IsObject, |
||||
ValidateNested |
ValidateNested, |
||||
} from 'class-validator' |
} from "class-validator"; |
||||
import { PointDtoValidated } from './PointDtoValidated' |
import { PointDtoValidated } from "./PointDtoValidated"; |
||||
import { RectDtoValidated } from './RectDtoValidated' |
import { RectDtoValidated } from "./RectDtoValidated"; |
||||
|
|
||||
export class MapDtoValidated { |
export class MapDtoValidated { |
||||
@IsObject() |
@IsObject() |
||||
@IsDefined() |
@IsDefined() |
||||
@Type(() => PointDtoValidated) |
@Type(() => PointDtoValidated) |
||||
size!: PointDtoValidated |
size!: PointDtoValidated; |
||||
|
|
||||
@IsArray() |
@IsArray() |
||||
@ArrayMaxSize(5) |
@ArrayMaxSize(5) |
||||
@ValidateNested({ each: true }) |
@ValidateNested({ each: true }) |
||||
@Type(() => RectDtoValidated) |
@Type(() => RectDtoValidated) |
||||
walls!: RectDtoValidated[] |
walls!: RectDtoValidated[]; |
||||
} |
} |
||||
|
@ -1,3 +1,3 @@ |
|||||
export class MatchmakingDto { |
export class MatchmakingDto { |
||||
matchmaking!: boolean |
matchmaking!: boolean; |
||||
} |
} |
||||
|
@ -1,7 +1,7 @@ |
|||||
import { IsBoolean } from 'class-validator' |
import { IsBoolean } from "class-validator"; |
||||
import { MatchmakingDto } from './MatchmakingDto' |
import { MatchmakingDto } from "./MatchmakingDto"; |
||||
|
|
||||
export class MatchmakingDtoValidated extends MatchmakingDto { |
export class MatchmakingDtoValidated extends MatchmakingDto { |
||||
@IsBoolean() |
@IsBoolean() |
||||
matchmaking!: boolean |
matchmaking!: boolean; |
||||
} |
} |
||||
|
@ -1,10 +1,10 @@ |
|||||
import { IsNumber } from 'class-validator' |
import { IsNumber } from "class-validator"; |
||||
import { Point } from '../game/utils' |
import { Point } from "../game/utils"; |
||||
|
|
||||
export class PointDtoValidated extends Point { |
export class PointDtoValidated extends Point { |
||||
@IsNumber() |
@IsNumber() |
||||
x!: number |
x!: number; |
||||
|
|
||||
@IsNumber() |
@IsNumber() |
||||
y!: number |
y!: number; |
||||
} |
} |
||||
|
@ -1,14 +1,14 @@ |
|||||
import { Type } from 'class-transformer' |
import { Type } from "class-transformer"; |
||||
import { ValidateNested } from 'class-validator' |
import { ValidateNested } from "class-validator"; |
||||
import { Rect } from '../game/utils' |
import { Rect } from "../game/utils"; |
||||
import { PointDtoValidated } from './PointDtoValidated' |
import { PointDtoValidated } from "./PointDtoValidated"; |
||||
|
|
||||
export class RectDtoValidated extends Rect { |
export class RectDtoValidated extends Rect { |
||||
@ValidateNested() |
@ValidateNested() |
||||
@Type(() => PointDtoValidated) |
@Type(() => PointDtoValidated) |
||||
center!: PointDtoValidated |
center!: PointDtoValidated; |
||||
|
|
||||
@ValidateNested() |
@ValidateNested() |
||||
@Type(() => PointDtoValidated) |
@Type(() => PointDtoValidated) |
||||
size!: PointDtoValidated |
size!: PointDtoValidated; |
||||
} |
} |
||||
|
@ -1,3 +1,3 @@ |
|||||
export class StringDto { |
export class StringDto { |
||||
value!: string |
value!: string; |
||||
} |
} |
||||
|
@ -1,7 +1,7 @@ |
|||||
import { IsString } from 'class-validator' |
import { IsString } from "class-validator"; |
||||
import { StringDto } from './StringDto' |
import { StringDto } from "./StringDto"; |
||||
|
|
||||
export class StringDtoValidated extends StringDto { |
export class StringDtoValidated extends StringDto { |
||||
@IsString() |
@IsString() |
||||
value!: string |
value!: string; |
||||
} |
} |
||||
|
@ -1,12 +1,12 @@ |
|||||
import { IsString } from 'class-validator' |
import { IsString } from "class-validator"; |
||||
|
|
||||
export class UserDto { |
export class UserDto { |
||||
@IsString() |
@IsString() |
||||
username!: string |
username!: string; |
||||
|
|
||||
@IsString() |
@IsString() |
||||
avatar!: string |
avatar!: string; |
||||
|
|
||||
@IsString() |
@IsString() |
||||
status!: string |
status!: string; |
||||
} |
} |
||||
|
@ -1,114 +1,114 @@ |
|||||
import { type Paddle } from './Paddle' |
import { type Paddle } from "./Paddle"; |
||||
import { type Point, Rect } from './utils' |
import { type Point, Rect } from "./utils"; |
||||
import { type MapDtoValidated } from '../dtos/MapDtoValidated' |
import { type MapDtoValidated } from "../dtos/MapDtoValidated"; |
||||
import { |
import { |
||||
DEFAULT_BALL_SIZE, |
DEFAULT_BALL_SIZE, |
||||
GAME_TICKS, |
GAME_TICKS, |
||||
DEFAULT_BALL_SPEED_INCREMENT, |
DEFAULT_BALL_SPEED_INCREMENT, |
||||
DEFAULT_MAX_BALL_SPEED |
DEFAULT_MAX_BALL_SPEED, |
||||
} from './constants' |
} from "./constants"; |
||||
|
|
||||
export class Ball { |
export class Ball { |
||||
rect: Rect |
rect: Rect; |
||||
initial_speed: Point |
initial_speed: Point; |
||||
speed: Point |
speed: Point; |
||||
spawn: Point |
spawn: Point; |
||||
indexPlayerScored: number |
indexPlayerScored: number; |
||||
timeoutTime: number |
timeoutTime: number; |
||||
|
|
||||
constructor ( |
constructor( |
||||
spawn: Point, |
spawn: Point, |
||||
initialSpeed: Point, |
initialSpeed: Point, |
||||
size: Point = DEFAULT_BALL_SIZE.clone() |
size: Point = DEFAULT_BALL_SIZE.clone() |
||||
) { |
) { |
||||
this.rect = new Rect(spawn, size) |
this.rect = new Rect(spawn, size); |
||||
this.speed = initialSpeed.clone() |
this.speed = initialSpeed.clone(); |
||||
this.initial_speed = initialSpeed.clone() |
this.initial_speed = initialSpeed.clone(); |
||||
this.spawn = spawn.clone() |
this.spawn = spawn.clone(); |
||||
this.indexPlayerScored = -1 |
this.indexPlayerScored = -1; |
||||
this.timeoutTime = 0 |
this.timeoutTime = 0; |
||||
} |
} |
||||
|
|
||||
getIndexPlayerScored (): number { |
getIndexPlayerScored(): number { |
||||
return this.indexPlayerScored |
return this.indexPlayerScored; |
||||
} |
} |
||||
|
|
||||
update (canvasRect: Rect, paddles: Paddle[], map: MapDtoValidated): void { |
update(canvasRect: Rect, paddles: Paddle[], map: MapDtoValidated): void { |
||||
if (!canvasRect.contains_x(this.rect)) { |
if (!canvasRect.contains_x(this.rect)) { |
||||
this.indexPlayerScored = this.playerScored() |
this.indexPlayerScored = this.playerScored(); |
||||
this.timeoutTime = 2000 |
this.timeoutTime = 2000; |
||||
} else { |
} else { |
||||
this.indexPlayerScored = -1 |
this.indexPlayerScored = -1; |
||||
if (this.timeoutTime <= 0) { |
if (this.timeoutTime <= 0) { |
||||
this.move(canvasRect, paddles, map) |
this.move(canvasRect, paddles, map); |
||||
} else { |
} else { |
||||
this.timeoutTime -= 1000 / GAME_TICKS |
this.timeoutTime -= 1000 / GAME_TICKS; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
move (canvasRect: Rect, paddles: Paddle[], map: MapDtoValidated): void { |
move(canvasRect: Rect, paddles: Paddle[], map: MapDtoValidated): void { |
||||
for (const paddle of paddles) { |
for (const paddle of paddles) { |
||||
if (paddle.rect.collides(this.rect)) { |
if (paddle.rect.collides(this.rect)) { |
||||
if (this.speed.x < 0) { |
if (this.speed.x < 0) { |
||||
this.rect.center.x = paddle.rect.center.x + paddle.rect.size.x |
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 |
} else this.rect.center.x = paddle.rect.center.x - paddle.rect.size.x; |
||||
this.speed.x = this.speed.x * -1 |
this.speed.x = this.speed.x * -1; |
||||
this.speed.y = |
this.speed.y = |
||||
((this.rect.center.y - paddle.rect.center.y) / paddle.rect.size.y) * |
((this.rect.center.y - paddle.rect.center.y) / paddle.rect.size.y) * |
||||
20 |
20; |
||||
break |
break; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
for (const wall of map.walls) { |
for (const wall of map.walls) { |
||||
if (wall.collides(this.rect)) { |
if (wall.collides(this.rect)) { |
||||
if (this.speed.x < 0) { |
if (this.speed.x < 0) { |
||||
this.rect.center.x = wall.center.x + wall.size.x |
this.rect.center.x = wall.center.x + wall.size.x; |
||||
} else 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.x = this.speed.x * -1; |
||||
this.speed.y = |
this.speed.y = |
||||
((this.rect.center.y - wall.center.y) / wall.size.y) * 20 |
((this.rect.center.y - wall.center.y) / wall.size.y) * 20; |
||||
break |
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) { |
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) { |
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) { |
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) { |
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 { |
playerScored(): number { |
||||
let indexPlayerScored: number |
let indexPlayerScored: number; |
||||
|
|
||||
if (this.rect.center.x <= this.spawn.x) { |
if (this.rect.center.x <= this.spawn.x) { |
||||
indexPlayerScored = 1 |
indexPlayerScored = 1; |
||||
this.speed.x = this.initial_speed.x |
this.speed.x = this.initial_speed.x; |
||||
} else { |
} else { |
||||
indexPlayerScored = 0 |
indexPlayerScored = 0; |
||||
this.speed.x = -this.initial_speed.x |
this.speed.x = -this.initial_speed.x; |
||||
} |
} |
||||
|
|
||||
if (this.speed.y < 0) { |
if (this.speed.y < 0) { |
||||
this.speed.y = this.initial_speed.y |
this.speed.y = this.initial_speed.y; |
||||
} else { |
} 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; |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,64 +1,64 @@ |
|||||
import { type Socket } from 'socket.io' |
import { type Socket } from "socket.io"; |
||||
import { type GameCreationDtoValidated } from '../dtos/GameCreationDtoValidated' |
import { type GameCreationDtoValidated } from "../dtos/GameCreationDtoValidated"; |
||||
import { DEFAULT_BALL_INITIAL_SPEED, DEFAULT_MAP_SIZE } from './constants' |
import { DEFAULT_BALL_INITIAL_SPEED, DEFAULT_MAP_SIZE } from "./constants"; |
||||
import { type Games } from './Games' |
import { type Games } from "./Games"; |
||||
|
|
||||
export class MatchmakingQueue { |
export class MatchmakingQueue { |
||||
games: Games |
games: Games; |
||||
queue: Array<{ name: string, socket: Socket, uuid: string }> |
queue: Array<{ name: string; socket: Socket; uuid: string }>; |
||||
|
|
||||
constructor (games: Games) { |
constructor(games: Games) { |
||||
this.games = games |
this.games = games; |
||||
this.queue = [] |
this.queue = []; |
||||
} |
} |
||||
|
|
||||
addPlayer (name: string, socket: Socket, uuid: string): void { |
addPlayer(name: string, socket: Socket, uuid: string): void { |
||||
if (!this.isInQueue(name)) { |
if (!this.isInQueue(name)) { |
||||
console.log('Adding player to queue: ', name) |
console.log("Adding player to queue: ", name); |
||||
this.queue.push({ name, socket, uuid }) |
this.queue.push({ name, socket, uuid }); |
||||
if (this.canCreateGame()) { |
if (this.canCreateGame()) { |
||||
this.createGame() |
this.createGame(); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
removePlayer (name: string): void { |
removePlayer(name: string): void { |
||||
if (this.isInQueue(name)) { |
if (this.isInQueue(name)) { |
||||
console.log('Removing player from queue: ', name) |
console.log("Removing player from queue: ", name); |
||||
this.queue = this.queue.filter((player) => player.name !== name) |
this.queue = this.queue.filter((player) => player.name !== name); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
isInQueue (name: string): boolean { |
isInQueue(name: string): boolean { |
||||
return this.queue.some((player) => player.name === name) |
return this.queue.some((player) => player.name === name); |
||||
} |
} |
||||
|
|
||||
canCreateGame (): boolean { |
canCreateGame(): boolean { |
||||
return this.queue.length >= 2 |
return this.queue.length >= 2; |
||||
} |
} |
||||
|
|
||||
createGame (): void { |
createGame(): void { |
||||
const player1 = this.queue.shift() |
const player1 = this.queue.shift(); |
||||
const player2 = this.queue.shift() |
const player2 = this.queue.shift(); |
||||
if (player1 === undefined || player2 === undefined) { |
if (player1 === undefined || player2 === undefined) { |
||||
return |
return; |
||||
} |
} |
||||
const gameCreationDto: GameCreationDtoValidated = { |
const gameCreationDto: GameCreationDtoValidated = { |
||||
playerNames: [player1.name, player2.name], |
playerNames: [player1.name, player2.name], |
||||
map: { |
map: { |
||||
size: DEFAULT_MAP_SIZE, |
size: DEFAULT_MAP_SIZE, |
||||
walls: [] |
walls: [], |
||||
}, |
}, |
||||
initialBallSpeedX: DEFAULT_BALL_INITIAL_SPEED.x, |
initialBallSpeedX: DEFAULT_BALL_INITIAL_SPEED.x, |
||||
initialBallSpeedY: DEFAULT_BALL_INITIAL_SPEED.y |
initialBallSpeedY: DEFAULT_BALL_INITIAL_SPEED.y, |
||||
} |
}; |
||||
const ranked = true |
const ranked = true; |
||||
|
|
||||
this.games.newGame( |
this.games.newGame( |
||||
[player1.socket, player2.socket], |
[player1.socket, player2.socket], |
||||
[player1.uuid, player2.uuid], |
[player1.uuid, player2.uuid], |
||||
gameCreationDto, |
gameCreationDto, |
||||
ranked |
ranked |
||||
) |
); |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,32 +1,32 @@ |
|||||
import { DEFAULT_PADDLE_SIZE } from './constants' |
import { DEFAULT_PADDLE_SIZE } from "./constants"; |
||||
import { type Point, Rect } from './utils' |
import { type Point, Rect } from "./utils"; |
||||
|
|
||||
export class Paddle { |
export class Paddle { |
||||
rect: Rect |
rect: Rect; |
||||
color: string | CanvasGradient | CanvasPattern = 'white' |
color: string | CanvasGradient | CanvasPattern = "white"; |
||||
mapSize: Point |
mapSize: Point; |
||||
|
|
||||
constructor ( |
constructor( |
||||
spawn: Point, |
spawn: Point, |
||||
gameSize: Point, |
gameSize: Point, |
||||
size: Point = DEFAULT_PADDLE_SIZE |
size: Point = DEFAULT_PADDLE_SIZE |
||||
) { |
) { |
||||
this.rect = new Rect(spawn, size) |
this.rect = new Rect(spawn, size); |
||||
this.mapSize = gameSize |
this.mapSize = gameSize; |
||||
} |
} |
||||
|
|
||||
draw (context: CanvasRenderingContext2D): void { |
draw(context: CanvasRenderingContext2D): void { |
||||
this.rect.draw(context, this.color) |
this.rect.draw(context, this.color); |
||||
} |
} |
||||
|
|
||||
move (newY: number): void { |
move(newY: number): void { |
||||
const offset: number = this.rect.size.y / 2 |
const offset: number = this.rect.size.y / 2; |
||||
if (newY - offset < 0) { |
if (newY - offset < 0) { |
||||
this.rect.center.y = offset |
this.rect.center.y = offset; |
||||
} else if (newY + offset > this.mapSize.y) { |
} else if (newY + offset > this.mapSize.y) { |
||||
this.rect.center.y = this.mapSize.y - offset |
this.rect.center.y = this.mapSize.y - offset; |
||||
} else { |
} else { |
||||
this.rect.center.y = newY |
this.rect.center.y = newY; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,36 +1,36 @@ |
|||||
import { type Socket } from 'socket.io' |
import { type Socket } from "socket.io"; |
||||
import { Paddle } from './Paddle' |
import { Paddle } from "./Paddle"; |
||||
import { type Point } from './utils' |
import { type Point } from "./utils"; |
||||
|
|
||||
export class Player { |
export class Player { |
||||
socket: Socket |
socket: Socket; |
||||
uuid: string |
uuid: string; |
||||
name: string |
name: string; |
||||
ready: boolean |
ready: boolean; |
||||
paddle: Paddle |
paddle: Paddle; |
||||
paddleCoords: Point |
paddleCoords: Point; |
||||
mapSize: Point |
mapSize: Point; |
||||
score: number |
score: number; |
||||
|
|
||||
constructor ( |
constructor( |
||||
socket: Socket, |
socket: Socket, |
||||
uuid: string, |
uuid: string, |
||||
name: string, |
name: string, |
||||
paddleCoords: Point, |
paddleCoords: Point, |
||||
mapSize: Point |
mapSize: Point |
||||
) { |
) { |
||||
this.socket = socket |
this.socket = socket; |
||||
this.uuid = uuid |
this.uuid = uuid; |
||||
this.name = name |
this.name = name; |
||||
this.ready = false |
this.ready = false; |
||||
this.paddle = new Paddle(paddleCoords, mapSize) |
this.paddle = new Paddle(paddleCoords, mapSize); |
||||
this.paddleCoords = paddleCoords |
this.paddleCoords = paddleCoords; |
||||
this.mapSize = mapSize |
this.mapSize = mapSize; |
||||
this.score = 0 |
this.score = 0; |
||||
} |
} |
||||
|
|
||||
newGame (): void { |
newGame(): void { |
||||
this.score = 0 |
this.score = 0; |
||||
this.paddle = new Paddle(this.paddleCoords, this.mapSize) |
this.paddle = new Paddle(this.paddleCoords, this.mapSize); |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,22 +1,22 @@ |
|||||
import { Point } from './utils' |
import { Point } from "./utils"; |
||||
|
|
||||
export const GAME_EVENTS = { |
export const GAME_EVENTS = { |
||||
START_GAME: 'START_GAME', |
START_GAME: "START_GAME", |
||||
READY: 'READY', |
READY: "READY", |
||||
GAME_TICK: 'GAME_TICK', |
GAME_TICK: "GAME_TICK", |
||||
PLAYER_MOVE: 'PLAYER_MOVE', |
PLAYER_MOVE: "PLAYER_MOVE", |
||||
GET_GAME_INFO: 'GET_GAME_INFO', |
GET_GAME_INFO: "GET_GAME_INFO", |
||||
CREATE_GAME: 'CREATE_GAME', |
CREATE_GAME: "CREATE_GAME", |
||||
REGISTER_PLAYER: 'REGISTER_PLAYER', |
REGISTER_PLAYER: "REGISTER_PLAYER", |
||||
MATCHMAKING: 'MATCHMAKING', |
MATCHMAKING: "MATCHMAKING", |
||||
LEAVE_GAME: 'LEAVE_GAME' |
LEAVE_GAME: "LEAVE_GAME", |
||||
} |
}; |
||||
|
|
||||
export const DEFAULT_MAP_SIZE = new Point(500, 400) |
export const DEFAULT_MAP_SIZE = new Point(500, 400); |
||||
export const DEFAULT_PADDLE_SIZE = new Point(30, 50) |
export const DEFAULT_PADDLE_SIZE = new Point(30, 50); |
||||
export const DEFAULT_BALL_SIZE = new Point(10, 10) |
export const DEFAULT_BALL_SIZE = new Point(10, 10); |
||||
export const DEFAULT_BALL_INITIAL_SPEED = new Point(10, 2) |
export const DEFAULT_BALL_INITIAL_SPEED = new Point(10, 2); |
||||
export const DEFAULT_MAX_BALL_SPEED = new Point(30, 20) |
export const DEFAULT_MAX_BALL_SPEED = new Point(30, 20); |
||||
export const DEFAULT_BALL_SPEED_INCREMENT = new Point(0.05, 0) |
export const DEFAULT_BALL_SPEED_INCREMENT = new Point(0.05, 0); |
||||
export const DEFAULT_WIN_SCORE = 5 |
export const DEFAULT_WIN_SCORE = 5; |
||||
export const GAME_TICKS = 30 |
export const GAME_TICKS = 30; |
||||
|
@ -1,18 +1,18 @@ |
|||||
import { Test, type TestingModule } from '@nestjs/testing' |
import { Test, type TestingModule } from "@nestjs/testing"; |
||||
import { PongGateway } from './pong.gateway' |
import { PongGateway } from "./pong.gateway"; |
||||
|
|
||||
describe('PongGateway', () => { |
describe("PongGateway", () => { |
||||
let gateway: PongGateway |
let gateway: PongGateway; |
||||
|
|
||||
beforeEach(async () => { |
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
const module: TestingModule = await Test.createTestingModule({ |
||||
providers: [PongGateway] |
providers: [PongGateway], |
||||
}).compile() |
}).compile(); |
||||
|
|
||||
gateway = module.get<PongGateway>(PongGateway) |
gateway = module.get<PongGateway>(PongGateway); |
||||
}) |
}); |
||||
|
|
||||
it('should be defined', () => { |
it("should be defined", () => { |
||||
expect(gateway).toBeDefined() |
expect(gateway).toBeDefined(); |
||||
}) |
}); |
||||
}) |
}); |
||||
|
@ -1,15 +1,15 @@ |
|||||
import { forwardRef, Module } from '@nestjs/common' |
import { forwardRef, Module } from "@nestjs/common"; |
||||
import { PongGateway } from './pong.gateway' |
import { PongGateway } from "./pong.gateway"; |
||||
import Result from './entity/result.entity' |
import Result from "./entity/result.entity"; |
||||
import { TypeOrmModule } from '@nestjs/typeorm' |
import { TypeOrmModule } from "@nestjs/typeorm"; |
||||
import { PongService } from './pong.service' |
import { PongService } from "./pong.service"; |
||||
import { UsersModule } from 'src/users/users.module' |
import { UsersModule } from "src/users/users.module"; |
||||
import { PongController } from './pong.controller' |
import { PongController } from "./pong.controller"; |
||||
|
|
||||
@Module({ |
@Module({ |
||||
imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])], |
imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])], |
||||
providers: [PongGateway, PongService], |
providers: [PongGateway, PongService], |
||||
controllers: [PongController], |
controllers: [PongController], |
||||
exports: [PongService] |
exports: [PongService], |
||||
}) |
}) |
||||
export class PongModule {} |
export class PongModule {} |
||||
|
@ -1,89 +1,89 @@ |
|||||
import { Injectable } from '@nestjs/common' |
import { Injectable } from "@nestjs/common"; |
||||
import { InjectRepository } from '@nestjs/typeorm' |
import { InjectRepository } from "@nestjs/typeorm"; |
||||
import { Repository } from 'typeorm' |
import { Repository } from "typeorm"; |
||||
import { UsersService } from 'src/users/users.service' |
import { UsersService } from "src/users/users.service"; |
||||
import Result from './entity/result.entity' |
import Result from "./entity/result.entity"; |
||||
import type User from 'src/users/entity/user.entity' |
import type User from "src/users/entity/user.entity"; |
||||
import { type Player } from './game/Player' |
import { type Player } from "./game/Player"; |
||||
import { type PaginateQuery, paginate, type Paginated } from 'nestjs-paginate' |
import { type PaginateQuery, paginate, type Paginated } from "nestjs-paginate"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class PongService { |
export class PongService { |
||||
constructor ( |
constructor( |
||||
@InjectRepository(Result) |
@InjectRepository(Result) |
||||
private readonly resultsRepository: Repository<Result>, |
private readonly resultsRepository: Repository<Result>, |
||||
private readonly usersService: UsersService |
private readonly usersService: UsersService |
||||
) {} |
) {} |
||||
|
|
||||
async updateStats ( |
async updateStats( |
||||
player: User, |
player: User, |
||||
i: number, |
i: number, |
||||
result: Result, |
result: Result, |
||||
maxScore: number |
maxScore: number |
||||
): Promise<void> { |
): Promise<void> { |
||||
player.matchs++ |
player.matchs++; |
||||
if (result.score[i] === maxScore) player.wins++ |
if (result.score[i] === maxScore) player.wins++; |
||||
else player.looses++ |
else player.looses++; |
||||
player.winrate = (100 * player.wins) / player.matchs |
player.winrate = (100 * player.wins) / player.matchs; |
||||
} |
} |
||||
|
|
||||
async updatePlayer ( |
async updatePlayer( |
||||
i: number, |
i: number, |
||||
result: Result, |
result: Result, |
||||
maxScore: number |
maxScore: number |
||||
): Promise<void> { |
): Promise<void> { |
||||
const player: User | null = result.players[i] |
const player: User | null = result.players[i]; |
||||
if (player == null) return |
if (player == null) return; |
||||
if (result.ranked) await this.updateStats(player, i, result, maxScore) |
if (result.ranked) await this.updateStats(player, i, result, maxScore); |
||||
player.results.push(result) |
player.results.push(result); |
||||
player.status = 'online' |
player.status = "online"; |
||||
await this.usersService.save(player) |
await this.usersService.save(player); |
||||
} |
} |
||||
|
|
||||
async setInGame (playerName: string): Promise<void> { |
async setInGame(playerName: string): Promise<void> { |
||||
const player = await this.usersService.findUserByName(playerName) |
const player = await this.usersService.findUserByName(playerName); |
||||
player.status = 'in-game' |
player.status = "in-game"; |
||||
await this.usersService.save(player) |
await this.usersService.save(player); |
||||
} |
} |
||||
|
|
||||
async saveResult ( |
async saveResult( |
||||
players: Player[], |
players: Player[], |
||||
ranked: boolean, |
ranked: boolean, |
||||
maxScore: number |
maxScore: number |
||||
): Promise<void> { |
): Promise<void> { |
||||
const result = new Result() |
const result = new Result(); |
||||
const ply = new Array<User | null>() |
const ply = new Array<User | null>(); |
||||
ply.push(await this.usersService.findUserByName(players[0].name)) |
ply.push(await this.usersService.findUserByName(players[0].name)); |
||||
ply.push(await this.usersService.findUserByName(players[1].name)) |
ply.push(await this.usersService.findUserByName(players[1].name)); |
||||
result.ranked = ranked |
result.ranked = ranked; |
||||
result.players = ply |
result.players = ply; |
||||
result.score = [players[0].score, players[1].score] |
result.score = [players[0].score, players[1].score]; |
||||
await this.resultsRepository.save(result) |
await this.resultsRepository.save(result); |
||||
await this.updatePlayer(0, result, maxScore) |
await this.updatePlayer(0, result, maxScore); |
||||
await this.updatePlayer(1, result, maxScore) |
await this.updatePlayer(1, result, maxScore); |
||||
} |
} |
||||
|
|
||||
async getHistory ( |
async getHistory( |
||||
query: PaginateQuery, |
query: PaginateQuery, |
||||
ftId: number |
ftId: number |
||||
): Promise<Paginated<Result>> { |
): Promise<Paginated<Result>> { |
||||
let queryBuilder |
let queryBuilder; |
||||
if (ftId !== 0) { |
if (ftId !== 0) { |
||||
queryBuilder = this.resultsRepository |
queryBuilder = this.resultsRepository |
||||
.createQueryBuilder('result') |
.createQueryBuilder("result") |
||||
.innerJoin('result.players', 'player', 'player.ftId = :ftId', { ftId }) |
.innerJoin("result.players", "player", "player.ftId = :ftId", { ftId }); |
||||
} else { |
} else { |
||||
queryBuilder = this.resultsRepository |
queryBuilder = this.resultsRepository |
||||
.createQueryBuilder('result') |
.createQueryBuilder("result") |
||||
.where('result.ranked = :ranked', { ranked: true }) |
.where("result.ranked = :ranked", { ranked: true }); |
||||
} |
} |
||||
|
|
||||
return await paginate(query, queryBuilder, { |
return await paginate(query, queryBuilder, { |
||||
nullSort: 'last', |
nullSort: "last", |
||||
relations: ['players'], |
relations: ["players"], |
||||
defaultSortBy: [['date', 'DESC']], |
defaultSortBy: [["date", "DESC"]], |
||||
sortableColumns: ['date'], |
sortableColumns: ["date"], |
||||
maxLimit: 10 |
maxLimit: 10, |
||||
}) |
}); |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,18 +1,18 @@ |
|||||
import { Test, type TestingModule } from '@nestjs/testing' |
import { Test, type TestingModule } from "@nestjs/testing"; |
||||
import { Games } from './game/Games' |
import { Games } from "./game/Games"; |
||||
|
|
||||
describe('Pong', () => { |
describe("Pong", () => { |
||||
let provider: Games |
let provider: Games; |
||||
|
|
||||
beforeEach(async () => { |
beforeEach(async () => { |
||||
const module: TestingModule = await Test.createTestingModule({ |
const module: TestingModule = await Test.createTestingModule({ |
||||
providers: [Games] |
providers: [Games], |
||||
}).compile() |
}).compile(); |
||||
|
|
||||
provider = module.get<Games>(Games) |
provider = module.get<Games>(Games); |
||||
}) |
}); |
||||
|
|
||||
it('should be defined', () => { |
it("should be defined", () => { |
||||
expect(provider).toBeDefined() |
expect(provider).toBeDefined(); |
||||
}) |
}); |
||||
}) |
}); |
||||
|
@ -1,8 +1,8 @@ |
|||||
declare module 'passport-42' { |
declare module "passport-42" { |
||||
export type Profile = any |
export type Profile = any; |
||||
export type VerifyCallback = any |
export type VerifyCallback = any; |
||||
export class Strategy { |
export class Strategy { |
||||
constructor (options: any, verify: any) |
constructor(options: any, verify: any); |
||||
authenticate (req: any, options: any): any |
authenticate(req: any, options: any): any; |
||||
} |
} |
||||
} |
} |
||||
|
@ -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 { ApiProperty } from "@nestjs/swagger"; |
||||
import { Express } from 'express' |
import { Express } from "express"; |
||||
|
|
||||
export class UserDto { |
export class UserDto { |
||||
@IsPositive() |
@IsPositive() |
||||
@IsOptional() |
@IsOptional() |
||||
readonly ftId: number |
readonly ftId: number; |
||||
|
|
||||
@IsString() |
@IsString() |
||||
@IsNotEmpty() |
@IsNotEmpty() |
||||
readonly username: string |
readonly username: string; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
readonly status: string |
readonly status: string; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
readonly avatar: string |
readonly avatar: string; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
readonly authToken: string |
readonly authToken: string; |
||||
|
|
||||
@IsOptional() |
@IsOptional() |
||||
readonly isVerified: boolean |
readonly isVerified: boolean; |
||||
} |
} |
||||
|
|
||||
export class AvatarUploadDto { |
export class AvatarUploadDto { |
||||
@ApiProperty({ type: 'string', format: 'binary' }) |
@ApiProperty({ type: "string", format: "binary" }) |
||||
file: Express.Multer.File |
file: Express.Multer.File; |
||||
} |
} |
||||
|
@ -1,17 +1,15 @@ |
|||||
import { forwardRef, Module } from '@nestjs/common' |
import { forwardRef, Module } from "@nestjs/common"; |
||||
import { TypeOrmModule } from '@nestjs/typeorm' |
import { TypeOrmModule } from "@nestjs/typeorm"; |
||||
import { User } from './entity/user.entity' |
import { User } from "./entity/user.entity"; |
||||
import { UsersController } from './users.controller' |
import { UsersController } from "./users.controller"; |
||||
import { UsersService } from './users.service' |
import { UsersService } from "./users.service"; |
||||
import { PongModule } from 'src/pong/pong.module' |
import { PongModule } from "src/pong/pong.module"; |
||||
import { ChatModule } from 'src/chat/chat.module' |
import { ChatModule } from "src/chat/chat.module"; |
||||
|
|
||||
@Module({ |
@Module({ |
||||
imports: [ |
imports: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])], |
||||
forwardRef(() => PongModule), |
|
||||
TypeOrmModule.forFeature([User])], |
|
||||
controllers: [UsersController], |
controllers: [UsersController], |
||||
providers: [UsersService], |
providers: [UsersService], |
||||
exports: [UsersService] |
exports: [UsersService], |
||||
}) |
}) |
||||
export class UsersModule {} |
export class UsersModule {} |
||||
|
@ -1,182 +1,184 @@ |
|||||
import { BadRequestException, Catch, Injectable } from '@nestjs/common' |
import { BadRequestException, Catch, Injectable } from "@nestjs/common"; |
||||
import { InjectRepository } from '@nestjs/typeorm' |
import { InjectRepository } from "@nestjs/typeorm"; |
||||
import { EntityNotFoundError, QueryFailedError, Repository } from 'typeorm' |
import { EntityNotFoundError, QueryFailedError, Repository } from "typeorm"; |
||||
import { Cron } from '@nestjs/schedule' |
import { Cron } from "@nestjs/schedule"; |
||||
import { randomUUID } from 'crypto' |
import { randomUUID } from "crypto"; |
||||
|
|
||||
import { type UserDto } from './dto/user.dto' |
import { type UserDto } from "./dto/user.dto"; |
||||
import type Channel from 'src/chat/entity/channel.entity' |
import type Channel from "src/chat/entity/channel.entity"; |
||||
import User from './entity/user.entity' |
import User from "./entity/user.entity"; |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
@Catch(QueryFailedError, EntityNotFoundError) |
@Catch(QueryFailedError, EntityNotFoundError) |
||||
export class UsersService { |
export class UsersService { |
||||
constructor ( |
constructor( |
||||
@InjectRepository(User) private readonly usersRepository: Repository<User>, |
@InjectRepository(User) private readonly usersRepository: Repository<User> |
||||
) {} |
) {} |
||||
|
|
||||
async save (user: User): Promise<void> { |
async save(user: User): Promise<void> { |
||||
await this.usersRepository.save(user) |
await this.usersRepository.save(user); |
||||
} |
} |
||||
|
|
||||
async findUsers (): Promise<User[]> { |
async findUsers(): Promise<User[]> { |
||||
const users = await this.usersRepository.find({}) |
const users = await this.usersRepository.find({}); |
||||
users.forEach((usr) => usr.socketKey = '') |
users.forEach((usr) => (usr.socketKey = "")); |
||||
return users |
return users; |
||||
} |
} |
||||
|
|
||||
async findUserByName (username: string): Promise<User> { |
async findUserByName(username: string): Promise<User> { |
||||
const user = await this.usersRepository.findOne({ |
const user = await this.usersRepository.findOne({ |
||||
where: { username }, |
where: { username }, |
||||
relations: { results: true } |
relations: { results: true }, |
||||
}) |
}); |
||||
if (user == null) throw new BadRequestException('User not found.') |
if (user == null) throw new BadRequestException("User not found."); |
||||
return user |
return user; |
||||
} |
} |
||||
|
|
||||
@Cron('0 * * * * *') |
@Cron("0 * * * * *") |
||||
async updateStatus (): Promise<void> { |
async updateStatus(): Promise<void> { |
||||
const users = await this.usersRepository.find({}) |
const users = await this.usersRepository.find({}); |
||||
users.forEach((usr) => { |
users.forEach((usr) => { |
||||
if (Date.now() - usr.lastAccess > 60000) { |
if (Date.now() - usr.lastAccess > 60000) { |
||||
usr.isVerified = false |
usr.isVerified = false; |
||||
usr.status = 'offline' |
usr.status = "offline"; |
||||
this.usersRepository.save(usr).catch((err) => console.log(err)) |
this.usersRepository.save(usr).catch((err) => { |
||||
|
console.log(err); |
||||
|
}); |
||||
} |
} |
||||
}) |
}); |
||||
this.getLeaderboard(); |
this.getLeaderboard(); |
||||
} |
} |
||||
|
|
||||
async findUser (ftId: number): Promise<User | null> { |
async findUser(ftId: number): Promise<User | null> { |
||||
const user = await this.usersRepository.findOneBy({ ftId }) |
const user = await this.usersRepository.findOneBy({ ftId }); |
||||
if (user == null) return null |
if (user == null) return null; |
||||
user.lastAccess = Date.now() |
user.lastAccess = Date.now(); |
||||
if (user.status === 'offline') user.status = 'online' |
if (user.status === "offline") user.status = "online"; |
||||
await this.usersRepository.save(user) |
await this.usersRepository.save(user); |
||||
return user |
return user; |
||||
} |
} |
||||
|
|
||||
async findOnlineUsers (): Promise<User[]> { |
async findOnlineUsers(): Promise<User[]> { |
||||
const users = await this.usersRepository.find({ |
const users = await this.usersRepository.find({ |
||||
where: { status: 'online' } |
where: { status: "online" }, |
||||
}) |
}); |
||||
users.forEach((usr) => usr.socketKey = '') |
users.forEach((usr) => (usr.socketKey = "")); |
||||
return users |
return users; |
||||
} |
} |
||||
|
|
||||
async create (userData: UserDto): Promise<User | null> { |
async create(userData: UserDto): Promise<User | null> { |
||||
try { |
try { |
||||
const newUser = this.usersRepository.create(userData) |
const newUser = this.usersRepository.create(userData); |
||||
newUser.socketKey = randomUUID() |
newUser.socketKey = randomUUID(); |
||||
return await this.usersRepository.save(newUser) |
return await this.usersRepository.save(newUser); |
||||
} catch (err) { |
} catch (err) { |
||||
throw new BadRequestException('User already exists.') |
throw new BadRequestException("User already exists."); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
async findOnlineInChannel (channel: Channel): Promise<User[]> { |
async findOnlineInChannel(channel: Channel): Promise<User[]> { |
||||
return await this.usersRepository |
return await this.usersRepository |
||||
.createQueryBuilder('user') |
.createQueryBuilder("user") |
||||
.where('user.channel = :chan', { chan: channel }) |
.where("user.channel = :chan", { chan: channel }) |
||||
.andWhere('user.status := status)', { status: 'online' }) |
.andWhere("user.status := status)", { status: "online" }) |
||||
.getMany() |
.getMany(); |
||||
} |
} |
||||
|
|
||||
async update (user: User, changes: UserDto): Promise<User | null> { |
async update(user: User, changes: UserDto): Promise<User | null> { |
||||
this.usersRepository.merge(user, changes) |
this.usersRepository.merge(user, changes); |
||||
return await this.usersRepository.save(user) |
return await this.usersRepository.save(user); |
||||
} |
} |
||||
|
|
||||
async addAvatar (ftId: number, filename: string): Promise<void> { |
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[]> { |
async getFriends(ftId: number): Promise<User[]> { |
||||
const user = await this.usersRepository.findOne({ |
const user = await this.usersRepository.findOne({ |
||||
where: { ftId }, |
where: { ftId }, |
||||
relations: { friends: true } |
relations: { friends: true }, |
||||
}) |
}); |
||||
if (user == null) throw new BadRequestException('User not found.') |
if (user == null) throw new BadRequestException("User not found."); |
||||
user.friends.forEach((friend) => friend.socketKey = '') |
user.friends.forEach((friend) => (friend.socketKey = "")); |
||||
return user.friends |
return user.friends; |
||||
} |
} |
||||
|
|
||||
async getInvits (ftId: number): Promise<User[]> { |
async getInvits(ftId: number): Promise<User[]> { |
||||
const user = await this.usersRepository.findOne({ |
const user = await this.usersRepository.findOne({ |
||||
where: { ftId }, |
where: { ftId }, |
||||
relations: { |
relations: { |
||||
followers: true |
followers: true, |
||||
} |
}, |
||||
}) |
}); |
||||
if (user == null) throw new BadRequestException('User not found.') |
if (user == null) throw new BadRequestException("User not found."); |
||||
user.followers.forEach((follower) => follower.socketKey = '') |
user.followers.forEach((follower) => (follower.socketKey = "")); |
||||
return user.followers |
return user.followers; |
||||
} |
} |
||||
|
|
||||
async getLeaderboard (): Promise<User[]> { |
async getLeaderboard(): Promise<User[]> { |
||||
const leaderboard = await this.usersRepository.find({ |
const leaderboard = await this.usersRepository.find({ |
||||
order: { |
order: { |
||||
winrate: 'ASC' |
winrate: "ASC", |
||||
} |
}, |
||||
}) |
}); |
||||
let ret = leaderboard.filter((user) => user.matchs !== 0) |
const ret = leaderboard.filter((user) => user.matchs !== 0); |
||||
let r = 0 |
let r = 0; |
||||
ret.forEach((usr) => { |
ret.forEach((usr) => { |
||||
usr.rank = r++ |
usr.rank = r++; |
||||
this.usersRepository.save(usr) |
this.usersRepository.save(usr); |
||||
usr.socketKey = '' |
usr.socketKey = ""; |
||||
}) |
}); |
||||
return ret |
return ret; |
||||
} |
} |
||||
|
|
||||
async invit (ftId: number, targetFtId: number): Promise<string> { |
async invit(ftId: number, targetFtId: number): Promise<string> { |
||||
const user: User | null = await this.usersRepository.findOne({ |
const user: User | null = await this.usersRepository.findOne({ |
||||
where: { ftId }, |
where: { ftId }, |
||||
relations: { |
relations: { |
||||
followers: true, |
followers: true, |
||||
friends: true |
friends: true, |
||||
} |
}, |
||||
}) |
}); |
||||
if (user === null) throw new BadRequestException('User not found.') |
if (user === null) throw new BadRequestException("User not found."); |
||||
if (user.friends.findIndex((friend) => friend.ftId === targetFtId) !== -1) { |
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({ |
const target: User | null = await this.usersRepository.findOne({ |
||||
where: { ftId: targetFtId }, |
where: { ftId: targetFtId }, |
||||
relations: { |
relations: { |
||||
followers: true, |
followers: true, |
||||
friends: true |
friends: true, |
||||
} |
}, |
||||
}) |
}); |
||||
if (target == null) return 'Target not found.' |
if (target == null) return "Target not found."; |
||||
const id = user.followers.findIndex( |
const id = user.followers.findIndex( |
||||
(follower) => follower.ftId === targetFtId |
(follower) => follower.ftId === targetFtId |
||||
) |
); |
||||
if ( |
if ( |
||||
target.followers.findIndex((follower) => follower.ftId === user.ftId) !== |
target.followers.findIndex((follower) => follower.ftId === user.ftId) !== |
||||
-1 |
-1 |
||||
) { |
) { |
||||
return 'Invitation already sent.' |
return "Invitation already sent."; |
||||
} else if ( |
} else if ( |
||||
user.followers.findIndex((follower) => follower.ftId === targetFtId) !== |
user.followers.findIndex((follower) => follower.ftId === targetFtId) !== |
||||
-1 |
-1 |
||||
) { |
) { |
||||
user.friends.push(target) |
user.friends.push(target); |
||||
target.friends.push(user) |
target.friends.push(user); |
||||
user.followers.splice(id, 1) |
user.followers.splice(id, 1); |
||||
await this.usersRepository.save(user) |
await this.usersRepository.save(user); |
||||
} else target.followers.push(user) |
} else target.followers.push(user); |
||||
await this.usersRepository.save(target) |
await this.usersRepository.save(target); |
||||
return 'OK' |
return "OK"; |
||||
} |
} |
||||
|
|
||||
async findByCode (code: string): Promise<User> { |
async findByCode(code: string): Promise<User> { |
||||
const user = await this.usersRepository.findOneBy({ authToken: code }) |
const user = await this.usersRepository.findOneBy({ authToken: code }); |
||||
if (user == null) throw new BadRequestException('User not found') |
if (user == null) throw new BadRequestException("User not found"); |
||||
return user |
return user; |
||||
} |
} |
||||
|
|
||||
async turnOnTwoFactorAuthentication (ftId: number): Promise<void> { |
async turnOnTwoFactorAuthentication(ftId: number): Promise<void> { |
||||
await this.usersRepository.update({ ftId }, { twoFA: true }) |
await this.usersRepository.update({ ftId }, { twoFA: true }); |
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,5 @@ |
|||||
|
import ioClient from "socket.io-client"; |
||||
|
|
||||
|
export const io = ioClient("http://localhost:3001", { |
||||
|
withCredentials: true, |
||||
|
}); |
Loading…
Reference in new issue