Browse Source

routes refactoring and chat fetchs start to work

delete channel and chatServices to check is a user is...

fixed friends invitations

LINT

lint
master
nicolas-arnaud 2 years ago
parent
commit
b84ea3481d
  1. 142
      back/volume/src/chat/chat.controller.ts
  2. 9
      back/volume/src/chat/chat.module.ts
  3. 138
      back/volume/src/chat/chat.service.ts
  4. 13
      back/volume/src/chat/dto/connection.dto.ts
  5. 27
      back/volume/src/chat/dto/create-channel.dto.ts
  6. 8
      back/volume/src/chat/dto/create-message.dto.ts
  7. 30
      back/volume/src/chat/dto/update-channel.dto.ts
  8. 55
      back/volume/src/chat/entity/channel.entity.ts
  9. 16
      back/volume/src/chat/entity/connection.entity.ts
  10. 15
      back/volume/src/chat/entity/dm.entity.ts
  11. 15
      back/volume/src/chat/entity/message.entity.ts
  12. 33
      back/volume/src/pong/pong.controller.ts
  13. 2
      back/volume/src/pong/pong.module.ts
  14. 6
      back/volume/src/users/entity/user.entity.ts
  15. 41
      back/volume/src/users/users.controller.ts
  16. 9
      back/volume/src/users/users.service.ts
  17. 2
      front/volume/src/App.svelte
  18. 4
      front/volume/src/Auth.ts
  19. 2
      front/volume/src/FakeLogin.svelte
  20. 88
      front/volume/src/components/Channels.svelte
  21. 22
      front/volume/src/components/Chat.svelte
  22. 6
      front/volume/src/components/Friends.svelte
  23. 2
      front/volume/src/components/Leaderboard.svelte
  24. 19
      front/volume/src/components/MatchHistory.svelte
  25. 2
      front/volume/src/components/NavBar.svelte
  26. 16
      front/volume/src/components/Profile.svelte

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

