diff --git a/handlers/poker.js b/handlers/poker.js index a0d7dd1..28d8997 100644 --- a/handlers/poker.js +++ b/handlers/poker.js @@ -153,6 +153,32 @@ async function recoverPoker( const result = await service.recover(); + const activeTables = + await service.listActiveTables(); + + /* + * Rehydrate every still-active public Poker table after + * restart, even when no recovery action was necessary. + * Mongo remains authoritative; the Discord embed is only + * a projection of the current stored game state. + */ + + for (const game of activeTables) { + try { + await refreshPokerTableMessage( + client, + game + ); + } + catch (error) { + console.error( + `[Poker] Failed to refresh active table ${game.gameId}:`, + error + ); + } + } + + for ( const turn of result.turns ?? [] @@ -212,7 +238,8 @@ async function recoverPoker( `${result.cashouts.length} cash-out(s), ` + `${result.closingTables.length} closing table(s), ` + `${result.turns.length} expired turn(s), ` + - `${result.lobbies.length} expired lobby/lobbies.` + `${result.lobbies.length} expired lobby/lobbies, ` + + `${activeTables.length} active table(s) refreshed.` ); } diff --git a/utils/crumbPoker.js b/utils/crumbPoker.js index 9258229..5b7b727 100644 --- a/utils/crumbPoker.js +++ b/utils/crumbPoker.js @@ -36,7 +36,7 @@ const POKER_DEFAULT_BIG_BLIND = 200; const POKER_DEFAULT_BUY_IN = - 5000; + 1000; const POKER_TURN_TIMEOUT = 30; @@ -652,6 +652,22 @@ function getSpecialGameCollection() { ); } +function stripMongoId( + document +) { + if (!document) { + return null; + } + + const { + _id, + ...rest + } = document; + + return rest; +} + + function createMongoPokerStore( collection = getSpecialGameCollection() @@ -660,13 +676,18 @@ function createMongoPokerStore( async getGame( gameId ) { - return collection.findOne({ - gameId, - gameType: - POKER_GAME_TYPE, - recordType: - "game" - }); + const document = + await collection.findOne({ + gameId, + gameType: + POKER_GAME_TYPE, + recordType: + "game" + }); + + return stripMongoId( + document + ); }, async insertGame( @@ -687,6 +708,14 @@ function createMongoPokerStore( const replacement = clone(nextGame); + /* + * MongoDB owns _id. Domain game objects must never + * replace it, especially after structuredClone() + * has converted a BSON ObjectId into a plain object. + */ + + delete replacement._id; + const result = await collection.replaceOne( { @@ -709,24 +738,29 @@ function createMongoPokerStore( guildId, userId ) { - return collection.findOne({ - gameType: - POKER_GAME_TYPE, - recordType: - "game", - guildId, - state: { - $nin: [ - "CLOSED" - ] - }, - "data.players": { - $elemMatch: { - userId: - String(userId) + const document = + await collection.findOne({ + gameType: + POKER_GAME_TYPE, + recordType: + "game", + guildId, + state: { + $nin: [ + "CLOSED" + ] + }, + "data.players": { + $elemMatch: { + userId: + String(userId) + } } - } - }); + }); + + return stripMongoId( + document + ); }, async listJoinableGames( @@ -751,21 +785,50 @@ function createMongoPokerStore( channelId; } - return collection - .find(filter) - .sort({ - updatedAt: -1 - }) - .limit( - Math.max( - 1, - Math.min( - 25, - Number(limit) || 25 + const documents = + await collection + .find(filter) + .sort({ + updatedAt: -1 + }) + .limit( + Math.max( + 1, + Math.min( + 25, + Number(limit) || 25 + ) ) ) - ) - .toArray(); + .toArray(); + + return documents.map( + stripMongoId + ); + }, + + async listActiveGames() { + const documents = + await collection + .find({ + gameType: + POKER_GAME_TYPE, + recordType: + "game", + state: { + $nin: [ + "CLOSED" + ] + } + }) + .sort({ + updatedAt: -1 + }) + .toArray(); + + return documents.map( + stripMongoId + ); }, async claimMembership({ @@ -2624,6 +2687,11 @@ function createPokerService({ return results; } + async function listActiveTables() { + return store.listActiveGames(); + } + + async function recover() { const [ memberships, @@ -2670,6 +2738,7 @@ function createPokerService({ recoverClosingTables, recoverPendingMemberships, recover, + listActiveTables, publicTableState, sanitizeGameForCaller