diff --git a/src/app.module.ts b/src/app.module.ts index 2c2773b3c7a80e6e20b316dd2a753e32b310a49a..f2c7b7765304436488bbadb3fdfce4fbdc1fbd9a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -18,6 +18,7 @@ import { UserModule } from './user/user.module'; import { DrawModule } from './draw/draw.module'; import { FactionModule } from './faction/faction.module'; import { GameModule } from './game/game.module'; +import { ScoreModule } from './score/score.module'; @Module({ imports: [ @@ -29,6 +30,7 @@ import { GameModule } from './game/game.module'; DrawModule, FactionModule, TrackingModule, + ScoreModule, ], controllers: [AppController], providers: [ diff --git a/src/draw/coordinate.entity.ts b/src/draw/coordinate.entity.ts index 8f5c4e1d95cd7bf819818664aa41f662a412b316..ef5447f61cfd1dd07d1f81a8ee1fce8e8256da2a 100644 --- a/src/draw/coordinate.entity.ts +++ b/src/draw/coordinate.entity.ts @@ -21,7 +21,9 @@ export class MapDrawingEntity { onDelete: 'CASCADE', }) faction: FactionEntity; - @ManyToOne(type => GameEntity, gameEntity => gameEntity.id) + @ManyToOne(type => GameEntity, gameEntity => gameEntity.id, { + onDelete: 'CASCADE', + }) gameId: GameEntity; } @@ -30,8 +32,20 @@ export class Game_Person_MapDrawingEntity { @PrimaryGeneratedColumn('uuid') GPmapDrawingId: string; @Column({ type: 'timestamp' }) GPCTimeStamp: Timestamp; - @ManyToOne(type => Game_PersonEntity, game_person => game_person.gamepersonId) + @ManyToOne( + type => Game_PersonEntity, + game_person => game_person.gamepersonId, + { + onDelete: 'CASCADE', + }, + ) game_person: Game_PersonEntity; - @ManyToOne(type => MapDrawingEntity, map_drawing => map_drawing.mapDrawingId) + @ManyToOne( + type => MapDrawingEntity, + map_drawing => map_drawing.mapDrawingId, + { + onDelete: 'CASCADE', + }, + ) map_drawing: MapDrawingEntity; } diff --git a/src/draw/draw.controller.ts b/src/draw/draw.controller.ts index 10b836118855d17bee7856203b1258f4b572c8d5..26f6cf8c56142ef190ad3bcbfd7ce9d32caa371e 100644 --- a/src/draw/draw.controller.ts +++ b/src/draw/draw.controller.ts @@ -12,6 +12,7 @@ import { import { AuthGuard } from '../shared/auth.guard'; import { DrawService } from './draw.service'; import { Roles, GameStates } from '../shared/guard.decorator'; +import { MapDrawingDTO, ReturnDrawingsDTO } from './mapdrawing.dto'; /* DrawController @@ -25,17 +26,17 @@ export class DrawController { constructor(private drawService: DrawService) {} @Put('mapdrawing/:id') - @UsePipes(new ValidationPipe()) @Roles('admin', 'factionleader') @GameStates('CREATED', 'STARTED') - async draw(@Param('id') gameId, @Body() data) { + @UsePipes(new ValidationPipe()) + async draw(@Param('id') gameId, @Body() data: MapDrawingDTO) { return this.drawService.draw(gameId, data); } @Get('map/:id') @UseGuards(new AuthGuard()) @UsePipes(new ValidationPipe()) - async drawMap(@Param('id') id, @Body() data) { + async drawMap(@Param('id') id, @Body() data: ReturnDrawingsDTO) { return this.drawService.drawMap(id, data); } } diff --git a/src/draw/draw.service.ts b/src/draw/draw.service.ts index ffa48d4b634a35c6b0dc4bb26391d269c9f9b76a..d5ddf23e9574771bc769e2b6d58b59a4e95ad10d 100644 --- a/src/draw/draw.service.ts +++ b/src/draw/draw.service.ts @@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { MapDrawingEntity } from '../draw/coordinate.entity'; +import { MapDrawingDTO, ReturnDrawingsDTO } from './mapdrawing.dto'; @Injectable() export class DrawService { @@ -11,11 +12,9 @@ export class DrawService { private mapDrawingRepository: Repository<MapDrawingEntity>, ) {} - async draw(gameId, data: MapDrawingEntity) { + async draw(gameId, data: MapDrawingDTO) { data['gameId'] = gameId; - const drawing = await this.mapDrawingRepository.create(data); - if (data.mapDrawingId == null || data.mapDrawingId == '') { // luo uuden instanssin. const mapDrawing = await this.mapDrawingRepository.insert(drawing); @@ -27,13 +26,21 @@ export class DrawService { } // draw map based on game and - async drawMap(id, data: MapDrawingEntity) { - data['gameId'] = id; - data['drawingIsActive'] = true; - // get faction - const mapDrawings = await this.mapDrawingRepository.create(data); - + async drawMap(id, data: ReturnDrawingsDTO) { // return mapdrawings with given faction and gameid - return await this.mapDrawingRepository.find(mapDrawings); + return await this.mapDrawingRepository.find({ + where: [ + { + gameId: id, + faction: data.factionId, + drawingIsActive: true, + }, + { + gameId: id, + faction: null, + drawingIsActive: true, + }, + ], + }); } } diff --git a/src/draw/mapdrawing.dto.ts b/src/draw/mapdrawing.dto.ts index 03eca2d1057490558e271252d12a2cc1ef883273..f7e7c781a0cbd308edf5f8c14f068b449f43a05e 100644 --- a/src/draw/mapdrawing.dto.ts +++ b/src/draw/mapdrawing.dto.ts @@ -1,26 +1,27 @@ -import { IsUUID } from 'class-validator'; +import { IsUUID, IsOptional, IsBoolean, Allow } from 'class-validator'; -import { GameDTO } from '../game/game.dto'; -import { GameEntity, Game_PersonEntity } from '../game/game.entity'; import { FactionEntity } from '../faction/faction.entity'; -import { FactionDTO } from '../faction/faction.dto'; -import { MapDrawingEntity } from '../draw/coordinate.entity'; +import { GameEntity } from '../game/game.entity'; export class MapDrawingDTO { + @IsOptional() + @IsUUID('4') + mapDrawingId: string; + @Allow() data: JSON; - gameId: GameDTO; - faction?: FactionDTO; - isActive?: boolean; - validUntil?: string; + @IsOptional() + @IsUUID('4') + gameId: GameEntity; + @IsOptional() + @IsUUID('4') + faction?: FactionEntity; + @IsBoolean() + drawingIsActive?: boolean; + drawingValidTill?: string; } -export class DrawMapDTO { +export class ReturnDrawingsDTO { + @IsOptional() @IsUUID('4') - mapDrawingId: MapDrawingEntity; - - gameId: GameEntity; factionId: FactionEntity; - - gamepersonId: Game_PersonEntity; - data: JSON; } diff --git a/src/faction/faction.controller.ts b/src/faction/faction.controller.ts index 98fcc856d6a5e1cb469e81249eea044df4011877..7574d822c5aef8ef5c350ab349dc0801f916b753 100644 --- a/src/faction/faction.controller.ts +++ b/src/faction/faction.controller.ts @@ -88,4 +88,11 @@ export class FactionController { ) { return this.factionservice.joinFaction(person, data); } + + // check if person belongs to a faction in a game + @Get('check-faction/:id') + @UseGuards(new AuthGuard()) + checkFaction(@User('id') userId, @Param('id') gameId) { + return this.factionservice.verifyUser(userId, gameId); + } } diff --git a/src/faction/faction.entity.ts b/src/faction/faction.entity.ts index 8bc7548a4f37bf367a6cb0381bc2a131d8080da4..3aa74cf615d77deb1421d52d46d4159bf7e0d241 100644 --- a/src/faction/faction.entity.ts +++ b/src/faction/faction.entity.ts @@ -26,15 +26,13 @@ export class FactionEntity { @Column({ type: 'text' }) factionPassword: string; - @OneToMany(type => Game_PersonEntity, game_persons => game_persons.faction, { - onDelete: 'SET NULL', - }) + @OneToMany(type => Game_PersonEntity, game_persons => game_persons.faction) game_persons: Game_PersonEntity[]; - @ManyToOne(type => GameEntity, game => game.factions) - game: GameEntity; - @OneToMany(type => MapDrawingEntity, mapDrawings => mapDrawings.faction, { + @ManyToOne(type => GameEntity, game => game.factions, { onDelete: 'CASCADE', }) + game: GameEntity; + @OneToMany(type => MapDrawingEntity, mapDrawings => mapDrawings.faction) mapDrawings: MapDrawingEntity[]; factionObject() { @@ -47,7 +45,7 @@ export class FactionEntity { } } -@Entity('PowerUp') +/* @Entity('PowerUp') export class PowerUpEntity { @PrimaryGeneratedColumn('uuid') powerUpId: string; @Column({ type: 'text' }) powerUpName: string; @@ -55,7 +53,9 @@ export class PowerUpEntity { @Column({ type: 'int' }) amount: number; @Column({ type: 'time' }) cooldown: string; - @OneToMany(type => FactionEntity, factions => factions.factionId) + @OneToMany(type => FactionEntity, factions => factions.factionId, { + onDelete: 'CASCADE', + }) factions: Faction_PowerUpEntity[]; } @@ -81,17 +81,7 @@ export class FP_HistoryEntity { faction_PowerUp => faction_PowerUp.histories, ) faction_PowerUp: Faction_PowerUpEntity; -} - -@Entity('Score') -export class ScoreEntity { - @PrimaryGeneratedColumn('uuid') scoreId: string; - @Column({ type: 'float' }) score: number; - @Column({ type: 'timestamp' }) scoreTimeStamp: Timestamp; - - @ManyToOne(type => FactionEntity, factionName => factionName.factionId) - faction: FactionEntity; -} +} */ @Entity('GameGroup') export class GameGroupEntity { diff --git a/src/faction/faction.service.ts b/src/faction/faction.service.ts index 76242dfe1bceda0776435b43c4fdc4cb60fe272c..e6bec65d77e45e500aad4c49f1e5f7a6168e53c4 100644 --- a/src/faction/faction.service.ts +++ b/src/faction/faction.service.ts @@ -129,4 +129,19 @@ export class FactionService { }); return members; } + + async verifyUser(person, game) { + const gameperson = await this.game_PersonRepository.findOne({ + where: { person, game }, + relations: ['faction'], + }); + if (gameperson) { + return { + code: 200, + message: gameperson, + }; + } else { + throw new HttpException('No faction was found', HttpStatus.BAD_REQUEST); + } + } } diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index 24d42bbae81d23bbba83af27d06ec58268e69127..8d7494ba4dfa4a77e22963600e45a7579fca5d5d 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -9,6 +9,7 @@ import { Put, UseInterceptors, ClassSerializerInterceptor, + Delete, } from '@nestjs/common'; import { GameService } from './game.service'; @@ -39,6 +40,13 @@ export class GameController { return this.gameservice.editGame(id, body); } + @Delete('delete/:id') + @Roles('admin') + @GameStates('CREATED') + async deleteGame(@Param('id') id: string) { + return this.gameservice.deleteGame(id); + } + @Put('edit-state/:id') @Roles('admin') @UsePipes(new ValidationPipe()) diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts index 7b912b49cbb3c5ab710063873d2a60e30db74c9e..266a8b1564e5b11fb42da755359c79d37f4b170b 100644 --- a/src/game/game.dto.ts +++ b/src/game/game.dto.ts @@ -100,7 +100,7 @@ export class FlagboxEventDTO { @IsNumber() @Min(0) @Max(3) - owner: number; // owner = 0, => first entry in faction db, owner = 1, => second entry etc + owner: number; // owner = 0, => no owner, owner = 1, => first entry in faction db @IsNumber() @Min(0) @Max(3) diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts index c4f4e555ac2bb2aa45bfc0bf2eeac48349f25d86..7ae6e29955d02a2c1368b5f720200f009bd92ee1 100644 --- a/src/game/game.entity.ts +++ b/src/game/game.entity.ts @@ -57,19 +57,19 @@ export class Game_PersonEntity { @PrimaryGeneratedColumn('uuid') gamepersonId: string; @Column({ type: 'text', nullable: true }) role: string; @ManyToOne(type => FactionEntity, faction => faction.game_persons, { - onDelete: 'SET NULL', + onDelete: 'CASCADE', }) faction: FactionEntity; - @ManyToOne(type => GameEntity, game => game.id) + @ManyToOne(type => GameEntity, game => game.id, { + onDelete: 'CASCADE', + }) game: GameEntity; @ManyToOne(type => PersonEntity, person => person.id) person: PersonEntity; - @OneToOne(type => GameGroupEntity, group => group.leader, { - onDelete: 'CASCADE', - }) + @OneToOne(type => GameGroupEntity, group => group.leader) leaderGroup: GameGroupEntity; @ManyToOne(type => GameGroupEntity, group => group.players, { - onDelete: 'CASCADE', + onDelete: 'NO ACTION', }) @JoinColumn({ name: 'group' }) group: GameGroupEntity; @@ -81,9 +81,13 @@ export class ObjectivePointEntity { @Column({ type: 'text' }) objectivePointDescription: string; @Column({ type: 'float' }) objectivePointMultiplier: number; - @ManyToOne(type => MapDrawingEntity, coordinate => coordinate.data) + @ManyToOne(type => MapDrawingEntity, coordinate => coordinate.data, { + onDelete: 'CASCADE', + }) coordinate: MapDrawingEntity; - @ManyToOne(type => GameEntity, game => game.objective_points) + @ManyToOne(type => GameEntity, game => game.objective_points, { + onDelete: 'CASCADE', + }) game: GameEntity; } @@ -103,6 +107,9 @@ export class ObjectivePoint_HistoryEntity { @ManyToOne( type => ObjectivePointEntity, objective_point => objective_point.objectivePointId, + { + onDelete: 'CASCADE', + }, ) - objective_point: ObjectivePointEntity; + objective_point: string; } diff --git a/src/game/game.service.ts b/src/game/game.service.ts index a6c20c41d97c3b8b9c9848b5ceb377682c559a21..5027c6e29d3b1a30824764e2f475d16d745de2c4 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -149,7 +149,9 @@ export class GameService { updatedGame.state = game.state; await this.gameRepository.save(updatedGame); // notify players about game state change - this.notificationGateway.server.emit(game.id, 'event update'); + this.notificationGateway.server.emit(game.id, { + type: 'gamestate-update', + }); return { code: 200, message: 'State was updated', @@ -210,13 +212,14 @@ export class GameService { const eventUpdate = await this.objectivePoint_HistoryRepository.create({ oP_HistoryTimestamp: data.oP_HistoryTimestamp, action: data.action, - capture: factionRef[data.capture], - owner: factionRef[data.owner], - objective_point: objectiveRef, + // -1 as 0 means null + capture: data.capture !== 0 ? factionRef[data.capture - 1] : null, + owner: data.owner !== 0 ? factionRef[data.owner - 1] : null, + objective_point: objectiveRef.objectivePointId, }); await this.objectivePoint_HistoryRepository.insert(eventUpdate); // send flagbox event to flagbox subscribers - this.notificationGateway.server.emit('flagbox', 'event update'); + this.notificationGateway.server.emit(gameId, { type: 'flagbox-event' }); return { code: 201, message: 'OK', diff --git a/src/notifications/notification.dto.ts b/src/notifications/notification.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..807af14058161062d61c9b88a1d8e56637004acc --- /dev/null +++ b/src/notifications/notification.dto.ts @@ -0,0 +1,12 @@ +import { IsString, Length, IsUUID, IsIn } from 'class-validator'; + +export class NotificationdDTO { + // alert is for serious messages, note is for smaller updates on a situation + @IsIn(['alert', 'note']) + type: string; + @IsString() + @Length(0, 63) + message: string; + @IsUUID('4') + game: string; +} diff --git a/src/notifications/notification.entity.ts b/src/notifications/notification.entity.ts index 3f8781633ec42bc3132c5df92f4f74045721506b..f9d5dca9290c30da406a63adf56ee06fe716a7b5 100644 --- a/src/notifications/notification.entity.ts +++ b/src/notifications/notification.entity.ts @@ -1,11 +1,23 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from "typeorm"; +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + ManyToOne, +} from 'typeorm'; + +import { GameEntity } from '../game/game.entity'; // temporary table for warning notifications @Entity('Notifications') export class NotificationEntity { - @PrimaryGeneratedColumn('uuid') id: string; - @Column({type: 'text'}) message: string; - @CreateDateColumn() issued: Date; - // TODO: - // when game creation has been implemented, add logic so that the notifications are tied to games -} \ No newline at end of file + @PrimaryGeneratedColumn('uuid') id: string; + @Column('text') type: string; + @Column({ type: 'text' }) message: string; + @CreateDateColumn() issued: Date; + + @ManyToOne(type => GameEntity, game => game.id, { + onDelete: 'CASCADE', + }) + game: string; +} diff --git a/src/notifications/notifications.gateway.ts b/src/notifications/notifications.gateway.ts index c30114895ffa8d4b40c3cedeaf827c5b3401cb25..5e952d6c78c6f35977f362a7459c0ebf65d1ab5e 100644 --- a/src/notifications/notifications.gateway.ts +++ b/src/notifications/notifications.gateway.ts @@ -6,12 +6,15 @@ import { OnGatewayConnection, OnGatewayDisconnect, } from '@nestjs/websockets'; -import { Logger } from '@nestjs/common'; +import { Logger, UsePipes } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Server, Socket } from 'socket.io'; import { Repository } from 'typeorm'; import { NotificationEntity } from './notification.entity'; +import { GameEntity } from '../game/game.entity'; +import { NotificationdDTO } from './notification.dto'; +import { ValidationPipe } from '../shared/validation.pipe'; @WebSocketGateway() export class NotificationGateway @@ -20,6 +23,8 @@ export class NotificationGateway //create references to tables as repositories @InjectRepository(NotificationEntity) private notificationRepository: Repository<NotificationEntity>, + @InjectRepository(GameEntity) + private gameRepository: Repository<GameEntity>, ) {} @WebSocketServer() server: Server; @@ -40,14 +45,17 @@ export class NotificationGateway } // notifications for when game needs to be paused / stopped - @SubscribeMessage('shutItDown') - async handleMessage(client: Socket, text: string) { - this.logger.log(text); - // send the message to all clients listening to warningToPlayers branch - this.server.emit('warningToPlayers', text); - // create entity properties - const message = await this.notificationRepository.create({ message: text }); - // insert created entity NOTE: insert method doesn't check for duplicates. - await this.notificationRepository.insert(message); + @SubscribeMessage('game-info') + @UsePipes(new ValidationPipe()) + async handleMessage(client: Socket, data: NotificationdDTO) { + // check if the game exists and is either started or paused + const game = await this.gameRepository.findOne({ id: data.game }); + if (game && ['STARTED', 'PAUSED'].includes(game.state)) { + // send the message to all clients listening to gameId branch + this.server.emit(data.game, data); + // create entry for notification in db + const message = await this.notificationRepository.create(data); + await this.notificationRepository.insert(message); + } } } diff --git a/src/notifications/notifications.module.ts b/src/notifications/notifications.module.ts index 194c5c38155d0f976d286456a53814a08f517050..41e2fcaa36d1928dff9a60bd2c6853e240baf16f 100644 --- a/src/notifications/notifications.module.ts +++ b/src/notifications/notifications.module.ts @@ -3,9 +3,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { NotificationGateway } from './notifications.gateway'; import { NotificationEntity } from './notification.entity'; +import { GameEntity } from '../game/game.entity'; @Module({ - imports: [TypeOrmModule.forFeature([NotificationEntity])], + imports: [TypeOrmModule.forFeature([NotificationEntity, GameEntity])], providers: [NotificationGateway], exports: [NotificationGateway], }) diff --git a/src/score/score.controller.ts b/src/score/score.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..554e0617591574ce5c34b3dff8b5fdbaacab4ca4 --- /dev/null +++ b/src/score/score.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Post, UsePipes, Body, Param, Get } from '@nestjs/common'; + +import { ValidationPipe } from '../shared/validation.pipe'; +import { ScoreService } from './score.service'; +import { ScoreDTO } from './score.dto'; +import { GameEntity } from '../game/game.entity'; +import { Roles, GameStates } from '../shared/guard.decorator'; + +@Controller('score') +export class ScoreController { + constructor(private scoreService: ScoreService) {} + + // adds score manually to Faction + // :id is gameId + @Post('add-score/:id') + @Roles('admin') + @GameStates('STARTED') + @UsePipes(new ValidationPipe()) + async addingScore(@Body() data: ScoreDTO, @Param('id') gameId: GameEntity) { + return this.scoreService.addScore(data, gameId); + } + + // temporary scoreTick path, :id is gameId + @Get('tick-score/:id') + @GameStates('STARTED') + async scoreTick(@Param('id') gameId: GameEntity) { + return this.scoreService.scoreTick(gameId); + } +} diff --git a/src/score/score.dto.ts b/src/score/score.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..99955f56eff34a41ce46111d7fd0cb9cd597386f --- /dev/null +++ b/src/score/score.dto.ts @@ -0,0 +1,10 @@ +import { IsNumber, Min, Max, IsUUID } from 'class-validator'; + +export class ScoreDTO { + @IsNumber() + @Min(1) + @Max(99) + score: number; + @IsUUID('4') + faction: string; +} diff --git a/src/score/score.entity.ts b/src/score/score.entity.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ca9ab77b968060a6dc88c860425119914cec030 --- /dev/null +++ b/src/score/score.entity.ts @@ -0,0 +1,21 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + ManyToOne, + Timestamp, + CreateDateColumn, +} from 'typeorm'; +import { FactionEntity } from '../faction/faction.entity'; + +@Entity('Score') +export class ScoreEntity { + @PrimaryGeneratedColumn('uuid') scoreId: string; + @Column({ type: 'float' }) score: number; + @CreateDateColumn({ type: 'timestamp' }) scoreTimeStamp: Timestamp; + + @ManyToOne(type => FactionEntity, factionName => factionName.factionId, { + onDelete: 'CASCADE', + }) + faction: string; +} diff --git a/src/score/score.module.ts b/src/score/score.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c04486fc676fca0d7c6a38c74374e03e23a1008c --- /dev/null +++ b/src/score/score.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { ScoreController } from './score.controller'; +import { ScoreService } from './score.service'; +import { FactionEntity } from '../faction/faction.entity'; +import { + ObjectivePointEntity, + ObjectivePoint_HistoryEntity, +} from '../game/game.entity'; +import { ScoreEntity } from './score.entity'; +import { NotificationModule } from '../notifications/notifications.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + ScoreEntity, + ObjectivePointEntity, + ObjectivePoint_HistoryEntity, + FactionEntity, + ]), + NotificationModule, + ], + controllers: [ScoreController], + providers: [ScoreService], +}) +export class ScoreModule {} diff --git a/src/score/score.service.ts b/src/score/score.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..9dad4e965ae797b31a2f708a7008048556db0992 --- /dev/null +++ b/src/score/score.service.ts @@ -0,0 +1,96 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { FactionEntity } from '../faction/faction.entity'; +import { ScoreDTO } from './score.dto'; +import { + ObjectivePoint_HistoryEntity, + ObjectivePointEntity, + GameEntity, +} from '../game/game.entity'; +import { ScoreEntity } from './score.entity'; +import { NotificationGateway } from '../notifications/notifications.gateway'; + +@Injectable() +export class ScoreService { + constructor( + @InjectRepository(ScoreEntity) + private scoreRepository: Repository<ScoreEntity>, + @InjectRepository(ObjectivePointEntity) + private flagRepository: Repository<ObjectivePointEntity>, + @InjectRepository(ObjectivePoint_HistoryEntity) + private flagHistoryRepository: Repository<ObjectivePoint_HistoryEntity>, + @InjectRepository(FactionEntity) + private factionRepository: Repository<FactionEntity>, + private notificationGateway: NotificationGateway, + ) {} + + async addScore(scoreData: ScoreDTO, gameId: GameEntity) { + // check if faction exists + const faction = await this.factionRepository.findOne({ + factionId: scoreData.faction, + }); + if (!faction) { + throw new HttpException('Faction was not found', HttpStatus.BAD_REQUEST); + } + // get the previous score and add it, if it exists + let lastScore = await this.scoreRepository.findOne({ + where: { faction: scoreData.faction }, + order: { scoreTimeStamp: 'DESC' }, + }); + if (lastScore) { + scoreData.score += lastScore.score; + } + // add the score for Faction + const newScore = await this.scoreRepository.create(scoreData); + await this.scoreRepository.insert(newScore); + return { + code: 201, + message: 'Score updated!', + }; + } + + async scoreTick(gameId) { + // get game's flagboxes + const flagboxes = await this.flagRepository.find({ game: gameId }); + // create an array of DTOs for adding score + let scoreData = []; + await Promise.all( + flagboxes.map(async box => { + // get the newest entry in history + let current = await this.flagHistoryRepository.findOne({ + where: { objective_point: box.objectivePointId }, + relations: ['owner'], + order: { oP_HistoryTimestamp: 'DESC' }, + }); + // if result was found, add score to the owner + if (current.owner) { + let index = await scoreData.findIndex( + i => i.faction === current.owner.factionId, + ); + index !== -1 + ? await (scoreData[index]['score'] += box.objectivePointMultiplier) + : await scoreData.push({ + score: box.objectivePointMultiplier, + faction: current.owner.factionId, + }); + } + }), + ); + scoreData.map(async data => { + await this.addScore(data, gameId); + }); + this.notificationGateway.server.emit(gameId, { type: 'score-update' }); + return { + code: 200, + message: 'Scores added', + }; + } +} // + +// Hae kaikki Objective pointit +// aja map funktio pelin objective pointteihin +// jokaisella objective point ID:llä hae historystä +// relaatio, missä uusin timestamp +// katso uusimmista history entrystä omistaja diff --git a/src/shared/states.guard.ts b/src/shared/states.guard.ts index 4a4b5d423685e8ca5c2bd68cf715c50750981cd7..9e85e94e40f92ed6b016e451c592a3025a684cad 100644 --- a/src/shared/states.guard.ts +++ b/src/shared/states.guard.ts @@ -19,6 +19,8 @@ export class StatesGuard implements CanActivate { private gameRepository: Repository<GameEntity>, ) {} + // Checks the state for gameId and grants access if it matches the criteria + // allowed states are CREATED, STARTED, PAUSED, ENDED async canActivate(context: ExecutionContext): Promise<boolean> { // get game states that are allowed access, identified by @GameStates('state') decorators in controllers const states = this.reflector.get<string[]>('states', context.getHandler()); diff --git a/src/task/task.controller.ts b/src/task/task.controller.ts index acadbda71b34cb0ca591d974b383bca632cd22ce..8283ba5bd2c4d7da4eb668ac6268d5165930e3f4 100644 --- a/src/task/task.controller.ts +++ b/src/task/task.controller.ts @@ -1,7 +1,15 @@ -import { Controller, Post, Body, UsePipes, Get, Param } from '@nestjs/common'; +import { + Controller, + Post, + Body, + UsePipes, + Get, + Param, + Delete, +} from '@nestjs/common'; import { TaskService } from './task.service'; -import { CreateTaskDTO, EditTaskDTO } from './task.dto'; +import { CreateTaskDTO, EditTaskDTO, DeleteTaskDTO } from './task.dto'; import { Roles } from '../shared/guard.decorator'; import { ValidationPipe } from '../shared/validation.pipe'; import { User } from '../user/user.decorator'; @@ -28,7 +36,16 @@ export class TaskController { return this.taskService.editTask(data); } - // lists all the tasks for the game if user has game_person entry + // deletes a created task if the user has admin role in the game + // :id is the id of the game + @Delete('delete-task/:id') + @Roles('admin') + @UsePipes(new ValidationPipe()) + async deleteTask(@Param('id') id: string, @Body() data: DeleteTaskDTO) { + return this.taskService.deleteTask(data); + } + + // lists all the tasks for the game if the user has game_person entry // :id is the id of the game @Get('get-tasks/:id') @Roles('soldier', 'factionleader', 'admin') diff --git a/src/task/task.dto.ts b/src/task/task.dto.ts index a8830d7a770f8337c81b1922dcf02751eaf4e6ad..a1576e7d12c9d9a6263b445fdba293fe94e8964a 100644 --- a/src/task/task.dto.ts +++ b/src/task/task.dto.ts @@ -36,3 +36,8 @@ export class EditTaskDTO { @IsUUID('4') taskGame: GameEntity; } + +export class DeleteTaskDTO { + @IsUUID('4') + taskId: string; +} diff --git a/src/task/task.entity.ts b/src/task/task.entity.ts index c8f7d6218f373d8bd213e49dc7b9d6b2ab8ec68e..f1c1cf103fa4338a7f10d0305888d13c097d7a09 100644 --- a/src/task/task.entity.ts +++ b/src/task/task.entity.ts @@ -24,7 +24,9 @@ export class TaskEntity { onDelete: 'CASCADE', }) taskWinner: FactionEntity; - @ManyToOne(type => GameEntity, game => game.id) + @ManyToOne(type => GameEntity, game => game.id, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'taskGame' }) taskGame: GameEntity; } diff --git a/src/task/task.service.ts b/src/task/task.service.ts index abada374a0c79ae4627c0968b80ce8798fadab04..eedef60368fa3db271f072dd402d675a1e84a234 100644 --- a/src/task/task.service.ts +++ b/src/task/task.service.ts @@ -3,7 +3,7 @@ import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { TaskEntity } from './task.entity'; -import { CreateTaskDTO, EditTaskDTO } from './task.dto'; +import { CreateTaskDTO, EditTaskDTO, DeleteTaskDTO } from './task.dto'; import { FactionEntity } from '../faction/faction.entity'; import { Game_PersonEntity } from '../game/game.entity'; import { NotificationGateway } from '../notifications/notifications.gateway'; @@ -36,10 +36,11 @@ export class TaskService { // 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', + task.faction != null ? task.faction.toString() : task.taskGame.toString(), + { type: 'task-update' }, ); return { + code: 201, message: 'Task added', }; } @@ -63,10 +64,23 @@ export class TaskService { task.taskIsActive = false; await this.taskRepository.save(task); return { + code: 201, message: 'Task updated and closed', }; } + async deleteTask(data: DeleteTaskDTO) { + const task = await this.taskRepository.findOne({ taskId: data.taskId }); + if (task) { + await this.taskRepository.delete({ taskId: task.taskId }); + return { + code: 200, + message: 'Task deleted', + }; + } + throw new HttpException('Task not found', HttpStatus.BAD_REQUEST); + } + async fetchTasks(user, taskGame) { const gamePerson = await this.gamePersonRepository.findOne({ where: { @@ -78,23 +92,22 @@ export class TaskService { if (gamePerson.role == 'admin') { return await this.taskRepository.find({ where: { taskGame: taskGame }, - relations: ['faction'], + relations: ['faction', 'taskWinner'], }); } else { return await this.taskRepository.find({ - relations: ['faction'], + relations: ['faction', 'taskWinner'], where: [ { taskGame: taskGame, faction: gamePerson.faction.factionId, - taskIsActive: true, }, { taskGame: taskGame, faction: null, - taskIsActive: true, }, ], + order: { taskIsActive: 'DESC' }, }); } } diff --git a/src/tracking/tracking.entity.ts b/src/tracking/tracking.entity.ts index b31239101176a0316031cf6c1449205a26bcd150..cabe141a42bcfe076588e320a6eed6c20c6ceaa9 100644 --- a/src/tracking/tracking.entity.ts +++ b/src/tracking/tracking.entity.ts @@ -5,6 +5,8 @@ import { Game_PersonEntity } from '../game/game.entity'; export class TrackingEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column({ type: 'json', nullable: true }) data: JSON; - @ManyToOne(type => Game_PersonEntity, person => person.gamepersonId) + @ManyToOne(type => Game_PersonEntity, person => person.gamepersonId, { + onDelete: 'CASCADE', + }) gamepersonId: Game_PersonEntity; }