nicolas-arnaud
2 years ago
58 changed files with 1757 additions and 1740 deletions
@ -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 |
@ -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,25 +1,25 @@ |
|||||
import { forwardRef, Module } from "@nestjs/common"; |
import { forwardRef, Module } from '@nestjs/common' |
||||
import { TypeOrmModule } from "@nestjs/typeorm"; |
import { TypeOrmModule } from '@nestjs/typeorm' |
||||
|
|
||||
import { AuthModule } from "src/auth/auth.module"; |
import { AuthModule } from 'src/auth/auth.module' |
||||
import { UsersModule } from "src/users/users.module"; |
import { UsersModule } from 'src/users/users.module' |
||||
import { ChatGateway } from "./chat.gateway"; |
import { ChatGateway } from './chat.gateway' |
||||
import { ChatController } from "./chat.controller"; |
import { ChatController } from './chat.controller' |
||||
import { ChatService } from "./chat.service"; |
import { ChatService } from './chat.service' |
||||
import { MessageService } from "./message.service"; |
import { MessageService } from './message.service' |
||||
|
|
||||
import Channel from "./entity/channel.entity"; |
import Channel from './entity/channel.entity' |
||||
import Message from "./entity/message.entity"; |
import Message from './entity/message.entity' |
||||
import ConnectedUser from "./entity/connection.entity"; |
import ConnectedUser from './entity/connection.entity' |
||||
|
|
||||
@Module({ |
@Module({ |
||||
imports: [ |
imports: [ |
||||
UsersModule, |
UsersModule, |
||||
AuthModule, |
AuthModule, |
||||
TypeOrmModule.forFeature([Channel, Message, ConnectedUser]), |
TypeOrmModule.forFeature([Channel, Message, ConnectedUser]) |
||||
], |
], |
||||
controllers: [ChatController], |
controllers: [ChatController], |
||||
providers: [ChatService, ChatGateway, MessageService], |
providers: [ChatService, ChatGateway, MessageService], |
||||
exports: [ChatService], |
exports: [ChatService] |
||||
}) |
}) |
||||
export class ChatModule {} |
export class ChatModule {} |
||||
|
@ -1,173 +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(); |
const newChannel = new Channel() |
||||
newChannel.owner = user; |
newChannel.owner = user |
||||
newChannel.users = [user]; |
newChannel.users = [user] |
||||
newChannel.admins = [user]; |
newChannel.admins = [user] |
||||
newChannel.name = channel.name; |
newChannel.name = channel.name |
||||
newChannel.isPrivate = channel.isPrivate; |
newChannel.isPrivate = channel.isPrivate |
||||
newChannel.password = channel.password; |
newChannel.password = channel.password |
||||
return await this.ChannelRepository.save(newChannel); |
return await this.ChannelRepository.save(newChannel) |
||||
} |
} |
||||
|
|
||||
async updatePassword(id: number, password: string) { |
async updatePassword (id: number, password: string) { |
||||
const channel: Channel | null = await this.ChannelRepository.findOneBy({ |
const channel: Channel | null = await this.ChannelRepository.findOneBy({ |
||||
id, |
id |
||||
}); |
}) |
||||
if (channel === null) { |
if (channel === null) { |
||||
throw new NotFoundException(`Channel #${id} not found`); |
throw new NotFoundException(`Channel #${id} not found`) |
||||
} |
} |
||||
channel.password = password; |
channel.password = password |
||||
await this.ChannelRepository.save(channel); |
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> { |
||||
const channels = await this.ChannelRepository.find({}); |
const channels = await this.ChannelRepository.find({}) |
||||
channels.forEach((channel) => { |
channels.forEach((channel) => { |
||||
channel.muted = channel.muted.filter((data) => { |
channel.muted = channel.muted.filter((data) => { |
||||
return data[0] - Date.now() > 0; |
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) { |
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> { |
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) { |
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) { |
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) { |
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> { |
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) { |
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> { |
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) { |
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> { |
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) { |
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> { |
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) { |
if (channel == null) { |
||||
throw new NotFoundException(`Channel #${id} not found`); |
throw new NotFoundException(`Channel #${id} not found`) |
||||
} |
} |
||||
|
|
||||
const mutation: number[] | undefined = channel.muted.find( |
const mutation: number[] | undefined = channel.muted.find( |
||||
(mutation) => mutation[0] === userId |
(mutation) => mutation[0] === userId |
||||
); |
) |
||||
if (mutation == null) { |
if (mutation == null) { |
||||
return 0; |
return 0 |
||||
} |
} |
||||
return mutation[1]; |
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,15 +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: number[]; |
data: number[] |
||||
} |
} |
||||
|
@ -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,43 +1,43 @@ |
|||||
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 { ChatService } from "./chat.service"; |
import { ChatService } from './chat.service' |
||||
import { UsersService } from "src/users/users.service"; |
import { UsersService } from 'src/users/users.service' |
||||
|
|
||||
import { type CreateMessageDto } from "./dto/create-message.dto"; |
import { type CreateMessageDto } from './dto/create-message.dto' |
||||
import type User from "src/users/entity/user.entity"; |
import type User from 'src/users/entity/user.entity' |
||||
import type Channel from "./entity/channel.entity"; |
import type Channel from './entity/channel.entity' |
||||
import Message from "./entity/message.entity"; |
import Message from './entity/message.entity' |
||||
|
|
||||
@Injectable() |
@Injectable() |
||||
export class MessageService { |
export class MessageService { |
||||
constructor( |
constructor ( |
||||
@InjectRepository(Message) |
@InjectRepository(Message) |
||||
private readonly MessageRepository: Repository<Message>, |
private readonly MessageRepository: Repository<Message>, |
||||
private readonly channelService: ChatService, |
private readonly channelService: ChatService, |
||||
private readonly usersService: UsersService |
private readonly usersService: UsersService |
||||
) {} |
) {} |
||||
|
|
||||
async createMessage(message: CreateMessageDto): Promise<Message> { |
async createMessage (message: CreateMessageDto): Promise<Message> { |
||||
const msg = new Message(); |
const msg = new Message() |
||||
msg.text = message.text; |
msg.text = message.text |
||||
msg.channel = await this.channelService.getChannel(message.ChannelId); |
msg.channel = await this.channelService.getChannel(message.ChannelId) |
||||
msg.author = (await this.usersService.findUser(message.UserId)) as User; |
msg.author = (await this.usersService.findUser(message.UserId)) as User |
||||
msg.channel.messages.push(msg); |
msg.channel.messages.push(msg) |
||||
return await this.MessageRepository.save( |
return await this.MessageRepository.save( |
||||
this.MessageRepository.create(msg) |
this.MessageRepository.create(msg) |
||||
); |
) |
||||
} |
} |
||||
|
|
||||
async findMessagesInChannelForUser( |
async findMessagesInChannelForUser ( |
||||
channel: Channel, |
channel: Channel, |
||||
user: User |
user: User |
||||
): Promise<Message[]> { |
): Promise<Message[]> { |
||||
return await this.MessageRepository.createQueryBuilder("message") |
return await this.MessageRepository.createQueryBuilder('message') |
||||
.where("message.channel = :chan", { chan: channel }) |
.where('message.channel = :chan', { chan: channel }) |
||||
.andWhere("message.author NOT IN (:...blocked)", { |
.andWhere('message.author NOT IN (:...blocked)', { |
||||
blocked: user.blocked, |
blocked: user.blocked |
||||
}) |
}) |
||||
.getMany(); |
.getMany() |
||||
} |
} |
||||
} |
} |
||||
|
@ -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,15 +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: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])], |
imports: [forwardRef(() => PongModule), TypeOrmModule.forFeature([User])], |
||||
controllers: [UsersController], |
controllers: [UsersController], |
||||
providers: [UsersService], |
providers: [UsersService], |
||||
exports: [UsersService], |
exports: [UsersService] |
||||
}) |
}) |
||||
export class UsersModule {} |
export class UsersModule {} |
||||
|
@ -1,184 +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) => { |
this.usersRepository.save(usr).catch((err) => { |
||||
console.log(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' |
||||
}, |
} |
||||
}); |
}) |
||||
const 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 }) |
||||
} |
} |
||||
} |
} |
||||
|
Loading…
Reference in new issue