Skip to content

Commit

Permalink
add support for props in leaderboard entries
Browse files Browse the repository at this point in the history
  • Loading branch information
tudddorrr committed Sep 23, 2024
1 parent 50b783d commit 3d64a69
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 5 deletions.
7 changes: 6 additions & 1 deletion src/entities/leaderboard-entry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Cascade, Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/mysql'
import { Cascade, Embedded, Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/mysql'
import Leaderboard from './leaderboard'
import PlayerAlias from './player-alias'
import Prop from './prop'

@Entity()
export default class LeaderboardEntry {
Expand All @@ -16,6 +17,9 @@ export default class LeaderboardEntry {
@ManyToOne(() => PlayerAlias, { cascade: [Cascade.REMOVE], eager: true })
playerAlias: PlayerAlias

@Embedded(() => Prop, { array: true })
props: Prop[] = []

@Property({ default: false })
hidden: boolean

Expand All @@ -37,6 +41,7 @@ export default class LeaderboardEntry {
leaderboardInternalName: this.leaderboard.internalName,
playerAlias: this.playerAlias,
hidden: this.hidden,
props: this.props,
createdAt: this.createdAt,
updatedAt: this.updatedAt
}
Expand Down
10 changes: 10 additions & 0 deletions src/migrations/.snapshot-gs_dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,16 @@
"length": null,
"mappedType": "integer"
},
"props": {
"name": "props",
"type": "json",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": null,
"mappedType": "json"
},
"hidden": {
"name": "hidden",
"type": "tinyint(1)",
Expand Down
13 changes: 13 additions & 0 deletions src/migrations/20240922222426AddLeaderboardEntryPropsColumn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Migration } from '@mikro-orm/migrations'

export class AddLeaderboardEntryPropsColumn extends Migration {

override async up(): Promise<void> {
this.addSql('alter table `leaderboard_entry` add `props` json not null;')
}

override async down(): Promise<void> {
this.addSql('alter table `leaderboard_entry` drop column `props`;')
}

}
5 changes: 5 additions & 0 deletions src/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { CreatePlayerAuthTable } from './20240628155142CreatePlayerAuthTable'
import { CreatePlayerAuthActivityTable } from './20240725183402CreatePlayerAuthActivityTable'
import { UpdatePlayerAliasServiceColumn } from './20240916213402UpdatePlayerAliasServiceColumn'
import { AddPlayerAliasAnonymisedColumn } from './20240920121232AddPlayerAliasAnonymisedColumn'
import { AddLeaderboardEntryPropsColumn } from './20240922222426AddLeaderboardEntryPropsColumn'

export default [
{
Expand Down Expand Up @@ -149,5 +150,9 @@ export default [
{
name: 'AddPlayerAliasAnonymisedColumn',
class: AddPlayerAliasAnonymisedColumn
},
{
name: 'AddLeaderboardEntryPropsColumn',
class: AddLeaderboardEntryPropsColumn
}
]
25 changes: 21 additions & 4 deletions src/services/api/leaderboard-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Leaderboard, { LeaderboardSortMode } from '../../entities/leaderboard'
import LeaderboardAPIDocs from '../../docs/leaderboard-api.docs'
import triggerIntegrations from '../../lib/integrations/triggerIntegrations'
import { devDataPlayerFilter } from '../../middlewares/dev-data-middleware'
import sanitiseProps from '../../lib/props/sanitiseProps'
import { uniqWith } from 'lodash'

@Routes([
{
Expand All @@ -31,7 +33,7 @@ export default class LeaderboardAPIService extends APIService {
})
}

async createEntry(req: Request): Promise<LeaderboardEntry> {
async createEntry(req: Request, props?: { key: string, value: string }[]): Promise<LeaderboardEntry> {
const em: EntityManager = req.ctx.em

const entry = new LeaderboardEntry(req.ctx.state.leaderboard)
Expand All @@ -40,6 +42,9 @@ export default class LeaderboardAPIService extends APIService {
if (req.ctx.state.continuityDate) {
entry.createdAt = req.ctx.state.continuityDate
}
if (props) {
entry.props = sanitiseProps(props)
}

await em.persistAndFlush(entry)

Expand All @@ -53,9 +58,13 @@ export default class LeaderboardAPIService extends APIService {
@HasPermission(LeaderboardAPIPolicy, 'post')
@Docs(LeaderboardAPIDocs.post)
async post(req: Request): Promise<Response> {
const { score } = req.body
const { score, props } = req.body
const em: EntityManager = req.ctx.em

if (props && !Array.isArray(props)) {
req.ctx.throw(400, 'Props must be an array')
}

Check warning on line 66 in src/services/api/leaderboard-api.service.ts

View check run for this annotation

Codecov / codecov/patch

src/services/api/leaderboard-api.service.ts#L65-L66

Added lines #L65 - L66 were not covered by tests

const leaderboard: Leaderboard = req.ctx.state.leaderboard

let entry: LeaderboardEntry = null
Expand All @@ -71,15 +80,23 @@ export default class LeaderboardAPIService extends APIService {
if ((leaderboard.sortMode === LeaderboardSortMode.ASC && score < entry.score) || (leaderboard.sortMode === LeaderboardSortMode.DESC && score > entry.score)) {
entry.score = score
entry.createdAt = req.ctx.state.continuityDate ?? new Date()
if (props) {
const mergedProps = uniqWith([
...sanitiseProps(props),
...entry.props
], (a, b) => a.key === b.key)

entry.props = sanitiseProps(mergedProps, true)
}
await em.flush()

updated = true
}
} else {
entry = await this.createEntry(req)
entry = await this.createEntry(req, props)
}
} catch (err) {
entry = await this.createEntry(req)
entry = await this.createEntry(req, props)
}

await triggerIntegrations(em, leaderboard.game, (integration) => {
Expand Down
64 changes: 64 additions & 0 deletions tests/services/_api/leaderboard-api/post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,4 +266,68 @@ describe('Leaderboard API service - post', () => {

expect(new Date(res.body.entry.createdAt).getHours()).toBe(continuityDate.getHours())
})

it('should create entries with props', async () => {
const [apiKey, token] = await createAPIKeyAndToken([APIKeyScope.WRITE_LEADERBOARDS])
const player = await new PlayerFactory([apiKey.game]).one()
const leaderboard = await new LeaderboardFactory([apiKey.game]).state(() => ({ unique: false })).one()
await (<EntityManager>global.em).persistAndFlush([player, leaderboard])

const res = await request(global.app)
.post(`/v1/leaderboards/${leaderboard.internalName}/entries`)
.send({
score: 300,
props: [
{ key: 'key1', value: 'value1' },
{ key: 'key2', value: 'value2' }
]
})
.auth(token, { type: 'bearer' })
.set('x-talo-alias', String(player.aliases[0].id))
.expect(200)

expect(res.body.entry.score).toBe(300)
expect(res.body.entry.props).toStrictEqual([
{ key: 'key1', value: 'value1' },
{ key: 'key2', value: 'value2' }
])
})

it('should update an existing entry\'s props', async () => {
const [apiKey, token] = await createAPIKeyAndToken([APIKeyScope.WRITE_LEADERBOARDS])
const player = await new PlayerFactory([apiKey.game]).one()
const leaderboard = await new LeaderboardFactory([apiKey.game]).state(() => ({ unique: true, sortMode: LeaderboardSortMode.DESC })).one()

const entry = await new LeaderboardEntryFactory(leaderboard, [player]).state(() => ({
score: 100,
playerAlias: player.aliases[0],
props: [
{ key: 'key1', value: 'value1' },
{ key: 'delete-me', value: 'delete-me' }
]
})).one()

await (<EntityManager>global.em).persistAndFlush([player, leaderboard, entry])

const res = await request(global.app)
.post(`/v1/leaderboards/${leaderboard.internalName}/entries`)
.send({
score: 300,
props: [
{ key: 'key2', value: 'value2' },
{ key: 'delete-me', value: null }
]
})
.auth(token, { type: 'bearer' })
.set('x-talo-alias', String(player.aliases[0].id))
.expect(200)

expect(res.body.entry.score).toBe(300)
expect(res.body.updated).toBe(true)

expect(res.body.entry.props).toStrictEqual([
{ key: 'key2', value: 'value2' },
{ key: 'key1', value: 'value1' }
])
})
})

0 comments on commit 3d64a69

Please sign in to comment.