diff --git a/src/faction/faction.dto.ts b/src/faction/faction.dto.ts index 1db90ceedeafb78cd9068da1ae834820382f33d3..a5b9f044bdf74edefa37ac077e09a3e384e5e5c3 100644 --- a/src/faction/faction.dto.ts +++ b/src/faction/faction.dto.ts @@ -4,6 +4,9 @@ import { Validate, IsString, IsNotEmpty, + IsNumber, + Min, + Max, } from 'class-validator'; import { GameEntity } from '../game/game.entity'; @@ -14,9 +17,15 @@ import { FactionEntity, GameGroupEntity } from './faction.entity'; export class FactionDTO { @IsString() @IsNotEmpty() - @Length(2, 15) + @Length(2, 31) factionName: string; + @IsString() + @IsNotEmpty() + @Length(3, 15) factionPassword: string; + @IsNumber() + @Min(1) + @Max(3) multiplier?: number; game: GameDTO; } diff --git a/src/faction/faction.service.ts b/src/faction/faction.service.ts index 4402878647ee98fa7f73a40bfbee859a25a4a39a..76242dfe1bceda0776435b43c4fdc4cb60fe272c 100644 --- a/src/faction/faction.service.ts +++ b/src/faction/faction.service.ts @@ -94,6 +94,7 @@ export class FactionService { await this.game_PersonRepository.save(gamePerson); return { + code: 201, message: 'created new group', }; } @@ -113,6 +114,7 @@ export class FactionService { gamePerson.group = data.groupId; await this.game_PersonRepository.save(gamePerson); return { + code: 200, message: 'Joined group', }; } diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index a97155c82d672de5092b9cf246f7e2c21f49f48b..015baf9673563cd8732ce15493e80e23c19e0e6b 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -17,6 +17,7 @@ import { User } from '../user/user.decorator'; import { GameDTO, FlagboxEventDTO } from './game.dto'; import { ValidationPipe } from '../shared/validation.pipe'; import { Roles } from '../shared/roles.decorator'; +import { GameEntity } from './game.entity'; @Controller('game') export class GameController { @@ -48,6 +49,12 @@ export class GameController { return this.gameservice.returnGameInfo(id); } + @Get('get-factions/:id') + @Roles('admin') + async returnGameFactions(@Param('id') id: GameEntity) { + return this.gameservice.listFactions(id); + } + @Get('flag/:id') async flagboxQuery(@Param('id') id: string) { return this.gameservice.flagboxQuery(id); diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts index 09c39b7ecf630b176a1098b703e8a59f7f550ee2..913b6c0f76637ecc94c82c8853cb5428e411f06d 100644 --- a/src/game/game.dto.ts +++ b/src/game/game.dto.ts @@ -7,11 +7,15 @@ import { Validate, Min, Max, + ValidateNested, + Allow, } from 'class-validator'; import { ObjectivePointEntity } from './game.entity'; import { CenterJSON } from '../shared/custom-validation'; import { FactionDTO } from '../faction/faction.dto'; +import { CenterDTO, NodeSettingsDTO } from './game.json.dto'; +import { Type } from 'class-transformer'; export class GameDTO { @IsString() @@ -21,18 +25,26 @@ export class GameDTO { @IsNotEmpty() @Length(1, 255) desc: string; - @IsNotEmpty() - @Validate(CenterJSON) - center: JSON; + @ValidateNested() + @Type(() => CenterDTO) + center: CenterDTO; + @Allow() + @ValidateNested() + @Type(() => NodeSettingsDTO) + nodesettings?: NodeSettingsDTO; + @Allow() map?: JSON; - nodesettings?: JSON; @IsDateString() @IsNotEmpty() startdate: string; @IsDateString() @IsNotEmpty() enddate: string; + @ValidateNested() + @Type(() => FactionDTO) factions?: FactionDTO[]; + @ValidateNested() + @Type(() => FlagboxDTO) objective_points?: FlagboxDTO[]; } diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts index b58066590357070155b28bbd046083454527b8e6..5f019e83c0b3af08bd423809b79ec5039850bbd2 100644 --- a/src/game/game.entity.ts +++ b/src/game/game.entity.ts @@ -14,6 +14,7 @@ import { PersonEntity } from '../user/user.entity'; import { GameGroupEntity } from '../faction/faction.entity'; import { FactionEntity } from '../faction/faction.entity'; import { TaskEntity } from '../task/task.entity'; +import { CenterDTO, NodeSettingsDTO } from './game.json.dto'; // table that stores all created games @Entity('Game') @@ -21,9 +22,9 @@ export class GameEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column('text') name: string; @Column('text') desc: string; - @Column('json') center: JSON; + @Column('json') center: CenterDTO; @Column({ type: 'json', nullable: true }) map: JSON; - @Column({ type: 'json', nullable: true }) nodesettings?: JSON; + @Column({ type: 'json', nullable: true }) nodesettings?: NodeSettingsDTO; @Column('timestamp') startdate: Timestamp; @Column('timestamp') enddate: Timestamp; diff --git a/src/game/game.json-nested.dto.ts b/src/game/game.json-nested.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..df67071f089caf3670e46188f92959cfa4e349ad --- /dev/null +++ b/src/game/game.json-nested.dto.ts @@ -0,0 +1,16 @@ +import { IsNumber } from 'class-validator'; + +export class NodeCoreSettingsDTO { + @IsNumber() + capture_time: number; + @IsNumber() + confirmation_time: number; + @IsNumber() + owner: number; + @IsNumber() + capture: number; + @IsNumber() + buttons_available: number; + @IsNumber() + heartbeat_interval: number; +} diff --git a/src/game/game.json.dto.ts b/src/game/game.json.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b9484aaa3334d6d564398a96c7e2747f86aab84 --- /dev/null +++ b/src/game/game.json.dto.ts @@ -0,0 +1,16 @@ +import { IsNumber, ValidateNested } from 'class-validator'; +import { NodeCoreSettingsDTO } from './game.json-nested.dto'; +import { Type } from 'class-transformer'; + +export class CenterDTO { + @IsNumber() + lat: number; + @IsNumber() + lng: number; +} + +export class NodeSettingsDTO { + @ValidateNested() + @Type(() => NodeCoreSettingsDTO) + node_settings: NodeCoreSettingsDTO; +} diff --git a/src/game/game.service.ts b/src/game/game.service.ts index 9a74a46d8506ccc4a1d03dc6873ba86d74758bb6..573bb2255ba2d65f828ad3900ff064e1fc471d13 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -48,6 +48,7 @@ export class GameService { }); await this.game_PersonRepository.insert(gamePerson); return { + code: 201, message: 'New game added', }; } @@ -106,14 +107,20 @@ export class GameService { // TO DO: ADD FLAGBOX LOCATION TO MAPDRAWING ENTITY return { + code: 200, message: 'Game updated', }; } + async listFactions(game: GameEntity) { + return this.factionRepository.find({ game }); + } + async deleteGame(id) { // TODO: Delete factions from Faction table associated with the deleted game await this.gameRepository.delete({ id }); return { + code: 200, message: 'Game deleted', }; } @@ -164,5 +171,9 @@ export class GameService { await this.objectivePoint_HistoryRepository.insert(eventUpdate); // send flagbox event to flagbox subscribers this.notificationGateway.server.emit('flagbox', 'event update'); + return { + code: 201, + message: 'OK', + }; } } diff --git a/src/shared/validation.pipe.ts b/src/shared/validation.pipe.ts index 27f078b8d48f7cf160aeac90ff50d4a080edf3d4..cff51f1b068b67886c29dd85587760a1fa016f1e 100644 --- a/src/shared/validation.pipe.ts +++ b/src/shared/validation.pipe.ts @@ -1,44 +1,65 @@ - -import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, HttpException, HttpStatus } from '@nestjs/common'; +import { + PipeTransform, + Injectable, + ArgumentMetadata, + HttpException, + HttpStatus, +} from '@nestjs/common'; import { validate } from 'class-validator'; import { plainToClass } from 'class-transformer'; +import { AdvancedConsoleLogger } from 'typeorm'; @Injectable() export class ValidationPipe implements PipeTransform<any> { - async transform(value: any, metadata: ArgumentMetadata) { - - if (value instanceof Object && this.isEmpty(value)) { - throw new HttpException( - 'Validation failed: No body submitted', HttpStatus.BAD_REQUEST - ); - } - - const { metatype } = metadata; - if (!metatype || !this.toValidate(metatype)) { - return value; - } - const object = plainToClass(metatype, value); - const errors = await validate(object); - if (errors.length > 0) { - throw new HttpException(`Validation failed: ${this.formatErrors(errors)}`, HttpStatus.BAD_REQUEST); - } - return value; + async transform(value: any, metadata: ArgumentMetadata) { + if (value instanceof Object && this.isEmpty(value)) { + throw new HttpException( + 'Validation failed: No body submitted', + HttpStatus.BAD_REQUEST, + ); } - private toValidate(metatype: Function): boolean { - const types: Function[] = [String, Boolean, Number, Array, Object]; - return !types.includes(metatype); + const { metatype } = metadata; + if (!metatype || !this.toValidate(metatype)) { + return value; } - - private formatErrors(errors: any[]) { - return errors.map(err => { - for (let property in err.constraints) { - return err.constraints[property] - } - }).join(", "); + const object = plainToClass(metatype, value); + const errors = await validate(object, { + whitelist: true, + forbidNonWhitelisted: true, + }); + if (errors.length > 0) { + throw new HttpException( + `Validation failed: ${this.formatErrors(errors)}`, + HttpStatus.BAD_REQUEST, + ); } + return value; + } - private isEmpty(value: any) { - return (Object.keys(value).length > 0) ? false : true; + private toValidate(metatype: Function): boolean { + const types: Function[] = [String, Boolean, Number, Array, Object]; + return !types.includes(metatype); + } + + private formatErrors(errors: any[]) { + return errors + .map(err => { + return this.returnError(err); + }) + .join(', '); + } + + private returnError(err) { + if (err['children'] !== undefined && err['children'].length != 0) { + return this.formatErrors(err['children']); } -} \ No newline at end of file + for (let property in err.constraints) { + return err.constraints[property]; + } + } + + private isEmpty(value: any) { + return Object.keys(value).length > 0 ? false : true; + } +}