Skip to content

Commit a860a99

Browse files
authored
Merge pull request #59 from TaloDev/develop
Release 0.5.0
2 parents 10b248b + f0dd348 commit a860a99

28 files changed

+620
-26
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ 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+
- [Unity SDK](https://github.com/TaloDev/unity)
9+
- Data exports
810
- Leaderboards
11+
- Game saves
912

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

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

src/config/protected-routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Koa, { Context, Next } from 'koa'
22
import { service } from 'koa-rest-services'
3+
import GameActivitiesService from '../services/game-activities.service'
34
import LeaderboardsService from '../services/leaderboards.service'
45
import DataExportsService from '../services/data-exports.service'
56
import APIKeysService from '../services/api-keys.service'
@@ -17,6 +18,7 @@ export default (app: Koa) => {
1718
await next()
1819
})
1920

21+
app.use(service('/game-activities', new GameActivitiesService()))
2022
app.use(service('/leaderboards', new LeaderboardsService()))
2123
app.use(service('/data-exports', new DataExportsService()))
2224
app.use(service('/api-keys', new APIKeysService()))

src/entities/game-activity.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
2+
import Game from './game'
3+
import User from './user'
4+
5+
export enum GameActivityType {
6+
PLAYER_PROPS_UPDATED,
7+
LEADERBOARD_CREATED,
8+
LEADERBOARD_UPDATED,
9+
LEADERBOARD_DELETED,
10+
LEADERBOARD_ENTRY_HIDDEN,
11+
LEADERBOARD_ENTRY_RESTORED,
12+
API_KEY_CREATED,
13+
API_KEY_REVOKED
14+
}
15+
16+
@Entity()
17+
export default class GameActivity {
18+
@PrimaryKey()
19+
id: number
20+
21+
@ManyToOne(() => Game)
22+
game: Game
23+
24+
@ManyToOne(() => User)
25+
user: User
26+
27+
@Enum(() => GameActivityType)
28+
type: GameActivityType
29+
30+
@Property({ type: 'json' })
31+
extra: {
32+
[key: string]: unknown,
33+
displayable?: {
34+
[key: string]: string
35+
}
36+
} = {}
37+
38+
@Property()
39+
createdAt: Date = new Date()
40+
41+
constructor(game: Game, user: User) {
42+
this.game = game
43+
this.user = user
44+
}
45+
46+
/* istanbul ignore next */
47+
private getActivity(): string {
48+
switch (this.type) {
49+
case GameActivityType.PLAYER_PROPS_UPDATED:
50+
return `${this.user.email} updated a player's props`
51+
case GameActivityType.LEADERBOARD_CREATED:
52+
return `${this.user.email} created the leaderboard ${this.extra.leaderboardInternalName}`
53+
case GameActivityType.LEADERBOARD_UPDATED:
54+
return `${this.user.email} updated the leaderboard ${this.extra.leaderboardInternalName}`
55+
case GameActivityType.LEADERBOARD_DELETED:
56+
return `${this.user.email} deleted the leaderboard ${this.extra.leaderboardInternalName}`
57+
case GameActivityType.LEADERBOARD_ENTRY_HIDDEN:
58+
return `${this.user.email} hid a leaderboard entry in ${this.extra.leaderboardInternalName}`
59+
case GameActivityType.LEADERBOARD_ENTRY_RESTORED:
60+
return `${this.user.email} restored a leaderboard entry in ${this.extra.leaderboardInternalName}`
61+
case GameActivityType.API_KEY_CREATED:
62+
return `${this.user.email} created an access key`
63+
case GameActivityType.API_KEY_REVOKED:
64+
return `${this.user.email} remove an access key`
65+
default:
66+
return ''
67+
}
68+
}
69+
70+
toJSON() {
71+
return {
72+
id: this.id,
73+
type: this.type,
74+
description: this.getActivity(),
75+
extra: this.extra.display,
76+
createdAt: this.createdAt
77+
}
78+
}
79+
}

src/entities/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import GameActivity from './game-activity'
12
import GameSave from './game-save'
23
import Leaderboard from './leaderboard'
34
import LeaderboardEntry from './leaderboard-entry'
@@ -17,6 +18,7 @@ import UserTwoFactorAuth from './user-two-factor-auth'
1718
import UserRecoveryCode from './user-recovery-code'
1819

1920
export default [
21+
GameActivity,
2022
GameSave,
2123
UserRecoveryCode,
2224
UserTwoFactorAuth,

src/entities/leaderboard-entry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default class LeaderboardEntry {
3434
id: this.id,
3535
score: this.score,
3636
leaderboardName: this.leaderboard.name,
37+
leaderboardInternalName: this.leaderboard.internalName,
3738
playerAlias: {
3839
id: this.playerAlias.id,
3940
service: this.playerAlias.service,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { EntityManager } from '@mikro-orm/core'
2+
import GameActivity from '../../entities/game-activity'
3+
4+
export default async function createGameActivity(em: EntityManager, data: Partial<GameActivity>): Promise<GameActivity> {
5+
const activity = new GameActivity(data.game, data.user)
6+
activity.type = data.type
7+
activity.extra = data.extra
8+
9+
em.persist(activity)
10+
11+
return activity
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Migration } from '@mikro-orm/migrations'
2+
3+
export class CreateGameActivitiesTable extends Migration {
4+
5+
async up(): Promise<void> {
6+
this.addSql('create table `game_activity` (`id` int unsigned not null auto_increment primary key, `game_id` int(11) unsigned not null, `user_id` int(11) unsigned not null, `type` tinyint not null, `extra` json not null, `created_at` datetime not null) default character set utf8mb4 engine = InnoDB;')
7+
this.addSql('alter table `game_activity` add index `game_activity_game_id_index`(`game_id`);')
8+
this.addSql('alter table `game_activity` add index `game_activity_user_id_index`(`user_id`);')
9+
10+
this.addSql('alter table `game_activity` add constraint `game_activity_game_id_foreign` foreign key (`game_id`) references `game` (`id`) on update cascade;')
11+
this.addSql('alter table `game_activity` add constraint `game_activity_user_id_foreign` foreign key (`user_id`) references `user` (`id`) on update cascade;')
12+
}
13+
14+
}

src/migrations/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CreateUserRecoveryCodeTable } from './20211209003017CreateUserRecoveryC
66
import { CascadeDeletePlayerAliasEvents } from './20211221195514CascadeDeletePlayerAliasEvents'
77
import { AddLeaderboardEntryHiddenColumn } from './20211224154919AddLeaderboardEntryHiddenColumn'
88
import { CreateGameSavesTable } from './20220109144435CreateGameSavesTable'
9+
import { CreateGameActivitiesTable } from './20220125220401CreateGameActivitiesTable'
910

1011
export default [
1112
{
@@ -39,5 +40,9 @@ export default [
3940
{
4041
name: 'CreateGameSavesTable',
4142
class: CreateGameSavesTable
43+
},
44+
{
45+
name: 'CreateGameActivitiesTable',
46+
class: CreateGameActivitiesTable
4247
}
4348
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Policy from './policy'
2+
import { ServiceRequest } from 'koa-rest-services'
3+
4+
export default class GameActivitysPolicy extends Policy {
5+
async index(req: ServiceRequest): Promise<boolean> {
6+
const { gameId } = req.query
7+
return await this.canAccessGame(Number(gameId))
8+
}
9+
}

0 commit comments

Comments
 (0)