feat: migrate production schema from OMW to Kaikki flat vocabulary model

- Replace terms/translations/term_glosses/term_examples with vocabulary_entries
  and entry_translations
- Remove decks, topics and related tables (deferred)
- Add cefr_level and difficulty to entry_translations for game query filtering
- Update termModel.ts for new schema — getDistractors now takes sourceLanguage
- Update gameService.ts and multiplayerGameService.ts for entryId rename
- Update all test fixtures from termId to entryId
- Generate and apply migration 0011
This commit is contained in:
lila 2026-05-05 17:39:25 +02:00
parent 38d8b85228
commit 963bff4eb8
10 changed files with 949 additions and 215 deletions

View file

@ -64,9 +64,14 @@ const validBody = {
};
const fakeTerms = [
{ termId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null },
{ termId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null },
{ termId: "t3", sourceText: "house", targetText: "casa", sourceGloss: null },
{ entryId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null },
{ entryId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null },
{
entryId: "t3",
sourceText: "house",
targetText: "casa",
sourceGloss: "a building for living in",
},
];
beforeEach(() => {
@ -197,7 +202,7 @@ describe("POST /api/v1/game/answer", () => {
expect(body.success).toBe(false);
expect(body.error).toContain("Question already answered");
});
it("returns 400 when a field has an invalid value", async () => {
const res = await request(app)
.post("/api/v1/game/start")

View file

@ -19,10 +19,10 @@ const validRequest: GameRequest = {
};
const fakeTerms = [
{ termId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null },
{ termId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null },
{ entryId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null },
{ entryId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null },
{
termId: "t3",
entryId: "t3",
sourceText: "house",
targetText: "casa",
sourceGloss: "a building for living in",

View file

@ -38,8 +38,9 @@ export const createGameSession = async (
const questions: GameQuestion[] = await Promise.all(
terms.map(async (term) => {
const distractorTexts = await getDistractors(
term.termId,
term.entryId,
term.targetText,
request.source_language,
request.target_language,
request.pos,
request.difficulty,

View file

@ -9,10 +9,10 @@ const mockGetGameTerms = vi.mocked(getGameTerms);
const mockGetDistractors = vi.mocked(getDistractors);
const fakeTerms = [
{ termId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null },
{ termId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null },
{ entryId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null },
{ entryId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null },
{
termId: "t3",
entryId: "t3",
sourceText: "house",
targetText: "casa",
sourceGloss: "a building for living in",

View file

@ -44,8 +44,9 @@ export const generateMultiplayerQuestions = async (): Promise<
const questions: MultiplayerQuestion[] = await Promise.all(
correctAnswers.map(async (correctAnswer) => {
const distractorTexts = await getDistractors(
correctAnswer.termId,
correctAnswer.entryId,
correctAnswer.targetText,
MULTIPLAYER_DEFAULTS.sourceLanguage,
MULTIPLAYER_DEFAULTS.targetLanguage,
MULTIPLAYER_DEFAULTS.pos,
MULTIPLAYER_DEFAULTS.difficulty,