Skip to content

Commit 1638419

Browse files
authored
Merge pull request #602 from TaloDev/develop
Release 0.87.0
2 parents 2c6353d + 0ebbec4 commit 1638419

32 files changed

+281
-190
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Claude Code Review
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
# Optional: Only run on specific file changes
7+
# paths:
8+
# - "src/**/*.ts"
9+
# - "src/**/*.tsx"
10+
# - "src/**/*.js"
11+
# - "src/**/*.jsx"
12+
13+
jobs:
14+
claude-review:
15+
# Optional: Filter by PR author
16+
# if: |
17+
# github.event.pull_request.user.login == 'external-contributor' ||
18+
# github.event.pull_request.user.login == 'new-developer' ||
19+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20+
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
pull-requests: read
25+
issues: read
26+
id-token: write
27+
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 1
33+
34+
- name: Run Claude Code Review
35+
id: claude-review
36+
uses: anthropics/claude-code-action@v1
37+
with:
38+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39+
prompt: |
40+
Please review this pull request and provide feedback on:
41+
- Code quality and best practices
42+
- Potential bugs or issues
43+
- Performance considerations
44+
- Security concerns
45+
- Test coverage
46+
47+
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
48+
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
49+
50+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
51+
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
52+
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
53+
use_sticky_comment: true

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

