From 334573dbf0b799ca827aaea7b761d20541a6ed79 Mon Sep 17 00:00:00 2001
From: L4168 <L4168@student.jamk.fi>
Date: Fri, 7 Jun 2019 12:26:23 +0300
Subject: [PATCH] added input validation & comments

---
 src/app.module.ts              |  3 +-
 src/game/game.controller.ts    | 21 ++++++++++----
 src/game/game.dto.ts           | 17 +++++++++++-
 src/game/game.entity.ts        | 29 ++++++++++++++++++--
 src/game/game.service.ts       | 50 +++++++++++++++++++++++-----------
 src/shared/array-validation.ts | 17 ++++++++++++
 src/user/user.decorator.ts     |  1 +
 src/user/user.entity.ts        |  6 ++++
 8 files changed, 119 insertions(+), 25 deletions(-)
 create mode 100644 src/shared/array-validation.ts

diff --git a/src/app.module.ts b/src/app.module.ts
index 13f94c7..13fd352 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -1,8 +1,9 @@
 import { Module } from '@nestjs/common';
 import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
 import { TypeOrmModule } from "@nestjs/typeorm";
-import { AppService } from './app.service';
 import { Connection } from "typeorm";
+
+import { AppService } from './app.service';
 import { UserModule } from './user/user.module';
 import { HttpErrorFilter } from './shared/http-error.filter';
 import { LoggingInterceptor } from './shared/logging.interceptor';
diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts
index 4ec3580..ed3bef3 100644
--- a/src/game/game.controller.ts
+++ b/src/game/game.controller.ts
@@ -1,8 +1,11 @@
-import { Controller, Post, UseGuards, Body, Get, Param } from '@nestjs/common';
+import { Controller, Post, UseGuards, Body, Get, Param, UsePipes } from '@nestjs/common';
+
 import { GameService } from './game.service';
 import { AuthGuard } from '../shared/auth.guard';
