Skip to content

Commit c013d17

Browse files
committed
API overall history endpoint
1 parent b6ffb46 commit c013d17

File tree

3 files changed

+296
-0
lines changed

3 files changed

+296
-0
lines changed

src/api/routers/commands/stats.router.ts

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,216 @@ statsRouter.openapi(
101101
},
102102
)
103103

104+
statsRouter.openapi(
105+
createRoute({
106+
method: 'get',
107+
path: '/overall-history/{queue_id}',
108+
description: 'Get overall match history for a queue.',
109+
request: {
110+
params: z.object({
111+
queue_id: z
112+
.string()
113+
.regex(/^\d+$/)
114+
.transform(Number)
115+
.openapi({
116+
param: {
117+
name: 'queue_id',
118+
in: 'path',
119+
},
120+
example: '1',
121+
}),
122+
}),
123+
query: z.object({
124+
limit: z
125+
.string()
126+
.regex(/^\d+$/)
127+
.transform(Number)
128+
.optional()
129+
.openapi({
130+
param: {
131+
name: 'limit',
132+
in: 'query',
133+
},
134+
example: '50',
135+
}),
136+
}),
137+
},
138+
responses: {
139+
200: {
140+
content: {
141+
'application/json': {
142+
schema: z.object({
143+
matches: z.array(
144+
z.object({
145+
match_id: z.number(),
146+
winning_team: z.number().nullable(),
147+
deck: z.string().nullable(),
148+
stake: z.string().nullable(),
149+
best_of_3: z.boolean(),
150+
best_of_5: z.boolean(),
151+
created_at: z.string(),
152+
players: z.array(
153+
z.object({
154+
user_id: z.string(),
155+
team: z.number().nullable(),
156+
elo_change: z.number().nullable(),
157+
}),
158+
),
159+
}),
160+
),
161+
}),
162+
},
163+
},
164+
description: 'Overall match history retrieved successfully.',
165+
},
166+
500: {
167+
content: {
168+
'application/json': {
169+
schema: z.object({
170+
error: z.string(),
171+
}),
172+
},
173+
},
174+
description: 'Internal server error.',
175+
},
176+
},
177+
}),
178+
async (c) => {
179+
const { queue_id } = c.req.valid('param')
180+
const { limit } = c.req.valid('query')
181+
const finalLimit = limit || 50
182+
183+
try {
184+
const matches = await COMMAND_HANDLERS.STATS.GET_OVERALL_HISTORY(
185+
queue_id,
186+
finalLimit,
187+
)
188+
189+
return c.json(
190+
{
191+
matches,
192+
},
193+
200,
194+
)
195+
} catch (error) {
196+
console.error('Error fetching overall match history:', error)
197+
return c.json(
198+
{
199+
error: 'Internal server error',
200+
},
201+
500,
202+
)
203+
}
204+
},
205+
)
206+
207+
statsRouter.openapi(
208+
createRoute({
209+
method: 'get',
210+
path: '/history/{user_id}/{queue_id}',
211+
description: 'Get match history for a player in a specific queue.',
212+
request: {
213+
params: z.object({
214+
user_id: z.string().openapi({
215+
param: {
216+
name: 'user_id',
217+
in: 'path',
218+
},
219+
example: '123456789012345678',
220+
}),
221+
queue_id: z
222+
.string()
223+
.regex(/^\d+$/)
224+
.transform(Number)
225+
.openapi({
226+
param: {
227+
name: 'queue_id',
228+
in: 'path',
229+
},
230+
example: '1',
231+
}),
232+
}),
233+
query: z.object({
234+
limit: z
235+
.string()
236+
.regex(/^\d+$/)
237+
.transform(Number)
238+
.optional()
239+
.openapi({
240+
param: {
241+
name: 'limit',
242+
in: 'query',
243+
},
244+
example: '10',
245+
}),
246+
}),
247+
},
248+
responses: {
249+
200: {
250+
content: {
251+
'application/json': {
252+
schema: z.object({
253+
matches: z.array(
254+
z.object({
255+
match_id: z.number(),
256+
won: z.boolean(),
257+
elo_change: z.number().nullable(),
258+
team: z.number().nullable(),
259+
deck: z.string().nullable(),
260+
stake: z.string().nullable(),
261+
best_of_3: z.boolean(),
262+
best_of_5: z.boolean(),
263+
created_at: z.string(),
264+
winning_team: z.number().nullable(),
265+
}),
266+
),
267+
}),
268+
},
269+
},
270+
description: 'Match history retrieved successfully.',
271+
},
272+
500: {
273+
content: {
274+
'application/json': {
275+
schema: z.object({
276+
error: z.string(),
277+
}),
278+
},
279+
},
280+
description: 'Internal server error.',
281+
},
282+
},
283+
}),
284+
async (c) => {
285+
const { user_id, queue_id } = c.req.valid('param')
286+
const { limit } = c.req.valid('query')
287+
const finalLimit = limit || 50
288+
289+
try {
290+
const matches = await COMMAND_HANDLERS.STATS.GET_MATCH_HISTORY(
291+
user_id,
292+
queue_id,
293+
finalLimit,
294+
)
295+
296+
return c.json(
297+
{
298+
matches,
299+
},
300+
200,
301+
)
302+
} catch (error) {
303+
console.error('Error fetching match history:', error)
304+
return c.json(
305+
{
306+
error: 'Internal server error',
307+
},
308+
500,
309+
)
310+
}
311+
},
312+
)
313+
104314
statsRouter.openapi(
105315
createRoute({
106316
method: 'get',
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { pool } from '../../db'
2+
3+
export type OverallHistoryEntry = {
4+
match_id: number
5+
winning_team: number | null
6+
deck: string | null
7+
stake: string | null
8+
best_of_3: boolean
9+
best_of_5: boolean
10+
created_at: string
11+
players: {
12+
user_id: string
13+
team: number | null
14+
elo_change: number | null
15+
}[]
16+
}
17+
18+
/**
19+
* Gets overall match history for a queue.
20+
*
21+
* @param {number} queueId - The queue ID to fetch match history for.
22+
* @param {number} limit - Maximum number of matches to return (default: 50).
23+
* @return {Promise<OverallHistoryEntry[]>} A promise that resolves to an array of match history entries.
24+
*/
25+
export async function getOverallHistory(
26+
queueId: number,
27+
limit: number = 50,
28+
): Promise<OverallHistoryEntry[]> {
29+
try {
30+
// Get overall match history for the queue
31+
const historyRes = await pool.query(
32+
`
33+
SELECT
34+
m.id as match_id,
35+
m.winning_team,
36+
m.deck,
37+
m.stake,
38+
m.best_of_3,
39+
m.best_of_5,
40+
m.created_at
41+
FROM matches m
42+
WHERE m.queue_id = $1 AND m.winning_team IS NOT NULL
43+
ORDER BY m.created_at DESC
44+
LIMIT $2
45+
`,
46+
[queueId, limit],
47+
)
48+
49+
// For each match, get the players
50+
const matches = await Promise.all(
51+
historyRes.rows.map(async (row) => {
52+
const playersRes = await pool.query(
53+
`
54+
SELECT user_id, team, elo_change
55+
FROM match_users
56+
WHERE match_id = $1
57+
ORDER BY team, user_id
58+
`,
59+
[row.match_id],
60+
)
61+
62+
return {
63+
match_id: row.match_id,
64+
winning_team: row.winning_team,
65+
deck: row.deck,
66+
stake: row.stake,
67+
best_of_3: row.best_of_3,
68+
best_of_5: row.best_of_5,
69+
created_at: row.created_at.toISOString(),
70+
players: playersRes.rows.map((player) => ({
71+
user_id: player.user_id,
72+
team: player.team,
73+
elo_change: player.elo_change,
74+
})),
75+
}
76+
}),
77+
)
78+
79+
return matches
80+
} catch (error) {
81+
console.error('Error fetching overall match history:', error)
82+
throw error
83+
}
84+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { getPlayerStats } from './getPlayerStats'
22
import { getMatchHistory } from './getMatchHistory'
33
import { getLeaderboard } from './getLeaderboard'
4+
import { getOverallHistory } from './getOverallHistory'
45

56
export const STATS_COMMAND_HANDLERS = {
67
GET_PLAYER_STATS: getPlayerStats,
78
GET_MATCH_HISTORY: getMatchHistory,
89
GET_LEADERBOARD: getLeaderboard,
10+
GET_OVERALL_HISTORY: getOverallHistory,
911
}

0 commit comments

Comments
 (0)