Fix : add transaction
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
SESSION_SECRET=dev
|
||||
|
||||
DISCORD_CLIENTID=
|
||||
DISCORD_CLIENTSECRET=
|
||||
|
||||
MONGO_ENDPOINT=127.0.0.1
|
||||
MONGO_PASSWORD=example
|
||||
MONGO_USER=root
|
||||
|
||||
DEV_MODE=true
|
||||
@@ -398,3 +398,4 @@ Temporary Items
|
||||
dist
|
||||
.webpack
|
||||
.serverless/**/*.zip
|
||||
mongodb-keyfile
|
||||
Vendored
+2
-1
@@ -20,7 +20,8 @@
|
||||
"type": "node",
|
||||
"console": "internalConsole",
|
||||
"outputCapture": "std",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,3 +10,8 @@ services:
|
||||
MONGO_INITDB_ROOT_PASSWORD: example
|
||||
ports:
|
||||
- 27017:27017
|
||||
command: mongod --replSet rs0 --keyFile /opt/keyfile/mongodb-keyfile
|
||||
volumes:
|
||||
- ./:/opt/keyfile/
|
||||
|
||||
# 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'}]})"
|
||||
@@ -82,20 +82,8 @@ export class GamesController {
|
||||
const currentTurn = game.teams.find(
|
||||
(t) => t._id.toHexString() == game.currentTurn,
|
||||
);
|
||||
if (!currentTurn.players.some((p) => p._id == player._id)) {
|
||||
throw new HttpException('It is not your turn', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (
|
||||
!currentTurn.players.some(
|
||||
(p) => p._id.toHexString() == player._id.toHexString(),
|
||||
)
|
||||
) {
|
||||
throw new HttpException(
|
||||
'It is not your turn to play',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
await this.gameService.endTurn(game);
|
||||
|
||||
await this.gameService.endTurn(game, player._id.toHexString());
|
||||
}
|
||||
|
||||
@Sse('/subscribe')
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { InjectModel, InjectConnection } from '@nestjs/mongoose';
|
||||
import { Container, Game, Player } from '../schemas/game.schema';
|
||||
import { Document, Model } from 'mongoose';
|
||||
import mongoose, { Document, Model } from 'mongoose';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Console } from 'console';
|
||||
|
||||
function shuffle(a) {
|
||||
let j, x, i;
|
||||
@@ -16,33 +17,74 @@ function shuffle(a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
function WithTransaction(
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
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) => {
|
||||
throw new HttpException(
|
||||
'Another request is processing',
|
||||
HttpStatus.TOO_MANY_REQUESTS,
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GameService {
|
||||
constructor(
|
||||
@InjectModel(Game.name) private gameModel: Model<Game>,
|
||||
@InjectModel(Card.name) private cardModel: Model<Card>,
|
||||
@InjectConnection() private readonly connection: mongoose.Connection,
|
||||
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async endTurn(game: Document<Game> & Game) {
|
||||
@WithTransaction
|
||||
async endTurn(
|
||||
game: Document<Game> & Game,
|
||||
currentPlayerId: string,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
let currentTurn = game.teams.find(
|
||||
(t) => t._id.toHexString() == game.currentTurn,
|
||||
);
|
||||
if (
|
||||
!currentTurn.players.some((p) => p._id.toHexString() == currentPlayerId)
|
||||
) {
|
||||
throw new HttpException(
|
||||
'It is not your turn to play',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
for (const player of currentTurn.players) {
|
||||
await this.distributeCards(
|
||||
player.board.cards.filter((c) => !c.defense),
|
||||
player.board,
|
||||
player.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
|
||||
await this.drawToHand(5, player, game);
|
||||
await this.drawToHand(5, player, game, session);
|
||||
}
|
||||
|
||||
const index = game.teams.indexOf(currentTurn);
|
||||
game.currentTurn =
|
||||
game.teams[(index + 1) % game.teams.length]._id.toHexString();
|
||||
await game.save();
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'endTurn',
|
||||
@@ -59,68 +101,105 @@ export class GameService {
|
||||
player.hand,
|
||||
player.board,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
async drawToBoard(amount, player: Player, game: Document<Game> & Game) {
|
||||
@WithTransaction
|
||||
async drawToBoard(
|
||||
amount,
|
||||
player: Player,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
await this.drawCards(
|
||||
amount,
|
||||
player.stack,
|
||||
player.board,
|
||||
player.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
|
||||
async drawToHand(amount, player: Player, game: Document<Game> & Game) {
|
||||
@WithTransaction
|
||||
async drawToHand(
|
||||
amount,
|
||||
player: Player,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
await this.drawCards(
|
||||
amount,
|
||||
player.stack,
|
||||
player.hand,
|
||||
player.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
private async drawCards(
|
||||
amount: number,
|
||||
from: Container,
|
||||
to: Container,
|
||||
discard: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (amount <= from.cards.length) {
|
||||
await this.distributeCards(from.cards.slice(0, amount), from, to, game);
|
||||
await game.save();
|
||||
await this.distributeCards(
|
||||
from.cards.slice(0, amount),
|
||||
from,
|
||||
to,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
await game.save({ session });
|
||||
return;
|
||||
} else {
|
||||
const toDraw = from.cards.length;
|
||||
await this.distributeCards(from.cards.slice(0, toDraw), from, to, game);
|
||||
await this.distributeCards(
|
||||
from.cards.slice(0, toDraw),
|
||||
from,
|
||||
to,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
if (discard.cards.length == 0) {
|
||||
await game.save();
|
||||
await game.save({ session });
|
||||
return;
|
||||
}
|
||||
// restack all discarded
|
||||
await this.distributeCards(discard.cards, discard, from, game);
|
||||
await this.shuffleContainer(from, game);
|
||||
await this.drawCards(amount - toDraw, from, to, discard, game);
|
||||
await this.distributeCards(discard.cards, discard, from, game, session);
|
||||
await this.shuffleContainer(from, game, session);
|
||||
await this.drawCards(amount - toDraw, from, to, discard, game, session);
|
||||
}
|
||||
}
|
||||
|
||||
async shuffleContainer(container: Container, game: Document<Game> & Game) {
|
||||
@WithTransaction
|
||||
async shuffleContainer(
|
||||
container: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
container.cards = shuffle(container.cards);
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'shuffleContainer',
|
||||
container: container._id.toHexString(),
|
||||
});
|
||||
await game.save();
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async distributeCards(
|
||||
cards: Card[],
|
||||
from: Container,
|
||||
to: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (cards.some((c) => !from.cards.includes(c))) {
|
||||
throw new HttpException(
|
||||
@@ -134,7 +213,7 @@ export class GameService {
|
||||
from.cards.splice(index, 1);
|
||||
to.cards.push(c);
|
||||
}
|
||||
await game.save();
|
||||
await game.save({ session });
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'distributeCards',
|
||||
from: from._id.toHexString(),
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as session from 'express-session';
|
||||
import { AppModule } from './app.module';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import MongoStore from 'connect-mongo';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
@@ -15,6 +16,8 @@ async function bootstrap() {
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
mongoose.set('transactionAsyncLocalStorage', true);
|
||||
|
||||
app.use(
|
||||
session.default({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
|
||||
Reference in New Issue
Block a user