feat : use dto for serialization

This commit is contained in:
2024-12-28 18:52:17 +01:00
parent c7c778a55b
commit b97f380131
17 changed files with 153 additions and 44 deletions
+3 -1
View File
@@ -2,5 +2,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
},
"javascript.preferences.quoteStyle": "single",
"typescript.preferences.quoteStyle": "single"
}
+7
View File
@@ -18,6 +18,7 @@
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.2",
"@types/ranjs": "^1.22.7",
"class-transformer": "^0.5.1",
"connect-redis": "^8.0.1",
"express-session": "^1.18.0",
"pg": "^8.13.1",
@@ -4488,6 +4489,12 @@
"integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==",
"dev": true
},
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
"license": "MIT"
},
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+1
View File
@@ -32,6 +32,7 @@
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.2",
"@types/ranjs": "^1.22.7",
"class-transformer": "^0.5.1",
"connect-redis": "^8.0.1",
"express-session": "^1.18.0",
"pg": "^8.13.1",
+9 -6
View File
@@ -17,6 +17,7 @@ import { GamePipe } from '../pipe/game.pipe';
import { Game } from '../entities/game.entity';
import { Observable, fromEvent, map } from 'rxjs';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { GameResponseDTO } from '../dto/gameresponse.dto';
@ApiTags('lobby')
@Controller('games')
@@ -52,9 +53,9 @@ export class LobbyController {
@Get()
@ApiOperation({ summary: 'List all games' })
findAll() {
const games = this.lobbyService.findAll();
return games;
async findAll(): Promise<GameResponseDTO[]> {
const games = await this.lobbyService.findAll();
return games.map((g) => new GameResponseDTO(g));
}
@Sse('/subscribe')
@@ -70,9 +71,11 @@ export class LobbyController {
@Get(':id')
@ApiOperation({ summary: 'Get game information' })
async findOne(@Param('id') id: string, @Param('id', GamePipe) game: Game) {
return game;
// return new ReadGameDto(game);
async findOne(
@Param('id') id: string,
@Param('id', GamePipe) game: Game,
): Promise<GameResponseDTO> {
return new GameResponseDTO(game);
}
@Delete(':id')
@@ -0,0 +1,20 @@
import { Expose, Type } from 'class-transformer';
import { ConceptResponseDTO } from './conceptresponse.dto';
export class ConceptInTurnResponseDTO {
constructor(partial: Partial<ConceptInTurnResponseDTO>) {
Object.assign(this, partial);
}
@Type(() => ConceptResponseDTO)
@Expose()
concept: ConceptResponseDTO;
@Expose()
order: number;
@Expose()
subconcept: number;
@Expose()
markers: number;
}
+13
View File
@@ -0,0 +1,13 @@
import { Expose } from 'class-transformer';
export class ConceptResponseDTO {
constructor(partial: Partial<ConceptResponseDTO>) {
Object.assign(this, partial);
}
@Expose()
id: number;
@Expose()
value: string;
}
+36
View File
@@ -0,0 +1,36 @@
import { Expose, Type } from 'class-transformer';
import { UserResponseDTO } from 'src/users/dto/userresponse.dto';
import { PlayerResponseDTO } from './playerresponse.dto';
import { ConceptInTurnResponseDTO } from './conceptinturnresponse.dto';
export class GameResponseDTO {
constructor(partial: Partial<GameResponseDTO>) {
Object.assign(this, partial);
this.owner = partial.owner;
this.players = partial.players;
this.currentPlayer = partial.currentPlayer;
this.currentConcepts = partial.currentConcepts;
}
@Expose()
id: number;
@Type(() => UserResponseDTO)
@Expose()
owner: UserResponseDTO;
@Type(() => PlayerResponseDTO)
@Expose()
players: PlayerResponseDTO[];
@Type(() => PlayerResponseDTO)
@Expose()
currentPlayer: PlayerResponseDTO;
@Expose()
started: boolean;
@Type(() => ConceptInTurnResponseDTO)
@Expose()
currentConcepts: ConceptInTurnResponseDTO[];
}
+21
View File
@@ -0,0 +1,21 @@
import { Expose, Transform, Type } from 'class-transformer';
import { UserResponseDTO } from 'src/users/dto/userresponse.dto';
export class PlayerResponseDTO {
constructor(partial: Partial<PlayerResponseDTO>) {
Object.assign(this, partial);
this.user = partial.user;
}
@Type(() => UserResponseDTO)
@Expose()
user: UserResponseDTO;
@Expose()
get userId(): string {
return this.user.userId;
}
@Expose()
score: number;
}
+2 -2
View File
@@ -19,10 +19,10 @@ export class Game {
@Column()
seed: string;
@ManyToOne(() => User)
@ManyToOne(() => User, { eager: true })
owner: User;
@OneToMany(() => Player, (player) => player.game, { onDelete: 'CASCADE' })
@OneToMany(() => Player, (player) => player.game, { eager: true })
@JoinTable()
players: Player[];
+5 -1
View File
@@ -17,7 +17,7 @@ export class Player {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(() => User)
@ManyToOne(() => User, { eager: true })
user: User;
@ManyToOne(() => Game)
@@ -25,4 +25,8 @@ export class Player {
@Column({ default: 0 })
score: number;
get userId(): string {
return this.user.userId;
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ export class GamePipe implements PipeTransform {
) {}
async transform(value: any) {
const game = await this.gameRepository.findBy({ id: value });
const game = await this.gameRepository.findOneBy({ id: value });
if (!game) {
throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
}
+9 -1
View File
@@ -1,7 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { NestFactory, Reflector } from '@nestjs/core';
import * as session from 'express-session';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ClassSerializerInterceptor, INestApplication } from '@nestjs/common';
import { RedisStore } from 'connect-redis';
import { createClient } from 'redis';
@@ -30,6 +31,13 @@ async function bootstrap() {
}),
);
app.useGlobalInterceptors(
new ClassSerializerInterceptor(app.get(Reflector), {
// strategy: 'excludeAll', 👈 we'll talk about this later
excludeExtraneousValues: true,
}),
);
await app.listen(8765);
}
bootstrap();
-5
View File
@@ -1,5 +0,0 @@
export class CreateUserDto {
method: 'discord' | 'password';
username?: string;
password: string;
}
-17
View File
@@ -1,17 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
export class LoginDto {
constructor(userId: string, username: string, avatar: string) {
this.userId = userId;
this.username = username;
this.avatar = avatar;
}
@ApiProperty()
userId: string;
@ApiProperty()
username: string;
@ApiProperty()
avatar: string;
}
+15
View File
@@ -0,0 +1,15 @@
import { Expose } from 'class-transformer';
export class UserResponseDTO {
constructor(partial: Partial<UserResponseDTO>) {
Object.assign(this, partial);
}
@Expose()
userId: string;
@Expose()
username: string;
@Expose()
avatar: string;
}
+6 -4
View File
@@ -8,7 +8,7 @@ import {
Redirect,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { LoginDto } from './dto/login.dto';
import { UserResponseDTO } from './dto/userresponse.dto';
import { ApiTags } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';
@@ -39,14 +39,16 @@ export class UsersController {
}
@Get('/whoami')
async remove(@Session() session: Record<string, any>): Promise<LoginDto> {
async remove(
@Session() session: Record<string, any>,
): Promise<UserResponseDTO> {
if (!session.tokenData) {
throw new HttpException(
"Session doesn't contains auth information",
HttpStatus.UNAUTHORIZED,
);
}
const user = await this.usersService.discordLogin(session);
return user;
await this.usersService.discordLogin(session);
return new UserResponseDTO(session.user);
}
}
+5 -6
View File
@@ -1,5 +1,5 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
import { UserResponseDTO } from './dto/userresponse.dto';
import { URLSearchParams } from 'url';
import { User } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
@@ -108,7 +108,7 @@ export class UsersService {
async discordLogin(
session: Record<string, any>,
recursive: boolean = false,
): Promise<LoginDto> {
): Promise<User> {
const req2 = await fetch('https://discord.com/api/users/@me', {
headers: {
authorization:
@@ -135,13 +135,12 @@ export class UsersService {
}
}
const data2 = await req2.json();
const dto = new LoginDto(data2.id, data2.global_name, data2.avatar);
const user = await this.getOrCreateUser(dto.userId, dto);
const user = await this.getOrCreateUser(data2.id, data2);
session.user = user;
return dto;
return user;
}
async getOrCreateUser(userId: string, data?: LoginDto) {
async getOrCreateUser(userId: string, data?: UserResponseDTO) {
let user = await this.usersRepository.findOneBy({ userId });
if (!user) {
user = this.usersRepository.create({ ...data, userId });