diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index bd6551ad714d9abbe172ef2a9be0d9301d8d79a6..433d570aef23d31a3da5b9aea8f9f778e08e4083 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -64,6 +64,11 @@ export class GameController { return this.gameservice.joinGroup(person, id); } + @Get('get-faction-members/:id') + async getFactionMembers(@Param('id') factionId) { + return this. gameservice.listFactionMembers(factionId) + } + // param game ID is passed to @Roles @Put('promote/:id') @UseGuards(new AuthGuard()) diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts index df93b55b574ce8590c728a82f14f928a3b16a6d4..6460193fce2481385a90560bb36d2317cbdc13a9 100644 --- a/src/game/game.dto.ts +++ b/src/game/game.dto.ts @@ -1,22 +1,15 @@ import { IsNotEmpty, IsString, - IsDate, Length, - IsInt, - Min, - Max, - IsArray, - IsJSON, IsDateString, IsNumber, - IsUUID, Validate, + Min, + Max, } from 'class-validator'; -import { Timestamp } from 'typeorm'; -import { ObjectivePointEntity, GameEntity } from './game.entity'; -import { StringifyOptions } from 'querystring'; -import { Uuid, RoleValidation } from '../shared/custom-validation'; +import { ObjectivePointEntity } from './game.entity'; +import { CenterJSON } from '../shared/custom-validation'; export class GameDTO { @IsString() @@ -27,11 +20,8 @@ export class GameDTO { @Length(1, 255) desc: string; @IsNotEmpty() - //@IsJSON() + @Validate(CenterJSON) center: JSON; - //@IsJSON() - // doesn't accept with IsJSON, WIP to get validation for map and center - // IsJSON checks with json.parse, expecting string map?: JSON; nodesettings?: JSON; @IsDateString() @@ -40,12 +30,30 @@ export class GameDTO { @IsDateString() @IsNotEmpty() enddate: string; - // custom validation for array length (arr>min, arr<max) - //@Validate(ArrayLength, [4, 8]) factions?: FactionDTO[]; objective_points?: FlagboxDTO[]; } +export class newGameDTO { + @IsString() + @IsNotEmpty() + @Length(3, 30) + name: string; + @IsString() + @IsNotEmpty() + @Length(1, 255) + desc: string; + @IsNotEmpty() + @Validate(CenterJSON) + center: JSON; + @IsDateString() + @IsNotEmpty() + startdate: string; + @IsDateString() + @IsNotEmpty() + enddate: string; +} + export class FactionDTO { @IsString() @IsNotEmpty() @@ -66,10 +74,22 @@ export class FlagboxDTO { } export class FlagboxEventDTO { + @IsString() + @IsNotEmpty() + @Length(7) node_id: string; - owner: number; - action: number; - capture: number; + @IsNumber() + @Min(0) + @Max(3) + owner: number; // owner = 0, => first entry in faction db, owner = 1, => second entry etc + @IsNumber() + @Min(0) + @Max(3) + action: number; // 0=no capture ongoing, 1=captured, 2=capture ongoing + @IsNumber() + @Min(0) + @Max(3) + capture: number; // which faction is capturing, same logic as in owner with numbers oP_HistoryTimestamp?: string; objective_point?: ObjectivePointEntity; } diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts index 3b36297c022992a43b7a4999fc9eccba1b493b27..036b31c8a3cf1d0343c8f31c691a50d749d97d0f 100644 --- a/src/game/game.entity.ts +++ b/src/game/game.entity.ts @@ -12,10 +12,8 @@ import { import { PersonEntity } from '../user/user.entity'; import { GameGroupEntity } from './group.entity'; import { FactionEntity } from './faction.entity'; -import { - MapDrawingEntity, - Game_Person_MapDrawingEntity, -} from './coordinate.entity'; +import { MapDrawingEntity } from './coordinate.entity'; +import { TaskEntity } from '../task/task.entity'; // table that stores all created games @Entity('Game') @@ -40,6 +38,8 @@ export class GameEntity { objective_points => objective_points.game, ) objective_points: ObjectivePointEntity[]; + @OneToMany(type => TaskEntity, tasks => tasks.taskGame) + tasks: TaskEntity[]; gameObject() { const { id, name } = this; diff --git a/src/game/game.service.ts b/src/game/game.service.ts index 571075290d777049db0a90e80a28b62e0f55e102..7c12bdd3dbdc96b21100db39ec0e5736f445db5d 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -8,7 +8,12 @@ import { ObjectivePointEntity, ObjectivePoint_HistoryEntity, } from './game.entity'; -import { GameDTO, FlagboxEventDTO, JoinFactionDTO } from './game.dto'; +import { + GameDTO, + FlagboxEventDTO, + JoinFactionDTO, + GameGroupDTO, +} from './game.dto'; import { PersonEntity } from '../user/user.entity'; import { GameGroupEntity } from './group.entity'; import { FactionEntity } from './faction.entity'; @@ -39,22 +44,18 @@ export class GameService { // create a new game async createNewGame(personId: PersonEntity, gameData: GameDTO) { // checks if a game with the same name exists already - const { name } = gameData; - if (await this.gameRepository.findOne({ where: { name } })) { + if (await this.gameRepository.findOne({ name: gameData.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: gameData.factions, - }); + const game = await this.gameRepository.create(gameData); await this.gameRepository.insert(game); + // add gamePerson with role admin to the game const gamePerson = await this.game_PersonRepository.create({ - faction: null, game: game, person: personId, + role: 'admin', }); - gamePerson['role'] = 'admin'; await this.game_PersonRepository.insert(gamePerson); return { message: 'New game added', @@ -64,16 +65,13 @@ export class GameService { // edit already created game async editGame(id: string, gameData: GameDTO) { // checks if a game with the same name exists already - const { name } = gameData; - if (await this.gameRepository.findOne({ name: name, id: Not(id) })) { + if ( + await this.gameRepository.findOne({ name: gameData.name, id: Not(id) }) + ) { throw new HttpException('Game already exists', HttpStatus.BAD_REQUEST); } // update game entry in db - const updatedGame = await this.gameRepository.create({ - ...gameData, - factions: null, - objective_points: null, - }); + const updatedGame = await this.gameRepository.create(gameData); updatedGame['id'] = id; const gameId = await this.gameRepository.save(updatedGame); @@ -131,7 +129,7 @@ export class GameService { } // checks the password, creates an entry in GamePerson table with associated role&faction - async createGroup(person, gameId, groupData) { + async createGroup(person, gameId, groupData: GameGroupDTO) { // check if the person already is in a group in this game const checkDuplicate = await this.game_PersonRepository.findOne({ person: person, @@ -235,6 +233,14 @@ export class GameService { this.notificationGateway.server.emit('flagbox', 'event update'); } + async listFactionMembers(faction) { + return await this.game_PersonRepository.find({ + where: { faction }, + relations: ['person'], + order: { person: 'DESC' }, + }); + } + async promotePlayer(body) { const gamepersonId = body.player; // get playerdata diff --git a/src/shared/array-validation.ts b/src/shared/array-validation.ts deleted file mode 100644 index 494ab42f06d0d067fcb115e6eebb63ef8dca2746..0000000000000000000000000000000000000000 --- a/src/shared/array-validation.ts +++ /dev/null @@ -1,17 +0,0 @@ -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/shared/custom-validation.ts b/src/shared/custom-validation.ts index a920e717e08a6981f5f2e82ddc5cf237ecddba11..13b96372e0b76a2897364baa15562edf383a2d81 100644 --- a/src/shared/custom-validation.ts +++ b/src/shared/custom-validation.ts @@ -30,3 +30,23 @@ export class RoleValidation implements ValidatorConstraintInterface { return 'Not valid uuid'; } } + +// checks for valid JSON for center +@ValidatorConstraint({ name: 'centerJSON', async: true }) +export class CenterJSON implements ValidatorConstraintInterface { + validate(center: JSON, args: ValidationArguments) { + const validator = new Validator(); + return ( + validator.isNumber(center['lat']) && + validator.isNumber(center['lng']) && + validator.min(center['lat'], -90) && + validator.max(center['lat'], 90) && + validator.min(center['lng'], -180) && + validator.max(center['lng'], 180) + ); + } + + defaultMessage(args: ValidationArguments) { + return 'Error with center JSON'; + } +} diff --git a/src/shared/roles.guard.ts b/src/shared/roles.guard.ts index e65916a94b3cc552259a63a9f5395e3118f95e1b..73693f9e1551da1b1b34056a665eb59c1211b3ef 100644 --- a/src/shared/roles.guard.ts +++ b/src/shared/roles.guard.ts @@ -32,9 +32,9 @@ export class RolesGuard implements CanActivate { return false; } const gameId = request.params.id; - const user = await this.getUserObject(request.headers.authorization); + request.user = await this.getUserObject(request.headers.authorization); const role = await this.game_PersonRepository.findOne({ - person: user['id'], + person: request.user['id'], game: gameId, }); // check that the role matches the criteria and that token is valid for this game diff --git a/src/task/task.controller.ts b/src/task/task.controller.ts index 6f272a0ce4c9d51e49ec3fbba78d666e283b0910..32766ffd6bc1c6f5f51a5234ae2024bc345582e2 100644 --- a/src/task/task.controller.ts +++ b/src/task/task.controller.ts @@ -7,35 +7,41 @@ import { UseGuards, Param, } from '@nestjs/common'; + import { TaskService } from './task.service'; +import { CreateTaskDTO, EditTaskDTO } from './task.dto'; +import { AuthGuard } from '../shared/auth.guard'; import { Roles } from '../shared/roles.decorator'; import { ValidationPipe } from '../shared/validation.pipe'; -import { TaskDTO } from './task.dto'; +import { User } from 'src/user/user.decorator'; @Controller('task') export class TaskController { constructor(private taskService: TaskService) {} - /* @Post('new') - @UseGuards(new AuthGuard()) + // creates a new task if the user has admin role in the game + // :id is the id of the game + @Post('new-task/:id') + @Roles('admin') @UsePipes(new ValidationPipe()) - async newGame(@User('id') person, @Body() body: GameDTO) { - return this.gameservice.createNewGame(person, body); + async newTask(@Param('id') id: string, @Body() task: CreateTaskDTO) { + return this.taskService.newTask(task); } - @Put(':id') + // edits a created task if the user has admin role in the game + // :id is the id of the game + @Post('edit-task/:id') @Roles('admin') @UsePipes(new ValidationPipe()) - async editGame(@Param('id') id: string, @Body() body: GameDTO) { - return this.gameservice.editGame(id, body); - } */ + async editTask(@Param('id') id: string, @Body() data: EditTaskDTO) { + return this.taskService.editTask(data); + } - // creates a new task if the user has admin role in the game + // lists all the tasks for the game if user has game_person entry // :id is the id of the game - @Post('new-task/:id') - //@Roles('admin') - @UsePipes(new ValidationPipe()) - async newTask(@Param('id') id: string, @Body() task: TaskDTO) { - return this.taskService.newTask(task); + @Get('get-tasks/:id') + @Roles('soldier', 'factionleader', 'admin') + async fetchTasks(@User('id') person, @Param('id') id: string) { + return this.taskService.fetchTasks(person, id); } } diff --git a/src/task/task.dto.ts b/src/task/task.dto.ts index aa3238f888d0ad2975d4c6bab519421ddd41635d..3a363b5f95602d03294a5d256982558f19c1b937 100644 --- a/src/task/task.dto.ts +++ b/src/task/task.dto.ts @@ -1,30 +1,38 @@ import { IsString, Length, - IsNumber, IsBoolean, - Min, - Max, + Validate, + IsUUID, + Equals, } from 'class-validator'; -import { FactionEntity } from 'src/game/faction.entity'; +import { FactionEntity } from '../game/faction.entity'; +import { GameEntity } from '../game/game.entity'; +import { Uuid } from '../shared/custom-validation'; -export class TaskDTO { +export class CreateTaskDTO { @IsString() @Length(3, 31) taskName: string; @IsString() @Length(0, 255) taskDescription: string; - @IsNumber() - @Min(1) - @Max(99) - taskScore: number; - @IsString() - @Length(3, 31) - taskWinner?: string; @IsBoolean() taskIsActive: boolean; - // faction unique id - @IsString() + @Validate(Uuid) faction: FactionEntity; + @Equals(null) + taskWinner: FactionEntity; + // faction unique id + @IsUUID('4') + taskGame: GameEntity; +} + +export class EditTaskDTO { + @IsUUID('4') + taskId: string; + @IsUUID('4') + taskWinner: FactionEntity; + @IsUUID('4') + taskGame: GameEntity; } diff --git a/src/task/task.entity.ts b/src/task/task.entity.ts index 1e7470102800e44bb60d68d73340eedb67e8b93e..e6fd61879325993ff34609d7e0b15f5dd89dfc18 100644 --- a/src/task/task.entity.ts +++ b/src/task/task.entity.ts @@ -1,15 +1,25 @@ -import { PrimaryGeneratedColumn, Column, Entity, ManyToOne } from 'typeorm'; +import { + PrimaryGeneratedColumn, + Column, + Entity, + ManyToOne, + JoinColumn, +} from 'typeorm'; import { FactionEntity } from 'src/game/faction.entity'; +import { GameEntity } from 'src/game/game.entity'; @Entity('Task') export class TaskEntity { @PrimaryGeneratedColumn('uuid') taskId: string; @Column({ type: 'text' }) taskName: string; @Column({ type: 'text' }) taskDescription: string; - @Column({ type: 'float' }) taskScore: number; - @Column({ type: 'text' }) taskWinner: string; @Column({ type: 'bool' }) taskIsActive: boolean; @ManyToOne(type => FactionEntity, faction => faction.factionId) faction: FactionEntity; + @ManyToOne(type => FactionEntity, faction => faction.factionId) + taskWinner: FactionEntity; + @ManyToOne(type => GameEntity, game => game.id) + @JoinColumn({ name: 'taskGame' }) + taskGame: GameEntity; } diff --git a/src/task/task.module.ts b/src/task/task.module.ts index ea321484169a3868f1f5d8d49f62574ab2253be8..12112f92a47ef2c30f0c130d15dba0d5b4c41126 100644 --- a/src/task/task.module.ts +++ b/src/task/task.module.ts @@ -4,9 +4,15 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { TaskService } from './task.service'; import { TaskController } from './task.controller'; import { TaskEntity } from './task.entity'; +import { FactionEntity } from '../game/faction.entity'; +import { Game_PersonEntity } from '../game/game.entity'; +import { NotificationModule } from '../notifications/notifications.module'; @Module({ - imports: [TypeOrmModule.forFeature([TaskEntity])], + imports: [ + TypeOrmModule.forFeature([TaskEntity, FactionEntity, Game_PersonEntity]), + NotificationModule, + ], controllers: [TaskController], providers: [TaskService], }) diff --git a/src/task/task.service.ts b/src/task/task.service.ts index 830e6156222be1039e16c44cd50de1b453628991..3fbe8c51c827d516aedf97a789ccd9badd1cf50b 100644 --- a/src/task/task.service.ts +++ b/src/task/task.service.ts @@ -1,21 +1,102 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; + import { TaskEntity } from './task.entity'; -import { TaskDTO } from './task.dto'; +import { CreateTaskDTO, EditTaskDTO } from './task.dto'; +import { FactionEntity } from '../game/faction.entity'; +import { Game_PersonEntity } from '../game/game.entity'; +import { NotificationGateway } from '../notifications/notifications.gateway'; @Injectable() export class TaskService { constructor( @InjectRepository(TaskEntity) private taskRepository: Repository<TaskEntity>, + @InjectRepository(FactionEntity) + private factionRepository: Repository<FactionEntity>, + @InjectRepository(Game_PersonEntity) + private gamePersonRepository: Repository<Game_PersonEntity>, + private notificationGateway: NotificationGateway, ) {} - async newTask(task: TaskDTO) { + async newTask(task: CreateTaskDTO) { + // check if is not null, check that the faction exists in the game + if ( + task.faction != null && + !(await this.factionRepository.findOne({ + factionId: task.faction.toString(), + game: task.taskGame, + })) + ) { + throw new HttpException('Faction not found', HttpStatus.BAD_REQUEST); + } const createdTask = await this.taskRepository.create(task); await this.taskRepository.insert(createdTask); + // notify subscribers about a new task + // if faction was set it notifies only faction members, else everyone + this.notificationGateway.server.emit( + task.faction != null ? task.faction.toString() : 'global', + 'new task', + ); return { message: 'Task added', }; } + + async editTask(data: EditTaskDTO) { + const task = await this.taskRepository.findOne(data.taskId); + console.log(task); + // checks if task is already closed + if (!task.taskIsActive) { + throw new HttpException('Task is not active', HttpStatus.BAD_REQUEST); + } + // checks if faction is valid + if ( + !(await this.factionRepository.findOne({ + factionId: data.taskWinner.toString(), + game: data.taskGame, + })) + ) { + throw new HttpException('Faction not found', HttpStatus.BAD_REQUEST); + } + task.taskWinner = data.taskWinner; + task.taskIsActive = false; + await this.taskRepository.save(task); + return { + message: 'Task updated and closed', + }; + } + + async fetchTasks(user, taskGame) { + const gamePerson = await this.gamePersonRepository.findOne({ + where: { + person: user, + game: taskGame, + }, + relations: ['faction'], + }); + if (gamePerson.role == 'admin') { + return await this.taskRepository.find({ + where: { taskGame: taskGame }, + relations: ['faction'], + }); + } else { + return await this.taskRepository.find({ + relations: ['faction'], + where: [ + { + taskGame: taskGame, + faction: gamePerson.faction.factionId, + taskIsActive: true, + }, + { + taskGame: taskGame, + faction: null, + taskIsActive: true, + }, + ], + }); + } + } }