src/entities/event.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,58 @@ export default class Event extends ClickHouseEntity<ClickHouseEvent, [string, Ga
3535
createdAt!: Date
3636
updatedAt: Date = new Date()
3737

38+
static async massHydrate(em: EntityManager, data: ClickHouseEvent[], clickhouse: ClickHouseClient, loadProps: boolean = false): Promise<Event[]> {
39+
const playerAliasIds = Array.from(new Set(data.map((event) => event.player_alias_id)))
40+
41+
const playerAliases = await em.getRepository(PlayerAlias).find({
42+
id: {
43+
$in: playerAliasIds
44+
}
45+
}, { populate: ['player'] })
46+
47+
const playerAliasesMap = new Map<number, PlayerAlias>()
48+
playerAliases.forEach((alias) => playerAliasesMap.set(alias.id, alias))
49+
50+
const propsMap = new Map<string, Prop[]>()
51+
if (loadProps) {
52+
const eventIds = data.map((event) => event.id)
53+
if (eventIds.length > 0) {
54+
const props = await clickhouse.query({
55+
query: 'SELECT * FROM event_props WHERE event_id IN ({eventIds:Array(String)})',
56+
query_params: { eventIds },
57+
format: 'JSONEachRow'
58+
}).then((res) => res.json<ClickHouseEventProp>())
59+
60+
props.forEach((prop) => {
61+
if (!propsMap.has(prop.event_id)) {
62+
propsMap.set(prop.event_id, [])
63+
}
64+
propsMap.get(prop.event_id)!.push(new Prop(prop.prop_key, prop.prop_value))
65+
})
66+
}
67+
}
68+
69+
return data.map((eventData) => {
70+
const playerAlias = playerAliasesMap.get(eventData.player_alias_id)
71+
if (!playerAlias) {
72+
return null
73+
}
74+
75+
const event = new Event()
76+
event.construct(eventData.name, playerAlias.player.game)
77+
event.id = eventData.id
78+
event.playerAlias = playerAlias
79+
event.createdAt = new Date(eventData.created_at)
80+
event.updatedAt = new Date(eventData.updated_at)
81+
82+
if (loadProps) {
83+
event.props = propsMap.get(eventData.id) || []
84+
}
85+
86+
return event
87+
}).filter((event) => !!event)
88+
}
89+
3890
construct(name: string, game: Game): this {
3991
this.name = name
4092
this.game = game
@@ -91,7 +143,8 @@ export default class Event extends ClickHouseEntity<ClickHouseEvent, [string, Ga
91143

92144
if (loadProps) {
93145
const props = await clickhouse.query({
94-
query: `SELECT * FROM event_props WHERE event_id = '${data.id}'`,
146+
query: 'SELECT * FROM event_props WHERE event_id = {eventId:String}',
147+
query_params: { eventId: data.id },
95148
format: 'JSONEachRow'
96149
}).then((res) => res.json<ClickHouseEventProp>())
97150

src/entities/organisation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default class Organisation {
1313
@Property()
1414
name!: string
1515

16-
@OneToMany(() => Game, (game) => game.organisation, { eager: true })
16+
@OneToMany(() => Game, (game) => game.organisation)
1717
games = new Collection<Game>(this)
1818

1919
@OneToOne({ orphanRemoval: true, eager: true })

src/entities/player-auth-activity.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export enum PlayerAuthActivityType {
1212
CHANGED_EMAIL,
1313
PASSWORD_RESET_REQUESTED,
1414
PASSWORD_RESET_COMPLETED,
15-
VERFICIATION_TOGGLED,
15+
VERIFICATION_TOGGLED,
1616
CHANGE_PASSWORD_FAILED,
1717
CHANGE_EMAIL_FAILED,
1818
TOGGLE_VERIFICATION_FAILED,
@@ -78,7 +78,7 @@ export default class PlayerAuthActivity {
7878
return `A password reset request was made for ${authAlias.identifier}'s account`
7979
case PlayerAuthActivityType.PASSWORD_RESET_COMPLETED:
8080
return `A password reset was completed for ${authAlias.identifier}'s account`
81-
case PlayerAuthActivityType.VERFICIATION_TOGGLED:
81+
case PlayerAuthActivityType.VERIFICATION_TOGGLED:
8282
return `${authAlias.identifier} toggled verification`
8383
case PlayerAuthActivityType.CHANGE_PASSWORD_FAILED:
8484
return `${authAlias.identifier} failed to change their password`

src/entities/player-auth.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { Entity, EntityManager, OneToOne, PrimaryKey, Property } from '@mikro-orm/mysql'
1+
import { Entity, OneToOne, PrimaryKey, Property } from '@mikro-orm/mysql'
22
import Player from './player'
33
import { v4 } from 'uuid'
4-
import PlayerAlias, { PlayerAliasService } from './player-alias'
4+
import PlayerAlias from './player-alias'
55
import { sign } from '../lib/auth/jwt'
6-
import { getAuthMiddlewareAliasKey, getAuthMiddlewarePlayerKey } from '../middleware/player-auth-middleware'
76

87
const errorCodes = [
98
'INVALID_CREDENTIALS',
@@ -50,35 +49,19 @@ export default class PlayerAuth {
5049
@Property({ onUpdate: () => new Date() })
5150
updatedAt: Date = new Date()
5251

53-
async createSession(em: EntityManager, alias: PlayerAlias): Promise<string> {
52+
async createSession(alias: PlayerAlias): Promise<string> {
5453
this.player.lastSeenAt = new Date()
5554

5655
this.sessionKey = v4()
5756
this.sessionCreatedAt = new Date()
58-
await this.clearAuthMiddlewareKeys(em)
5957

6058
const payload = { playerId: this.player.id, aliasId: alias.id }
6159
return sign(payload, this.sessionKey)
6260
}
6361

64-
async clearSession(em: EntityManager) {
62+
clearSession() {
6563
this.sessionKey = null
6664
this.sessionCreatedAt = null
67-
await this.clearAuthMiddlewareKeys(em)
68-
}
69-
70-
private async clearAuthMiddlewareKeys(em: EntityManager) {
71-
const alias = await em.repo(PlayerAlias).findOne({
72-
service: PlayerAliasService.TALO,
73-
player: this.player
74-
})
75-
76-
const keysToClear: string[] = [
77-
getAuthMiddlewarePlayerKey(this.player.id),
78-
alias ? getAuthMiddlewareAliasKey(alias.id) : null
79-
].filter((key): key is string => key !== null)
80-
81-
await Promise.all(keysToClear.map((key) => em.clearCache(key)))
8265
}
8366

8467
toJSON() {

src/lib/auth/getUserFromToken.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { EntityManager } from '@mikro-orm/mysql'
22
import { Context } from 'koa'
33
import User from '../../entities/user'
4+
import { getResultCacheOptions } from '../perf/getResultCacheOptions'
5+
6+
async function getUserFromToken(ctx: Context) {
7+
const em: EntityManager = ctx.em
48

5-
const getUserFromToken = async (ctx: Context, relations?: string[]): Promise<User> => {
69
// user with email = loaded entity, user with sub = jwt
710
if (ctx.state.user.email) {
8-
const user: User = ctx.state.user
9-
await (<EntityManager>ctx.em).getRepository(User).populate(user, relations as never[])
10-
return user
11+
return ctx.state.user as User
1112
}
1213

1314
const userId: number = ctx.state.user.sub
14-
const user = await (<EntityManager>ctx.em).getRepository(User).findOneOrFail(userId, { populate: relations as never[] })
15+
const user = await em.repo(User).findOneOrFail(
16+
userId,
17+
getResultCacheOptions(`user-from-token-${userId}-${ctx.state.user.iat}`)
18+
)
1519
return user
1620
}
1721

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { EntityManager } from '@mikro-orm/mysql'
22
import Organisation from '../../entities/organisation'
33
import Player from '../../entities/player'
4+
import { getResultCacheOptions } from '../perf/getResultCacheOptions'
45

56
export default async function getBillablePlayerCount(em: EntityManager, organisation: Organisation): Promise<number> {
67
return em.getRepository(Player).count({
78
game: { organisation }
8-
})
9+
}, getResultCacheOptions(`billable-player-count-${organisation.id}`))
910
}

src/middleware/player-auth-middleware.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import { isAPIRoute } from './route-middleware'
33
import { EntityManager } from '@mikro-orm/mysql'
44
import PlayerAlias, { PlayerAliasService } from '../entities/player-alias'
55
import { verify } from '../lib/auth/jwt'
6-
import { getResultCacheOptions } from '../lib/perf/getResultCacheOptions'
7-
8-
export function getAuthMiddlewarePlayerKey(playerId: string) {
9-
return `auth-middleware-player-${playerId}`
10-
}
11-
12-
export function getAuthMiddlewareAliasKey(aliasId: number) {
13-
return `auth-middleware-alias-${aliasId}`
14-
}
156

167
export default async function playerAuthMiddleware(ctx: Context, next: Next): Promise<void> {
178
if (isAPIRoute(ctx) && (ctx.state.currentPlayerId || ctx.state.currentAliasId)) {
@@ -20,18 +11,22 @@ export default async function playerAuthMiddleware(ctx: Context, next: Next): Pr
2011

2112
if (ctx.state.currentPlayerId) {
2213
alias = await em.getRepository(PlayerAlias).findOne({
23-
player: ctx.state.currentPlayerId,
24-
service: PlayerAliasService.TALO
14+
service: PlayerAliasService.TALO,
15+
player: {
16+
id: ctx.state.currentPlayerId,
17+
game: ctx.state.game
18+
}
2519
}, {
26-
...getResultCacheOptions(getAuthMiddlewarePlayerKey(ctx.state.currentPlayerId)),
2720
populate: ['player.auth']
2821
})
2922
} else {
3023
alias = await em.getRepository(PlayerAlias).findOne({
3124
id: ctx.state.currentAliasId,
32-
service: PlayerAliasService.TALO
25+
service: PlayerAliasService.TALO,
26+
player: {
27+
game: ctx.state.game
28+
}
3329
}, {
34-
...getResultCacheOptions(getAuthMiddlewareAliasKey(ctx.state.currentAliasId)),
3530
populate: ['player.auth']
3631
})
3732
}

0 commit comments

Comments
 (0)