@ -1,99 +1,97 @@
import { import {
BadRequestException,
Body, Body,
Controller, Controller,
Delete, Delete,
Get, Get,
NotFoundException, NotFoundException,
Param, Param,
ParseIntPipe,
Post, Post,
} from "@nestjs/common"; UseGuards
//import { channel, Channel } from "diagnostics_channel"; } from '@nestjs/common'
import { Channel } from './entity/channel.entity'; import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import { ChannelService } from "./chat.service"; import { UsersService } from 'src/users/users.service'
import { CreateChannelDto } from "./dto/create-channel.dto"; import { ChannelService } from './chat.service'
import { UsersService } from "src/users/users.service";
import { UpdateChannelDto } from "./dto/update-channel.dto";
import { User } from "src/users/entity/user.entity";
@Controller("chat") import { CreateChannelDto } from './dto/create-channel.dto'
export class ChatController { import { UpdateChannelDto } from './dto/update-channel.dto'
private readonly channelService: ChannelService;
private readonly usersService: UsersService;
@Get("channels/:id") import type User from 'src/users/entity/user.entity'
getChannelsForUser(@Param("id") id: number): Promise<Array<Channel>> { import type Channel from './entity/channel.entity'
return this.channelService.getChannelsForUser(id); import { Profile42 } from 'src/auth/42.decorator'
} import { Profile } from 'passport-42'
@Post("channels") @Controller('channels')
async createChannel(@Body() channel: CreateChannelDto) { export class ChatController {
return await this.channelService.createChannel(channel); constructor (
} private readonly channelService: ChannelService,
private readonly usersService: UsersService
) {}
@Delete("channels/:id") @Post(':id/admin')
async deleteChannel(@Param("id") id: number) { async addAdmin (@Param('id') id: number, @Body() userId: number) {
return await this.channelService.removeChannel(id); const channel = await this.channelService.getChannel(id)
const user: User | null = await this.usersService.findUser(userId)
if (user == null) throw new NotFoundException(`User #${userId} not found`)
channel.admins.push(user)
this.channelService.update(channel)
} }
@Post("channels/:id/owner") @Delete(':id/admin')
async moveOwner(@Param("id") id: number, @Body() userId: number) { async removeAdmin (@Param('id') id: number, @Body() userId: number) {
const channel = await this.channelService.getChannel(id); const channel = await this.channelService.getChannel(id)
const user: User | null = await this.usersService.findUser(userId); channel.admins = channel.admins.filter((usr: User) => {
if (user == null) throw new NotFoundException(`User #${userId} not found`); return usr.ftId !== userId
channel.owner = user; })
this.channelService.update(channel); this.channelService.update(channel)
} }
@Post("channels/:id/admin") @Post(':id/ban')
async addAdmin(@Param("id") id: number, @Body() userId: number) { async addBan (@Param('id') id: number, @Body() userId: number) {
const channel = await this.channelService.getChannel(id); const channel = await this.channelService.getChannel(id)
const user: User | null = await this.usersService.findUser(userId); const user: User | null = await this.usersService.findUser(userId)
if (user == null) throw new NotFoundException(`User #${userId} not found`); if (user == null) throw new NotFoundException(`User #${userId} not found`)
channel.admins.push(user); channel.banned.push(user)
this.channelService.update(channel); this.channelService.update(channel)
} }
@Delete("channels/:id/admin") @Post(':id/mute')
async removeAdmin(@Param("id") id: number, @Body() userId: number) { async addMute (@Param('id') id: number, @Body() userId: number) {
const channel = await this.channelService.getChannel(id); const channel = await this.channelService.getChannel(id)
channel.admins = channel.admins.filter((a) => { const user: User | null = await this.usersService.findUser(userId)
return a.ftId !== userId; if (user == null) throw new NotFoundException(`User #${userId} not found`)
}); channel.muted.push(user)
this.channelService.update(channel); this.channelService.update(channel)
} }
@Post("channels/:id/ban") @Delete(':id/mute')
async addBan(@Param("id") id: number, @Body() userId: number) { async removeMute (@Param('id') id: number, @Body() userId: number) {
const channel = await this.channelService.getChannel(id); const channel = await this.channelService.getChannel(id)
const user: User | null = await this.usersService.findUser(userId); channel.muted = channel.muted.filter((usr: User) => {
if (user == null) throw new NotFoundException(`User #${userId} not found`); return usr.ftId !== userId
channel.banned.push(user); })
this.channelService.update(channel); this.channelService.update(channel)
} }
@Delete("channels/:id/ban") @Delete(':id')
async removeBan(@Param("id") id: number, @Body() userId: number) { @UseGuards(AuthenticatedGuard)
const channel = await this.channelService.getChannel(id); async deleteChannel (@Profile42() profile: Profile, @Param('id') id: number) {
channel.banned = channel.banned.filter((a) => { if (await this.channelService.isOwner(id, +profile.id)) {
return a.ftId !== userId; await this.channelService.removeChannel(id)
}); return
this.channelService.update(channel); }
throw new BadRequestException('You are not the owner of this channel')
} }
@Post("channels/:id/mute") @Get()
async addMute(@Param("id") id: number, @Body() userId: number) { @UseGuards(AuthenticatedGuard)
const channel = await this.channelService.getChannel(id); async getChannelsForUser (@Profile42() profile: Profile): Promise<Channel[]> {
const user: User | null = await this.usersService.findUser(userId); return await this.channelService.getChannelsForUser(profile.id)
if (user == null) throw new NotFoundException(`User #${userId} not found`);
channel.mute.push(user);
this.channelService.update(channel);
} }
@Delete("channels/:id/mute")
async removeMute(@Param("id") id: number, @Body() userId: number) { @Post()
const channel = await this.channelService.getChannel(id); async createChannel (@Body() channel: CreateChannelDto) {
channel.mute = channel.mute.filter((a) => { return await this.channelService.createChannel(channel)
return a.ftId !== userId;
});
this.channelService.update(channel);
} }
} }

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

@ -3,11 +3,12 @@ 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 { ChannelService } from './chat.service' import { ChannelService } from './chat.service'
import { Channel } from './entity/channel.entity'
import { Message } from './entity/message.entity' import Channel from './entity/channel.entity'
import Message from './entity/message.entity'
@Module({ @Module({
imports: [ imports: [
@ -17,6 +18,6 @@ import { Message } from './entity/message.entity'
TypeOrmModule.forFeature([Message]) TypeOrmModule.forFeature([Message])
], ],
controllers: [ChatController], controllers: [ChatController],
providers: [/*ChatGateway, */ChannelService] providers: [ChannelService]
}) })
export class ChatModule {} export class ChatModule {}

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

@ -1,56 +1,118 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm'
import { Channel } from './entity/channel.entity'; import { Repository } from 'typeorm'
import { User } from 'src/users/entity/user.entity';
import { Repository } from 'typeorm'; import { type CreateChannelDto } from './dto/create-channel.dto'
import { 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 Channel from './entity/channel.entity'
import { classToPlain, plainToClass } from 'class-transformer'
@Injectable() @Injectable()
export class ChannelService { export class ChannelService {
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 newChannel = this.ChannelRepository.create({ const user: User | null = await this.usersService.findUser(channel.owner)
name: channel.name, if (user == null) {
password: channel.password, throw new NotFoundException(`User #${channel.owner} not found`)
}); }
let user: User| null = await this.usersService.findUser(channel.owner); const newChannel = new Channel()
if (user == null) throw new NotFoundException(`User #${channel.owner} not found`) newChannel.owner = user
newChannel.owner = user; newChannel.users = [user]
return await this.ChannelRepository.save(newChannel); newChannel.admins = [user]
newChannel.name = channel.name
newChannel.isPrivate = channel.isPrivate
newChannel.password = channel.password
return await this.ChannelRepository.save(newChannel)
}
async getChannelsForUser (ftId: number): Promise<Channel[]> {
let rooms: Channel[] = []
rooms = [
...(await this.ChannelRepository.createQueryBuilder('room')
.where('room.isPrivate = false')
.getMany())
]
rooms = [
...rooms,
...(await this.ChannelRepository.createQueryBuilder('room')
.innerJoin('room.users', 'users')
.where('room.isPrivate = true')
.andWhere('users.ftId = :ftId', { ftId })
.getMany())
]
return rooms
} }
async getChannelsForUser(ftId: number): Promise<Array<Channel>> { async addUserToChannel (channel: Channel, user: User): Promise<Channel> {
const query = await this.ChannelRepository.createQueryBuilder('room') channel.owner = user
.innerJoin('room.users', 'users') return await this.ChannelRepository.save(channel)
.where('users.ftId = :ftId', { ftId })
.leftJoinAndSelect('room.users', 'all_users')
.orderBy('room.id', 'DESC') // TODO: order by last message
.getRawMany();
return query; //where userId is in User[] of channel?
} }
async addUserToChannel(channel: Channel, user: User): Promise<Channel> { async getChannel (id: number): Promise<Channel> {
channel.owner = user; const channel = await this.ChannelRepository.findOneBy({ id })
return await this.ChannelRepository.save(channel); if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel
} }
async getChannel(id: number): Promise<Channel> { async update (channel: Channel) {
const channel = await this.ChannelRepository.findOneBy({ id }); this.ChannelRepository.update(channel.id, channel)
if (!channel) throw new NotFoundException(`Channel #${id} not found`);
return channel;
} }
async update(channel: Channel) { async removeChannel (channelId: number) {
this.ChannelRepository.update(channel.id, channel); await this.ChannelRepository.delete(channelId)
} }
async removeChannel(id: number) {
await this.ChannelRepository.delete(id); async isOwner (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { owner: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
console.log(channel.owner.ftId, userId)
return channel.owner.ftId == userId
} }
}
async isAdmin (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { admins: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.admins.findIndex((user) => user.ftId == userId) != -1
}
async isUser (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { users: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.users.findIndex((user) => user.ftId == userId) != -1
}
async isBanned (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { banned: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.banned.findIndex((user) => user.ftId == userId) != -1
}
async isMuted (id: number, userId: number): Promise<boolean> {
const channel = await this.ChannelRepository.findOne({
where: { id },
relations: { muted: true }
})
if (channel == null) { throw new NotFoundException(`Channel #${id} not found`) }
return channel.muted.findIndex((user) => user.ftId == userId) != -1
}
}

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

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

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

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

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

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

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

@ -1,20 +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 { 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()
users: [number]; @IsNumber()
users: [number]
@IsOptional() @IsOptional()
messages: [number]; @IsNumber()
messages: [number]
@IsOptional() @IsOptional()
owners: [number]; //user id @IsNumber()
owners: [number] // user id
@IsOptional() @IsOptional()
banned: [number]; //user id @IsNumber()
banned: [number] // user id
@IsOptional() @IsOptional()
muted: [number]; //user id @IsNumber()
muted: [number] // user id
@IsString() @IsString()
@IsOptional() @IsOptional()
password: string; password: string
} }

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

@ -2,31 +2,39 @@ import {
BeforeInsert, BeforeInsert,
Column, Column,
Entity, Entity,
JoinColumn,
JoinTable, JoinTable,
ManyToMany, ManyToMany,
ManyToOne,
OneToMany, OneToMany,
OneToOne,
PrimaryGeneratedColumn PrimaryGeneratedColumn
} from 'typeorm' } from 'typeorm'
import User from 'src/users/entity/user.entity'
import Message from './message.entity'
import * as bcrypt from 'bcrypt' import * as bcrypt from 'bcrypt'
import { User } from 'src/users/entity/user.entity'
import { Message } from './message.entity'
@Entity() @Entity()
export class Channel { export default class Channel {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number id: number
@Column() @Column()
name: string name: string
@ManyToMany(() => User) @Column({ default: false })
@JoinTable() isPrivate: boolean
owner: User
@ManyToMany(() => User) @Column({ select: false, default: '' })
@JoinTable() password: string
admins: User[]
@BeforeInsert()
async hashPassword () {
this.password = await bcrypt.hash(
this.password,
Number(process.env.HASH_SALT)
)
}
@ManyToMany(() => User) @ManyToMany(() => User)
@JoinTable() @JoinTable()
@ -35,22 +43,19 @@ export class Channel {
@OneToMany(() => Message, (message: Message) => message.channel) @OneToMany(() => Message, (message: Message) => message.channel)
messages: Message[] messages: Message[]
@OneToMany(() => User, (user: User) => user.id) // refuse connection @ManyToOne(() => User)
banned: User[] @JoinColumn()
owner: User
@OneToMany(() => User, (user: User) => user.id) // refuse post @ManyToMany(() => User)
mute: User[] @JoinTable()
admins: User[]
@Column({ select: false }) @ManyToMany(() => User) // refuse connection
password: string @JoinTable()
banned: User[]
@BeforeInsert() @ManyToMany(() => User) // refuse post
async hashPassword () { @JoinTable()
this.password = await bcrypt.hash( muted: User[]
this.password,
Number(process.env.HASH_SALT)
)
}
} }
export default Channel

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

@ -0,0 +1,16 @@
import { Column, Entity, OneToOne } from 'typeorm'
import Channel from './channel.entity'
import User from 'src/users/entity/user.entity'
@Entity()
export class connectedUser {
@OneToOne(() => User)
user: User
@OneToOne(() => Channel, (channel) => channel.id)
channel: Channel
@Column()
socket: string
}

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

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

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

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

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

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

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

@ -4,10 +4,12 @@ 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'
@Module({ @Module({
imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])], imports: [forwardRef(() => UsersModule), TypeOrmModule.forFeature([Result])],
providers: [PongGateway, PongService], providers: [PongGateway, PongService],
controllers: [PongController],
exports: [PongService] exports: [PongService]
}) })
export class PongModule {} export class PongModule {}

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

