Skip to content

Commit 1a7c3fb

Browse files
authored
Merge pull request #45 from TaloDev/develop
Tech debt & bug fixes
2 parents c4160b4 + 4455156 commit 1a7c3fb

File tree

7 files changed

+44
-35
lines changed

7 files changed

+44
-35
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Talo's backend is a set of self-hostable services that helps you build games fas
55
## Features
66
- Event tracking
77
- Player management (including identity and cross-session data)
8+
- Leaderboards
89

910
### In progress
1011
- Global stats (track unique or aggregated stats e.g. total quests completed)

__mocks__/bee-queue.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,32 @@ import { EventEmitter } from 'events'
33
class Job<T> extends EventEmitter {
44
data: T
55
queue: Queue<T>
6+
date: Date
67

78
constructor(data: T, queue: Queue<T>) {
89
super()
910
this.data = data
1011
this.queue = queue
12+
this.date = new Date()
1113
}
1214

13-
async process(handler: (job: Job<T>) => Promise<T>): Promise<void> {
14-
await handler(this)
15+
delayUntil(date: Date): Job<T> {
16+
this.date = date
17+
return this
18+
}
19+
20+
async save(): Promise<Job<T>> {
21+
try {
22+
await this.queue.handler(this)
23+
} catch (err) {
24+
this.emit('failed', err)
25+
}
26+
27+
return this
1528
}
1629
}
1730

18-
export default class Queue<T = any> extends EventEmitter {
19-
job: Job<T>
31+
export default class Queue<T = unknown> extends EventEmitter {
2032
handler: (job: Job<T>) => Promise<T>
2133
name: string
2234

@@ -29,22 +41,12 @@ export default class Queue<T = any> extends EventEmitter {
2941
this.handler = handler
3042
}
3143

32-
createJob(data: T): Queue<T> {
33-
this.job = new Job(data, this)
34-
return this
35-
}
44+
createJob(data: T): Job<T> {
45+
const job = new Job<T>(data, this)
46+
job.on('failed', (err: Error) => {
47+
this.emit('failed', job, err)
48+
})
3649

37-
delayUntil(date: Date): Queue<T> {
38-
return this
39-
}
40-
41-
async save(): Promise<Queue<T>> {
42-
try {
43-
await this.job.process(this.handler)
44-
} catch (err) {
45-
this.emit('failed', this.job, err)
46-
}
47-
48-
return this
50+
return job
4951
}
5052
}

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.1.1",
3+
"version": "0.1.2",
44
"description": "",
55
"main": "src/index.ts",
66
"scripts": {

src/entities/event.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Entity, Embedded, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
1+
import { Entity, Embedded, ManyToOne, PrimaryKey, Property, Cascade } from '@mikro-orm/core'
22
import Game from './game'
33
import PlayerAlias from './player-alias'
44
import Prop from './prop'
@@ -17,7 +17,7 @@ export default class Event {
1717
@ManyToOne(() => Game)
1818
game: Game
1919

20-
@ManyToOne(() => PlayerAlias)
20+
@ManyToOne(() => PlayerAlias, { cascade: [Cascade.REMOVE] })
2121
playerAlias: PlayerAlias
2222

2323
@Property()

src/services/leaderboards.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default class LeaderboardsService implements Service {
5959
gameId: true,
6060
internalName: async (val: string, req: ServiceRequest): Promise<boolean> => {
6161
const em: EntityManager = req.ctx.em
62-
const duplicateInternalName = await em.getRepository(Leaderboard).findOne({ internalName: val })
62+
const duplicateInternalName = await em.getRepository(Leaderboard).findOne({ internalName: val, game: req.body.gameId })
6363

6464
if (duplicateInternalName) throw new Error(`A leaderboard with the internalName ${val} already exists`)
6565

tests/services/_api/events-api/post.test.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import Player from '../../../../src/entities/player'
66
import Game from '../../../../src/entities/game'
77
import APIKey, { APIKeyScope } from '../../../../src/entities/api-key'
88
import { createToken } from '../../../../src/services/api-keys.service'
9-
import Event from '../../../../src/entities/event'
109
import UserFactory from '../../../fixtures/UserFactory'
1110
import PlayerFactory from '../../../fixtures/PlayerFactory'
1211
import GameFactory from '../../../fixtures/GameFactory'
@@ -31,12 +30,6 @@ describe('Events API service - post', () => {
3130
await (<EntityManager>app.context.em).persistAndFlush([apiKey, validPlayer])
3231
})
3332

34-
beforeEach(async () => {
35-
const repo = (<EntityManager>app.context.em).getRepository(Event)
36-
const events = await repo.findAll()
37-
await repo.removeAndFlush(events)
38-
})
39-
4033
afterAll(async () => {
4134
await (<EntityManager>app.context.em).getConnection().close()
4235
})
@@ -46,14 +39,15 @@ describe('Events API service - post', () => {
4639
await (<EntityManager>app.context.em).flush()
4740
token = await createToken(apiKey)
4841

49-
await request(app.callback())
42+
const res = await request(app.callback())
5043
.post(`${baseUrl}`)
5144
.send({ events: [{ name: 'Craft bow', aliasId: validPlayer.aliases[0].id, timestamp: Date.now() }] })
5245
.auth(token, { type: 'bearer' })
5346
.expect(200)
5447

55-
const event = await (<EntityManager>app.context.em).getRepository(Event).findOne({ name: 'Craft bow' })
56-
expect(event).toBeTruthy()
48+
expect(res.body.events).toHaveLength(1)
49+
expect(res.body.events[0].name).toBe('Craft bow')
50+
expect(res.body.events[0].playerAlias.id).toBe(validPlayer.aliases[0].id)
5751
})
5852

5953
it('should create multiple events if the scope is valid', async () => {

tests/services/leaderboards/post.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,22 @@ describe('Leaderboards service - post', () => {
104104

105105
const res = await request(app.callback())
106106
.post(`${baseUrl}`)
107-
.send({ gameId: validGame.id, internalName: 'highscores', name: 'Highscores', sortMode: 'blah', unique: true })
107+
.send({ gameId: validGame.id, internalName: 'highscores', name: 'Highscores', sortMode: 'asc', unique: true })
108108
.auth(token, { type: 'bearer' })
109109
.expect(400)
110110

111111
expect(res.body).toStrictEqual({ message: 'A leaderboard with the internalName highscores already exists' })
112112
})
113+
114+
it('should create a leaderboard with a duplicate internal name for another game', async () => {
115+
const otherGame = await new GameFactory(user.organisation).one()
116+
const otherLeaderboard = await new LeaderboardFactory([otherGame]).with(() => ({ internalName: 'time-survived' })).one()
117+
await (<EntityManager>app.context.em).persistAndFlush(otherLeaderboard)
118+
119+
await request(app.callback())
120+
.post(`${baseUrl}`)
121+
.send({ gameId: validGame.id, internalName: 'time-survived', name: 'Time survived', sortMode: 'asc', unique: true })
122+
.auth(token, { type: 'bearer' })
123+
.expect(200)
124+
})
113125
})

0 commit comments

Comments
 (0)