-import { User } from 'src/user/user.decorator';
-import { GameDTO, FactionDTO } from './game.dto';
+import { User } from '../user/user.decorator';
+import { GameDTO } from './game.dto';
+import { ValidationPipe } from '../shared/validation.pipe';
+import { async } from 'rxjs/internal/scheduler/async';
 
 @Controller('game')
 export class GameController {
@@ -10,8 +13,16 @@ export class GameController {
 
     @Post('new')
     @UseGuards(new AuthGuard())
-    async newGame(@User('id') person, @Body() body) {
-        return this.gameservice.createNewGame(person, body, body.factions);
+    @UsePipes(new ValidationPipe())
+    async newGame(@User('id') person, @Body() body: GameDTO ) {
+        return this.gameservice.createNewGame(person, body);
+    }
+
+    @UseGuards(new AuthGuard())
+    @UsePipes(new ValidationPipe())
+    @Post(':id')
+    async joinGame(@User('id') person, @Param('id') id: string, @Body() password: string) {
+        return this.gameservice.joinGame(person, id, password);
     }
 
     @Get('listgames')
diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts
index 3f7bed0..5a41f38 100644
--- a/src/game/game.dto.ts
+++ b/src/game/game.dto.ts
@@ -4,9 +4,14 @@ import {
   IsJSON,
   IsNotEmpty,
   Length,
+  IsArray,
+  Validate,
 } from 'class-validator';
+import { ArrayLength } from 'src/shared/array-validation';
 
 export class GameDTO {
+  // uses class-validator built in validations
+  // see https://github.com/typestack/class-validator
   @IsString()
   @IsNotEmpty()
   @Length(2, 31)
@@ -15,7 +20,8 @@ export class GameDTO {
   @IsNotEmpty()
   @Length(1, 255)
   desc: string;
-  @IsJSON()
+  //@IsJSON()
+  // doesn't accept with IsJSON, WIP to get validation for map
   map: JSON;
   @IsDateString()
   @IsNotEmpty()
@@ -23,6 +29,15 @@ export class GameDTO {
   @IsDateString()
   @IsNotEmpty()
   enddate: string;
+  @IsArray()
+  @IsNotEmpty()
+  @Length(5, 15, {
+    each: true,
+  })
+  // custom validation for array length (arr>min, arr<max)
+  @Validate(ArrayLength, [4, 8])
+  passwords: [];
+  factions: FactionDTO[];
 }
 
 export class FactionDTO {
diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts
index 0a35559..c4008f7 100644
--- a/src/game/game.entity.ts
+++ b/src/game/game.entity.ts
@@ -6,7 +6,7 @@ import {
   OneToMany,
   Timestamp,
 } from 'typeorm';
-
+import { PersonEntity } from 'src/user/user.entity';
 
 // table that stores all created games
 @Entity('Game')
@@ -17,8 +17,12 @@ export class GameEntity {
   @Column('json') map: JSON;
   @Column('timestamp') startdate: Timestamp;
   @Column('timestamp') enddate: Timestamp;
+  @Column("text", {array: true}) passwords: [];
   @OneToMany(type => FactionEntity, faction => faction.game)
   factions: FactionEntity[];
+  @OneToMany(type => Game_PersonEntity, game_persons => game_persons.game)
+  game_persons: Game_PersonEntity[];
+
 
   gameObject() {
     const { id, name } = this;
@@ -33,4 +37,25 @@ export class FactionEntity {
   @Column('text') name: string;
   @ManyToOne(type => GameEntity, game => game.factions)
   game: GameEntity;
-}
\ No newline at end of file
+  @OneToMany(type => Game_PersonEntity, game_persons => game_persons.faction)
+  game_persons: Game_PersonEntity[];
+
+}
+
+// table that stores players associated with particular game
+@Entity('Game_Person')
+export class Game_PersonEntity {
+    @PrimaryGeneratedColumn('uuid') gameId: string;
+    @ManyToOne(type => FactionEntity, faction => faction.game_persons)
+    faction: FactionEntity;
+    @ManyToOne(type => GameEntity, game => game.game_persons)
+    game: GameEntity;
+    @ManyToOne(type => PersonEntity, person => person.game_persons)
+    person: PersonEntity;
+    /*
+    @ManyToOne(type => PersonRoleEntity, person_role => person_role.game_persons)
+    person_role: PersonRoleEntity;
+    @ManyToMany(type => CoordinateEntity, game_person_coordinates => game_person_coordinates.game_persons)
+    game_person_coordinates: CoordinateEntity[]; */
+}
+
diff --git a/src/game/game.service.ts b/src/game/game.service.ts
index a14ae18..5c5fca0 100644
--- a/src/game/game.service.ts
+++ b/src/game/game.service.ts
@@ -1,8 +1,10 @@
-import { Injectable, Logger } from '@nestjs/common';
+import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
+import { Repository, In } from 'typeorm';
+
 import { GameEntity, FactionEntity } from './game.entity';
-import { FactionDTO, GameDTO } from './game.dto';
+import { GameDTO } from './game.dto';
+import { PersonEntity } from 'src/user/user.entity';
 
 @Injectable()
 export class GameService {
@@ -11,43 +13,59 @@ export class GameService {
     private gameRepository: Repository<GameEntity>,
     @InjectRepository(FactionEntity)
     private factionRepository: Repository<FactionEntity>,
-  ) { }
+    @InjectRepository(PersonEntity)
+    private personRepository: Repository<PersonEntity>,
+  ) {}
 
   // create a new game
-  async createNewGame(
-    personId: string,
-    gameData: GameDTO,
-    factions: FactionDTO[],
-  ) {
+  async createNewGame(personId: string, gameData: GameDTO) {
+    // checks if a game with the same name exists already
+    const { name } = gameData;
+    if (await this.gameRepository.findOne({ where: { name } })) {
+      throw new HttpException('Game already exists', HttpStatus.BAD_REQUEST);
+    }
+    // else add the game to the database
     const game = await this.gameRepository.create({
       ...gameData,
-      factions: factions,
+      factions: gameData.factions,
     });
     await this.gameRepository.insert(game);
     // get the id of the game created to pass it to factions table
-    const gameid = await this.gameRepository.findOne({ where: { name: gameData.name } })
+    const gameid = await this.gameRepository.findOne({
+      where: { name: gameData.name },
+    });
 
-    factions.map(async faction => {
+    gameData.factions.map(async faction => {
       let name = await this.factionRepository.create({
         ...faction,
-        game: gameid
+        game: gameid,
       });
       await this.factionRepository.insert(name);
     });
     return 'success';
   }
 
+  // checks the password, creates an entry in GamePerson table with associated role&faction
+  async joinGame(person, gameId, password) {
+    const user = await this.personRepository.findOne({ where: { id: person.id } });
+    const game = await this.gameRepository.findOne({ where: { id: gameId } });
+    return 'WIP';
+  }
+
   // returns name and id of each game
   async listGames() {
     const games = await this.gameRepository.find({ relations: ['factions'] });
     return games.map(game => {
-      return { game };
+      return game.gameObject();
     });
   }
 
   // returns information about a game identified by id
   async returnGameInfo(id: string) {
-    const game = await this.gameRepository.findOne({ where: { id: id }, relations: ['factions'] });
+    const game = await this.gameRepository.findOne({
+      where: { id: id },
+      relations: ['factions'],
+    });
     return game;
   }
-}
\ No newline at end of file
+}
diff --git a/src/shared/array-validation.ts b/src/shared/array-validation.ts
new file mode 100644
index 0000000..494ab42
--- /dev/null
+++ b/src/shared/array-validation.ts
@@ -0,0 +1,17 @@
+import {ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator";
+import { Logger } from "@nestjs/common";
+
+// validates array length
+@ValidatorConstraint({ name: "arrayLength", async: true })
+export class ArrayLength implements ValidatorConstraintInterface {
+
+    validate(array: string[], args: ValidationArguments) {
+        Logger.log(array.length)
+        return array.length > args.constraints[0] && array.length < args.constraints[1]; // for async validations you must return a Promise<boolean> here
+    }
+
+    defaultMessage(args: ValidationArguments) { // here you can provide default error message if validation failed
+        return "Please input all passwords";
+    }
+
+}
\ No newline at end of file
diff --git a/src/user/user.decorator.ts b/src/user/user.decorator.ts
index 29f93b7..2c499d9 100644
--- a/src/user/user.decorator.ts
+++ b/src/user/user.decorator.ts
@@ -1,5 +1,6 @@
 import { createParamDecorator } from "@nestjs/common";
 
+// used to pass user information to controllers
 export const User = createParamDecorator((data, req) => {
     return data ? req.user[data] : req.user;
 })
\ No newline at end of file
diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts
index e718ce4..6c00a00 100644
--- a/src/user/user.entity.ts
+++ b/src/user/user.entity.ts
@@ -2,6 +2,7 @@ import { Entity, Column, PrimaryGeneratedColumn, BeforeInsert, OneToMany } from
 import * as bcrypt from 'bcryptjs';
 import * as jwt from 'jsonwebtoken';
 import { MapMarkerEntity } from 'src/mapmarkers/mapmarker.entity';
+import { Game_PersonEntity } from 'src/game/game.entity';
 
 @Entity('Person')
 export class PersonEntity {
@@ -10,12 +11,17 @@ export class PersonEntity {
     @Column('text') password: string;
     @OneToMany(type => MapMarkerEntity, marker => marker.player)
     markers: MapMarkerEntity[];
+    @OneToMany(type => Game_PersonEntity, game_persons => game_persons.person)
+    game_persons: Game_PersonEntity[];
 
+
+    // hashes the password before inserting it to database
     @BeforeInsert()
     async hashPassword() {
         this.password = await bcrypt.hash(this.password, 10);
     }
 
+    // returns username and associated token
     tokenObject() {
         const {name, token} = this;
         return {name, token};
-- 
GitLab