Skip to content

Commit b163a5c

Browse files
authored
Merge pull request #592 from TaloDev/develop
Release 0.85.1
2 parents 6061e53 + c78d5a4 commit b163a5c

File tree

4 files changed

+57
-10
lines changed

4 files changed

+57
-10
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "game-services",
3-
"version": "0.85.0",
3+
"version": "0.85.1",
44
"description": "",
55
"main": "src/index.ts",
66
"scripts": {

src/lib/groups/checkGroupMemberships.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EntityManager } from '@mikro-orm/mysql'
1+
import { EntityManager, UniqueConstraintViolationException } from '@mikro-orm/mysql'
22
import Player from '../../entities/player'
33
import PlayerGroup from '../../entities/player-group'
44
import { getResultCacheOptions } from '../perf/getResultCacheOptions'
@@ -21,7 +21,7 @@ async function runMembershipChecksForGroups(em: EntityManager, player: Player, g
2121

2222
for (const group of groups) {
2323
const eligible = await group.isPlayerEligible(em, player)
24-
const isInGroup = player.groups.contains(group)
24+
const isInGroup = player.groups.getIdentifiers().includes(group.id)
2525

2626
const eligibleButNotInGroup = eligible && !isInGroup
2727
const inGroupButNotEligible = !eligible && isInGroup
@@ -60,21 +60,25 @@ export default async function checkGroupMemberships(em: EntityManager, player: P
6060

6161
const redisKey = `checkMembership:${player.id}`
6262
let lockCreated: 'OK' | null = null
63-
let shouldFlush = false
6463

6564
try {
6665
lockCreated = await redis.set(redisKey, '1', 'EX', 30, 'NX')
6766
if (lockCreated) {
68-
shouldFlush = await runMembershipChecksForGroups(em, player, groups)
67+
const shouldFlush = await runMembershipChecksForGroups(em, player, groups)
68+
if (shouldFlush) {
69+
await em.flush()
70+
}
6971
}
7072
} catch (err) {
73+
if (err instanceof UniqueConstraintViolationException) {
74+
console.info(`Duplicate group attempt for player ${player.id}`)
75+
return
76+
}
77+
7178
console.error(`Failed checking memberships: ${(err as Error).message}`)
7279
captureException(err)
7380
} finally {
7481
if (lockCreated) {
75-
if (shouldFlush) {
76-
await em.flush()
77-
}
7882
await redis.del(redisKey)
7983
}
8084
}

tests/services/_api/player-api/patch.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import PlayerGroupRule, { PlayerGroupRuleCastType, PlayerGroupRuleName } from '.
99
import createAPIKeyAndToken from '../../../utils/createAPIKeyAndToken'
1010
import { randWord } from '@ngneat/falso'
1111
import PlayerGroup from '../../../../src/entities/player-group'
12+
import Redis from 'ioredis'
1213

1314
describe('Player API service - patch', () => {
1415
it('should update a player\'s properties', async () => {
@@ -347,4 +348,46 @@ describe('Player API service - patch', () => {
347348
isPlayerEligibleSpy.mockRestore()
348349
consoleSpy.mockRestore()
349350
})
351+
352+
// this is more likely to happen with event/stat flushing, but easier to test it here
353+
it('should handle unique constraint failures for groups', async () => {
354+
const redisSetSpy = vi.spyOn(Redis.prototype, 'set').mockResolvedValue('OK')
355+
const consoleSpy = vi.spyOn(console, 'info')
356+
357+
const [apiKey, token] = await createAPIKeyAndToken([APIKeyScope.WRITE_PLAYERS])
358+
359+
const rule = new PlayerGroupRule(PlayerGroupRuleName.GTE, 'props.currentLevel')
360+
rule.castType = PlayerGroupRuleCastType.DOUBLE
361+
rule.operands = ['60']
362+
363+
const group = await new PlayerGroupFactory().construct(apiKey.game).state(() => ({ rules: [rule] })).one()
364+
365+
const player = await new PlayerFactory([apiKey.game]).state((player) => ({
366+
props: new Collection<PlayerProp>(player, [
367+
new PlayerProp(player, 'collectibles', '0'),
368+
new PlayerProp(player, 'currentLevel', '59')
369+
])
370+
})).one()
371+
await em.persistAndFlush([group, player])
372+
373+
await Promise.allSettled(['60', '61', '62', '63', '64', '65'].map((level) => {
374+
return request(app)
375+
.patch(`/v1/players/${player.id}`)
376+
.send({
377+
props: [
378+
{
379+
key: 'currentLevel',
380+
value: level
381+
}
382+
]
383+
})
384+
.auth(token, { type: 'bearer' })
385+
.expect(200)
386+
}))
387+
388+
expect(consoleSpy).toHaveBeenCalledWith(`Duplicate group attempt for player ${player.id}`)
389+
390+
redisSetSpy.mockRestore()
391+
consoleSpy.mockRestore()
392+
})
350393
})

0 commit comments

Comments
 (0)