update stream card, update leaderboards on match finished webhook

This commit is contained in:
2025-04-24 00:58:22 +02:00
parent 002fdd4c6d
commit 68540ee136
9 changed files with 362 additions and 63 deletions

View File

@@ -1,6 +1,7 @@
import { discord_router } from '@/server/api/routers/discord'
import { history_router } from '@/server/api/routers/history'
import { leaderboard_router } from '@/server/api/routers/leaderboard'
import { playerStateRouter } from '@/server/api/routers/player-state'
import { createCallerFactory, createTRPCRouter } from '@/server/api/trpc'
/**
@@ -12,6 +13,7 @@ export const appRouter = createTRPCRouter({
history: history_router,
discord: discord_router,
leaderboard: leaderboard_router,
playerState: playerStateRouter,
})
// export type definition of API

View File

@@ -0,0 +1,50 @@
import { createEventIterator, globalEmitter } from '@/lib/events'
import { redis } from '@/server/redis'
import { tracked } from '@trpc/server'
import { z } from 'zod'
import { createTRPCRouter, publicProcedure } from '../trpc'
export type PlayerState = {
status: 'idle' | 'queuing' | 'in_game'
queueStartTime?: number
currentMatch?: {
opponentId: string
startTime: number
}
}
const PLAYER_STATE_KEY = (userId: string) => `player:${userId}:state`
export const playerStateRouter = createTRPCRouter({
getState: publicProcedure
.input(z.string())
.query(async ({ input: userId }) => {
const state = await redis.get(PLAYER_STATE_KEY(userId))
return state ? (JSON.parse(state) as PlayerState) : null
}),
onStateChange: publicProcedure
.input(
z.object({
userId: z.string(),
lastEventId: z.string().optional(),
})
)
.subscription(async function* ({ input, ctx, signal }) {
const iterator = createEventIterator<PlayerState>(
globalEmitter,
`state-change:${input.userId}`,
{ signal: signal }
)
// get initial state
const initialState = await redis.get(PLAYER_STATE_KEY(input.userId))
if (initialState) {
yield tracked('initial', JSON.parse(initialState) as PlayerState)
}
// listen for updates
for await (const [state] of iterator) {
yield tracked(Date.now().toString(), state)
}
}),
})

View File

@@ -2,3 +2,5 @@ import { env } from '@/env'
import { Redis } from 'ioredis'
export const redis = new Redis(env.REDIS_URL)
export const PLAYER_STATE_KEY = (userId: string) => `player:${userId}:state`

View File

@@ -1,5 +1,5 @@
import { redis } from '../redis'
import { neatqueue_service } from './neatqueue.service'
import { type LeaderboardEntry, neatqueue_service } from './neatqueue.service'
export class LeaderboardService {
private getZSetKey(channel_id: string) {
@@ -56,23 +56,19 @@ export class LeaderboardService {
async getUserRank(channel_id: string, user_id: string) {
try {
const zsetKey = this.getZSetKey(channel_id)
const rank = await redis.zrevrank(zsetKey, user_id)
if (rank === null) return null
const userData = await redis.hgetall(this.getUserKey(user_id, channel_id))
if (!userData) return null
return {
rank: rank + 1,
...userData,
mmr: Number(userData.mmr),
streak: userData.streak,
}
} as unknown as LeaderboardEntry
} catch (error) {
console.error('Error getting user rank:', error)
throw error
}
}
}
export const leaderboardService = new LeaderboardService()