diff --git a/docker-compose.yml b/docker-compose.yml index 509e636..1fa3b87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,6 @@ version: '3.1' services: - mongo: image: docker.io/mongo restart: always @@ -10,11 +9,14 @@ services: MONGO_INITDB_ROOT_PASSWORD: example ports: - 27017:27017 -# entrypoint: bash -c "chown 999:999 /opt/keyfile/mongodb-keyfile && chmod 400 /opt/keyfile/mongodb-keyfile && exec docker-entrypoint.sh $$@" + # entrypoint: bash -c "chown 999:999 /opt/keyfile/mongodb-keyfile && chmod 400 /opt/keyfile/mongodb-keyfile && exec docker-entrypoint.sh $$@" command: mongod --replSet rs0 --keyFile /opt/keyfile/mongodb-keyfile volumes: - ./:/opt/keyfile/ + - mongo:/data/db groups: - 999 +volumes: + mongo: # docker run --rm -it --network=host --name mongoContainer mongo:latest mongosh mongodb://127.0.0.1:27017 -u root -p example --eval "rs.initiate({'_id':'rs0', members: [{'_id':1, 'host':'127.0.0.1:27017'}]})" diff --git a/src/games/controllers/games.lobby.controller.ts b/src/games/controllers/games.lobby.controller.ts index 5bde975..3106c57 100644 --- a/src/games/controllers/games.lobby.controller.ts +++ b/src/games/controllers/games.lobby.controller.ts @@ -27,7 +27,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; @Controller('games') export class LobbyController { constructor( - private readonly gamesService: LobbyService, + private readonly lobbyService: LobbyService, private eventEmitter: EventEmitter2, ) {} @@ -45,20 +45,20 @@ export class LobbyController { @ApiResponse({ status: 403, description: 'Forbidden.' }) @UseGuards(AuthGuard) async create(@Session() session: Record) { - const games = await this.gamesService.findByOwner(session.user._id); + const games = await this.lobbyService.findByOwner(session.user._id); if (games) { throw new HttpException( 'You can only create one game at a time', HttpStatus.BAD_REQUEST, ); } - return this.gamesService.create(session.user); + return await this.lobbyService.create(session.user); } @Get() @ApiOperation({ summary: 'List all games' }) findAll() { - const games = this.gamesService.findAll(); + const games = this.lobbyService.findAll(); return games; } @@ -94,7 +94,7 @@ export class LobbyController { HttpStatus.FORBIDDEN, ); } - return this.gamesService.update(game, updateGameDto); + return this.lobbyService.update(game, updateGameDto); } @Delete(':id') @@ -111,7 +111,7 @@ export class LobbyController { HttpStatus.FORBIDDEN, ); } - const deleted = await this.gamesService.remove(game); + const deleted = await this.lobbyService.remove(game); if (deleted) { return; } @@ -152,7 +152,32 @@ export class LobbyController { HttpStatus.FORBIDDEN, ); } - this.gamesService.start(game); + this.lobbyService.start(game); return; } + + @Post(':id/join') + @ApiOperation({ summary: 'Join game' }) + @UseGuards(AuthGuard) + async join( + @Param('id') id: string, + @Session() session: Record, + @Param('id', GamePipe) game: Document & Game, + ) { + if (game.started) { + throw new HttpException('Game already started', HttpStatus.BAD_REQUEST); + } + + if ( + game.teams + .flatMap((t) => t.players) + .some((p) => p.user.userId == session.user.userId) + ) { + throw new HttpException( + 'You are already in this game', + HttpStatus.BAD_REQUEST, + ); + } + return this.lobbyService.joinGame(game, session.user); + } } diff --git a/src/games/dto/database-object.dto.ts b/src/games/dto/database-object.dto.ts index 2a15953..dfbd5a9 100644 --- a/src/games/dto/database-object.dto.ts +++ b/src/games/dto/database-object.dto.ts @@ -3,7 +3,7 @@ import { Types } from 'mongoose'; export class DatabaseObjectDto { constructor(data: { _id: Types.ObjectId }) { - this._id = data._id.toString(); + this._id = data._id.toHexString(); } @ApiProperty() diff --git a/src/games/dto/read-games.dto.ts b/src/games/dto/read-games.dto.ts index 3bd2069..af90059 100644 --- a/src/games/dto/read-games.dto.ts +++ b/src/games/dto/read-games.dto.ts @@ -15,6 +15,8 @@ export class ReadPlayerDto extends DatabaseObjectDto { this.stack = new ReadContainerDto(data.stack, true); this.race = data.race; this.class = data.class; + this.discardMarkers = data.discardMarkers; + this.fuckUMarkers = data.fuckUMarkers; } user: ReadUserDto; @@ -28,6 +30,9 @@ export class ReadPlayerDto extends DatabaseObjectDto { class: string; race: string; + + discardMarkers: number; + fuckUMarkers: number; } export class ReadTeamDto extends DatabaseObjectDto { @@ -54,11 +59,10 @@ export class ReadContainerDto extends DatabaseObjectDto { // if (hidden) { // this.cards = data.cards.map(() => null); // } else { - this.id = data._id.toHexString(); this.cards = data.cards; // } } - id: string; + cards: Card[]; } diff --git a/src/games/schemas/game.schema.ts b/src/games/schemas/game.schema.ts index c619c29..1ec8289 100644 --- a/src/games/schemas/game.schema.ts +++ b/src/games/schemas/game.schema.ts @@ -65,6 +65,12 @@ export class Player extends Types.ObjectId { @Prop({ type: ContainerSchema, default: {}, autopopulate: true }) discard!: Container; + + @Prop({ type: Number, default: 0 }) + discardMarkers: number; + + @Prop({ type: Number, default: 0 }) + fuckUMarkers: number; } export const PlayerSchema = SchemaFactory.createForClass(Player); diff --git a/src/games/services/games.lobby.service.ts b/src/games/services/games.lobby.service.ts index c46703d..c0392d2 100644 --- a/src/games/services/games.lobby.service.ts +++ b/src/games/services/games.lobby.service.ts @@ -1,9 +1,13 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DatabaseObjectDto } from '../dto/database-object.dto'; -import { InjectModel } from '@nestjs/mongoose'; +import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { Game, Player, Team } from '../schemas/game.schema'; import mongoose, { Document, Model } from 'mongoose'; -import { ReadContainerDto, ReadGameDto } from '../dto/read-games.dto'; +import { + ReadContainerDto, + ReadGameDto, + ReadTeamDto, +} from '../dto/read-games.dto'; import { User } from 'src/users/schemas/user.entity'; import { Card } from 'src/cards/schemas/cards.schema'; import { CardRole } from 'src/cards/schemas/cards.types'; @@ -17,11 +21,13 @@ export class LobbyService { constructor( @InjectModel(Game.name) private gameModel: Model, @InjectModel(Card.name) private cardModel: Model, + @InjectConnection() private readonly connection: mongoose.Connection, private readonly gamesService: GameService, private eventEmitter: EventEmitter2, ) {} - async create(owner: User): Promise { + @WithTransaction + async create(owner: User, session?: mongoose.mongo.ClientSession) { const createdGame = new this.gameModel(); createdGame.owner = owner; createdGame.packs = [ @@ -31,15 +37,39 @@ export class LobbyService { 'pack1_base_imperial', 'pack1_base_guild', ]; - createdGame.teams.push({ - players: [{ user: owner } as Player], + await this.joinGame(createdGame, owner, session); + const dto = new ReadGameDto(createdGame); + this.eventEmitter.emit('sse.lobby', { type: 'create', ...dto }); + return dto; + } + + @WithTransaction + async joinGame( + game: Document & Game, + user: User, + session?: mongoose.mongo.ClientSession, + ) { + game.teams.push({ + players: [{ user } as Player], } as Team); - const gamedata = await createdGame.save(); - const dto = new ReadGameDto(gamedata); - this.eventEmitter.emit('sse.lobby', { type: 'create', ...dto }); + const team = game.teams.at(-1); + // this.eventEmitter.emit('sse.lobby', { + // type: 'joinGame', + // team: new ReadTeamDto(team), + // }); + this.eventEmitter.emit('sse.lobby', { + type: 'joinGame', + team: new ReadTeamDto(team), + game: game._id.toHexString(), + }); + this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'joinGame', + team: new ReadTeamDto(team), + }); + await game.save({ session }); - return dto; + return 'true!'; } async findAll(): Promise { @@ -100,7 +130,7 @@ export class LobbyService { game.started = true; this.eventEmitter.emit('sse.lobby', { type: 'start', - _id: game._id, + game: game._id, }); this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'start', diff --git a/src/games/utils.ts b/src/games/utils.ts index 4d0211f..e89c29d 100644 --- a/src/games/utils.ts +++ b/src/games/utils.ts @@ -55,24 +55,30 @@ export function WithTransaction( ) { const originalFunc = descriptor.value; - descriptor.value = async function (...args: any[]) { - if (args.at(-1) instanceof mongoose.mongo.ClientSession) { - return originalFunc.apply(this, args); - } else { - await this.connection - .transaction(async (session) => { - return originalFunc.apply(this, [...args, session]); - }) - .catch((e) => { - if (e instanceof HttpException) { - throw e; - } - throw new HttpException( - 'Another request is processing', - HttpStatus.TOO_MANY_REQUESTS, - ); - }); - } + descriptor.value = function (...args: any[]) { + return new Promise(async (resolve) => { + if (args.at(-1) instanceof mongoose.mongo.ClientSession) { + const data = await originalFunc.apply(this, args); + resolve(data); + return data; + } else { + await this.connection + .transaction(async (session) => { + const data = await originalFunc.apply(this, [...args, session]); + resolve(data); + return data; + }) + .catch((e) => { + if (e instanceof HttpException) { + throw e; + } + throw new HttpException( + 'Another request is processing', + HttpStatus.TOO_MANY_REQUESTS, + ); + }); + } + }); }; return descriptor;