@ -65,12 +65,6 @@ export class User {
@JoinTable() @JoinTable()
results: Result[] results: Result[]
@OneToMany(() => Message, (message: Message) => message.author)
messages: Message[]
@ManyToMany(() => Channel, (channel: Channel) => channel.users)
rooms: Channel[]
@ManyToMany(() => User) @ManyToMany(() => User)
@JoinTable() @JoinTable()
blocked: User[] blocked: User[]

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

@ -13,7 +13,6 @@ import {
BadRequestException, BadRequestException,
Redirect Redirect
} from '@nestjs/common' } from '@nestjs/common'
import { PaginateQuery, type Paginated, Paginate } from 'nestjs-paginate'
import { FileInterceptor } from '@nestjs/platform-express' import { FileInterceptor } from '@nestjs/platform-express'
import { diskStorage } from 'multer' import { diskStorage } from 'multer'
@ -31,14 +30,10 @@ import { ApiBody, ApiConsumes } from '@nestjs/swagger'
import { type Request, Response } from 'express' import { type Request, Response } from 'express'
import { createReadStream } from 'fs' import { createReadStream } from 'fs'
import { join } from 'path' import { join } from 'path'
import type Result from 'src/pong/entity/result.entity'
@Controller() @Controller('users')
export class UsersController { export class UsersController {
constructor ( constructor (private readonly usersService: UsersService) {}
private readonly usersService: UsersService,
private readonly pongService: PongService
) {}
@Get('all') @Get('all')
async getAllUsers (): Promise<User[]> { async getAllUsers (): Promise<User[]> {
@ -68,29 +63,12 @@ export class UsersController {
return await this.usersService.getLeaderboard() return await this.usersService.getLeaderboard()
} }
@Get('rank/:id') @Get(':id/rank')
@UseGuards(AuthenticatedGuard) @UseGuards(AuthenticatedGuard)
async getRank (@Param('id', ParseIntPipe) id: number): Promise<number> { async getRank (@Param('id', ParseIntPipe) id: number): Promise<number> {
return await this.usersService.getRank(id) return await this.usersService.getRank(id)
} }
@Get('globalHistory')
@UseGuards(AuthenticatedGuard)
async getGlobalHistory (
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, 0)
}
@Get('history/:id')
@UseGuards(AuthenticatedGuard)
async getHistoryById (
@Param('id', ParseIntPipe) id: number,
@Paginate() query: PaginateQuery
): Promise<Paginated<Result>> {
return await this.pongService.getHistory(query, id)
}
@Post('avatar') @Post('avatar')
@UseGuards(AuthenticatedGuard) @UseGuards(AuthenticatedGuard)
@Redirect('http://localhost') @Redirect('http://localhost')
@ -130,16 +108,17 @@ export class UsersController {
return await this.getAvatarById(profile.id, response) return await this.getAvatarById(profile.id, response)
} }
@Get('user/:name') @Get(':name/byname')
async getUserByName (@Param('name') username: string): Promise<User> { async getUserByName (@Param('name') username: string): Promise<User> {
const user = await this.usersService.findUserByName(username) const user = await this.usersService.findUserByName(username)
user.socketKey = '' user.socketKey = ''
return user return user
} }
@Get('invit/:id/:username') @Get('invit/:username')
@UseGuards(AuthenticatedGuard)
async invitUser ( async invitUser (
@Param('id', ParseIntPipe) id: number, @Profile42() profile: Profile,
@Param('username') username: string @Param('username') username: string
): Promise<void> { ): Promise<void> {
const target: User | null = await this.usersService.findUserByName( const target: User | null = await this.usersService.findUserByName(
@ -148,14 +127,14 @@ export class UsersController {
if (target === null) { if (target === null) {
throw new BadRequestException(`User ${username} not found.`) throw new BadRequestException(`User ${username} not found.`)
} }
if (id === target.ftId) { if (profile.id === target.ftId) {
throw new BadRequestException("You can't invite yourself.") throw new BadRequestException("You can't invite yourself.")
} }
const ret: string = await this.usersService.invit(id, target.ftId) const ret: string = await this.usersService.invit(profile.id, target.ftId)
if (ret !== 'OK') throw new BadRequestException(ret) if (ret !== 'OK') throw new BadRequestException(ret)
} }
@Get('avatar/:id') @Get(':id/avatar')
async getAvatarById ( async getAvatarById (
@Param('id', ParseIntPipe) ftId: number, @Param('id', ParseIntPipe) ftId: number,
@Res({ passthrough: true }) response: Response @Res({ passthrough: true }) response: Response

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

@ -1,12 +1,13 @@
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 { User } from './entity/user.entity'
import { type UserDto } from './dto/user.dto'
import { type Channel } from 'src/chat/entity/channel.entity'
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 Channel from 'src/chat/entity/channel.entity'
import User from './entity/user.entity'
@Injectable() @Injectable()
@Catch(QueryFailedError, EntityNotFoundError) @Catch(QueryFailedError, EntityNotFoundError)
export class UsersService { export class UsersService {
@ -167,7 +168,7 @@ export class UsersService {
) { ) {
user.friends.push(target) user.friends.push(target)
target.friends.push(user) target.friends.push(user)
user.followers = user.followers.slice(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)

2
front/volume/src/App.svelte

@ -148,7 +148,7 @@
</div> </div>
{:else} {:else}
<div on:click={resetAppState} on:keydown={resetAppState}> <div on:click={resetAppState} on:keydown={resetAppState}>
<Channels {channels} onSelectChannel={handleSelectChannel} /> <Channels onSelectChannel={handleSelectChannel} />
</div> </div>
{/if} {/if}
{/if} {/if}

4
front/volume/src/Auth.ts

@ -11,8 +11,8 @@ export const API_URL = `http://${import.meta.env.VITE_HOST}:${
import.meta.env.VITE_BACK_PORT import.meta.env.VITE_BACK_PORT
}`; }`;
export async function getUser() { export async function getUser(bypass: boolean = false) {
const res = await fetch(API_URL, { const res = await fetch(API_URL + "/users", {
method: "get", method: "get",
mode: "cors", mode: "cors",
cache: "no-cache", cache: "no-cache",

2
front/volume/src/FakeLogin.svelte

@ -5,7 +5,7 @@
export let ftId; export let ftId;
async function doPost() { async function doPost() {
await fetch(API_URL + "/" + ftId, { await fetch(API_URL + "/users/" + ftId, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",

88
front/volume/src/components/Channels.svelte

@ -1,34 +1,31 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import type { chatMessagesType } from "./Chat.svelte";
export interface ChannelsType { export interface ChannelsType {
id: string; id: number;
name: string; name: string;
privacy: string; isPrivate: boolean;
password: string; password: string;
owner: string; owner: number;
} }
import { onMount } from "svelte"; import { onMount } from "svelte";
import { API_URL, store } from "../Auth"; import { API_URL, store } from "../Auth";
import { dataset_dev } from "svelte/internal";
</script> </script>
<script lang="ts"> <script lang="ts">
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
export let channels: Array<ChannelsType> = []; let channels: Array<ChannelsType> = [];
// onMount(async () => { onMount(async () => {
// const res = await fetch(API_URL + "/channels" + $store.ftId, { const res = await fetch(API_URL + "/channels", {
// method: "GET", credentials: "include",
// mode: "cors", mode: "cors",
// }); });
// const data = await res.json(); if (res.ok) channels = await res.json();
// channels = data; });
// });
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
export let onSelectChannel: (channel: ChannelsType) => void; export let onSelectChannel: (channel: ChannelsType) => void;
const selectChat = (id: string) => { const selectChat = (id: number) => {
const channel = channels.find((c) => c.id === id); const channel = channels.find((c) => c.id === id);
if (channel) { if (channel) {
onSelectChannel(channel); onSelectChannel(channel);
@ -36,7 +33,6 @@
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const createChannel = async () => { const createChannel = async () => {
const name = prompt("Enter a name for the new channel:"); const name = prompt("Enter a name for the new channel:");
if (name) { if (name) {
@ -54,47 +50,42 @@
} }
} }
if (privacy === "public" || password) { if (privacy === "public" || password) {
const newChannel: ChannelsType = { const response = await fetch(API_URL + "/channels", {
id: Math.random().toString(), credentials: "include",
name, method: "POST",
owner: $store.username, mode: "cors",
password, headers: {
privacy, "Content-Type": "application/json",
}; },
// const response = await fetch(API_URL + "/channels" + $store.ftId , { body: JSON.stringify({
// method: "POST", name: name,
// mode: "cors", owner: $store.ftId,
// body: JSON.stringify(newChannel), password: password || "",
// }); isPrivate: privacy === "private",
// const data = await response.json(); }),
// if (data.ok) { });
// channels = [newChannel, ...channels]; if (response.ok) {
// } else { channels.push(await response.json());
// alert("Error creating channel"); } else {
// } alert("Error creating channel");
channels = [newChannel, ...channels]; }
} }
} }
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const removeChannel = async (id: string) => { const removeChannel = async (id: number) => {
let string = prompt("type 'delete' to delete this channel"); let string = prompt("type 'delete' to delete this channel");
if (string === "delete") { if (string === "delete") {
// const response = await fetch(API_URL + "/channels" + $store.ftId + "/" + id, { const response = await fetch(API_URL + "/channels/" + id, {
// method: "DELETE", credentials: "include",
// mode: "cors", method: "DELETE",
// }); mode: "cors",
// const data = await response.json(); });
// if (data.ok) { if (response.ok) channels = channels.filter((c) => c.id !== id);
// channels = channels.filter((c) => c.id !== id); else alert("Error deleting channel");
// } else {
// alert("Error deleting channel");
// }
channels = channels.filter((c) => c.id !== id);
} }
// TODO: save to database
}; };
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
@ -134,7 +125,6 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.channels { .channels {
background-color: #fff; background-color: #fff;
border: 1px solid #ccc; border: 1px solid #ccc;

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

@ -75,7 +75,7 @@
// let chatMembers: Array<Player> = []; // let chatMembers: Array<Player> = [];
// async function getChatMembers() { // async function getChatMembers() {
// console.log("Getting chat members"); // console.log("Getting chat members");
// const res = await fetch(API_URL + "/chat/members", { // const res = await fetch(API_URL + "/channels/members", {
// mode: "cors", // mode: "cors",
// }); // });
// chatMembers = await res.json(); // chatMembers = await res.json();
@ -97,11 +97,11 @@
const banUser = async (username: string) => { const banUser = async (username: string) => {
// const prompt = window.prompt("Enter ban duration in seconds"); // const prompt = window.prompt("Enter ban duration in seconds");
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/ban", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/ban", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -117,11 +117,11 @@
const kickUser = async (username: string) => { const kickUser = async (username: string) => {
// set-up channel joining and kicking // set-up channel joining and kicking
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/kick", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/kick", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -138,11 +138,11 @@
const muteUser = async (username: string) => { const muteUser = async (username: string) => {
// use minutes prompt to determine mute duration // use minutes prompt to determine mute duration
// const prompt = window.prompt("Enter mute duration in seconds"); // const prompt = window.prompt("Enter mute duration in seconds");
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/mute", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/mute", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -157,11 +157,11 @@
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const adminUser = async (username: string) => { const adminUser = async (username: string) => {
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/admin", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/admin", {
// method: "POST", // method: "POST",
// mode: "cors", // mode: "cors",
// }); // });
@ -176,11 +176,11 @@
//--------------------------------------------------------------------------------/ //--------------------------------------------------------------------------------/
const removeAdminUser = async (username: string) => { const removeAdminUser = async (username: string) => {
// const res1 = await fetch(API_URL + "/user/" + username, { // const res1 = await fetch(API_URL + "/users/" + username + "/byname", {
// mode: "cors", // mode: "cors",
// }); // });
// const data1 = await res1.json(); // const data1 = await res1.json();
// const res2 = await fetch(API_URL + "/chat/channels/" + data1.ftId + "/admin", { // const res2 = await fetch(API_URL + "/channels/" + data1.ftId + "/admin", {
// method: "DELETE", // method: "DELETE",
// mode: "cors", // mode: "cors",
// }); // });

6
front/volume/src/components/Friends.svelte

@ -12,7 +12,7 @@
? event.target.querySelector('input[type="text"]').value ? event.target.querySelector('input[type="text"]').value
: event.detail; : event.detail;
const response = await fetch(API_URL + "/invit/" + username, { const response = await fetch(API_URL + "/users/invit/" + username, {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
@ -34,14 +34,14 @@
let friendsInterval: ReturnType<typeof setInterval>; let friendsInterval: ReturnType<typeof setInterval>;
async function getFriends(): Promise<void> { async function getFriends(): Promise<void> {
let response = await fetch(API_URL + "/friends", { let response = await fetch(API_URL + "/users/friends", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
friends = await response.json(); friends = await response.json();
} }
async function getInvits(): Promise<void> { async function getInvits(): Promise<void> {
let response = await fetch(API_URL + "/invits", { let response = await fetch(API_URL + "/users/invits", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });

2
front/volume/src/components/Leaderboard.svelte

@ -6,7 +6,7 @@
let leaderboard: Array<Player> = []; let leaderboard: Array<Player> = [];
async function getLeader(): Promise<void> { async function getLeader(): Promise<void> {
let response = await fetch(API_URL + "/leaderboard", { let response = await fetch(API_URL + "/users/leaderboard", {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });

19
front/volume/src/components/MatchHistory.svelte

@ -25,17 +25,17 @@
let newBatch: Array<Match> = []; let newBatch: Array<Match> = [];
async function fetchData() { async function fetchData() {
let response; let response: Response;
if (username === "Global") { if (username === "Global") {
response = await fetch(`${API_URL}/globalHistory?page=${page}`, { response = await fetch(`${API_URL}/results/global?page=${page}`, {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
} else { } else {
response = await fetch(`${API_URL}/user/${username}`); response = await fetch(`${API_URL}/users/${username}/byname`);
if (response.ok) { if (response.ok) {
let user = await response.json(); let user = await response.json();
response = await fetch(`${API_URL}/history/${user.ftId}?page=${page}`, { response = await fetch(`${API_URL}/results/${user.ftId}?page=${page}`, {
credentials: "include", credentials: "include",
mode: "cors", mode: "cors",
}); });
@ -46,12 +46,11 @@
return { return {
players: match.players, players: match.players,
score: match.score, score: match.score,
date: date: new Date(match.date).toLocaleString("fr-FR", {
new Date(match.date).toLocaleString("fr-FR", { timeZone: "Europe/Paris",
timeZone: "Europe/Paris", dateStyle: "short",
dateStyle: "short", timeStyle: "short",
timeStyle: "short", }),
}),
ranked: match.ranked, ranked: match.ranked,
}; };
}); });

2
front/volume/src/components/NavBar.svelte

@ -50,7 +50,7 @@
{#if link.text === "Profile"} {#if link.text === "Profile"}
<li> <li>
<button on:click={clickProfile}> <button on:click={clickProfile}>
<img src={api + "/avatar"} alt="avatar" /> <img src={api + "/users/avatar"} alt="avatar" />
</button> </button>
</li> </li>
{/if} {/if}

16
front/volume/src/components/Profile.svelte

@ -24,7 +24,7 @@
async function getUser() { async function getUser() {
if (username !== $store.username) { if (username !== $store.username) {
edit = false; edit = false;
const res = await fetch(API_URL + "/user/" + username, { const res = await fetch(API_URL + "/users/" + username + "/byname", {
mode: "cors", mode: "cors",
}); });
user = res.json(); user = res.json();
@ -37,7 +37,7 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
async function handleSubmit() { async function handleSubmit() {
let response = await fetch(API_URL, { let response = await fetch(API_URL + "/users", {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
method: "POST", method: "POST",
body: JSON.stringify({ username: user.username }), body: JSON.stringify({ username: user.username }),
@ -52,7 +52,7 @@
async function handle2fa(event: Event) { async function handle2fa(event: Event) {
event.preventDefault(); event.preventDefault();
user.twoFA = !user.twoFA; user.twoFA = !user.twoFA;
let response = await fetch(API_URL, { let response = await fetch(API_URL + "/users", {
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
method: "POST", method: "POST",
body: JSON.stringify(user), body: JSON.stringify(user),
@ -69,10 +69,10 @@
<h3>===| <mark>{user.username}'s Profile</mark> |===</h3> <h3>===| <mark>{user.username}'s Profile</mark> |===</h3>
<div class="profile-header"> <div class="profile-header">
{#if edit} {#if edit}
<img src={API_URL + "/avatar"} alt="avatar" class="profile-img" /> <img src={API_URL + "/users/avatar"} alt="avatar" class="profile-img" />
{:else} {:else}
<form <form
action={`${API_URL}/avatar`} action={`${API_URL}/users/avatar`}
method="post" method="post"
enctype="multipart/form-data" enctype="multipart/form-data"
bind:this={avatarForm} bind:this={avatarForm}
@ -86,7 +86,11 @@
/> />
</form> </form>
<label class="img-class" for="avatar-input"> <label class="img-class" for="avatar-input">
<img src={API_URL + "/avatar"} alt="avatar" class="profile-img" /> <img
src={API_URL + "/users/avatar"}
alt="avatar"
class="profile-img"
/>
</label> </label>
{/if} {/if}
</div> </div>

Loading…
Cancel
Save