feat(api): assemble full GameSession with shuffled answer options
Extend the game flow from raw term rows to a complete GameSession matching the shared schema: - Add getDistractors model query: fetches N same-POS, same-difficulty, same-target-language terms excluding a given termId. Returns bare strings since distractors only need their display text. - Fix getGameTerms select clause to use the neutral field names (sourceText, targetText, sourceGloss) that the type already declared. - Rename gameService entry point to createGameSession; signature now takes a GameRequest and returns a GameSession. - Per question: fetch 3 distractors, combine with the correct answer, shuffle (Fisher-Yates), assign optionIds 0-3 by post-shuffle index, and assemble into a GameQuestion with a fresh UUID. - Wrap the questions in a GameSession with its own UUID. - Run per-question distractor fetches in parallel via Promise.all. Known gap: the correct option is not yet remembered server-side, so /game/answer cannot evaluate submissions. SessionStore lands next.
This commit is contained in:
parent
0cf6a852b2
commit
f53ac618bb
4 changed files with 95 additions and 20 deletions
|
|
@ -10,4 +10,4 @@ config({
|
|||
|
||||
export const db = drizzle(process.env["DATABASE_URL"]!);
|
||||
|
||||
export { getGameTerms } from "./models/termModel.js";
|
||||
export * from "./models/termModel.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { db } from "@glossa/db";
|
||||
import { eq, and, isNotNull, sql } from "drizzle-orm";
|
||||
import { eq, and, isNotNull, sql, ne } from "drizzle-orm";
|
||||
import { terms, translations, term_glosses } from "@glossa/db/schema";
|
||||
import { alias } from "drizzle-orm/pg-core";
|
||||
|
||||
|
|
@ -33,9 +33,9 @@ export const getGameTerms = async (
|
|||
const rows = await db
|
||||
.select({
|
||||
termId: terms.id,
|
||||
prompt: sourceTranslations.text,
|
||||
answer: targetTranslations.text,
|
||||
gloss: term_glosses.text,
|
||||
sourceText: sourceTranslations.text,
|
||||
targetText: targetTranslations.text,
|
||||
sourceGloss: term_glosses.text,
|
||||
})
|
||||
.from(terms)
|
||||
.innerJoin(
|
||||
|
|
@ -79,3 +79,34 @@ export const getGameTerms = async (
|
|||
|
||||
return rows;
|
||||
};
|
||||
|
||||
export const getDistractors = async (
|
||||
excludeTermId: string,
|
||||
targetLanguage: SupportedLanguageCode,
|
||||
pos: SupportedPos,
|
||||
difficulty: DifficultyLevel,
|
||||
count: number,
|
||||
): Promise<string[]> => {
|
||||
const rows = await db
|
||||
.select({ text: translations.text })
|
||||
.from(terms)
|
||||
.innerJoin(
|
||||
translations,
|
||||
and(
|
||||
eq(translations.term_id, terms.id),
|
||||
eq(translations.language_code, targetLanguage),
|
||||
),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(terms.pos, pos),
|
||||
eq(translations.difficulty, difficulty),
|
||||
ne(terms.id, excludeTermId),
|
||||
),
|
||||
)
|
||||
// TODO(post-mvp): same ORDER BY RANDOM() concern as getGameTerms — see comment there.
|
||||
.orderBy(sql`RANDOM()`)
|
||||
.limit(count);
|
||||
|
||||
return rows.map((row) => row.text);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue