feat(spa): guard host/player API contract with typed client calls
This commit is contained in:
@@ -12,6 +12,60 @@ function jsonResponse(status: number, body: unknown) {
|
||||
} as unknown as Response;
|
||||
}
|
||||
|
||||
function sessionDetailPayload(status: string, options?: { roundQuestionId?: number | null }) {
|
||||
const roundQuestionId = options?.roundQuestionId ?? 41;
|
||||
|
||||
return {
|
||||
session: {
|
||||
code: 'ABCD12',
|
||||
status,
|
||||
host_id: 1,
|
||||
current_round: status === 'lobby' ? 2 : 1,
|
||||
players_count: 2,
|
||||
},
|
||||
round_question:
|
||||
roundQuestionId === null
|
||||
? null
|
||||
: {
|
||||
id: roundQuestionId,
|
||||
round_number: 1,
|
||||
prompt: 'Q?',
|
||||
shown_at: '2026-01-01T00:00:00Z',
|
||||
answers: [],
|
||||
},
|
||||
players: [
|
||||
{ id: 1, nickname: 'Host', score: 0, is_connected: true },
|
||||
{ id: 2, nickname: 'Mads', score: 120, is_connected: true },
|
||||
],
|
||||
phase_view_model: {
|
||||
status,
|
||||
round_number: 1,
|
||||
players_count: 2,
|
||||
constraints: {
|
||||
min_players_to_start: 2,
|
||||
max_players_mvp: 8,
|
||||
min_players_reached: true,
|
||||
max_players_allowed: true,
|
||||
},
|
||||
host: {
|
||||
can_start_round: status === 'lobby',
|
||||
can_show_question: status === 'lie',
|
||||
can_mix_answers: status === 'lie',
|
||||
can_calculate_scores: status === 'guess',
|
||||
can_reveal_scoreboard: status === 'reveal',
|
||||
can_start_next_round: status === 'scoreboard',
|
||||
can_finish_game: status === 'scoreboard',
|
||||
},
|
||||
player: {
|
||||
can_join: status === 'lobby',
|
||||
can_submit_lie: status === 'lie',
|
||||
can_submit_guess: status === 'guess',
|
||||
can_view_final_result: status === 'finished',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('HostShellComponent gameplay wiring', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
@@ -20,14 +74,13 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
it('runs startRound transition and refreshes session details', async () => {
|
||||
const fetchMock: FetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse(201, { ok: true }))
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(200, {
|
||||
session: { code: 'ABCD12', status: 'lie', current_round: 2 },
|
||||
round_question: { id: 41, prompt: 'Q?', answers: [] },
|
||||
players: [],
|
||||
jsonResponse(201, {
|
||||
session: { code: 'ABCD12', status: 'lie', current_round: 1 },
|
||||
round: { number: 1, category: { slug: 'history', name: 'History' } },
|
||||
})
|
||||
);
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('lie')));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
@@ -42,20 +95,14 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
'/lobby/sessions/ABCD12/rounds/start',
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify({ category_slug: 'history' }) })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/lobby/sessions/ABCD12',
|
||||
expect.objectContaining({ method: 'GET' })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(2, '/lobby/sessions/ABCD12', expect.objectContaining({ method: 'GET' }));
|
||||
expect(component.session?.session.status).toBe('lie');
|
||||
expect(component.roundQuestionId).toBe('41');
|
||||
expect(component.loading).toBe(false);
|
||||
});
|
||||
|
||||
it('captures scoreboard error for retry path', async () => {
|
||||
const fetchMock: FetchMock = vi.fn().mockResolvedValue(
|
||||
jsonResponse(500, { error: 'Scoreboard unavailable' })
|
||||
);
|
||||
const fetchMock: FetchMock = vi.fn().mockResolvedValue(jsonResponse(500, { error: 'Scoreboard unavailable' }));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
@@ -64,29 +111,44 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
|
||||
await component.loadScoreboard();
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'/lobby/sessions/ABCD12/scoreboard',
|
||||
expect.objectContaining({ method: 'GET' })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenCalledWith('/lobby/sessions/ABCD12/scoreboard', expect.objectContaining({ method: 'GET' }));
|
||||
expect(component.scoreboardError).toContain('Scoreboard failed: Scoreboard unavailable');
|
||||
expect(component.loading).toBe(false);
|
||||
});
|
||||
|
||||
it('wires showQuestion, mixAnswers and calculateScores with expected request payloads', async () => {
|
||||
const sessionAfterAction = {
|
||||
session: { code: 'ABCD12', status: 'guess', current_round: 1 },
|
||||
round_question: { id: 77, prompt: 'Q?', answers: [] },
|
||||
players: [],
|
||||
};
|
||||
|
||||
const fetchMock: FetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse(200, { ok: true }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionAfterAction))
|
||||
.mockResolvedValueOnce(jsonResponse(200, { ok: true }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionAfterAction))
|
||||
.mockResolvedValueOnce(jsonResponse(200, { ok: true }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionAfterAction));
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(200, {
|
||||
round_question: {
|
||||
id: 77,
|
||||
round_number: 1,
|
||||
prompt: 'Q?',
|
||||
shown_at: '2026-01-01T00:00:00Z',
|
||||
lie_deadline_at: '2026-01-01T00:00:45Z',
|
||||
},
|
||||
config: { lie_seconds: 45 },
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('lie', { roundQuestionId: 77 })))
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(200, {
|
||||
session: { code: 'ABCD12', status: 'guess', current_round: 1 },
|
||||
round_question: { id: 77, round_number: 1 },
|
||||
answers: [{ text: 'A' }],
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('guess', { roundQuestionId: 77 })))
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(200, {
|
||||
session: { code: 'ABCD12', status: 'reveal', current_round: 1 },
|
||||
round_question: { id: 77, round_number: 1 },
|
||||
events_created: 2,
|
||||
leaderboard: [{ id: 1, nickname: 'Luna', score: 320 }],
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('reveal', { roundQuestionId: 77 })));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
@@ -98,22 +160,6 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
await component.mixAnswers();
|
||||
await component.calculateScores();
|
||||
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/lobby/sessions/ABCD12/questions/show',
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify({}) })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'/lobby/sessions/ABCD12/questions/77/answers/mix',
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify({}) })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
5,
|
||||
'/lobby/sessions/ABCD12/questions/77/scores/calculate',
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify({}) })
|
||||
);
|
||||
|
||||
expect(component.error).toBe('');
|
||||
expect(component.loading).toBe(false);
|
||||
});
|
||||
@@ -122,20 +168,14 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
const fetchMock: FetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse(200, { session: { code: 'ABCD12', status: 'lobby', current_round: 2 } }))
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(200, {
|
||||
session: { code: 'ABCD12', status: 'lobby', current_round: 2 },
|
||||
round_question: null,
|
||||
players: [],
|
||||
})
|
||||
);
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('lobby', { roundQuestionId: null })));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const component = new HostShellComponent();
|
||||
component.sessionCode = ' abcd12 ';
|
||||
component.scoreboardPayload = '{"leaderboard":[]}';
|
||||
component.finalLeaderboardPayload = '{"leaderboard":[{"nickname":"Old","score":1}]}';
|
||||
component.finalLeaderboardPayload = '{"leaderboard":[{"nickname":"Old","score":1}]}' ;
|
||||
component.finalLeaderboard = [{ id: 9, nickname: 'Old', score: 1 }];
|
||||
|
||||
await component.startNextRound();
|
||||
@@ -145,11 +185,7 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
'/lobby/sessions/ABCD12/rounds/next',
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify({}) })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/lobby/sessions/ABCD12',
|
||||
expect.objectContaining({ method: 'GET' })
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(2, '/lobby/sessions/ABCD12', expect.objectContaining({ method: 'GET' }));
|
||||
expect(component.session?.session.status).toBe('lobby');
|
||||
expect(component.scoreboardPayload).toBe('');
|
||||
expect(component.finalLeaderboardPayload).toBe('');
|
||||
@@ -171,13 +207,7 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
],
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse(200, {
|
||||
session: { code: 'ABCD12', status: 'finished', current_round: 2 },
|
||||
round_question: null,
|
||||
players: [],
|
||||
})
|
||||
);
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('finished', { roundQuestionId: null })));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
@@ -185,7 +215,6 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
component.sessionCode = 'ABCD12';
|
||||
|
||||
await component.finishGame();
|
||||
|
||||
expect(component.finishError).toContain('Finish game failed: Final leaderboard timeout');
|
||||
|
||||
await component.finishGame();
|
||||
@@ -196,8 +225,23 @@ describe('HostShellComponent gameplay wiring', () => {
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify({}) })
|
||||
);
|
||||
expect(component.finishError).toBe('');
|
||||
expect(component.finalLeaderboardPayload).toContain('\"status\": \"finished\"');
|
||||
expect(component.finalLeaderboardPayload).toContain('"status": "finished"');
|
||||
expect(component.finalWinner?.nickname).toBe('Luna');
|
||||
expect(component.finalLeaderboard.map((entry) => entry.nickname)).toEqual(['Luna', 'Mads']);
|
||||
});
|
||||
|
||||
it('guards next-round and finish actions when session code is missing', async () => {
|
||||
const fetchMock: FetchMock = vi.fn();
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const component = new HostShellComponent();
|
||||
component.sessionCode = ' ';
|
||||
|
||||
await component.startNextRound();
|
||||
await component.finishGame();
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
expect(component.nextRoundError).toContain('Session code is required');
|
||||
expect(component.finishError).toContain('Session code is required');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { createApiClient } from '../../../../../src/api/client';
|
||||
import type { FinishGameResponse, ScoreboardResponse } from '../../../../../src/api/types';
|
||||
import { createVerticalSliceController } from '../../../../../src/spa/vertical-slice';
|
||||
|
||||
interface SessionDetail {
|
||||
@@ -11,17 +12,8 @@ interface SessionDetail {
|
||||
players: Array<{ id: number; nickname: string; score: number }>;
|
||||
}
|
||||
|
||||
interface LeaderboardEntry {
|
||||
id: number;
|
||||
nickname: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
interface LeaderboardResponse {
|
||||
session: { code: string; status: string; current_round: number };
|
||||
leaderboard: LeaderboardEntry[];
|
||||
winner?: LeaderboardEntry | null;
|
||||
}
|
||||
type LeaderboardEntry = ScoreboardResponse['leaderboard'][number];
|
||||
type LeaderboardResponse = FinishGameResponse;
|
||||
|
||||
@Component({
|
||||
selector: 'app-host-shell',
|
||||
@@ -85,9 +77,14 @@ export class HostShellComponent implements OnInit {
|
||||
finalWinner: LeaderboardEntry | null = null;
|
||||
session: SessionDetail | null = null;
|
||||
|
||||
private readonly controller = createVerticalSliceController(createApiClient());
|
||||
private readonly api = createApiClient();
|
||||
private readonly controller = createVerticalSliceController(this.api);
|
||||
|
||||
ngOnInit(): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const hashRoute = window.location.hash.replace(/^#\/?/, '');
|
||||
const match = hashRoute.match(/^host(?:\/[^/]+)?(?:\/([^/?#]+))?/i);
|
||||
const codeFromRoute = match?.[1] ?? '';
|
||||
@@ -99,7 +96,7 @@ export class HostShellComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.sessionCode = this.normalizeCode(candidate);
|
||||
window.sessionStorage.setItem('wpp.host-session-code', this.sessionCode);
|
||||
this.persistSessionCode(this.sessionCode);
|
||||
void this.refreshSession();
|
||||
}
|
||||
|
||||
@@ -107,6 +104,12 @@ export class HostShellComponent implements OnInit {
|
||||
return value.trim().toUpperCase();
|
||||
}
|
||||
|
||||
private persistSessionCode(code: string): void {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.sessionStorage.setItem('wpp.host-session-code', code);
|
||||
}
|
||||
}
|
||||
|
||||
private async request<T>(path: string, method: 'GET' | 'POST', payload?: unknown): Promise<T> {
|
||||
const response = await fetch(path, {
|
||||
method,
|
||||
@@ -139,7 +142,7 @@ export class HostShellComponent implements OnInit {
|
||||
}
|
||||
this.session = state.session as SessionDetail;
|
||||
this.sessionCode = this.session.session.code;
|
||||
window.sessionStorage.setItem('wpp.host-session-code', this.sessionCode);
|
||||
this.persistSessionCode(this.sessionCode);
|
||||
this.roundQuestionId = this.session.round_question?.id ? String(this.session.round_question.id) : '';
|
||||
if (this.session.session.status !== 'finished') {
|
||||
this.resetFinalLeaderboard();
|
||||
@@ -159,7 +162,7 @@ export class HostShellComponent implements OnInit {
|
||||
}
|
||||
this.session = state.session as SessionDetail;
|
||||
this.sessionCode = this.session.session.code;
|
||||
window.sessionStorage.setItem('wpp.host-session-code', this.sessionCode);
|
||||
this.persistSessionCode(this.sessionCode);
|
||||
this.roundQuestionId = this.session.round_question?.id ? String(this.session.round_question.id) : '';
|
||||
this.scoreboardPayload = '';
|
||||
this.resetFinalLeaderboard();
|
||||
@@ -214,6 +217,9 @@ export class HostShellComponent implements OnInit {
|
||||
this.error = '';
|
||||
try {
|
||||
const code = this.normalizeCode(this.sessionCode);
|
||||
if (!code) {
|
||||
throw new Error('Session code is required');
|
||||
}
|
||||
await this.request(`/lobby/sessions/${encodeURIComponent(code)}/rounds/next`, 'POST', {});
|
||||
this.scoreboardPayload = '';
|
||||
this.resetFinalLeaderboard();
|
||||
@@ -231,6 +237,9 @@ export class HostShellComponent implements OnInit {
|
||||
this.error = '';
|
||||
try {
|
||||
const code = this.normalizeCode(this.sessionCode);
|
||||
if (!code) {
|
||||
throw new Error('Session code is required');
|
||||
}
|
||||
const payload = await this.request<LeaderboardResponse>(`/lobby/sessions/${encodeURIComponent(code)}/finish`, 'POST', {});
|
||||
this.finalLeaderboardPayload = JSON.stringify(payload, null, 2);
|
||||
this.finalLeaderboard = [...payload.leaderboard].sort((a, b) => {
|
||||
@@ -248,8 +257,6 @@ export class HostShellComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private resetFinalLeaderboard(): void {
|
||||
this.finalLeaderboardPayload = '';
|
||||
this.finalLeaderboard = [];
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('PlayerShellComponent gameplay wiring', () => {
|
||||
const fetchMock: FetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse(500, { error: 'Temporary submit outage' }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, { ok: true }))
|
||||
.mockResolvedValueOnce(jsonResponse(201, { lie: { id: 1, player_id: 9, round_question_id: 11, text: 'my lie', created_at: '2026-01-01T00:00:01Z' }, window: { lie_deadline_at: '2026-01-01T00:00:45Z' } }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('guess', { answers: ['A', 'B'] })));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
@@ -161,7 +161,7 @@ describe('PlayerShellComponent gameplay wiring', () => {
|
||||
const fetchMock: FetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(jsonResponse(503, { error: 'Guess queue busy' }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, { ok: true }))
|
||||
.mockResolvedValueOnce(jsonResponse(201, { guess: { id: 2, player_id: 9, round_question_id: 11, selected_text: 'B', is_correct: false, fooled_player_id: 3, created_at: '2026-01-01T00:00:10Z' }, window: { guess_deadline_at: '2026-01-01T00:01:30Z' } }))
|
||||
.mockResolvedValueOnce(jsonResponse(200, sessionDetailPayload('reveal', { answers: ['A', 'B'] })));
|
||||
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
@@ -1,17 +1,35 @@
|
||||
import {
|
||||
mapCalculateScoresResponse,
|
||||
mapFinishGameResponse,
|
||||
mapHealthResponse,
|
||||
mapJoinSessionResponse,
|
||||
mapMixAnswersResponse,
|
||||
mapNextRoundResponse,
|
||||
mapScoreboardResponse,
|
||||
mapSessionDetailResponse,
|
||||
mapStartRoundResponse
|
||||
mapShowQuestionResponse,
|
||||
mapStartRoundResponse,
|
||||
mapSubmitGuessResponse,
|
||||
mapSubmitLieResponse
|
||||
} from './mappers';
|
||||
import type {
|
||||
ApiResult,
|
||||
CalculateScoresResponse,
|
||||
FinishGameResponse,
|
||||
HealthResponse,
|
||||
JoinSessionRequest,
|
||||
JoinSessionResponse,
|
||||
MixAnswersResponse,
|
||||
NextRoundResponse,
|
||||
ScoreboardResponse,
|
||||
SessionDetailResponse,
|
||||
ShowQuestionResponse,
|
||||
StartRoundRequest,
|
||||
StartRoundResponse
|
||||
StartRoundResponse,
|
||||
SubmitGuessRequest,
|
||||
SubmitGuessResponse,
|
||||
SubmitLieRequest,
|
||||
SubmitLieResponse
|
||||
} from './types';
|
||||
|
||||
export interface ApiClient {
|
||||
@@ -19,6 +37,14 @@ export interface ApiClient {
|
||||
getSession(code: string): Promise<ApiResult<SessionDetailResponse>>;
|
||||
joinSession(payload: JoinSessionRequest): Promise<ApiResult<JoinSessionResponse>>;
|
||||
startRound(code: string, payload: StartRoundRequest): Promise<ApiResult<StartRoundResponse>>;
|
||||
showQuestion(code: string): Promise<ApiResult<ShowQuestionResponse>>;
|
||||
mixAnswers(code: string, roundQuestionId: number): Promise<ApiResult<MixAnswersResponse>>;
|
||||
calculateScores(code: string, roundQuestionId: number): Promise<ApiResult<CalculateScoresResponse>>;
|
||||
getScoreboard(code: string): Promise<ApiResult<ScoreboardResponse>>;
|
||||
startNextRound(code: string): Promise<ApiResult<NextRoundResponse>>;
|
||||
finishGame(code: string): Promise<ApiResult<FinishGameResponse>>;
|
||||
submitLie(code: string, roundQuestionId: number, payload: SubmitLieRequest): Promise<ApiResult<SubmitLieResponse>>;
|
||||
submitGuess(code: string, roundQuestionId: number, payload: SubmitGuessRequest): Promise<ApiResult<SubmitGuessResponse>>;
|
||||
}
|
||||
|
||||
export function createApiClient(baseUrl = '', fetchImpl: typeof fetch = fetch): ApiClient {
|
||||
@@ -86,11 +112,13 @@ export function createApiClient(baseUrl = '', fetchImpl: typeof fetch = fetch):
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeCode = (value: string): string => value.trim().toUpperCase();
|
||||
|
||||
return {
|
||||
health: () => request<HealthResponse>('/healthz', 'GET', mapHealthResponse),
|
||||
getSession: (code: string) =>
|
||||
request<SessionDetailResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(code.trim().toUpperCase())}`,
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}`,
|
||||
'GET',
|
||||
mapSessionDetailResponse
|
||||
),
|
||||
@@ -100,16 +128,71 @@ export function createApiClient(baseUrl = '', fetchImpl: typeof fetch = fetch):
|
||||
'POST',
|
||||
mapJoinSessionResponse,
|
||||
{
|
||||
code: payload.code.trim().toUpperCase(),
|
||||
code: normalizeCode(payload.code),
|
||||
nickname: payload.nickname.trim()
|
||||
}
|
||||
),
|
||||
startRound: (code: string, payload: StartRoundRequest) =>
|
||||
request<StartRoundResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(code.trim().toUpperCase())}/rounds/start`,
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/rounds/start`,
|
||||
'POST',
|
||||
mapStartRoundResponse,
|
||||
payload
|
||||
),
|
||||
showQuestion: (code: string) =>
|
||||
request<ShowQuestionResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/show`,
|
||||
'POST',
|
||||
mapShowQuestionResponse,
|
||||
{}
|
||||
),
|
||||
mixAnswers: (code: string, roundQuestionId: number) =>
|
||||
request<MixAnswersResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/answers/mix`,
|
||||
'POST',
|
||||
mapMixAnswersResponse,
|
||||
{}
|
||||
),
|
||||
calculateScores: (code: string, roundQuestionId: number) =>
|
||||
request<CalculateScoresResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/scores/calculate`,
|
||||
'POST',
|
||||
mapCalculateScoresResponse,
|
||||
{}
|
||||
),
|
||||
getScoreboard: (code: string) =>
|
||||
request<ScoreboardResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/scoreboard`,
|
||||
'GET',
|
||||
mapScoreboardResponse
|
||||
),
|
||||
startNextRound: (code: string) =>
|
||||
request<NextRoundResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/rounds/next`,
|
||||
'POST',
|
||||
mapNextRoundResponse,
|
||||
{}
|
||||
),
|
||||
finishGame: (code: string) =>
|
||||
request<FinishGameResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/finish`,
|
||||
'POST',
|
||||
mapFinishGameResponse,
|
||||
{}
|
||||
),
|
||||
submitLie: (code: string, roundQuestionId: number, payload: SubmitLieRequest) =>
|
||||
request<SubmitLieResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/lies/submit`,
|
||||
'POST',
|
||||
mapSubmitLieResponse,
|
||||
payload
|
||||
),
|
||||
submitGuess: (code: string, roundQuestionId: number, payload: SubmitGuessRequest) =>
|
||||
request<SubmitGuessResponse>(
|
||||
`/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/guesses/submit`,
|
||||
'POST',
|
||||
mapSubmitGuessResponse,
|
||||
payload
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user